• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SAP / sap-btp-service-operator / 8276096664

14 Mar 2024 05:48AM UTC coverage: 79.386% (-0.03%) from 79.419%
8276096664

push

github

web-flow
Bump google.golang.org/protobuf from 1.31.0 to 1.33.0 (#407)

Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

2403 of 3027 relevant lines covered (79.39%)

0.89 hits per line

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

81.55
/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
        "crypto/md5"
22
        "encoding/hex"
23
        "encoding/json"
24
        "fmt"
25
        "net/http"
26

27
        "github.com/SAP/sap-btp-service-operator/api/common"
28
        "github.com/SAP/sap-btp-service-operator/internal/config"
29
        "github.com/SAP/sap-btp-service-operator/internal/utils"
30
        "github.com/go-logr/logr"
31
        "k8s.io/apimachinery/pkg/runtime"
32
        "k8s.io/client-go/tools/record"
33

34
        "k8s.io/client-go/util/workqueue"
35
        "sigs.k8s.io/controller-runtime/pkg/controller"
36

37
        servicesv1 "github.com/SAP/sap-btp-service-operator/api/v1"
38
        "k8s.io/apimachinery/pkg/api/meta"
39
        "k8s.io/utils/pointer"
40

41
        "github.com/google/uuid"
42

43
        "github.com/SAP/sap-btp-service-operator/client/sm"
44
        smClientTypes "github.com/SAP/sap-btp-service-operator/client/sm/types"
45
        apierrors "k8s.io/apimachinery/pkg/api/errors"
46
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
47
        ctrl "sigs.k8s.io/controller-runtime"
48
        "sigs.k8s.io/controller-runtime/pkg/client"
49
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
50
)
51

52
// ServiceInstanceReconciler reconciles a ServiceInstance object
53
type ServiceInstanceReconciler struct {
54
        client.Client
55
        Log            logr.Logger
56
        Scheme         *runtime.Scheme
57
        GetSMClient    func(ctx context.Context, secretResolver *utils.SecretResolver, resourceNamespace, btpAccessSecretName string) (sm.Client, error)
58
        Config         config.Config
59
        SecretResolver *utils.SecretResolver
60
        Recorder       record.EventRecorder
61
}
62

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

68
func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
1✔
69
        log := r.Log.WithValues("serviceinstance", req.NamespacedName).WithValues("correlation_id", uuid.New().String())
1✔
70
        ctx = context.WithValue(ctx, utils.LogKey{}, log)
1✔
71

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

1✔
84
        if len(serviceInstance.GetConditions()) == 0 {
2✔
85
                err := utils.InitConditions(ctx, r.Client, serviceInstance)
1✔
86
                if err != nil {
1✔
87
                        return ctrl.Result{}, err
×
88
                }
×
89
        }
90

91
        if isFinalState(ctx, serviceInstance) {
2✔
92
                if len(serviceInstance.Status.HashedSpec) == 0 {
2✔
93
                        updateHashedSpecValue(serviceInstance)
1✔
94
                        err := r.Client.Status().Update(ctx, serviceInstance)
1✔
95
                        if err != nil {
1✔
96
                                return ctrl.Result{}, err
×
97
                        }
×
98
                }
99

100
                return ctrl.Result{}, nil
1✔
101
        }
102

103
        if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
2✔
104
                // delete updates the generation
1✔
105
                serviceInstance.SetObservedGeneration(serviceInstance.Generation)
1✔
106
                return r.deleteInstance(ctx, serviceInstance)
1✔
107
        }
1✔
108

109
        if len(serviceInstance.Status.OperationURL) > 0 {
2✔
110
                // ongoing operation - poll status from SM
1✔
111
                return r.poll(ctx, serviceInstance)
1✔
112
        }
1✔
113

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

122
        log.Info(fmt.Sprintf("instance is not in final state, handling... (generation: %d, observedGen: %d", serviceInstance.Generation, serviceInstance.Status.ObservedGeneration))
1✔
123
        serviceInstance.SetObservedGeneration(serviceInstance.Generation)
1✔
124

1✔
125
        smClient, err := r.GetSMClient(ctx, r.SecretResolver, serviceInstance.Namespace, serviceInstance.Spec.BTPAccessCredentialsSecret)
1✔
126
        if err != nil {
1✔
127
                log.Error(err, "failed to get sm client")
×
128
                return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceInstance)
×
129
        }
×
130

131
        if serviceInstance.Status.InstanceID == "" {
2✔
132
                log.Info("Instance ID is empty, checking if instance exist in SM")
1✔
133
                smInstance, err := r.getInstanceForRecovery(ctx, smClient, serviceInstance)
1✔
134
                if err != nil {
1✔
135
                        log.Error(err, "failed to check instance recovery")
×
136
                        return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceInstance)
×
137
                }
×
138
                if smInstance != nil {
2✔
139
                        return r.recover(ctx, smClient, serviceInstance, smInstance)
1✔
140
                }
1✔
141

142
                // if instance was not recovered then create new instance
143
                return r.createInstance(ctx, smClient, serviceInstance)
1✔
144
        }
145

146
        // Update
147
        if updateRequired(serviceInstance) {
2✔
148
                if res, err := r.updateInstance(ctx, smClient, serviceInstance); err != nil {
2✔
149
                        log.Info("got error while trying to update instance")
1✔
150
                        return ctrl.Result{}, err
1✔
151
                } else if res.Requeue {
3✔
152
                        return res, nil
1✔
153
                }
1✔
154
        }
155

156
        // Handle instance share if needed
157
        if sharingUpdateRequired(serviceInstance) {
2✔
158
                return r.handleInstanceSharing(ctx, serviceInstance, smClient)
1✔
159
        }
1✔
160

161
        return ctrl.Result{}, nil
1✔
162
}
163

164
func (r *ServiceInstanceReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
165
        return ctrl.NewControllerManagedBy(mgr).
1✔
166
                For(&servicesv1.ServiceInstance{}).
1✔
167
                WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
168
                Complete(r)
1✔
169
}
1✔
170

171
func (r *ServiceInstanceReconciler) createInstance(ctx context.Context, smClient sm.Client, serviceInstance *servicesv1.ServiceInstance) (ctrl.Result, error) {
1✔
172
        log := utils.GetLogger(ctx)
1✔
173
        log.Info("Creating instance in SM")
1✔
174
        updateHashedSpecValue(serviceInstance)
1✔
175
        _, instanceParameters, err := utils.BuildSMRequestParameters(r.Client, serviceInstance.Namespace, serviceInstance.Spec.ParametersFrom, serviceInstance.Spec.Parameters)
1✔
176
        if err != nil {
1✔
177
                // if parameters are invalid there is nothing we can do, the user should fix it according to the error message in the condition
×
178
                log.Error(err, "failed to parse instance parameters")
×
179
                return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, err.Error(), serviceInstance)
×
180
        }
×
181

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

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

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

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

1✔
216
                return ctrl.Result{Requeue: true, RequeueAfter: r.Config.PollInterval}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
217
        }
1✔
218

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

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

1✔
229
        updateHashedSpecValue(serviceInstance)
1✔
230

1✔
231
        _, instanceParameters, err := utils.BuildSMRequestParameters(r.Client, serviceInstance.Namespace, serviceInstance.Spec.ParametersFrom, serviceInstance.Spec.Parameters)
1✔
232
        if err != nil {
1✔
233
                log.Error(err, "failed to parse instance parameters")
×
234
                return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.UPDATE, fmt.Sprintf("failed to parse parameters: %v", err.Error()), serviceInstance)
×
235
        }
×
236

237
        _, operationURL, err := smClient.UpdateInstance(serviceInstance.Status.InstanceID, &smClientTypes.ServiceInstance{
1✔
238
                Name:          serviceInstance.Spec.ExternalName,
1✔
239
                ServicePlanID: serviceInstance.Spec.ServicePlanID,
1✔
240
                Parameters:    instanceParameters,
1✔
241
        }, serviceInstance.Spec.ServiceOfferingName, serviceInstance.Spec.ServicePlanName, nil, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo), serviceInstance.Spec.DataCenter)
1✔
242

1✔
243
        if err != nil {
2✔
244
                log.Error(err, fmt.Sprintf("failed to update service instance with ID %s", serviceInstance.Status.InstanceID))
1✔
245
                return utils.HandleError(ctx, r.Client, smClientTypes.UPDATE, err, serviceInstance, true)
1✔
246
        }
1✔
247

248
        if operationURL != "" {
2✔
249
                log.Info(fmt.Sprintf("Update request accepted, operation URL: %s", operationURL))
1✔
250
                serviceInstance.Status.OperationURL = operationURL
1✔
251
                serviceInstance.Status.OperationType = smClientTypes.UPDATE
1✔
252
                utils.SetInProgressConditions(ctx, smClientTypes.UPDATE, "", serviceInstance)
1✔
253

1✔
254
                if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil {
1✔
255
                        return ctrl.Result{}, err
×
256
                }
×
257

258
                return ctrl.Result{Requeue: true, RequeueAfter: r.Config.PollInterval}, nil
1✔
259
        }
260
        log.Info("Instance updated successfully")
1✔
261
        utils.SetSuccessConditions(smClientTypes.UPDATE, serviceInstance)
1✔
262
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
263
}
264

265
func (r *ServiceInstanceReconciler) deleteInstance(ctx context.Context, serviceInstance *servicesv1.ServiceInstance) (ctrl.Result, error) {
1✔
266
        log := utils.GetLogger(ctx)
1✔
267

1✔
268
        if controllerutil.ContainsFinalizer(serviceInstance, common.FinalizerName) {
2✔
269
                smClient, err := r.GetSMClient(ctx, r.SecretResolver, serviceInstance.Namespace, serviceInstance.Spec.BTPAccessCredentialsSecret)
1✔
270
                if err != nil {
1✔
271
                        log.Error(err, "failed to get sm client")
×
272
                        return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceInstance)
×
273
                }
×
274
                if len(serviceInstance.Status.InstanceID) == 0 {
2✔
275
                        log.Info("No instance id found validating instance does not exists in SM before removing finalizer")
1✔
276
                        smInstance, err := r.getInstanceForRecovery(ctx, smClient, serviceInstance)
1✔
277
                        if err != nil {
1✔
278
                                return ctrl.Result{}, err
×
279
                        }
×
280
                        if smInstance != nil {
2✔
281
                                log.Info("instance exists in SM continue with deletion")
1✔
282
                                serviceInstance.Status.InstanceID = smInstance.ID
1✔
283
                                utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceInstance)
1✔
284
                                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
285
                        }
1✔
286
                        log.Info("instance does not exists in SM, removing finalizer")
1✔
287
                        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName)
1✔
288
                }
289

290
                if len(serviceInstance.Status.OperationURL) > 0 && serviceInstance.Status.OperationType == smClientTypes.DELETE {
2✔
291
                        // ongoing delete operation - poll status from SM
1✔
292
                        return r.poll(ctx, serviceInstance)
1✔
293
                }
1✔
294

295
                log.Info(fmt.Sprintf("Deleting instance with id %v from SM", serviceInstance.Status.InstanceID))
1✔
296
                operationURL, deprovisionErr := smClient.Deprovision(serviceInstance.Status.InstanceID, nil, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo))
1✔
297
                if deprovisionErr != nil {
2✔
298
                        // delete will proceed anyway
1✔
299
                        return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.DELETE, deprovisionErr.Error(), serviceInstance)
1✔
300
                }
1✔
301

302
                if operationURL != "" {
2✔
303
                        log.Info("Deleting instance async")
1✔
304
                        return r.handleAsyncDelete(ctx, serviceInstance, operationURL)
1✔
305
                }
1✔
306

307
                log.Info("Instance was deleted successfully, removing finalizer")
1✔
308
                // remove our finalizer from the list and update it.
1✔
309
                return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceInstance, common.FinalizerName)
1✔
310
        }
311
        return ctrl.Result{}, nil
×
312
}
313

314
func (r *ServiceInstanceReconciler) handleInstanceSharing(ctx context.Context, serviceInstance *servicesv1.ServiceInstance, smClient sm.Client) (ctrl.Result, error) {
1✔
315
        log := utils.GetLogger(ctx)
1✔
316
        log.Info("Handling change in instance sharing")
1✔
317

1✔
318
        if serviceInstance.ShouldBeShared() {
2✔
319
                log.Info("Service instance is shouldBeShared, sharing the instance")
1✔
320
                err := smClient.ShareInstance(serviceInstance.Status.InstanceID, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo))
1✔
321
                if err != nil {
2✔
322
                        log.Error(err, "failed to share instance")
1✔
323
                        return r.handleInstanceSharingError(ctx, serviceInstance, metav1.ConditionFalse, common.ShareFailed, err)
1✔
324
                }
1✔
325
                log.Info("instance shared successfully")
1✔
326
                setSharedCondition(serviceInstance, metav1.ConditionTrue, common.ShareSucceeded, "instance shared successfully")
1✔
327
        } else { //un-share
1✔
328
                log.Info("Service instance is un-shouldBeShared, un-sharing the instance")
1✔
329
                err := smClient.UnShareInstance(serviceInstance.Status.InstanceID, utils.BuildUserInfo(ctx, serviceInstance.Spec.UserInfo))
1✔
330
                if err != nil {
2✔
331
                        log.Error(err, "failed to un-share instance")
1✔
332
                        return r.handleInstanceSharingError(ctx, serviceInstance, metav1.ConditionTrue, common.UnShareFailed, err)
1✔
333
                }
1✔
334
                log.Info("instance un-shared successfully")
1✔
335
                if serviceInstance.Spec.Shared != nil {
2✔
336
                        setSharedCondition(serviceInstance, metav1.ConditionFalse, common.UnShareSucceeded, "instance un-shared successfully")
1✔
337
                } else {
2✔
338
                        log.Info("removing Shared condition since shared is undefined in instance")
1✔
339
                        conditions := serviceInstance.GetConditions()
1✔
340
                        meta.RemoveStatusCondition(&conditions, common.ConditionShared)
1✔
341
                        serviceInstance.SetConditions(conditions)
1✔
342
                }
1✔
343
        }
344

345
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
346
}
347

348
func (r *ServiceInstanceReconciler) poll(ctx context.Context, serviceInstance *servicesv1.ServiceInstance) (ctrl.Result, error) {
1✔
349
        log := utils.GetLogger(ctx)
1✔
350
        log.Info(fmt.Sprintf("resource is in progress, found operation url %s", serviceInstance.Status.OperationURL))
1✔
351
        smClient, err := r.GetSMClient(ctx, r.SecretResolver, serviceInstance.Namespace, serviceInstance.Spec.BTPAccessCredentialsSecret)
1✔
352
        if err != nil {
1✔
353
                log.Error(err, "failed to get sm client")
×
354
                return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceInstance)
×
355
        }
×
356

357
        status, statusErr := smClient.Status(serviceInstance.Status.OperationURL, nil)
1✔
358
        if statusErr != nil {
1✔
359
                log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceInstance.Status.OperationURL)
×
360
                utils.SetInProgressConditions(ctx, serviceInstance.Status.OperationType, statusErr.Error(), serviceInstance)
×
361
                // if failed to read operation status we cleanup the status to trigger re-sync from SM
×
362
                freshStatus := servicesv1.ServiceInstanceStatus{Conditions: serviceInstance.GetConditions(), ObservedGeneration: serviceInstance.Generation}
×
363
                if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
×
364
                        freshStatus.InstanceID = serviceInstance.Status.InstanceID
×
365
                }
×
366
                serviceInstance.Status = freshStatus
×
367
                if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil {
×
368
                        log.Error(err, "failed to update status during polling")
×
369
                }
×
370
                return ctrl.Result{}, statusErr
×
371
        }
372

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

422
        serviceInstance.Status.OperationURL = ""
1✔
423
        serviceInstance.Status.OperationType = ""
1✔
424

1✔
425
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceInstance)
1✔
426
}
427

428
func (r *ServiceInstanceReconciler) handleAsyncDelete(ctx context.Context, serviceInstance *servicesv1.ServiceInstance, opURL string) (ctrl.Result, error) {
1✔
429
        serviceInstance.Status.OperationURL = opURL
1✔
430
        serviceInstance.Status.OperationType = smClientTypes.DELETE
1✔
431
        utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceInstance)
1✔
432

1✔
433
        if err := utils.UpdateStatus(ctx, r.Client, serviceInstance); err != nil {
1✔
434
                return ctrl.Result{}, err
×
435
        }
×
436

437
        return ctrl.Result{Requeue: true, RequeueAfter: r.Config.PollInterval}, nil
1✔
438
}
439

440
func (r *ServiceInstanceReconciler) getInstanceForRecovery(ctx context.Context, smClient sm.Client, serviceInstance *servicesv1.ServiceInstance) (*smClientTypes.ServiceInstance, error) {
1✔
441
        log := utils.GetLogger(ctx)
1✔
442
        parameters := sm.Parameters{
1✔
443
                FieldQuery: []string{
1✔
444
                        fmt.Sprintf("name eq '%s'", serviceInstance.Spec.ExternalName),
1✔
445
                        fmt.Sprintf("context/clusterid eq '%s'", r.Config.ClusterID),
1✔
446
                        fmt.Sprintf("context/namespace eq '%s'", serviceInstance.Namespace)},
1✔
447
                LabelQuery: []string{
1✔
448
                        fmt.Sprintf("%s eq '%s'", common.K8sNameLabel, serviceInstance.Name)},
1✔
449
                GeneralParams: []string{"attach_last_operations=true"},
1✔
450
        }
1✔
451

1✔
452
        instances, err := smClient.ListInstances(&parameters)
1✔
453
        if err != nil {
1✔
454
                log.Error(err, "failed to list instances in SM")
×
455
                return nil, err
×
456
        }
×
457

458
        if instances != nil && len(instances.ServiceInstances) > 0 {
2✔
459
                return &instances.ServiceInstances[0], nil
1✔
460
        }
1✔
461
        log.Info("instance not found in SM")
1✔
462
        return nil, nil
1✔
463
}
464

465
func (r *ServiceInstanceReconciler) recover(ctx context.Context, smClient sm.Client, k8sInstance *servicesv1.ServiceInstance, smInstance *smClientTypes.ServiceInstance) (ctrl.Result, error) {
1✔
466
        log := utils.GetLogger(ctx)
1✔
467

1✔
468
        log.Info(fmt.Sprintf("found existing instance in SM with id %s, updating status", smInstance.ID))
1✔
469
        updateHashedSpecValue(k8sInstance)
1✔
470
        // set observed generation to 0 because we dont know which generation the current state in SM represents,
1✔
471
        // unless the generation is 1 and SM is in the same state as operator
1✔
472
        if k8sInstance.Generation == 1 {
2✔
473
                k8sInstance.SetObservedGeneration(1)
1✔
474
        } else {
1✔
475
                k8sInstance.SetObservedGeneration(0)
×
476
        }
×
477

478
        if smInstance.Ready {
2✔
479
                k8sInstance.Status.Ready = metav1.ConditionTrue
1✔
480
        }
1✔
481
        if smInstance.Shared {
1✔
482
                setSharedCondition(k8sInstance, metav1.ConditionTrue, common.ShareSucceeded, "Instance shared successfully")
×
483
        }
×
484
        k8sInstance.Status.InstanceID = smInstance.ID
1✔
485
        k8sInstance.Status.OperationURL = ""
1✔
486
        k8sInstance.Status.OperationType = ""
1✔
487
        tags, err := getOfferingTags(smClient, smInstance.ServicePlanID)
1✔
488
        if err != nil {
2✔
489
                log.Error(err, "could not recover offering tags")
1✔
490
        }
1✔
491
        if len(tags) > 0 {
1✔
492
                k8sInstance.Status.Tags = tags
×
493
        }
×
494

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

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

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

522
func (r *ServiceInstanceReconciler) handleInstanceSharingError(ctx context.Context, object common.SAPBTPResource, status metav1.ConditionStatus, reason string, err error) (ctrl.Result, error) {
1✔
523
        log := utils.GetLogger(ctx)
1✔
524

1✔
525
        errMsg := err.Error()
1✔
526
        isTransient := false
1✔
527

1✔
528
        if smError, ok := err.(*sm.ServiceManagerError); ok {
2✔
529
                log.Info(fmt.Sprintf("SM returned error with status code %d", smError.StatusCode))
1✔
530
                isTransient = utils.IsTransientError(smError, log)
1✔
531
                errMsg = smError.Error()
1✔
532

1✔
533
                if smError.StatusCode == http.StatusTooManyRequests {
2✔
534
                        errMsg = "in progress"
1✔
535
                        reason = common.InProgress
1✔
536
                } else if reason == common.ShareFailed &&
2✔
537
                        (smError.StatusCode == http.StatusBadRequest || smError.StatusCode == http.StatusInternalServerError) {
2✔
538
                        /* non-transient error may occur only when sharing
1✔
539
                           SM return 400 when plan is not sharable
1✔
540
                           SM returns 500 when TOGGLES_ENABLE_INSTANCE_SHARE_FROM_OPERATOR feature toggle is off */
1✔
541
                        reason = common.ShareNotSupported
1✔
542
                }
1✔
543
        }
544

545
        setSharedCondition(object, status, reason, errMsg)
1✔
546
        return ctrl.Result{Requeue: isTransient}, utils.UpdateStatus(ctx, r.Client, object)
1✔
547
}
548

549
func isFinalState(ctx context.Context, serviceInstance *servicesv1.ServiceInstance) bool {
1✔
550
        log := utils.GetLogger(ctx)
1✔
551
        if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
2✔
552
                log.Info("instance is not in final state, it is marked for deletion")
1✔
553
                return false
1✔
554
        }
1✔
555
        if len(serviceInstance.Status.OperationURL) > 0 {
2✔
556
                log.Info(fmt.Sprintf("instance is not in final state, async operation is in progress (%s)", serviceInstance.Status.OperationURL))
1✔
557
                return false
1✔
558
        }
1✔
559
        if serviceInstance.Generation != serviceInstance.GetObservedGeneration() {
2✔
560
                log.Info(fmt.Sprintf("instance is not in final state, generation: %d, observedGen: %d", serviceInstance.Generation, serviceInstance.GetObservedGeneration()))
1✔
561
                return false
1✔
562
        }
1✔
563

564
        // succeeded=false for current generation, and without failed=true --> transient error retry
565
        if utils.IsInProgress(serviceInstance) {
2✔
566
                log.Info("instance is not in final state, sync operation is in progress")
1✔
567
                return false
1✔
568
        }
1✔
569

570
        if sharingUpdateRequired(serviceInstance) {
2✔
571
                log.Info("instance is not in final state, need to sync sharing status")
1✔
572
                if len(serviceInstance.Status.HashedSpec) == 0 {
1✔
573
                        updateHashedSpecValue(serviceInstance)
×
574
                }
×
575
                return false
1✔
576
        }
577

578
        log.Info(fmt.Sprintf("instance is in final state (generation: %d)", serviceInstance.Generation))
1✔
579
        return true
1✔
580
}
581

582
func updateRequired(serviceInstance *servicesv1.ServiceInstance) bool {
1✔
583
        //update is not supported for failed instances (this can occur when instance creation was asynchronously)
1✔
584
        if serviceInstance.Status.Ready != metav1.ConditionTrue {
1✔
585
                return false
×
586
        }
×
587

588
        cond := meta.FindStatusCondition(serviceInstance.Status.Conditions, common.ConditionSucceeded)
1✔
589
        if cond != nil && cond.Reason == common.UpdateInProgress { //in case of transient error occurred
2✔
590
                return true
1✔
591
        }
1✔
592

593
        return getSpecHash(serviceInstance) != serviceInstance.Status.HashedSpec
1✔
594
}
595

596
func sharingUpdateRequired(serviceInstance *servicesv1.ServiceInstance) bool {
1✔
597
        //relevant only for non-shared instances - sharing instance is possible only for usable instances
1✔
598
        if serviceInstance.Status.Ready != metav1.ConditionTrue {
2✔
599
                return false
1✔
600
        }
1✔
601

602
        sharedCondition := meta.FindStatusCondition(serviceInstance.GetConditions(), common.ConditionShared)
1✔
603
        shouldBeShared := serviceInstance.ShouldBeShared()
1✔
604

1✔
605
        if sharedCondition == nil {
2✔
606
                return shouldBeShared
1✔
607
        }
1✔
608

609
        if sharedCondition.Reason == common.ShareNotSupported {
2✔
610
                return false
1✔
611
        }
1✔
612

613
        if sharedCondition.Reason == common.InProgress || sharedCondition.Reason == common.ShareFailed || sharedCondition.Reason == common.UnShareFailed {
2✔
614
                return true
1✔
615
        }
1✔
616

617
        if shouldBeShared {
2✔
618
                return sharedCondition.Status == metav1.ConditionFalse
1✔
619
        }
1✔
620

621
        return sharedCondition.Status == metav1.ConditionTrue
1✔
622
}
623

624
func getOfferingTags(smClient sm.Client, planID string) ([]string, error) {
1✔
625
        planQuery := &sm.Parameters{
1✔
626
                FieldQuery: []string{fmt.Sprintf("id eq '%s'", planID)},
1✔
627
        }
1✔
628
        plans, err := smClient.ListPlans(planQuery)
1✔
629
        if err != nil {
1✔
630
                return nil, err
×
631
        }
×
632

633
        if plans == nil || len(plans.ServicePlans) != 1 {
2✔
634
                return nil, fmt.Errorf("could not find plan with id %s", planID)
1✔
635
        }
1✔
636

637
        offeringQuery := &sm.Parameters{
×
638
                FieldQuery: []string{fmt.Sprintf("id eq '%s'", plans.ServicePlans[0].ServiceOfferingID)},
×
639
        }
×
640

×
641
        offerings, err := smClient.ListOfferings(offeringQuery)
×
642
        if err != nil {
×
643
                return nil, err
×
644
        }
×
645
        if offerings == nil || len(offerings.ServiceOfferings) != 1 {
×
646
                return nil, fmt.Errorf("could not find offering with id %s", plans.ServicePlans[0].ServiceOfferingID)
×
647
        }
×
648

649
        var tags []string
×
650
        if err := json.Unmarshal(offerings.ServiceOfferings[0].Tags, &tags); err != nil {
×
651
                return nil, err
×
652
        }
×
653
        return tags, nil
×
654
}
655

656
func getTags(tags []byte) ([]string, error) {
1✔
657
        var tagsArr []string
1✔
658
        if err := json.Unmarshal(tags, &tagsArr); err != nil {
1✔
659
                return nil, err
×
660
        }
×
661
        return tagsArr, nil
1✔
662
}
663

664
func getSpecHash(serviceInstance *servicesv1.ServiceInstance) string {
1✔
665
        spec := serviceInstance.Spec
1✔
666
        spec.Shared = pointer.Bool(false)
1✔
667
        specBytes, _ := json.Marshal(spec)
1✔
668
        s := string(specBytes)
1✔
669
        return generateEncodedMD5Hash(s)
1✔
670
}
1✔
671

672
func generateEncodedMD5Hash(str string) string {
1✔
673
        hash := md5.Sum([]byte(str))
1✔
674
        return hex.EncodeToString(hash[:])
1✔
675
}
1✔
676

677
func setSharedCondition(object common.SAPBTPResource, status metav1.ConditionStatus, reason, msg string) {
1✔
678
        conditions := object.GetConditions()
1✔
679
        // align all conditions to latest generation
1✔
680
        for _, cond := range object.GetConditions() {
2✔
681
                if cond.Type != common.ConditionShared {
2✔
682
                        cond.ObservedGeneration = object.GetGeneration()
1✔
683
                        meta.SetStatusCondition(&conditions, cond)
1✔
684
                }
1✔
685
        }
686

687
        shareCondition := metav1.Condition{
1✔
688
                Type:    common.ConditionShared,
1✔
689
                Status:  status,
1✔
690
                Reason:  reason,
1✔
691
                Message: msg,
1✔
692
                // shared condition does not contain observed generation
1✔
693
        }
1✔
694

1✔
695
        // remove shared condition and add it as new (in case it has observed generation)
1✔
696
        meta.RemoveStatusCondition(&conditions, common.ConditionShared)
1✔
697
        meta.SetStatusCondition(&conditions, shareCondition)
1✔
698

1✔
699
        object.SetConditions(conditions)
1✔
700
}
701

702
func updateHashedSpecValue(serviceInstance *servicesv1.ServiceInstance) {
1✔
703
        serviceInstance.Status.HashedSpec = getSpecHash(serviceInstance)
1✔
704
}
1✔
705

706
func getErrorMsgFromLastOperation(status *smClientTypes.Operation) string {
1✔
707
        errMsg := "async operation error"
1✔
708
        if status == nil || len(status.Errors) == 0 {
1✔
709
                return errMsg
×
710
        }
×
711
        var errMap map[string]interface{}
1✔
712

1✔
713
        if err := json.Unmarshal(status.Errors, &errMap); err != nil {
1✔
714
                return errMsg
×
715
        }
×
716

717
        if description, found := errMap["description"]; found {
2✔
718
                if descStr, ok := description.(string); ok {
2✔
719
                        errMsg = descStr
1✔
720
                }
1✔
721
        }
722
        return errMsg
1✔
723
}
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