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

SAP / sap-btp-service-operator / 20506687510

25 Dec 2025 02:40PM UTC coverage: 78.351% (-0.07%) from 78.422%
20506687510

Pull #592

github

I065450
[JIRA: SAPBTPCFS-28335] fips and update libs
Pull Request #592: [JIRA: SAPBTPCFS-28335] fips and update libs

10 of 10 new or added lines in 2 files covered. (100.0%)

4 existing lines in 2 files now uncovered.

2736 of 3492 relevant lines covered (78.35%)

0.88 hits per line

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

82.48
/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
        "github.com/SAP/sap-btp-service-operator/internal/utils/logutils"
27
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
28

29
        "github.com/pkg/errors"
30

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

38
        "fmt"
39

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

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

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

48
        "github.com/google/uuid"
49

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

180
        if serviceBinding.Status.BindingID == "" {
2✔
181
                if err := r.validateSecretNameIsAvailable(ctx, serviceBinding); err != nil {
2✔
182
                        log.Error(err, "secret validation failed")
1✔
183
                        utils.SetBlockedCondition(ctx, err.Error(), serviceBinding)
1✔
184
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
185
                }
1✔
186

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

567
        if secret.Labels == nil {
1✔
568
                secret.Labels = map[string]string{}
×
569
        }
×
570
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
571
        if len(k8sBinding.Labels) > 0 && len(k8sBinding.Labels[common.StaleBindingIDLabel]) > 0 {
2✔
572
                secret.Labels[common.StaleBindingIDLabel] = k8sBinding.Labels[common.StaleBindingIDLabel]
1✔
573
        }
1✔
574

575
        if secret.Annotations == nil {
1✔
576
                secret.Annotations = map[string]string{}
×
577
        }
×
578
        secret.Annotations["binding"] = k8sBinding.Name
1✔
579

1✔
580
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
581
}
582

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

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

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

1✔
604
        var credentialsMap map[string][]byte
1✔
605
        var credentialProperties []utils.SecretMetadataProperty
1✔
606

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

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

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

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

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

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

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

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

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

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

725
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
726
        dbSecret.Data = secret.Data
1✔
727
        dbSecret.StringData = secret.StringData
1✔
728
        dbSecret.Labels = secret.Labels
1✔
729
        dbSecret.Annotations = secret.Annotations
1✔
730
        return r.Client.Update(ctx, dbSecret)
1✔
731
}
732

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

746
                // secret not found, nothing more to do
747
                log.Info("secret was deleted successfully")
1✔
748
                return nil
1✔
749
        }
750
        bindingSecret = bindingSecret.DeepCopy()
1✔
751

1✔
752
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
753
                log.Error(err, "Failed to delete binding secret")
×
754
                return err
×
755
        }
×
756

757
        log.Info("secret was deleted successfully")
1✔
758
        return nil
1✔
759
}
760

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

767
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
768
}
769

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

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

782
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
783
                return nil
1✔
784
        }
1✔
785

786
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
787
        if ownerRef != nil {
2✔
788
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
789
                if err != nil {
1✔
790
                        return err
×
791
                }
×
792

793
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
794
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
795
                }
1✔
796
        }
797

798
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
799
}
800

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

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

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

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

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

870
        return metadata, nil
1✔
871
}
872

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
1027
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1028
}
1029

1030
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1031
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1032
                return false
×
1033
        }
×
1034

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

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

1✔
1054
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1055
        if lastCredentialRotationTime == nil {
2✔
1056
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1057
                lastCredentialRotationTime = &ts
1✔
1058
        }
1✔
1059

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

1066
        return false
1✔
1067
}
1068

1069
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1070
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1071
}
1✔
1072

1073
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1074
        var tags []string
1✔
1075

1✔
1076
        for _, tag := range append(offeringTags, customTags...) {
2✔
1077
                if !utils.SliceContains(tags, tag) {
2✔
1078
                        tags = append(tags, tag)
1✔
1079
                }
1✔
1080
        }
1081
        return tags
1✔
1082
}
1083

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

1097
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1098
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1099
}
1✔
1100

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

1108
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1109
        stringCredentialsMap := make(map[string]string)
1✔
1110
        for k, v := range credentialsMap {
2✔
1111
                stringCredentialsMap[k] = string(v)
1✔
1112
        }
1✔
1113

1114
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1115
        if err != nil {
1✔
1116
                return nil, err
×
1117
        }
×
1118

1119
        return map[string][]byte{
1✔
1120
                key: credBytes,
1✔
1121
        }, nil
1✔
1122
}
1123

1124
func truncateString(str string, length int) string {
1✔
1125
        if len(str) > length {
2✔
1126
                return str[:length]
1✔
1127
        }
1✔
1128
        return str
1✔
1129
}
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