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

SAP / sap-btp-service-operator / 17091410426

20 Aug 2025 07:17AM UTC coverage: 80.011% (-0.06%) from 80.068%
17091410426

push

github

web-flow
bump go (#551)

2826 of 3532 relevant lines covered (80.01%)

0.9 hits per line

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

82.31
/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 {
2✔
155
                        if !shouldUpdateStatus {
2✔
156
                                log.Error(err, "internal error occurred during cred rotation, requeuing binding")
1✔
157
                                return ctrl.Result{}, err
1✔
158
                        }
1✔
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✔
199
                        return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceBinding)
×
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")
×
205
                        return utils.MarkAsTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceBinding)
×
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")
×
233
                return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceBinding)
×
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.HandleError(ctx, r.Client, smClientTypes.CREATE, bindErr, serviceBinding)
1✔
250
        }
1✔
251

252
        if operationURL != "" {
2✔
253
                var bindingID string
1✔
254
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
255
                        return utils.MarkAsNonTransientError(ctx, r.Client, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL), serviceBinding)
×
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{Requeue: true, 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✔
295
                        return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceBinding)
×
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✔
302
                                return ctrl.Result{}, 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.HandleDeleteError(ctx, r.Client, unbindErr, serviceBinding)
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{Requeue: true, 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✔
357
                return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceBinding)
×
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✔
378
                return utils.MarkAsTransientError(ctx, r.Client, serviceBinding.Status.OperationType, fmt.Errorf("failed to get last operation status of %s", serviceBinding.Name), serviceBinding)
×
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{Requeue: true, RequeueAfter: r.Config.PollInterval}, nil
1✔
392
        case smClientTypes.FAILED:
1✔
393
                // non transient error - 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
        if apierrors.ReasonForError(err) == metav1.StatusReasonUnknown {
2✔
826
                return utils.MarkAsNonTransientError(ctx, r.Client, op, err, binding)
1✔
827
        }
1✔
828
        return utils.MarkAsTransientError(ctx, r.Client, op, err, binding)
×
829
}
830

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

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

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

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

894
        return metadata, nil
1✔
895
}
896

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1051
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1052
}
1053

1054
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1055
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1056
                return false
×
1057
        }
×
1058

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

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

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

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

1090
        return false
1✔
1091
}
1092

1093
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1094
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1095
}
1✔
1096

1097
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1098
        var tags []string
1✔
1099

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

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

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

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

1133
                return aGV.Group == bGV.Group && existing.Kind == instance.Kind && existing.Name == instance.Name
1✔
1134
        }
1135
        return false
1✔
1136
}
1137

1138
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1139
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1140
}
1✔
1141

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

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

1155
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1156
        if err != nil {
1✔
1157
                return nil, err
×
1158
        }
×
1159

1160
        return map[string][]byte{
1✔
1161
                key: credBytes,
1✔
1162
        }, nil
1✔
1163
}
1164

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