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

SAP / sap-btp-service-operator / 21147079550

19 Jan 2026 05:52PM UTC coverage: 78.403% (+0.06%) from 78.346%
21147079550

Pull #590

github

Daniele
Address review feedback on custom CA section

Reorder text per avilupu's suggestion:
- First describe the generic feature (custom CA support)
- Then note when it's useful (restricted environments)
- Remove "organization-specific internal" phrasing
Pull Request #590: custom CA readme

2788 of 3556 relevant lines covered (78.4%)

0.88 hits per line

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

82.74
/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) {
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 {
1✔
90
                if !apierrors.IsNotFound(err) {
2✔
91
                        log.Error(err, "unable to fetch ServiceBinding")
1✔
92
                }
×
93
                return ctrl.Result{}, client.IgnoreNotFound(err)
×
94
        }
1✔
95
        serviceBinding = serviceBinding.DeepCopy()
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 {
1✔
99
                if err := utils.InitConditions(ctx, r.Client, serviceBinding); err != nil {
2✔
100
                        return ctrl.Result{}, err
1✔
101
                }
×
102
        }
×
103

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

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

1✔
128
        if controllerutil.AddFinalizer(serviceBinding, common.FinalizerName) {
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
        }
2✔
134

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

1✔
140
        if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
1✔
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

1✔
146
        if !serviceInstanceReady(serviceInstance) {
1✔
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 {
1✔
156
                        if !shouldUpdateStatus {
×
157
                                log.Error(err, "internal error occurred during cred rotation, requeuing binding")
×
158
                                return ctrl.Result{}, err
159
                        }
160
                        return utils.HandleCredRotationError(ctx, r.Client, serviceBinding, err)
2✔
161
                }
1✔
162
        }
1✔
163

1✔
164
        // is binding ready
165
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionReady) {
2✔
166
                if isStaleServiceBinding(serviceBinding) {
1✔
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

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

1✔
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
                }
×
186

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

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

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

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

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

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

×
216
func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
×
217
        log := logutils.GetLogger(ctx)
2✔
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)
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
        }
1✔
225

1✔
226
        smBinding, operationURL, bindErr := smClient.Bind(&smClientTypes.ServiceBinding{
227
                Name: serviceBinding.Spec.ExternalName,
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))
236

1✔
237
        if bindErr != nil {
1✔
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

1✔
242
        if operationURL != "" {
×
243
                var bindingID string
×
244
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
×
245
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
246
                }
1✔
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✔
254
                        log.Error(err, "unable to update ServiceBinding status")
1✔
255
                        return ctrl.Result{}, err
1✔
256
                }
1✔
257
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
2✔
258
        }
1✔
259

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

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 := ""
×
267
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
268
                subaccountID = smBinding.Labels["subaccount_id"][0]
1✔
269
        }
1✔
270

1✔
271
        serviceBinding.Status.BindingID = smBinding.ID
1✔
272
        serviceBinding.Status.SubaccountID = subaccountID
1✔
273
        serviceBinding.Status.Ready = metav1.ConditionTrue
2✔
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
                }
1✔
287

1✔
288
                if len(serviceBinding.Status.BindingID) == 0 {
×
289
                        log.Info("No binding id found validating binding does not exists in SM before removing finalizer")
×
290
                        smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
291
                        if err != nil {
1✔
292
                                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
1✔
293
                        }
1✔
294
                        if smBinding != nil {
1✔
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)
299
                        }
300

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

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

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

1✔
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 {
321
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr)
322
                }
1✔
323

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

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

2✔
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))
344

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

1✔
350
        status, statusErr := smClient.Status(serviceBinding.Status.OperationURL, nil)
×
351
        if statusErr != nil {
×
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)
354
                freshStatus := v1.ServiceBindingStatus{
355
                        Conditions: serviceBinding.GetConditions(),
1✔
356
                }
1✔
357
                if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
358
                        freshStatus.BindingID = serviceBinding.Status.BindingID
×
359
                }
360
                serviceBinding.Status = freshStatus
361
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
362
                        log.Error(err, "failed to update status during polling")
1✔
363
                }
1✔
364
                return ctrl.Result{}, statusErr
1✔
365
        }
1✔
366

1✔
367
        if status == nil {
×
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:
2✔
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)
1✔
376
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
377
                                log.Error(err, "unable to update ServiceBinding polling description")
2✔
378
                                return ctrl.Result{}, err
1✔
379
                        }
1✔
380
                }
1✔
381
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
382
        case smClientTypes.FAILED:
×
383
                // if async operation failed we should not retry
×
384
                utils.SetFailureConditions(status.Type, status.Description, serviceBinding, true)
1✔
385
                if serviceBinding.Status.OperationType == smClientTypes.DELETE {
386
                        serviceBinding.Status.OperationURL = ""
387
                        serviceBinding.Status.OperationType = ""
1✔
388
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
×
389
                                log.Error(err, "unable to update ServiceBinding status")
×
390
                                return ctrl.Result{}, err
1✔
391
                        }
1✔
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))
1✔
395
                        }
×
396
                        return ctrl.Result{}, errors.New(errMsg)
×
397
                }
×
398
        case smClientTypes.SUCCEEDED:
×
399
                utils.SetSuccessConditions(status.Type, serviceBinding, true)
×
400
                switch serviceBinding.Status.OperationType {
401
                case smClientTypes.CREATE:
1✔
402
                        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
403
                        if err != nil || smBinding == nil {
1✔
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
2✔
406
                        }
1✔
407
                        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
408
                                serviceBinding.Status.SubaccountID = smBinding.Labels["subaccount_id"][0]
1✔
409
                        }
×
410

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

1✔
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

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

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

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

1✔
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 {
1✔
457
                log.Error(err, "failed to maintain secret")
1✔
458
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
459
        }
1✔
460

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

×
465
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, serviceBinding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) error {
2✔
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 {
1✔
470
                        log.Info("secret exists, no need to maintain secret")
471
                        return nil
1✔
472
                }
473

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

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

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

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

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

516
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
517
        k8sBinding.Status.BindingID = smBinding.ID
518
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
519
        k8sBinding.Status.OperationURL = ""
520
        k8sBinding.Status.OperationType = ""
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 {
2✔
530
                bindingStatus = smClientTypes.FAILED
1✔
531
        }
1✔
532
        switch bindingStatus {
533
        case smClientTypes.PENDING:
1✔
534
                fallthrough
535
        case smClientTypes.INPROGRESS:
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
        }
1✔
544
}
1✔
545

2✔
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

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

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

1✔
559
        if err != nil {
1✔
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

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

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

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

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

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

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

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

1✔
607
        if len(smBinding.Credentials) == 0 {
1✔
608
                log.Info("Binding credentials are empty")
609
                credentialsMap = make(map[string][]byte)
1✔
610
        } else if k8sBinding.Spec.SecretKey != nil {
1✔
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
                        },
620
                }
621
        } else {
1✔
622
                var err error
1✔
623
                credentialsMap, credentialProperties, err = utils.NormalizeCredentials(smBinding.Credentials)
1✔
624
                if err != nil {
1✔
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)
2✔
631
        if err != nil {
1✔
632
                log.Error(err, "failed to enrich binding with service instance info")
1✔
633
        }
1✔
634

1✔
635
        if k8sBinding.Spec.SecretRootKey != nil {
1✔
636
                var err error
1✔
637
                credentialsMap, err = singleKeyMap(credentialsMap, *k8sBinding.Spec.SecretRootKey)
1✔
638
                if err != nil {
1✔
639
                        return nil, err
1✔
640
                }
1✔
641
        } else {
2✔
642
                metadata := map[string][]utils.SecretMetadataProperty{
1✔
643
                        "metaDataProperties":   metaDataProperties,
1✔
644
                        "credentialProperties": credentialProperties,
2✔
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 {
650
                        credentialsMap[".metadata"] = metadataByte
1✔
651
                }
1✔
652
        }
×
653
        return credentialsMap, nil
×
654
}
655

2✔
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

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

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

1✔
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 {
1✔
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
        }
2✔
684
        secret.SetNamespace(k8sBinding.Namespace)
1✔
685
        secret.SetName(k8sBinding.Spec.SecretName)
1✔
686
        if secret.Labels == nil {
×
687
                secret.Labels = map[string]string{}
×
688
        }
×
689
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
690

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

1✔
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 {
1✔
707
                if !apierrors.IsNotFound(err) {
×
708
                        return err
×
709
                }
1✔
710
                create = true
1✔
711
        }
1✔
712

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

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

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

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

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

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

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

767
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
768
}
1✔
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
×
774
}
×
775

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

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

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

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

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

1✔
801
func (r *ServiceBindingReconciler) handleSecretError(ctx context.Context, op smClientTypes.OperationCategory, err error, binding *v1.ServiceBinding) (ctrl.Result, error) {
802
        log := logutils.GetLogger(ctx)
2✔
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
}
806

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

1✔
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)
827
        if err != nil {
1✔
828
                return nil, err
1✔
829
        }
1✔
830

×
831
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
×
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 {
1✔
837
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
838
                if err != nil {
2✔
839
                        return nil, err
1✔
840
                }
1✔
841
                credentialsMap["tags"] = tagsBytes
1✔
842
        }
1✔
843

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

1✔
870
        return metadata, nil
1✔
871
}
1✔
872

1✔
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")
1✔
877
                return false, err
1✔
878
        }
1✔
879

1✔
880
        credInProgressCondition := meta.FindStatusCondition(binding.GetConditions(), common.ConditionCredRotationInProgress)
1✔
881
        if credInProgressCondition.Reason == common.CredRotating {
1✔
882
                if len(binding.Status.BindingID) > 0 && binding.Status.Ready == metav1.ConditionTrue {
1✔
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)
2✔
887
                } else if utils.IsFailed(binding) {
1✔
888
                        log.Info("Credentials rotation - binding failed stopping rotation")
1✔
889
                        return false, r.stopRotation(ctx, binding)
890
                }
1✔
891
                log.Info("Credentials rotation - waiting to finish")
892
                return false, nil
893
        }
1✔
894

1✔
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))
2✔
902
        if err != nil {
2✔
903
                return false, err
1✔
904
        }
1✔
905

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

1✔
913
                // rename current binding
914
                suffix := "-" + utils.RandStringRunes(6)
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 {
×
917
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
918
                        return true, errRenaming
×
919
                }
920

1✔
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
        }
2✔
927

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

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

×
946
func (r *ServiceBindingReconciler) stopRotation(ctx context.Context, binding *v1.ServiceBinding) error {
947
        conditions := binding.GetConditions()
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

1✔
953
func (r *ServiceBindingReconciler) createOldBinding(ctx context.Context, suffix string, binding *v1.ServiceBinding) error {
954
        oldBinding := newBindingObject(binding.Name+suffix, binding.Namespace)
955
        err := controllerutil.SetControllerReference(binding, oldBinding, r.Scheme)
1✔
956
        if err != nil {
2✔
957
                return err
2✔
958
        }
1✔
959
        oldBinding.Labels = map[string]string{
1✔
960
                common.StaleBindingIDLabel:         binding.Status.BindingID,
1✔
961
                common.StaleBindingRotationOfLabel: truncateString(binding.Name, 63),
1✔
962
        }
963
        oldBinding.Annotations = map[string]string{
1✔
964
                common.StaleBindingOrigBindingNameAnnotation: binding.Name,
965
        }
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

1✔
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 {
×
978
                //if the user removed the "OrigBindingName" annotation and rotationOf label not exist as well
×
979
                //the stale binding should be deleted otherwise it will remain forever
1✔
980
                if originalBindingName, ok = serviceBinding.Labels[common.StaleBindingRotationOfLabel]; !ok {
1✔
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
        }
1✔
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) {
1✔
988
                        log.Info("original binding not found, deleting stale binding")
1✔
989
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
990
                }
1✔
991
                return ctrl.Result{}, err
1✔
992
        }
993
        if meta.IsStatusConditionTrue(origBinding.Status.Conditions, common.ConditionReady) {
994
                return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
995
        }
1✔
996

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

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

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

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

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

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

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

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

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

1✔
1066
        return false
1067
}
1068

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

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

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

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

2✔
1097
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
2✔
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" {
1103
                return []byte(instance.Name)
1104
        }
1✔
1105
        return []byte(instance.Spec.ExternalName)
1✔
1106
}
1✔
1107

1✔
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 {
1✔
1111
                stringCredentialsMap[k] = string(v)
1✔
1112
        }
1✔
1113

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

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

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