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

SAP / sap-btp-service-operator / 17434078831

03 Sep 2025 12:53PM UTC coverage: 78.608% (+0.2%) from 78.427%
17434078831

push

github

web-flow
omit ServiceInstance owner reference from ServiceBinding (#557)

* omit ServiceInstance owner reference from ServiceBinding

2734 of 3478 relevant lines covered (78.61%)

0.88 hits per line

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

83.02
/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
        if serviceBinding.Status.BindingID == "" {
2✔
180
                if err := r.validateSecretNameIsAvailable(ctx, serviceBinding); err != nil {
2✔
181
                        log.Error(err, "secret validation failed")
1✔
182
                        utils.SetBlockedCondition(ctx, err.Error(), serviceBinding)
1✔
183
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
184
                }
1✔
185

186
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
187
                if err != nil {
1✔
188
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
189
                }
×
190

191
                smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
192
                if err != nil {
1✔
193
                        log.Error(err, "failed to check binding recovery")
×
194
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
195
                }
×
196
                if smBinding != nil {
2✔
197
                        return r.recover(ctx, serviceBinding, smBinding)
1✔
198
                }
1✔
199

200
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
201
        }
202

203
        log.Error(fmt.Errorf("update binding is not allowed, this line should not be reached"), "")
1✔
204
        return ctrl.Result{}, nil
1✔
205
}
206

207
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
208

1✔
209
        return ctrl.NewControllerManagedBy(mgr).
1✔
210
                For(&v1.ServiceBinding{}).
1✔
211
                WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
212
                Complete(r)
1✔
213
}
1✔
214

215
func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
216
        log := utils.GetLogger(ctx)
1✔
217
        log.Info("Creating smBinding in SM")
1✔
218
        serviceBinding.Status.InstanceID = serviceInstance.Status.InstanceID
1✔
219
        bindingParameters, _, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.Parameters, serviceBinding.Spec.ParametersFrom)
1✔
220
        if err != nil {
1✔
221
                log.Error(err, "failed to parse smBinding parameters")
×
222
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
223
        }
×
224

225
        smBinding, operationURL, bindErr := smClient.Bind(&smClientTypes.ServiceBinding{
1✔
226
                Name: serviceBinding.Spec.ExternalName,
1✔
227
                Labels: smClientTypes.Labels{
1✔
228
                        common.NamespaceLabel: []string{serviceBinding.Namespace},
1✔
229
                        common.K8sNameLabel:   []string{serviceBinding.Name},
1✔
230
                        common.ClusterIDLabel: []string{r.Config.ClusterID},
1✔
231
                },
1✔
232
                ServiceInstanceID: serviceInstance.Status.InstanceID,
1✔
233
                Parameters:        bindingParameters,
1✔
234
        }, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
235

1✔
236
        if bindErr != nil {
2✔
237
                log.Error(err, "failed to create service binding", "serviceInstanceID", serviceInstance.Status.InstanceID)
1✔
238
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, bindErr)
1✔
239
        }
1✔
240

241
        if operationURL != "" {
2✔
242
                var bindingID string
1✔
243
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
244
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
×
245
                }
×
246
                serviceBinding.Status.BindingID = bindingID
1✔
247

1✔
248
                log.Info("Create smBinding request is async")
1✔
249
                serviceBinding.Status.OperationURL = operationURL
1✔
250
                serviceBinding.Status.OperationType = smClientTypes.CREATE
1✔
251
                utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceBinding, false)
1✔
252
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
2✔
253
                        log.Error(err, "unable to update ServiceBinding status")
1✔
254
                        return ctrl.Result{}, err
1✔
255
                }
1✔
256
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
257
        }
258

259
        log.Info("Binding created successfully")
1✔
260

1✔
261
        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
262
                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
1✔
263
        }
1✔
264

265
        subaccountID := ""
1✔
266
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
267
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
268
        }
×
269

270
        serviceBinding.Status.BindingID = smBinding.ID
1✔
271
        serviceBinding.Status.SubaccountID = subaccountID
1✔
272
        serviceBinding.Status.Ready = metav1.ConditionTrue
1✔
273
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false)
1✔
274
        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
275

1✔
276
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
277
}
278

279
func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
280
        log := utils.GetLogger(ctx)
1✔
281
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
282
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
283
                if err != nil {
1✔
284
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
285
                }
×
286

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

300
                        // make sure there's no secret stored for the binding
301
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
302
                                return ctrl.Result{}, err
×
303
                        }
×
304

305
                        log.Info("Binding does not exists in SM, removing finalizer")
1✔
306
                        if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName); err != nil {
1✔
307
                                return ctrl.Result{}, err
×
308
                        }
×
309
                        return ctrl.Result{}, nil
1✔
310
                }
311

312
                if len(serviceBinding.Status.OperationURL) > 0 && serviceBinding.Status.OperationType == smClientTypes.DELETE {
2✔
313
                        // ongoing delete operation - poll status from SM
1✔
314
                        return r.poll(ctx, serviceBinding, serviceInstance)
1✔
315
                }
1✔
316

317
                log.Info(fmt.Sprintf("Deleting binding with id %v from SM", serviceBinding.Status.BindingID))
1✔
318
                operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
319
                if unbindErr != nil {
2✔
320
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr)
1✔
321
                }
1✔
322

323
                if operationURL != "" {
2✔
324
                        log.Info("Deleting binding async")
1✔
325
                        serviceBinding.Status.OperationURL = operationURL
1✔
326
                        serviceBinding.Status.OperationType = smClientTypes.DELETE
1✔
327
                        utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceBinding, false)
1✔
328
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
329
                                return ctrl.Result{}, err
×
330
                        }
×
331
                        return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
332
                }
333

334
                log.Info("Binding was deleted successfully")
1✔
335
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
336
        }
337
        return ctrl.Result{}, nil
×
338
}
339

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

1✔
344
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
345
        if err != nil {
1✔
346
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
347
        }
×
348

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

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

410
                        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
×
411
                                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
×
412
                        }
×
413
                        utils.SetSuccessConditions(status.Type, serviceBinding, false)
×
414
                case smClientTypes.DELETE:
1✔
415
                        return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
416
                }
417
        }
418

419
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
420
        serviceBinding.Status.OperationURL = ""
1✔
421
        serviceBinding.Status.OperationType = ""
1✔
422

1✔
423
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
424
}
425

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

1✔
439
        bindings, err := smClient.ListBindings(&parameters)
1✔
440
        if err != nil {
1✔
441
                log.Error(err, "failed to list bindings in SM")
×
442
                return nil, err
×
443
        }
×
444
        if bindings != nil {
2✔
445
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
446
                if len(bindings.ServiceBindings) == 1 {
2✔
447
                        return &bindings.ServiceBindings[0], nil
1✔
448
                }
1✔
449
        }
450
        return nil, nil
1✔
451
}
452

453
func (r *ServiceBindingReconciler) maintain(ctx context.Context, binding *v1.ServiceBinding, instance *v1.ServiceInstance) (ctrl.Result, error) {
1✔
454
        log := utils.GetLogger(ctx)
1✔
455
        if err := r.maintainSecret(ctx, binding, instance); err != nil {
2✔
456
                log.Error(err, "failed to maintain secret")
1✔
457
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
458
        }
1✔
459

460
        log.Info("maintain finished successfully")
1✔
461
        return ctrl.Result{}, nil
1✔
462
}
463

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

473
                log.Info("binding's secret was not found")
1✔
474
                r.Recorder.Event(serviceBinding, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted")
1✔
475
        }
476

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

497
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
498
}
499

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

512
        return serviceInstance.DeepCopy(), nil
1✔
513
}
514

515
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
516
        k8sBinding.Status.BindingID = smBinding.ID
1✔
517
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
518
        k8sBinding.Status.OperationURL = ""
1✔
519
        k8sBinding.Status.OperationType = ""
1✔
520

1✔
521
        bindingStatus := smClientTypes.SUCCEEDED
1✔
522
        operationType := smClientTypes.CREATE
1✔
523
        description := ""
1✔
524
        if smBinding.LastOperation != nil {
2✔
525
                bindingStatus = smBinding.LastOperation.State
1✔
526
                operationType = smBinding.LastOperation.Type
1✔
527
                description = smBinding.LastOperation.Description
1✔
528
        } else if !smBinding.Ready {
3✔
529
                bindingStatus = smClientTypes.FAILED
1✔
530
        }
1✔
531
        switch bindingStatus {
1✔
532
        case smClientTypes.PENDING:
×
533
                fallthrough
×
534
        case smClientTypes.INPROGRESS:
1✔
535
                k8sBinding.Status.OperationURL = sm.BuildOperationURL(smBinding.LastOperation.ID, smBinding.ID, smClientTypes.ServiceBindingsURL)
1✔
536
                k8sBinding.Status.OperationType = smBinding.LastOperation.Type
1✔
537
                utils.SetInProgressConditions(ctx, smBinding.LastOperation.Type, smBinding.LastOperation.Description, k8sBinding, false)
1✔
538
        case smClientTypes.SUCCEEDED:
1✔
539
                utils.SetSuccessConditions(operationType, k8sBinding, false)
1✔
540
        case smClientTypes.FAILED:
1✔
541
                utils.SetFailureConditions(operationType, description, k8sBinding, false)
1✔
542
        }
543
}
544

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

1✔
549
        var secret *corev1.Secret
1✔
550
        var err error
1✔
551

1✔
552
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
553
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
554
        } else {
2✔
555
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
556
        }
1✔
557

558
        if err != nil {
2✔
559
                return err
1✔
560
        }
1✔
561
        if err = controllerutil.SetControllerReference(k8sBinding, secret, r.Scheme); err != nil {
1✔
562
                logger.Error(err, "Failed to set secret owner")
×
563
                return err
×
564
        }
×
565

566
        if secret.Labels == nil {
1✔
567
                secret.Labels = map[string]string{}
×
568
        }
×
569
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
570

1✔
571
        if secret.Annotations == nil {
1✔
572
                secret.Annotations = map[string]string{}
×
573
        }
×
574
        secret.Annotations["binding"] = k8sBinding.Name
1✔
575

1✔
576
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
577
}
578

579
func (r *ServiceBindingReconciler) createBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
580
        credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
581
        if err != nil {
2✔
582
                return nil, err
1✔
583
        }
1✔
584

585
        secret := &corev1.Secret{
1✔
586
                ObjectMeta: metav1.ObjectMeta{
1✔
587
                        Name:        k8sBinding.Spec.SecretName,
1✔
588
                        Annotations: map[string]string{"binding": k8sBinding.Name},
1✔
589
                        Labels:      map[string]string{common.ManagedByBTPOperatorLabel: "true"},
1✔
590
                        Namespace:   k8sBinding.Namespace,
1✔
591
                },
1✔
592
                Data: credentialsMap,
1✔
593
        }
1✔
594
        return secret, nil
1✔
595
}
596

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

1✔
600
        var credentialsMap map[string][]byte
1✔
601
        var credentialProperties []utils.SecretMetadataProperty
1✔
602

1✔
603
        if len(smBinding.Credentials) == 0 {
1✔
604
                log.Info("Binding credentials are empty")
×
605
                credentialsMap = make(map[string][]byte)
×
606
        } else if k8sBinding.Spec.SecretKey != nil {
2✔
607
                credentialsMap = map[string][]byte{
1✔
608
                        *k8sBinding.Spec.SecretKey: smBinding.Credentials,
1✔
609
                }
1✔
610
                credentialProperties = []utils.SecretMetadataProperty{
1✔
611
                        {
1✔
612
                                Name:      *k8sBinding.Spec.SecretKey,
1✔
613
                                Format:    string(utils.JSON),
1✔
614
                                Container: true,
1✔
615
                        },
1✔
616
                }
1✔
617
        } else {
2✔
618
                var err error
1✔
619
                credentialsMap, credentialProperties, err = utils.NormalizeCredentials(smBinding.Credentials)
1✔
620
                if err != nil {
2✔
621
                        log.Error(err, "Failed to store binding secret")
1✔
622
                        return nil, fmt.Errorf("failed to create secret. Error: %v", err.Error())
1✔
623
                }
1✔
624
        }
625

626
        metaDataProperties, err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap)
1✔
627
        if err != nil {
1✔
628
                log.Error(err, "failed to enrich binding with service instance info")
×
629
        }
×
630

631
        if k8sBinding.Spec.SecretRootKey != nil {
2✔
632
                var err error
1✔
633
                credentialsMap, err = singleKeyMap(credentialsMap, *k8sBinding.Spec.SecretRootKey)
1✔
634
                if err != nil {
1✔
635
                        return nil, err
×
636
                }
×
637
        } else {
1✔
638
                metadata := map[string][]utils.SecretMetadataProperty{
1✔
639
                        "metaDataProperties":   metaDataProperties,
1✔
640
                        "credentialProperties": credentialProperties,
1✔
641
                }
1✔
642
                metadataByte, err := json.Marshal(metadata)
1✔
643
                if err != nil {
1✔
644
                        log.Error(err, "failed to enrich binding with metadata")
×
645
                } else {
1✔
646
                        credentialsMap[".metadata"] = metadataByte
1✔
647
                }
1✔
648
        }
649
        return credentialsMap, nil
1✔
650
}
651

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

1✔
656
        logger.Info("Create Object using SecretTemplate from ServiceBinding Specs")
1✔
657
        inputSmCredentials := smBinding.Credentials
1✔
658
        smBindingCredentials := make(map[string]interface{})
1✔
659
        if inputSmCredentials != nil {
2✔
660
                err := json.Unmarshal(inputSmCredentials, &smBindingCredentials)
1✔
661
                if err != nil {
1✔
662
                        logger.Error(err, "failed to unmarshal given service binding credentials")
×
663
                        return nil, errors.Wrap(err, "failed to unmarshal given service binding credentials")
×
664
                }
×
665
        }
666

667
        instanceInfos, err := r.getInstanceInfo(ctx, k8sBinding)
1✔
668
        if err != nil {
1✔
669
                logger.Error(err, "failed to addInstanceInfo")
×
670
                return nil, errors.Wrap(err, "failed to add service instance info")
×
671
        }
×
672

673
        parameters := commonutils.GetSecretDataForTemplate(smBindingCredentials, instanceInfos)
1✔
674
        templateName := fmt.Sprintf("%s/%s", k8sBinding.Namespace, k8sBinding.Name)
1✔
675
        secret, err := commonutils.CreateSecretFromTemplate(templateName, k8sBinding.Spec.SecretTemplate, "missingkey=error", parameters)
1✔
676
        if err != nil {
2✔
677
                logger.Error(err, "failed to create secret from template")
1✔
678
                return nil, errors.Wrap(err, "failed to create secret from template")
1✔
679
        }
1✔
680
        secret.SetNamespace(k8sBinding.Namespace)
1✔
681
        secret.SetName(k8sBinding.Spec.SecretName)
1✔
682
        if secret.Labels == nil {
1✔
683
                secret.Labels = map[string]string{}
×
684
        }
×
685
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
686

1✔
687
        // if no data provided use the default data
1✔
688
        if len(secret.Data) == 0 && len(secret.StringData) == 0 {
2✔
689
                credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
690
                if err != nil {
1✔
691
                        return nil, err
×
692
                }
×
693
                secret.Data = credentialsMap
1✔
694
        }
695
        return secret, nil
1✔
696
}
697

698
func (r *ServiceBindingReconciler) createOrUpdateBindingSecret(ctx context.Context, binding *v1.ServiceBinding, secret *corev1.Secret) error {
1✔
699
        log := utils.GetLogger(ctx)
1✔
700
        dbSecret := &corev1.Secret{}
1✔
701
        create := false
1✔
702
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.SecretName, Namespace: binding.Namespace}, dbSecret); err != nil {
2✔
703
                if !apierrors.IsNotFound(err) {
1✔
704
                        return err
×
705
                }
×
706
                create = true
1✔
707
        }
708

709
        if create {
2✔
710
                log.Info("Creating binding secret", "name", secret.Name)
1✔
711
                if err := r.Client.Create(ctx, secret); err != nil {
1✔
712
                        if !apierrors.IsAlreadyExists(err) {
×
713
                                return err
×
714
                        }
×
715
                        return nil
×
716
                }
717
                r.Recorder.Event(binding, corev1.EventTypeNormal, "SecretCreated", "SecretCreated")
1✔
718
                return nil
1✔
719
        }
720

721
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
722
        dbSecret.Data = secret.Data
1✔
723
        dbSecret.StringData = secret.StringData
1✔
724
        dbSecret.Labels = secret.Labels
1✔
725
        dbSecret.Annotations = secret.Annotations
1✔
726
        return r.Client.Update(ctx, dbSecret)
1✔
727
}
728

729
func (r *ServiceBindingReconciler) deleteBindingSecret(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
730
        log := utils.GetLogger(ctx)
1✔
731
        log.Info("Deleting binding secret")
1✔
732
        bindingSecret := &corev1.Secret{}
1✔
733
        if err := r.Client.Get(ctx, types.NamespacedName{
1✔
734
                Namespace: binding.Namespace,
1✔
735
                Name:      binding.Spec.SecretName,
1✔
736
        }, bindingSecret); err != nil {
2✔
737
                if !apierrors.IsNotFound(err) {
1✔
738
                        log.Error(err, "unable to fetch binding secret")
×
739
                        return err
×
740
                }
×
741

742
                // secret not found, nothing more to do
743
                log.Info("secret was deleted successfully")
1✔
744
                return nil
1✔
745
        }
746
        bindingSecret = bindingSecret.DeepCopy()
1✔
747

1✔
748
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
749
                log.Error(err, "Failed to delete binding secret")
×
750
                return err
×
751
        }
×
752

753
        log.Info("secret was deleted successfully")
1✔
754
        return nil
1✔
755
}
756

757
func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
758
        // delete binding secret if exist
1✔
759
        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
760
                return ctrl.Result{}, err
×
761
        }
×
762

763
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
764
}
765

766
func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) {
1✔
767
        secret := &corev1.Secret{}
1✔
768
        err := utils.GetSecretWithFallback(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret)
1✔
769
        return secret, err
1✔
770
}
1✔
771

772
func (r *ServiceBindingReconciler) validateSecretNameIsAvailable(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
773
        currentSecret, err := r.getSecret(ctx, binding.Namespace, binding.Spec.SecretName)
1✔
774
        if err != nil {
2✔
775
                return client.IgnoreNotFound(err)
1✔
776
        }
1✔
777

778
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
779
                return nil
1✔
780
        }
1✔
781

782
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
783
        if ownerRef != nil {
2✔
784
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
785
                if err != nil {
1✔
786
                        return err
×
787
                }
×
788

789
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
790
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
791
                }
1✔
792
        }
793

794
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
795
}
796

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

803
func (r *ServiceBindingReconciler) getInstanceInfo(ctx context.Context, binding *v1.ServiceBinding) (map[string]string, error) {
1✔
804
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
805
        if err != nil {
1✔
806
                return nil, err
×
807
        }
×
808
        instanceInfos := make(map[string]string)
1✔
809
        instanceInfos["instance_name"] = string(getInstanceNameForSecretCredentials(instance))
1✔
810
        instanceInfos["instance_guid"] = instance.Status.InstanceID
1✔
811
        instanceInfos["plan"] = instance.Spec.ServicePlanName
1✔
812
        instanceInfos["label"] = instance.Spec.ServiceOfferingName
1✔
813
        instanceInfos["type"] = instance.Spec.ServiceOfferingName
1✔
814
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
815
                tags := mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags)
1✔
816
                instanceInfos["tags"] = strings.Join(tags, ",")
1✔
817
        }
1✔
818
        return instanceInfos, nil
1✔
819
}
820

821
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *v1.ServiceBinding, credentialsMap map[string][]byte) ([]utils.SecretMetadataProperty, error) {
1✔
822
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
823
        if err != nil {
1✔
824
                return nil, err
×
825
        }
×
826

827
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
1✔
828
        credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
1✔
829
        credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
1✔
830
        credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
1✔
831
        credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
1✔
832
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
833
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
834
                if err != nil {
1✔
835
                        return nil, err
×
836
                }
×
837
                credentialsMap["tags"] = tagsBytes
1✔
838
        }
839

840
        metadata := []utils.SecretMetadataProperty{
1✔
841
                {
1✔
842
                        Name:   "instance_name",
1✔
843
                        Format: string(utils.TEXT),
1✔
844
                },
1✔
845
                {
1✔
846
                        Name:   "instance_guid",
1✔
847
                        Format: string(utils.TEXT),
1✔
848
                },
1✔
849
                {
1✔
850
                        Name:   "plan",
1✔
851
                        Format: string(utils.TEXT),
1✔
852
                },
1✔
853
                {
1✔
854
                        Name:   "label",
1✔
855
                        Format: string(utils.TEXT),
1✔
856
                },
1✔
857
                {
1✔
858
                        Name:   "type",
1✔
859
                        Format: string(utils.TEXT),
1✔
860
                },
1✔
861
        }
1✔
862
        if _, ok := credentialsMap["tags"]; ok {
2✔
863
                metadata = append(metadata, utils.SecretMetadataProperty{Name: "tags", Format: string(utils.JSON)})
1✔
864
        }
1✔
865

866
        return metadata, nil
1✔
867
}
868

869
func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, binding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (bool, error) {
1✔
870
        log := utils.GetLogger(ctx)
1✔
871
        if err := r.removeForceRotateAnnotationIfNeeded(ctx, binding, log); err != nil {
1✔
872
                log.Info("Credentials rotation - failed to delete force rotate annotation")
×
873
                return false, err
×
874
        }
×
875

876
        credInProgressCondition := meta.FindStatusCondition(binding.GetConditions(), common.ConditionCredRotationInProgress)
1✔
877
        if credInProgressCondition.Reason == common.CredRotating {
2✔
878
                if len(binding.Status.BindingID) > 0 && binding.Status.Ready == metav1.ConditionTrue {
2✔
879
                        log.Info("Credentials rotation - finished successfully")
1✔
880
                        now := metav1.NewTime(time.Now())
1✔
881
                        binding.Status.LastCredentialsRotationTime = &now
1✔
882
                        return false, r.stopRotation(ctx, binding)
1✔
883
                } else if utils.IsFailed(binding) {
2✔
884
                        log.Info("Credentials rotation - binding failed stopping rotation")
×
885
                        return false, r.stopRotation(ctx, binding)
×
886
                }
×
887
                log.Info("Credentials rotation - waiting to finish")
1✔
888
                return false, nil
1✔
889
        }
890

891
        if len(binding.Status.BindingID) == 0 {
1✔
892
                log.Info("Credentials rotation - no binding id found nothing to do")
×
893
                return false, r.stopRotation(ctx, binding)
×
894
        }
×
895

896
        bindings := &v1.ServiceBindingList{}
1✔
897
        err := r.Client.List(ctx, bindings, client.MatchingLabels{common.StaleBindingIDLabel: binding.Status.BindingID}, client.InNamespace(binding.Namespace))
1✔
898
        if err != nil {
1✔
899
                return false, err
×
900
        }
×
901

902
        if len(bindings.Items) == 0 {
2✔
903
                // create the backup binding
1✔
904
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
905
                if err != nil {
1✔
906
                        return false, err
×
907
                }
×
908

909
                // rename current binding
910
                suffix := "-" + utils.RandStringRunes(6)
1✔
911
                log.Info("Credentials rotation - renaming binding to old in SM", "current", binding.Spec.ExternalName)
1✔
912
                if _, errRenaming := smClient.RenameBinding(binding.Status.BindingID, binding.Spec.ExternalName+suffix, binding.Name+suffix); errRenaming != nil {
1✔
913
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
914
                        return true, errRenaming
×
915
                }
×
916

917
                log.Info("Credentials rotation - backing up old binding in K8S", "name", binding.Name+suffix)
1✔
918
                if err := r.createOldBinding(ctx, suffix, binding); err != nil {
1✔
919
                        log.Error(err, "Credentials rotation - failed to back up old binding in K8S")
×
920
                        return true, err
×
921
                }
×
922
        }
923

924
        binding.Status.BindingID = ""
1✔
925
        binding.Status.Ready = metav1.ConditionFalse
1✔
926
        utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
1✔
927
        utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding)
1✔
928
        return false, utils.UpdateStatus(ctx, r.Client, binding)
1✔
929
}
930

931
func (r *ServiceBindingReconciler) removeForceRotateAnnotationIfNeeded(ctx context.Context, binding *v1.ServiceBinding, log logr.Logger) error {
1✔
932
        if binding.Annotations != nil {
2✔
933
                if _, ok := binding.Annotations[common.ForceRotateAnnotation]; ok {
2✔
934
                        log.Info("Credentials rotation - deleting force rotate annotation")
1✔
935
                        delete(binding.Annotations, common.ForceRotateAnnotation)
1✔
936
                        return r.Client.Update(ctx, binding)
1✔
937
                }
1✔
938
        }
939
        return nil
1✔
940
}
941

942
func (r *ServiceBindingReconciler) stopRotation(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
943
        conditions := binding.GetConditions()
1✔
944
        meta.RemoveStatusCondition(&conditions, common.ConditionCredRotationInProgress)
1✔
945
        binding.Status.Conditions = conditions
1✔
946
        return utils.UpdateStatus(ctx, r.Client, binding)
1✔
947
}
1✔
948

949
func (r *ServiceBindingReconciler) createOldBinding(ctx context.Context, suffix string, binding *v1.ServiceBinding) error {
1✔
950
        oldBinding := newBindingObject(binding.Name+suffix, binding.Namespace)
1✔
951
        err := controllerutil.SetControllerReference(binding, oldBinding, r.Scheme)
1✔
952
        if err != nil {
1✔
953
                return err
×
954
        }
×
955
        oldBinding.Labels = map[string]string{
1✔
956
                common.StaleBindingIDLabel:         binding.Status.BindingID,
1✔
957
                common.StaleBindingRotationOfLabel: truncateString(binding.Name, 63),
1✔
958
        }
1✔
959
        oldBinding.Annotations = map[string]string{
1✔
960
                common.StaleBindingOrigBindingNameAnnotation: binding.Name,
1✔
961
        }
1✔
962
        spec := binding.Spec.DeepCopy()
1✔
963
        spec.CredRotationPolicy.Enabled = false
1✔
964
        spec.SecretName = spec.SecretName + suffix
1✔
965
        spec.ExternalName = spec.ExternalName + suffix
1✔
966
        oldBinding.Spec = *spec
1✔
967
        return r.Client.Create(ctx, oldBinding)
1✔
968
}
969

970
func (r *ServiceBindingReconciler) handleStaleServiceBinding(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
971
        log := utils.GetLogger(ctx)
1✔
972
        originalBindingName, ok := serviceBinding.Annotations[common.StaleBindingOrigBindingNameAnnotation]
1✔
973
        if !ok {
2✔
974
                //if the user removed the "OrigBindingName" annotation and rotationOf label not exist as well
1✔
975
                //the stale binding should be deleted otherwise it will remain forever
1✔
976
                if originalBindingName, ok = serviceBinding.Labels[common.StaleBindingRotationOfLabel]; !ok {
2✔
977
                        log.Info("missing rotationOf label/annotation, unable to fetch original binding, deleting stale")
1✔
978
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
979
                }
1✔
980
        }
981
        origBinding := &v1.ServiceBinding{}
1✔
982
        if err := r.Client.Get(ctx, types.NamespacedName{Namespace: serviceBinding.Namespace, Name: originalBindingName}, origBinding); err != nil {
2✔
983
                if apierrors.IsNotFound(err) {
2✔
984
                        log.Info("original binding not found, deleting stale binding")
1✔
985
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
986
                }
1✔
987
                return ctrl.Result{}, err
×
988
        }
989
        if meta.IsStatusConditionTrue(origBinding.Status.Conditions, common.ConditionReady) {
1✔
990
                return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
×
991
        }
×
992

993
        log.Info("not deleting stale binding since original binding is not ready")
1✔
994
        if !meta.IsStatusConditionPresentAndEqual(serviceBinding.Status.Conditions, common.ConditionPendingTermination, metav1.ConditionTrue) {
2✔
995
                pendingTerminationCondition := metav1.Condition{
1✔
996
                        Type:               common.ConditionPendingTermination,
1✔
997
                        Status:             metav1.ConditionTrue,
1✔
998
                        Reason:             common.ConditionPendingTermination,
1✔
999
                        Message:            "waiting for new credentials to be ready",
1✔
1000
                        ObservedGeneration: serviceBinding.GetGeneration(),
1✔
1001
                }
1✔
1002
                meta.SetStatusCondition(&serviceBinding.Status.Conditions, pendingTerminationCondition)
1✔
1003
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1004
        }
1✔
1005
        return ctrl.Result{}, nil
1✔
1006
}
1007

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

1✔
1012
        if smBinding.Credentials != nil {
2✔
1013
                if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
1014
                        operationType := smClientTypes.CREATE
×
1015
                        if smBinding.LastOperation != nil {
×
1016
                                operationType = smBinding.LastOperation.Type
×
1017
                        }
×
1018
                        return r.handleSecretError(ctx, operationType, err, serviceBinding)
×
1019
                }
1020
        }
1021
        r.resyncBindingStatus(ctx, serviceBinding, smBinding)
1✔
1022

1✔
1023
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1024
}
1025

1026
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1027
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1028
                return false
×
1029
        }
×
1030

1031
        if binding.Labels != nil {
2✔
1032
                if _, ok := binding.Labels[common.StaleBindingIDLabel]; ok {
2✔
1033
                        if binding.Spec.CredRotationPolicy != nil {
2✔
1034
                                keepFor, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotatedBindingTTL)
1✔
1035
                                if time.Since(binding.CreationTimestamp.Time) > keepFor {
2✔
1036
                                        return true
1✔
1037
                                }
1✔
1038
                        }
1039
                }
1040
        }
1041
        return false
1✔
1042
}
1043

1044
func initCredRotationIfRequired(binding *v1.ServiceBinding) bool {
1✔
1045
        if utils.IsFailed(binding) || !credRotationEnabled(binding) {
2✔
1046
                return false
1✔
1047
        }
1✔
1048
        _, forceRotate := binding.Annotations[common.ForceRotateAnnotation]
1✔
1049

1✔
1050
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1051
        if lastCredentialRotationTime == nil {
2✔
1052
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1053
                lastCredentialRotationTime = &ts
1✔
1054
        }
1✔
1055

1056
        rotationInterval, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotationFrequency)
1✔
1057
        if time.Since(lastCredentialRotationTime.Time) > rotationInterval || forceRotate {
2✔
1058
                utils.SetCredRotationInProgressConditions(common.CredPreparing, "", binding)
1✔
1059
                return true
1✔
1060
        }
1✔
1061

1062
        return false
1✔
1063
}
1064

1065
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1066
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1067
}
1✔
1068

1069
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1070
        var tags []string
1✔
1071

1✔
1072
        for _, tag := range append(offeringTags, customTags...) {
2✔
1073
                if !utils.SliceContains(tags, tag) {
2✔
1074
                        tags = append(tags, tag)
1✔
1075
                }
1✔
1076
        }
1077
        return tags
1✔
1078
}
1079

1080
func newBindingObject(name, namespace string) *v1.ServiceBinding {
1✔
1081
        return &v1.ServiceBinding{
1✔
1082
                TypeMeta: metav1.TypeMeta{
1✔
1083
                        APIVersion: v1.GroupVersion.String(),
1✔
1084
                        Kind:       "ServiceBinding",
1✔
1085
                },
1✔
1086
                ObjectMeta: metav1.ObjectMeta{
1✔
1087
                        Name:      name,
1✔
1088
                        Namespace: namespace,
1✔
1089
                },
1✔
1090
        }
1✔
1091
}
1✔
1092

1093
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1094
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1095
}
1✔
1096

1097
func getInstanceNameForSecretCredentials(instance *v1.ServiceInstance) []byte {
1✔
1098
        if useMetaName, ok := instance.Annotations[common.UseInstanceMetadataNameInSecret]; ok && useMetaName == "true" {
2✔
1099
                return []byte(instance.Name)
1✔
1100
        }
1✔
1101
        return []byte(instance.Spec.ExternalName)
1✔
1102
}
1103

1104
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1105
        stringCredentialsMap := make(map[string]string)
1✔
1106
        for k, v := range credentialsMap {
2✔
1107
                stringCredentialsMap[k] = string(v)
1✔
1108
        }
1✔
1109

1110
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1111
        if err != nil {
1✔
1112
                return nil, err
×
1113
        }
×
1114

1115
        return map[string][]byte{
1✔
1116
                key: credBytes,
1✔
1117
        }, nil
1✔
1118
}
1119

1120
func truncateString(str string, length int) string {
1✔
1121
        if len(str) > length {
2✔
1122
                return str[:length]
1✔
1123
        }
1✔
1124
        return str
1✔
1125
}
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