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

SAP / sap-btp-service-operator / 21901568631

11 Feb 2026 10:31AM UTC coverage: 78.429% (+0.2%) from 78.254%
21901568631

Pull #599

github

kerenlahav
fix
Pull Request #599: Async operation failure retry

73 of 99 new or added lines in 5 files covered. (73.74%)

8 existing lines in 4 files now uncovered.

2796 of 3565 relevant lines covered (78.43%)

0.88 hits per line

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

83.32
/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
                } else if len(serviceBinding.Status.BindingID) == 0 {
2✔
123
                        log.Info("service instance not found, binding is marked for deletion and has no binding id, removing finalizer if exists")
1✔
124
                        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
125
                }
1✔
126
        }
127

128
        if shouldBindingBeDeleted(serviceBinding) {
2✔
129
                return r.delete(ctx, serviceBinding, serviceInstance)
1✔
130
        }
1✔
131

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

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

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

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

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

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

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

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

207
                log.Info("binding in final state, maintaining secret")
1✔
208
                return r.maintain(ctx, smClient, serviceBinding)
1✔
209
        }
210

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

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

227
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
228
        }
229

NEW
230
        log.Info("nothing to do for this binding")
×
UNCOV
231
        return ctrl.Result{}, nil
×
232
}
233

234
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
235

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

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

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

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

268
        if operationURL != "" {
2✔
269
                var bindingID string
1✔
270
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
271
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
×
272
                }
×
273
                log.Info(fmt.Sprintf("binding is being created async, bindingID=%s", bindingID))
1✔
274
                serviceBinding.Status.BindingID = bindingID
1✔
275

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

287
        log.Info("Binding created successfully")
1✔
288

1✔
289
        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
290
                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
1✔
291
        }
1✔
292

293
        subaccountID := ""
1✔
294
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
295
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
296
        }
×
297

298
        serviceBinding.Status.BindingID = smBinding.ID
1✔
299
        serviceBinding.Status.SubaccountID = subaccountID
1✔
300
        serviceBinding.Status.Ready = metav1.ConditionTrue
1✔
301
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false)
1✔
302
        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
303

1✔
304
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
305
}
306

307
func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
308
        log := logutils.GetLogger(ctx)
1✔
309
        log.Info(fmt.Sprintf("binding in delete phase, marked for deletion=%v, bindingID=%s, ready=%s", utils.IsMarkedForDeletion(serviceBinding.ObjectMeta), serviceBinding.Status.BindingID, serviceBinding.Status.Ready))
1✔
310
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
311
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
312
                if err != nil {
1✔
313
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
314
                }
×
315

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

329
                        // make sure there's no secret stored for the binding
330
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
331
                                return ctrl.Result{}, err
×
332
                        }
×
333

334
                        log.Info("Binding does not exists in SM, removing finalizer")
1✔
335
                        if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName); err != nil {
2✔
336
                                return ctrl.Result{}, err
1✔
337
                        }
1✔
338
                        return ctrl.Result{}, nil
1✔
339
                }
340

341
                if len(serviceBinding.Status.OperationURL) > 0 && serviceBinding.Status.OperationType == smClientTypes.DELETE {
2✔
342
                        // ongoing delete operation - poll status from SM
1✔
343
                        return r.poll(ctx, serviceBinding, serviceInstance)
1✔
344
                }
1✔
345

346
                log.Info(fmt.Sprintf("Deleting binding with id %v from SM, resourceMarkedForDeletions=%v", serviceBinding.Status.BindingID, utils.IsMarkedForDeletion(serviceBinding.ObjectMeta)))
1✔
347
                operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
348
                if unbindErr != nil {
2✔
349
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr)
1✔
350
                }
1✔
351

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

363
                log.Info("reset binding id after successful sync delete operation")
1✔
364
                serviceBinding.Status.BindingID = ""
1✔
365
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
NEW
366
                        log.Error(err, "unable to update ServiceBinding status after deletion")
×
NEW
367
                        return ctrl.Result{}, err
×
NEW
368
                }
×
369
                log.Info("Binding was deleted successfully")
1✔
370
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
371
        }
372
        return ctrl.Result{}, nil
×
373
}
374

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

1✔
379
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
380
        if err != nil {
1✔
381
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
382
        }
×
383

384
        status, statusErr := smClient.Status(serviceBinding.Status.OperationURL, nil)
1✔
385
        if statusErr != nil {
2✔
386
                log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceBinding.Status.OperationURL)
1✔
387
                utils.SetInProgressConditions(ctx, serviceBinding.Status.OperationType, string(smClientTypes.INPROGRESS), serviceBinding, false)
1✔
388
                freshStatus := v1.ServiceBindingStatus{
1✔
389
                        Conditions: serviceBinding.GetConditions(),
1✔
390
                }
1✔
391
                if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
392
                        freshStatus.BindingID = serviceBinding.Status.BindingID
1✔
393
                }
1✔
394
                serviceBinding.Status = freshStatus
1✔
395
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
396
                        log.Error(err, "failed to update status during polling")
×
397
                }
×
398
                return ctrl.Result{}, statusErr
1✔
399
        }
400

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

444
                        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
445
                                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
×
446
                        }
×
447
                        utils.SetSuccessConditions(status.Type, serviceBinding, false)
1✔
448
                case smClientTypes.DELETE:
1✔
449
                        _, err := r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
450
                        if err != nil {
1✔
NEW
451
                                log.Error(err, "failed to delete binding secret and remove finalizer after delete operation completed")
×
NEW
452
                                return ctrl.Result{}, err
×
NEW
453
                        }
×
454

455
                        log.Info("reset binding id after successful async delete operation")
1✔
456
                        serviceBinding.Status.BindingID = ""
1✔
457
                        return ctrl.Result{RequeueAfter: time.Second}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
458
                }
459
        }
460

461
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
462
        serviceBinding.Status.OperationURL = ""
1✔
463
        serviceBinding.Status.OperationType = ""
1✔
464

1✔
465
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
466
}
467

468
func (r *ServiceBindingReconciler) getBindingForRecovery(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (*smClientTypes.ServiceBinding, error) {
1✔
469
        log := logutils.GetLogger(ctx)
1✔
470
        nameQuery := fmt.Sprintf("name eq '%s'", serviceBinding.Spec.ExternalName)
1✔
471
        clusterIDQuery := fmt.Sprintf("context/clusterid eq '%s'", r.Config.ClusterID)
1✔
472
        namespaceQuery := fmt.Sprintf("context/namespace eq '%s'", serviceBinding.Namespace)
1✔
473
        k8sNameQuery := fmt.Sprintf("%s eq '%s'", common.K8sNameLabel, serviceBinding.Name)
1✔
474
        parameters := sm.Parameters{
1✔
475
                FieldQuery:    []string{nameQuery, clusterIDQuery, namespaceQuery},
1✔
476
                LabelQuery:    []string{k8sNameQuery},
1✔
477
                GeneralParams: []string{"attach_last_operations=true"},
1✔
478
        }
1✔
479
        log.Info(fmt.Sprintf("binding recovery query params: %s, %s, %s, %s", nameQuery, clusterIDQuery, namespaceQuery, k8sNameQuery))
1✔
480

1✔
481
        bindings, err := smClient.ListBindings(&parameters)
1✔
482
        if err != nil {
1✔
483
                log.Error(err, "failed to list bindings in SM")
×
484
                return nil, err
×
485
        }
×
486
        if bindings != nil {
2✔
487
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
488
                if len(bindings.ServiceBindings) == 1 {
2✔
489
                        return &bindings.ServiceBindings[0], nil
1✔
490
                }
1✔
491
        }
492
        return nil, nil
1✔
493
}
494

495
func (r *ServiceBindingReconciler) maintain(ctx context.Context, smClient sm.Client, binding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
496
        log := logutils.GetLogger(ctx)
1✔
497
        if err := r.maintainSecret(ctx, smClient, binding); err != nil {
2✔
498
                log.Error(err, "failed to maintain secret")
1✔
499
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
500
        }
1✔
501

502
        log.Info("maintain finished successfully")
1✔
503
        return ctrl.Result{}, nil
1✔
504
}
505

506
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) error {
1✔
507
        log := logutils.GetLogger(ctx)
1✔
508
        if common.GetObservedGeneration(serviceBinding) == serviceBinding.Generation {
2✔
509
                log.Info("observed generation is up to date, checking if secret exists")
1✔
510
                if _, err := r.getSecret(ctx, serviceBinding.Namespace, serviceBinding.Spec.SecretName); err == nil {
2✔
511
                        log.Info("secret exists, no need to maintain secret")
1✔
512
                        return nil
1✔
513
                }
1✔
514

515
                log.Info("binding's secret was not found")
1✔
516
                r.Recorder.Event(serviceBinding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted")
1✔
517
        }
518

519
        log.Info("maintaining binding's secret")
1✔
520
        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
521
        if err != nil {
1✔
522
                log.Error(err, "failed to get binding for update secret")
×
523
                return err
×
524
        }
×
525
        if smBinding != nil {
2✔
526
                if smBinding.Credentials != nil {
2✔
527
                        if err = r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
528
                                return err
1✔
529
                        }
1✔
530
                        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
531
                        utils.SetSuccessConditions(smClientTypes.UPDATE, serviceBinding, false)
1✔
532
                }
533
        }
534

535
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
536
}
537

538
func (r *ServiceBindingReconciler) getServiceInstanceForBinding(ctx context.Context, binding *v1.ServiceBinding) (*v1.ServiceInstance, error) {
1✔
539
        log := logutils.GetLogger(ctx)
1✔
540
        serviceInstance := &v1.ServiceInstance{}
1✔
541
        namespace := binding.Namespace
1✔
542
        if len(binding.Spec.ServiceInstanceNamespace) > 0 {
2✔
543
                namespace = binding.Spec.ServiceInstanceNamespace
1✔
544
        }
1✔
545
        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✔
546
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.ServiceInstanceName, Namespace: namespace}, serviceInstance); err != nil {
2✔
547
                return serviceInstance, err
1✔
548
        }
1✔
549

550
        return serviceInstance.DeepCopy(), nil
1✔
551
}
552

553
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
554
        k8sBinding.Status.BindingID = smBinding.ID
1✔
555
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
556
        k8sBinding.Status.OperationURL = ""
1✔
557
        k8sBinding.Status.OperationType = ""
1✔
558

1✔
559
        bindingStatus := smClientTypes.SUCCEEDED
1✔
560
        operationType := smClientTypes.CREATE
1✔
561
        description := ""
1✔
562
        if smBinding.LastOperation != nil {
2✔
563
                bindingStatus = smBinding.LastOperation.State
1✔
564
                operationType = smBinding.LastOperation.Type
1✔
565
                description = smBinding.LastOperation.Description
1✔
566
        } else if !smBinding.Ready {
3✔
567
                bindingStatus = smClientTypes.FAILED
1✔
568
        }
1✔
569
        switch bindingStatus {
1✔
570
        case smClientTypes.PENDING:
×
571
                fallthrough
×
572
        case smClientTypes.INPROGRESS:
1✔
573
                k8sBinding.Status.OperationURL = sm.BuildOperationURL(smBinding.LastOperation.ID, smBinding.ID, smClientTypes.ServiceBindingsURL)
1✔
574
                k8sBinding.Status.OperationType = smBinding.LastOperation.Type
1✔
575
                utils.SetInProgressConditions(ctx, smBinding.LastOperation.Type, smBinding.LastOperation.Description, k8sBinding, false)
1✔
576
        case smClientTypes.SUCCEEDED:
1✔
577
                utils.SetSuccessConditions(operationType, k8sBinding, false)
1✔
578
        case smClientTypes.FAILED:
1✔
579
                utils.SetFailureConditions(operationType, description, k8sBinding, false)
1✔
580
        }
581
}
582

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

1✔
587
        var secret *corev1.Secret
1✔
588
        var err error
1✔
589

1✔
590
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
591
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
592
        } else {
2✔
593
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
594
        }
1✔
595

596
        if err != nil {
2✔
597
                return err
1✔
598
        }
1✔
599
        if err = controllerutil.SetControllerReference(k8sBinding, secret, r.Scheme); err != nil {
1✔
600
                logger.Error(err, "Failed to set secret owner")
×
601
                return err
×
602
        }
×
603

604
        if secret.Labels == nil {
1✔
605
                secret.Labels = map[string]string{}
×
606
        }
×
607
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
608
        if len(k8sBinding.Labels) > 0 && len(k8sBinding.Labels[common.StaleBindingIDLabel]) > 0 {
2✔
609
                secret.Labels[common.StaleBindingIDLabel] = k8sBinding.Labels[common.StaleBindingIDLabel]
1✔
610
        }
1✔
611

612
        if secret.Annotations == nil {
1✔
613
                secret.Annotations = map[string]string{}
×
614
        }
×
615
        secret.Annotations["binding"] = k8sBinding.Name
1✔
616

1✔
617
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
618
}
619

620
func (r *ServiceBindingReconciler) createBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
621
        credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
622
        if err != nil {
2✔
623
                return nil, err
1✔
624
        }
1✔
625

626
        secret := &corev1.Secret{
1✔
627
                ObjectMeta: metav1.ObjectMeta{
1✔
628
                        Name:        k8sBinding.Spec.SecretName,
1✔
629
                        Annotations: map[string]string{"binding": k8sBinding.Name},
1✔
630
                        Labels:      map[string]string{common.ManagedByBTPOperatorLabel: "true"},
1✔
631
                        Namespace:   k8sBinding.Namespace,
1✔
632
                },
1✔
633
                Data: credentialsMap,
1✔
634
        }
1✔
635
        return secret, nil
1✔
636
}
637

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

1✔
641
        var credentialsMap map[string][]byte
1✔
642
        var credentialProperties []utils.SecretMetadataProperty
1✔
643

1✔
644
        if len(smBinding.Credentials) == 0 {
2✔
645
                log.Info("Binding credentials are empty")
1✔
646
                credentialsMap = make(map[string][]byte)
1✔
647
        } else if k8sBinding.Spec.SecretKey != nil {
3✔
648
                credentialsMap = map[string][]byte{
1✔
649
                        *k8sBinding.Spec.SecretKey: smBinding.Credentials,
1✔
650
                }
1✔
651
                credentialProperties = []utils.SecretMetadataProperty{
1✔
652
                        {
1✔
653
                                Name:      *k8sBinding.Spec.SecretKey,
1✔
654
                                Format:    string(utils.JSON),
1✔
655
                                Container: true,
1✔
656
                        },
1✔
657
                }
1✔
658
        } else {
2✔
659
                var err error
1✔
660
                credentialsMap, credentialProperties, err = utils.NormalizeCredentials(smBinding.Credentials)
1✔
661
                if err != nil {
2✔
662
                        log.Error(err, "Failed to store binding secret")
1✔
663
                        return nil, fmt.Errorf("failed to create secret. Error: %v", err.Error())
1✔
664
                }
1✔
665
        }
666

667
        metaDataProperties, err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap)
1✔
668
        if err != nil {
1✔
669
                log.Error(err, "failed to enrich binding with service instance info")
×
670
        }
×
671

672
        if k8sBinding.Spec.SecretRootKey != nil {
2✔
673
                var err error
1✔
674
                credentialsMap, err = singleKeyMap(credentialsMap, *k8sBinding.Spec.SecretRootKey)
1✔
675
                if err != nil {
1✔
676
                        return nil, err
×
677
                }
×
678
        } else {
1✔
679
                metadata := map[string][]utils.SecretMetadataProperty{
1✔
680
                        "metaDataProperties":   metaDataProperties,
1✔
681
                        "credentialProperties": credentialProperties,
1✔
682
                }
1✔
683
                metadataByte, err := json.Marshal(metadata)
1✔
684
                if err != nil {
1✔
685
                        log.Error(err, "failed to enrich binding with metadata")
×
686
                } else {
1✔
687
                        credentialsMap[".metadata"] = metadataByte
1✔
688
                }
1✔
689
        }
690
        return credentialsMap, nil
1✔
691
}
692

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

1✔
697
        logger.Info("Create Object using SecretTemplate from ServiceBinding Specs")
1✔
698
        inputSmCredentials := smBinding.Credentials
1✔
699
        smBindingCredentials := make(map[string]interface{})
1✔
700
        if inputSmCredentials != nil {
2✔
701
                err := json.Unmarshal(inputSmCredentials, &smBindingCredentials)
1✔
702
                if err != nil {
1✔
703
                        logger.Error(err, "failed to unmarshal given service binding credentials")
×
704
                        return nil, errors.Wrap(err, "failed to unmarshal given service binding credentials")
×
705
                }
×
706
        }
707

708
        instanceInfos, err := r.getInstanceInfo(ctx, k8sBinding)
1✔
709
        if err != nil {
1✔
710
                logger.Error(err, "failed to addInstanceInfo")
×
711
                return nil, errors.Wrap(err, "failed to add service instance info")
×
712
        }
×
713

714
        parameters := commonutils.GetSecretDataForTemplate(smBindingCredentials, instanceInfos)
1✔
715
        templateName := fmt.Sprintf("%s/%s", k8sBinding.Namespace, k8sBinding.Name)
1✔
716
        secret, err := commonutils.CreateSecretFromTemplate(templateName, k8sBinding.Spec.SecretTemplate, "missingkey=error", parameters)
1✔
717
        if err != nil {
2✔
718
                logger.Error(err, "failed to create secret from template")
1✔
719
                return nil, errors.Wrap(err, "failed to create secret from template")
1✔
720
        }
1✔
721
        secret.SetNamespace(k8sBinding.Namespace)
1✔
722
        secret.SetName(k8sBinding.Spec.SecretName)
1✔
723
        if secret.Labels == nil {
1✔
724
                secret.Labels = map[string]string{}
×
725
        }
×
726
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
727

1✔
728
        // if no data provided use the default data
1✔
729
        if len(secret.Data) == 0 && len(secret.StringData) == 0 {
2✔
730
                credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
731
                if err != nil {
1✔
732
                        return nil, err
×
733
                }
×
734
                secret.Data = credentialsMap
1✔
735
        }
736
        return secret, nil
1✔
737
}
738

739
func (r *ServiceBindingReconciler) createOrUpdateBindingSecret(ctx context.Context, binding *v1.ServiceBinding, secret *corev1.Secret) error {
1✔
740
        log := logutils.GetLogger(ctx)
1✔
741
        dbSecret := &corev1.Secret{}
1✔
742
        create := false
1✔
743
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.SecretName, Namespace: binding.Namespace}, dbSecret); err != nil {
2✔
744
                if !apierrors.IsNotFound(err) {
1✔
745
                        return err
×
746
                }
×
747
                create = true
1✔
748
        }
749

750
        if create {
2✔
751
                log.Info("Creating binding secret", "name", secret.Name)
1✔
752
                if err := r.Client.Create(ctx, secret); err != nil {
1✔
753
                        if !apierrors.IsAlreadyExists(err) {
×
754
                                return err
×
755
                        }
×
756
                        return nil
×
757
                }
758
                r.Recorder.Event(binding, corev1.EventTypeNormal, "SecretCreated", "SecretCreated")
1✔
759
                return nil
1✔
760
        }
761

762
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
763
        dbSecret.Data = secret.Data
1✔
764
        dbSecret.StringData = secret.StringData
1✔
765
        dbSecret.Labels = secret.Labels
1✔
766
        dbSecret.Annotations = secret.Annotations
1✔
767
        return r.Client.Update(ctx, dbSecret)
1✔
768
}
769

770
func (r *ServiceBindingReconciler) deleteBindingSecret(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
771
        log := logutils.GetLogger(ctx)
1✔
772
        log.Info("Deleting binding secret")
1✔
773
        bindingSecret := &corev1.Secret{}
1✔
774
        if err := r.Client.Get(ctx, types.NamespacedName{
1✔
775
                Namespace: binding.Namespace,
1✔
776
                Name:      binding.Spec.SecretName,
1✔
777
        }, bindingSecret); err != nil {
2✔
778
                if !apierrors.IsNotFound(err) {
1✔
779
                        log.Error(err, "unable to fetch binding secret")
×
780
                        return err
×
781
                }
×
782

783
                // secret not found, nothing more to do
784
                log.Info("secret was deleted successfully")
1✔
785
                return nil
1✔
786
        }
787
        bindingSecret = bindingSecret.DeepCopy()
1✔
788

1✔
789
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
790
                log.Error(err, "Failed to delete binding secret")
×
791
                return err
×
792
        }
×
793

794
        log.Info("secret was deleted successfully")
1✔
795
        return nil
1✔
796
}
797

798
func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
799
        // delete binding secret if exist
1✔
800
        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
801
                return ctrl.Result{}, err
×
802
        }
×
803

804
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
805
}
806

807
func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) {
1✔
808
        secret := &corev1.Secret{}
1✔
809
        err := utils.GetSecretWithFallback(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret)
1✔
810
        return secret, err
1✔
811
}
1✔
812

813
func (r *ServiceBindingReconciler) validateSecretNameIsAvailable(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
814
        currentSecret, err := r.getSecret(ctx, binding.Namespace, binding.Spec.SecretName)
1✔
815
        if err != nil {
2✔
816
                return client.IgnoreNotFound(err)
1✔
817
        }
1✔
818

819
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
820
                return nil
1✔
821
        }
1✔
822

823
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
824
        if ownerRef != nil {
2✔
825
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
826
                if err != nil {
1✔
827
                        return err
×
828
                }
×
829

830
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
831
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
832
                }
1✔
833
        }
834

835
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
836
}
837

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

844
func (r *ServiceBindingReconciler) getInstanceInfo(ctx context.Context, binding *v1.ServiceBinding) (map[string]string, error) {
1✔
845
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
846
        if err != nil {
1✔
847
                return nil, err
×
848
        }
×
849
        instanceInfos := make(map[string]string)
1✔
850
        instanceInfos["instance_name"] = string(getInstanceNameForSecretCredentials(instance))
1✔
851
        instanceInfos["instance_guid"] = instance.Status.InstanceID
1✔
852
        instanceInfos["plan"] = instance.Spec.ServicePlanName
1✔
853
        instanceInfos["label"] = instance.Spec.ServiceOfferingName
1✔
854
        instanceInfos["type"] = instance.Spec.ServiceOfferingName
1✔
855
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
856
                tags := mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags)
1✔
857
                instanceInfos["tags"] = strings.Join(tags, ",")
1✔
858
        }
1✔
859
        return instanceInfos, nil
1✔
860
}
861

862
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *v1.ServiceBinding, credentialsMap map[string][]byte) ([]utils.SecretMetadataProperty, error) {
1✔
863
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
864
        if err != nil {
1✔
865
                return nil, err
×
866
        }
×
867

868
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
1✔
869
        credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
1✔
870
        credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
1✔
871
        credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
1✔
872
        credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
1✔
873
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
874
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
875
                if err != nil {
1✔
876
                        return nil, err
×
877
                }
×
878
                credentialsMap["tags"] = tagsBytes
1✔
879
        }
880

881
        metadata := []utils.SecretMetadataProperty{
1✔
882
                {
1✔
883
                        Name:   "instance_name",
1✔
884
                        Format: string(utils.TEXT),
1✔
885
                },
1✔
886
                {
1✔
887
                        Name:   "instance_guid",
1✔
888
                        Format: string(utils.TEXT),
1✔
889
                },
1✔
890
                {
1✔
891
                        Name:   "plan",
1✔
892
                        Format: string(utils.TEXT),
1✔
893
                },
1✔
894
                {
1✔
895
                        Name:   "label",
1✔
896
                        Format: string(utils.TEXT),
1✔
897
                },
1✔
898
                {
1✔
899
                        Name:   "type",
1✔
900
                        Format: string(utils.TEXT),
1✔
901
                },
1✔
902
        }
1✔
903
        if _, ok := credentialsMap["tags"]; ok {
2✔
904
                metadata = append(metadata, utils.SecretMetadataProperty{Name: "tags", Format: string(utils.JSON)})
1✔
905
        }
1✔
906

907
        return metadata, nil
1✔
908
}
909

910
func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, binding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (bool, error) {
1✔
911
        log := logutils.GetLogger(ctx)
1✔
912
        if err := r.removeForceRotateAnnotationIfNeeded(ctx, binding, log); err != nil {
1✔
913
                log.Info("Credentials rotation - failed to delete force rotate annotation")
×
914
                return false, err
×
915
        }
×
916

917
        credInProgressCondition := meta.FindStatusCondition(binding.GetConditions(), common.ConditionCredRotationInProgress)
1✔
918
        if credInProgressCondition.Reason == common.CredRotating {
2✔
919
                if len(binding.Status.BindingID) > 0 && binding.Status.Ready == metav1.ConditionTrue {
2✔
920
                        log.Info("Credentials rotation - finished successfully")
1✔
921
                        now := metav1.NewTime(time.Now())
1✔
922
                        binding.Status.LastCredentialsRotationTime = &now
1✔
923
                        return false, r.stopRotation(ctx, binding)
1✔
924
                }
1✔
925
                log.Info("Credentials rotation - waiting to finish")
1✔
926
                return false, nil
1✔
927
        }
928

929
        if len(binding.Status.BindingID) == 0 {
1✔
930
                log.Info("Credentials rotation - no binding id found nothing to do")
×
931
                return false, r.stopRotation(ctx, binding)
×
932
        }
×
933

934
        bindings := &v1.ServiceBindingList{}
1✔
935
        err := r.Client.List(ctx, bindings, client.MatchingLabels{common.StaleBindingIDLabel: binding.Status.BindingID}, client.InNamespace(binding.Namespace))
1✔
936
        if err != nil {
1✔
937
                return false, err
×
938
        }
×
939

940
        if len(bindings.Items) == 0 {
2✔
941
                // create the backup binding
1✔
942
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
943
                if err != nil {
1✔
944
                        return false, err
×
945
                }
×
946

947
                // rename current binding
948
                suffix := "-" + utils.RandStringRunes(6)
1✔
949
                log.Info("Credentials rotation - renaming binding to old in SM", "current", binding.Spec.ExternalName)
1✔
950
                if _, errRenaming := smClient.RenameBinding(binding.Status.BindingID, binding.Spec.ExternalName+suffix, binding.Name+suffix); errRenaming != nil {
1✔
951
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
952
                        return true, errRenaming
×
953
                }
×
954

955
                log.Info("Credentials rotation - backing up old binding in K8S", "name", binding.Name+suffix)
1✔
956
                if err := r.createOldBinding(ctx, suffix, binding); err != nil {
1✔
957
                        log.Error(err, "Credentials rotation - failed to back up old binding in K8S")
×
958
                        return true, err
×
959
                }
×
960
        }
961

962
        log.Info("reset binding id after successful rotation")
1✔
963
        binding.Status.BindingID = ""
1✔
964
        binding.Status.Ready = metav1.ConditionFalse
1✔
965
        utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
1✔
966
        utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding)
1✔
967
        return false, utils.UpdateStatus(ctx, r.Client, binding)
1✔
968
}
969

970
func (r *ServiceBindingReconciler) removeForceRotateAnnotationIfNeeded(ctx context.Context, binding *v1.ServiceBinding, log logr.Logger) error {
1✔
971
        if binding.Annotations != nil {
2✔
972
                if _, ok := binding.Annotations[common.ForceRotateAnnotation]; ok {
2✔
973
                        log.Info("Credentials rotation - deleting force rotate annotation")
1✔
974
                        delete(binding.Annotations, common.ForceRotateAnnotation)
1✔
975
                        return r.Client.Update(ctx, binding)
1✔
976
                }
1✔
977
        }
978
        return nil
1✔
979
}
980

981
func (r *ServiceBindingReconciler) stopRotation(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
982
        conditions := binding.GetConditions()
1✔
983
        meta.RemoveStatusCondition(&conditions, common.ConditionCredRotationInProgress)
1✔
984
        binding.Status.Conditions = conditions
1✔
985
        return utils.UpdateStatus(ctx, r.Client, binding)
1✔
986
}
1✔
987

988
func (r *ServiceBindingReconciler) createOldBinding(ctx context.Context, suffix string, binding *v1.ServiceBinding) error {
1✔
989
        oldBinding := newBindingObject(binding.Name+suffix, binding.Namespace)
1✔
990
        err := controllerutil.SetControllerReference(binding, oldBinding, r.Scheme)
1✔
991
        if err != nil {
1✔
992
                return err
×
993
        }
×
994
        oldBinding.Labels = map[string]string{
1✔
995
                common.StaleBindingIDLabel:         binding.Status.BindingID,
1✔
996
                common.StaleBindingRotationOfLabel: truncateString(binding.Name, 63),
1✔
997
        }
1✔
998
        oldBinding.Annotations = map[string]string{
1✔
999
                common.StaleBindingOrigBindingNameAnnotation: binding.Name,
1✔
1000
        }
1✔
1001
        spec := binding.Spec.DeepCopy()
1✔
1002
        spec.CredRotationPolicy.Enabled = false
1✔
1003
        spec.SecretName = spec.SecretName + suffix
1✔
1004
        spec.ExternalName = spec.ExternalName + suffix
1✔
1005
        oldBinding.Spec = *spec
1✔
1006
        return r.Client.Create(ctx, oldBinding)
1✔
1007
}
1008

1009
func (r *ServiceBindingReconciler) handleStaleServiceBinding(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
1010
        log := logutils.GetLogger(ctx)
1✔
1011
        originalBindingName, ok := serviceBinding.Annotations[common.StaleBindingOrigBindingNameAnnotation]
1✔
1012
        if !ok {
2✔
1013
                //if the user removed the "OrigBindingName" annotation and rotationOf label not exist as well
1✔
1014
                //the stale binding should be deleted otherwise it will remain forever
1✔
1015
                if originalBindingName, ok = serviceBinding.Labels[common.StaleBindingRotationOfLabel]; !ok {
2✔
1016
                        log.Info("missing rotationOf label/annotation, unable to fetch original binding, deleting stale")
1✔
1017
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1018
                }
1✔
1019
        }
1020
        origBinding := &v1.ServiceBinding{}
1✔
1021
        if err := r.Client.Get(ctx, types.NamespacedName{Namespace: serviceBinding.Namespace, Name: originalBindingName}, origBinding); err != nil {
1✔
1022
                if apierrors.IsNotFound(err) {
×
1023
                        log.Info("original binding not found, deleting stale binding")
×
1024
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
×
1025
                }
×
1026
                return ctrl.Result{}, err
×
1027
        }
1028
        if meta.IsStatusConditionTrue(origBinding.Status.Conditions, common.ConditionReady) {
2✔
1029
                return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1030
        }
1✔
1031

1032
        log.Info("not deleting stale binding since original binding is not ready")
1✔
1033
        if !meta.IsStatusConditionPresentAndEqual(serviceBinding.Status.Conditions, common.ConditionPendingTermination, metav1.ConditionTrue) {
2✔
1034
                pendingTerminationCondition := metav1.Condition{
1✔
1035
                        Type:               common.ConditionPendingTermination,
1✔
1036
                        Status:             metav1.ConditionTrue,
1✔
1037
                        Reason:             common.ConditionPendingTermination,
1✔
1038
                        Message:            "waiting for new credentials to be ready",
1✔
1039
                        ObservedGeneration: serviceBinding.GetGeneration(),
1✔
1040
                }
1✔
1041
                meta.SetStatusCondition(&serviceBinding.Status.Conditions, pendingTerminationCondition)
1✔
1042
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1043
        }
1✔
1044
        return ctrl.Result{}, nil
1✔
1045
}
1046

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

1✔
1051
        if smBinding.Credentials != nil {
2✔
1052
                if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
1053
                        operationType := smClientTypes.CREATE
×
1054
                        if smBinding.LastOperation != nil {
×
1055
                                operationType = smBinding.LastOperation.Type
×
1056
                        }
×
1057
                        return r.handleSecretError(ctx, operationType, err, serviceBinding)
×
1058
                }
1059
        }
1060
        r.resyncBindingStatus(ctx, serviceBinding, smBinding)
1✔
1061

1✔
1062
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1063
}
1064

1065
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1066
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1067
                return false
×
1068
        }
×
1069

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

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

1✔
1089
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1090
        if lastCredentialRotationTime == nil {
2✔
1091
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1092
                lastCredentialRotationTime = &ts
1✔
1093
        }
1✔
1094

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

1101
        return false
1✔
1102
}
1103

1104
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1105
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1106
}
1✔
1107

1108
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1109
        var tags []string
1✔
1110

1✔
1111
        for _, tag := range append(offeringTags, customTags...) {
2✔
1112
                if !utils.SliceContains(tags, tag) {
2✔
1113
                        tags = append(tags, tag)
1✔
1114
                }
1✔
1115
        }
1116
        return tags
1✔
1117
}
1118

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

1132
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1133
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1134
}
1✔
1135

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

1143
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1144
        stringCredentialsMap := make(map[string]string)
1✔
1145
        for k, v := range credentialsMap {
2✔
1146
                stringCredentialsMap[k] = string(v)
1✔
1147
        }
1✔
1148

1149
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1150
        if err != nil {
1✔
1151
                return nil, err
×
1152
        }
×
1153

1154
        return map[string][]byte{
1✔
1155
                key: credBytes,
1✔
1156
        }, nil
1✔
1157
}
1158

1159
func truncateString(str string, length int) string {
1✔
1160
        if len(str) > length {
2✔
1161
                return str[:length]
1✔
1162
        }
1✔
1163
        return str
1✔
1164
}
1165

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

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

1189
func shouldBindingBeDeleted(serviceBinding *v1.ServiceBinding) bool {
1✔
1190
        return utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) ||
1✔
1191
                (len(serviceBinding.Status.BindingID) > 0 && serviceBinding.Status.Ready == metav1.ConditionFalse && len(serviceBinding.Status.OperationURL) == 0)
1✔
1192
}
1✔
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