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

SAP / sap-btp-service-operator / 27672620344

17 Jun 2026 07:21AM UTC coverage: 78.02% (+0.2%) from 77.837%
27672620344

push

github

web-flow
Bug fix - loosing last operation when polling returns unexpected error (#645)

2 of 2 new or added lines in 2 files covered. (100.0%)

3 existing lines in 1 file now uncovered.

2900 of 3717 relevant lines covered (78.02%)

0.88 hits per line

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

82.61
/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
        correlationID := uuid.New().String()
1✔
88
        retry := r.Retries.Get(req.NamespacedName)
1✔
89
        if retry != nil {
2✔
90
                correlationID = retry.CorrelationID
1✔
91
        }
1✔
92
        log := r.Log.WithValues("servicebinding", req.NamespacedName).WithValues("correlation_id", correlationID, req.Name, req.Namespace)
1✔
93
        if retry != nil && time.Now().Before(retry.NextRetry) {
2✔
94
                remaining := time.Until(retry.NextRetry)
1✔
95
                log.Info(fmt.Sprintf("skipping binding reconcile due to backoff. attempts=%d retryIn=%s", retry.Attempts, remaining))
1✔
96
                return ctrl.Result{RequeueAfter: remaining}, nil
1✔
97
        }
1✔
98
        ctx = context.WithValue(ctx, logutils.LogKey, log)
1✔
99
        ctx = context.WithValue(ctx, logutils.CorrelationIDKey, correlationID)
1✔
100

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

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

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

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

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

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

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

151
        if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
152
                return r.delete(ctx, smClient, serviceBinding)
1✔
153
        }
1✔
154

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

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

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

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

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

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

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

221
                log.Info("binding in final state, maintaining secret")
1✔
222
                return r.maintain(ctx, smClient, serviceBinding)
1✔
223
        }
224

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

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

241
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
242
        }
243

244
        log.Info("nothing to do for this binding")
1✔
245
        return ctrl.Result{}, nil
1✔
246
}
247

248
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
249

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

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

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

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

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

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

301
        log.Info("Binding created successfully")
1✔
302

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

307
        subaccountID := ""
1✔
308
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
309
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
310
        }
×
311

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

1✔
319
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
320
}
321

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

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

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

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

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

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

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

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

1✔
389
        status, statusErr := smClient.Status(serviceBinding.Status.OperationURL, nil)
1✔
390
        if statusErr != nil {
2✔
391
                log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceBinding.Status.OperationURL)
1✔
392
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, serviceBinding.Status.OperationType, statusErr, true)
1✔
393
        }
1✔
394

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

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

458
                        log.Info("reset binding id after successful async delete operation")
1✔
459
                        serviceBinding.Status.BindingID = ""
1✔
460
                }
461
        }
462

463
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
464
        serviceBinding.Status.OperationURL = ""
1✔
465
        serviceBinding.Status.OperationType = ""
1✔
466
        r.Retries.Reset(types.NamespacedName{Name: serviceBinding.Name, Namespace: serviceBinding.Namespace})
1✔
467

1✔
468
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
469
}
470

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

480
        if operationURL != "" {
1✔
481
                log.Info(fmt.Sprintf("handleFailedAsyncBinding unbind is async, operation url %s", operationURL))
×
482
                serviceBinding.Status.OperationURL = operationURL
×
483
                serviceBinding.Status.OperationType = smClientTypes.DELETE
×
484
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
×
485
        }
×
486

487
        log.Info(fmt.Sprintf("handleFailedAsyncBinding binding %s deleted successfully", serviceBinding.Status.BindingID))
1✔
488
        serviceBinding.Status.OperationURL = ""
1✔
489
        serviceBinding.Status.OperationType = ""
1✔
490
        serviceBinding.Status.BindingID = ""
1✔
491
        if err := r.Client.Status().Update(ctx, serviceBinding); err != nil {
1✔
492
                log.Error(err, "handleFailedAsyncBinding failed to update service binding status after deletion")
×
493
                return ctrl.Result{}, err
×
494
        }
×
495
        return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
496
}
497

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

1✔
511
        bindings, err := smClient.ListBindings(&parameters)
1✔
512
        if err != nil {
1✔
513
                log.Error(err, "failed to list bindings in SM")
×
514
                return nil, err
×
515
        }
×
516
        if bindings != nil {
2✔
517
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
518
                if len(bindings.ServiceBindings) == 1 {
2✔
519
                        return &bindings.ServiceBindings[0], nil
1✔
520
                }
1✔
521
        }
522
        return nil, nil
1✔
523
}
524

525
func (r *ServiceBindingReconciler) maintain(ctx context.Context, smClient sm.Client, binding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
526
        log := logutils.GetLogger(ctx)
1✔
527
        if err := r.maintainSecret(ctx, smClient, binding); err != nil {
2✔
528
                log.Error(err, "failed to maintain secret")
1✔
529
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
530
        }
1✔
531

532
        log.Info("maintain finished successfully")
1✔
533
        return ctrl.Result{}, nil
1✔
534
}
535

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

545
                log.Info("binding's secret was not found")
1✔
546
                r.Recorder.Eventf(serviceBinding, nil, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted", "SecretDeleted")
1✔
547
        }
548

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

565
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
566
}
567

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

580
        return serviceInstance.DeepCopy(), nil
1✔
581
}
582

583
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
584
        k8sBinding.Status.BindingID = smBinding.ID
1✔
585
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
586
        k8sBinding.Status.OperationURL = ""
1✔
587
        k8sBinding.Status.OperationType = ""
1✔
588

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

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

1✔
617
        var secret *corev1.Secret
1✔
618
        var err error
1✔
619

1✔
620
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
621
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
622
        } else {
2✔
623
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
624
        }
1✔
625

626
        if err != nil {
2✔
627
                return err
1✔
628
        }
1✔
629
        if err = controllerutil.SetControllerReference(k8sBinding, secret, r.Scheme); err != nil {
1✔
630
                logger.Error(err, "Failed to set secret owner")
×
631
                return err
×
632
        }
×
633

634
        if secret.Labels == nil {
1✔
635
                secret.Labels = map[string]string{}
×
636
        }
×
637
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
638
        if len(k8sBinding.Labels) > 0 && len(k8sBinding.Labels[common.StaleBindingIDLabel]) > 0 {
2✔
639
                secret.Labels[common.StaleBindingIDLabel] = k8sBinding.Labels[common.StaleBindingIDLabel]
1✔
640
        }
1✔
641

642
        if secret.Annotations == nil {
1✔
643
                secret.Annotations = map[string]string{}
×
644
        }
×
645
        secret.Annotations["binding"] = k8sBinding.Name
1✔
646

1✔
647
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
648
}
649

650
func (r *ServiceBindingReconciler) createBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
651
        credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
652
        if err != nil {
2✔
653
                return nil, err
1✔
654
        }
1✔
655

656
        secret := &corev1.Secret{
1✔
657
                ObjectMeta: metav1.ObjectMeta{
1✔
658
                        Name:        k8sBinding.Spec.SecretName,
1✔
659
                        Annotations: map[string]string{"binding": k8sBinding.Name},
1✔
660
                        Labels:      map[string]string{common.ManagedByBTPOperatorLabel: "true"},
1✔
661
                        Namespace:   k8sBinding.Namespace,
1✔
662
                },
1✔
663
                Data: credentialsMap,
1✔
664
        }
1✔
665
        return secret, nil
1✔
666
}
667

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

1✔
671
        var credentialsMap map[string][]byte
1✔
672
        var credentialProperties []utils.SecretMetadataProperty
1✔
673

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

697
        metaDataProperties, err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap)
1✔
698
        if err != nil {
1✔
699
                log.Error(err, "failed to enrich binding with service instance info")
×
700
        }
×
701

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

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

1✔
727
        logger.Info("Create Object using SecretTemplate from ServiceBinding Specs")
1✔
728
        inputSmCredentials := smBinding.Credentials
1✔
729
        smBindingCredentials := make(map[string]interface{})
1✔
730
        if inputSmCredentials != nil {
2✔
731
                err := json.Unmarshal(inputSmCredentials, &smBindingCredentials)
1✔
732
                if err != nil {
1✔
733
                        logger.Error(err, "failed to unmarshal given service binding credentials")
×
734
                        return nil, errors.Wrap(err, "failed to unmarshal given service binding credentials")
×
735
                }
×
736
        }
737

738
        instanceInfos, err := r.getInstanceInfo(ctx, k8sBinding)
1✔
739
        if err != nil {
1✔
740
                logger.Error(err, "failed to addInstanceInfo")
×
741
                return nil, errors.Wrap(err, "failed to add service instance info")
×
742
        }
×
743

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

1✔
758
        // if no data provided use the default data
1✔
759
        if len(secret.Data) == 0 && len(secret.StringData) == 0 {
2✔
760
                credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
761
                if err != nil {
1✔
762
                        return nil, err
×
763
                }
×
764
                secret.Data = credentialsMap
1✔
765
        }
766
        return secret, nil
1✔
767
}
768

769
func (r *ServiceBindingReconciler) createOrUpdateBindingSecret(ctx context.Context, binding *v1.ServiceBinding, secret *corev1.Secret) error {
1✔
770
        log := logutils.GetLogger(ctx)
1✔
771
        dbSecret := &corev1.Secret{}
1✔
772
        create := false
1✔
773
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.SecretName, Namespace: binding.Namespace}, dbSecret); err != nil {
2✔
774
                if !apierrors.IsNotFound(err) {
1✔
775
                        return err
×
776
                }
×
777
                create = true
1✔
778
        }
779

780
        if create {
2✔
781
                log.Info("Creating binding secret", "name", secret.Name)
1✔
782
                if err := r.Client.Create(ctx, secret); err != nil {
1✔
783
                        if !apierrors.IsAlreadyExists(err) {
×
784
                                return err
×
785
                        }
×
786
                        return nil
×
787
                }
788
                r.Recorder.Eventf(binding, nil, corev1.EventTypeNormal, "SecretCreated", "SecretCreated", "SecretCreated")
1✔
789
                return nil
1✔
790
        }
791

792
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
793
        dbSecret.Data = secret.Data
1✔
794
        dbSecret.StringData = secret.StringData
1✔
795
        dbSecret.Labels = secret.Labels
1✔
796
        dbSecret.Annotations = secret.Annotations
1✔
797
        return r.Client.Update(ctx, dbSecret)
1✔
798
}
799

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

813
                // secret not found, nothing more to do
814
                log.Info("secret was deleted successfully")
1✔
815
                return nil
1✔
816
        }
817
        bindingSecret = bindingSecret.DeepCopy()
1✔
818

1✔
819
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
820
                log.Error(err, "Failed to delete binding secret")
×
821
                return err
×
822
        }
×
823

824
        log.Info("secret was deleted successfully")
1✔
825
        return nil
1✔
826
}
827

828
func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
829
        // delete binding secret if exist
1✔
830
        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
831
                return ctrl.Result{}, err
×
832
        }
×
833

834
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
835
}
836

837
func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) {
1✔
838
        secret := &corev1.Secret{}
1✔
839
        err := utils.GetSecretWithFallback(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret)
1✔
840
        return secret, err
1✔
841
}
1✔
842

843
func (r *ServiceBindingReconciler) validateSecretNameIsAvailable(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
844
        currentSecret, err := r.getSecret(ctx, binding.Namespace, binding.Spec.SecretName)
1✔
845
        if err != nil {
2✔
846
                return client.IgnoreNotFound(err)
1✔
847
        }
1✔
848

849
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
850
                return nil
1✔
851
        }
1✔
852

853
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
854
        if ownerRef != nil {
2✔
855
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
856
                if err != nil {
1✔
857
                        return err
×
858
                }
×
859

860
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
861
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
862
                }
1✔
863
        }
864

865
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
866
}
867

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

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

892
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *v1.ServiceBinding, credentialsMap map[string][]byte) ([]utils.SecretMetadataProperty, error) {
1✔
893
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
894
        if err != nil {
1✔
895
                return nil, err
×
896
        }
×
897

898
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
1✔
899
        credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
1✔
900
        credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
1✔
901
        credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
1✔
902
        credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
1✔
903
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
904
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
905
                if err != nil {
1✔
906
                        return nil, err
×
907
                }
×
908
                credentialsMap["tags"] = tagsBytes
1✔
909
        }
910

911
        metadata := []utils.SecretMetadataProperty{
1✔
912
                {
1✔
913
                        Name:   "instance_name",
1✔
914
                        Format: string(utils.TEXT),
1✔
915
                },
1✔
916
                {
1✔
917
                        Name:   "instance_guid",
1✔
918
                        Format: string(utils.TEXT),
1✔
919
                },
1✔
920
                {
1✔
921
                        Name:   "plan",
1✔
922
                        Format: string(utils.TEXT),
1✔
923
                },
1✔
924
                {
1✔
925
                        Name:   "label",
1✔
926
                        Format: string(utils.TEXT),
1✔
927
                },
1✔
928
                {
1✔
929
                        Name:   "type",
1✔
930
                        Format: string(utils.TEXT),
1✔
931
                },
1✔
932
        }
1✔
933
        if _, ok := credentialsMap["tags"]; ok {
2✔
934
                metadata = append(metadata, utils.SecretMetadataProperty{Name: "tags", Format: string(utils.JSON)})
1✔
935
        }
1✔
936

937
        return metadata, nil
1✔
938
}
939

940
func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, binding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (bool, error) {
1✔
941
        log := logutils.GetLogger(ctx)
1✔
942
        if err := r.removeForceRotateAnnotationIfNeeded(ctx, binding, log); err != nil {
1✔
943
                log.Info("Credentials rotation - failed to delete force rotate annotation")
×
944
                return false, err
×
945
        }
×
946

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

959
        if len(binding.Status.BindingID) == 0 {
1✔
960
                log.Info("Credentials rotation - no binding id found nothing to do")
×
961
                return false, r.stopRotation(ctx, binding)
×
962
        }
×
963

964
        bindings := &v1.ServiceBindingList{}
1✔
965
        err := r.Client.List(ctx, bindings, client.MatchingLabels{common.StaleBindingIDLabel: binding.Status.BindingID}, client.InNamespace(binding.Namespace))
1✔
966
        if err != nil {
1✔
967
                return false, err
×
968
        }
×
969

970
        if len(bindings.Items) == 0 {
2✔
971
                // create the backup binding
1✔
972
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
973
                if err != nil {
1✔
974
                        return false, err
×
975
                }
×
976

977
                // rename current binding
978
                suffix := "-" + utils.RandStringRunes(6)
1✔
979
                log.Info("Credentials rotation - renaming binding to old in SM", "current", binding.Spec.ExternalName)
1✔
980
                if _, errRenaming := smClient.RenameBinding(binding.Status.BindingID, binding.Spec.ExternalName+suffix, binding.Name+suffix); errRenaming != nil {
1✔
981
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
982
                        return true, errRenaming
×
983
                }
×
984

985
                log.Info("Credentials rotation - backing up old binding in K8S", "name", binding.Name+suffix)
1✔
986
                if err := r.createOldBinding(ctx, suffix, binding); err != nil {
1✔
987
                        log.Error(err, "Credentials rotation - failed to back up old binding in K8S")
×
988
                        return true, err
×
989
                }
×
990
        }
991

992
        log.Info("reset binding id after successful rotation")
1✔
993
        binding.Status.BindingID = ""
1✔
994
        binding.Status.Ready = metav1.ConditionFalse
1✔
995
        utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
1✔
996
        utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding)
1✔
997
        return false, utils.UpdateStatus(ctx, r.Client, binding)
1✔
998
}
999

1000
func (r *ServiceBindingReconciler) removeForceRotateAnnotationIfNeeded(ctx context.Context, binding *v1.ServiceBinding, log logr.Logger) error {
1✔
1001
        if binding.Annotations != nil {
2✔
1002
                if _, ok := binding.Annotations[common.ForceRotateAnnotation]; ok {
2✔
1003
                        log.Info("Credentials rotation - deleting force rotate annotation")
1✔
1004
                        delete(binding.Annotations, common.ForceRotateAnnotation)
1✔
1005
                        return r.Client.Update(ctx, binding)
1✔
1006
                }
1✔
1007
        }
1008
        return nil
1✔
1009
}
1010

1011
func (r *ServiceBindingReconciler) stopRotation(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
1012
        conditions := binding.GetConditions()
1✔
1013
        meta.RemoveStatusCondition(&conditions, common.ConditionCredRotationInProgress)
1✔
1014
        binding.Status.Conditions = conditions
1✔
1015
        return utils.UpdateStatus(ctx, r.Client, binding)
1✔
1016
}
1✔
1017

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

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

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

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

1✔
1081
        if smBinding.Credentials != nil {
2✔
1082
                if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
1083
                        operationType := smClientTypes.CREATE
×
1084
                        if smBinding.LastOperation != nil {
×
1085
                                operationType = smBinding.LastOperation.Type
×
1086
                        }
×
1087
                        return r.handleSecretError(ctx, operationType, err, serviceBinding)
×
1088
                }
1089
        }
1090
        r.resyncBindingStatus(ctx, serviceBinding, smBinding)
1✔
1091

1✔
1092
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1093
}
1094

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

1110
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1111
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1112
                return false
×
1113
        }
×
1114

1115
        if binding.Labels != nil {
2✔
1116
                if _, ok := binding.Labels[common.StaleBindingIDLabel]; ok {
2✔
1117
                        if binding.Spec.CredRotationPolicy != nil {
2✔
1118
                                keepFor, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotatedBindingTTL)
1✔
1119
                                if time.Since(binding.CreationTimestamp.Time) > keepFor {
2✔
1120
                                        return true
1✔
1121
                                }
1✔
1122
                        }
1123
                }
1124
        }
1125
        return false
1✔
1126
}
1127

1128
func initCredRotationIfRequired(binding *v1.ServiceBinding) bool {
1✔
1129
        if utils.IsFailed(binding) || !credRotationEnabled(binding) {
2✔
1130
                return false
1✔
1131
        }
1✔
1132
        _, forceRotate := binding.Annotations[common.ForceRotateAnnotation]
1✔
1133

1✔
1134
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1135
        if lastCredentialRotationTime == nil {
2✔
1136
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1137
                lastCredentialRotationTime = &ts
1✔
1138
        }
1✔
1139

1140
        rotationInterval, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotationFrequency)
1✔
1141
        if time.Since(lastCredentialRotationTime.Time) > rotationInterval || forceRotate {
2✔
1142
                utils.SetCredRotationInProgressConditions(common.CredPreparing, "", binding)
1✔
1143
                return true
1✔
1144
        }
1✔
1145

1146
        return false
1✔
1147
}
1148

1149
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1150
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1151
}
1✔
1152

1153
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1154
        var tags []string
1✔
1155

1✔
1156
        for _, tag := range append(offeringTags, customTags...) {
2✔
1157
                if !utils.SliceContains(tags, tag) {
2✔
1158
                        tags = append(tags, tag)
1✔
1159
                }
1✔
1160
        }
1161
        return tags
1✔
1162
}
1163

1164
func newBindingObject(name, namespace string) *v1.ServiceBinding {
1✔
1165
        return &v1.ServiceBinding{
1✔
1166
                TypeMeta: metav1.TypeMeta{
1✔
1167
                        APIVersion: v1.GroupVersion.String(),
1✔
1168
                        Kind:       "ServiceBinding",
1✔
1169
                },
1✔
1170
                ObjectMeta: metav1.ObjectMeta{
1✔
1171
                        Name:      name,
1✔
1172
                        Namespace: namespace,
1✔
1173
                },
1✔
1174
        }
1✔
1175
}
1✔
1176

1177
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1178
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1179
}
1✔
1180

1181
func getInstanceNameForSecretCredentials(instance *v1.ServiceInstance) []byte {
1✔
1182
        if useMetaName, ok := instance.Annotations[common.UseInstanceMetadataNameInSecret]; ok && useMetaName == "true" {
2✔
1183
                return []byte(instance.Name)
1✔
1184
        }
1✔
1185
        return []byte(instance.Spec.ExternalName)
1✔
1186
}
1187

1188
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1189
        stringCredentialsMap := make(map[string]string)
1✔
1190
        for k, v := range credentialsMap {
2✔
1191
                stringCredentialsMap[k] = string(v)
1✔
1192
        }
1✔
1193

1194
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1195
        if err != nil {
1✔
1196
                return nil, err
×
1197
        }
×
1198

1199
        return map[string][]byte{
1✔
1200
                key: credBytes,
1✔
1201
        }, nil
1✔
1202
}
1203

1204
func truncateString(str string, length int) string {
1✔
1205
        if len(str) > length {
2✔
1206
                return str[:length]
1✔
1207
        }
1✔
1208
        return str
1✔
1209
}
1210

1211
func isBindingExistInSM(smClient sm.Client, instance *v1.ServiceInstance, bindingID string, log logr.Logger) (bool, error) {
1✔
1212
        log.Info("checking if k8s instance status is NotFound")
1✔
1213
        instanceReadyCond := meta.FindStatusCondition(instance.GetConditions(), common.ConditionReady)
1✔
1214
        if instanceReadyCond != nil && instanceReadyCond.Reason == common.ResourceNotFound {
2✔
1215
                log.Info("k8s instance is in NotFound state -> invalid binding")
1✔
1216
                return false, nil
1✔
1217
        }
1✔
1218

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