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

SAP / sap-btp-service-operator / 25327412899

04 May 2026 03:20PM UTC coverage: 78.279% (-0.1%) from 78.377%
25327412899

push

github

web-flow
async provision retry fix (#631)

44 of 62 new or added lines in 5 files covered. (70.97%)

23 existing lines in 3 files now uncovered.

2829 of 3614 relevant lines covered (78.28%)

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
        // poll only if delete sm operation is in progress or there is create/update ongoing operation and instance is not marked for deletion
130
        // if marked for deletion we should trigger the sm delete and ignore the current operation url
131
        if len(serviceBinding.Status.OperationURL) > 0 &&
1✔
132
                (serviceBinding.Status.OperationType == smClientTypes.DELETE || !utils.IsMarkedForDeletion(serviceBinding.ObjectMeta)) {
2✔
133
                return r.poll(ctx, smClient, serviceBinding)
1✔
134
        }
1✔
135

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

306
func (r *ServiceBindingReconciler) delete(ctx context.Context, smClient sm.Client, serviceBinding *v1.ServiceBinding) (ctrl.Result, error) {
1✔
307
        log := logutils.GetLogger(ctx)
1✔
308
        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✔
309
        if controllerutil.ContainsFinalizer(serviceBinding, common.FinalizerName) {
2✔
310
                if len(serviceBinding.Status.BindingID) == 0 {
2✔
311
                        log.Info("No binding id found validating binding does not exists in SM before removing finalizer")
1✔
312
                        smBinding, err := r.getBindingForRecovery(ctx, smClient, serviceBinding)
1✔
313
                        if err != nil {
1✔
314
                                return utils.HandleServiceManagerError(ctx, r.Client, serviceBinding, smClientTypes.DELETE, err)
×
315
                        }
×
316
                        if smBinding != nil {
2✔
317
                                log.Info("binding exists in SM continue with deletion")
1✔
318
                                serviceBinding.Status.BindingID = smBinding.ID
1✔
319
                                utils.SetInProgressConditions(ctx, smClientTypes.DELETE, "delete after recovery", serviceBinding, false)
1✔
320
                                return ctrl.Result{}, utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
321
                        }
1✔
322

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

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

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

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

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

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

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

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

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

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

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

459
        log.Info(fmt.Sprintf("finished polling operation %s '%s'", serviceBinding.Status.OperationType, serviceBinding.Status.OperationURL))
1✔
460
        serviceBinding.Status.OperationURL = ""
1✔
461
        serviceBinding.Status.OperationType = ""
1✔
462

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

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

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

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

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

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

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

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

533
        return utils.UpdateStatus(ctx, r.Client, serviceBinding)
1✔
534
}
535

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

548
        return serviceInstance.DeepCopy(), nil
1✔
549
}
550

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

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

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

1✔
585
        var secret *corev1.Secret
1✔
586
        var err error
1✔
587

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

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

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

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

1✔
615
        return r.createOrUpdateBindingSecret(ctx, k8sBinding, secret)
1✔
616
}
617

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

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

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

1✔
639
        var credentialsMap map[string][]byte
1✔
640
        var credentialProperties []utils.SecretMetadataProperty
1✔
641

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

833
        return fmt.Errorf(secretNameTakenErrorFormat, binding.Spec.SecretName)
1✔
834
}
835

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

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

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

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

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

905
        return metadata, nil
1✔
906
}
907

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1114
        return false
1✔
1115
}
1116

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

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

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

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

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

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

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

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

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

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

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

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

1202
func shouldBindingBeDeleted(serviceBinding *v1.ServiceBinding) bool {
1✔
1203
        return utils.IsMarkedForDeletion(serviceBinding.ObjectMeta) ||
1✔
1204
                serviceBinding.IsAsyncBindFailed()
1✔
1205
}
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