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

SAP / sap-btp-service-operator / 20781332420

07 Jan 2026 12:22PM UTC coverage: 77.73% (-0.7%) from 78.436%
20781332420

Pull #593

github

web-flow
Merge branch 'main' into move
Pull Request #593: validate resource exists

8 of 44 new or added lines in 2 files covered. (18.18%)

5 existing lines in 1 file now uncovered.

2740 of 3525 relevant lines covered (77.73%)

0.88 hits per line

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

81.32
/controllers/servicebinding_controller.go
1
/*
2

3

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

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

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

17
package controllers
18

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

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

30
        "github.com/pkg/errors"
31

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

39
        "fmt"
40

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

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

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

49
        "github.com/google/uuid"
50

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

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

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

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

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

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

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

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

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

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

125
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
126
        if err != nil {
1✔
NEW
127
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
NEW
128
        }
×
129

130
        if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
131
                return r.delete(ctx, serviceBinding, serviceInstance)
1✔
132
        } else if len(serviceBinding.Status.BindingID) > 0 {
3✔
133
                if _, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil); err != nil {
1✔
NEW
134
                        var smError *sm.ServiceManagerError
×
NEW
135
                        if ok := errors.As(err, &smError); ok {
×
NEW
136
                                if smError.StatusCode == http.StatusNotFound {
×
NEW
137
                                        condition := metav1.Condition{
×
NEW
138
                                                Type:               common.ConditionReady,
×
NEW
139
                                                Status:             metav1.ConditionFalse,
×
NEW
140
                                                ObservedGeneration: serviceBinding.Generation,
×
NEW
141
                                                Reason:             common.ResourceNotFound,
×
NEW
142
                                                Message:            fmt.Sprintf("Binding %s not found for this cluster or namespace; or it is not managed by this operator-access instance.", serviceInstance.Status.InstanceID),
×
NEW
143
                                        }
×
NEW
144
                                        meta.SetStatusCondition(&serviceBinding.Status.Conditions, condition)
×
NEW
145
                                }
×
NEW
146
                                return ctrl.Result{}, nil
×
147
                        }
NEW
148
                        log.Error(err, "failed to get binding by id from SM with unknown error")
×
NEW
149
                        return ctrl.Result{}, err
×
150
                }
151
        }
152

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

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

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

171
        if !serviceInstanceReady(serviceInstance) {
2✔
172
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is not ready, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
173
                utils.SetBlockedCondition(ctx, "service instance is not ready", serviceBinding)
1✔
174
                return ctrl.Result{Requeue: true}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
175
        }
1✔
176

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

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

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

201
                log.Info("binding in final state, maintaining secret")
1✔
202
                return r.maintain(ctx, serviceBinding, serviceInstance)
1✔
203
        }
204

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

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

221
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
222
        }
223

224
        log.Error(fmt.Errorf("update binding is not allowed, this line should not be reached"), "")
1✔
225
        return ctrl.Result{}, nil
1✔
226
}
227

228
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
229

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

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

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

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

262
        if operationURL != "" {
2✔
263
                var bindingID string
1✔
264
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
265
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
×
266
                }
×
267
                serviceBinding.Status.BindingID = bindingID
1✔
268

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

280
        log.Info("Binding created successfully")
1✔
281

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

286
        subaccountID := ""
1✔
287
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
288
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
289
        }
×
290

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

1✔
297
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
298
}
299

300
func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
301
        log := logutils.GetLogger(ctx)
1✔
302
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
303
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
304
                if err != nil {
1✔
305
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
306
                }
×
307

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

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

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

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

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

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

355
                log.Info("Binding was deleted successfully")
1✔
356
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
357
        }
358
        return ctrl.Result{}, nil
×
359
}
360

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

1✔
365
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
366
        if err != nil {
1✔
367
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
368
        }
×
369

370
        status, statusErr := smClient.Status(serviceBinding.Status.OperationURL, nil)
1✔
371
        if statusErr != nil {
2✔
372
                log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceBinding.Status.OperationURL)
1✔
373
                utils.SetInProgressConditions(ctx, serviceBinding.Status.OperationType, string(smClientTypes.INPROGRESS), serviceBinding, false)
1✔
374
                freshStatus := v1.ServiceBindingStatus{
1✔
375
                        Conditions: serviceBinding.GetConditions(),
1✔
376
                }
1✔
377
                if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
378
                        freshStatus.BindingID = serviceBinding.Status.BindingID
1✔
379
                }
1✔
380
                serviceBinding.Status = freshStatus
1✔
381
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
382
                        log.Error(err, "failed to update status during polling")
×
383
                }
×
384
                return ctrl.Result{}, statusErr
1✔
385
        }
386

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

431
                        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
×
432
                                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
×
433
                        }
×
434
                        utils.SetSuccessConditions(status.Type, serviceBinding, false)
×
435
                case smClientTypes.DELETE:
1✔
436
                        return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
437
                }
438
        }
439

440
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
441
        serviceBinding.Status.OperationURL = ""
1✔
442
        serviceBinding.Status.OperationType = ""
1✔
443

1✔
444
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
445
}
446

447
func (r *ServiceBindingReconciler) getBindingForRecovery(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (*smClientTypes.ServiceBinding, error) {
1✔
448
        log := logutils.GetLogger(ctx)
1✔
449
        nameQuery := fmt.Sprintf("name eq '%s'", serviceBinding.Spec.ExternalName)
1✔
450
        clusterIDQuery := fmt.Sprintf("context/clusterid eq '%s'", r.Config.ClusterID)
1✔
451
        namespaceQuery := fmt.Sprintf("context/namespace eq '%s'", serviceBinding.Namespace)
1✔
452
        k8sNameQuery := fmt.Sprintf("%s eq '%s'", common.K8sNameLabel, serviceBinding.Name)
1✔
453
        parameters := sm.Parameters{
1✔
454
                FieldQuery:    []string{nameQuery, clusterIDQuery, namespaceQuery},
1✔
455
                LabelQuery:    []string{k8sNameQuery},
1✔
456
                GeneralParams: []string{"attach_last_operations=true"},
1✔
457
        }
1✔
458
        log.Info(fmt.Sprintf("binding recovery query params: %s, %s, %s, %s", nameQuery, clusterIDQuery, namespaceQuery, k8sNameQuery))
1✔
459

1✔
460
        bindings, err := smClient.ListBindings(&parameters)
1✔
461
        if err != nil {
1✔
462
                log.Error(err, "failed to list bindings in SM")
×
463
                return nil, err
×
464
        }
×
465
        if bindings != nil {
2✔
466
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
467
                if len(bindings.ServiceBindings) == 1 {
2✔
468
                        return &bindings.ServiceBindings[0], nil
1✔
469
                }
1✔
470
        }
471
        return nil, nil
1✔
472
}
473

474
func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.ServiceBinding, instance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
475
        log := logutils.GetLogger(ctx)
1✔
476
        if err := r.maintainSecret(ctx, binding, instance); err != nil {
2✔
477
                log.Error(err, "failed to maintain secret")
1✔
478
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
479
        }
1✔
480

481
        log.Info("maintain finished successfully")
1✔
482
        return ctrl.Result{}, nil
1✔
483
}
484

485
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) error {
1✔
486
        log := logutils.GetLogger(ctx)
1✔
487
        if common.GetObservedGeneration(serviceBinding) == serviceBinding.Generation {
2✔
488
                log.Info("observed generation is up to date, checking if secret exists")
1✔
489
                if _, err := r.getSecret(ctx, serviceBinding.Namespace, serviceBinding.Spec.SecretName); err == nil {
2✔
490
                        log.Info("secret exists, no need to maintain secret")
1✔
491
                        return nil
1✔
492
                }
1✔
493

494
                log.Info("binding's secret was not found")
1✔
495
                r.Recorder.Event(serviceBinding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted")
1✔
496
        }
497

498
        log.Info("maintaining binding's secret")
1✔
499
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
500
        if err != nil {
1✔
501
                return err
×
502
        }
×
503
        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
504
        if err != nil {
1✔
505
                log.Error(err, "failed to get binding for update secret")
×
506
                return err
×
507
        }
×
508
        if smBinding != nil {
2✔
509
                if smBinding.Credentials != nil {
2✔
510
                        if err = r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
511
                                return err
1✔
512
                        }
1✔
513
                        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
514
                        utils.SetSuccessConditions(smClientTypes.UPDATE, serviceBinding, false)
1✔
515
                }
516
        }
517

518
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
519
}
520

521
func (r *ServiceBindingReconciler) getServiceInstanceForBinding(ctx context.Context, binding *v1.ServiceBinding) (*v1.ServiceInstance, error) {
1✔
522
        log := logutils.GetLogger(ctx)
1✔
523
        serviceInstance := &v1.ServiceInstance{}
1✔
524
        namespace := binding.Namespace
1✔
525
        if len(binding.Spec.ServiceInstanceNamespace) > 0 {
2✔
526
                namespace = binding.Spec.ServiceInstanceNamespace
1✔
527
        }
1✔
528
        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✔
529
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.ServiceInstanceName, Namespace: namespace}, serviceInstance); err != nil {
2✔
530
                return serviceInstance, err
1✔
531
        }
1✔
532

533
        return serviceInstance.DeepCopy(), nil
1✔
534
}
535

536
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
537
        k8sBinding.Status.BindingID = smBinding.ID
1✔
538
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
539
        k8sBinding.Status.OperationURL = ""
1✔
540
        k8sBinding.Status.OperationType = ""
1✔
541

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

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

1✔
570
        var secret *corev1.Secret
1✔
571
        var err error
1✔
572

1✔
573
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
574
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
575
        } else {
2✔
576
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
577
        }
1✔
578

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

587
        if secret.Labels == nil {
1✔
588
                secret.Labels = map[string]string{}
×
589
        }
×
590
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
591
        if len(k8sBinding.Labels) > 0 && len(k8sBinding.Labels[common.StaleBindingIDLabel]) > 0 {
2✔
592
                secret.Labels[common.StaleBindingIDLabel] = k8sBinding.Labels[common.StaleBindingIDLabel]
1✔
593
        }
1✔
594

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

890
        return metadata, nil
1✔
891
}
892

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1086
        return false
1✔
1087
}
1088

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

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

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

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

1117
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1118
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1119
}
1✔
1120

1121
func getInstanceNameForSecretCredentials(instance *v1.ServiceInstance) []byte {
1✔
1122
        if useMetaName, ok := instance.Annotations[common.UseInstanceMetadataNameInSecret]; ok && useMetaName == "true" {
2✔
1123
                return []byte(instance.Name)
1✔
1124
        }
1✔
1125
        return []byte(instance.Spec.ExternalName)
1✔
1126
}
1127

1128
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1129
        stringCredentialsMap := make(map[string]string)
1✔
1130
        for k, v := range credentialsMap {
2✔
1131
                stringCredentialsMap[k] = string(v)
1✔
1132
        }
1✔
1133

1134
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1135
        if err != nil {
1✔
1136
                return nil, err
×
1137
        }
×
1138

1139
        return map[string][]byte{
1✔
1140
                key: credBytes,
1✔
1141
        }, nil
1✔
1142
}
1143

1144
func truncateString(str string, length int) string {
1✔
1145
        if len(str) > length {
2✔
1146
                return str[:length]
1✔
1147
        }
1✔
1148
        return str
1✔
1149
}
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