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

SAP / sap-btp-service-operator / 26029653484

18 May 2026 11:06AM UTC coverage: 77.933% (+0.08%) from 77.858%
26029653484

Pull #635

github

kerenlahav
handle rate limit
Pull Request #635: Fixasync2

112 of 146 new or added lines in 4 files covered. (76.71%)

32 existing lines in 3 files now uncovered.

2896 of 3716 relevant lines covered (77.93%)

0.88 hits per line

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

82.81
/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/events"
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    events.EventRecorder
77
        Retries     *utils.RetryStore
78
}
79

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

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

1✔
90
        retry := r.Retries.Get(req.NamespacedName)
1✔
91
        if retry != nil && time.Now().Before(retry.NextRetry) {
2✔
92
                remaining := time.Until(retry.NextRetry)
1✔
93
                log.Info(fmt.Sprintf("skipping binding reconcile due to backoff. attempts=%d retryIn=%s", retry.Attempts, remaining))
1✔
94

1✔
95
                return ctrl.Result{RequeueAfter: remaining}, nil
1✔
96
        }
1✔
97

98
        serviceBinding := &v1.ServiceBinding{}
1✔
99
        if err := r.Client.Get(ctx, req.NamespacedName, serviceBinding); err != nil {
2✔
100
                if !apierrors.IsNotFound(err) {
1✔
101
                        log.Error(err, "unable to fetch ServiceBinding")
×
102
                }
×
103
                return ctrl.Result{}, client.IgnoreNotFound(err)
1✔
104
        }
105

106
        log.Info(fmt.Sprintf("*** staring reconcile of ServiceBinding %s/%s ***", serviceBinding.Namespace, serviceBinding.Name))
1✔
107

1✔
108
        serviceBinding = serviceBinding.DeepCopy()
1✔
109
        log.Info(fmt.Sprintf("Current generation is %v and observed is %v", serviceBinding.Generation, common.GetObservedGeneration(serviceBinding)))
1✔
110

1✔
111
        if len(serviceBinding.GetConditions()) == 0 {
2✔
112
                if err := utils.InitConditions(ctx, r.Client, serviceBinding); err != nil {
1✔
113
                        return ctrl.Result{}, err
×
114
                }
×
115
        }
116

117
        serviceInstance, instanceErr := r.getServiceInstanceForBinding(ctx, serviceBinding)
1✔
118
        if instanceErr != nil {
2✔
119
                if !apierrors.IsNotFound(instanceErr) {
1✔
120
                        log.Error(instanceErr, "failed to get service instance for binding")
×
121
                        return ctrl.Result{}, instanceErr
×
122
                }
×
123
                if !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
124
                        //instance is not found and binding is not marked for deletion
1✔
125
                        return r.handleInstanceForBindingNotFound(ctx, serviceBinding)
1✔
126
                }
1✔
127
                if len(serviceBinding.Status.BindingID) == 0 {
2✔
128
                        log.Info("service instance not found, binding is marked for deletion and has no binding id, removing finalizer if exists")
1✔
129
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
130
                                return ctrl.Result{}, err
×
131
                        }
×
132
                        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
133
                }
134
        }
135

136
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
137
        if err != nil {
1✔
138
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
139
        }
×
140

141
        // poll only if delete sm operation is in progress or there is create/update ongoing operation and instance is not marked for deletion
142
        // if marked for deletion we should trigger the sm delete and ignore the current operation url
143
        if len(serviceBinding.Status.OperationURL) > 0 &&
1✔
144
                (serviceBinding.Status.OperationType == smClientTypes.DELETE || !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta)) {
2✔
145
                return r.poll(ctx, smClient, serviceBinding)
1✔
146
        }
1✔
147

148
        if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
149
                return r.delete(ctx, smClient, serviceBinding)
1✔
150
        }
1✔
151

152
        if len(serviceBinding.Status.BindingID) > 0 {
2✔
153
                if bindingExist, err := isBindingExistInSM(smClient, serviceInstance, serviceBinding.Status.BindingID, log); err != nil {
1✔
154
                        log.Error(err, "failed to check if binding exist in sm due to unknown error")
×
NEW
155
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, common.Unknown, err, false)
×
156
                } else if !bindingExist {
2✔
157
                        log.Info("binding not found in SM for this operator, updating status")
1✔
158
                        condition := metav1.Condition{
1✔
159
                                Type:               common.ConditionReady,
1✔
160
                                Status:             metav1.ConditionFalse,
1✔
161
                                ObservedGeneration: serviceBinding.Generation,
1✔
162
                                LastTransitionTime: metav1.NewTime(time.Now()),
1✔
163
                                Reason:             common.ResourceNotFound,
1✔
164
                                Message:            fmt.Sprintf(common.ResourceNotFoundMessageFormat, "binding", serviceBinding.Status.BindingID),
1✔
165
                        }
1✔
166
                        serviceBinding.Status.Conditions = []metav1.Condition{condition}
1✔
167
                        serviceBinding.Status.Ready = metav1.ConditionFalse
1✔
168
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
169
                }
1✔
170
        }
171

172
        if controllerutil.AddFinalizer(serviceBinding, common.FinalizerName) {
2✔
173
                log.Info(fmt.Sprintf("added finalizer '%s' to service binding", common.FinalizerName))
1✔
174
                if err := r.Client.Update(ctx, serviceBinding); err != nil {
1✔
175
                        return ctrl.Result{}, err
×
176
                }
×
177
        }
178

179
        if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
2✔
180
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is marked for deletion, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
181
                utils.SetBlockedCondition(ctx, "instance is in deletion process", serviceBinding)
1✔
182
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
183
        }
1✔
184

185
        if !serviceInstanceReady(serviceInstance) {
2✔
186
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is not ready, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
187
                utils.SetBlockedCondition(ctx, "service instance is not ready", serviceBinding)
1✔
188
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
189
                        return ctrl.Result{}, err
×
190
                }
×
191
                return ctrl.Result{}, errors.New("ServiceInstance is not ready")
1✔
192
        }
193

194
        // should rotate creds
195
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionCredRotationInProgress) {
2✔
196
                log.Info("rotating credentials")
1✔
197
                if shouldUpdateStatus, err := r.rotateCredentials(ctx, serviceBinding, serviceInstance); err != nil {
2✔
198
                        if !shouldUpdateStatus {
2✔
199
                                log.Error(err, "internal error occurred during cred rotation, requeuing binding")
1✔
200
                                return ctrl.Result{}, err
1✔
201
                        }
1✔
202
                        return utils.HandleCredRotationError(ctx, r.Client, serviceBinding, err)
×
203
                }
204
        }
205

206
        // is binding ready
207
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionReady) {
2✔
208
                if isStaleServiceBinding(serviceBinding) {
2✔
209
                        log.Info("binding is stale, handling")
1✔
210
                        return r.handleStaleServiceBinding(ctx, serviceBinding)
1✔
211
                }
1✔
212

213
                if initCredRotationIfRequired(serviceBinding) {
2✔
214
                        log.Info("cred rotation required, updating status")
1✔
215
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
216
                }
1✔
217

218
                log.Info("binding in final state, maintaining secret")
1✔
219
                return r.maintain(ctx, smClient, serviceBinding)
1✔
220
        }
221

222
        if serviceBinding.Status.BindingID == "" {
2✔
223
                if err := r.validateSecretNameIsAvailable(ctx, serviceBinding); err != nil {
2✔
224
                        log.Error(err, "secret validation failed")
1✔
225
                        utils.SetBlockedCondition(ctx, err.Error(), serviceBinding)
1✔
226
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
227
                }
1✔
228

229
                smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
230
                if err != nil {
1✔
231
                        log.Error(err, "failed to check binding recovery")
×
NEW
232
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err, true)
×
233
                }
×
234
                if smBinding != nil {
2✔
235
                        return r.recover(ctx, serviceBinding, smBinding)
1✔
236
                }
1✔
237

238
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
239
        }
240

241
        log.Info("nothing to do for this binding")
1✔
242
        return ctrl.Result{}, nil
1✔
243
}
244

245
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
246

1✔
247
        return ctrl.NewControllerManagedBy(mgr).
1✔
248
                For(&v1.ServiceBinding{}).
1✔
249
                WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
250
                Complete(r)
1✔
251
}
1✔
252

253
func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
254
        log := logutils.GetLogger(ctx)
1✔
255
        log.Info("Creating smBinding in SM")
1✔
256
        serviceBinding.Status.InstanceID = serviceInstance.Status.InstanceID
1✔
257
        bindingParameters, _, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.Parameters, serviceBinding.Spec.ParametersFrom)
1✔
258
        if err != nil {
1✔
259
                log.Error(err, "failed to parse smBinding parameters")
×
260
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
261
        }
×
262

263
        smBinding, operationURL, bindErr := smClient.Bind(&smClientTypes.ServiceBinding{
1✔
264
                Name: serviceBinding.Spec.ExternalName,
1✔
265
                Labels: smClientTypes.Labels{
1✔
266
                        common.NamespaceLabel: []string{serviceBinding.Namespace},
1✔
267
                        common.K8sNameLabel:   []string{serviceBinding.Name},
1✔
268
                        common.ClusterIDLabel: []string{r.Config.ClusterID},
1✔
269
                },
1✔
270
                ServiceInstanceID: serviceInstance.Status.InstanceID,
1✔
271
                Parameters:        bindingParameters,
1✔
272
        }, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
273

1✔
274
        if bindErr != nil {
2✔
275
                log.Error(err, "failed to create service binding", "serviceInstanceID", serviceInstance.Status.InstanceID)
1✔
276
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, bindErr, true)
1✔
277
        }
1✔
278

279
        if operationURL != "" {
2✔
280
                var bindingID string
1✔
281
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
282
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
×
283
                }
×
284
                log.Info(fmt.Sprintf("binding is being created async, bindingID=%s", bindingID))
1✔
285
                serviceBinding.Status.BindingID = bindingID
1✔
286

1✔
287
                log.Info("Create smBinding request is async")
1✔
288
                serviceBinding.Status.OperationURL = operationURL
1✔
289
                serviceBinding.Status.OperationType = smClientTypes.CREATE
1✔
290
                utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceBinding, false)
1✔
291
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
2✔
292
                        log.Error(err, "unable to update ServiceBinding status")
1✔
293
                        return ctrl.Result{}, err
1✔
294
                }
1✔
295
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
296
        }
297

298
        log.Info("Binding created successfully")
1✔
299

1✔
300
        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
301
                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
1✔
302
        }
1✔
303

304
        subaccountID := ""
1✔
305
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
306
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
307
        }
×
308

309
        serviceBinding.Status.BindingID = smBinding.ID
1✔
310
        serviceBinding.Status.SubaccountID = subaccountID
1✔
311
        serviceBinding.Status.Ready = metav1.ConditionTrue
1✔
312
        r.Retries.Reset(types.NamespacedName{Name: serviceBinding.Name, Namespace: serviceBinding.Namespace})
1✔
313
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false)
1✔
314
        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
315

1✔
316
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
317
}
318

319
func (r *ServiceBindingReconciler) delete(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
320
        log := logutils.GetLogger(ctx)
1✔
321
        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✔
322
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
323
                if len(serviceBinding.Status.BindingID) == 0 {
2✔
324
                        log.Info("No binding id found validating binding does not exists in SM before removing finalizer")
1✔
325
                        smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
326
                        if err != nil {
1✔
NEW
327
                                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err, true)
×
328
                        }
×
329
                        if smBinding != nil {
2✔
330
                                log.Info("binding exists in SM continue with deletion")
1✔
331
                                serviceBinding.Status.BindingID = smBinding.ID
1✔
332
                                utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceBinding, false)
1✔
333
                                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
334
                        }
1✔
335

336
                        // make sure there's no secret stored for the binding
337
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
338
                                return ctrl.Result{}, err
×
339
                        }
×
340

341
                        log.Info("Binding does not exists in SM, removing finalizer")
1✔
342
                        if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName); err != nil {
2✔
343
                                return ctrl.Result{}, err
1✔
344
                        }
1✔
345
                        return ctrl.Result{}, nil
1✔
346
                }
347

348
                if len(serviceBinding.Status.OperationURL) > 0 && serviceBinding.Status.OperationType == smClientTypes.DELETE {
1✔
349
                        // ongoing delete operation - poll status from SM
×
350
                        return r.poll(ctx, smClient, serviceBinding)
×
351
                }
×
352

353
                log.Info(fmt.Sprintf("Deleting binding with id %v from SM, resourceMarkedForDeletions=%v", serviceBinding.Status.BindingID, utils.IsMarkedForDeletion(serviceBinding.ObjectMeta)))
1✔
354
                operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
355
                if unbindErr != nil {
2✔
356
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr, true)
1✔
357
                }
1✔
358

359
                if operationURL != "" {
2✔
360
                        log.Info("Deleting binding async")
1✔
361
                        serviceBinding.Status.OperationURL = operationURL
1✔
362
                        serviceBinding.Status.OperationType = smClientTypes.DELETE
1✔
363
                        utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceBinding, false)
1✔
364
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
365
                                return ctrl.Result{}, err
×
366
                        }
×
367
                        return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
368
                }
369

370
                log.Info("reset binding id after successful sync delete operation")
1✔
371
                serviceBinding.Status.BindingID = ""
1✔
372
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
UNCOV
373
                        log.Error(err, "unable to update ServiceBinding status after deletion")
×
374
                        return ctrl.Result{}, err
×
375
                }
×
376
                log.Info("Binding was deleted successfully")
1✔
377
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
378
        }
379
        return ctrl.Result{}, nil
×
380
}
381

382
func (r *ServiceBindingReconciler) poll(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
383
        log := logutils.GetLogger(ctx)
1✔
384
        log.Info(fmt.Sprintf("binding resource is in progress, found operation url %s", serviceBinding.Status.OperationURL))
1✔
385

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

403
        if status == nil {
1✔
404
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, serviceBinding.Status.OperationType, fmt.Errorf("failed to get last operation status of %s", serviceBinding.Name))
×
405
        }
×
406
        switch status.State {
1✔
407
        case smClientTypes.INPROGRESS:
1✔
408
                fallthrough
1✔
409
        case smClientTypes.PENDING:
1✔
410
                log.Info(fmt.Sprintf("%s is still in progress", serviceBinding.Status.OperationURL))
1✔
411
                if len(status.Description) != 0 {
1✔
412
                        utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceBinding, true)
×
413
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
×
414
                                log.Error(err, "unable to update ServiceBinding polling description")
×
415
                                return ctrl.Result{}, err
×
416
                        }
×
417
                }
418
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
419
        case smClientTypes.FAILED:
1✔
420
                log.Info(fmt.Sprintf("%s ended with failure", serviceBinding.Status.OperationURL))
1✔
421
                utils.SetFailureConditions(status.Type, status.Description, serviceBinding, true)
1✔
422
                if serviceBinding.Status.OperationType == smClientTypes.CREATE {
2✔
423
                        errMsg := getErrorMsgFromLastOperation(status)
1✔
424
                        log.Info(fmt.Sprintf("async binding failed for binding id %s, error: %s", serviceBinding.Status.BindingID, errMsg))
1✔
425
                        key := types.NamespacedName{Namespace: serviceBinding.GetNamespace(), Name: serviceBinding.GetName()}
1✔
426
                        newState := r.Retries.RegisterFailure(key)
1✔
427
                        log.Info(fmt.Sprintf("async binding failed. attempts=%d nextRetry=%s currrent error=%s\n", newState.Attempts, newState.NextRetry.Format(time.RFC3339), errMsg))
1✔
428
                        return r.handleFailedAsyncBinding(ctx, smClient, serviceBinding)
1✔
429
                }
1✔
430
                serviceBinding.Status.OperationURL = ""
1✔
431
                serviceBinding.Status.OperationType = ""
1✔
432
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
UNCOV
433
                        log.Error(err, "unable to update ServiceBinding status")
×
UNCOV
434
                        return ctrl.Result{}, err
×
UNCOV
435
                }
×
436
                errMsg := fmt.Sprintf("Async binding %s operation failed", serviceBinding.Status.OperationType)
1✔
437
                if status.Errors != nil {
1✔
438
                        errMsg = fmt.Sprintf("Async unbind operation failed, errors: %s", string(status.Errors))
×
439
                }
×
440
                return ctrl.Result{}, errors.New(errMsg)
1✔
441
        case smClientTypes.SUCCEEDED:
1✔
442
                log.Info(fmt.Sprintf("%s completed successfully", serviceBinding.Status.OperationURL))
1✔
443
                switch serviceBinding.Status.OperationType {
1✔
444
                case smClientTypes.CREATE:
1✔
445
                        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
446
                        if err != nil || smBinding == nil {
2✔
447
                                log.Error(err, fmt.Sprintf("binding %s succeeded but could not fetch it from SM", serviceBinding.Status.BindingID))
1✔
448
                                return ctrl.Result{}, err
1✔
449
                        }
1✔
450
                        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
451
                                serviceBinding.Status.SubaccountID = smBinding.Labels["subaccount_id"][0]
×
452
                        }
×
453

454
                        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
455
                                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
×
456
                        }
×
457
                        utils.SetSuccessConditions(status.Type, serviceBinding, false)
1✔
458
                case smClientTypes.DELETE:
1✔
459
                        _, err := r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
460
                        if err != nil {
1✔
461
                                log.Error(err, "failed to delete binding secret and remove finalizer after delete operation completed")
×
462
                                return ctrl.Result{}, err
×
463
                        }
×
464

465
                        log.Info("reset binding id after successful async delete operation")
1✔
466
                        serviceBinding.Status.BindingID = ""
1✔
467
                }
468
        }
469

470
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
471
        serviceBinding.Status.OperationURL = ""
1✔
472
        serviceBinding.Status.OperationType = ""
1✔
473
        r.Retries.Reset(types.NamespacedName{Name: serviceBinding.Name, Namespace: serviceBinding.Namespace})
1✔
474

1✔
475
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
476
}
477

478
func (r *ServiceBindingReconciler) handleFailedAsyncBinding(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
479
        log := logutils.GetLogger(ctx)
1✔
480
        log.Info(fmt.Sprintf("handleFailedAsyncBinding deleting binding id %s that failed from SM", serviceBinding.Status.BindingID))
1✔
481
        operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
482
        if unbindErr != nil {
1✔
NEW
483
                log.Error(unbindErr, fmt.Sprintf("handleFailedAsyncBinding unbind binding with id %s failed", serviceBinding.Status.BindingID))
×
NEW
484
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr, false)
×
NEW
485
        }
×
486

487
        if operationURL != "" {
1✔
NEW
488
                log.Info(fmt.Sprintf("handleFailedAsyncBinding unbind is async, operation url %s", operationURL))
×
NEW
489
                serviceBinding.Status.OperationURL = operationURL
×
NEW
490
                serviceBinding.Status.OperationType = smClientTypes.DELETE
×
NEW
491
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
×
NEW
492
        }
×
493

494
        log.Info(fmt.Sprintf("handleFailedAsyncBinding binding %s deleted successfully", serviceBinding.Status.BindingID))
1✔
495
        serviceBinding.Status.OperationURL = ""
1✔
496
        serviceBinding.Status.OperationType = ""
1✔
497
        serviceBinding.Status.BindingID = ""
1✔
498
        if err := r.Client.Status().Update(ctx, serviceBinding); err != nil {
1✔
NEW
499
                log.Error(err, "handleFailedAsyncBinding failed to update service binding status after deletion")
×
NEW
500
                return ctrl.Result{}, err
×
NEW
501
        }
×
502
        return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
503
}
504

505
func (r *ServiceBindingReconciler) getBindingForRecovery(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (*smClientTypes.ServiceBinding, error) {
1✔
506
        log := logutils.GetLogger(ctx)
1✔
507
        nameQuery := fmt.Sprintf("name eq '%s'", serviceBinding.Spec.ExternalName)
1✔
508
        clusterIDQuery := fmt.Sprintf("context/clusterid eq '%s'", r.Config.ClusterID)
1✔
509
        namespaceQuery := fmt.Sprintf("context/namespace eq '%s'", serviceBinding.Namespace)
1✔
510
        k8sNameQuery := fmt.Sprintf("%s eq '%s'", common.K8sNameLabel, serviceBinding.Name)
1✔
511
        parameters := sm.Parameters{
1✔
512
                FieldQuery:    []string{nameQuery, clusterIDQuery, namespaceQuery},
1✔
513
                LabelQuery:    []string{k8sNameQuery},
1✔
514
                GeneralParams: []string{"attach_last_operations=true"},
1✔
515
        }
1✔
516
        log.Info(fmt.Sprintf("binding recovery query params: %s, %s, %s, %s", nameQuery, clusterIDQuery, namespaceQuery, k8sNameQuery))
1✔
517

1✔
518
        bindings, err := smClient.ListBindings(&parameters)
1✔
519
        if err != nil {
1✔
520
                log.Error(err, "failed to list bindings in SM")
×
521
                return nil, err
×
522
        }
×
523
        if bindings != nil {
2✔
524
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
525
                if len(bindings.ServiceBindings) == 1 {
2✔
526
                        return &bindings.ServiceBindings[0], nil
1✔
527
                }
1✔
528
        }
529
        return nil, nil
1✔
530
}
531

532
func (r *ServiceBindingReconciler) maintain(ctx context.Context, smClient sm.Client, binding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
533
        log := logutils.GetLogger(ctx)
1✔
534
        if err := r.maintainSecret(ctx, smClient, binding); err != nil {
2✔
535
                log.Error(err, "failed to maintain secret")
1✔
536
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
537
        }
1✔
538

539
        log.Info("maintain finished successfully")
1✔
540
        return ctrl.Result{}, nil
1✔
541
}
542

543
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) error {
1✔
544
        log := logutils.GetLogger(ctx)
1✔
545
        if common.GetObservedGeneration(serviceBinding) == serviceBinding.Generation {
2✔
546
                log.Info("observed generation is up to date, checking if secret exists")
1✔
547
                if _, err := r.getSecret(ctx, serviceBinding.Namespace, serviceBinding.Spec.SecretName); err == nil {
2✔
548
                        log.Info("secret exists, no need to maintain secret")
1✔
549
                        return nil
1✔
550
                }
1✔
551

552
                log.Info("binding's secret was not found")
1✔
553
                r.Recorder.Eventf(serviceBinding, nil, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted", "SecretDeleted")
1✔
554
        }
555

556
        log.Info("maintaining binding's secret")
1✔
557
        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
558
        if err != nil {
1✔
559
                log.Error(err, "failed to get binding for update secret")
×
560
                return err
×
561
        }
×
562
        if smBinding != nil {
2✔
563
                if smBinding.Credentials != nil {
2✔
564
                        if err = r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
565
                                return err
1✔
566
                        }
1✔
567
                        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
568
                        utils.SetSuccessConditions(smClientTypes.UPDATE, serviceBinding, false)
1✔
569
                }
570
        }
571

572
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
573
}
574

575
func (r *ServiceBindingReconciler) getServiceInstanceForBinding(ctx context.Context, binding *v1.ServiceBinding) (*v1.ServiceInstance, error) {
1✔
576
        log := logutils.GetLogger(ctx)
1✔
577
        serviceInstance := &v1.ServiceInstance{}
1✔
578
        namespace := binding.Namespace
1✔
579
        if len(binding.Spec.ServiceInstanceNamespace) > 0 {
2✔
580
                namespace = binding.Spec.ServiceInstanceNamespace
1✔
581
        }
1✔
582
        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✔
583
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.ServiceInstanceName, Namespace: namespace}, serviceInstance); err != nil {
2✔
584
                return serviceInstance, err
1✔
585
        }
1✔
586

587
        return serviceInstance.DeepCopy(), nil
1✔
588
}
589

590
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
591
        k8sBinding.Status.BindingID = smBinding.ID
1✔
592
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
593
        k8sBinding.Status.OperationURL = ""
1✔
594
        k8sBinding.Status.OperationType = ""
1✔
595

1✔
596
        bindingStatus := smClientTypes.SUCCEEDED
1✔
597
        operationType := smClientTypes.CREATE
1✔
598
        description := ""
1✔
599
        if smBinding.LastOperation != nil {
2✔
600
                bindingStatus = smBinding.LastOperation.State
1✔
601
                operationType = smBinding.LastOperation.Type
1✔
602
                description = smBinding.LastOperation.Description
1✔
603
        } else if !smBinding.Ready {
3✔
604
                bindingStatus = smClientTypes.FAILED
1✔
605
        }
1✔
606
        switch bindingStatus {
1✔
607
        case smClientTypes.PENDING:
×
608
                fallthrough
×
609
        case smClientTypes.INPROGRESS:
1✔
610
                k8sBinding.Status.OperationURL = sm.BuildOperationURL(smBinding.LastOperation.ID, smBinding.ID, smClientTypes.ServiceBindingsURL)
1✔
611
                k8sBinding.Status.OperationType = smBinding.LastOperation.Type
1✔
612
                utils.SetInProgressConditions(ctx, smBinding.LastOperation.Type, smBinding.LastOperation.Description, k8sBinding, false)
1✔
613
        case smClientTypes.SUCCEEDED:
1✔
614
                utils.SetSuccessConditions(operationType, k8sBinding, false)
1✔
615
        case smClientTypes.FAILED:
1✔
616
                utils.SetFailureConditions(operationType, description, k8sBinding, false)
1✔
617
        }
618
}
619

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

1✔
624
        var secret *corev1.Secret
1✔
625
        var err error
1✔
626

1✔
627
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
628
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
629
        } else {
2✔
630
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
631
        }
1✔
632

633
        if err != nil {
2✔
634
                return err
1✔
635
        }
1✔
636
        if err = controllerutil.SetControllerReference(k8sBinding, secret, r.Scheme); err != nil {
1✔
637
                logger.Error(err, "Failed to set secret owner")
×
638
                return err
×
639
        }
×
640

641
        if secret.Labels == nil {
1✔
642
                secret.Labels = map[string]string{}
×
643
        }
×
644
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
645
        if len(k8sBinding.Labels) > 0 && len(k8sBinding.Labels[common.StaleBindingIDLabel]) > 0 {
2✔
646
                secret.Labels[common.StaleBindingIDLabel] = k8sBinding.Labels[common.StaleBindingIDLabel]
1✔
647
        }
1✔
648

649
        if secret.Annotations == nil {
1✔
650
                secret.Annotations = map[string]string{}
×
651
        }
×
652
        secret.Annotations["binding"] = k8sBinding.Name
1✔
653

1✔
654
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
655
}
656

657
func (r *ServiceBindingReconciler) createBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
658
        credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
659
        if err != nil {
2✔
660
                return nil, err
1✔
661
        }
1✔
662

663
        secret := &corev1.Secret{
1✔
664
                ObjectMeta: metav1.ObjectMeta{
1✔
665
                        Name:        k8sBinding.Spec.SecretName,
1✔
666
                        Annotations: map[string]string{"binding": k8sBinding.Name},
1✔
667
                        Labels:      map[string]string{common.ManagedByBTPOperatorLabel: "true"},
1✔
668
                        Namespace:   k8sBinding.Namespace,
1✔
669
                },
1✔
670
                Data: credentialsMap,
1✔
671
        }
1✔
672
        return secret, nil
1✔
673
}
674

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

1✔
678
        var credentialsMap map[string][]byte
1✔
679
        var credentialProperties []utils.SecretMetadataProperty
1✔
680

1✔
681
        if len(smBinding.Credentials) == 0 {
2✔
682
                log.Info("Binding credentials are empty")
1✔
683
                credentialsMap = make(map[string][]byte)
1✔
684
        } else if k8sBinding.Spec.SecretKey != nil {
3✔
685
                credentialsMap = map[string][]byte{
1✔
686
                        *k8sBinding.Spec.SecretKey: smBinding.Credentials,
1✔
687
                }
1✔
688
                credentialProperties = []utils.SecretMetadataProperty{
1✔
689
                        {
1✔
690
                                Name:      *k8sBinding.Spec.SecretKey,
1✔
691
                                Format:    string(utils.JSON),
1✔
692
                                Container: true,
1✔
693
                        },
1✔
694
                }
1✔
695
        } else {
2✔
696
                var err error
1✔
697
                credentialsMap, credentialProperties, err = utils.NormalizeCredentials(smBinding.Credentials)
1✔
698
                if err != nil {
2✔
699
                        log.Error(err, "Failed to store binding secret")
1✔
700
                        return nil, fmt.Errorf("failed to create secret. Error: %v", err.Error())
1✔
701
                }
1✔
702
        }
703

704
        metaDataProperties, err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap)
1✔
705
        if err != nil {
1✔
706
                log.Error(err, "failed to enrich binding with service instance info")
×
707
        }
×
708

709
        if k8sBinding.Spec.SecretRootKey != nil {
2✔
710
                var err error
1✔
711
                credentialsMap, err = singleKeyMap(credentialsMap, *k8sBinding.Spec.SecretRootKey)
1✔
712
                if err != nil {
1✔
713
                        return nil, err
×
714
                }
×
715
        } else {
1✔
716
                metadata := map[string][]utils.SecretMetadataProperty{
1✔
717
                        "metaDataProperties":   metaDataProperties,
1✔
718
                        "credentialProperties": credentialProperties,
1✔
719
                }
1✔
720
                metadataByte, err := json.Marshal(metadata)
1✔
721
                if err != nil {
1✔
722
                        log.Error(err, "failed to enrich binding with metadata")
×
723
                } else {
1✔
724
                        credentialsMap[".metadata"] = metadataByte
1✔
725
                }
1✔
726
        }
727
        return credentialsMap, nil
1✔
728
}
729

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

1✔
734
        logger.Info("Create Object using SecretTemplate from ServiceBinding Specs")
1✔
735
        inputSmCredentials := smBinding.Credentials
1✔
736
        smBindingCredentials := make(map[string]interface{})
1✔
737
        if inputSmCredentials != nil {
2✔
738
                err := json.Unmarshal(inputSmCredentials, &smBindingCredentials)
1✔
739
                if err != nil {
1✔
740
                        logger.Error(err, "failed to unmarshal given service binding credentials")
×
741
                        return nil, errors.Wrap(err, "failed to unmarshal given service binding credentials")
×
742
                }
×
743
        }
744

745
        instanceInfos, err := r.getInstanceInfo(ctx, k8sBinding)
1✔
746
        if err != nil {
1✔
747
                logger.Error(err, "failed to addInstanceInfo")
×
748
                return nil, errors.Wrap(err, "failed to add service instance info")
×
749
        }
×
750

751
        parameters := commonutils.GetSecretDataForTemplate(smBindingCredentials, instanceInfos)
1✔
752
        templateName := fmt.Sprintf("%s/%s", k8sBinding.Namespace, k8sBinding.Name)
1✔
753
        secret, err := commonutils.CreateSecretFromTemplate(templateName, k8sBinding.Spec.SecretTemplate, "missingkey=error", parameters)
1✔
754
        if err != nil {
2✔
755
                logger.Error(err, "failed to create secret from template")
1✔
756
                return nil, errors.Wrap(err, "failed to create secret from template")
1✔
757
        }
1✔
758
        secret.SetNamespace(k8sBinding.Namespace)
1✔
759
        secret.SetName(k8sBinding.Spec.SecretName)
1✔
760
        if secret.Labels == nil {
1✔
761
                secret.Labels = map[string]string{}
×
762
        }
×
763
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
764

1✔
765
        // if no data provided use the default data
1✔
766
        if len(secret.Data) == 0 && len(secret.StringData) == 0 {
2✔
767
                credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
768
                if err != nil {
1✔
769
                        return nil, err
×
770
                }
×
771
                secret.Data = credentialsMap
1✔
772
        }
773
        return secret, nil
1✔
774
}
775

776
func (r *ServiceBindingReconciler) createOrUpdateBindingSecret(ctx context.Context, binding *v1.ServiceBinding, secret *corev1.Secret) error {
1✔
777
        log := logutils.GetLogger(ctx)
1✔
778
        dbSecret := &corev1.Secret{}
1✔
779
        create := false
1✔
780
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.SecretName, Namespace: binding.Namespace}, dbSecret); err != nil {
2✔
781
                if !apierrors.IsNotFound(err) {
1✔
782
                        return err
×
783
                }
×
784
                create = true
1✔
785
        }
786

787
        if create {
2✔
788
                log.Info("Creating binding secret", "name", secret.Name)
1✔
789
                if err := r.Client.Create(ctx, secret); err != nil {
1✔
790
                        if !apierrors.IsAlreadyExists(err) {
×
791
                                return err
×
792
                        }
×
793
                        return nil
×
794
                }
795
                r.Recorder.Eventf(binding, nil, corev1.EventTypeNormal, "SecretCreated", "SecretCreated", "SecretCreated")
1✔
796
                return nil
1✔
797
        }
798

799
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
800
        dbSecret.Data = secret.Data
1✔
801
        dbSecret.StringData = secret.StringData
1✔
802
        dbSecret.Labels = secret.Labels
1✔
803
        dbSecret.Annotations = secret.Annotations
1✔
804
        return r.Client.Update(ctx, dbSecret)
1✔
805
}
806

807
func (r *ServiceBindingReconciler) deleteBindingSecret(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
808
        log := logutils.GetLogger(ctx)
1✔
809
        log.Info("Deleting binding secret")
1✔
810
        bindingSecret := &corev1.Secret{}
1✔
811
        if err := r.Client.Get(ctx, types.NamespacedName{
1✔
812
                Namespace: binding.Namespace,
1✔
813
                Name:      binding.Spec.SecretName,
1✔
814
        }, bindingSecret); err != nil {
2✔
815
                if !apierrors.IsNotFound(err) {
1✔
816
                        log.Error(err, "unable to fetch binding secret")
×
817
                        return err
×
818
                }
×
819

820
                // secret not found, nothing more to do
821
                log.Info("secret was deleted successfully")
1✔
822
                return nil
1✔
823
        }
824
        bindingSecret = bindingSecret.DeepCopy()
1✔
825

1✔
826
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
827
                log.Error(err, "Failed to delete binding secret")
×
828
                return err
×
829
        }
×
830

831
        log.Info("secret was deleted successfully")
1✔
832
        return nil
1✔
833
}
834

835
func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
836
        // delete binding secret if exist
1✔
837
        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
838
                return ctrl.Result{}, err
×
839
        }
×
840

841
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
842
}
843

844
func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) {
1✔
845
        secret := &corev1.Secret{}
1✔
846
        err := utils.GetSecretWithFallback(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret)
1✔
847
        return secret, err
1✔
848
}
1✔
849

850
func (r *ServiceBindingReconciler) validateSecretNameIsAvailable(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
851
        currentSecret, err := r.getSecret(ctx, binding.Namespace, binding.Spec.SecretName)
1✔
852
        if err != nil {
2✔
853
                return client.IgnoreNotFound(err)
1✔
854
        }
1✔
855

856
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
857
                return nil
1✔
858
        }
1✔
859

860
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
861
        if ownerRef != nil {
2✔
862
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
863
                if err != nil {
1✔
864
                        return err
×
865
                }
×
866

867
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
868
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
869
                }
1✔
870
        }
871

872
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
873
}
874

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

881
func (r *ServiceBindingReconciler) getInstanceInfo(ctx context.Context, binding *v1.ServiceBinding) (map[string]string, error) {
1✔
882
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
883
        if err != nil {
1✔
884
                return nil, err
×
885
        }
×
886
        instanceInfos := make(map[string]string)
1✔
887
        instanceInfos["instance_name"] = string(getInstanceNameForSecretCredentials(instance))
1✔
888
        instanceInfos["instance_guid"] = instance.Status.InstanceID
1✔
889
        instanceInfos["plan"] = instance.Spec.ServicePlanName
1✔
890
        instanceInfos["label"] = instance.Spec.ServiceOfferingName
1✔
891
        instanceInfos["type"] = instance.Spec.ServiceOfferingName
1✔
892
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
893
                tags := mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags)
1✔
894
                instanceInfos["tags"] = strings.Join(tags, ",")
1✔
895
        }
1✔
896
        return instanceInfos, nil
1✔
897
}
898

899
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *v1.ServiceBinding, credentialsMap map[string][]byte) ([]utils.SecretMetadataProperty, error) {
1✔
900
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
901
        if err != nil {
1✔
902
                return nil, err
×
903
        }
×
904

905
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
1✔
906
        credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
1✔
907
        credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
1✔
908
        credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
1✔
909
        credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
1✔
910
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
911
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
912
                if err != nil {
1✔
913
                        return nil, err
×
914
                }
×
915
                credentialsMap["tags"] = tagsBytes
1✔
916
        }
917

918
        metadata := []utils.SecretMetadataProperty{
1✔
919
                {
1✔
920
                        Name:   "instance_name",
1✔
921
                        Format: string(utils.TEXT),
1✔
922
                },
1✔
923
                {
1✔
924
                        Name:   "instance_guid",
1✔
925
                        Format: string(utils.TEXT),
1✔
926
                },
1✔
927
                {
1✔
928
                        Name:   "plan",
1✔
929
                        Format: string(utils.TEXT),
1✔
930
                },
1✔
931
                {
1✔
932
                        Name:   "label",
1✔
933
                        Format: string(utils.TEXT),
1✔
934
                },
1✔
935
                {
1✔
936
                        Name:   "type",
1✔
937
                        Format: string(utils.TEXT),
1✔
938
                },
1✔
939
        }
1✔
940
        if _, ok := credentialsMap["tags"]; ok {
2✔
941
                metadata = append(metadata, utils.SecretMetadataProperty{Name: "tags", Format: string(utils.JSON)})
1✔
942
        }
1✔
943

944
        return metadata, nil
1✔
945
}
946

947
func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, binding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (bool, error) {
1✔
948
        log := logutils.GetLogger(ctx)
1✔
949
        if err := r.removeForceRotateAnnotationIfNeeded(ctx, binding, log); err != nil {
1✔
950
                log.Info("Credentials rotation - failed to delete force rotate annotation")
×
951
                return false, err
×
952
        }
×
953

954
        credInProgressCondition := meta.FindStatusCondition(binding.GetConditions(), common.ConditionCredRotationInProgress)
1✔
955
        if credInProgressCondition.Reason == common.CredRotating {
2✔
956
                if len(binding.Status.BindingID) > 0 && binding.Status.Ready == metav1.ConditionTrue {
2✔
957
                        log.Info("Credentials rotation - finished successfully")
1✔
958
                        now := metav1.NewTime(time.Now())
1✔
959
                        binding.Status.LastCredentialsRotationTime = &now
1✔
960
                        return false, r.stopRotation(ctx, binding)
1✔
961
                }
1✔
962
                log.Info("Credentials rotation - waiting to finish")
1✔
963
                return false, nil
1✔
964
        }
965

966
        if len(binding.Status.BindingID) == 0 {
1✔
967
                log.Info("Credentials rotation - no binding id found nothing to do")
×
968
                return false, r.stopRotation(ctx, binding)
×
969
        }
×
970

971
        bindings := &v1.ServiceBindingList{}
1✔
972
        err := r.Client.List(ctx, bindings, client.MatchingLabels{common.StaleBindingIDLabel: binding.Status.BindingID}, client.InNamespace(binding.Namespace))
1✔
973
        if err != nil {
1✔
974
                return false, err
×
975
        }
×
976

977
        if len(bindings.Items) == 0 {
2✔
978
                // create the backup binding
1✔
979
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
980
                if err != nil {
1✔
981
                        return false, err
×
982
                }
×
983

984
                // rename current binding
985
                suffix := "-" + utils.RandStringRunes(6)
1✔
986
                log.Info("Credentials rotation - renaming binding to old in SM", "current", binding.Spec.ExternalName)
1✔
987
                if _, errRenaming := smClient.RenameBinding(binding.Status.BindingID, binding.Spec.ExternalName+suffix, binding.Name+suffix); errRenaming != nil {
1✔
988
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
989
                        return true, errRenaming
×
990
                }
×
991

992
                log.Info("Credentials rotation - backing up old binding in K8S", "name", binding.Name+suffix)
1✔
993
                if err := r.createOldBinding(ctx, suffix, binding); err != nil {
1✔
994
                        log.Error(err, "Credentials rotation - failed to back up old binding in K8S")
×
995
                        return true, err
×
996
                }
×
997
        }
998

999
        log.Info("reset binding id after successful rotation")
1✔
1000
        binding.Status.BindingID = ""
1✔
1001
        binding.Status.Ready = metav1.ConditionFalse
1✔
1002
        utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
1✔
1003
        utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding)
1✔
1004
        return false, utils.UpdateStatus(ctx, r.Client, binding)
1✔
1005
}
1006

1007
func (r *ServiceBindingReconciler) removeForceRotateAnnotationIfNeeded(ctx context.Context, binding *v1.ServiceBinding, log logr.Logger) error {
1✔
1008
        if binding.Annotations != nil {
2✔
1009
                if _, ok := binding.Annotations[common.ForceRotateAnnotation]; ok {
2✔
1010
                        log.Info("Credentials rotation - deleting force rotate annotation")
1✔
1011
                        delete(binding.Annotations, common.ForceRotateAnnotation)
1✔
1012
                        return r.Client.Update(ctx, binding)
1✔
1013
                }
1✔
1014
        }
1015
        return nil
1✔
1016
}
1017

1018
func (r *ServiceBindingReconciler) stopRotation(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
1019
        conditions := binding.GetConditions()
1✔
1020
        meta.RemoveStatusCondition(&conditions, common.ConditionCredRotationInProgress)
1✔
1021
        binding.Status.Conditions = conditions
1✔
1022
        return utils.UpdateStatus(ctx, r.Client, binding)
1✔
1023
}
1✔
1024

1025
func (r *ServiceBindingReconciler) createOldBinding(ctx context.Context, suffix string, binding *v1.ServiceBinding) error {
1✔
1026
        oldBinding := newBindingObject(binding.Name+suffix, binding.Namespace)
1✔
1027
        err := controllerutil.SetControllerReference(binding, oldBinding, r.Scheme)
1✔
1028
        if err != nil {
1✔
1029
                return err
×
1030
        }
×
1031
        oldBinding.Labels = map[string]string{
1✔
1032
                common.StaleBindingIDLabel:         binding.Status.BindingID,
1✔
1033
                common.StaleBindingRotationOfLabel: truncateString(binding.Name, 63),
1✔
1034
        }
1✔
1035
        oldBinding.Annotations = map[string]string{
1✔
1036
                common.StaleBindingOrigBindingNameAnnotation: binding.Name,
1✔
1037
        }
1✔
1038
        spec := binding.Spec.DeepCopy()
1✔
1039
        spec.CredRotationPolicy.Enabled = false
1✔
1040
        spec.SecretName = spec.SecretName + suffix
1✔
1041
        spec.ExternalName = spec.ExternalName + suffix
1✔
1042
        oldBinding.Spec = *spec
1✔
1043
        return r.Client.Create(ctx, oldBinding)
1✔
1044
}
1045

1046
func (r *ServiceBindingReconciler) handleStaleServiceBinding(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
1047
        log := logutils.GetLogger(ctx)
1✔
1048
        originalBindingName, ok := serviceBinding.Annotations[common.StaleBindingOrigBindingNameAnnotation]
1✔
1049
        if !ok {
2✔
1050
                //if the user removed the "OrigBindingName" annotation and rotationOf label not exist as well
1✔
1051
                //the stale binding should be deleted otherwise it will remain forever
1✔
1052
                if originalBindingName, ok = serviceBinding.Labels[common.StaleBindingRotationOfLabel]; !ok {
2✔
1053
                        log.Info("missing rotationOf label/annotation, unable to fetch original binding, deleting stale")
1✔
1054
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1055
                }
1✔
1056
        }
1057
        origBinding := &v1.ServiceBinding{}
1✔
1058
        if err := r.Client.Get(ctx, types.NamespacedName{Namespace: serviceBinding.Namespace, Name: originalBindingName}, origBinding); err != nil {
2✔
1059
                if apierrors.IsNotFound(err) {
2✔
1060
                        log.Info("original binding not found, deleting stale binding")
1✔
1061
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1062
                }
1✔
1063
                return ctrl.Result{}, err
×
1064
        }
1065
        if meta.IsStatusConditionTrue(origBinding.Status.Conditions, common.ConditionReady) {
1✔
1066
                return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
×
1067
        }
×
1068

1069
        log.Info("not deleting stale binding since original binding is not ready")
1✔
1070
        if !meta.IsStatusConditionPresentAndEqual(serviceBinding.Status.Conditions, common.ConditionPendingTermination, metav1.ConditionTrue) {
2✔
1071
                pendingTerminationCondition := metav1.Condition{
1✔
1072
                        Type:               common.ConditionPendingTermination,
1✔
1073
                        Status:             metav1.ConditionTrue,
1✔
1074
                        Reason:             common.ConditionPendingTermination,
1✔
1075
                        Message:            "waiting for new credentials to be ready",
1✔
1076
                        ObservedGeneration: serviceBinding.GetGeneration(),
1✔
1077
                }
1✔
1078
                meta.SetStatusCondition(&serviceBinding.Status.Conditions, pendingTerminationCondition)
1✔
1079
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1080
        }
1✔
1081
        return ctrl.Result{}, nil
1✔
1082
}
1083

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

1✔
1088
        if smBinding.Credentials != nil {
2✔
1089
                if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
1090
                        operationType := smClientTypes.CREATE
×
1091
                        if smBinding.LastOperation != nil {
×
1092
                                operationType = smBinding.LastOperation.Type
×
1093
                        }
×
1094
                        return r.handleSecretError(ctx, operationType, err, serviceBinding)
×
1095
                }
1096
        }
1097
        r.resyncBindingStatus(ctx, serviceBinding, smBinding)
1✔
1098

1✔
1099
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1100
}
1101

1102
func (r *ServiceBindingReconciler) handleInstanceForBindingNotFound(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
1103
        log := logutils.GetLogger(ctx)
1✔
1104
        instanceNamespace := serviceBinding.Namespace
1✔
1105
        if len(serviceBinding.Spec.ServiceInstanceNamespace) > 0 {
1✔
1106
                instanceNamespace = serviceBinding.Spec.ServiceInstanceNamespace
×
1107
        }
×
1108
        errMsg := fmt.Sprintf("couldn't find the service instance '%s' in namespace '%s'", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1✔
1109
        log.Info(errMsg)
1✔
1110
        utils.SetBlockedCondition(ctx, errMsg, serviceBinding)
1✔
1111
        if updateErr := utils.UpdateStatus(ctx, r.Client, serviceBinding); updateErr != nil {
2✔
1112
                return ctrl.Result{}, updateErr
1✔
1113
        }
1✔
1114
        return ctrl.Result{}, fmt.Errorf("instance %s not found in namespace %s", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1✔
1115
}
1116

1117
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1118
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1119
                return false
×
1120
        }
×
1121

1122
        if binding.Labels != nil {
2✔
1123
                if _, ok := binding.Labels[common.StaleBindingIDLabel]; ok {
2✔
1124
                        if binding.Spec.CredRotationPolicy != nil {
2✔
1125
                                keepFor, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotatedBindingTTL)
1✔
1126
                                if time.Since(binding.CreationTimestamp.Time) > keepFor {
2✔
1127
                                        return true
1✔
1128
                                }
1✔
1129
                        }
1130
                }
1131
        }
1132
        return false
1✔
1133
}
1134

1135
func initCredRotationIfRequired(binding *v1.ServiceBinding) bool {
1✔
1136
        if utils.IsFailed(binding) || !credRotationEnabled(binding) {
2✔
1137
                return false
1✔
1138
        }
1✔
1139
        _, forceRotate := binding.Annotations[common.ForceRotateAnnotation]
1✔
1140

1✔
1141
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1142
        if lastCredentialRotationTime == nil {
2✔
1143
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1144
                lastCredentialRotationTime = &ts
1✔
1145
        }
1✔
1146

1147
        rotationInterval, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotationFrequency)
1✔
1148
        if time.Since(lastCredentialRotationTime.Time) > rotationInterval || forceRotate {
2✔
1149
                utils.SetCredRotationInProgressConditions(common.CredPreparing, "", binding)
1✔
1150
                return true
1✔
1151
        }
1✔
1152

1153
        return false
1✔
1154
}
1155

1156
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1157
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1158
}
1✔
1159

1160
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1161
        var tags []string
1✔
1162

1✔
1163
        for _, tag := range append(offeringTags, customTags...) {
2✔
1164
                if !utils.SliceContains(tags, tag) {
2✔
1165
                        tags = append(tags, tag)
1✔
1166
                }
1✔
1167
        }
1168
        return tags
1✔
1169
}
1170

1171
func newBindingObject(name, namespace string) *v1.ServiceBinding {
1✔
1172
        return &v1.ServiceBinding{
1✔
1173
                TypeMeta: metav1.TypeMeta{
1✔
1174
                        APIVersion: v1.GroupVersion.String(),
1✔
1175
                        Kind:       "ServiceBinding",
1✔
1176
                },
1✔
1177
                ObjectMeta: metav1.ObjectMeta{
1✔
1178
                        Name:      name,
1✔
1179
                        Namespace: namespace,
1✔
1180
                },
1✔
1181
        }
1✔
1182
}
1✔
1183

1184
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1185
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1186
}
1✔
1187

1188
func getInstanceNameForSecretCredentials(instance *v1.ServiceInstance) []byte {
1✔
1189
        if useMetaName, ok := instance.Annotations[common.UseInstanceMetadataNameInSecret]; ok && useMetaName == "true" {
2✔
1190
                return []byte(instance.Name)
1✔
1191
        }
1✔
1192
        return []byte(instance.Spec.ExternalName)
1✔
1193
}
1194

1195
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1196
        stringCredentialsMap := make(map[string]string)
1✔
1197
        for k, v := range credentialsMap {
2✔
1198
                stringCredentialsMap[k] = string(v)
1✔
1199
        }
1✔
1200

1201
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1202
        if err != nil {
1✔
1203
                return nil, err
×
1204
        }
×
1205

1206
        return map[string][]byte{
1✔
1207
                key: credBytes,
1✔
1208
        }, nil
1✔
1209
}
1210

1211
func truncateString(str string, length int) string {
1✔
1212
        if len(str) > length {
2✔
1213
                return str[:length]
1✔
1214
        }
1✔
1215
        return str
1✔
1216
}
1217

1218
func isBindingExistInSM(smClient sm.Client, instance *v1.ServiceInstance, bindingID string, log logr.Logger) (bool, error) {
1✔
1219
        log.Info("checking if k8s instance status is NotFound")
1✔
1220
        instanceReadyCond := meta.FindStatusCondition(instance.GetConditions(), common.ConditionReady)
1✔
1221
        if instanceReadyCond != nil && instanceReadyCond.Reason == common.ResourceNotFound {
2✔
1222
                log.Info("k8s instance is in NotFound state -> invalid binding")
1✔
1223
                return false, nil
1✔
1224
        }
1✔
1225

1226
        log.Info(fmt.Sprintf("trying to get from SM binding with id %s", bindingID))
1✔
1227
        if _, err := smClient.GetBindingByID(bindingID, nil); err != nil {
1✔
1228
                var smError *sm.ServiceManagerError
×
1229
                if ok := errors.As(err, &smError); ok {
×
1230
                        log.Error(smError, fmt.Sprintf("SM returned status code %d", smError.StatusCode))
×
1231
                        if smError.StatusCode == http.StatusNotFound {
×
1232
                                return false, nil
×
1233
                        }
×
1234
                }
1235
                return false, err
×
1236
        }
1237
        log.Info("binding found in SM")
1✔
1238
        return true, nil
1✔
1239
}
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