Skip to content

Commit ffab0af

Browse files
authored
Merge pull request #139 from SAP/fix_service_instance_deletion
fix[112]: service instance deletion without secret
2 parents 2872e42 + ba04682 commit ffab0af

File tree

3 files changed

+57
-6
lines changed

3 files changed

+57
-6
lines changed

internal/controller/serviceinstance/controller.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,9 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
135135

136136
// Check if the external resource exists
137137
guid := meta.GetExternalName(cr)
138-
r, err := serviceinstance.GetByIDOrSpec(ctx, c.serviceinstance, guid, cr.Spec.ForProvider)
139138

139+
// Normal (non‑deletion) observe path.
140+
r, err := serviceinstance.GetByIDOrSpec(ctx, c.serviceinstance, guid, cr.Spec.ForProvider)
140141
if err != nil {
141142
if clients.ErrorIsNotFound(err) {
142143
return managed.ExternalObservation{ResourceExists: false}, nil
@@ -157,6 +158,13 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
157158
// Update atProvider from the retrieved the service instance
158159
serviceinstance.UpdateObservation(&cr.Status.AtProvider, r)
159160

161+
// If the CR is marked for deletion we stop normal observe logic.
162+
// We report "resource exists" so Crossplane will call Delete() next.
163+
// (Delete() will handle a "not found" case safely, so we don't check again here.)
164+
if meta.WasDeleted(mg) {
165+
return managed.ExternalObservation{ResourceExists: true}, nil
166+
}
167+
160168
switch r.LastOperation.State {
161169
case v1alpha1.LastOperationInitial, v1alpha1.LastOperationInProgress:
162170
// Set the CR to unavailable and signal that the reconciler should not update the resource
@@ -189,17 +197,13 @@ func (c *external) Observe(ctx context.Context, mg resource.Managed) (managed.Ex
189197
return managed.ExternalObservation{ResourceExists: true}, errors.Wrap(err, errGetParameters)
190198
}
191199
cr.Status.AtProvider.Credentials = iSha256(cred)
192-
193200
credentialsUpToDate = jsonContain(cred, desiredCredentials)
194201
} else {
195202
desiredHash := iSha256(desiredCredentials)
196-
197203
credentialsUpToDate = bytes.Equal(desiredHash, cr.Status.AtProvider.Credentials)
198204
}
199-
200205
// Check if the credentials in the spec match the credentials in the external resource
201206
upToDate := credentialsUpToDate && serviceinstance.IsUpToDate(&cr.Spec.ForProvider, r)
202-
203207
return managed.ExternalObservation{ResourceExists: true, ResourceUpToDate: upToDate}, nil
204208
default:
205209
// should never reach here

internal/controller/serviceinstance/controller_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ func withDriftDetection(d bool) modifier {
8383
}
8484
}
8585

86+
func withDeletionTimestamp() modifier {
87+
return func(r *v1alpha1.ServiceInstance) {
88+
ts := metav1.Now()
89+
r.ObjectMeta.DeletionTimestamp = &ts
90+
}
91+
}
92+
8693
func serviceInstance(typ string, m ...modifier) *v1alpha1.ServiceInstance {
8794
r := &v1alpha1.ServiceInstance{
8895
ObjectMeta: metav1.ObjectMeta{
@@ -379,6 +386,46 @@ func TestObserve(t *testing.T) {
379386
return m
380387
},
381388
},
389+
"DeletionFastPath_Exists": {
390+
args: args{
391+
mg: serviceInstance("managed",
392+
withExternalName(guid),
393+
withSpace(spaceGUID),
394+
withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan}),
395+
withDeletionTimestamp(), // triggers meta.WasDeleted short-circuit
396+
),
397+
},
398+
want: want{
399+
mg: serviceInstance("managed",
400+
withExternalName(guid),
401+
withSpace(spaceGUID),
402+
withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan}),
403+
withDeletionTimestamp(),
404+
// Status updated by UpdateObservation before early return
405+
withStatus(v1alpha1.ServiceInstanceObservation{ID: &guid, ServicePlan: &servicePlan}),
406+
),
407+
// Early return only sets ResourceExists: true
408+
obs: managed.ExternalObservation{ResourceExists: true},
409+
err: nil,
410+
},
411+
service: func() *fake.MockServiceInstance {
412+
m := &fake.MockServiceInstance{}
413+
m.On("Get", guid).Return(
414+
// LastOperationFailed + Create would have produced ResourceExists:false in the normal path,
415+
// proving we exited early.
416+
&fake.NewServiceInstance("managed").
417+
SetName(name).
418+
SetGUID(guid).
419+
SetServicePlan(servicePlan).
420+
SetLastOperation(v1alpha1.LastOperationCreate, v1alpha1.LastOperationFailed).
421+
ServiceInstance,
422+
nil,
423+
)
424+
// Fallback shouldn't be called, keep safe default.
425+
m.On("Single").Return(fake.ServiceInstanceNil, fake.ErrNoResultReturned)
426+
return m
427+
},
428+
},
382429
"DriftDetectionLoop": {
383430
args: args{
384431
mg: serviceInstance("managed", withExternalName(guid), withSpace(spaceGUID), withServicePlan(v1alpha1.ServicePlanParameters{ID: &servicePlan}), withParameters("{\"foo\":\"bar\", \"baz\": 1}"), withDriftDetection(true)),

test/e2e/cloudfoundry_services_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func TestCloudFoundryServices(t *testing.T) {
5656
},
5757
)
5858

59-
var steps = [...]string{"space", "service_instance", "ups", "scb_key"}
59+
var steps = [...]string{"space", "service_instance", "ups", "ups_no_credentials", "scb_key"}
6060
for _, name := range steps {
6161
ft, ok := feats[name]
6262
if !ok {

0 commit comments

Comments
 (0)