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

SAP / sap-btp-service-operator / 25311451957

04 May 2026 09:27AM UTC coverage: 78.143%. First build
25311451957

Pull #631

github

kerenlahav
tests
Pull Request #631: async provision retry fix

47 of 65 new or added lines in 5 files covered. (72.31%)

2828 of 3619 relevant lines covered (78.14%)

0.88 hits per line

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

83.26
/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
        "net/http"
23
        "strings"
24
        "time"
25

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

30
        "github.com/pkg/errors"
31

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

39
        "fmt"
40

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

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

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

49
        "github.com/google/uuid"
50

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

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

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

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

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

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

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

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

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

105
        serviceInstance, instanceErr := r.getServiceInstanceForBinding(ctx, serviceBinding)
1✔
106
        if instanceErr != nil {
2✔
107
                if !apierrors.IsNotFound(instanceErr) {
1✔
108
                        log.Error(instanceErr, "failed to get service instance for binding")
×
109
                        return ctrl.Result{}, instanceErr
×
110
                }
×
111
                if !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
112
                        //instance is not found and binding is not marked for deletion
1✔
113
                        return r.handleInstanceForBindingNotFound(ctx, serviceBinding)
1✔
114
                }
1✔
115
                if len(serviceBinding.Status.BindingID) == 0 {
2✔
116
                        log.Info("service instance not found, binding is marked for deletion and has no binding id, removing finalizer if exists")
1✔
117
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
118
                                return ctrl.Result{}, err
×
119
                        }
×
120
                        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
121
                }
122
        }
123

124
        smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
125
        if err != nil {
1✔
126
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, common.Unknown, err)
×
127
        }
×
128

129
        if len(serviceBinding.Status.OperationURL) > 0 &&
1✔
130
                (serviceBinding.Status.OperationType == smClientTypes.DELETE || !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta)) {
2✔
131
                // ongoing operation - poll status from SM
1✔
132
                return r.poll(ctx, serviceBinding, smClient)
1✔
133
        }
1✔
134

135
        if shouldBindingBeDeleted(serviceBinding) {
2✔
136
                return r.delete(ctx, serviceBinding, smClient)
1✔
137
        }
1✔
138

139
        if len(serviceBinding.Status.BindingID) > 0 {
2✔
140
                if bindingExist, err := isBindingExistInSM(smClient, serviceInstance, serviceBinding.Status.BindingID, log); err != nil {
1✔
141
                        log.Error(err, "failed to check if binding exist in sm due to unknown error")
×
142
                        return ctrl.Result{}, err
×
143
                } else if !bindingExist {
2✔
144
                        log.Info("binding not found in SM, updating status")
1✔
145
                        condition := metav1.Condition{
1✔
146
                                Type:               common.ConditionReady,
1✔
147
                                Status:             metav1.ConditionFalse,
1✔
148
                                ObservedGeneration: serviceBinding.Generation,
1✔
149
                                LastTransitionTime: metav1.NewTime(time.Now()),
1✔
150
                                Reason:             common.ResourceNotFound,
1✔
151
                                Message:            fmt.Sprintf(common.ResourceNotFoundMessageFormat, "binding", serviceBinding.Status.BindingID),
1✔
152
                        }
1✔
153
                        serviceBinding.Status.Conditions = []metav1.Condition{condition}
1✔
154
                        serviceBinding.Status.Ready = metav1.ConditionFalse
1✔
155
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
156
                }
1✔
157
        }
158

159
        if controllerutil.AddFinalizer(serviceBinding, common.FinalizerName) {
2✔
160
                log.Info(fmt.Sprintf("added finalizer '%s' to service binding", common.FinalizerName))
1✔
161
                if err := r.Client.Update(ctx, serviceBinding); err != nil {
1✔
162
                        return ctrl.Result{}, err
×
163
                }
×
164
        }
165

166
        if utils.IsMarkedForDeletion(serviceInstance.ObjectMeta) {
2✔
167
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is marked for deletion, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
168
                utils.SetBlockedCondition(ctx, "instance is in deletion process", serviceBinding)
1✔
169
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
170
        }
1✔
171

172
        if !serviceInstanceReady(serviceInstance) {
2✔
173
                log.Info(fmt.Sprintf("service instance name: %s namespace: %s is not ready, unable to create binding", serviceInstance.Name, serviceInstance.Namespace))
1✔
174
                utils.SetBlockedCondition(ctx, "service instance is not ready", serviceBinding)
1✔
175
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
176
                        return ctrl.Result{}, err
×
177
                }
×
178
                return ctrl.Result{}, errors.New("ServiceInstance is not ready")
1✔
179
        }
180

181
        // should rotate creds
182
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionCredRotationInProgress) {
2✔
183
                log.Info("rotating credentials")
1✔
184
                if shouldUpdateStatus, err := r.rotateCredentials(ctx, serviceBinding, serviceInstance); err != nil {
2✔
185
                        if !shouldUpdateStatus {
2✔
186
                                log.Error(err, "internal error occurred during cred rotation, requeuing binding")
1✔
187
                                return ctrl.Result{}, err
1✔
188
                        }
1✔
189
                        return utils.HandleCredRotationError(ctx, r.Client, serviceBinding, err)
×
190
                }
191
        }
192

193
        // is binding ready
194
        if meta.IsStatusConditionTrue(serviceBinding.Status.Conditions, common.ConditionReady) {
2✔
195
                if isStaleServiceBinding(serviceBinding) {
2✔
196
                        log.Info("binding is stale, handling")
1✔
197
                        return r.handleStaleServiceBinding(ctx, serviceBinding)
1✔
198
                }
1✔
199

200
                if initCredRotationIfRequired(serviceBinding) {
2✔
201
                        log.Info("cred rotation required, updating status")
1✔
202
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
203
                }
1✔
204

205
                log.Info("binding in final state, maintaining secret")
1✔
206
                return r.maintain(ctx, smClient, serviceBinding)
1✔
207
        }
208

209
        if serviceBinding.Status.BindingID == "" {
2✔
210
                if err := r.validateSecretNameIsAvailable(ctx, serviceBinding); err != nil {
2✔
211
                        log.Error(err, "secret validation failed")
1✔
212
                        utils.SetBlockedCondition(ctx, err.Error(), serviceBinding)
1✔
213
                        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
214
                }
1✔
215

216
                smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
217
                if err != nil {
1✔
218
                        log.Error(err, "failed to check binding recovery")
×
219
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
220
                }
×
221
                if smBinding != nil {
2✔
222
                        return r.recover(ctx, serviceBinding, smBinding)
1✔
223
                }
1✔
224

225
                return r.createBinding(ctx, smClient, serviceInstance, serviceBinding)
1✔
226
        }
227

228
        log.Info("nothing to do for this binding")
1✔
229
        return ctrl.Result{}, nil
1✔
230
}
231

232
func (r *ServiceBindingReconciler) SetupWithManager(mgr ctrl.Manager) error {
1✔
233

1✔
234
        return ctrl.NewControllerManagedBy(mgr).
1✔
235
                For(&v1.ServiceBinding{}).
1✔
236
                WithOptions(controller.Options{RateLimiter: workqueue.NewTypedItemExponentialFailureRateLimiter[reconcile.Request](r.Config.RetryBaseDelay, r.Config.RetryMaxDelay)}).
1✔
237
                Complete(r)
1✔
238
}
1✔
239

240
func (r *ServiceBindingReconciler) createBinding(ctx context.Context, smClient sm.Client, serviceInstance *v1.ServiceInstance, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
241
        log := logutils.GetLogger(ctx)
1✔
242
        log.Info("Creating smBinding in SM")
1✔
243
        serviceBinding.Status.InstanceID = serviceInstance.Status.InstanceID
1✔
244
        bindingParameters, _, err := utils.BuildSMRequestParameters(serviceBinding.Namespace, serviceBinding.Spec.Parameters, serviceBinding.Spec.ParametersFrom)
1✔
245
        if err != nil {
1✔
246
                log.Error(err, "failed to parse smBinding parameters")
×
247
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, err)
×
248
        }
×
249

250
        smBinding, operationURL, bindErr := smClient.Bind(&smClientTypes.ServiceBinding{
1✔
251
                Name: serviceBinding.Spec.ExternalName,
1✔
252
                Labels: smClientTypes.Labels{
1✔
253
                        common.NamespaceLabel: []string{serviceBinding.Namespace},
1✔
254
                        common.K8sNameLabel:   []string{serviceBinding.Name},
1✔
255
                        common.ClusterIDLabel: []string{r.Config.ClusterID},
1✔
256
                },
1✔
257
                ServiceInstanceID: serviceInstance.Status.InstanceID,
1✔
258
                Parameters:        bindingParameters,
1✔
259
        }, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
260

1✔
261
        if bindErr != nil {
2✔
262
                log.Error(err, "failed to create service binding", "serviceInstanceID", serviceInstance.Status.InstanceID)
1✔
263
                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.CREATE, bindErr)
1✔
264
        }
1✔
265

266
        if operationURL != "" {
2✔
267
                var bindingID string
1✔
268
                if bindingID = sm.ExtractBindingID(operationURL); len(bindingID) == 0 {
1✔
269
                        return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, smClientTypes.CREATE, fmt.Errorf("failed to extract smBinding ID from operation URL %s", operationURL))
×
270
                }
×
271
                log.Info(fmt.Sprintf("binding is being created async, bindingID=%s", bindingID))
1✔
272
                serviceBinding.Status.BindingID = bindingID
1✔
273

1✔
274
                log.Info("Create smBinding request is async")
1✔
275
                serviceBinding.Status.OperationURL = operationURL
1✔
276
                serviceBinding.Status.OperationType = smClientTypes.CREATE
1✔
277
                utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "", serviceBinding, false)
1✔
278
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
2✔
279
                        log.Error(err, "unable to update ServiceBinding status")
1✔
280
                        return ctrl.Result{}, err
1✔
281
                }
1✔
282
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
283
        }
284

285
        log.Info("Binding created successfully")
1✔
286

1✔
287
        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
288
                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
1✔
289
        }
1✔
290

291
        subaccountID := ""
1✔
292
        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
293
                subaccountID = smBinding.Labels["subaccount_id"][0]
×
294
        }
×
295

296
        serviceBinding.Status.BindingID = smBinding.ID
1✔
297
        serviceBinding.Status.SubaccountID = subaccountID
1✔
298
        serviceBinding.Status.Ready = metav1.ConditionTrue
1✔
299
        utils.SetSuccessConditions(smClientTypes.CREATE, serviceBinding, false)
1✔
300
        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
301

1✔
302
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
303
}
304

305
func (r *ServiceBindingReconciler) delete(ctx context.Context, serviceBinding *v1.ServiceBinding, smClient sm.Client) (ctrl.Result, error) {
1✔
306
        log := logutils.GetLogger(ctx)
1✔
307
        log.Info(fmt.Sprintf("binding in delete phase, marked for deletion=%v, bindingID=%s, ready=%s", utils.IsMarkedForDeletion(serviceBinding.ObjectMeta), serviceBinding.Status.BindingID, serviceBinding.Status.Ready))
1✔
308
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
309
                if len(serviceBinding.Status.BindingID) == 0 {
2✔
310
                        log.Info("No binding id found validating binding does not exists in SM before removing finalizer")
1✔
311
                        smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
312
                        if err != nil {
1✔
313
                                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
314
                        }
×
315
                        if smBinding != nil {
2✔
316
                                log.Info("binding exists in SM continue with deletion")
1✔
317
                                serviceBinding.Status.BindingID = smBinding.ID
1✔
318
                                utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceBinding, false)
1✔
319
                                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
320
                        }
1✔
321

322
                        // make sure there's no secret stored for the binding
323
                        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
324
                                return ctrl.Result{}, err
×
325
                        }
×
326

327
                        log.Info("Binding does not exists in SM, removing finalizer")
1✔
328
                        if err := utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName); err != nil {
2✔
329
                                return ctrl.Result{}, err
1✔
330
                        }
1✔
331
                        return ctrl.Result{}, nil
1✔
332
                }
333

334
                if len(serviceBinding.Status.OperationURL) > 0 && serviceBinding.Status.OperationType == smClientTypes.DELETE {
1✔
335
                        // ongoing delete operation - poll status from SM
×
NEW
336
                        return r.poll(ctx, serviceBinding, smClient)
×
337
                }
×
338

339
                log.Info(fmt.Sprintf("Deleting binding with id %v from SM, resourceMarkedForDeletions=%v", serviceBinding.Status.BindingID, utils.IsMarkedForDeletion(serviceBinding.ObjectMeta)))
1✔
340
                operationURL, unbindErr := smClient.Unbind(serviceBinding.Status.BindingID, nil, utils.BuildUserInfo(ctx, serviceBinding.Spec.UserInfo))
1✔
341
                if unbindErr != nil {
2✔
342
                        return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, unbindErr)
1✔
343
                }
1✔
344

345
                if operationURL != "" {
2✔
346
                        log.Info("Deleting binding async")
1✔
347
                        serviceBinding.Status.OperationURL = operationURL
1✔
348
                        serviceBinding.Status.OperationType = smClientTypes.DELETE
1✔
349
                        utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "", serviceBinding, false)
1✔
350
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
351
                                return ctrl.Result{}, err
×
352
                        }
×
353
                        return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
354
                }
355

356
                log.Info("reset binding id after successful sync delete operation")
1✔
357
                serviceBinding.Status.BindingID = ""
1✔
358
                serviceBinding.Status.AsyncBindFailed = nil
1✔
359
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
360
                        log.Error(err, "unable to update ServiceBinding status after deletion")
×
361
                        return ctrl.Result{}, err
×
362
                }
×
363
                log.Info("Binding was deleted successfully")
1✔
364
                return r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
365
        }
366
        return ctrl.Result{}, nil
×
367
}
368

369
func (r *ServiceBindingReconciler) poll(ctx context.Context, serviceBinding *v1.ServiceBinding, smClient sm.Client) (ctrl.Result, error) {
1✔
370
        log := logutils.GetLogger(ctx)
1✔
371
        log.Info(fmt.Sprintf("binding resource is in progress, found operation url %s", serviceBinding.Status.OperationURL))
1✔
372

1✔
373
        status, statusErr := smClient.Status(serviceBinding.Status.OperationURL, nil)
1✔
374
        if statusErr != nil {
2✔
375
                log.Info(fmt.Sprintf("failed to fetch operation, got error from SM: %s", statusErr.Error()), "operationURL", serviceBinding.Status.OperationURL)
1✔
376
                utils.SetInProgressConditions(ctx, serviceBinding.Status.OperationType, string(smClientTypes.INPROGRESS), serviceBinding, false)
1✔
377
                freshStatus := v1.ServiceBindingStatus{
1✔
378
                        Conditions: serviceBinding.GetConditions(),
1✔
379
                }
1✔
380
                if utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) {
2✔
381
                        freshStatus.BindingID = serviceBinding.Status.BindingID
1✔
382
                }
1✔
383
                serviceBinding.Status = freshStatus
1✔
384
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
385
                        log.Error(err, "failed to update status during polling")
×
386
                }
×
387
                return ctrl.Result{}, statusErr
1✔
388
        }
389

390
        if status == nil {
1✔
391
                return utils.HandleOperationFailure(ctx, r.Client, serviceBinding, serviceBinding.Status.OperationType, fmt.Errorf("failed to get last operation status of %s", serviceBinding.Name))
×
392
        }
×
393
        switch status.State {
1✔
394
        case smClientTypes.INPROGRESS:
1✔
395
                fallthrough
1✔
396
        case smClientTypes.PENDING:
1✔
397
                log.Info(fmt.Sprintf("%s is still in progress", serviceBinding.Status.OperationURL))
1✔
398
                if len(status.Description) != 0 {
1✔
399
                        utils.SetInProgressConditions(ctx, status.Type, status.Description, serviceBinding, true)
×
400
                        if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
×
401
                                log.Error(err, "unable to update ServiceBinding polling description")
×
402
                                return ctrl.Result{}, err
×
403
                        }
×
404
                }
405
                return ctrl.Result{RequeueAfter: r.Config.PollInterval}, nil
1✔
406
        case smClientTypes.FAILED:
1✔
407
                log.Info(fmt.Sprintf("%s ended with failure", serviceBinding.Status.OperationURL))
1✔
408
                utils.SetFailureConditions(status.Type, status.Description, serviceBinding, true)
1✔
409
                if serviceBinding.Status.OperationType == smClientTypes.CREATE {
2✔
410
                        log.Info(fmt.Sprintf("async binding failed for binding id %s", serviceBinding.Status.BindingID))
1✔
411
                        trueVal := true
1✔
412
                        serviceBinding.Status.AsyncBindFailed = &trueVal
1✔
413
                }
1✔
414
                serviceBinding.Status.OperationURL = ""
1✔
415
                serviceBinding.Status.OperationType = ""
1✔
416
                if err := utils.UpdateStatus(ctx, r.Client, serviceBinding); err != nil {
1✔
417
                        log.Error(err, "unable to update ServiceBinding status")
×
418
                        return ctrl.Result{}, err
×
419
                }
×
420
                errMsg := fmt.Sprintf("Async binding %s operation failed", serviceBinding.Status.OperationType)
1✔
421
                if status.Errors != nil {
1✔
422
                        errMsg = fmt.Sprintf("Async unbind operation failed, errors: %s", string(status.Errors))
×
423
                }
×
424
                return ctrl.Result{}, errors.New(errMsg)
1✔
425
        case smClientTypes.SUCCEEDED:
1✔
426
                log.Info(fmt.Sprintf("%s completed successfully", serviceBinding.Status.OperationURL))
1✔
427
                switch serviceBinding.Status.OperationType {
1✔
428
                case smClientTypes.CREATE:
1✔
429
                        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
430
                        if err != nil || smBinding == nil {
2✔
431
                                log.Error(err, fmt.Sprintf("binding %s succeeded but could not fetch it from SM", serviceBinding.Status.BindingID))
1✔
432
                                return ctrl.Result{}, err
1✔
433
                        }
1✔
434
                        if len(smBinding.Labels["subaccount_id"]) > 0 {
1✔
435
                                serviceBinding.Status.SubaccountID = smBinding.Labels["subaccount_id"][0]
×
436
                        }
×
437

438
                        if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
439
                                return r.handleSecretError(ctx, smClientTypes.CREATE, err, serviceBinding)
×
440
                        }
×
441
                        utils.SetSuccessConditions(status.Type, serviceBinding, false)
1✔
442
                case smClientTypes.DELETE:
1✔
443
                        _, err := r.deleteSecretAndRemoveFinalizer(ctx, serviceBinding)
1✔
444
                        if err != nil {
1✔
445
                                log.Error(err, "failed to delete binding secret and remove finalizer after delete operation completed")
×
446
                                return ctrl.Result{}, err
×
447
                        }
×
448

449
                        log.Info("reset binding id after successful async delete operation")
1✔
450
                        serviceBinding.Status.BindingID = ""
1✔
451
                        serviceBinding.Status.AsyncBindFailed = nil
1✔
452
                        return ctrl.Result{RequeueAfter: time.Second}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
453
                }
454
        }
455

456
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
457
        serviceBinding.Status.OperationURL = ""
1✔
458
        serviceBinding.Status.OperationType = ""
1✔
459
        serviceBinding.Status.AsyncBindFailed = nil
1✔
460

1✔
461
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
462
}
463

464
func (r *ServiceBindingReconciler) getBindingForRecovery(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (*smClientTypes.ServiceBinding, error) {
1✔
465
        log := logutils.GetLogger(ctx)
1✔
466
        nameQuery := fmt.Sprintf("name eq '%s'", serviceBinding.Spec.ExternalName)
1✔
467
        clusterIDQuery := fmt.Sprintf("context/clusterid eq '%s'", r.Config.ClusterID)
1✔
468
        namespaceQuery := fmt.Sprintf("context/namespace eq '%s'", serviceBinding.Namespace)
1✔
469
        k8sNameQuery := fmt.Sprintf("%s eq '%s'", common.K8sNameLabel, serviceBinding.Name)
1✔
470
        parameters := sm.Parameters{
1✔
471
                FieldQuery:    []string{nameQuery, clusterIDQuery, namespaceQuery},
1✔
472
                LabelQuery:    []string{k8sNameQuery},
1✔
473
                GeneralParams: []string{"attach_last_operations=true"},
1✔
474
        }
1✔
475
        log.Info(fmt.Sprintf("binding recovery query params: %s, %s, %s, %s", nameQuery, clusterIDQuery, namespaceQuery, k8sNameQuery))
1✔
476

1✔
477
        bindings, err := smClient.ListBindings(&parameters)
1✔
478
        if err != nil {
1✔
479
                log.Error(err, "failed to list bindings in SM")
×
480
                return nil, err
×
481
        }
×
482
        if bindings != nil {
2✔
483
                log.Info(fmt.Sprintf("found %d bindings", len(bindings.ServiceBindings)))
1✔
484
                if len(bindings.ServiceBindings) == 1 {
2✔
485
                        return &bindings.ServiceBindings[0], nil
1✔
486
                }
1✔
487
        }
488
        return nil, nil
1✔
489
}
490

491
func (r *ServiceBindingReconciler) maintain(ctx context.Context, smClient sm.Client, binding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
492
        log := logutils.GetLogger(ctx)
1✔
493
        if err := r.maintainSecret(ctx, smClient, binding); err != nil {
2✔
494
                log.Error(err, "failed to maintain secret")
1✔
495
                return r.handleSecretError(ctx, smClientTypes.UPDATE, err, binding)
1✔
496
        }
1✔
497

498
        log.Info("maintain finished successfully")
1✔
499
        return ctrl.Result{}, nil
1✔
500
}
501

502
func (r *ServiceBindingReconciler) maintainSecret(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) error {
1✔
503
        log := logutils.GetLogger(ctx)
1✔
504
        if common.GetObservedGeneration(serviceBinding) == serviceBinding.Generation {
2✔
505
                log.Info("observed generation is up to date, checking if secret exists")
1✔
506
                if _, err := r.getSecret(ctx, serviceBinding.Namespace, serviceBinding.Spec.SecretName); err == nil {
2✔
507
                        log.Info("secret exists, no need to maintain secret")
1✔
508
                        return nil
1✔
509
                }
1✔
510

511
                log.Info("binding's secret was not found")
1✔
512
                r.Recorder.Eventf(serviceBinding, nil, corev1.EventTypeWarning, "SecretDeleted", "SecretDeleted", "SecretDeleted")
1✔
513
        }
514

515
        log.Info("maintaining binding's secret")
1✔
516
        smBinding, err := smClient.GetBindingByID(serviceBinding.Status.BindingID, nil)
1✔
517
        if err != nil {
1✔
518
                log.Error(err, "failed to get binding for update secret")
×
519
                return err
×
520
        }
×
521
        if smBinding != nil {
2✔
522
                if smBinding.Credentials != nil {
2✔
523
                        if err = r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
2✔
524
                                return err
1✔
525
                        }
1✔
526
                        log.Info("Updating binding", "bindingID", smBinding.ID)
1✔
527
                        utils.SetSuccessConditions(smClientTypes.UPDATE, serviceBinding, false)
1✔
528
                }
529
        }
530

531
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
532
}
533

534
func (r *ServiceBindingReconciler) getServiceInstanceForBinding(ctx context.Context, binding *v1.ServiceBinding) (*v1.ServiceInstance, error) {
1✔
535
        log := logutils.GetLogger(ctx)
1✔
536
        serviceInstance := &v1.ServiceInstance{}
1✔
537
        namespace := binding.Namespace
1✔
538
        if len(binding.Spec.ServiceInstanceNamespace) > 0 {
2✔
539
                namespace = binding.Spec.ServiceInstanceNamespace
1✔
540
        }
1✔
541
        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✔
542
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.ServiceInstanceName, Namespace: namespace}, serviceInstance); err != nil {
2✔
543
                return serviceInstance, err
1✔
544
        }
1✔
545

546
        return serviceInstance.DeepCopy(), nil
1✔
547
}
548

549
func (r *ServiceBindingReconciler) resyncBindingStatus(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) {
1✔
550
        k8sBinding.Status.BindingID = smBinding.ID
1✔
551
        k8sBinding.Status.InstanceID = smBinding.ServiceInstanceID
1✔
552
        k8sBinding.Status.OperationURL = ""
1✔
553
        k8sBinding.Status.OperationType = ""
1✔
554

1✔
555
        bindingStatus := smClientTypes.SUCCEEDED
1✔
556
        operationType := smClientTypes.CREATE
1✔
557
        description := ""
1✔
558
        if smBinding.LastOperation != nil {
2✔
559
                bindingStatus = smBinding.LastOperation.State
1✔
560
                operationType = smBinding.LastOperation.Type
1✔
561
                description = smBinding.LastOperation.Description
1✔
562
        } else if !smBinding.Ready {
3✔
563
                bindingStatus = smClientTypes.FAILED
1✔
564
        }
1✔
565
        switch bindingStatus {
1✔
566
        case smClientTypes.PENDING:
×
567
                fallthrough
×
568
        case smClientTypes.INPROGRESS:
1✔
569
                k8sBinding.Status.OperationURL = sm.BuildOperationURL(smBinding.LastOperation.ID, smBinding.ID, smClientTypes.ServiceBindingsURL)
1✔
570
                k8sBinding.Status.OperationType = smBinding.LastOperation.Type
1✔
571
                utils.SetInProgressConditions(ctx, smBinding.LastOperation.Type, smBinding.LastOperation.Description, k8sBinding, false)
1✔
572
        case smClientTypes.SUCCEEDED:
1✔
573
                utils.SetSuccessConditions(operationType, k8sBinding, false)
1✔
574
        case smClientTypes.FAILED:
1✔
575
                utils.SetFailureConditions(operationType, description, k8sBinding, false)
1✔
576
        }
577
}
578

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

1✔
583
        var secret *corev1.Secret
1✔
584
        var err error
1✔
585

1✔
586
        if k8sBinding.Spec.SecretTemplate != "" {
2✔
587
                secret, err = r.createBindingSecretFromSecretTemplate(ctx, k8sBinding, smBinding)
1✔
588
        } else {
2✔
589
                secret, err = r.createBindingSecret(ctx, k8sBinding, smBinding)
1✔
590
        }
1✔
591

592
        if err != nil {
2✔
593
                return err
1✔
594
        }
1✔
595
        if err = controllerutil.SetControllerReference(k8sBinding, secret, r.Scheme); err != nil {
1✔
596
                logger.Error(err, "Failed to set secret owner")
×
597
                return err
×
598
        }
×
599

600
        if secret.Labels == nil {
1✔
601
                secret.Labels = map[string]string{}
×
602
        }
×
603
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
604
        if len(k8sBinding.Labels) > 0 && len(k8sBinding.Labels[common.StaleBindingIDLabel]) > 0 {
2✔
605
                secret.Labels[common.StaleBindingIDLabel] = k8sBinding.Labels[common.StaleBindingIDLabel]
1✔
606
        }
1✔
607

608
        if secret.Annotations == nil {
1✔
609
                secret.Annotations = map[string]string{}
×
610
        }
×
611
        secret.Annotations["binding"] = k8sBinding.Name
1✔
612

1✔
613
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
614
}
615

616
func (r *ServiceBindingReconciler) createBindingSecret(ctx context.Context, k8sBinding *v1.ServiceBinding, smBinding *smClientTypes.ServiceBinding) (*corev1.Secret, error) {
1✔
617
        credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
618
        if err != nil {
2✔
619
                return nil, err
1✔
620
        }
1✔
621

622
        secret := &corev1.Secret{
1✔
623
                ObjectMeta: metav1.ObjectMeta{
1✔
624
                        Name:        k8sBinding.Spec.SecretName,
1✔
625
                        Annotations: map[string]string{"binding": k8sBinding.Name},
1✔
626
                        Labels:      map[string]string{common.ManagedByBTPOperatorLabel: "true"},
1✔
627
                        Namespace:   k8sBinding.Namespace,
1✔
628
                },
1✔
629
                Data: credentialsMap,
1✔
630
        }
1✔
631
        return secret, nil
1✔
632
}
633

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

1✔
637
        var credentialsMap map[string][]byte
1✔
638
        var credentialProperties []utils.SecretMetadataProperty
1✔
639

1✔
640
        if len(smBinding.Credentials) == 0 {
2✔
641
                log.Info("Binding credentials are empty")
1✔
642
                credentialsMap = make(map[string][]byte)
1✔
643
        } else if k8sBinding.Spec.SecretKey != nil {
3✔
644
                credentialsMap = map[string][]byte{
1✔
645
                        *k8sBinding.Spec.SecretKey: smBinding.Credentials,
1✔
646
                }
1✔
647
                credentialProperties = []utils.SecretMetadataProperty{
1✔
648
                        {
1✔
649
                                Name:      *k8sBinding.Spec.SecretKey,
1✔
650
                                Format:    string(utils.JSON),
1✔
651
                                Container: true,
1✔
652
                        },
1✔
653
                }
1✔
654
        } else {
2✔
655
                var err error
1✔
656
                credentialsMap, credentialProperties, err = utils.NormalizeCredentials(smBinding.Credentials)
1✔
657
                if err != nil {
2✔
658
                        log.Error(err, "Failed to store binding secret")
1✔
659
                        return nil, fmt.Errorf("failed to create secret. Error: %v", err.Error())
1✔
660
                }
1✔
661
        }
662

663
        metaDataProperties, err := r.addInstanceInfo(ctx, k8sBinding, credentialsMap)
1✔
664
        if err != nil {
1✔
665
                log.Error(err, "failed to enrich binding with service instance info")
×
666
        }
×
667

668
        if k8sBinding.Spec.SecretRootKey != nil {
2✔
669
                var err error
1✔
670
                credentialsMap, err = singleKeyMap(credentialsMap, *k8sBinding.Spec.SecretRootKey)
1✔
671
                if err != nil {
1✔
672
                        return nil, err
×
673
                }
×
674
        } else {
1✔
675
                metadata := map[string][]utils.SecretMetadataProperty{
1✔
676
                        "metaDataProperties":   metaDataProperties,
1✔
677
                        "credentialProperties": credentialProperties,
1✔
678
                }
1✔
679
                metadataByte, err := json.Marshal(metadata)
1✔
680
                if err != nil {
1✔
681
                        log.Error(err, "failed to enrich binding with metadata")
×
682
                } else {
1✔
683
                        credentialsMap[".metadata"] = metadataByte
1✔
684
                }
1✔
685
        }
686
        return credentialsMap, nil
1✔
687
}
688

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

1✔
693
        logger.Info("Create Object using SecretTemplate from ServiceBinding Specs")
1✔
694
        inputSmCredentials := smBinding.Credentials
1✔
695
        smBindingCredentials := make(map[string]interface{})
1✔
696
        if inputSmCredentials != nil {
2✔
697
                err := json.Unmarshal(inputSmCredentials, &smBindingCredentials)
1✔
698
                if err != nil {
1✔
699
                        logger.Error(err, "failed to unmarshal given service binding credentials")
×
700
                        return nil, errors.Wrap(err, "failed to unmarshal given service binding credentials")
×
701
                }
×
702
        }
703

704
        instanceInfos, err := r.getInstanceInfo(ctx, k8sBinding)
1✔
705
        if err != nil {
1✔
706
                logger.Error(err, "failed to addInstanceInfo")
×
707
                return nil, errors.Wrap(err, "failed to add service instance info")
×
708
        }
×
709

710
        parameters := commonutils.GetSecretDataForTemplate(smBindingCredentials, instanceInfos)
1✔
711
        templateName := fmt.Sprintf("%s/%s", k8sBinding.Namespace, k8sBinding.Name)
1✔
712
        secret, err := commonutils.CreateSecretFromTemplate(templateName, k8sBinding.Spec.SecretTemplate, "missingkey=error", parameters)
1✔
713
        if err != nil {
2✔
714
                logger.Error(err, "failed to create secret from template")
1✔
715
                return nil, errors.Wrap(err, "failed to create secret from template")
1✔
716
        }
1✔
717
        secret.SetNamespace(k8sBinding.Namespace)
1✔
718
        secret.SetName(k8sBinding.Spec.SecretName)
1✔
719
        if secret.Labels == nil {
1✔
720
                secret.Labels = map[string]string{}
×
721
        }
×
722
        secret.Labels[common.ManagedByBTPOperatorLabel] = "true"
1✔
723

1✔
724
        // if no data provided use the default data
1✔
725
        if len(secret.Data) == 0 && len(secret.StringData) == 0 {
2✔
726
                credentialsMap, err := r.getSecretDefaultData(ctx, k8sBinding, smBinding)
1✔
727
                if err != nil {
1✔
728
                        return nil, err
×
729
                }
×
730
                secret.Data = credentialsMap
1✔
731
        }
732
        return secret, nil
1✔
733
}
734

735
func (r *ServiceBindingReconciler) createOrUpdateBindingSecret(ctx context.Context, binding *v1.ServiceBinding, secret *corev1.Secret) error {
1✔
736
        log := logutils.GetLogger(ctx)
1✔
737
        dbSecret := &corev1.Secret{}
1✔
738
        create := false
1✔
739
        if err := r.Client.Get(ctx, types.NamespacedName{Name: binding.Spec.SecretName, Namespace: binding.Namespace}, dbSecret); err != nil {
2✔
740
                if !apierrors.IsNotFound(err) {
1✔
741
                        return err
×
742
                }
×
743
                create = true
1✔
744
        }
745

746
        if create {
2✔
747
                log.Info("Creating binding secret", "name", secret.Name)
1✔
748
                if err := r.Client.Create(ctx, secret); err != nil {
1✔
749
                        if !apierrors.IsAlreadyExists(err) {
×
750
                                return err
×
751
                        }
×
752
                        return nil
×
753
                }
754
                r.Recorder.Eventf(binding, nil, corev1.EventTypeNormal, "SecretCreated", "SecretCreated", "SecretCreated")
1✔
755
                return nil
1✔
756
        }
757

758
        log.Info("Updating existing binding secret", "name", secret.Name)
1✔
759
        dbSecret.Data = secret.Data
1✔
760
        dbSecret.StringData = secret.StringData
1✔
761
        dbSecret.Labels = secret.Labels
1✔
762
        dbSecret.Annotations = secret.Annotations
1✔
763
        return r.Client.Update(ctx, dbSecret)
1✔
764
}
765

766
func (r *ServiceBindingReconciler) deleteBindingSecret(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
767
        log := logutils.GetLogger(ctx)
1✔
768
        log.Info("Deleting binding secret")
1✔
769
        bindingSecret := &corev1.Secret{}
1✔
770
        if err := r.Client.Get(ctx, types.NamespacedName{
1✔
771
                Namespace: binding.Namespace,
1✔
772
                Name:      binding.Spec.SecretName,
1✔
773
        }, bindingSecret); err != nil {
2✔
774
                if !apierrors.IsNotFound(err) {
1✔
775
                        log.Error(err, "unable to fetch binding secret")
×
776
                        return err
×
777
                }
×
778

779
                // secret not found, nothing more to do
780
                log.Info("secret was deleted successfully")
1✔
781
                return nil
1✔
782
        }
783
        bindingSecret = bindingSecret.DeepCopy()
1✔
784

1✔
785
        if err := r.Client.Delete(ctx, bindingSecret); err != nil {
1✔
786
                log.Error(err, "Failed to delete binding secret")
×
787
                return err
×
788
        }
×
789

790
        log.Info("secret was deleted successfully")
1✔
791
        return nil
1✔
792
}
793

794
func (r *ServiceBindingReconciler) deleteSecretAndRemoveFinalizer(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
795
        // delete binding secret if exist
1✔
796
        if err := r.deleteBindingSecret(ctx, serviceBinding); err != nil {
1✔
797
                return ctrl.Result{}, err
×
798
        }
×
799

800
        return ctrl.Result{}, utils.RemoveFinalizer(ctx, r.Client, serviceBinding, common.FinalizerName)
1✔
801
}
802

803
func (r *ServiceBindingReconciler) getSecret(ctx context.Context, namespace string, name string) (*corev1.Secret, error) {
1✔
804
        secret := &corev1.Secret{}
1✔
805
        err := utils.GetSecretWithFallback(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret)
1✔
806
        return secret, err
1✔
807
}
1✔
808

809
func (r *ServiceBindingReconciler) validateSecretNameIsAvailable(ctx context.Context, binding *v1.ServiceBinding) error {
1✔
810
        currentSecret, err := r.getSecret(ctx, binding.Namespace, binding.Spec.SecretName)
1✔
811
        if err != nil {
2✔
812
                return client.IgnoreNotFound(err)
1✔
813
        }
1✔
814

815
        if metav1.IsControlledBy(currentSecret, binding) {
2✔
816
                return nil
1✔
817
        }
1✔
818

819
        ownerRef := metav1.GetControllerOf(currentSecret)
1✔
820
        if ownerRef != nil {
2✔
821
                owner, err := schema.ParseGroupVersion(ownerRef.APIVersion)
1✔
822
                if err != nil {
1✔
823
                        return err
×
824
                }
×
825

826
                if owner.Group == binding.GroupVersionKind().Group && ownerRef.Kind == binding.Kind {
2✔
827
                        return fmt.Errorf(secretAlreadyOwnedErrorFormat, binding.Spec.SecretName, ownerRef.Name)
1✔
828
                }
1✔
829
        }
830

831
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
832
}
833

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

840
func (r *ServiceBindingReconciler) getInstanceInfo(ctx context.Context, binding *v1.ServiceBinding) (map[string]string, error) {
1✔
841
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
842
        if err != nil {
1✔
843
                return nil, err
×
844
        }
×
845
        instanceInfos := make(map[string]string)
1✔
846
        instanceInfos["instance_name"] = string(getInstanceNameForSecretCredentials(instance))
1✔
847
        instanceInfos["instance_guid"] = instance.Status.InstanceID
1✔
848
        instanceInfos["plan"] = instance.Spec.ServicePlanName
1✔
849
        instanceInfos["label"] = instance.Spec.ServiceOfferingName
1✔
850
        instanceInfos["type"] = instance.Spec.ServiceOfferingName
1✔
851
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
852
                tags := mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags)
1✔
853
                instanceInfos["tags"] = strings.Join(tags, ",")
1✔
854
        }
1✔
855
        return instanceInfos, nil
1✔
856
}
857

858
func (r *ServiceBindingReconciler) addInstanceInfo(ctx context.Context, binding *v1.ServiceBinding, credentialsMap map[string][]byte) ([]utils.SecretMetadataProperty, error) {
1✔
859
        instance, err := r.getServiceInstanceForBinding(ctx, binding)
1✔
860
        if err != nil {
1✔
861
                return nil, err
×
862
        }
×
863

864
        credentialsMap["instance_name"] = getInstanceNameForSecretCredentials(instance)
1✔
865
        credentialsMap["instance_guid"] = []byte(instance.Status.InstanceID)
1✔
866
        credentialsMap["plan"] = []byte(instance.Spec.ServicePlanName)
1✔
867
        credentialsMap["label"] = []byte(instance.Spec.ServiceOfferingName)
1✔
868
        credentialsMap["type"] = []byte(instance.Spec.ServiceOfferingName)
1✔
869
        if len(instance.Status.Tags) > 0 || len(instance.Spec.CustomTags) > 0 {
2✔
870
                tagsBytes, err := json.Marshal(mergeInstanceTags(instance.Status.Tags, instance.Spec.CustomTags))
1✔
871
                if err != nil {
1✔
872
                        return nil, err
×
873
                }
×
874
                credentialsMap["tags"] = tagsBytes
1✔
875
        }
876

877
        metadata := []utils.SecretMetadataProperty{
1✔
878
                {
1✔
879
                        Name:   "instance_name",
1✔
880
                        Format: string(utils.TEXT),
1✔
881
                },
1✔
882
                {
1✔
883
                        Name:   "instance_guid",
1✔
884
                        Format: string(utils.TEXT),
1✔
885
                },
1✔
886
                {
1✔
887
                        Name:   "plan",
1✔
888
                        Format: string(utils.TEXT),
1✔
889
                },
1✔
890
                {
1✔
891
                        Name:   "label",
1✔
892
                        Format: string(utils.TEXT),
1✔
893
                },
1✔
894
                {
1✔
895
                        Name:   "type",
1✔
896
                        Format: string(utils.TEXT),
1✔
897
                },
1✔
898
        }
1✔
899
        if _, ok := credentialsMap["tags"]; ok {
2✔
900
                metadata = append(metadata, utils.SecretMetadataProperty{Name: "tags", Format: string(utils.JSON)})
1✔
901
        }
1✔
902

903
        return metadata, nil
1✔
904
}
905

906
func (r *ServiceBindingReconciler) rotateCredentials(ctx context.Context, binding *v1.ServiceBinding, serviceInstance *v1.ServiceInstance) (bool, error) {
1✔
907
        log := logutils.GetLogger(ctx)
1✔
908
        if err := r.removeForceRotateAnnotationIfNeeded(ctx, binding, log); err != nil {
1✔
909
                log.Info("Credentials rotation - failed to delete force rotate annotation")
×
910
                return false, err
×
911
        }
×
912

913
        credInProgressCondition := meta.FindStatusCondition(binding.GetConditions(), common.ConditionCredRotationInProgress)
1✔
914
        if credInProgressCondition.Reason == common.CredRotating {
2✔
915
                if len(binding.Status.BindingID) > 0 && binding.Status.Ready == metav1.ConditionTrue {
2✔
916
                        log.Info("Credentials rotation - finished successfully")
1✔
917
                        now := metav1.NewTime(time.Now())
1✔
918
                        binding.Status.LastCredentialsRotationTime = &now
1✔
919
                        return false, r.stopRotation(ctx, binding)
1✔
920
                }
1✔
921
                log.Info("Credentials rotation - waiting to finish")
1✔
922
                return false, nil
1✔
923
        }
924

925
        if len(binding.Status.BindingID) == 0 {
1✔
926
                log.Info("Credentials rotation - no binding id found nothing to do")
×
927
                return false, r.stopRotation(ctx, binding)
×
928
        }
×
929

930
        bindings := &v1.ServiceBindingList{}
1✔
931
        err := r.Client.List(ctx, bindings, client.MatchingLabels{common.StaleBindingIDLabel: binding.Status.BindingID}, client.InNamespace(binding.Namespace))
1✔
932
        if err != nil {
1✔
933
                return false, err
×
934
        }
×
935

936
        if len(bindings.Items) == 0 {
2✔
937
                // create the backup binding
1✔
938
                smClient, err := r.GetSMClient(ctx, serviceInstance)
1✔
939
                if err != nil {
1✔
940
                        return false, err
×
941
                }
×
942

943
                // rename current binding
944
                suffix := "-" + utils.RandStringRunes(6)
1✔
945
                log.Info("Credentials rotation - renaming binding to old in SM", "current", binding.Spec.ExternalName)
1✔
946
                if _, errRenaming := smClient.RenameBinding(binding.Status.BindingID, binding.Spec.ExternalName+suffix, binding.Name+suffix); errRenaming != nil {
1✔
947
                        log.Error(errRenaming, "Credentials rotation - failed renaming binding to old in SM", "binding", binding.Spec.ExternalName)
×
948
                        return true, errRenaming
×
949
                }
×
950

951
                log.Info("Credentials rotation - backing up old binding in K8S", "name", binding.Name+suffix)
1✔
952
                if err := r.createOldBinding(ctx, suffix, binding); err != nil {
1✔
953
                        log.Error(err, "Credentials rotation - failed to back up old binding in K8S")
×
954
                        return true, err
×
955
                }
×
956
        }
957

958
        log.Info("reset binding id after successful rotation")
1✔
959
        binding.Status.BindingID = ""
1✔
960
        binding.Status.Ready = metav1.ConditionFalse
1✔
961
        utils.SetInProgressConditions(ctx, smClientTypes.CREATE, "rotating binding credentials", binding, false)
1✔
962
        utils.SetCredRotationInProgressConditions(common.CredRotating, "", binding)
1✔
963
        return false, utils.UpdateStatus(ctx, r.Client, binding)
1✔
964
}
965

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

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

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

1005
func (r *ServiceBindingReconciler) handleStaleServiceBinding(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
1006
        log := logutils.GetLogger(ctx)
1✔
1007
        originalBindingName, ok := serviceBinding.Annotations[common.StaleBindingOrigBindingNameAnnotation]
1✔
1008
        if !ok {
2✔
1009
                //if the user removed the "OrigBindingName" annotation and rotationOf label not exist as well
1✔
1010
                //the stale binding should be deleted otherwise it will remain forever
1✔
1011
                if originalBindingName, ok = serviceBinding.Labels[common.StaleBindingRotationOfLabel]; !ok {
2✔
1012
                        log.Info("missing rotationOf label/annotation, unable to fetch original binding, deleting stale")
1✔
1013
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1014
                }
1✔
1015
        }
1016
        origBinding := &v1.ServiceBinding{}
1✔
1017
        if err := r.Client.Get(ctx, types.NamespacedName{Namespace: serviceBinding.Namespace, Name: originalBindingName}, origBinding); err != nil {
1✔
1018
                if apierrors.IsNotFound(err) {
×
1019
                        log.Info("original binding not found, deleting stale binding")
×
1020
                        return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
×
1021
                }
×
1022
                return ctrl.Result{}, err
×
1023
        }
1024
        if meta.IsStatusConditionTrue(origBinding.Status.Conditions, common.ConditionReady) {
2✔
1025
                return ctrl.Result{}, r.Client.Delete(ctx, serviceBinding)
1✔
1026
        }
1✔
1027

1028
        log.Info("not deleting stale binding since original binding is not ready")
1✔
1029
        if !meta.IsStatusConditionPresentAndEqual(serviceBinding.Status.Conditions, common.ConditionPendingTermination, metav1.ConditionTrue) {
2✔
1030
                pendingTerminationCondition := metav1.Condition{
1✔
1031
                        Type:               common.ConditionPendingTermination,
1✔
1032
                        Status:             metav1.ConditionTrue,
1✔
1033
                        Reason:             common.ConditionPendingTermination,
1✔
1034
                        Message:            "waiting for new credentials to be ready",
1✔
1035
                        ObservedGeneration: serviceBinding.GetGeneration(),
1✔
1036
                }
1✔
1037
                meta.SetStatusCondition(&serviceBinding.Status.Conditions, pendingTerminationCondition)
1✔
1038
                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1039
        }
1✔
1040
        return ctrl.Result{}, nil
1✔
1041
}
1042

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

1✔
1047
        if smBinding.Credentials != nil {
2✔
1048
                if err := r.storeBindingSecret(ctx, serviceBinding, smBinding); err != nil {
1✔
1049
                        operationType := smClientTypes.CREATE
×
1050
                        if smBinding.LastOperation != nil {
×
1051
                                operationType = smBinding.LastOperation.Type
×
1052
                        }
×
1053
                        return r.handleSecretError(ctx, operationType, err, serviceBinding)
×
1054
                }
1055
        }
1056
        r.resyncBindingStatus(ctx, serviceBinding, smBinding)
1✔
1057

1✔
1058
        return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
1059
}
1060

1061
func (r *ServiceBindingReconciler) handleInstanceForBindingNotFound(ctx context.Context, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
1062
        log := logutils.GetLogger(ctx)
1✔
1063
        instanceNamespace := serviceBinding.Namespace
1✔
1064
        if len(serviceBinding.Spec.ServiceInstanceNamespace) > 0 {
1✔
1065
                instanceNamespace = serviceBinding.Spec.ServiceInstanceNamespace
×
1066
        }
×
1067
        errMsg := fmt.Sprintf("couldn't find the service instance '%s' in namespace '%s'", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1✔
1068
        log.Info(errMsg)
1✔
1069
        utils.SetBlockedCondition(ctx, errMsg, serviceBinding)
1✔
1070
        if updateErr := utils.UpdateStatus(ctx, r.Client, serviceBinding); updateErr != nil {
2✔
1071
                return ctrl.Result{}, updateErr
1✔
1072
        }
1✔
1073
        return ctrl.Result{}, fmt.Errorf("instance %s not found in namespace %s", serviceBinding.Spec.ServiceInstanceName, instanceNamespace)
1✔
1074
}
1075

1076
func isStaleServiceBinding(binding *v1.ServiceBinding) bool {
1✔
1077
        if utils.IsMarkedForDeletion(binding.ObjectMeta) {
1✔
1078
                return false
×
1079
        }
×
1080

1081
        if binding.Labels != nil {
2✔
1082
                if _, ok := binding.Labels[common.StaleBindingIDLabel]; ok {
2✔
1083
                        if binding.Spec.CredRotationPolicy != nil {
2✔
1084
                                keepFor, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotatedBindingTTL)
1✔
1085
                                if time.Since(binding.CreationTimestamp.Time) > keepFor {
2✔
1086
                                        return true
1✔
1087
                                }
1✔
1088
                        }
1089
                }
1090
        }
1091
        return false
1✔
1092
}
1093

1094
func initCredRotationIfRequired(binding *v1.ServiceBinding) bool {
1✔
1095
        if utils.IsFailed(binding) || !credRotationEnabled(binding) {
2✔
1096
                return false
1✔
1097
        }
1✔
1098
        _, forceRotate := binding.Annotations[common.ForceRotateAnnotation]
1✔
1099

1✔
1100
        lastCredentialRotationTime := binding.Status.LastCredentialsRotationTime
1✔
1101
        if lastCredentialRotationTime == nil {
2✔
1102
                ts := metav1.NewTime(binding.CreationTimestamp.Time)
1✔
1103
                lastCredentialRotationTime = &ts
1✔
1104
        }
1✔
1105

1106
        rotationInterval, _ := time.ParseDuration(binding.Spec.CredRotationPolicy.RotationFrequency)
1✔
1107
        if time.Since(lastCredentialRotationTime.Time) > rotationInterval || forceRotate {
2✔
1108
                utils.SetCredRotationInProgressConditions(common.CredPreparing, "", binding)
1✔
1109
                return true
1✔
1110
        }
1✔
1111

1112
        return false
1✔
1113
}
1114

1115
func credRotationEnabled(binding *v1.ServiceBinding) bool {
1✔
1116
        return binding.Spec.CredRotationPolicy != nil && binding.Spec.CredRotationPolicy.Enabled
1✔
1117
}
1✔
1118

1119
func mergeInstanceTags(offeringTags, customTags []string) []string {
1✔
1120
        var tags []string
1✔
1121

1✔
1122
        for _, tag := range append(offeringTags, customTags...) {
2✔
1123
                if !utils.SliceContains(tags, tag) {
2✔
1124
                        tags = append(tags, tag)
1✔
1125
                }
1✔
1126
        }
1127
        return tags
1✔
1128
}
1129

1130
func newBindingObject(name, namespace string) *v1.ServiceBinding {
1✔
1131
        return &v1.ServiceBinding{
1✔
1132
                TypeMeta: metav1.TypeMeta{
1✔
1133
                        APIVersion: v1.GroupVersion.String(),
1✔
1134
                        Kind:       "ServiceBinding",
1✔
1135
                },
1✔
1136
                ObjectMeta: metav1.ObjectMeta{
1✔
1137
                        Name:      name,
1✔
1138
                        Namespace: namespace,
1✔
1139
                },
1✔
1140
        }
1✔
1141
}
1✔
1142

1143
func serviceInstanceReady(instance *v1.ServiceInstance) bool {
1✔
1144
        return instance.Status.Ready == metav1.ConditionTrue
1✔
1145
}
1✔
1146

1147
func getInstanceNameForSecretCredentials(instance *v1.ServiceInstance) []byte {
1✔
1148
        if useMetaName, ok := instance.Annotations[common.UseInstanceMetadataNameInSecret]; ok && useMetaName == "true" {
2✔
1149
                return []byte(instance.Name)
1✔
1150
        }
1✔
1151
        return []byte(instance.Spec.ExternalName)
1✔
1152
}
1153

1154
func singleKeyMap(credentialsMap map[string][]byte, key string) (map[string][]byte, error) {
1✔
1155
        stringCredentialsMap := make(map[string]string)
1✔
1156
        for k, v := range credentialsMap {
2✔
1157
                stringCredentialsMap[k] = string(v)
1✔
1158
        }
1✔
1159

1160
        credBytes, err := json.Marshal(stringCredentialsMap)
1✔
1161
        if err != nil {
1✔
1162
                return nil, err
×
1163
        }
×
1164

1165
        return map[string][]byte{
1✔
1166
                key: credBytes,
1✔
1167
        }, nil
1✔
1168
}
1169

1170
func truncateString(str string, length int) string {
1✔
1171
        if len(str) > length {
2✔
1172
                return str[:length]
1✔
1173
        }
1✔
1174
        return str
1✔
1175
}
1176

1177
func isBindingExistInSM(smClient sm.Client, instance *v1.ServiceInstance, bindingID string, log logr.Logger) (bool, error) {
1✔
1178
        log.Info("checking if k8s instance status is NotFound")
1✔
1179
        instanceReadyCond := meta.FindStatusCondition(instance.GetConditions(), common.ConditionReady)
1✔
1180
        if instanceReadyCond != nil && instanceReadyCond.Reason == common.ResourceNotFound {
2✔
1181
                log.Info("k8s instance is in NotFound state -> invalid binding")
1✔
1182
                return false, nil
1✔
1183
        }
1✔
1184

1185
        log.Info(fmt.Sprintf("trying to get from SM binding with id %s", bindingID))
1✔
1186
        if _, err := smClient.GetBindingByID(bindingID, nil); err != nil {
1✔
1187
                var smError *sm.ServiceManagerError
×
1188
                if ok := errors.As(err, &smError); ok {
×
1189
                        log.Error(smError, fmt.Sprintf("SM returned status code %d", smError.StatusCode))
×
1190
                        if smError.StatusCode == http.StatusNotFound {
×
1191
                                return false, nil
×
1192
                        }
×
1193
                }
1194
                return false, err
×
1195
        }
1196
        log.Info("binding found in SM")
1✔
1197
        return true, nil
1✔
1198
}
1199

1200
func shouldBindingBeDeleted(serviceBinding *v1.ServiceBinding) bool {
1✔
1201
        return utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) ||
1✔
1202
                serviceBinding.IsAsyncBindFailed()
1✔
1203
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc