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

SAP / sap-btp-service-operator / 21268045196

22 Jan 2026 11:04PM UTC coverage: 78.303% (-0.04%) from 78.346%
21268045196

Pull #599

github

kerenlahav
fix
Pull Request #599: Async operation failure retry

48 of 72 new or added lines in 4 files covered. (66.67%)

9 existing lines in 4 files now uncovered.

2797 of 3572 relevant lines covered (78.3%)

0.88 hits per line

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

82.82
/controllers/servicebinding_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
        "net/http"
23
        "strings"
24
        "time"
25

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

30
        "github.com/pkg/errors"
31

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

39
        "fmt"
40

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

44
        v1 "github.com/SAP/sap-btp-service-operator/api/v1"
45

46
        "k8s.io/apimachinery/pkg/api/meta"
47
        "k8s.io/apimachinery/pkg/runtime/schema"
48

49
        "github.com/google/uuid"
50

51
        "github.com/SAP/sap-btp-service-operator/client/sm"
52

53
        smClientTypes "github.com/SAP/sap-btp-service-operator/client/sm/types"
54

55
        corev1 "k8s.io/api/core/v1"
56
        apierrors "k8s.io/apimachinery/pkg/api/errors"
57
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
58
        "k8s.io/apimachinery/pkg/types"
59
        ctrl "sigs.k8s.io/controller-runtime"
60
        "sigs.k8s.io/controller-runtime/pkg/client"
61
        "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
62
)
63

64
const (
65
        secretNameTakenErrorFormat    = "the specified secret name '%s' is already taken. Choose another name and try again"
66
        secretAlreadyOwnedErrorFormat = "secret %s belongs to another binding %s, choose a different name"
67
)
68

69
// ServiceBindingReconciler reconciles a ServiceBinding object
70
type ServiceBindingReconciler struct {
71
        client.Client
72
        Log         logr.Logger
73
        Scheme      *runtime.Scheme
74
        GetSMClient func(ctx context.Context, instance *v1.ServiceInstance) (sm.Client, error)
75
        Config      config.Config
76
        Recorder    record.EventRecorder
77
}
78

79
// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=servicebindings,verbs=get;list;watch;create;update;patch;delete
80
// +kubebuilder:rbac:groups=services.cloud.sap.com,resources=servicebindings/status,verbs=get;update;patch
81
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete
82
// +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete
83
// +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;create;update
84

85
func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
1✔
86
        log := r.Log.WithValues("servicebinding", req.NamespacedName).WithValues("correlation_id", uuid.New().String(), req.Name, req.Namespace)
1✔
87
        ctx = context.WithValue(ctx, logutils.LogKey, log)
1✔
88

1✔
89
        serviceBinding := &v1.ServiceBinding{}
1✔
90
        if err := r.Client.Get(ctx, req.NamespacedName, serviceBinding); err != nil {
2✔
91
                if !apierrors.IsNotFound(err) {
1✔
92
                        log.Error(err, "unable to fetch ServiceBinding")
×
93
                }
×
94
                return ctrl.Result{}, client.IgnoreNotFound(err)
1✔
95
        }
96
        serviceBinding = serviceBinding.DeepCopy()
1✔
97
        log.Info(fmt.Sprintf("Current generation is %v and observed is %v", serviceBinding.Generation, common.GetObservedGeneration(serviceBinding)))
1✔
98

1✔
99
        if len(serviceBinding.GetConditions()) == 0 {
2✔
100
                if err := utils.InitConditions(ctx, r.Client, serviceBinding); err != nil {
1✔
101
                        return ctrl.Result{}, err
×
102
                }
×
103
        }
104

105
        serviceInstance, instanceErr := r.getServiceInstanceForBinding(ctx, serviceBinding)
1✔
106
        if instanceErr != nil {
2✔
107
                if !apierrors.IsNotFound(instanceErr) {
1✔
108
                        log.Error(instanceErr, "failed to get service instance for binding")
×
109
                        return ctrl.Result{}, instanceErr
×
110
                } else if !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
111
                        //instance is not found and binding is not marked for deletion
1✔
112
                        instanceNamespace := serviceBinding.Namespace
1✔
113
                        if len(serviceBinding.Spec.ServiceInstanceNamespace) > 0 {
1✔
114
                                instanceNamespace = serviceBinding.Spec.ServiceInstanceNamespace
×
115
                        }
×
116
                        errMsg := fmt.Sprintf("couldn't find the service instance '%s' in namespace '%s'", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1✔
117
                        utils.SetBlockedCondition(ctx, errMsg, serviceBinding)
1✔
118
                        if updateErr := utils.UpdateStatus(ctx, r.Client, serviceBinding); updateErr != nil {
2✔
119
                                return ctrl.Result{}, updateErr
1✔
120
                        }
1✔
121
                        return ctrl.Result{}, instanceErr
1✔
122
                }
123
        }
124

125
        if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
126
                return r.delete(ctx, serviceBinding, serviceInstance)
1✔
127
        }
1✔
128

129
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
130
        if err != nil {
1✔
131
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
132
        }
×
133
        if len(serviceBinding.Status.BindingID) > 0 {
2✔
134
                if bindingExist, err := isBindingExistInSM(smClient, serviceInstance, serviceBinding.Status.BindingID, log); err != nil {
1✔
135
                        log.Error(err, "failed to check if binding exist in sm due to unknown error")
×
136
                        return ctrl.Result{}, err
×
137
                } else if !bindingExist {
2✔
138
                        log.Info("binding not found in SM, updating status")
1✔
139
                        condition := metav1.Condition{
1✔
140
                                Type:               common.ConditionReady,
1✔
141
                                Status:             metav1.ConditionFalse,
1✔
142
                                ObservedGeneration: serviceBinding.Generation,
1✔
143
                                LastTransitionTime: metav1.NewTime(time.Now()),
1✔
144
                                Reason:             common.ResourceNotFound,
1✔
145
                                Message:            fmt.Sprintf(common.ResourceNotFoundMessageFormat, "binding", serviceBinding.Status.BindingID),
1✔
146
                        }
1✔
147
                        serviceBinding.Status.Conditions = []metav1.Condition{condition}
1✔
148
                        serviceBinding.Status.Ready = metav1.ConditionFalse
1✔
149
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
150
                }
1✔
151
        }
152

153
        if controllerutil.AddFinalizer(serviceBinding, common.FinalizerName) {
2✔
154
                log.Info(fmt.Sprintf("added finalizer '%s' to service binding", common.FinalizerName))
1✔
155
                if err := r.Client.Update(ctx, serviceBinding); err != nil {
1✔
156
                        return ctrl.Result{}, err
×
157
                }
×
158
        }
159

160
        if len(serviceBinding.Status.OperationURL) > 0 {
2✔
161
                // ongoing operation - poll status from SM
1✔
162
                return r.poll(ctx, serviceBinding, serviceInstance)
1✔
163
        }
1✔
164

165
        if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
2✔
166
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is marked for deletion, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
167
                utils.SetBlockedCondition(ctx, "instance is in deletion process", serviceBinding)
1✔
168
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
169
        }
1✔
170

171
        if !serviceInstanceReady(serviceInstance) {
2✔
172
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is not ready, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
173
                utils.SetBlockedCondition(ctx, "service instance is not ready", serviceBinding)
1✔
174
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
NEW
175
                        return ctrl.Result{}, err
×
NEW
176
                }
×
177
                return ctrl.Result{}, errors.New("ServiceInstance is not ready")
1✔
178
        }
179

180
        // should rotate creds
181
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionCredRotationInProgress) {
2✔
182
                log.Info("rotating credentials")
1✔
183
                if shouldUpdateStatus, err := r.rotateCredentials(ctx, serviceBinding, serviceInstance); err != nil {
2✔
184
                        if !shouldUpdateStatus {
2✔
185
                                log.Error(err, "internal error occurred during cred rotation, requeuing binding")
1✔
186
                                return ctrl.Result{}, err
1✔
187
                        }
1✔
188
                        return utils.HandleCredRotationError(ctx, r.Client, serviceBinding, err)
×
189
                }
190
        }
191

192
        // is binding ready
193
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionReady) {
2✔
194
                if isStaleServiceBinding(serviceBinding) {
2✔
195
                        log.Info("binding is stale, handling")
1✔
196
                        return r.handleStaleServiceBinding(ctx, serviceBinding)
1✔
197
                }
1✔
198

199
                if initCredRotationIfRequired(serviceBinding) {
2✔
200
                        log.Info("cred rotation required, updating status")
1✔
201
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
202
                }
1✔
203

204
                log.Info("binding in final state, maintaining secret")
1✔
205
                return r.maintain(ctx, serviceBinding, serviceInstance)
1✔
206
        }
207

208
        if serviceBinding.Status.BindingID == "" {
2✔
209
                if err := r.validateSecretNameIsAvailable(ctx, serviceBinding); err != nil {
2✔
210
                        log.Error(err, "secret validation failed")
1✔
211
                        utils.SetBlockedCondition(ctx, err.Error(), serviceBinding)
1✔
212
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
213
                }
1✔
214

215
                smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
216
                if err != nil {
1✔
217
                        log.Error(err, "failed to check binding recovery")
×
218
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
219
                }
×
220
                if smBinding != nil {
2✔
221
                        return r.recover(ctx, serviceBinding, smBinding)
1✔
222
                }
1✔
223

224
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
225
        }
226

227
        //binding exists in sm but not ready
228
        return r.handleFailedBinding(ctx, serviceBinding, smClient)
1✔
229
}
230

231
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
232

1✔
233
        return ctrl.NewControllerManagedBy(mgr).
1✔
234
                For(&v1.ServiceBinding{}).
1✔
235
                WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
236
                Complete(r)
1✔
237
}
1✔
238

239
func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
240
        log := logutils.GetLogger(ctx)
1✔
241
        log.Info("Creating smBinding in SM")
1✔
242
        serviceBinding.Status.InstanceID = serviceInstance.Status.InstanceID
1✔
243
        bindingParameters, _, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.Parameters, serviceBinding.Spec.ParametersFrom)
1✔
244
        if err != nil {
1✔
245
                log.Error(err, "failed to parse smBinding parameters")
×
246
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
247
        }
×
248

249
        smBinding, operationURL, bindErr := smClient.Bind(&smClientTypes.ServiceBinding{
1✔
250
                Name: serviceBinding.Spec.ExternalName,
1✔
251
                Labels: smClientTypes.Labels{
1✔
252
                        common.NamespaceLabel: []string{serviceBinding.Namespace},
1✔
253
                        common.K8sNameLabel:   []string{serviceBinding.Name},
1✔
254
                        common.ClusterIDLabel: []string{r.Config.ClusterID},
1✔
255
                },
1✔
256
                ServiceInstanceID: serviceInstance.Status.InstanceID,
1✔
257
                Parameters:        bindingParameters,
1✔
258
        }, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
259

1✔
260
        if bindErr != nil {
2✔
261
                log.Error(err, "failed to create service binding", "serviceInstanceID", serviceInstance.Status.InstanceID)
1✔
262
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, bindErr)
1✔
263
        }
1✔
264

265
        if operationURL != "" {
2✔
266
                var bindingID string
1✔
267
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
268
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
×
269
                }
×
270
                serviceBinding.Status.BindingID = bindingID
1✔
271

1✔
272
                log.Info("Create smBinding request is async")
1✔
273
                serviceBinding.Status.OperationURL = operationURL
1✔
274
                serviceBinding.Status.OperationType = smClientTypes.CREATE
1✔
275
                utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceBinding, false)
1✔
276
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
2✔
277
                        log.Error(err, "unable to update ServiceBinding status")
1✔
278
                        return ctrl.Result{}, err
1✔
279
                }
1✔
280
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
281
        }
282

283
        log.Info("Binding created successfully")
1✔
284

1✔
285
        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
286
                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
1✔
287
        }
1✔
288

289
        subaccountID := ""
1✔
290
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
291
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
292
        }
×
293

294
        serviceBinding.Status.BindingID = smBinding.ID
1✔
295
        serviceBinding.Status.SubaccountID = subaccountID
1✔
296
        serviceBinding.Status.Ready = metav1.ConditionTrue
1✔
297
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false)
1✔
298
        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
299

1✔
300
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
301
}
302

303
func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
304
        log := logutils.GetLogger(ctx)
1✔
305
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
306
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
307
                if err != nil {
1✔
308
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
309
                }
×
310

311
                if len(serviceBinding.Status.BindingID) == 0 {
2✔
312
                        log.Info("No binding id found validating binding does not exists in SM before removing finalizer")
1✔
313
                        smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
314
                        if err != nil {
1✔
315
                                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
316
                        }
×
317
                        if smBinding != nil {
2✔
318
                                log.Info("binding exists in SM continue with deletion")
1✔
319
                                serviceBinding.Status.BindingID = smBinding.ID
1✔
320
                                utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceBinding, false)
1✔
321
                                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
322
                        }
1✔
323

324
                        // make sure there's no secret stored for the binding
325
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
326
                                return ctrl.Result{}, err
×
327
                        }
×
328

329
                        log.Info("Binding does not exists in SM, removing finalizer")
1✔
330
                        if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName); err != nil {
1✔
331
                                return ctrl.Result{}, err
×
332
                        }
×
333
                        return ctrl.Result{}, nil
1✔
334
                }
335

336
                if len(serviceBinding.Status.OperationURL) > 0 && serviceBinding.Status.OperationType == smClientTypes.DELETE {
2✔
337
                        // ongoing delete operation - poll status from SM
1✔
338
                        return r.poll(ctx, serviceBinding, serviceInstance)
1✔
339
                }
1✔
340

341
                log.Info(fmt.Sprintf("Deleting binding with id %v from SM", serviceBinding.Status.BindingID))
1✔
342
                operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
343
                if unbindErr != nil {
2✔
344
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr)
1✔
345
                }
1✔
346

347
                if operationURL != "" {
2✔
348
                        log.Info("Deleting binding async")
1✔
349
                        serviceBinding.Status.OperationURL = operationURL
1✔
350
                        serviceBinding.Status.OperationType = smClientTypes.DELETE
1✔
351
                        utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceBinding, false)
1✔
352
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
353
                                return ctrl.Result{}, err
×
354
                        }
×
355
                        return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
356
                }
357

358
                log.Info("Binding was deleted successfully")
1✔
359
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
360
        }
361
        return ctrl.Result{}, nil
×
362
}
363

364
func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
365
        log := logutils.GetLogger(ctx)
1✔
366
        log.Info(fmt.Sprintf("resource is in progress, found operation url %s", serviceBinding.Status.OperationURL))
1✔
367

1✔
368
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
369
        if err != nil {
1✔
370
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
371
        }
×
372

373
        status, statusErr := smClient.Status(serviceBinding.Status.OperationURL, nil)
1✔
374
        if statusErr != nil {
2✔
375
                log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceBinding.Status.OperationURL)
1✔
376
                utils.SetInProgressConditions(ctx, serviceBinding.Status.OperationType, string(smClientTypes.INPROGRESS), serviceBinding, false)
1✔
377
                freshStatus := v1.ServiceBindingStatus{
1✔
378
                        Conditions: serviceBinding.GetConditions(),
1✔
379
                }
1✔
380
                if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
381
                        freshStatus.BindingID = serviceBinding.Status.BindingID
1✔
382
                }
1✔
383
                serviceBinding.Status = freshStatus
1✔
384
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
385
                        log.Error(err, "failed to update status during polling")
×
386
                }
×
387
                return ctrl.Result{}, statusErr
1✔
388
        }
389

390
        if status == nil {
1✔
391
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, serviceBinding.Status.OperationType, fmt.Errorf("failed to get last operation status of %s", serviceBinding.Name))
×
392
        }
×
393
        switch status.State {
1✔
394
        case smClientTypes.INPROGRESS:
1✔
395
                fallthrough
1✔
396
        case smClientTypes.PENDING:
1✔
397
                log.Info(fmt.Sprintf("%s is still in progress", serviceBinding.Status.OperationURL))
1✔
398
                if len(status.Description) != 0 {
1✔
399
                        utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceBinding, true)
×
400
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
×
401
                                log.Error(err, "unable to update ServiceBinding polling description")
×
402
                                return ctrl.Result{}, err
×
403
                        }
×
404
                }
405
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
406
        case smClientTypes.FAILED:
1✔
407
                log.Info(fmt.Sprintf("%s ended with failure", serviceBinding.Status.OperationURL))
1✔
408
                utils.SetFailureConditions(status.Type, status.Description, serviceBinding, true)
1✔
409
                serviceBinding.Status.OperationURL = ""
1✔
410
                serviceBinding.Status.OperationType = ""
1✔
411
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
NEW
412
                        log.Error(err, "unable to update ServiceBinding status")
×
NEW
413
                        return ctrl.Result{}, err
×
NEW
414
                }
×
415
                errMsg := fmt.Sprintf("Async binding %s operation failed", serviceBinding.Status.OperationType)
1✔
416
                if status.Errors != nil {
1✔
NEW
417
                        errMsg = fmt.Sprintf("Async unbind operation failed, errors: %s", string(status.Errors))
×
UNCOV
418
                }
×
419
                return ctrl.Result{}, errors.New(errMsg)
1✔
420
        case smClientTypes.SUCCEEDED:
1✔
421
                log.Info(fmt.Sprintf("%s completed successfully", serviceBinding.Status.OperationURL))
1✔
422
                utils.SetSuccessConditions(status.Type, serviceBinding, true)
1✔
423
                switch serviceBinding.Status.OperationType {
1✔
424
                case smClientTypes.CREATE:
1✔
425
                        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
426
                        if err != nil || smBinding == nil {
2✔
427
                                log.Error(err, fmt.Sprintf("binding %s succeeded but could not fetch it from SM", serviceBinding.Status.BindingID))
1✔
428
                                return ctrl.Result{}, err
1✔
429
                        }
1✔
430
                        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
431
                                serviceBinding.Status.SubaccountID = smBinding.Labels["subaccount_id"][0]
×
432
                        }
×
433

434
                        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
435
                                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
×
436
                        }
×
437
                        utils.SetSuccessConditions(status.Type, serviceBinding, false)
1✔
438
                case smClientTypes.DELETE:
1✔
439
                        return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
440
                }
441
        }
442

443
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
444
        serviceBinding.Status.OperationURL = ""
1✔
445
        serviceBinding.Status.OperationType = ""
1✔
446

1✔
447
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
448
}
449

450
func (r *ServiceBindingReconciler) getBindingForRecovery(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (*smClientTypes.ServiceBinding, error) {
1✔
451
        log := logutils.GetLogger(ctx)
1✔
452
        nameQuery := fmt.Sprintf("name eq '%s'", serviceBinding.Spec.ExternalName)
1✔
453
        clusterIDQuery := fmt.Sprintf("context/clusterid eq '%s'", r.Config.ClusterID)
1✔
454
        namespaceQuery := fmt.Sprintf("context/namespace eq '%s'", serviceBinding.Namespace)
1✔
455
        k8sNameQuery := fmt.Sprintf("%s eq '%s'", common.K8sNameLabel, serviceBinding.Name)
1✔
456
        parameters := sm.Parameters{
1✔
457
                FieldQuery:    []string{nameQuery, clusterIDQuery, namespaceQuery},
1✔
458
                LabelQuery:    []string{k8sNameQuery},
1✔
459
                GeneralParams: []string{"attach_last_operations=true"},
1✔
460
        }
1✔
461
        log.Info(fmt.Sprintf("binding recovery query params: %s, %s, %s, %s", nameQuery, clusterIDQuery, namespaceQuery, k8sNameQuery))
1✔
462

1✔
463
        bindings, err := smClient.ListBindings(&parameters)
1✔
464
        if err != nil {
1✔
465
                log.Error(err, "failed to list bindings in SM")
×
466
                return nil, err
×
467
        }
×
468
        if bindings != nil {
2✔
469
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
470
                if len(bindings.ServiceBindings) == 1 {
2✔
471
                        return &bindings.ServiceBindings[0], nil
1✔
472
                }
1✔
473
        }
474
        return nil, nil
1✔
475
}
476

477
func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.ServiceBinding, instance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
478
        log := logutils.GetLogger(ctx)
1✔
479
        if err := r.maintainSecret(ctx, binding, instance); err != nil {
2✔
480
                log.Error(err, "failed to maintain secret")
1✔
481
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
482
        }
1✔
483

484
        log.Info("maintain finished successfully")
1✔
485
        return ctrl.Result{}, nil
1✔
486
}
487

488
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) error {
1✔
489
        log := logutils.GetLogger(ctx)
1✔
490
        if common.GetObservedGeneration(serviceBinding) == serviceBinding.Generation {
2✔
491
                log.Info("observed generation is up to date, checking if secret exists")
1✔
492
                if _, err := r.getSecret(ctx, serviceBinding.Namespace, serviceBinding.Spec.SecretName); err == nil {
2✔
493
                        log.Info("secret exists, no need to maintain secret")
1✔
494
                        return nil
1✔
495
                }
1✔
496

497
                log.Info("binding's secret was not found")
1✔
498
                r.Recorder.Event(serviceBinding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted")
1✔
499
        }
500

501
        log.Info("maintaining binding's secret")
1✔
502
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
503
        if err != nil {
1✔
504
                return err
×
505
        }
×
506
        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
507
        if err != nil {
1✔
508
                log.Error(err, "failed to get binding for update secret")
×
509
                return err
×
510
        }
×
511
        if smBinding != nil {
2✔
512
                if smBinding.Credentials != nil {
2✔
513
                        if err = r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
514
                                return err
1✔
515
                        }
1✔
516
                        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
517
                        utils.SetSuccessConditions(smClientTypes.UPDATE, serviceBinding, false)
1✔
518
                }
519
        }
520

521
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
522
}
523

524
func (r *ServiceBindingReconciler) getServiceInstanceForBinding(ctx context.Context, binding *v1.ServiceBinding) (*v1.ServiceInstance, error) {
1✔
525
        log := logutils.GetLogger(ctx)
1✔
526
        serviceInstance := &v1.ServiceInstance{}
1✔
527
        namespace := binding.Namespace
1✔
528
        if len(binding.Spec.ServiceInstanceNamespace) > 0 {
2✔
529
                namespace = binding.Spec.ServiceInstanceNamespace
1✔
530
        }
1✔
531
        log.Info(fmt.Sprintf("getting service instance named %s in namespace %s for binding %s in namespace %s", binding.Spec.ServiceInstanceName, namespace, binding.Name, binding.Namespace))
1✔
532
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.ServiceInstanceName, Namespace: namespace}, serviceInstance); err != nil {
2✔
533
                return serviceInstance, err
1✔
534
        }
1✔
535

536
        return serviceInstance.DeepCopy(), nil
1✔
537
}
538

539
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
540
        k8sBinding.Status.BindingID = smBinding.ID
1✔
541
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
542
        k8sBinding.Status.OperationURL = ""
1✔
543
        k8sBinding.Status.OperationType = ""
1✔
544

1✔
545
        bindingStatus := smClientTypes.SUCCEEDED
1✔
546
        operationType := smClientTypes.CREATE
1✔
547
        description := ""
1✔
548
        if smBinding.LastOperation != nil {
2✔
549
                bindingStatus = smBinding.LastOperation.State
1✔
550
                operationType = smBinding.LastOperation.Type
1✔
551
                description = smBinding.LastOperation.Description
1✔
552
        } else if !smBinding.Ready {
3✔
553
                bindingStatus = smClientTypes.FAILED
1✔
554
        }
1✔
555
        switch bindingStatus {
1✔
556
        case smClientTypes.PENDING:
×
557
                fallthrough
×
558
        case smClientTypes.INPROGRESS:
1✔
559
                k8sBinding.Status.OperationURL = sm.BuildOperationURL(smBinding.LastOperation.ID, smBinding.ID, smClientTypes.ServiceBindingsURL)
1✔
560
                k8sBinding.Status.OperationType = smBinding.LastOperation.Type
1✔
561
                utils.SetInProgressConditions(ctx, smBinding.LastOperation.Type, smBinding.LastOperation.Description, k8sBinding, false)
1✔
562
        case smClientTypes.SUCCEEDED:
1✔
563
                utils.SetSuccessConditions(operationType, k8sBinding, false)
1✔
564
        case smClientTypes.FAILED:
1✔
565
                utils.SetFailureConditions(operationType, description, k8sBinding, false)
1✔
566
        }
567
}
568

569
func (r *ServiceBindingReconciler) storeBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) error {
1✔
570
        log := logutils.GetLogger(ctx)
1✔
571
        logger := log.WithValues("bindingName", k8sBinding.Name, "secretName", k8sBinding.Spec.SecretName)
1✔
572

1✔
573
        var secret *corev1.Secret
1✔
574
        var err error
1✔
575

1✔
576
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
577
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
578
        } else {
2✔
579
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
580
        }
1✔
581

582
        if err != nil {
2✔
583
                return err
1✔
584
        }
1✔
585
        if err = controllerutil.SetControllerReference(k8sBinding, secret, r.Scheme); err != nil {
1✔
586
                logger.Error(err, "Failed to set secret owner")
×
587
                return err
×
588
        }
×
589

590
        if secret.Labels == nil {
1✔
591
                secret.Labels = map[string]string{}
×
592
        }
×
593
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
594
        if len(k8sBinding.Labels) > 0 && len(k8sBinding.Labels[common.StaleBindingIDLabel]) > 0 {
2✔
595
                secret.Labels[common.StaleBindingIDLabel] = k8sBinding.Labels[common.StaleBindingIDLabel]
1✔
596
        }
1✔
597

598
        if secret.Annotations == nil {
1✔
599
                secret.Annotations = map[string]string{}
×
600
        }
×
601
        secret.Annotations["binding"] = k8sBinding.Name
1✔
602

1✔
603
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
604
}
605

606
func (r *ServiceBindingReconciler) createBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
607
        credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
608
        if err != nil {
2✔
609
                return nil, err
1✔
610
        }
1✔
611

612
        secret := &corev1.Secret{
1✔
613
                ObjectMeta: metav1.ObjectMeta{
1✔
614
                        Name:        k8sBinding.Spec.SecretName,
1✔
615
                        Annotations: map[string]string{"binding": k8sBinding.Name},
1✔
616
                        Labels:      map[string]string{common.ManagedByBTPOperatorLabel: "true"},
1✔
617
                        Namespace:   k8sBinding.Namespace,
1✔
618
                },
1✔
619
                Data: credentialsMap,
1✔
620
        }
1✔
621
        return secret, nil
1✔
622
}
623

624
func (r *ServiceBindingReconciler) getSecretDefaultData(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (map[string][]byte, error) {
1✔
625
        log := logutils.GetLogger(ctx).WithValues("bindingName", k8sBinding.Name, "secretName", k8sBinding.Spec.SecretName)
1✔
626

1✔
627
        var credentialsMap map[string][]byte
1✔
628
        var credentialProperties []utils.SecretMetadataProperty
1✔
629

1✔
630
        if len(smBinding.Credentials) == 0 {
2✔
631
                log.Info("Binding credentials are empty")
1✔
632
                credentialsMap = make(map[string][]byte)
1✔
633
        } else if k8sBinding.Spec.SecretKey != nil {
3✔
634
                credentialsMap = map[string][]byte{
1✔
635
                        *k8sBinding.Spec.SecretKey: smBinding.Credentials,
1✔
636
                }
1✔
637
                credentialProperties = []utils.SecretMetadataProperty{
1✔
638
                        {
1✔
639
                                Name:      *k8sBinding.Spec.SecretKey,
1✔
640
                                Format:    string(utils.JSON),
1✔
641
                                Container: true,
1✔
642
                        },
1✔
643
                }
1✔
644
        } else {
2✔
645
                var err error
1✔
646
                credentialsMap, credentialProperties, err = utils.NormalizeCredentials(smBinding.Credentials)
1✔
647
                if err != nil {
2✔
648
                        log.Error(err, "Failed to store binding secret")
1✔
649
                        return nil, fmt.Errorf("failed to create secret. Error: %v", err.Error())
1✔
650
                }
1✔
651
        }
652

653
        metaDataProperties, err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap)
1✔
654
        if err != nil {
1✔
655
                log.Error(err, "failed to enrich binding with service instance info")
×
656
        }
×
657

658
        if k8sBinding.Spec.SecretRootKey != nil {
2✔
659
                var err error
1✔
660
                credentialsMap, err = singleKeyMap(credentialsMap, *k8sBinding.Spec.SecretRootKey)
1✔
661
                if err != nil {
1✔
662
                        return nil, err
×
663
                }
×
664
        } else {
1✔
665
                metadata := map[string][]utils.SecretMetadataProperty{
1✔
666
                        "metaDataProperties":   metaDataProperties,
1✔
667
                        "credentialProperties": credentialProperties,
1✔
668
                }
1✔
669
                metadataByte, err := json.Marshal(metadata)
1✔
670
                if err != nil {
1✔
671
                        log.Error(err, "failed to enrich binding with metadata")
×
672
                } else {
1✔
673
                        credentialsMap[".metadata"] = metadataByte
1✔
674
                }
1✔
675
        }
676
        return credentialsMap, nil
1✔
677
}
678

679
func (r *ServiceBindingReconciler) createBindingSecretFromSecretTemplate(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
680
        log := logutils.GetLogger(ctx)
1✔
681
        logger := log.WithValues("bindingName", k8sBinding.Name, "secretName", k8sBinding.Spec.SecretName)
1✔
682

1✔
683
        logger.Info("Create Object using SecretTemplate from ServiceBinding Specs")
1✔
684
        inputSmCredentials := smBinding.Credentials
1✔
685
        smBindingCredentials := make(map[string]interface{})
1✔
686
        if inputSmCredentials != nil {
2✔
687
                err := json.Unmarshal(inputSmCredentials, &smBindingCredentials)
1✔
688
                if err != nil {
1✔
689
                        logger.Error(err, "failed to unmarshal given service binding credentials")
×
690
                        return nil, errors.Wrap(err, "failed to unmarshal given service binding credentials")
×
691
                }
×
692
        }
693

694
        instanceInfos, err := r.getInstanceInfo(ctx, k8sBinding)
1✔
695
        if err != nil {
1✔
696
                logger.Error(err, "failed to addInstanceInfo")
×
697
                return nil, errors.Wrap(err, "failed to add service instance info")
×
698
        }
×
699

700
        parameters := commonutils.GetSecretDataForTemplate(smBindingCredentials, instanceInfos)
1✔
701
        templateName := fmt.Sprintf("%s/%s", k8sBinding.Namespace, k8sBinding.Name)
1✔
702
        secret, err := commonutils.CreateSecretFromTemplate(templateName, k8sBinding.Spec.SecretTemplate, "missingkey=error", parameters)
1✔
703
        if err != nil {
2✔
704
                logger.Error(err, "failed to create secret from template")
1✔
705
                return nil, errors.Wrap(err, "failed to create secret from template")
1✔
706
        }
1✔
707
        secret.SetNamespace(k8sBinding.Namespace)
1✔
708
        secret.SetName(k8sBinding.Spec.SecretName)
1✔
709
        if secret.Labels == nil {
1✔
710
                secret.Labels = map[string]string{}
×
711
        }
×
712
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
713

1✔
714
        // if no data provided use the default data
1✔
715
        if len(secret.Data) == 0 && len(secret.StringData) == 0 {
2✔
716
                credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
717
                if err != nil {
1✔
718
                        return nil, err
×
719
                }
×
720
                secret.Data = credentialsMap
1✔
721
        }
722
        return secret, nil
1✔
723
}
724

725
func (r *ServiceBindingReconciler) createOrUpdateBindingSecret(ctx context.Context, binding *v1.ServiceBinding, secret *corev1.Secret) error {
1✔
726
        log := logutils.GetLogger(ctx)
1✔
727
        dbSecret := &corev1.Secret{}
1✔
728
        create := false
1✔
729
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.SecretName, Namespace: binding.Namespace}, dbSecret); err != nil {
2✔
730
                if !apierrors.IsNotFound(err) {
1✔
731
                        return err
×
732
                }
×
733
                create = true
1✔
734
        }
735

736
        if create {
2✔
737
                log.Info("Creating binding secret", "name", secret.Name)
1✔
738
                if err := r.Client.Create(ctx, secret); err != nil {
1✔
739
                        if !apierrors.IsAlreadyExists(err) {
×
740
                                return err
×
741
                        }
×
742
                        return nil
×
743
                }
744
                r.Recorder.Event(binding, corev1.EventTypeNormal, "SecretCreated", "SecretCreated")
1✔
745
                return nil
1✔
746
        }
747

748
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
749
        dbSecret.Data = secret.Data
1✔
750
        dbSecret.StringData = secret.StringData
1✔
751
        dbSecret.Labels = secret.Labels
1✔
752
        dbSecret.Annotations = secret.Annotations
1✔
753
        return r.Client.Update(ctx, dbSecret)
1✔
754
}
755

756
func (r *ServiceBindingReconciler) deleteBindingSecret(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
757
        log := logutils.GetLogger(ctx)
1✔
758
        log.Info("Deleting binding secret")
1✔
759
        bindingSecret := &corev1.Secret{}
1✔
760
        if err := r.Client.Get(ctx, types.NamespacedName{
1✔
761
                Namespace: binding.Namespace,
1✔
762
                Name:      binding.Spec.SecretName,
1✔
763
        }, bindingSecret); err != nil {
2✔
764
                if !apierrors.IsNotFound(err) {
1✔
765
                        log.Error(err, "unable to fetch binding secret")
×
766
                        return err
×
767
                }
×
768

769
                // secret not found, nothing more to do
770
                log.Info("secret was deleted successfully")
1✔
771
                return nil
1✔
772
        }
773
        bindingSecret = bindingSecret.DeepCopy()
1✔
774

1✔
775
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
776
                log.Error(err, "Failed to delete binding secret")
×
777
                return err
×
778
        }
×
779

780
        log.Info("secret was deleted successfully")
1✔
781
        return nil
1✔
782
}
783

784
func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
785
        // delete binding secret if exist
1✔
786
        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
787
                return ctrl.Result{}, err
×
788
        }
×
789

790
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
791
}
792

793
func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) {
1✔
794
        secret := &corev1.Secret{}
1✔
795
        err := utils.GetSecretWithFallback(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret)
1✔
796
        return secret, err
1✔
797
}
1✔
798

799
func (r *ServiceBindingReconciler) validateSecretNameIsAvailable(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
800
        currentSecret, err := r.getSecret(ctx, binding.Namespace, binding.Spec.SecretName)
1✔
801
        if err != nil {
2✔
802
                return client.IgnoreNotFound(err)
1✔
803
        }
1✔
804

805
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
806
                return nil
1✔
807
        }
1✔
808

809
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
810
        if ownerRef != nil {
2✔
811
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
812
                if err != nil {
1✔
813
                        return err
×
814
                }
×
815

816
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
817
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
818
                }
1✔
819
        }
820

821
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
822
}
823

824
func (r *ServiceBindingReconciler) handleSecretError(ctx context.Context, op smClientTypes.OperationCategory, err error, binding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
825
        log := logutils.GetLogger(ctx)
1✔
826
        log.Error(err, fmt.Sprintf("failed to store secret %s for binding %s", binding.Spec.SecretName, binding.Name))
1✔
827
        return utils.HandleOperationFailure(ctx, r.Client, binding, op, err)
1✔
828
}
1✔
829

830
func (r *ServiceBindingReconciler) getInstanceInfo(ctx context.Context, binding *v1.ServiceBinding) (map[string]string, error) {
1✔
831
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
832
        if err != nil {
1✔
833
                return nil, err
×
834
        }
×
835
        instanceInfos := make(map[string]string)
1✔
836
        instanceInfos["instance_name"] = string(getInstanceNameForSecretCredentials(instance))
1✔
837
        instanceInfos["instance_guid"] = instance.Status.InstanceID
1✔
838
        instanceInfos["plan"] = instance.Spec.ServicePlanName
1✔
839
        instanceInfos["label"] = instance.Spec.ServiceOfferingName
1✔
840
        instanceInfos["type"] = instance.Spec.ServiceOfferingName
1✔
841
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
842
                tags := mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags)
1✔
843
                instanceInfos["tags"] = strings.Join(tags, ",")
1✔
844
        }
1✔
845
        return instanceInfos, nil
1✔
846
}
847

848
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *v1.ServiceBinding, credentialsMap map[string][]byte) ([]utils.SecretMetadataProperty, error) {
1✔
849
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
850
        if err != nil {
1✔
851
                return nil, err
×
852
        }
×
853

854
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
1✔
855
        credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
1✔
856
        credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
1✔
857
        credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
1✔
858
        credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
1✔
859
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
860
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
861
                if err != nil {
1✔
862
                        return nil, err
×
863
                }
×
864
                credentialsMap["tags"] = tagsBytes
1✔
865
        }
866

867
        metadata := []utils.SecretMetadataProperty{
1✔
868
                {
1✔
869
                        Name:   "instance_name",
1✔
870
                        Format: string(utils.TEXT),
1✔
871
                },
1✔
872
                {
1✔
873
                        Name:   "instance_guid",
1✔
874
                        Format: string(utils.TEXT),
1✔
875
                },
1✔
876
                {
1✔
877
                        Name:   "plan",
1✔
878
                        Format: string(utils.TEXT),
1✔
879
                },
1✔
880
                {
1✔
881
                        Name:   "label",
1✔
882
                        Format: string(utils.TEXT),
1✔
883
                },
1✔
884
                {
1✔
885
                        Name:   "type",
1✔
886
                        Format: string(utils.TEXT),
1✔
887
                },
1✔
888
        }
1✔
889
        if _, ok := credentialsMap["tags"]; ok {
2✔
890
                metadata = append(metadata, utils.SecretMetadataProperty{Name: "tags", Format: string(utils.JSON)})
1✔
891
        }
1✔
892

893
        return metadata, nil
1✔
894
}
895

896
func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, binding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (bool, error) {
1✔
897
        log := logutils.GetLogger(ctx)
1✔
898
        if err := r.removeForceRotateAnnotationIfNeeded(ctx, binding, log); err != nil {
1✔
899
                log.Info("Credentials rotation - failed to delete force rotate annotation")
×
900
                return false, err
×
901
        }
×
902

903
        credInProgressCondition := meta.FindStatusCondition(binding.GetConditions(), common.ConditionCredRotationInProgress)
1✔
904
        if credInProgressCondition.Reason == common.CredRotating {
2✔
905
                if len(binding.Status.BindingID) > 0 && binding.Status.Ready == metav1.ConditionTrue {
2✔
906
                        log.Info("Credentials rotation - finished successfully")
1✔
907
                        now := metav1.NewTime(time.Now())
1✔
908
                        binding.Status.LastCredentialsRotationTime = &now
1✔
909
                        return false, r.stopRotation(ctx, binding)
1✔
910
                }
1✔
911
                log.Info("Credentials rotation - waiting to finish")
1✔
912
                return false, nil
1✔
913
        }
914

915
        if len(binding.Status.BindingID) == 0 {
1✔
916
                log.Info("Credentials rotation - no binding id found nothing to do")
×
917
                return false, r.stopRotation(ctx, binding)
×
918
        }
×
919

920
        bindings := &v1.ServiceBindingList{}
1✔
921
        err := r.Client.List(ctx, bindings, client.MatchingLabels{common.StaleBindingIDLabel: binding.Status.BindingID}, client.InNamespace(binding.Namespace))
1✔
922
        if err != nil {
1✔
923
                return false, err
×
924
        }
×
925

926
        if len(bindings.Items) == 0 {
2✔
927
                // create the backup binding
1✔
928
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
929
                if err != nil {
1✔
930
                        return false, err
×
931
                }
×
932

933
                // rename current binding
934
                suffix := "-" + utils.RandStringRunes(6)
1✔
935
                log.Info("Credentials rotation - renaming binding to old in SM", "current", binding.Spec.ExternalName)
1✔
936
                if _, errRenaming := smClient.RenameBinding(binding.Status.BindingID, binding.Spec.ExternalName+suffix, binding.Name+suffix); errRenaming != nil {
1✔
937
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
938
                        return true, errRenaming
×
939
                }
×
940

941
                log.Info("Credentials rotation - backing up old binding in K8S", "name", binding.Name+suffix)
1✔
942
                if err := r.createOldBinding(ctx, suffix, binding); err != nil {
1✔
943
                        log.Error(err, "Credentials rotation - failed to back up old binding in K8S")
×
944
                        return true, err
×
945
                }
×
946
        }
947

948
        binding.Status.BindingID = ""
1✔
949
        binding.Status.Ready = metav1.ConditionFalse
1✔
950
        utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
1✔
951
        utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding)
1✔
952
        return false, utils.UpdateStatus(ctx, r.Client, binding)
1✔
953
}
954

955
func (r *ServiceBindingReconciler) removeForceRotateAnnotationIfNeeded(ctx context.Context, binding *v1.ServiceBinding, log logr.Logger) error {
1✔
956
        if binding.Annotations != nil {
2✔
957
                if _, ok := binding.Annotations[common.ForceRotateAnnotation]; ok {
2✔
958
                        log.Info("Credentials rotation - deleting force rotate annotation")
1✔
959
                        delete(binding.Annotations, common.ForceRotateAnnotation)
1✔
960
                        return r.Client.Update(ctx, binding)
1✔
961
                }
1✔
962
        }
963
        return nil
1✔
964
}
965

966
func (r *ServiceBindingReconciler) stopRotation(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
967
        conditions := binding.GetConditions()
1✔
968
        meta.RemoveStatusCondition(&conditions, common.ConditionCredRotationInProgress)
1✔
969
        binding.Status.Conditions = conditions
1✔
970
        return utils.UpdateStatus(ctx, r.Client, binding)
1✔
971
}
1✔
972

973
func (r *ServiceBindingReconciler) createOldBinding(ctx context.Context, suffix string, binding *v1.ServiceBinding) error {
1✔
974
        oldBinding := newBindingObject(binding.Name+suffix, binding.Namespace)
1✔
975
        err := controllerutil.SetControllerReference(binding, oldBinding, r.Scheme)
1✔
976
        if err != nil {
1✔
977
                return err
×
978
        }
×
979
        oldBinding.Labels = map[string]string{
1✔
980
                common.StaleBindingIDLabel:         binding.Status.BindingID,
1✔
981
                common.StaleBindingRotationOfLabel: truncateString(binding.Name, 63),
1✔
982
        }
1✔
983
        oldBinding.Annotations = map[string]string{
1✔
984
                common.StaleBindingOrigBindingNameAnnotation: binding.Name,
1✔
985
        }
1✔
986
        spec := binding.Spec.DeepCopy()
1✔
987
        spec.CredRotationPolicy.Enabled = false
1✔
988
        spec.SecretName = spec.SecretName + suffix
1✔
989
        spec.ExternalName = spec.ExternalName + suffix
1✔
990
        oldBinding.Spec = *spec
1✔
991
        return r.Client.Create(ctx, oldBinding)
1✔
992
}
993

994
func (r *ServiceBindingReconciler) handleStaleServiceBinding(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
995
        log := logutils.GetLogger(ctx)
1✔
996
        originalBindingName, ok := serviceBinding.Annotations[common.StaleBindingOrigBindingNameAnnotation]
1✔
997
        if !ok {
2✔
998
                //if the user removed the "OrigBindingName" annotation and rotationOf label not exist as well
1✔
999
                //the stale binding should be deleted otherwise it will remain forever
1✔
1000
                if originalBindingName, ok = serviceBinding.Labels[common.StaleBindingRotationOfLabel]; !ok {
2✔
1001
                        log.Info("missing rotationOf label/annotation, unable to fetch original binding, deleting stale")
1✔
1002
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1003
                }
1✔
1004
        }
1005
        origBinding := &v1.ServiceBinding{}
1✔
1006
        if err := r.Client.Get(ctx, types.NamespacedName{Namespace: serviceBinding.Namespace, Name: originalBindingName}, origBinding); err != nil {
2✔
1007
                if apierrors.IsNotFound(err) {
2✔
1008
                        log.Info("original binding not found, deleting stale binding")
1✔
1009
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1010
                }
1✔
1011
                return ctrl.Result{}, err
×
1012
        }
1013
        if meta.IsStatusConditionTrue(origBinding.Status.Conditions, common.ConditionReady) {
1✔
UNCOV
1014
                return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
×
UNCOV
1015
        }
×
1016

1017
        log.Info("not deleting stale binding since original binding is not ready")
1✔
1018
        if !meta.IsStatusConditionPresentAndEqual(serviceBinding.Status.Conditions, common.ConditionPendingTermination, metav1.ConditionTrue) {
2✔
1019
                pendingTerminationCondition := metav1.Condition{
1✔
1020
                        Type:               common.ConditionPendingTermination,
1✔
1021
                        Status:             metav1.ConditionTrue,
1✔
1022
                        Reason:             common.ConditionPendingTermination,
1✔
1023
                        Message:            "waiting for new credentials to be ready",
1✔
1024
                        ObservedGeneration: serviceBinding.GetGeneration(),
1✔
1025
                }
1✔
1026
                meta.SetStatusCondition(&serviceBinding.Status.Conditions, pendingTerminationCondition)
1✔
1027
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1028
        }
1✔
1029
        return ctrl.Result{}, nil
1✔
1030
}
1031

1032
func (r *ServiceBindingReconciler) recover(ctx context.Context, serviceBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (ctrl.Result, error) {
1✔
1033
        log := logutils.GetLogger(ctx)
1✔
1034
        log.Info(fmt.Sprintf("found existing smBinding in SM with id %s, updating status", smBinding.ID))
1✔
1035

1✔
1036
        if smBinding.Credentials != nil {
2✔
1037
                if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
1038
                        operationType := smClientTypes.CREATE
×
1039
                        if smBinding.LastOperation != nil {
×
1040
                                operationType = smBinding.LastOperation.Type
×
1041
                        }
×
1042
                        return r.handleSecretError(ctx, operationType, err, serviceBinding)
×
1043
                }
1044
        }
1045
        r.resyncBindingStatus(ctx, serviceBinding, smBinding)
1✔
1046

1✔
1047
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1048
}
1049

1050
func (r *ServiceBindingReconciler) handleFailedBinding(ctx context.Context, serviceBinding *v1.ServiceBinding, smClient sm.Client) (ctrl.Result, error) {
1✔
1051
        log := logutils.GetLogger(ctx)
1✔
1052
        log.Info(fmt.Sprintf("binding %s failed to be created, deleting it", serviceBinding.Status.BindingID))
1✔
1053
        operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
1054
        if unbindErr != nil {
1✔
NEW
1055
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr)
×
NEW
1056
        }
×
1057
        if operationURL != "" {
1✔
NEW
1058
                log.Info("delete binding request is async")
×
NEW
1059
                serviceBinding.Status.OperationURL = operationURL
×
NEW
1060
                serviceBinding.Status.OperationType = smClientTypes.DELETE
×
NEW
1061
                utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "deleting failed binding", serviceBinding, true)
×
NEW
1062
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
×
NEW
1063
        }
×
1064
        log.Info("binding deleted successfully")
1✔
1065
        serviceBinding.Status.BindingID = ""
1✔
1066
        return ctrl.Result{RequeueAfter: time.Second}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1067
}
1068

1069
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1070
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1071
                return false
×
1072
        }
×
1073

1074
        if binding.Labels != nil {
2✔
1075
                if _, ok := binding.Labels[common.StaleBindingIDLabel]; ok {
2✔
1076
                        if binding.Spec.CredRotationPolicy != nil {
2✔
1077
                                keepFor, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotatedBindingTTL)
1✔
1078
                                if time.Since(binding.CreationTimestamp.Time) > keepFor {
2✔
1079
                                        return true
1✔
1080
                                }
1✔
1081
                        }
1082
                }
1083
        }
1084
        return false
1✔
1085
}
1086

1087
func initCredRotationIfRequired(binding *v1.ServiceBinding) bool {
1✔
1088
        if utils.IsFailed(binding) || !credRotationEnabled(binding) {
2✔
1089
                return false
1✔
1090
        }
1✔
1091
        _, forceRotate := binding.Annotations[common.ForceRotateAnnotation]
1✔
1092

1✔
1093
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1094
        if lastCredentialRotationTime == nil {
2✔
1095
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1096
                lastCredentialRotationTime = &ts
1✔
1097
        }
1✔
1098

1099
        rotationInterval, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotationFrequency)
1✔
1100
        if time.Since(lastCredentialRotationTime.Time) > rotationInterval || forceRotate {
2✔
1101
                utils.SetCredRotationInProgressConditions(common.CredPreparing, "", binding)
1✔
1102
                return true
1✔
1103
        }
1✔
1104

1105
        return false
1✔
1106
}
1107

1108
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1109
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1110
}
1✔
1111

1112
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1113
        var tags []string
1✔
1114

1✔
1115
        for _, tag := range append(offeringTags, customTags...) {
2✔
1116
                if !utils.SliceContains(tags, tag) {
2✔
1117
                        tags = append(tags, tag)
1✔
1118
                }
1✔
1119
        }
1120
        return tags
1✔
1121
}
1122

1123
func newBindingObject(name, namespace string) *v1.ServiceBinding {
1✔
1124
        return &v1.ServiceBinding{
1✔
1125
                TypeMeta: metav1.TypeMeta{
1✔
1126
                        APIVersion: v1.GroupVersion.String(),
1✔
1127
                        Kind:       "ServiceBinding",
1✔
1128
                },
1✔
1129
                ObjectMeta: metav1.ObjectMeta{
1✔
1130
                        Name:      name,
1✔
1131
                        Namespace: namespace,
1✔
1132
                },
1✔
1133
        }
1✔
1134
}
1✔
1135

1136
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1137
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1138
}
1✔
1139

1140
func getInstanceNameForSecretCredentials(instance *v1.ServiceInstance) []byte {
1✔
1141
        if useMetaName, ok := instance.Annotations[common.UseInstanceMetadataNameInSecret]; ok && useMetaName == "true" {
2✔
1142
                return []byte(instance.Name)
1✔
1143
        }
1✔
1144
        return []byte(instance.Spec.ExternalName)
1✔
1145
}
1146

1147
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1148
        stringCredentialsMap := make(map[string]string)
1✔
1149
        for k, v := range credentialsMap {
2✔
1150
                stringCredentialsMap[k] = string(v)
1✔
1151
        }
1✔
1152

1153
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1154
        if err != nil {
1✔
1155
                return nil, err
×
1156
        }
×
1157

1158
        return map[string][]byte{
1✔
1159
                key: credBytes,
1✔
1160
        }, nil
1✔
1161
}
1162

1163
func truncateString(str string, length int) string {
1✔
1164
        if len(str) > length {
2✔
1165
                return str[:length]
1✔
1166
        }
1✔
1167
        return str
1✔
1168
}
1169

1170
func isBindingExistInSM(smClient sm.Client, instance *v1.ServiceInstance, bindingID string, log logr.Logger) (bool, error) {
1✔
1171
        log.Info("checking if k8s instance status is NotFound")
1✔
1172
        instanceReadyCond := meta.FindStatusCondition(instance.GetConditions(), common.ConditionReady)
1✔
1173
        if instanceReadyCond != nil && instanceReadyCond.Reason == common.ResourceNotFound {
2✔
1174
                log.Info("k8s instance is in NotFound state -> invalid binding")
1✔
1175
                return false, nil
1✔
1176
        }
1✔
1177

1178
        log.Info(fmt.Sprintf("trying to get from SM binding with id %s", bindingID))
1✔
1179
        if _, err := smClient.GetBindingByID(bindingID, nil); err != nil {
1✔
1180
                var smError *sm.ServiceManagerError
×
1181
                if ok := errors.As(err, &smError); ok {
×
1182
                        log.Error(smError, fmt.Sprintf("SM returned status code %d", smError.StatusCode))
×
1183
                        if smError.StatusCode == http.StatusNotFound {
×
1184
                                return false, nil
×
1185
                        }
×
1186
                }
1187
                return false, err
×
1188
        }
1189
        log.Info("binding found in SM")
1✔
1190
        return true, nil
1✔
1191
}
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