• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
You are now the owner of this repo.

SAP / sap-btp-service-operator / 20505859000

25 Dec 2025 01:31PM UTC coverage: 78.408% (-0.01%) from 78.422%
20505859000

Pull #592

github

kerenlahav
cosmetics
Pull Request #592: [JIRA: SAPBTPCFS-28335] fips and update libs

4 of 9 new or added lines in 2 files covered. (44.44%)

3 existing lines in 2 files now uncovered.

2738 of 3492 relevant lines covered (78.41%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.58
/controllers/serviceinstance_controller.go
1
/*
2

3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package controllers
18

19
import (
20
        "context"
21
        "encoding/json"
22
        "fmt"
23
        "strings"
24

25
        "github.com/SAP/sap-btp-service-operator/internal/utils/logutils"
26
        "github.com/pkg/errors"
27
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
28

29
        "k8s.io/apimachinery/pkg/types"
30

31
        "sigs.k8s.io/controller-runtime/pkg/predicate"
32

33
        "github.com/SAP/sap-btp-service-operator/api/common"
34
        "github.com/SAP/sap-btp-service-operator/internal/config"
35
        "github.com/SAP/sap-btp-service-operator/internal/utils"
36
        "github.com/go-logr/logr"
37
        "k8s.io/apimachinery/pkg/runtime"
38
        "k8s.io/client-go/tools/record"
39

40
        "k8s.io/client-go/util/workqueue"
41
        "sigs.k8s.io/controller-runtime/pkg/controller"
42

43
        v1 "github.com/SAP/sap-btp-service-operator/api/v1"
44
        "k8s.io/apimachinery/pkg/api/meta"
45

46
        "github.com/google/uuid"
47

48
        "github.com/SAP/sap-btp-service-operator/client/sm"
49
        smClientTypes "github.com/SAP/sap-btp-service-operator/client/sm/types"
50
        apierrors "k8s.io/apimachinery/pkg/api/errors"
51
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
52
        ctrl "sigs.k8s.io/controller-runtime"
53
        "sigs.k8s.io/controller-runtime/pkg/client"
54
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
55
)
56

57
// ServiceInstanceReconciler reconciles a ServiceInstance object
58
type ServiceInstanceReconciler struct {
59
        client.Client
60
        Log         logr.Logger
61
        Scheme      *runtime.Scheme
62
        GetSMClient func(ctx context.Context, serviceInstance *v1.ServiceInstance) (sm.Client, error)
63
        Config      config.Config
64
        Recorder    record.EventRecorder
65
}
66

67
// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=serviceinstances,verbs=get;list;watch;create;update;patch;delete
68
// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=serviceinstances/status,verbs=get;update;patch
69
// +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete
70
// +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;create;update
71

72
func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
1✔
73
        log := r.Log.WithValues("serviceinstance", req.NamespacedName).WithValues("correlation_id", uuid.New().String())
1✔
74
        ctx = context.WithValue(ctx, logutils.LogKey, log)
1✔
75

1✔
76
        serviceInstance := &v1.ServiceInstance{}
1✔
77
        if err := r.Client.Get(ctx, req.NamespacedName, serviceInstance); err != nil {
2✔
78
                if !apierrors.IsNotFound(err) {
1✔
79
                        log.Error(err, "unable to fetch ServiceInstance")
×
80
                }
×
81
                // we'll ignore not-found errors, since they can't be fixed by an immediate
82
                // requeue (we'll need to wait for a new notification), and we can get them
83
                // on deleted requests.
84
                return ctrl.Result{}, client.IgnoreNotFound(err)
1✔
85
        }
86
        serviceInstance = serviceInstance.DeepCopy()
1✔
87

1✔
88
        if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
2✔
89
                return r.deleteInstance(ctx, serviceInstance)
1✔
90
        }
1✔
91
        if len(serviceInstance.GetConditions()) == 0 {
2✔
92
                err := utils.InitConditions(ctx, r.Client, serviceInstance)
1✔
93
                if err != nil {
1✔
94
                        return ctrl.Result{}, err
×
95
                }
×
96
        }
97

98
        if len(serviceInstance.Status.OperationURL) > 0 {
2✔
99
                // ongoing operation - poll status from SM
1✔
100
                return r.poll(ctx, serviceInstance)
1✔
101
        }
1✔
102

103
        if isFinalState(ctx, serviceInstance) {
2✔
104
                if len(serviceInstance.Status.HashedSpec) == 0 {
2✔
105
                        updateHashedSpecValue(serviceInstance)
1✔
106
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
107
                }
1✔
108

109
                return ctrl.Result{}, nil
1✔
110
        }
111

112
        if controllerutil.AddFinalizer(serviceInstance, common.FinalizerName) {
2✔
113
                log.Info(fmt.Sprintf("added finalizer '%s' to service instance", common.FinalizerName))
1✔
114
                if err := r.Client.Update(ctx, serviceInstance); err != nil {
1✔
115
                        return ctrl.Result{}, err
×
116
                }
×
117
        }
118

119
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
120
        if err != nil {
1✔
121
                log.Error(err, "failed to get sm client")
×
122
                return utils.HandleOperationFailure(ctx, r.Client, serviceInstance, common.Unknown, err)
×
123
        }
×
124

125
        if serviceInstance.Status.InstanceID == "" {
2✔
126
                log.Info("Instance ID is empty, checking if instance exist in SM")
1✔
127
                smInstance, err := r.getInstanceForRecovery(ctx, smClient, serviceInstance)
1✔
128
                if err != nil {
1✔
129
                        log.Error(err, "failed to check instance recovery")
×
130
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceInstance, smClientTypes.CREATE, err)
×
131
                }
×
132
                if smInstance != nil {
2✔
133
                        return r.recover(ctx, smClient, serviceInstance, smInstance)
1✔
134
                }
1✔
135

136
                // if instance was not recovered then create new instance
137
                return r.createInstance(ctx, smClient, serviceInstance)
1✔
138
        }
139

140
        if updateRequired(serviceInstance) {
2✔
141
                // If stored hash is MD5 (32 chars) and we're now using SHA256 (64 chars),
1✔
142
                // perform one-time migration by updating the stored hash without triggering update
1✔
143
                if len(serviceInstance.Status.HashedSpec) == 32 {
1✔
NEW
144
                        // This is likely an MD5->SHA256 migration, update the stored hash silently
×
NEW
145
                        // to prevent unnecessary service updates during FIPS migration
×
NEW
146
                        updateHashedSpecValue(serviceInstance)
×
NEW
147
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
×
NEW
148
                }
×
149
                return r.updateInstance(ctx, smClient, serviceInstance)
1✔
150
        }
151

152
        // share/unshare
153
        if shareOrUnshareRequired(serviceInstance) {
2✔
154
                return r.handleInstanceSharing(ctx, serviceInstance, smClient)
1✔
155
        }
1✔
156

157
        log.Info("No action required")
1✔
158
        return ctrl.Result{}, nil
1✔
159
}
160

161
func (r *ServiceInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
162
        return ctrl.NewControllerManagedBy(mgr).
1✔
163
                For(&v1.ServiceInstance{}).
1✔
164
                WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
165
                Complete(r)
1✔
166
}
1✔
167

168
func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
169
        log := logutils.GetLogger(ctx)
1✔
170
        log.Info("Creating instance in SM")
1✔
171
        updateHashedSpecValue(serviceInstance)
1✔
172
        instanceParameters, err := r.buildSMRequestParameters(ctx, serviceInstance)
1✔
173
        if err != nil {
2✔
174
                // if parameters are invalid there is nothing we can do, the user should fix it according to the error message in the condition
1✔
175
                log.Error(err, "failed to parse instance parameters")
1✔
176
                return utils.HandleOperationFailure(ctx, r.Client, serviceInstance, smClientTypes.CREATE, err)
1✔
177
        }
1✔
178

179
        provision, provisionErr := smClient.Provision(&smClientTypes.ServiceInstance{
1✔
180
                Name:          serviceInstance.Spec.ExternalName,
1✔
181
                ServicePlanID: serviceInstance.Spec.ServicePlanID,
1✔
182
                Parameters:    instanceParameters,
1✔
183
                Labels: smClientTypes.Labels{
1✔
184
                        common.NamespaceLabel: []string{serviceInstance.Namespace},
1✔
185
                        common.K8sNameLabel:   []string{serviceInstance.Name},
1✔
186
                        common.ClusterIDLabel: []string{r.Config.ClusterID},
1✔
187
                },
1✔
188
        }, serviceInstance.Spec.ServiceOfferingName, serviceInstance.Spec.ServicePlanName, nil, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo), serviceInstance.Spec.DataCenter)
1✔
189

1✔
190
        if provisionErr != nil {
2✔
191
                log.Error(provisionErr, "failed to create service instance", "serviceOfferingName", serviceInstance.Spec.ServiceOfferingName,
1✔
192
                        "servicePlanName", serviceInstance.Spec.ServicePlanName)
1✔
193
                return utils.HandleServiceManagerError(ctx, r.Client, serviceInstance, smClientTypes.CREATE, provisionErr)
1✔
194
        }
1✔
195

196
        serviceInstance.Status.InstanceID = provision.InstanceID
1✔
197
        serviceInstance.Status.SubaccountID = provision.SubaccountID
1✔
198
        if len(provision.Tags) > 0 {
2✔
199
                tags, err := getTags(provision.Tags)
1✔
200
                if err != nil {
1✔
201
                        log.Error(err, "failed to unmarshal tags")
×
202
                } else {
1✔
203
                        serviceInstance.Status.Tags = tags
1✔
204
                }
1✔
205
        }
206

207
        if provision.Location != "" {
2✔
208
                log.Info("Provision request is in progress (async)")
1✔
209
                serviceInstance.Status.OperationURL = provision.Location
1✔
210
                serviceInstance.Status.OperationType = smClientTypes.CREATE
1✔
211
                utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceInstance, false)
1✔
212

1✔
213
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
214
        }
1✔
215

216
        log.Info(fmt.Sprintf("Instance provisioned successfully, instanceID: %s, subaccountID: %s", serviceInstance.Status.InstanceID,
1✔
217
                serviceInstance.Status.SubaccountID))
1✔
218
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceInstance, false)
1✔
219
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
220
}
221

222
func (r *ServiceInstanceReconciler) updateInstance(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
223
        log := logutils.GetLogger(ctx)
1✔
224
        log.Info(fmt.Sprintf("updating instance %s in SM", serviceInstance.Status.InstanceID))
1✔
225

1✔
226
        instanceParameters, err := r.buildSMRequestParameters(ctx, serviceInstance)
1✔
227
        if err != nil {
2✔
228
                log.Error(err, "failed to parse instance parameters")
1✔
229
                return utils.HandleOperationFailure(ctx, r.Client, serviceInstance, smClientTypes.UPDATE, err)
1✔
230
        }
1✔
231

232
        updateHashedSpecValue(serviceInstance)
1✔
233
        _, operationURL, err := smClient.UpdateInstance(serviceInstance.Status.InstanceID, &smClientTypes.ServiceInstance{
1✔
234
                Name:          serviceInstance.Spec.ExternalName,
1✔
235
                ServicePlanID: serviceInstance.Spec.ServicePlanID,
1✔
236
                Parameters:    instanceParameters,
1✔
237
        }, serviceInstance.Spec.ServiceOfferingName, serviceInstance.Spec.ServicePlanName, nil, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo), serviceInstance.Spec.DataCenter)
1✔
238

1✔
239
        if err != nil {
2✔
240
                log.Error(err, fmt.Sprintf("failed to update service instance with ID %s", serviceInstance.Status.InstanceID))
1✔
241
                return utils.HandleServiceManagerError(ctx, r.Client, serviceInstance, smClientTypes.UPDATE, err)
1✔
242
        }
1✔
243

244
        if operationURL != "" {
2✔
245
                log.Info(fmt.Sprintf("Update request accepted, operation URL: %s", operationURL))
1✔
246
                serviceInstance.Status.OperationURL = operationURL
1✔
247
                serviceInstance.Status.OperationType = smClientTypes.UPDATE
1✔
248
                utils.SetInProgressConditions(ctx, smClientTypes.UPDATE, "", serviceInstance, false)
1✔
249
                serviceInstance.Status.ForceReconcile = false
1✔
250
                if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil {
2✔
251
                        return ctrl.Result{}, err
1✔
252
                }
1✔
253

254
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
255
        }
256
        log.Info("Instance updated successfully")
1✔
257
        utils.SetSuccessConditions(smClientTypes.UPDATE, serviceInstance, false)
1✔
258
        serviceInstance.Status.ForceReconcile = false
1✔
259
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
260
}
261

262
func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
263
        log := logutils.GetLogger(ctx)
1✔
264

1✔
265
        log.Info("deleting instance")
1✔
266
        if controllerutil.ContainsFinalizer(serviceInstance, common.FinalizerName) {
2✔
267
                for key, secretName := range serviceInstance.Labels {
2✔
268
                        if strings.HasPrefix(key, common.InstanceSecretRefLabel) {
2✔
269
                                if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: secretName, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil {
1✔
270
                                        log.Error(err, fmt.Sprintf("failed to unwatch secret %s", secretName))
×
271
                                        return ctrl.Result{}, err
×
272
                                }
×
273
                        }
274
                }
275

276
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
277
                if err != nil {
1✔
278
                        log.Error(err, "failed to get sm client")
×
279
                        return utils.HandleOperationFailure(ctx, r.Client, serviceInstance, smClientTypes.DELETE, err)
×
280
                }
×
281
                if len(serviceInstance.Status.InstanceID) == 0 {
2✔
282
                        log.Info("No instance id found validating instance does not exists in SM before removing finalizer")
1✔
283
                        smInstance, err := r.getInstanceForRecovery(ctx, smClient, serviceInstance)
1✔
284
                        if err != nil {
1✔
285
                                return utils.HandleServiceManagerError(ctx, r.Client, serviceInstance, smClientTypes.DELETE, err)
×
286
                        }
×
287
                        if smInstance != nil {
2✔
288
                                log.Info("instance exists in SM continue with deletion")
1✔
289
                                serviceInstance.Status.InstanceID = smInstance.ID
1✔
290
                                utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceInstance, false)
1✔
291
                                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
292
                        }
1✔
293
                        log.Info("instance does not exists in SM, removing finalizer")
1✔
294
                        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName)
1✔
295
                }
296

297
                if len(serviceInstance.Status.OperationURL) > 0 && serviceInstance.Status.OperationType == smClientTypes.DELETE {
2✔
298
                        // ongoing delete operation - poll status from SM
1✔
299
                        return r.poll(ctx, serviceInstance)
1✔
300
                }
1✔
301

302
                log.Info(fmt.Sprintf("Deleting instance with id %v from SM", serviceInstance.Status.InstanceID))
1✔
303
                operationURL, deprovisionErr := smClient.Deprovision(serviceInstance.Status.InstanceID, nil, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo))
1✔
304
                if deprovisionErr != nil {
2✔
305
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceInstance, smClientTypes.DELETE, deprovisionErr)
1✔
306
                }
1✔
307

308
                if operationURL != "" {
2✔
309
                        log.Info("Deleting instance async")
1✔
310
                        return r.handleAsyncDelete(ctx, serviceInstance, operationURL)
1✔
311
                }
1✔
312

313
                log.Info("Instance was deleted successfully, removing finalizer")
1✔
314
                // remove our finalizer from the list and update it.
1✔
315
                return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName)
1✔
316
        }
317
        return ctrl.Result{}, nil
1✔
318
}
319

320
func (r *ServiceInstanceReconciler) handleInstanceSharing(ctx context.Context, serviceInstance *v1.ServiceInstance, smClient sm.Client) (ctrl.Result, error) {
1✔
321
        log := logutils.GetLogger(ctx)
1✔
322
        log.Info("Handling change in instance sharing")
1✔
323

1✔
324
        if serviceInstance.GetShared() {
2✔
325
                log.Info("Service instance appears to be unshared, sharing the instance")
1✔
326
                err := smClient.ShareInstance(serviceInstance.Status.InstanceID, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo))
1✔
327
                if err != nil {
2✔
328
                        log.Error(err, "failed to share instance")
1✔
329
                        return utils.HandleInstanceSharingError(ctx, r.Client, serviceInstance, metav1.ConditionFalse, common.ShareFailed, err)
1✔
330
                }
1✔
331
                log.Info("instance shared successfully")
1✔
332
                utils.SetSharedCondition(serviceInstance, metav1.ConditionTrue, common.ShareSucceeded, "instance shared successfully")
1✔
333
        } else { //un-share
1✔
334
                log.Info("Service instance appears to be shared, un-sharing the instance")
1✔
335
                err := smClient.UnShareInstance(serviceInstance.Status.InstanceID, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo))
1✔
336
                if err != nil {
2✔
337
                        log.Error(err, "failed to un-share instance")
1✔
338
                        return utils.HandleInstanceSharingError(ctx, r.Client, serviceInstance, metav1.ConditionTrue, common.UnShareFailed, err)
1✔
339
                }
1✔
340
                log.Info("instance un-shared successfully")
1✔
341
                if serviceInstance.Spec.Shared != nil {
2✔
342
                        utils.SetSharedCondition(serviceInstance, metav1.ConditionFalse, common.UnShareSucceeded, "instance un-shared successfully")
1✔
343
                } else {
2✔
344
                        log.Info("removing Shared condition since shared is undefined in instance")
1✔
345
                        conditions := serviceInstance.GetConditions()
1✔
346
                        meta.RemoveStatusCondition(&conditions, common.ConditionShared)
1✔
347
                        serviceInstance.SetConditions(conditions)
1✔
348
                }
1✔
349
        }
350

351
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
352
}
353

354
func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
355
        log := logutils.GetLogger(ctx)
1✔
356
        log.Info(fmt.Sprintf("resource is in progress, found operation url %s", serviceInstance.Status.OperationURL))
1✔
357
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
358
        if err != nil {
1✔
359
                log.Error(err, "failed to get sm client")
×
360
                return utils.HandleOperationFailure(ctx, r.Client, serviceInstance, common.Unknown, err)
×
361
        }
×
362

363
        status, statusErr := smClient.Status(serviceInstance.Status.OperationURL, nil)
1✔
364
        if statusErr != nil {
1✔
365
                log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceInstance.Status.OperationURL)
×
366
                utils.SetInProgressConditions(ctx, serviceInstance.Status.OperationType, string(smClientTypes.INPROGRESS), serviceInstance, false)
×
367
                // if failed to read operation status we cleanup the status to trigger re-sync from SM
×
368
                freshStatus := v1.ServiceInstanceStatus{Conditions: serviceInstance.GetConditions()}
×
369
                if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
×
370
                        freshStatus.InstanceID = serviceInstance.Status.InstanceID
×
371
                }
×
372
                serviceInstance.Status = freshStatus
×
373
                if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil {
×
374
                        log.Error(err, "failed to update status during polling")
×
375
                }
×
376
                return ctrl.Result{}, statusErr
×
377
        }
378

379
        if status == nil {
1✔
380
                log.Error(fmt.Errorf("last operation is nil"), fmt.Sprintf("polling %s returned nil", serviceInstance.Status.OperationURL))
×
381
                return ctrl.Result{}, fmt.Errorf("last operation is nil")
×
382
        }
×
383
        switch status.State {
1✔
384
        case smClientTypes.INPROGRESS:
1✔
385
                fallthrough
1✔
386
        case smClientTypes.PENDING:
1✔
387
                if len(status.Description) > 0 {
1✔
388
                        log.Info(fmt.Sprintf("last operation description is '%s'", status.Description))
×
389
                        utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceInstance, true)
×
390
                        if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil {
×
391
                                log.Error(err, "unable to update ServiceInstance polling description")
×
392
                                return ctrl.Result{}, err
×
393
                        }
×
394
                }
395
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
396
        case smClientTypes.FAILED:
1✔
397
                errMsg := getErrorMsgFromLastOperation(status)
1✔
398
                utils.SetFailureConditions(status.Type, errMsg, serviceInstance, true)
1✔
399
                // in order to delete eventually the object we need return with error
1✔
400
                if serviceInstance.Status.OperationType == smClientTypes.DELETE {
2✔
401
                        serviceInstance.Status.OperationURL = ""
1✔
402
                        serviceInstance.Status.OperationType = ""
1✔
403
                        if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil {
1✔
404
                                return ctrl.Result{}, err
×
405
                        }
×
406
                        return ctrl.Result{}, errors.New(errMsg)
1✔
407
                }
408
        case smClientTypes.SUCCEEDED:
1✔
409
                if serviceInstance.Status.OperationType == smClientTypes.CREATE {
2✔
410
                        smInstance, err := smClient.GetInstanceByID(serviceInstance.Status.InstanceID, nil)
1✔
411
                        if err != nil {
1✔
412
                                log.Error(err, fmt.Sprintf("instance %s succeeded but could not fetch it from SM", serviceInstance.Status.InstanceID))
×
413
                                return ctrl.Result{}, err
×
414
                        }
×
415
                        if len(smInstance.Labels["subaccount_id"]) > 0 {
2✔
416
                                serviceInstance.Status.SubaccountID = smInstance.Labels["subaccount_id"][0]
1✔
417
                        }
1✔
418
                        serviceInstance.Status.Ready = metav1.ConditionTrue
1✔
419
                } else if serviceInstance.Status.OperationType == smClientTypes.DELETE {
2✔
420
                        // delete was successful - remove our finalizer from the list and update it.
1✔
421
                        if err := utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName); err != nil {
1✔
422
                                return ctrl.Result{}, err
×
423
                        }
×
424
                }
425
                utils.SetSuccessConditions(status.Type, serviceInstance, true)
1✔
426
        }
427

428
        serviceInstance.Status.OperationURL = ""
1✔
429
        serviceInstance.Status.OperationType = ""
1✔
430

1✔
431
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
432
}
433

434
func (r *ServiceInstanceReconciler) handleAsyncDelete(ctx context.Context, serviceInstance *v1.ServiceInstance, opURL string) (ctrl.Result, error) {
1✔
435
        serviceInstance.Status.OperationURL = opURL
1✔
436
        serviceInstance.Status.OperationType = smClientTypes.DELETE
1✔
437
        utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceInstance, false)
1✔
438

1✔
439
        if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil {
1✔
440
                return ctrl.Result{}, err
×
441
        }
×
442

443
        return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
444
}
445

446
func (r *ServiceInstanceReconciler) getInstanceForRecovery(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance) (*smClientTypes.ServiceInstance, error) {
1✔
447
        log := logutils.GetLogger(ctx)
1✔
448
        parameters := sm.Parameters{
1✔
449
                FieldQuery: []string{
1✔
450
                        fmt.Sprintf("name eq '%s'", serviceInstance.Spec.ExternalName),
1✔
451
                        fmt.Sprintf("context/clusterid eq '%s'", r.Config.ClusterID),
1✔
452
                        fmt.Sprintf("context/namespace eq '%s'", serviceInstance.Namespace)},
1✔
453
                LabelQuery: []string{
1✔
454
                        fmt.Sprintf("%s eq '%s'", common.K8sNameLabel, serviceInstance.Name)},
1✔
455
                GeneralParams: []string{"attach_last_operations=true"},
1✔
456
        }
1✔
457

1✔
458
        instances, err := smClient.ListInstances(&parameters)
1✔
459
        if err != nil {
1✔
460
                log.Error(err, "failed to list instances in SM")
×
461
                return nil, err
×
462
        }
×
463

464
        if instances != nil && len(instances.ServiceInstances) > 0 {
2✔
465
                return &instances.ServiceInstances[0], nil
1✔
466
        }
1✔
467
        log.Info("instance not found in SM")
1✔
468
        return nil, nil
1✔
469
}
470

471
func (r *ServiceInstanceReconciler) recover(ctx context.Context, smClient sm.Client, k8sInstance *v1.ServiceInstance, smInstance *smClientTypes.ServiceInstance) (ctrl.Result, error) {
1✔
472
        log := logutils.GetLogger(ctx)
1✔
473

1✔
474
        log.Info(fmt.Sprintf("found existing instance in SM with id %s, updating status", smInstance.ID))
1✔
475
        updateHashedSpecValue(k8sInstance)
1✔
476
        if smInstance.Ready {
2✔
477
                k8sInstance.Status.Ready = metav1.ConditionTrue
1✔
478
        }
1✔
479
        if smInstance.Shared {
1✔
480
                utils.SetSharedCondition(k8sInstance, metav1.ConditionTrue, common.ShareSucceeded, "Instance shared successfully")
×
481
        }
×
482
        k8sInstance.Status.InstanceID = smInstance.ID
1✔
483
        k8sInstance.Status.OperationURL = ""
1✔
484
        k8sInstance.Status.OperationType = ""
1✔
485
        tags, err := getOfferingTags(smClient, smInstance.ServicePlanID)
1✔
486
        if err != nil {
2✔
487
                log.Error(err, "could not recover offering tags")
1✔
488
        }
1✔
489
        if len(tags) > 0 {
1✔
490
                k8sInstance.Status.Tags = tags
×
491
        }
×
492

493
        instanceState := smClientTypes.SUCCEEDED
1✔
494
        operationType := smClientTypes.CREATE
1✔
495
        description := ""
1✔
496
        if smInstance.LastOperation != nil {
2✔
497
                instanceState = smInstance.LastOperation.State
1✔
498
                operationType = smInstance.LastOperation.Type
1✔
499
                description = smInstance.LastOperation.Description
1✔
500
        } else if !smInstance.Ready {
3✔
501
                instanceState = smClientTypes.FAILED
1✔
502
        }
1✔
503

504
        switch instanceState {
1✔
505
        case smClientTypes.PENDING:
1✔
506
                fallthrough
1✔
507
        case smClientTypes.INPROGRESS:
1✔
508
                k8sInstance.Status.OperationURL = sm.BuildOperationURL(smInstance.LastOperation.ID, smInstance.ID, smClientTypes.ServiceInstancesURL)
1✔
509
                k8sInstance.Status.OperationType = smInstance.LastOperation.Type
1✔
510
                utils.SetInProgressConditions(ctx, smInstance.LastOperation.Type, smInstance.LastOperation.Description, k8sInstance, false)
1✔
511
        case smClientTypes.SUCCEEDED:
1✔
512
                utils.SetSuccessConditions(operationType, k8sInstance, false)
1✔
513
        case smClientTypes.FAILED:
1✔
514
                utils.SetFailureConditions(operationType, description, k8sInstance, false)
1✔
515
        }
516

517
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, k8sInstance)
1✔
518
}
519

520
func (r *ServiceInstanceReconciler) buildSMRequestParameters(ctx context.Context, serviceInstance *v1.ServiceInstance) ([]byte, error) {
1✔
521
        log := logutils.GetLogger(ctx)
1✔
522
        instanceParameters, paramSecrets, err := utils.BuildSMRequestParameters(serviceInstance.Namespace, serviceInstance.Spec.Parameters, serviceInstance.Spec.ParametersFrom)
1✔
523
        if err != nil {
2✔
524
                log.Error(err, "failed to build instance parameters")
1✔
525
                return nil, err
1✔
526
        }
1✔
527
        instanceLabelsChanged := false
1✔
528
        newInstanceLabels := make(map[string]string)
1✔
529
        if serviceInstance.IsSubscribedToParamSecretsChanges() {
2✔
530
                // find all new secrets on the instance
1✔
531
                for _, secret := range paramSecrets {
2✔
532
                        labelKey := utils.GetLabelKeyForInstanceSecret(secret.Name)
1✔
533
                        newInstanceLabels[labelKey] = secret.Name
1✔
534
                        if _, ok := serviceInstance.Labels[labelKey]; !ok {
2✔
535
                                instanceLabelsChanged = true
1✔
536
                        }
1✔
537

538
                        if err := utils.AddWatchForSecretIfNeeded(ctx, r.Client, secret, string(serviceInstance.UID)); err != nil {
1✔
539
                                log.Error(err, fmt.Sprintf("failed to mark secret for watch %s", secret.Name))
×
540
                                return nil, err
×
541
                        }
×
542

543
                }
544
        }
545

546
        //sync instance labels
547
        for labelKey, labelValue := range serviceInstance.Labels {
2✔
548
                if strings.HasPrefix(labelKey, common.InstanceSecretRefLabel) {
2✔
549
                        if _, ok := newInstanceLabels[labelKey]; !ok {
2✔
550
                                log.Info(fmt.Sprintf("params secret named %s was removed, unwatching it", labelValue))
1✔
551
                                instanceLabelsChanged = true
1✔
552
                                if err := utils.RemoveWatchForSecret(ctx, r.Client, types.NamespacedName{Name: labelValue, Namespace: serviceInstance.Namespace}, string(serviceInstance.UID)); err != nil {
1✔
553
                                        log.Error(err, fmt.Sprintf("failed to unwatch secret %s", labelValue))
×
554
                                        return nil, err
×
555
                                }
×
556
                        }
557
                } else {
×
558
                        // this label not related to secrets, add it
×
559
                        newInstanceLabels[labelKey] = labelValue
×
560
                }
×
561
        }
562
        if instanceLabelsChanged {
2✔
563
                serviceInstance.Labels = newInstanceLabels
1✔
564
                log.Info("updating instance with secret labels")
1✔
565
                return instanceParameters, r.Client.Update(ctx, serviceInstance)
1✔
566
        }
1✔
567

568
        return instanceParameters, nil
1✔
569
}
570

571
func isFinalState(ctx context.Context, serviceInstance *v1.ServiceInstance) bool {
1✔
572
        log := logutils.GetLogger(ctx)
1✔
573

1✔
574
        if serviceInstance.Status.ForceReconcile {
2✔
575
                log.Info("instance is not in final state, ForceReconcile is true")
1✔
576
                return false
1✔
577
        }
1✔
578

579
        observedGen := common.GetObservedGeneration(serviceInstance)
1✔
580
        if serviceInstance.Generation != observedGen {
2✔
581
                log.Info(fmt.Sprintf("instance is not in final state, generation: %d, observedGen: %d", serviceInstance.Generation, observedGen))
1✔
582
                return false
1✔
583
        }
1✔
584

585
        if utils.ShouldRetryOperation(serviceInstance) {
2✔
586
                log.Info("instance is not in final state, last operation failed, retrying")
1✔
587
                return false
1✔
588
        }
1✔
589

590
        if shareOrUnshareRequired(serviceInstance) {
2✔
591
                log.Info("instance is not in final state, need to sync sharing status")
1✔
592
                if len(serviceInstance.Status.HashedSpec) == 0 {
1✔
593
                        updateHashedSpecValue(serviceInstance)
×
594
                }
×
595
                return false
1✔
596
        }
597

598
        log.Info(fmt.Sprintf("instance is in final state (generation: %d)", serviceInstance.Generation))
1✔
599
        return true
1✔
600
}
601

602
func updateRequired(serviceInstance *v1.ServiceInstance) bool {
1✔
603
        //update is not supported for failed instances (this can occur when instance creation was asynchronously)
1✔
604
        if serviceInstance.Status.Ready != metav1.ConditionTrue {
1✔
605
                return false
×
606
        }
×
607

608
        if serviceInstance.Status.ForceReconcile {
2✔
609
                return true
1✔
610
        }
1✔
611

612
        cond := meta.FindStatusCondition(serviceInstance.Status.Conditions, common.ConditionSucceeded)
1✔
613
        if cond != nil && cond.Reason == common.UpdateInProgress { //in case of transient error occurred
1✔
614
                return true
×
615
        }
×
616

617
        return serviceInstance.GetSpecHash() != serviceInstance.Status.HashedSpec
1✔
618
}
619

620
func shareOrUnshareRequired(serviceInstance *v1.ServiceInstance) bool {
1✔
621
        //relevant only for non-shared instances - sharing instance is possible only for usable instances
1✔
622
        if serviceInstance.Status.Ready != metav1.ConditionTrue {
2✔
623
                return false
1✔
624
        }
1✔
625

626
        sharedCondition := meta.FindStatusCondition(serviceInstance.GetConditions(), common.ConditionShared)
1✔
627
        if sharedCondition == nil {
2✔
628
                return serviceInstance.GetShared()
1✔
629
        }
1✔
630

631
        if sharedCondition.Reason == common.ShareNotSupported {
2✔
632
                return false
1✔
633
        }
1✔
634

635
        if sharedCondition.Status == metav1.ConditionFalse {
2✔
636
                // instance does not appear to be shared, should share it if shared is requested
1✔
637
                return serviceInstance.GetShared()
1✔
638
        }
1✔
639

640
        // instance appears to be shared, should unshare it if shared is not requested
641
        return !serviceInstance.GetShared()
1✔
642
}
643

644
func getOfferingTags(smClient sm.Client, planID string) ([]string, error) {
1✔
645
        planQuery := &sm.Parameters{
1✔
646
                FieldQuery: []string{fmt.Sprintf("id eq '%s'", planID)},
1✔
647
        }
1✔
648
        plans, err := smClient.ListPlans(planQuery)
1✔
649
        if err != nil {
1✔
650
                return nil, err
×
651
        }
×
652

653
        if plans == nil || len(plans.ServicePlans) != 1 {
2✔
654
                return nil, fmt.Errorf("could not find plan with id %s", planID)
1✔
655
        }
1✔
656

657
        offeringQuery := &sm.Parameters{
×
658
                FieldQuery: []string{fmt.Sprintf("id eq '%s'", plans.ServicePlans[0].ServiceOfferingID)},
×
659
        }
×
660

×
661
        offerings, err := smClient.ListOfferings(offeringQuery)
×
662
        if err != nil {
×
663
                return nil, err
×
664
        }
×
665
        if offerings == nil || len(offerings.ServiceOfferings) != 1 {
×
666
                return nil, fmt.Errorf("could not find offering with id %s", plans.ServicePlans[0].ServiceOfferingID)
×
667
        }
×
668

669
        var tags []string
×
670
        if err := json.Unmarshal(offerings.ServiceOfferings[0].Tags, &tags); err != nil {
×
671
                return nil, err
×
672
        }
×
673
        return tags, nil
×
674
}
675

676
func getTags(tags []byte) ([]string, error) {
1✔
677
        var tagsArr []string
1✔
678
        if err := json.Unmarshal(tags, &tagsArr); err != nil {
1✔
679
                return nil, err
×
680
        }
×
681
        return tagsArr, nil
1✔
682
}
683

684
func updateHashedSpecValue(serviceInstance *v1.ServiceInstance) {
1✔
685
        serviceInstance.Status.HashedSpec = serviceInstance.GetSpecHash()
1✔
686
}
1✔
687

688
func getErrorMsgFromLastOperation(status *smClientTypes.Operation) string {
1✔
689
        errMsg := "async operation error"
1✔
690
        if status == nil || len(status.Errors) == 0 {
1✔
691
                return errMsg
×
692
        }
×
693
        var errMap map[string]interface{}
1✔
694

1✔
695
        if err := json.Unmarshal(status.Errors, &errMap); err != nil {
1✔
696
                return errMsg
×
697
        }
×
698

699
        if description, found := errMap["description"]; found {
2✔
700
                if descStr, ok := description.(string); ok {
2✔
701
                        errMsg = descStr
1✔
702
                }
1✔
703
        }
704
        return errMsg
1✔
705
}
706

707
type SecretPredicate struct {
708
        predicate.Funcs
709
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc