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

SAP / sap-btp-service-operator / 16544846850

26 Jul 2025 11:16PM UTC coverage: 80.331% (+0.1%) from 80.217%
16544846850

Pull #543

github

web-flow
Update newdocu.md
Pull Request #543: NEW VERSION DOCU.md

2814 of 3503 relevant lines covered (80.33%)

0.9 hits per line

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

81.4
/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 err := r.rotateCredentials(ctx, serviceBinding, serviceInstance); err != nil {
2✔
155
                        return ctrl.Result{}, err
1✔
156
                }
1✔
157
        }
158

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

166
                if initCredRotationIfRequired(serviceBinding) {
2✔
167
                        log.Info("cred rotation required, updating status")
1✔
168
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
169
                }
1✔
170

171
                log.Info("binding in final state, maintaining secret")
1✔
172
                return r.maintain(ctx, serviceBinding, serviceInstance)
1✔
173
        }
174

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

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

193
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
194
                if err != nil {
1✔
195
                        return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceBinding)
×
196
                }
×
197

198
                smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
199
                if err != nil {
1✔
200
                        log.Error(err, "failed to check binding recovery")
×
201
                        return utils.MarkAsTransientError(ctx, r.Client, smClientTypes.CREATE, err, serviceBinding)
×
202
                }
×
203
                if smBinding != nil {
2✔
204
                        return r.recover(ctx, serviceBinding, smBinding)
1✔
205
                }
1✔
206

207
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
208
        }
209

210
        log.Error(fmt.Errorf("update binding is not allowed, this line should not be reached"), "")
1✔
211
        return ctrl.Result{}, nil
1✔
212
}
213

214
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
215

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

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

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

1✔
243
        if bindErr != nil {
2✔
244
                log.Error(err, "failed to create service binding", "serviceInstanceID", serviceInstance.Status.InstanceID)
1✔
245
                return utils.HandleError(ctx, r.Client, smClientTypes.CREATE, bindErr, serviceBinding)
1✔
246
        }
1✔
247

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

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

266
        log.Info("Binding created successfully")
1✔
267

1✔
268
        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
269
                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
1✔
270
        }
1✔
271

272
        subaccountID := ""
1✔
273
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
274
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
275
        }
×
276

277
        serviceBinding.Status.BindingID = smBinding.ID
1✔
278
        serviceBinding.Status.SubaccountID = subaccountID
1✔
279
        serviceBinding.Status.Ready = metav1.ConditionTrue
1✔
280
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false)
1✔
281
        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
282

1✔
283
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
284
}
285

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

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

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

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

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

324
                log.Info(fmt.Sprintf("Deleting binding with id %v from SM", serviceBinding.Status.BindingID))
1✔
325
                operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
326
                if unbindErr != nil {
2✔
327
                        return utils.HandleDeleteError(ctx, r.Client, unbindErr, serviceBinding)
1✔
328
                }
1✔
329

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

341
                log.Info("Binding was deleted successfully")
1✔
342
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
343
        }
344
        return ctrl.Result{}, nil
×
345
}
346

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

1✔
351
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
352
        if err != nil {
1✔
353
                return utils.MarkAsTransientError(ctx, r.Client, common.Unknown, err, serviceBinding)
×
354
        }
×
355

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

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

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

426
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
427
        serviceBinding.Status.OperationURL = ""
1✔
428
        serviceBinding.Status.OperationType = ""
1✔
429

1✔
430
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
431
}
432

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

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

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

467
        log.Info("maintain finished successfully")
1✔
468
        return ctrl.Result{}, nil
1✔
469
}
470

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

480
                log.Info("binding's secret was not found")
1✔
481
                r.Recorder.Event(serviceBinding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted")
1✔
482
        }
483

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

504
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
505
}
506

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

519
        return serviceInstance.DeepCopy(), nil
1✔
520
}
521

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

1✔
592
        if secret.Annotations == nil {
1✔
593
                secret.Annotations = map[string]string{}
×
594
        }
×
595
        secret.Annotations["binding"] = k8sBinding.Name
1✔
596

1✔
597
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
598
}
599

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

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

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

1✔
621
        var credentialsMap map[string][]byte
1✔
622
        var credentialProperties []utils.SecretMetadataProperty
1✔
623

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

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

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

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

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

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

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

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

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

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

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

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

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

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

774
        log.Info("secret was deleted successfully")
1✔
775
        return nil
1✔
776
}
777

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

784
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
785
}
786

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

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

799
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
800
                return nil
1✔
801
        }
1✔
802

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

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

815
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
816
}
817

818
func (r *ServiceBindingReconciler) handleSecretError(ctx context.Context, op smClientTypes.OperationCategory, err error, binding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
819
        log := utils.GetLogger(ctx)
1✔
820
        log.Error(err, fmt.Sprintf("failed to store secret %s for binding %s", binding.Spec.SecretName, binding.Name))
1✔
821
        if apierrors.ReasonForError(err) == metav1.StatusReasonUnknown {
2✔
822
                return utils.MarkAsNonTransientError(ctx, r.Client, op, err, binding)
1✔
823
        }
1✔
824
        return utils.MarkAsTransientError(ctx, r.Client, op, err, binding)
×
825
}
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 {
2✔
848
                return nil, err
1✔
849
        }
1✔
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) error {
1✔
894
        log := utils.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 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 r.stopRotation(ctx, binding)
1✔
907
                } else if utils.IsFailed(binding) {
2✔
908
                        log.Info("Credentials rotation - binding failed stopping rotation")
×
909
                        return r.stopRotation(ctx, binding)
×
910
                }
×
911
                log.Info("Credentials rotation - waiting to finish")
1✔
912
                return 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 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 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 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
                        utils.SetCredRotationInProgressConditions(common.CredPreparing, errRenaming.Error(), binding)
×
939
                        if errStatus := utils.UpdateStatus(ctx, r.Client, binding); errStatus != nil {
×
940
                                return errStatus
×
941
                        }
×
942
                        return 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

×
949
                        utils.SetCredRotationInProgressConditions(common.CredPreparing, err.Error(), binding)
×
950
                        if errStatus := utils.UpdateStatus(ctx, r.Client, binding); errStatus != nil {
×
951
                                return errStatus
×
952
                        }
×
953
                        return err
×
954
                }
955
        }
956

957
        binding.Status.BindingID = ""
1✔
958
        binding.Status.Ready = metav1.ConditionFalse
1✔
959
        utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
1✔
960
        utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding)
1✔
961
        return utils.UpdateStatus(ctx, r.Client, binding)
1✔
962
}
963

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

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

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

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

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

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

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

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

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

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

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

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

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

1089
        return false
1✔
1090
}
1091

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

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

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

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

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

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

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

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

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

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

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

1159
        return map[string][]byte{
1✔
1160
                key: credBytes,
1✔
1161
        }, nil
1✔
1162
}
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