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

SAP / sap-btp-service-operator / 12546731727

30 Dec 2024 12:45PM UTC coverage: 80.503% (-0.1%) from 80.646%
12546731727

Pull #497

github

web-flow
Update README.md
Pull Request #497: watchParametersFromChanges.md

2816 of 3498 relevant lines covered (80.5%)

0.91 hits per line

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

81.15
/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

27
        "github.com/pkg/errors"
28

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

36
        "fmt"
37

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

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

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

46
        "github.com/google/uuid"
47

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

150
        // should rotate creds
151
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionCredRotationInProgress) {
2✔
152
                log.Info("rotating credentials")
1✔
153
                if err := r.rotateCredentials(ctx, serviceBinding, serviceInstance); err != nil {
2✔
154
                        return ctrl.Result{}, err
1✔
155
                }
1✔
156
        }
157

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

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

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

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

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

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

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

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

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

213
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
214
        return ctrl.NewControllerManagedBy(mgr).
1✔
215
                For(&v1.ServiceBinding{}).
1✔
216
                WithOptions(controller.Options{RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
217
                Complete(r)
1✔
218
}
1✔
219

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

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

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

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

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

264
        log.Info("Binding created successfully")
1✔
265

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

502
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
503
}
504

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

517
        return serviceInstance.DeepCopy(), nil
1✔
518
}
519

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

534
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
535
        k8sBinding.Status.BindingID = smBinding.ID
1✔
536
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
537
        k8sBinding.Status.OperationURL = ""
1✔
538
        k8sBinding.Status.OperationType = ""
1✔
539

1✔
540
        bindingStatus := smClientTypes.SUCCEEDED
1✔
541
        operationType := smClientTypes.CREATE
1✔
542
        description := ""
1✔
543
        if smBinding.LastOperation != nil {
2✔
544
                bindingStatus = smBinding.LastOperation.State
1✔
545
                operationType = smBinding.LastOperation.Type
1✔
546
                description = smBinding.LastOperation.Description
1✔
547
        } else if !smBinding.Ready {
3✔
548
                bindingStatus = smClientTypes.FAILED
1✔
549
        }
1✔
550
        switch bindingStatus {
1✔
551
        case smClientTypes.PENDING:
×
552
                fallthrough
×
553
        case smClientTypes.INPROGRESS:
1✔
554
                k8sBinding.Status.OperationURL = sm.BuildOperationURL(smBinding.LastOperation.ID, smBinding.ID, smClientTypes.ServiceBindingsURL)
1✔
555
                k8sBinding.Status.OperationType = smBinding.LastOperation.Type
1✔
556
                utils.SetInProgressConditions(ctx, smBinding.LastOperation.Type, smBinding.LastOperation.Description, k8sBinding, false)
1✔
557
        case smClientTypes.SUCCEEDED:
1✔
558
                utils.SetSuccessConditions(operationType, k8sBinding, false)
1✔
559
        case smClientTypes.FAILED:
1✔
560
                utils.SetFailureConditions(operationType, description, k8sBinding, false)
1✔
561
        }
562
}
563

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

1✔
568
        var secret *corev1.Secret
1✔
569
        var err error
1✔
570

1✔
571
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
572
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
573
        } else {
2✔
574
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
575
        }
1✔
576

577
        if err != nil {
2✔
578
                return err
1✔
579
        }
1✔
580
        if err = controllerutil.SetControllerReference(k8sBinding, secret, r.Scheme); err != nil {
1✔
581
                logger.Error(err, "Failed to set secret owner")
×
582
                return err
×
583
        }
×
584

585
        if secret.Labels == nil {
1✔
586
                secret.Labels = map[string]string{}
×
587
        }
×
588
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
589

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

1✔
595
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
596
}
597

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

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

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

1✔
619
        var credentialsMap map[string][]byte
1✔
620
        var credentialProperties []utils.SecretMetadataProperty
1✔
621

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

813
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
814
}
815

816
func (r *ServiceBindingReconciler) handleSecretError(ctx context.Context, op smClientTypes.OperationCategory, err error, binding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
817
        log := utils.GetLogger(ctx)
1✔
818
        log.Error(err, fmt.Sprintf("failed to store secret %s for binding %s", binding.Spec.SecretName, binding.Name))
1✔
819
        if apierrors.ReasonForError(err) == metav1.StatusReasonUnknown {
2✔
820
                return utils.MarkAsNonTransientError(ctx, r.Client, op, err, binding)
1✔
821
        }
1✔
822
        return utils.MarkAsTransientError(ctx, r.Client, op, err, binding)
×
823
}
824

825
func (r *ServiceBindingReconciler) getInstanceInfo(ctx context.Context, binding *v1.ServiceBinding) (map[string]string, error) {
1✔
826
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
827
        if err != nil {
1✔
828
                return nil, err
×
829
        }
×
830
        instanceInfos := make(map[string]string)
1✔
831
        instanceInfos["instance_name"] = string(getInstanceNameForSecretCredentials(instance))
1✔
832
        instanceInfos["instance_guid"] = instance.Status.InstanceID
1✔
833
        instanceInfos["plan"] = instance.Spec.ServicePlanName
1✔
834
        instanceInfos["label"] = instance.Spec.ServiceOfferingName
1✔
835
        instanceInfos["type"] = instance.Spec.ServiceOfferingName
1✔
836
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
837
                tags := mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags)
1✔
838
                instanceInfos["tags"] = strings.Join(tags, ",")
1✔
839
        }
1✔
840
        return instanceInfos, nil
1✔
841
}
842

843
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *v1.ServiceBinding, credentialsMap map[string][]byte) ([]utils.SecretMetadataProperty, error) {
1✔
844
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
845
        if err != nil {
2✔
846
                return nil, err
1✔
847
        }
1✔
848

849
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
1✔
850
        credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
1✔
851
        credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
1✔
852
        credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
1✔
853
        credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
1✔
854
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
855
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
856
                if err != nil {
1✔
857
                        return nil, err
×
858
                }
×
859
                credentialsMap["tags"] = tagsBytes
1✔
860
        }
861

862
        metadata := []utils.SecretMetadataProperty{
1✔
863
                {
1✔
864
                        Name:   "instance_name",
1✔
865
                        Format: string(utils.TEXT),
1✔
866
                },
1✔
867
                {
1✔
868
                        Name:   "instance_guid",
1✔
869
                        Format: string(utils.TEXT),
1✔
870
                },
1✔
871
                {
1✔
872
                        Name:   "plan",
1✔
873
                        Format: string(utils.TEXT),
1✔
874
                },
1✔
875
                {
1✔
876
                        Name:   "label",
1✔
877
                        Format: string(utils.TEXT),
1✔
878
                },
1✔
879
                {
1✔
880
                        Name:   "type",
1✔
881
                        Format: string(utils.TEXT),
1✔
882
                },
1✔
883
        }
1✔
884
        if _, ok := credentialsMap["tags"]; ok {
2✔
885
                metadata = append(metadata, utils.SecretMetadataProperty{Name: "tags", Format: string(utils.JSON)})
1✔
886
        }
1✔
887

888
        return metadata, nil
1✔
889
}
890

891
func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, binding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) error {
1✔
892
        log := utils.GetLogger(ctx)
1✔
893
        if err := r.removeForceRotateAnnotationIfNeeded(ctx, binding, log); err != nil {
1✔
894
                log.Info("Credentials rotation - failed to delete force rotate annotation")
×
895
                return err
×
896
        }
×
897

898
        credInProgressCondition := meta.FindStatusCondition(binding.GetConditions(), common.ConditionCredRotationInProgress)
1✔
899
        if credInProgressCondition.Reason == common.CredRotating {
2✔
900
                if len(binding.Status.BindingID) > 0 && binding.Status.Ready == metav1.ConditionTrue {
2✔
901
                        log.Info("Credentials rotation - finished successfully")
1✔
902
                        now := metav1.NewTime(time.Now())
1✔
903
                        binding.Status.LastCredentialsRotationTime = &now
1✔
904
                        return r.stopRotation(ctx, binding)
1✔
905
                } else if utils.IsFailed(binding) {
2✔
906
                        log.Info("Credentials rotation - binding failed stopping rotation")
×
907
                        return r.stopRotation(ctx, binding)
×
908
                }
×
909
                log.Info("Credentials rotation - waiting to finish")
1✔
910
                return nil
1✔
911
        }
912

913
        if len(binding.Status.BindingID) == 0 {
1✔
914
                log.Info("Credentials rotation - no binding id found nothing to do")
×
915
                return r.stopRotation(ctx, binding)
×
916
        }
×
917

918
        bindings := &v1.ServiceBindingList{}
1✔
919
        err := r.Client.List(ctx, bindings, client.MatchingLabels{common.StaleBindingIDLabel: binding.Status.BindingID}, client.InNamespace(binding.Namespace))
1✔
920
        if err != nil {
1✔
921
                return err
×
922
        }
×
923

924
        if len(bindings.Items) == 0 {
2✔
925
                // create the backup binding
1✔
926
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
927
                if err != nil {
1✔
928
                        return err
×
929
                }
×
930

931
                // rename current binding
932
                suffix := "-" + utils.RandStringRunes(6)
1✔
933
                log.Info("Credentials rotation - renaming binding to old in SM", "current", binding.Spec.ExternalName)
1✔
934
                if _, errRenaming := smClient.RenameBinding(binding.Status.BindingID, binding.Spec.ExternalName+suffix, binding.Name+suffix); errRenaming != nil {
1✔
935
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
936
                        utils.SetCredRotationInProgressConditions(common.CredPreparing, errRenaming.Error(), binding)
×
937
                        if errStatus := utils.UpdateStatus(ctx, r.Client, binding); errStatus != nil {
×
938
                                return errStatus
×
939
                        }
×
940
                        return errRenaming
×
941
                }
942

943
                log.Info("Credentials rotation - backing up old binding in K8S", "name", binding.Name+suffix)
1✔
944
                if err := r.createOldBinding(ctx, suffix, binding); err != nil {
1✔
945
                        log.Error(err, "Credentials rotation - failed to back up old binding in K8S")
×
946

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1087
        return false
1✔
1088
}
1089

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

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

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

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

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

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

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

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

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

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

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

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