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

SAP / sap-btp-service-operator / 20483479846

24 Dec 2025 09:49AM UTC coverage: 77.664% (-0.8%) from 78.422%
20483479846

Pull #593

github

kerenlahav
.
Pull Request #593: check if instance exists

11 of 52 new or added lines in 2 files covered. (21.15%)

215 existing lines in 3 files now uncovered.

2740 of 3528 relevant lines covered (77.66%)

0.87 hits per line

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

81.06
/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
        } else {
1✔
104
                readyCond := meta.FindStatusCondition(serviceBinding.Status.Conditions, common.ConditionReady)
1✔
105
                if readyCond != nil && readyCond.Reason == common.ResourceNotFound {
1✔
NEW
UNCOV
106
                        log.Info(fmt.Sprintf("binding id %s is not found for this cluster, deleting credentials", serviceBinding.Status.BindingID))
×
NEW
UNCOV
107
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
×
NEW
108
                                return ctrl.Result{}, err
×
NEW
109
                        }
×
NEW
UNCOV
110
                        return ctrl.Result{}, nil
×
111
                }
112
        }
113

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

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

139
        if len(serviceBinding.Status.BindingID) > 0 {
2✔
140
                if _, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil); err != nil {
1✔
NEW
141
                        var smError *sm.ServiceManagerError
×
NEW
142
                        if ok := errors.As(err, &smError); ok {
×
NEW
143
                                if smError.StatusCode == http.StatusNotFound {
×
NEW
144
                                        condition := metav1.Condition{
×
NEW
145
                                                Type:               common.ConditionReady,
×
NEW
146
                                                Status:             metav1.ConditionFalse,
×
NEW
147
                                                ObservedGeneration: serviceBinding.Generation,
×
NEW
148
                                                Reason:             common.ResourceNotFound,
×
NEW
149
                                                Message:            fmt.Sprintf("binding %s not found in Service Manager", serviceInstance.Status.InstanceID),
×
NEW
150
                                        }
×
NEW
151
                                        meta.SetStatusCondition(&serviceBinding.Status.Conditions, condition)
×
NEW
152
                                }
×
NEW
UNCOV
153
                                return ctrl.Result{RequeueAfter: time.Hour * 3}, nil
×
154
                        }
NEW
UNCOV
155
                        log.Error(err, "failed to get binding by id from SM with unknown error")
×
NEW
156
                        return ctrl.Result{}, err
×
157
                }
158
        }
159

160
        if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
161
                return r.delete(ctx, serviceBinding, serviceInstance)
1✔
162
        }
1✔
163

164
        if controllerutil.AddFinalizer(serviceBinding, common.FinalizerName) {
2✔
165
                log.Info(fmt.Sprintf("added finalizer '%s' to service binding", common.FinalizerName))
1✔
166
                if err := r.Client.Update(ctx, serviceBinding); err != nil {
1✔
UNCOV
167
                        return ctrl.Result{}, err
×
UNCOV
168
                }
×
169
        }
170

171
        if len(serviceBinding.Status.OperationURL) > 0 {
2✔
172
                // ongoing operation - poll status from SM
1✔
173
                return r.poll(ctx, serviceBinding, serviceInstance)
1✔
174
        }
1✔
175

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

182
        if !serviceInstanceReady(serviceInstance) {
2✔
183
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is not ready, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
184
                utils.SetBlockedCondition(ctx, "service instance is not ready", serviceBinding)
1✔
185
                return ctrl.Result{Requeue: true}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
186
        }
1✔
187

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

200
        // is binding ready
201
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionReady) {
2✔
202
                if isStaleServiceBinding(serviceBinding) {
2✔
203
                        log.Info("binding is stale, handling")
1✔
204
                        return r.handleStaleServiceBinding(ctx, serviceBinding)
1✔
205
                }
1✔
206

207
                if initCredRotationIfRequired(serviceBinding) {
2✔
208
                        log.Info("cred rotation required, updating status")
1✔
209
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
210
                }
1✔
211

212
                log.Info("binding in final state, maintaining secret")
1✔
213
                return r.maintain(ctx, serviceBinding, serviceInstance)
1✔
214
        }
215

216
        if serviceBinding.Status.BindingID == "" {
2✔
217
                if err := r.validateSecretNameIsAvailable(ctx, serviceBinding); err != nil {
2✔
218
                        log.Error(err, "secret validation failed")
1✔
219
                        utils.SetBlockedCondition(ctx, err.Error(), serviceBinding)
1✔
220
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
221
                }
1✔
222

223
                smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
224
                if err != nil {
1✔
UNCOV
225
                        log.Error(err, "failed to check binding recovery")
×
UNCOV
226
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
UNCOV
227
                }
×
228
                if smBinding != nil {
2✔
229
                        return r.recover(ctx, serviceBinding, smBinding)
1✔
230
                }
1✔
231

232
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
233
        }
234

235
        log.Error(fmt.Errorf("update binding is not allowed, this line should not be reached"), "")
1✔
236
        return ctrl.Result{}, nil
1✔
237
}
238

239
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
240

1✔
241
        return ctrl.NewControllerManagedBy(mgr).
1✔
242
                For(&v1.ServiceBinding{}).
1✔
243
                WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
244
                Complete(r)
1✔
245
}
1✔
246

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

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

1✔
268
        if bindErr != nil {
2✔
269
                log.Error(err, "failed to create service binding", "serviceInstanceID", serviceInstance.Status.InstanceID)
1✔
270
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, bindErr)
1✔
271
        }
1✔
272

273
        if operationURL != "" {
2✔
274
                var bindingID string
1✔
275
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
UNCOV
276
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
×
UNCOV
277
                }
×
278
                serviceBinding.Status.BindingID = bindingID
1✔
279

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

291
        log.Info("Binding created successfully")
1✔
292

1✔
293
        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
294
                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
1✔
295
        }
1✔
296

297
        subaccountID := ""
1✔
298
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
UNCOV
299
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
UNCOV
300
        }
×
301

302
        serviceBinding.Status.BindingID = smBinding.ID
1✔
303
        serviceBinding.Status.SubaccountID = subaccountID
1✔
304
        serviceBinding.Status.Ready = metav1.ConditionTrue
1✔
305
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false)
1✔
306
        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
307

1✔
308
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
309
}
310

311
func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
312
        log := logutils.GetLogger(ctx)
1✔
313
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
314
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
315
                if err != nil {
1✔
UNCOV
316
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
UNCOV
317
                }
×
318

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

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

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

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

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

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

366
                log.Info("Binding was deleted successfully")
1✔
367
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
368
        }
UNCOV
369
        return ctrl.Result{}, nil
×
370
}
371

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

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

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

398
        if status == nil {
1✔
399
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, serviceBinding.Status.OperationType, fmt.Errorf("failed to get last operation status of %s", serviceBinding.Name))
×
UNCOV
400
        }
×
401
        switch status.State {
1✔
402
        case smClientTypes.INPROGRESS:
1✔
403
                fallthrough
1✔
404
        case smClientTypes.PENDING:
1✔
405
                if len(status.Description) != 0 {
1✔
UNCOV
406
                        utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceBinding, true)
×
UNCOV
407
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
×
UNCOV
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
                // if async operation failed we should not retry
1✔
415
                utils.SetFailureConditions(status.Type, status.Description, serviceBinding, true)
1✔
416
                if serviceBinding.Status.OperationType == smClientTypes.DELETE {
2✔
417
                        serviceBinding.Status.OperationURL = ""
1✔
418
                        serviceBinding.Status.OperationType = ""
1✔
419
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
UNCOV
420
                                log.Error(err, "unable to update ServiceBinding status")
×
UNCOV
421
                                return ctrl.Result{}, err
×
UNCOV
422
                        }
×
423
                        errMsg := "Async unbind operation failed"
1✔
424
                        if status.Errors != nil {
1✔
UNCOV
425
                                errMsg = fmt.Sprintf("Async unbind operation failed, errors: %s", string(status.Errors))
×
UNCOV
426
                        }
×
427
                        return ctrl.Result{}, errors.New(errMsg)
1✔
428
                }
429
        case smClientTypes.SUCCEEDED:
1✔
430
                utils.SetSuccessConditions(status.Type, serviceBinding, true)
1✔
431
                switch serviceBinding.Status.OperationType {
1✔
432
                case smClientTypes.CREATE:
1✔
433
                        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
434
                        if err != nil || smBinding == nil {
2✔
435
                                log.Error(err, fmt.Sprintf("binding %s succeeded but could not fetch it from SM", serviceBinding.Status.BindingID))
1✔
436
                                return ctrl.Result{}, err
1✔
437
                        }
1✔
UNCOV
438
                        if len(smBinding.Labels["subaccount_id"]) > 0 {
×
UNCOV
439
                                serviceBinding.Status.SubaccountID = smBinding.Labels["subaccount_id"][0]
×
UNCOV
440
                        }
×
441

UNCOV
442
                        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
×
UNCOV
443
                                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
×
UNCOV
444
                        }
×
UNCOV
445
                        utils.SetSuccessConditions(status.Type, serviceBinding, false)
×
446
                case smClientTypes.DELETE:
1✔
447
                        return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
448
                }
449
        }
450

451
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
452
        serviceBinding.Status.OperationURL = ""
1✔
453
        serviceBinding.Status.OperationType = ""
1✔
454

1✔
455
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
456
}
457

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

1✔
471
        bindings, err := smClient.ListBindings(&parameters)
1✔
472
        if err != nil {
1✔
UNCOV
473
                log.Error(err, "failed to list bindings in SM")
×
UNCOV
474
                return nil, err
×
UNCOV
475
        }
×
476
        if bindings != nil {
2✔
477
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
478
                if len(bindings.ServiceBindings) == 1 {
2✔
479
                        return &bindings.ServiceBindings[0], nil
1✔
480
                }
1✔
481
        }
482
        return nil, nil
1✔
483
}
484

485
func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.ServiceBinding, instance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
486
        log := logutils.GetLogger(ctx)
1✔
487
        if err := r.maintainSecret(ctx, binding, instance); err != nil {
2✔
488
                log.Error(err, "failed to maintain secret")
1✔
489
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
490
        }
1✔
491

492
        log.Info("maintain finished successfully")
1✔
493
        return ctrl.Result{}, nil
1✔
494
}
495

496
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) error {
1✔
497
        log := logutils.GetLogger(ctx)
1✔
498
        if common.GetObservedGeneration(serviceBinding) == serviceBinding.Generation {
2✔
499
                log.Info("observed generation is up to date, checking if secret exists")
1✔
500
                if _, err := r.getSecret(ctx, serviceBinding.Namespace, serviceBinding.Spec.SecretName); err == nil {
2✔
501
                        log.Info("secret exists, no need to maintain secret")
1✔
502
                        return nil
1✔
503
                }
1✔
504

505
                log.Info("binding's secret was not found")
1✔
506
                r.Recorder.Event(serviceBinding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted")
1✔
507
        }
508

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

529
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
530
}
531

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

544
        return serviceInstance.DeepCopy(), nil
1✔
545
}
546

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

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

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

1✔
581
        var secret *corev1.Secret
1✔
582
        var err error
1✔
583

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

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

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

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

1✔
611
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
612
}
613

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

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

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

1✔
635
        var credentialsMap map[string][]byte
1✔
636
        var credentialProperties []utils.SecretMetadataProperty
1✔
637

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

829
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
830
}
831

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

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

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

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

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

901
        return metadata, nil
1✔
902
}
903

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

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

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

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

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

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

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

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✔
UNCOV
988
                return err
×
UNCOV
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 {
1✔
UNCOV
1018
                if apierrors.IsNotFound(err) {
×
UNCOV
1019
                        log.Info("original binding not found, deleting stale binding")
×
UNCOV
1020
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
×
UNCOV
1021
                }
×
UNCOV
1022
                return ctrl.Result{}, err
×
1023
        }
1024
        if meta.IsStatusConditionTrue(origBinding.Status.Conditions, common.ConditionReady) {
2✔
1025
                return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1026
        }
1✔
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✔
UNCOV
1049
                        operationType := smClientTypes.CREATE
×
UNCOV
1050
                        if smBinding.LastOperation != nil {
×
UNCOV
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 isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1062
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
UNCOV
1063
                return false
×
UNCOV
1064
        }
×
1065

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

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

1✔
1085
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1086
        if lastCredentialRotationTime == nil {
2✔
1087
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1088
                lastCredentialRotationTime = &ts
1✔
1089
        }
1✔
1090

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

1097
        return false
1✔
1098
}
1099

1100
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1101
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1102
}
1✔
1103

1104
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1105
        var tags []string
1✔
1106

1✔
1107
        for _, tag := range append(offeringTags, customTags...) {
2✔
1108
                if !utils.SliceContains(tags, tag) {
2✔
1109
                        tags = append(tags, tag)
1✔
1110
                }
1✔
1111
        }
1112
        return tags
1✔
1113
}
1114

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

1128
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1129
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1130
}
1✔
1131

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

1139
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1140
        stringCredentialsMap := make(map[string]string)
1✔
1141
        for k, v := range credentialsMap {
2✔
1142
                stringCredentialsMap[k] = string(v)
1✔
1143
        }
1✔
1144

1145
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1146
        if err != nil {
1✔
UNCOV
1147
                return nil, err
×
UNCOV
1148
        }
×
1149

1150
        return map[string][]byte{
1✔
1151
                key: credBytes,
1✔
1152
        }, nil
1✔
1153
}
1154

1155
func truncateString(str string, length int) string {
1✔
1156
        if len(str) > length {
2✔
1157
                return str[:length]
1✔
1158
        }
1✔
1159
        return str
1✔
1160
}
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