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

SAP / sap-btp-service-operator / 21910678631

11 Feb 2026 03:12PM UTC coverage: 78.394% (+0.1%) from 78.254%
21910678631

push

github

web-flow
Async operation failure retry (#599)

88 of 118 new or added lines in 5 files covered. (74.58%)

11 existing lines in 4 files now uncovered.

2801 of 3573 relevant lines covered (78.39%)

0.88 hits per line

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

83.35
/controllers/servicebinding_controller.go
1
/*
2

3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package controllers
18

19
import (
20
        "context"
21
        "encoding/json"
22
        "net/http"
23
        "strings"
24
        "time"
25

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

30
        "github.com/pkg/errors"
31

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

39
        "fmt"
40

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

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

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

49
        "github.com/google/uuid"
50

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

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

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

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

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

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

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

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

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

105
        serviceInstance, instanceErr := r.getServiceInstanceForBinding(ctx, serviceBinding)
1✔
106
        if instanceErr != nil {
2✔
107
                if !apierrors.IsNotFound(instanceErr) {
1✔
108
                        log.Error(instanceErr, "failed to get service instance for binding")
×
109
                        return ctrl.Result{}, instanceErr
×
NEW
110
                }
×
111
                if !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
112
                        //instance is not found and binding is not marked for deletion
1✔
113
                        return r.handleInstanceForBindingNotFound(ctx, serviceBinding)
1✔
114
                }
1✔
115
                if len(serviceBinding.Status.BindingID) == 0 {
2✔
116
                        log.Info("service instance not found, binding is marked for deletion and has no binding id, removing finalizer if exists")
1✔
117
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
NEW
118
                                return ctrl.Result{}, err
×
UNCOV
119
                        }
×
120
                        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
121
                }
122
        }
123

124
        if shouldBindingBeDeleted(serviceBinding) {
2✔
125
                return r.delete(ctx, serviceBinding, serviceInstance)
1✔
126
        }
1✔
127

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

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

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

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

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

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

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

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

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

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

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

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

NEW
226
        log.Info("nothing to do for this binding")
×
UNCOV
227
        return ctrl.Result{}, nil
×
228
}
229

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
375
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
376
        if err != nil {
1✔
377
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
378
        }
×
379

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

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

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

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

457
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
458
        serviceBinding.Status.OperationURL = ""
1✔
459
        serviceBinding.Status.OperationType = ""
1✔
460

1✔
461
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
462
}
463

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

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

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

498
        log.Info("maintain finished successfully")
1✔
499
        return ctrl.Result{}, nil
1✔
500
}
501

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

511
                log.Info("binding's secret was not found")
1✔
512
                r.Recorder.Event(serviceBinding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted")
1✔
513
        }
514

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

531
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
532
}
533

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

546
        return serviceInstance.DeepCopy(), nil
1✔
547
}
548

549
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
550
        k8sBinding.Status.BindingID = smBinding.ID
1✔
551
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
552
        k8sBinding.Status.OperationURL = ""
1✔
553
        k8sBinding.Status.OperationType = ""
1✔
554

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

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

1✔
583
        var secret *corev1.Secret
1✔
584
        var err error
1✔
585

1✔
586
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
587
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
588
        } else {
2✔
589
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
590
        }
1✔
591

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

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

608
        if secret.Annotations == nil {
1✔
609
                secret.Annotations = map[string]string{}
×
610
        }
×
611
        secret.Annotations["binding"] = k8sBinding.Name
1✔
612

1✔
613
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
614
}
615

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

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

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

1✔
637
        var credentialsMap map[string][]byte
1✔
638
        var credentialProperties []utils.SecretMetadataProperty
1✔
639

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

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

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

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

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

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

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

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

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

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

758
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
759
        dbSecret.Data = secret.Data
1✔
760
        dbSecret.StringData = secret.StringData
1✔
761
        dbSecret.Labels = secret.Labels
1✔
762
        dbSecret.Annotations = secret.Annotations
1✔
763
        return r.Client.Update(ctx, dbSecret)
1✔
764
}
765

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

779
                // secret not found, nothing more to do
780
                log.Info("secret was deleted successfully")
1✔
781
                return nil
1✔
782
        }
783
        bindingSecret = bindingSecret.DeepCopy()
1✔
784

1✔
785
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
786
                log.Error(err, "Failed to delete binding secret")
×
787
                return err
×
788
        }
×
789

790
        log.Info("secret was deleted successfully")
1✔
791
        return nil
1✔
792
}
793

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

800
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
801
}
802

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

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

815
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
816
                return nil
1✔
817
        }
1✔
818

819
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
820
        if ownerRef != nil {
2✔
821
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
822
                if err != nil {
1✔
823
                        return err
×
824
                }
×
825

826
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
827
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
828
                }
1✔
829
        }
830

831
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
832
}
833

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

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

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

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

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

903
        return metadata, nil
1✔
904
}
905

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1058
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1059
}
1060

1061
func (r *ServiceBindingReconciler) handleInstanceForBindingNotFound(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
1062
        log := logutils.GetLogger(ctx)
1✔
1063
        instanceNamespace := serviceBinding.Namespace
1✔
1064
        if len(serviceBinding.Spec.ServiceInstanceNamespace) > 0 {
1✔
NEW
1065
                instanceNamespace = serviceBinding.Spec.ServiceInstanceNamespace
×
NEW
1066
        }
×
1067
        errMsg := fmt.Sprintf("couldn't find the service instance '%s' in namespace '%s'", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1✔
1068
        log.Info(errMsg)
1✔
1069
        utils.SetBlockedCondition(ctx, errMsg, serviceBinding)
1✔
1070
        if updateErr := utils.UpdateStatus(ctx, r.Client, serviceBinding); updateErr != nil {
2✔
1071
                return ctrl.Result{}, updateErr
1✔
1072
        }
1✔
1073
        return ctrl.Result{}, fmt.Errorf("instance %s not found in namespace %s", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1✔
1074
}
1075

1076
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1077
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1078
                return false
×
1079
        }
×
1080

1081
        if binding.Labels != nil {
2✔
1082
                if _, ok := binding.Labels[common.StaleBindingIDLabel]; ok {
2✔
1083
                        if binding.Spec.CredRotationPolicy != nil {
2✔
1084
                                keepFor, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotatedBindingTTL)
1✔
1085
                                if time.Since(binding.CreationTimestamp.Time) > keepFor {
2✔
1086
                                        return true
1✔
1087
                                }
1✔
1088
                        }
1089
                }
1090
        }
1091
        return false
1✔
1092
}
1093

1094
func initCredRotationIfRequired(binding *v1.ServiceBinding) bool {
1✔
1095
        if utils.IsFailed(binding) || !credRotationEnabled(binding) {
2✔
1096
                return false
1✔
1097
        }
1✔
1098
        _, forceRotate := binding.Annotations[common.ForceRotateAnnotation]
1✔
1099

1✔
1100
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1101
        if lastCredentialRotationTime == nil {
2✔
1102
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1103
                lastCredentialRotationTime = &ts
1✔
1104
        }
1✔
1105

1106
        rotationInterval, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotationFrequency)
1✔
1107
        if time.Since(lastCredentialRotationTime.Time) > rotationInterval || forceRotate {
2✔
1108
                utils.SetCredRotationInProgressConditions(common.CredPreparing, "", binding)
1✔
1109
                return true
1✔
1110
        }
1✔
1111

1112
        return false
1✔
1113
}
1114

1115
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1116
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1117
}
1✔
1118

1119
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1120
        var tags []string
1✔
1121

1✔
1122
        for _, tag := range append(offeringTags, customTags...) {
2✔
1123
                if !utils.SliceContains(tags, tag) {
2✔
1124
                        tags = append(tags, tag)
1✔
1125
                }
1✔
1126
        }
1127
        return tags
1✔
1128
}
1129

1130
func newBindingObject(name, namespace string) *v1.ServiceBinding {
1✔
1131
        return &v1.ServiceBinding{
1✔
1132
                TypeMeta: metav1.TypeMeta{
1✔
1133
                        APIVersion: v1.GroupVersion.String(),
1✔
1134
                        Kind:       "ServiceBinding",
1✔
1135
                },
1✔
1136
                ObjectMeta: metav1.ObjectMeta{
1✔
1137
                        Name:      name,
1✔
1138
                        Namespace: namespace,
1✔
1139
                },
1✔
1140
        }
1✔
1141
}
1✔
1142

1143
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1144
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1145
}
1✔
1146

1147
func getInstanceNameForSecretCredentials(instance *v1.ServiceInstance) []byte {
1✔
1148
        if useMetaName, ok := instance.Annotations[common.UseInstanceMetadataNameInSecret]; ok && useMetaName == "true" {
2✔
1149
                return []byte(instance.Name)
1✔
1150
        }
1✔
1151
        return []byte(instance.Spec.ExternalName)
1✔
1152
}
1153

1154
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1155
        stringCredentialsMap := make(map[string]string)
1✔
1156
        for k, v := range credentialsMap {
2✔
1157
                stringCredentialsMap[k] = string(v)
1✔
1158
        }
1✔
1159

1160
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1161
        if err != nil {
1✔
1162
                return nil, err
×
1163
        }
×
1164

1165
        return map[string][]byte{
1✔
1166
                key: credBytes,
1✔
1167
        }, nil
1✔
1168
}
1169

1170
func truncateString(str string, length int) string {
1✔
1171
        if len(str) > length {
2✔
1172
                return str[:length]
1✔
1173
        }
1✔
1174
        return str
1✔
1175
}
1176

1177
func isBindingExistInSM(smClient sm.Client, instance *v1.ServiceInstance, bindingID string, log logr.Logger) (bool, error) {
1✔
1178
        log.Info("checking if k8s instance status is NotFound")
1✔
1179
        instanceReadyCond := meta.FindStatusCondition(instance.GetConditions(), common.ConditionReady)
1✔
1180
        if instanceReadyCond != nil && instanceReadyCond.Reason == common.ResourceNotFound {
2✔
1181
                log.Info("k8s instance is in NotFound state -> invalid binding")
1✔
1182
                return false, nil
1✔
1183
        }
1✔
1184

1185
        log.Info(fmt.Sprintf("trying to get from SM binding with id %s", bindingID))
1✔
1186
        if _, err := smClient.GetBindingByID(bindingID, nil); err != nil {
1✔
1187
                var smError *sm.ServiceManagerError
×
1188
                if ok := errors.As(err, &smError); ok {
×
1189
                        log.Error(smError, fmt.Sprintf("SM returned status code %d", smError.StatusCode))
×
1190
                        if smError.StatusCode == http.StatusNotFound {
×
1191
                                return false, nil
×
1192
                        }
×
1193
                }
1194
                return false, err
×
1195
        }
1196
        log.Info("binding found in SM")
1✔
1197
        return true, nil
1✔
1198
}
1199

1200
func shouldBindingBeDeleted(serviceBinding *v1.ServiceBinding) bool {
1✔
1201
        return utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) ||
1✔
1202
                (len(serviceBinding.Status.OperationURL) == 0 && len(serviceBinding.Status.BindingID) > 0 && serviceBinding.Status.Ready == metav1.ConditionFalse)
1✔
1203
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc