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

SAP / sap-btp-service-operator / 17354254741

31 Aug 2025 07:45AM UTC coverage: 78.37% (-1.6%) from 80.011%
17354254741

Pull #538

github

kerenlahav
delete irrelevant comments
Pull Request #538: transient error - prototype

52 of 142 new or added lines in 5 files covered. (36.62%)

17 existing lines in 3 files now uncovered.

2750 of 3509 relevant lines covered (78.37%)

0.88 hits per line

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

81.93
/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
        "strings"
23
        "time"
24

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

28
        "github.com/pkg/errors"
29

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

37
        "fmt"
38

39
        "k8s.io/client-go/util/workqueue"
40
        "sigs.k8s.io/controller-runtime/pkg/controller"
41

42
        v1 "github.com/SAP/sap-btp-service-operator/api/v1"
43

44
        "k8s.io/apimachinery/pkg/api/meta"
45
        "k8s.io/apimachinery/pkg/runtime/schema"
46

47
        "github.com/google/uuid"
48

49
        "github.com/SAP/sap-btp-service-operator/client/sm"
50

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

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

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

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

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

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

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

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

103
        serviceInstance, instanceErr := r.getServiceInstanceForBinding(ctx, serviceBinding)
1✔
104
        if instanceErr != nil {
2✔
105
                if !apierrors.IsNotFound(instanceErr) {
1✔
106
                        log.Error(instanceErr, "failed to get service instance for binding")
×
107
                        return ctrl.Result{}, instanceErr
×
108
                } else if !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
109
                        //instance is not found and binding is not marked for deletion
1✔
110
                        instanceNamespace := serviceBinding.Namespace
1✔
111
                        if len(serviceBinding.Spec.ServiceInstanceNamespace) > 0 {
1✔
112
                                instanceNamespace = serviceBinding.Spec.ServiceInstanceNamespace
×
113
                        }
×
114
                        errMsg := fmt.Sprintf("couldn't find the service instance '%s' in namespace '%s'", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1✔
115
                        utils.SetBlockedCondition(ctx, errMsg, serviceBinding)
1✔
116
                        if updateErr := utils.UpdateStatus(ctx, r.Client, serviceBinding); updateErr != nil {
2✔
117
                                return ctrl.Result{}, updateErr
1✔
118
                        }
1✔
119
                        return ctrl.Result{}, instanceErr
1✔
120
                }
121
        }
122

123
        if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
124
                return r.delete(ctx, serviceBinding, serviceInstance)
1✔
125
        }
1✔
126

127
        if controllerutil.AddFinalizer(serviceBinding, common.FinalizerName) {
2✔
128
                log.Info(fmt.Sprintf("added finalizer '%s' to service binding", common.FinalizerName))
1✔
129
                if err := r.Client.Update(ctx, serviceBinding); err != nil {
1✔
130
                        return ctrl.Result{}, err
×
131
                }
×
132
        }
133

134
        if len(serviceBinding.Status.OperationURL) > 0 {
2✔
135
                // ongoing operation - poll status from SM
1✔
136
                return r.poll(ctx, serviceBinding, serviceInstance)
1✔
137
        }
1✔
138

139
        if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
2✔
140
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is marked for deletion, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
141
                utils.SetBlockedCondition(ctx, "instance is in deletion process", serviceBinding)
1✔
142
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
143
        }
1✔
144

145
        if !serviceInstanceReady(serviceInstance) {
2✔
146
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is not ready, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
147
                utils.SetBlockedCondition(ctx, "service instance is not ready", serviceBinding)
1✔
148
                return ctrl.Result{Requeue: true}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
149
        }
1✔
150

151
        // should rotate creds
152
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionCredRotationInProgress) {
2✔
153
                log.Info("rotating credentials")
1✔
154
                if shouldUpdateStatus, err := r.rotateCredentials(ctx, serviceBinding, serviceInstance); err != nil {
1✔
UNCOV
155
                        if !shouldUpdateStatus {
×
UNCOV
156
                                log.Error(err, "internal error occurred during cred rotation, requeuing binding")
×
UNCOV
157
                                return ctrl.Result{}, err
×
UNCOV
158
                        }
×
159
                        return utils.HandleCredRotationError(ctx, r.Client, serviceBinding, err)
×
160
                }
161
        }
162

163
        // is binding ready
164
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionReady) {
2✔
165
                if isStaleServiceBinding(serviceBinding) {
2✔
166
                        log.Info("binding is stale, handling")
1✔
167
                        return r.handleStaleServiceBinding(ctx, serviceBinding)
1✔
168
                }
1✔
169

170
                if initCredRotationIfRequired(serviceBinding) {
2✔
171
                        log.Info("cred rotation required, updating status")
1✔
172
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
173
                }
1✔
174

175
                log.Info("binding in final state, maintaining secret")
1✔
176
                return r.maintain(ctx, serviceBinding, serviceInstance)
1✔
177
        }
178

179
        //set owner instance only for original bindings (not rotated)
180
        if serviceBinding.Labels == nil || len(serviceBinding.Labels[common.StaleBindingIDLabel]) == 0 {
2✔
181
                if !bindingAlreadyOwnedByInstance(serviceInstance, serviceBinding) &&
1✔
182
                        serviceInstance.Namespace == serviceBinding.Namespace { //cross namespace reference not allowed
2✔
183
                        if err := r.setOwner(ctx, serviceInstance, serviceBinding); err != nil {
1✔
184
                                log.Error(err, "failed to set owner reference for binding")
×
185
                                return ctrl.Result{}, err
×
186
                        }
×
187
                }
188
        }
189

190
        if serviceBinding.Status.BindingID == "" {
2✔
191
                if err := r.validateSecretNameIsAvailable(ctx, serviceBinding); err != nil {
2✔
192
                        log.Error(err, "secret validation failed")
1✔
193
                        utils.SetBlockedCondition(ctx, err.Error(), serviceBinding)
1✔
194
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
195
                }
1✔
196

197
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
198
                if err != nil {
1✔
NEW
199
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
200
                }
×
201

202
                smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
203
                if err != nil {
1✔
204
                        log.Error(err, "failed to check binding recovery")
×
NEW
205
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
206
                }
×
207
                if smBinding != nil {
2✔
208
                        return r.recover(ctx, serviceBinding, smBinding)
1✔
209
                }
1✔
210

211
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
212
        }
213

214
        log.Error(fmt.Errorf("update binding is not allowed, this line should not be reached"), "")
1✔
215
        return ctrl.Result{}, nil
1✔
216
}
217

218
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
219

1✔
220
        return ctrl.NewControllerManagedBy(mgr).
1✔
221
                For(&v1.ServiceBinding{}).
1✔
222
                WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
223
                Complete(r)
1✔
224
}
1✔
225

226
func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
227
        log := utils.GetLogger(ctx)
1✔
228
        log.Info("Creating smBinding in SM")
1✔
229
        serviceBinding.Status.InstanceID = serviceInstance.Status.InstanceID
1✔
230
        bindingParameters, _, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.Parameters, serviceBinding.Spec.ParametersFrom)
1✔
231
        if err != nil {
1✔
232
                log.Error(err, "failed to parse smBinding parameters")
×
NEW
233
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
234
        }
×
235

236
        smBinding, operationURL, bindErr := smClient.Bind(&smClientTypes.ServiceBinding{
1✔
237
                Name: serviceBinding.Spec.ExternalName,
1✔
238
                Labels: smClientTypes.Labels{
1✔
239
                        common.NamespaceLabel: []string{serviceBinding.Namespace},
1✔
240
                        common.K8sNameLabel:   []string{serviceBinding.Name},
1✔
241
                        common.ClusterIDLabel: []string{r.Config.ClusterID},
1✔
242
                },
1✔
243
                ServiceInstanceID: serviceInstance.Status.InstanceID,
1✔
244
                Parameters:        bindingParameters,
1✔
245
        }, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
246

1✔
247
        if bindErr != nil {
2✔
248
                log.Error(err, "failed to create service binding", "serviceInstanceID", serviceInstance.Status.InstanceID)
1✔
249
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, bindErr)
1✔
250
        }
1✔
251

252
        if operationURL != "" {
2✔
253
                var bindingID string
1✔
254
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
NEW
255
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
×
256
                }
×
257
                serviceBinding.Status.BindingID = bindingID
1✔
258

1✔
259
                log.Info("Create smBinding request is async")
1✔
260
                serviceBinding.Status.OperationURL = operationURL
1✔
261
                serviceBinding.Status.OperationType = smClientTypes.CREATE
1✔
262
                utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceBinding, false)
1✔
263
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
2✔
264
                        log.Error(err, "unable to update ServiceBinding status")
1✔
265
                        return ctrl.Result{}, err
1✔
266
                }
1✔
267
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
268
        }
269

270
        log.Info("Binding created successfully")
1✔
271

1✔
272
        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
273
                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
1✔
274
        }
1✔
275

276
        subaccountID := ""
1✔
277
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
278
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
279
        }
×
280

281
        serviceBinding.Status.BindingID = smBinding.ID
1✔
282
        serviceBinding.Status.SubaccountID = subaccountID
1✔
283
        serviceBinding.Status.Ready = metav1.ConditionTrue
1✔
284
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false)
1✔
285
        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
286

1✔
287
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
288
}
289

290
func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
291
        log := utils.GetLogger(ctx)
1✔
292
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
293
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
294
                if err != nil {
1✔
NEW
295
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
296
                }
×
297

298
                if len(serviceBinding.Status.BindingID) == 0 {
2✔
299
                        log.Info("No binding id found validating binding does not exists in SM before removing finalizer")
1✔
300
                        smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
301
                        if err != nil {
1✔
NEW
302
                                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
303
                        }
×
304
                        if smBinding != nil {
2✔
305
                                log.Info("binding exists in SM continue with deletion")
1✔
306
                                serviceBinding.Status.BindingID = smBinding.ID
1✔
307
                                utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceBinding, false)
1✔
308
                                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
309
                        }
1✔
310

311
                        // make sure there's no secret stored for the binding
312
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
313
                                return ctrl.Result{}, err
×
314
                        }
×
315

316
                        log.Info("Binding does not exists in SM, removing finalizer")
1✔
317
                        if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName); err != nil {
1✔
318
                                return ctrl.Result{}, err
×
319
                        }
×
320
                        return ctrl.Result{}, nil
1✔
321
                }
322

323
                if len(serviceBinding.Status.OperationURL) > 0 && serviceBinding.Status.OperationType == smClientTypes.DELETE {
2✔
324
                        // ongoing delete operation - poll status from SM
1✔
325
                        return r.poll(ctx, serviceBinding, serviceInstance)
1✔
326
                }
1✔
327

328
                log.Info(fmt.Sprintf("Deleting binding with id %v from SM", serviceBinding.Status.BindingID))
1✔
329
                operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
330
                if unbindErr != nil {
2✔
331
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr)
1✔
332
                }
1✔
333

334
                if operationURL != "" {
2✔
335
                        log.Info("Deleting binding async")
1✔
336
                        serviceBinding.Status.OperationURL = operationURL
1✔
337
                        serviceBinding.Status.OperationType = smClientTypes.DELETE
1✔
338
                        utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceBinding, false)
1✔
339
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
340
                                return ctrl.Result{}, err
×
341
                        }
×
342
                        return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
343
                }
344

345
                log.Info("Binding was deleted successfully")
1✔
346
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
347
        }
348
        return ctrl.Result{}, nil
×
349
}
350

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

1✔
355
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
356
        if err != nil {
1✔
NEW
357
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
358
        }
×
359

360
        status, statusErr := smClient.Status(serviceBinding.Status.OperationURL, nil)
1✔
361
        if statusErr != nil {
2✔
362
                log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceBinding.Status.OperationURL)
1✔
363
                utils.SetInProgressConditions(ctx, serviceBinding.Status.OperationType, string(smClientTypes.INPROGRESS), serviceBinding, false)
1✔
364
                freshStatus := v1.ServiceBindingStatus{
1✔
365
                        Conditions: serviceBinding.GetConditions(),
1✔
366
                }
1✔
367
                if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
368
                        freshStatus.BindingID = serviceBinding.Status.BindingID
1✔
369
                }
1✔
370
                serviceBinding.Status = freshStatus
1✔
371
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
372
                        log.Error(err, "failed to update status during polling")
×
373
                }
×
374
                return ctrl.Result{}, statusErr
1✔
375
        }
376

377
        if status == nil {
1✔
NEW
378
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, serviceBinding.Status.OperationType, fmt.Errorf("failed to get last operation status of %s", serviceBinding.Name))
×
379
        }
×
380
        switch status.State {
1✔
381
        case smClientTypes.INPROGRESS:
1✔
382
                fallthrough
1✔
383
        case smClientTypes.PENDING:
1✔
384
                if len(status.Description) != 0 {
1✔
385
                        utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceBinding, true)
×
386
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
×
387
                                log.Error(err, "unable to update ServiceBinding polling description")
×
388
                                return ctrl.Result{}, err
×
389
                        }
×
390
                }
391
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
392
        case smClientTypes.FAILED:
1✔
393
                // if async operation failed we should not retry
1✔
394
                utils.SetFailureConditions(status.Type, status.Description, serviceBinding, true)
1✔
395
                if serviceBinding.Status.OperationType == smClientTypes.DELETE {
2✔
396
                        serviceBinding.Status.OperationURL = ""
1✔
397
                        serviceBinding.Status.OperationType = ""
1✔
398
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
399
                                log.Error(err, "unable to update ServiceBinding status")
×
400
                                return ctrl.Result{}, err
×
401
                        }
×
402
                        errMsg := "Async unbind operation failed"
1✔
403
                        if status.Errors != nil {
1✔
404
                                errMsg = fmt.Sprintf("Async unbind operation failed, errors: %s", string(status.Errors))
×
405
                        }
×
406
                        return ctrl.Result{}, errors.New(errMsg)
1✔
407
                }
408
        case smClientTypes.SUCCEEDED:
1✔
409
                utils.SetSuccessConditions(status.Type, serviceBinding, true)
1✔
410
                switch serviceBinding.Status.OperationType {
1✔
411
                case smClientTypes.CREATE:
1✔
412
                        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
413
                        if err != nil || smBinding == nil {
2✔
414
                                log.Error(err, fmt.Sprintf("binding %s succeeded but could not fetch it from SM", serviceBinding.Status.BindingID))
1✔
415
                                return ctrl.Result{}, err
1✔
416
                        }
1✔
417
                        if len(smBinding.Labels["subaccount_id"]) > 0 {
×
418
                                serviceBinding.Status.SubaccountID = smBinding.Labels["subaccount_id"][0]
×
419
                        }
×
420

421
                        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
×
422
                                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
×
423
                        }
×
424
                        utils.SetSuccessConditions(status.Type, serviceBinding, false)
×
425
                case smClientTypes.DELETE:
1✔
426
                        return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
427
                }
428
        }
429

430
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
431
        serviceBinding.Status.OperationURL = ""
1✔
432
        serviceBinding.Status.OperationType = ""
1✔
433

1✔
434
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
435
}
436

437
func (r *ServiceBindingReconciler) getBindingForRecovery(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (*smClientTypes.ServiceBinding, error) {
1✔
438
        log := utils.GetLogger(ctx)
1✔
439
        nameQuery := fmt.Sprintf("name eq '%s'", serviceBinding.Spec.ExternalName)
1✔
440
        clusterIDQuery := fmt.Sprintf("context/clusterid eq '%s'", r.Config.ClusterID)
1✔
441
        namespaceQuery := fmt.Sprintf("context/namespace eq '%s'", serviceBinding.Namespace)
1✔
442
        k8sNameQuery := fmt.Sprintf("%s eq '%s'", common.K8sNameLabel, serviceBinding.Name)
1✔
443
        parameters := sm.Parameters{
1✔
444
                FieldQuery:    []string{nameQuery, clusterIDQuery, namespaceQuery},
1✔
445
                LabelQuery:    []string{k8sNameQuery},
1✔
446
                GeneralParams: []string{"attach_last_operations=true"},
1✔
447
        }
1✔
448
        log.Info(fmt.Sprintf("binding recovery query params: %s, %s, %s, %s", nameQuery, clusterIDQuery, namespaceQuery, k8sNameQuery))
1✔
449

1✔
450
        bindings, err := smClient.ListBindings(&parameters)
1✔
451
        if err != nil {
1✔
452
                log.Error(err, "failed to list bindings in SM")
×
453
                return nil, err
×
454
        }
×
455
        if bindings != nil {
2✔
456
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
457
                if len(bindings.ServiceBindings) == 1 {
2✔
458
                        return &bindings.ServiceBindings[0], nil
1✔
459
                }
1✔
460
        }
461
        return nil, nil
1✔
462
}
463

464
func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.ServiceBinding, instance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
465
        log := utils.GetLogger(ctx)
1✔
466
        if err := r.maintainSecret(ctx, binding, instance); err != nil {
2✔
467
                log.Error(err, "failed to maintain secret")
1✔
468
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
469
        }
1✔
470

471
        log.Info("maintain finished successfully")
1✔
472
        return ctrl.Result{}, nil
1✔
473
}
474

475
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) error {
1✔
476
        log := utils.GetLogger(ctx)
1✔
477
        if common.GetObservedGeneration(serviceBinding) == serviceBinding.Generation {
2✔
478
                log.Info("observed generation is up to date, checking if secret exists")
1✔
479
                if _, err := r.getSecret(ctx, serviceBinding.Namespace, serviceBinding.Spec.SecretName); err == nil {
2✔
480
                        log.Info("secret exists, no need to maintain secret")
1✔
481
                        return nil
1✔
482
                }
1✔
483

484
                log.Info("binding's secret was not found")
1✔
485
                r.Recorder.Event(serviceBinding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted")
1✔
486
        }
487

488
        log.Info("maintaining binding's secret")
1✔
489
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
490
        if err != nil {
1✔
491
                return err
×
492
        }
×
493
        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
494
        if err != nil {
1✔
495
                log.Error(err, "failed to get binding for update secret")
×
496
                return err
×
497
        }
×
498
        if smBinding != nil {
2✔
499
                if smBinding.Credentials != nil {
2✔
500
                        if err = r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
501
                                return err
1✔
502
                        }
1✔
503
                        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
504
                        utils.SetSuccessConditions(smClientTypes.UPDATE, serviceBinding, false)
1✔
505
                }
506
        }
507

508
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
509
}
510

511
func (r *ServiceBindingReconciler) getServiceInstanceForBinding(ctx context.Context, binding *v1.ServiceBinding) (*v1.ServiceInstance, error) {
1✔
512
        log := utils.GetLogger(ctx)
1✔
513
        serviceInstance := &v1.ServiceInstance{}
1✔
514
        namespace := binding.Namespace
1✔
515
        if len(binding.Spec.ServiceInstanceNamespace) > 0 {
2✔
516
                namespace = binding.Spec.ServiceInstanceNamespace
1✔
517
        }
1✔
518
        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✔
519
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.ServiceInstanceName, Namespace: namespace}, serviceInstance); err != nil {
2✔
520
                return serviceInstance, err
1✔
521
        }
1✔
522

523
        return serviceInstance.DeepCopy(), nil
1✔
524
}
525

526
func (r *ServiceBindingReconciler) setOwner(ctx context.Context, serviceInstance *v1.ServiceInstance, serviceBinding *v1.ServiceBinding) error {
1✔
527
        log := utils.GetLogger(ctx)
1✔
528
        log.Info("Binding instance as owner of binding", "bindingName", serviceBinding.Name, "instanceName", serviceInstance.Name)
1✔
529
        if err := controllerutil.SetControllerReference(serviceInstance, serviceBinding, r.Scheme); err != nil {
1✔
530
                log.Error(err, fmt.Sprintf("Could not update the smBinding %s owner instance reference", serviceBinding.Name))
×
531
                return err
×
532
        }
×
533
        if err := r.Client.Update(ctx, serviceBinding); err != nil {
1✔
534
                log.Error(err, "Failed to set controller reference", "bindingName", serviceBinding.Name)
×
535
                return err
×
536
        }
×
537
        return nil
1✔
538
}
539

540
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
541
        k8sBinding.Status.BindingID = smBinding.ID
1✔
542
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
543
        k8sBinding.Status.OperationURL = ""
1✔
544
        k8sBinding.Status.OperationType = ""
1✔
545

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

570
func (r *ServiceBindingReconciler) storeBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) error {
1✔
571
        log := utils.GetLogger(ctx)
1✔
572
        logger := log.WithValues("bindingName", k8sBinding.Name, "secretName", k8sBinding.Spec.SecretName)
1✔
573

1✔
574
        var secret *corev1.Secret
1✔
575
        var err error
1✔
576

1✔
577
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
578
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
579
        } else {
2✔
580
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
581
        }
1✔
582

583
        if err != nil {
2✔
584
                return err
1✔
585
        }
1✔
586
        if err = controllerutil.SetControllerReference(k8sBinding, secret, r.Scheme); err != nil {
1✔
587
                logger.Error(err, "Failed to set secret owner")
×
588
                return err
×
589
        }
×
590

591
        if secret.Labels == nil {
1✔
592
                secret.Labels = map[string]string{}
×
593
        }
×
594
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
595

1✔
596
        if secret.Annotations == nil {
1✔
597
                secret.Annotations = map[string]string{}
×
598
        }
×
599
        secret.Annotations["binding"] = k8sBinding.Name
1✔
600

1✔
601
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
602
}
603

604
func (r *ServiceBindingReconciler) createBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
605
        credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
606
        if err != nil {
2✔
607
                return nil, err
1✔
608
        }
1✔
609

610
        secret := &corev1.Secret{
1✔
611
                ObjectMeta: metav1.ObjectMeta{
1✔
612
                        Name:        k8sBinding.Spec.SecretName,
1✔
613
                        Annotations: map[string]string{"binding": k8sBinding.Name},
1✔
614
                        Labels:      map[string]string{common.ManagedByBTPOperatorLabel: "true"},
1✔
615
                        Namespace:   k8sBinding.Namespace,
1✔
616
                },
1✔
617
                Data: credentialsMap,
1✔
618
        }
1✔
619
        return secret, nil
1✔
620
}
621

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

1✔
625
        var credentialsMap map[string][]byte
1✔
626
        var credentialProperties []utils.SecretMetadataProperty
1✔
627

1✔
628
        if len(smBinding.Credentials) == 0 {
1✔
629
                log.Info("Binding credentials are empty")
×
630
                credentialsMap = make(map[string][]byte)
×
631
        } else if k8sBinding.Spec.SecretKey != nil {
2✔
632
                credentialsMap = map[string][]byte{
1✔
633
                        *k8sBinding.Spec.SecretKey: smBinding.Credentials,
1✔
634
                }
1✔
635
                credentialProperties = []utils.SecretMetadataProperty{
1✔
636
                        {
1✔
637
                                Name:      *k8sBinding.Spec.SecretKey,
1✔
638
                                Format:    string(utils.JSON),
1✔
639
                                Container: true,
1✔
640
                        },
1✔
641
                }
1✔
642
        } else {
2✔
643
                var err error
1✔
644
                credentialsMap, credentialProperties, err = utils.NormalizeCredentials(smBinding.Credentials)
1✔
645
                if err != nil {
2✔
646
                        log.Error(err, "Failed to store binding secret")
1✔
647
                        return nil, fmt.Errorf("failed to create secret. Error: %v", err.Error())
1✔
648
                }
1✔
649
        }
650

651
        metaDataProperties, err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap)
1✔
652
        if err != nil {
2✔
653
                log.Error(err, "failed to enrich binding with service instance info")
1✔
654
        }
1✔
655

656
        if k8sBinding.Spec.SecretRootKey != nil {
2✔
657
                var err error
1✔
658
                credentialsMap, err = singleKeyMap(credentialsMap, *k8sBinding.Spec.SecretRootKey)
1✔
659
                if err != nil {
1✔
660
                        return nil, err
×
661
                }
×
662
        } else {
1✔
663
                metadata := map[string][]utils.SecretMetadataProperty{
1✔
664
                        "metaDataProperties":   metaDataProperties,
1✔
665
                        "credentialProperties": credentialProperties,
1✔
666
                }
1✔
667
                metadataByte, err := json.Marshal(metadata)
1✔
668
                if err != nil {
1✔
669
                        log.Error(err, "failed to enrich binding with metadata")
×
670
                } else {
1✔
671
                        credentialsMap[".metadata"] = metadataByte
1✔
672
                }
1✔
673
        }
674
        return credentialsMap, nil
1✔
675
}
676

677
func (r *ServiceBindingReconciler) createBindingSecretFromSecretTemplate(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
678
        log := utils.GetLogger(ctx)
1✔
679
        logger := log.WithValues("bindingName", k8sBinding.Name, "secretName", k8sBinding.Spec.SecretName)
1✔
680

1✔
681
        logger.Info("Create Object using SecretTemplate from ServiceBinding Specs")
1✔
682
        inputSmCredentials := smBinding.Credentials
1✔
683
        smBindingCredentials := make(map[string]interface{})
1✔
684
        if inputSmCredentials != nil {
2✔
685
                err := json.Unmarshal(inputSmCredentials, &smBindingCredentials)
1✔
686
                if err != nil {
1✔
687
                        logger.Error(err, "failed to unmarshal given service binding credentials")
×
688
                        return nil, errors.Wrap(err, "failed to unmarshal given service binding credentials")
×
689
                }
×
690
        }
691

692
        instanceInfos, err := r.getInstanceInfo(ctx, k8sBinding)
1✔
693
        if err != nil {
1✔
694
                logger.Error(err, "failed to addInstanceInfo")
×
695
                return nil, errors.Wrap(err, "failed to add service instance info")
×
696
        }
×
697

698
        parameters := commonutils.GetSecretDataForTemplate(smBindingCredentials, instanceInfos)
1✔
699
        templateName := fmt.Sprintf("%s/%s", k8sBinding.Namespace, k8sBinding.Name)
1✔
700
        secret, err := commonutils.CreateSecretFromTemplate(templateName, k8sBinding.Spec.SecretTemplate, "missingkey=error", parameters)
1✔
701
        if err != nil {
2✔
702
                logger.Error(err, "failed to create secret from template")
1✔
703
                return nil, errors.Wrap(err, "failed to create secret from template")
1✔
704
        }
1✔
705
        secret.SetNamespace(k8sBinding.Namespace)
1✔
706
        secret.SetName(k8sBinding.Spec.SecretName)
1✔
707
        if secret.Labels == nil {
1✔
708
                secret.Labels = map[string]string{}
×
709
        }
×
710
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
711

1✔
712
        // if no data provided use the default data
1✔
713
        if len(secret.Data) == 0 && len(secret.StringData) == 0 {
2✔
714
                credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
715
                if err != nil {
1✔
716
                        return nil, err
×
717
                }
×
718
                secret.Data = credentialsMap
1✔
719
        }
720
        return secret, nil
1✔
721
}
722

723
func (r *ServiceBindingReconciler) createOrUpdateBindingSecret(ctx context.Context, binding *v1.ServiceBinding, secret *corev1.Secret) error {
1✔
724
        log := utils.GetLogger(ctx)
1✔
725
        dbSecret := &corev1.Secret{}
1✔
726
        create := false
1✔
727
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.SecretName, Namespace: binding.Namespace}, dbSecret); err != nil {
2✔
728
                if !apierrors.IsNotFound(err) {
1✔
729
                        return err
×
730
                }
×
731
                create = true
1✔
732
        }
733

734
        if create {
2✔
735
                log.Info("Creating binding secret", "name", secret.Name)
1✔
736
                if err := r.Client.Create(ctx, secret); err != nil {
1✔
737
                        if !apierrors.IsAlreadyExists(err) {
×
738
                                return err
×
739
                        }
×
740
                        return nil
×
741
                }
742
                r.Recorder.Event(binding, corev1.EventTypeNormal, "SecretCreated", "SecretCreated")
1✔
743
                return nil
1✔
744
        }
745

746
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
747
        dbSecret.Data = secret.Data
1✔
748
        dbSecret.StringData = secret.StringData
1✔
749
        dbSecret.Labels = secret.Labels
1✔
750
        dbSecret.Annotations = secret.Annotations
1✔
751
        return r.Client.Update(ctx, dbSecret)
1✔
752
}
753

754
func (r *ServiceBindingReconciler) deleteBindingSecret(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
755
        log := utils.GetLogger(ctx)
1✔
756
        log.Info("Deleting binding secret")
1✔
757
        bindingSecret := &corev1.Secret{}
1✔
758
        if err := r.Client.Get(ctx, types.NamespacedName{
1✔
759
                Namespace: binding.Namespace,
1✔
760
                Name:      binding.Spec.SecretName,
1✔
761
        }, bindingSecret); err != nil {
2✔
762
                if !apierrors.IsNotFound(err) {
1✔
763
                        log.Error(err, "unable to fetch binding secret")
×
764
                        return err
×
765
                }
×
766

767
                // secret not found, nothing more to do
768
                log.Info("secret was deleted successfully")
1✔
769
                return nil
1✔
770
        }
771
        bindingSecret = bindingSecret.DeepCopy()
1✔
772

1✔
773
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
774
                log.Error(err, "Failed to delete binding secret")
×
775
                return err
×
776
        }
×
777

778
        log.Info("secret was deleted successfully")
1✔
779
        return nil
1✔
780
}
781

782
func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
783
        // delete binding secret if exist
1✔
784
        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
785
                return ctrl.Result{}, err
×
786
        }
×
787

788
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
789
}
790

791
func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) {
1✔
792
        secret := &corev1.Secret{}
1✔
793
        err := utils.GetSecretWithFallback(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret)
1✔
794
        return secret, err
1✔
795
}
1✔
796

797
func (r *ServiceBindingReconciler) validateSecretNameIsAvailable(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
798
        currentSecret, err := r.getSecret(ctx, binding.Namespace, binding.Spec.SecretName)
1✔
799
        if err != nil {
2✔
800
                return client.IgnoreNotFound(err)
1✔
801
        }
1✔
802

803
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
804
                return nil
1✔
805
        }
1✔
806

807
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
808
        if ownerRef != nil {
2✔
809
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
810
                if err != nil {
1✔
811
                        return err
×
812
                }
×
813

814
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
815
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
816
                }
1✔
817
        }
818

819
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
820
}
821

822
func (r *ServiceBindingReconciler) handleSecretError(ctx context.Context, op smClientTypes.OperationCategory, err error, binding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
823
        log := utils.GetLogger(ctx)
1✔
824
        log.Error(err, fmt.Sprintf("failed to store secret %s for binding %s", binding.Spec.SecretName, binding.Name))
1✔
825
        return utils.HandleOperationFailure(ctx, r.Client, binding, op, err)
1✔
826
}
1✔
827

828
func (r *ServiceBindingReconciler) getInstanceInfo(ctx context.Context, binding *v1.ServiceBinding) (map[string]string, error) {
1✔
829
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
830
        if err != nil {
1✔
831
                return nil, err
×
832
        }
×
833
        instanceInfos := make(map[string]string)
1✔
834
        instanceInfos["instance_name"] = string(getInstanceNameForSecretCredentials(instance))
1✔
835
        instanceInfos["instance_guid"] = instance.Status.InstanceID
1✔
836
        instanceInfos["plan"] = instance.Spec.ServicePlanName
1✔
837
        instanceInfos["label"] = instance.Spec.ServiceOfferingName
1✔
838
        instanceInfos["type"] = instance.Spec.ServiceOfferingName
1✔
839
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
840
                tags := mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags)
1✔
841
                instanceInfos["tags"] = strings.Join(tags, ",")
1✔
842
        }
1✔
843
        return instanceInfos, nil
1✔
844
}
845

846
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *v1.ServiceBinding, credentialsMap map[string][]byte) ([]utils.SecretMetadataProperty, error) {
1✔
847
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
848
        if err != nil {
2✔
849
                return nil, err
1✔
850
        }
1✔
851

852
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
1✔
853
        credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
1✔
854
        credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
1✔
855
        credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
1✔
856
        credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
1✔
857
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
858
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
859
                if err != nil {
1✔
860
                        return nil, err
×
861
                }
×
862
                credentialsMap["tags"] = tagsBytes
1✔
863
        }
864

865
        metadata := []utils.SecretMetadataProperty{
1✔
866
                {
1✔
867
                        Name:   "instance_name",
1✔
868
                        Format: string(utils.TEXT),
1✔
869
                },
1✔
870
                {
1✔
871
                        Name:   "instance_guid",
1✔
872
                        Format: string(utils.TEXT),
1✔
873
                },
1✔
874
                {
1✔
875
                        Name:   "plan",
1✔
876
                        Format: string(utils.TEXT),
1✔
877
                },
1✔
878
                {
1✔
879
                        Name:   "label",
1✔
880
                        Format: string(utils.TEXT),
1✔
881
                },
1✔
882
                {
1✔
883
                        Name:   "type",
1✔
884
                        Format: string(utils.TEXT),
1✔
885
                },
1✔
886
        }
1✔
887
        if _, ok := credentialsMap["tags"]; ok {
2✔
888
                metadata = append(metadata, utils.SecretMetadataProperty{Name: "tags", Format: string(utils.JSON)})
1✔
889
        }
1✔
890

891
        return metadata, nil
1✔
892
}
893

894
func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, binding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (bool, error) {
1✔
895
        log := utils.GetLogger(ctx)
1✔
896
        if err := r.removeForceRotateAnnotationIfNeeded(ctx, binding, log); err != nil {
1✔
897
                log.Info("Credentials rotation - failed to delete force rotate annotation")
×
898
                return false, err
×
899
        }
×
900

901
        credInProgressCondition := meta.FindStatusCondition(binding.GetConditions(), common.ConditionCredRotationInProgress)
1✔
902
        if credInProgressCondition.Reason == common.CredRotating {
2✔
903
                if len(binding.Status.BindingID) > 0 && binding.Status.Ready == metav1.ConditionTrue {
2✔
904
                        log.Info("Credentials rotation - finished successfully")
1✔
905
                        now := metav1.NewTime(time.Now())
1✔
906
                        binding.Status.LastCredentialsRotationTime = &now
1✔
907
                        return false, r.stopRotation(ctx, binding)
1✔
908
                } else if utils.IsFailed(binding) {
2✔
909
                        log.Info("Credentials rotation - binding failed stopping rotation")
×
910
                        return false, r.stopRotation(ctx, binding)
×
911
                }
×
912
                log.Info("Credentials rotation - waiting to finish")
1✔
913
                return false, nil
1✔
914
        }
915

916
        if len(binding.Status.BindingID) == 0 {
1✔
917
                log.Info("Credentials rotation - no binding id found nothing to do")
×
918
                return false, r.stopRotation(ctx, binding)
×
919
        }
×
920

921
        bindings := &v1.ServiceBindingList{}
1✔
922
        err := r.Client.List(ctx, bindings, client.MatchingLabels{common.StaleBindingIDLabel: binding.Status.BindingID}, client.InNamespace(binding.Namespace))
1✔
923
        if err != nil {
1✔
924
                return false, err
×
925
        }
×
926

927
        if len(bindings.Items) == 0 {
2✔
928
                // create the backup binding
1✔
929
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
930
                if err != nil {
1✔
931
                        return false, err
×
932
                }
×
933

934
                // rename current binding
935
                suffix := "-" + utils.RandStringRunes(6)
1✔
936
                log.Info("Credentials rotation - renaming binding to old in SM", "current", binding.Spec.ExternalName)
1✔
937
                if _, errRenaming := smClient.RenameBinding(binding.Status.BindingID, binding.Spec.ExternalName+suffix, binding.Name+suffix); errRenaming != nil {
1✔
938
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
939
                        return true, errRenaming
×
940
                }
×
941

942
                log.Info("Credentials rotation - backing up old binding in K8S", "name", binding.Name+suffix)
1✔
943
                if err := r.createOldBinding(ctx, suffix, binding); err != nil {
1✔
944
                        log.Error(err, "Credentials rotation - failed to back up old binding in K8S")
×
945
                        return true, err
×
946
                }
×
947
        }
948

949
        binding.Status.BindingID = ""
1✔
950
        binding.Status.Ready = metav1.ConditionFalse
1✔
951
        utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
1✔
952
        utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding)
1✔
953
        return false, utils.UpdateStatus(ctx, r.Client, binding)
1✔
954
}
955

956
func (r *ServiceBindingReconciler) removeForceRotateAnnotationIfNeeded(ctx context.Context, binding *v1.ServiceBinding, log logr.Logger) error {
1✔
957
        if binding.Annotations != nil {
2✔
958
                if _, ok := binding.Annotations[common.ForceRotateAnnotation]; ok {
2✔
959
                        log.Info("Credentials rotation - deleting force rotate annotation")
1✔
960
                        delete(binding.Annotations, common.ForceRotateAnnotation)
1✔
961
                        return r.Client.Update(ctx, binding)
1✔
962
                }
1✔
963
        }
964
        return nil
1✔
965
}
966

967
func (r *ServiceBindingReconciler) stopRotation(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
968
        conditions := binding.GetConditions()
1✔
969
        meta.RemoveStatusCondition(&conditions, common.ConditionCredRotationInProgress)
1✔
970
        binding.Status.Conditions = conditions
1✔
971
        return utils.UpdateStatus(ctx, r.Client, binding)
1✔
972
}
1✔
973

974
func (r *ServiceBindingReconciler) createOldBinding(ctx context.Context, suffix string, binding *v1.ServiceBinding) error {
1✔
975
        oldBinding := newBindingObject(binding.Name+suffix, binding.Namespace)
1✔
976
        err := controllerutil.SetControllerReference(binding, oldBinding, r.Scheme)
1✔
977
        if err != nil {
1✔
978
                return err
×
979
        }
×
980
        oldBinding.Labels = map[string]string{
1✔
981
                common.StaleBindingIDLabel:         binding.Status.BindingID,
1✔
982
                common.StaleBindingRotationOfLabel: truncateString(binding.Name, 63),
1✔
983
        }
1✔
984
        oldBinding.Annotations = map[string]string{
1✔
985
                common.StaleBindingOrigBindingNameAnnotation: binding.Name,
1✔
986
        }
1✔
987
        spec := binding.Spec.DeepCopy()
1✔
988
        spec.CredRotationPolicy.Enabled = false
1✔
989
        spec.SecretName = spec.SecretName + suffix
1✔
990
        spec.ExternalName = spec.ExternalName + suffix
1✔
991
        oldBinding.Spec = *spec
1✔
992
        return r.Client.Create(ctx, oldBinding)
1✔
993
}
994

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

1018
        log.Info("not deleting stale binding since original binding is not ready")
1✔
1019
        if !meta.IsStatusConditionPresentAndEqual(serviceBinding.Status.Conditions, common.ConditionPendingTermination, metav1.ConditionTrue) {
2✔
1020
                pendingTerminationCondition := metav1.Condition{
1✔
1021
                        Type:               common.ConditionPendingTermination,
1✔
1022
                        Status:             metav1.ConditionTrue,
1✔
1023
                        Reason:             common.ConditionPendingTermination,
1✔
1024
                        Message:            "waiting for new credentials to be ready",
1✔
1025
                        ObservedGeneration: serviceBinding.GetGeneration(),
1✔
1026
                }
1✔
1027
                meta.SetStatusCondition(&serviceBinding.Status.Conditions, pendingTerminationCondition)
1✔
1028
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1029
        }
1✔
1030
        return ctrl.Result{}, nil
1✔
1031
}
1032

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

1✔
1037
        if smBinding.Credentials != nil {
2✔
1038
                if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
1039
                        operationType := smClientTypes.CREATE
×
1040
                        if smBinding.LastOperation != nil {
×
1041
                                operationType = smBinding.LastOperation.Type
×
1042
                        }
×
1043
                        return r.handleSecretError(ctx, operationType, err, serviceBinding)
×
1044
                }
1045
        }
1046
        r.resyncBindingStatus(ctx, serviceBinding, smBinding)
1✔
1047

1✔
1048
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1049
}
1050

1051
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1052
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1053
                return false
×
1054
        }
×
1055

1056
        if binding.Labels != nil {
2✔
1057
                if _, ok := binding.Labels[common.StaleBindingIDLabel]; ok {
2✔
1058
                        if binding.Spec.CredRotationPolicy != nil {
2✔
1059
                                keepFor, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotatedBindingTTL)
1✔
1060
                                if time.Since(binding.CreationTimestamp.Time) > keepFor {
2✔
1061
                                        return true
1✔
1062
                                }
1✔
1063
                        }
1064
                }
1065
        }
1066
        return false
1✔
1067
}
1068

1069
func initCredRotationIfRequired(binding *v1.ServiceBinding) bool {
1✔
1070
        if utils.IsFailed(binding) || !credRotationEnabled(binding) {
2✔
1071
                return false
1✔
1072
        }
1✔
1073
        _, forceRotate := binding.Annotations[common.ForceRotateAnnotation]
1✔
1074

1✔
1075
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1076
        if lastCredentialRotationTime == nil {
2✔
1077
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1078
                lastCredentialRotationTime = &ts
1✔
1079
        }
1✔
1080

1081
        rotationInterval, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotationFrequency)
1✔
1082
        if time.Since(lastCredentialRotationTime.Time) > rotationInterval || forceRotate {
2✔
1083
                utils.SetCredRotationInProgressConditions(common.CredPreparing, "", binding)
1✔
1084
                return true
1✔
1085
        }
1✔
1086

1087
        return false
1✔
1088
}
1089

1090
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1091
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1092
}
1✔
1093

1094
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1095
        var tags []string
1✔
1096

1✔
1097
        for _, tag := range append(offeringTags, customTags...) {
2✔
1098
                if !utils.SliceContains(tags, tag) {
2✔
1099
                        tags = append(tags, tag)
1✔
1100
                }
1✔
1101
        }
1102
        return tags
1✔
1103
}
1104

1105
func newBindingObject(name, namespace string) *v1.ServiceBinding {
1✔
1106
        return &v1.ServiceBinding{
1✔
1107
                TypeMeta: metav1.TypeMeta{
1✔
1108
                        APIVersion: v1.GroupVersion.String(),
1✔
1109
                        Kind:       "ServiceBinding",
1✔
1110
                },
1✔
1111
                ObjectMeta: metav1.ObjectMeta{
1✔
1112
                        Name:      name,
1✔
1113
                        Namespace: namespace,
1✔
1114
                },
1✔
1115
        }
1✔
1116
}
1✔
1117

1118
func bindingAlreadyOwnedByInstance(instance *v1.ServiceInstance, binding *v1.ServiceBinding) bool {
1✔
1119
        if existing := metav1.GetControllerOf(binding); existing != nil {
2✔
1120
                aGV, err := schema.ParseGroupVersion(existing.APIVersion)
1✔
1121
                if err != nil {
1✔
1122
                        return false
×
1123
                }
×
1124

1125
                bGV, err := schema.ParseGroupVersion(instance.APIVersion)
1✔
1126
                if err != nil {
1✔
1127
                        return false
×
1128
                }
×
1129

1130
                return aGV.Group == bGV.Group && existing.Kind == instance.Kind && existing.Name == instance.Name
1✔
1131
        }
1132
        return false
1✔
1133
}
1134

1135
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1136
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1137
}
1✔
1138

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

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

1152
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1153
        if err != nil {
1✔
1154
                return nil, err
×
1155
        }
×
1156

1157
        return map[string][]byte{
1✔
1158
                key: credBytes,
1✔
1159
        }, nil
1✔
1160
}
1161

1162
func truncateString(str string, length int) string {
1✔
1163
        if len(str) > length {
2✔
1164
                return str[:length]
1✔
1165
        }
1✔
1166
        return str
1✔
1167
}
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

© 2025 Coveralls, Inc