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

SAP / sap-btp-service-operator / 18834636253

27 Oct 2025 08:31AM UTC coverage: 78.736% (+0.2%) from 78.563%
18834636253

push

github

web-flow
bump rbac proxy (#570)

2740 of 3480 relevant lines covered (78.74%)

0.89 hits per line

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

83.53
/controllers/servicebinding_controller.go
1
/*
2

3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package controllers
18

19
import (
20
        "context"
21
        "encoding/json"
22
        "strings"
23
        "time"
24

25
        commonutils "github.com/SAP/sap-btp-service-operator/api/common/utils"
26
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
27

28
        "github.com/pkg/errors"
29

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

37
        "fmt"
38

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

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

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

47
        "github.com/google/uuid"
48

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

500
func (r *ServiceBindingReconciler) getServiceInstanceForBinding(ctx context.Context, binding *v1.ServiceBinding) (*v1.ServiceInstance, error) {
1✔
501
        log := utils.GetLogger(ctx)
1✔
502
        serviceInstance := &v1.ServiceInstance{}
1✔
503
        namespace := binding.Namespace
1✔
504
        if len(binding.Spec.ServiceInstanceNamespace) > 0 {
2✔
505
                namespace = binding.Spec.ServiceInstanceNamespace
1✔
506
        }
1✔
507
        log.Info(fmt.Sprintf("getting service instance named %s in namespace %s for binding %s in namespace %s", binding.Spec.ServiceInstanceName, namespace, binding.Name, binding.Namespace))
1✔
508
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.ServiceInstanceName, Namespace: namespace}, serviceInstance); err != nil {
2✔
509
                return serviceInstance, err
1✔
510
        }
1✔
511

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

869
        return metadata, nil
1✔
870
}
871

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1065
        return false
1✔
1066
}
1067

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

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

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

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

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

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

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

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

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

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