From b7cad085f71115fee895e6315008f9986971635f Mon Sep 17 00:00:00 2001 From: Navid Shakibapour Date: Mon, 11 May 2020 18:22:36 -0400 Subject: [PATCH 1/8] Adding support for embedded ServiceBinding --- .../app.stacks_runtimecomponents_crd.yaml | 54 +++---- .../v1beta1/runtimecomponent_types.go | 11 +- .../v1beta1/zz_generated.deepcopy.go | 5 + pkg/common/types.go | 2 + .../runtimecomponent_controller.go | 2 + pkg/utils/reconciler.go | 12 ++ pkg/utils/service_binding_reconciler.go | 140 +++++++++++++++++- 7 files changed, 197 insertions(+), 29 deletions(-) diff --git a/deploy/crds/app.stacks_runtimecomponents_crd.yaml b/deploy/crds/app.stacks_runtimecomponents_crd.yaml index 4f003e63f..88191f9fb 100644 --- a/deploy/crds/app.stacks_runtimecomponents_crd.yaml +++ b/deploy/crds/app.stacks_runtimecomponents_crd.yaml @@ -94,6 +94,8 @@ spec: properties: autoDetect: type: boolean + embedded: + type: object resourceRef: type: string type: object @@ -476,8 +478,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -499,8 +501,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -570,8 +572,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -593,8 +595,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -662,8 +664,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -702,8 +704,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -822,8 +824,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -862,8 +864,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -1092,8 +1094,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -1132,8 +1134,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -1312,8 +1314,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. scheme: @@ -1350,8 +1352,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. required: @@ -1568,8 +1570,8 @@ spec: type: string targetPort: anyOf: - - type: string - type: integer + - type: string description: Name or number of the target port of the endpoint. Mutually exclusive with port. tlsConfig: @@ -1775,8 +1777,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. scheme: @@ -1813,8 +1815,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. required: @@ -2205,8 +2207,8 @@ spec: type: string targetPort: anyOf: - - type: string - type: integer + - type: string description: 'Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a @@ -2530,8 +2532,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2553,8 +2555,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2624,8 +2626,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2647,8 +2649,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2716,8 +2718,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2756,8 +2758,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2876,8 +2878,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2916,8 +2918,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -3146,8 +3148,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -3186,8 +3188,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. diff --git a/pkg/apis/appstacks/v1beta1/runtimecomponent_types.go b/pkg/apis/appstacks/v1beta1/runtimecomponent_types.go index fc6c7db44..6ce8238b9 100644 --- a/pkg/apis/appstacks/v1beta1/runtimecomponent_types.go +++ b/pkg/apis/appstacks/v1beta1/runtimecomponent_types.go @@ -8,6 +8,7 @@ import ( routev1 "github.com/openshift/api/route/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. @@ -148,8 +149,9 @@ type ServiceBindingAuth struct { // RuntimeComponentBindings represents service binding related parameters type RuntimeComponentBindings struct { - AutoDetect *bool `json:"autoDetect,omitempty"` - ResourceRef string `json:"resourceRef,omitempty"` + AutoDetect *bool `json:"autoDetect,omitempty"` + ResourceRef string `json:"resourceRef,omitempty"` + Embedded *runtime.RawExtension `json:"embedded,omitempty"` } // RuntimeComponentStatus defines the observed state of RuntimeComponent @@ -623,6 +625,11 @@ func (r *RuntimeComponentBindings) GetResourceRef() string { return r.ResourceRef } +// GetEmbedded returns the embedded underlying Service Binding resource +func (r *RuntimeComponentBindings) GetEmbedded() *runtime.RawExtension { + return r.Embedded +} + // Initialize the RuntimeComponent instance func (cr *RuntimeComponent) Initialize() { if cr.Spec.PullPolicy == nil { diff --git a/pkg/apis/appstacks/v1beta1/zz_generated.deepcopy.go b/pkg/apis/appstacks/v1beta1/zz_generated.deepcopy.go index f26be7193..7abf453c0 100644 --- a/pkg/apis/appstacks/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/appstacks/v1beta1/zz_generated.deepcopy.go @@ -128,6 +128,11 @@ func (in *RuntimeComponentBindings) DeepCopyInto(out *RuntimeComponentBindings) *out = new(bool) **out = **in } + if in.Embedded != nil { + in, out := &in.Embedded, &out.Embedded + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/common/types.go b/pkg/common/types.go index 13bbc274f..cae12d190 100644 --- a/pkg/common/types.go +++ b/pkg/common/types.go @@ -6,6 +6,7 @@ import ( routev1 "github.com/openshift/api/route/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) // StatusConditionType ... @@ -129,6 +130,7 @@ type ServiceBindingAuth interface { type BaseComponentBindings interface { GetAutoDetect() *bool GetResourceRef() string + GetEmbedded() *runtime.RawExtension } // ServiceBindingCategory ... diff --git a/pkg/controller/runtimecomponent/runtimecomponent_controller.go b/pkg/controller/runtimecomponent/runtimecomponent_controller.go index d5134f67f..6dc041976 100644 --- a/pkg/controller/runtimecomponent/runtimecomponent_controller.go +++ b/pkg/controller/runtimecomponent/runtimecomponent_controller.go @@ -119,6 +119,8 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { return err } + reconciler.SetController(c) + watchNamespaces, err := appstacksutils.GetWatchNamespaces() if err != nil { log.Error(err, "Failed to get watch namespace") diff --git a/pkg/utils/reconciler.go b/pkg/utils/reconciler.go index bd44238cd..29b6a7142 100644 --- a/pkg/utils/reconciler.go +++ b/pkg/utils/reconciler.go @@ -26,6 +26,7 @@ import ( applicationsv1beta1 "sigs.k8s.io/application/pkg/apis/app/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" @@ -38,6 +39,7 @@ type ReconcilerBase struct { recorder record.EventRecorder restConfig *rest.Config discovery discovery.DiscoveryInterface + controller controller.Controller } //NewReconcilerBase creates a new ReconcilerBase @@ -50,6 +52,16 @@ func NewReconcilerBase(client client.Client, scheme *runtime.Scheme, restConfig } } +// GetController returns controller +func (r *ReconcilerBase) GetController() controller.Controller { + return r.controller +} + +// SetController sets controller +func (r *ReconcilerBase) SetController(c controller.Controller) { + r.controller = c +} + // GetClient returns client func (r *ReconcilerBase) GetClient() client.Client { return r.client diff --git a/pkg/utils/service_binding_reconciler.go b/pkg/utils/service_binding_reconciler.go index 951ee6c45..aea45abe3 100644 --- a/pkg/utils/service_binding_reconciler.go +++ b/pkg/utils/service_binding_reconciler.go @@ -2,6 +2,7 @@ package utils import ( "context" + "encoding/json" "fmt" "reflect" "sort" @@ -18,7 +19,18 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// String constants +const ( + APIVersion = "apiVersion" + Kind = "kind" + Metadata = "metadata" + Spec = "spec" ) // SyncSecretAcrossNamespace syncs up the secret data across a namespace @@ -257,7 +269,12 @@ func (r *ReconcilerBase) ReconcileConsumes(ba common.BaseComponent) (reconcile.R // ReconcileBindings goes through the reconcile logic for service binding func (r *ReconcilerBase) ReconcileBindings(ba common.BaseComponent) (reconcile.Result, error) { - + if ba.GetBindings() != nil && ba.GetBindings().GetEmbedded() != nil { + if res, err := r.reconcileEmbedded(ba); isRequeue(res, err) { + return res, err + } + return r.done(ba) + } if res, err := r.reconcileExternals(ba); isRequeue(res, err) { return res, err } @@ -395,3 +412,124 @@ func (r *ReconcilerBase) IsSeriveBindingSupported() bool { } return false } + +func (r *ReconcilerBase) reconcileEmbedded(ba common.BaseComponent) (reconcile.Result, error) { + mObj := ba.(metav1.Object) + object, err := r.toJSONFromRaw(ba.GetBindings().GetEmbedded()) + if err != nil { + err = errors.Wrapf(err, "failed: unable marshalling to JSON") + return r.requeueError(ba, err) + } + + embedded := &unstructured.Unstructured{} + embedded.SetUnstructuredContent(object) + err = r.updateEmbeddedObject(object, embedded, ba) + if err != nil { + err = errors.Wrapf(err, "failed: cannot add required fields to the embedded Service Binding") + return r.requeueError(ba, err) + } + + apiVersion, kind := embedded.GetAPIVersion(), embedded.GetKind() + ok, err := r.IsGroupVersionSupported(apiVersion, kind) + if !ok { + err = errors.Wrapf(err, "failed: embedded Service Binding CRD with GroupVersion %q and Kind %q is not supported on the cluster", apiVersion, kind) + return r.requeueError(ba, err) + } + + err = r.createOrUpdateEmbedded(embedded, ba) + if err != nil { + return r.requeueError(ba, errors.Wrapf(err, "failed: cannot create or update embedded Service Binding resource %q in namespace %q", mObj.GetName(), mObj.GetNamespace())) + } + + return r.done(ba) +} + +func (r *ReconcilerBase) createOrUpdateEmbedded(embedded *unstructured.Unstructured, ba common.BaseComponent) error { + mObj := ba.(metav1.Object) + existing := &unstructured.Unstructured{} + existing.SetAPIVersion(embedded.GetAPIVersion()) + existing.SetKind(embedded.GetKind()) + key := types.NamespacedName{Name: mObj.GetName(), Namespace: mObj.GetNamespace()} + err := r.client.Get(context.TODO(), key, existing) + if err != nil { + if kerrors.IsNotFound(err) { + err = r.client.Create(context.TODO(), embedded) + if err != nil { + return err + } + + // add watcher + err = r.controller.Watch(&source.Kind{Type: existing}, &handler.EnqueueRequestForOwner{ + IsController: true, + OwnerType: ba.(runtime.Object), + }) + if err != nil { + return errors.Wrap(err, "Cannot add watcher") + } + } else { + return err + } + } else { + // Update the found object and write the result back if there are any changes + if !reflect.DeepEqual(embedded.Object[Spec], existing.Object[Spec]) { + existing.Object[Spec] = embedded.Object[Spec] + err = r.client.Update(context.TODO(), existing) + if err != nil { + return err + } + } + } + + return nil +} + +func (r *ReconcilerBase) toJSONFromRaw(content *runtime.RawExtension) (map[string]interface{}, error) { + var data map[string]interface{} + if err := json.Unmarshal(content.Raw, &data); err != nil { + return nil, err + } + return data, nil +} + +func (r *ReconcilerBase) updateEmbeddedObject(object map[string]interface{}, embedded *unstructured.Unstructured, ba common.BaseComponent) error { + mObj := ba.(metav1.Object) + + if _, ok := object[Spec]; !ok { + return errors.New("failed: embedded Service Binding is missing a 'spec' section") + } + + if _, ok := object[Metadata]; ok { + return errors.New("failed: embedded Service Binding must not have a 'metadata' section") + } + embedded.SetName(mObj.GetName()) + embedded.SetNamespace(mObj.GetNamespace()) + embedded.SetLabels(mObj.GetLabels()) + embedded.SetAnnotations(mObj.GetAnnotations()) + + if err := controllerutil.SetControllerReference(mObj, embedded, r.scheme); err != nil { + return errors.Wrap(err, "SetControllerReference returned error") + } + + apiVersion, okAPIVersion := object[APIVersion] + kind, okKind := object[Kind] + + // If either API Version or Kind is not set, try getting it from the Operator ConfigMap + var defaultGVK schema.GroupVersionKind + if !okAPIVersion || !okKind { + cmGVK := getServiceBindingGVK() + if len(cmGVK) == 0 { + return errors.New("failed: embedded Service Binding does not specify 'apiVersion' or 'kind' and there is no default GVK defined in the operator ConfigMap") + } + defaultGVK = cmGVK[0] + } + if !okAPIVersion { + apiVersion = defaultGVK.GroupVersion().String() + } + if !okKind { + kind = defaultGVK.Kind + } + + embedded.SetKind(kind.(string)) + embedded.SetAPIVersion(apiVersion.(string)) + return nil +} From 555f51db25424fa88f59ec3e27edc63bcdca2e27 Mon Sep 17 00:00:00 2001 From: Navid Shakibapour Date: Tue, 12 May 2020 18:57:51 -0400 Subject: [PATCH 2/8] Adding support for embedded ServiceBinding --- .../releases/daily/runtime-component-crd.yaml | 54 ++++++----- go.mod | 1 - .../runtimecomponent_controller.go | 2 +- pkg/utils/reconciler.go | 2 +- pkg/utils/service_binding_reconciler.go | 95 +++++++++++++++---- 5 files changed, 105 insertions(+), 49 deletions(-) diff --git a/deploy/releases/daily/runtime-component-crd.yaml b/deploy/releases/daily/runtime-component-crd.yaml index 4f003e63f..88191f9fb 100644 --- a/deploy/releases/daily/runtime-component-crd.yaml +++ b/deploy/releases/daily/runtime-component-crd.yaml @@ -94,6 +94,8 @@ spec: properties: autoDetect: type: boolean + embedded: + type: object resourceRef: type: string type: object @@ -476,8 +478,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -499,8 +501,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -570,8 +572,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -593,8 +595,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -662,8 +664,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -702,8 +704,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -822,8 +824,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -862,8 +864,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -1092,8 +1094,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -1132,8 +1134,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -1312,8 +1314,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. scheme: @@ -1350,8 +1352,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. required: @@ -1568,8 +1570,8 @@ spec: type: string targetPort: anyOf: - - type: string - type: integer + - type: string description: Name or number of the target port of the endpoint. Mutually exclusive with port. tlsConfig: @@ -1775,8 +1777,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. scheme: @@ -1813,8 +1815,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. required: @@ -2205,8 +2207,8 @@ spec: type: string targetPort: anyOf: - - type: string - type: integer + - type: string description: 'Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a @@ -2530,8 +2532,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2553,8 +2555,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2624,8 +2626,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2647,8 +2649,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2716,8 +2718,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2756,8 +2758,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2876,8 +2878,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -2916,8 +2918,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -3146,8 +3148,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. @@ -3186,8 +3188,8 @@ spec: type: string port: anyOf: - - type: string - type: integer + - type: string description: Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. diff --git a/go.mod b/go.mod index 2b5d25279..c7db3a468 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/pkg/errors v0.8.1 github.com/spf13/pflag v1.0.5 k8s.io/api v0.17.2 - k8s.io/apiextensions-apiserver v0.17.1 k8s.io/apimachinery v0.17.2 k8s.io/client-go v12.0.0+incompatible k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a diff --git a/pkg/controller/runtimecomponent/runtimecomponent_controller.go b/pkg/controller/runtimecomponent/runtimecomponent_controller.go index 6dc041976..a00ffc80d 100644 --- a/pkg/controller/runtimecomponent/runtimecomponent_controller.go +++ b/pkg/controller/runtimecomponent/runtimecomponent_controller.go @@ -449,7 +449,7 @@ func (r *ReconcileRuntimeComponent) Reconcile(request reconcile.Request) (reconc return result, nil } - if r.IsSeriveBindingSupported() { + if r.IsServiceBindingSupported() { result, err = r.ReconcileBindings(instance) if err != nil || result != (reconcile.Result{}) { return result, err diff --git a/pkg/utils/reconciler.go b/pkg/utils/reconciler.go index 29b6a7142..f2987972f 100644 --- a/pkg/utils/reconciler.go +++ b/pkg/utils/reconciler.go @@ -110,7 +110,7 @@ func (r *ReconcilerBase) CreateOrUpdate(obj metav1.Object, owner metav1.Object, var gvk schema.GroupVersionKind gvk, err = apiutil.GVKForObject(runtimeObj, r.scheme) if err == nil { - log.Info("Reconciled", "Kind", gvk.Kind, "Name", obj.GetName(), "Status", result) + log.Info("Reconciled", "Kind", gvk.Kind, "Namespace", obj.GetNamespace(), "Name", obj.GetName(), "Status", result) } return err diff --git a/pkg/utils/service_binding_reconciler.go b/pkg/utils/service_binding_reconciler.go index aea45abe3..b88c6428b 100644 --- a/pkg/utils/service_binding_reconciler.go +++ b/pkg/utils/service_binding_reconciler.go @@ -275,13 +275,16 @@ func (r *ReconcilerBase) ReconcileBindings(ba common.BaseComponent) (reconcile.R } return r.done(ba) } + if err := r.cleanUpEmbeddedBindings(ba); err != nil { + return r.requeueError(ba, err) + } if res, err := r.reconcileExternals(ba); isRequeue(res, err) { return res, err } return r.done(ba) } -func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (reconcile.Result, error) { +func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (retRes reconcile.Result, retErr error) { mObj := ba.(metav1.Object) var resolvedBindings []string @@ -300,14 +303,14 @@ func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (reconcile. bindingName := mObj.GetName() key := types.NamespacedName{Name: bindingName, Namespace: mObj.GetNamespace()} - for _, gvk := range getServiceBindingGVK() { + for _, gvk := range r.getServiceBindingGVK() { // Using a unstructured object to find ServiceBinding CR since GVK might change bindingObj := &unstructured.Unstructured{} bindingObj.SetGroupVersionKind(gvk) err := r.client.Get(context.Background(), key, bindingObj) if err != nil { if !kerrors.IsNotFound(err) { - log.Error(errors.Wrapf(err, "failed to find a service binding resource during auto-detect for GVK %q", gvk), "failed to get ServiceBinding CR") + log.Error(errors.Wrapf(err, "failed to find a service binding resource during auto-detect for GVK %q", gvk), "failed to get Service Binding CR") } continue } @@ -324,15 +327,13 @@ func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (reconcile. } } - if !equals(resolvedBindings, ba.GetStatus().GetResolvedBindings()) { - sort.Strings(resolvedBindings) - ba.GetStatus().SetResolvedBindings(resolvedBindings) - if err := r.UpdateStatus(ba.(runtime.Object)); err != nil { - return r.requeueError(ba, errors.Wrapf(err, "unable to update status with resolved service binding information")) + retRes, retErr = r.done(ba) + defer func() { + if res, err := r.updateBindingStatus(resolvedBindings, ba); isRequeue(res, err) { + retRes, retErr = res, err } - } - - return r.done(ba) + }() + return } //GetResolvedBindingSecret returns the secret referenced in .status.resolvedBindings @@ -385,7 +386,7 @@ func equals(sl1, sl2 []string) bool { return true } -func getServiceBindingGVK() []schema.GroupVersionKind { +func getOpConfigServiceBindingGVKs() []schema.GroupVersionKind { gvkStringList := strings.Split(common.Config[common.OpConfigSvcBindingGVKs], ",") for i := range gvkStringList { gvkStringList[i] = strings.TrimSpace(gvkStringList[i]) @@ -403,18 +404,44 @@ func getServiceBindingGVK() []schema.GroupVersionKind { return gvkList } -// IsSeriveBindingSupported returns true if at least one GVK in the operator ConfigMap's serviceBinding.groupVersionKinds is installed -func (r *ReconcilerBase) IsSeriveBindingSupported() bool { - for _, gvk := range getServiceBindingGVK() { +func (r *ReconcilerBase) getServiceBindingGVK() (gvkList []schema.GroupVersionKind) { + for _, gvk := range getOpConfigServiceBindingGVKs() { if ok, _ := r.IsGroupVersionSupported(gvk.GroupVersion().String(), gvk.Kind); ok { - return true + gvkList = append(gvkList, gvk) } } - return false + return gvkList } -func (r *ReconcilerBase) reconcileEmbedded(ba common.BaseComponent) (reconcile.Result, error) { +// IsServiceBindingSupported returns true if at least one GVK in the operator ConfigMap's serviceBinding.groupVersionKinds is installed +func (r *ReconcilerBase) IsServiceBindingSupported() bool { + return len(r.getServiceBindingGVK()) > 0 +} + +// cleanUpEmbeddedBindings deletes Service Binding resources owned by current CR based having the same name as CR +func (r *ReconcilerBase) cleanUpEmbeddedBindings(ba common.BaseComponent) error { mObj := ba.(metav1.Object) + for _, gvk := range r.getServiceBindingGVK() { + bindingObj := &unstructured.Unstructured{} + bindingObj.SetGroupVersionKind(gvk) + key := types.NamespacedName{Name: mObj.GetName(), Namespace: mObj.GetNamespace()} + err := r.client.Get(context.Background(), key, bindingObj) + if err == nil && metav1.IsControlledBy(bindingObj, mObj) { + err = r.client.Delete(context.Background(), bindingObj) + if client.IgnoreNotFound(err) != nil { + return err + } + } else if client.IgnoreNotFound(err) != nil { + return err + } + } + return nil +} + +func (r *ReconcilerBase) reconcileEmbedded(ba common.BaseComponent) (retRes reconcile.Result, retErr error) { + mObj := ba.(metav1.Object) + var resolvedBindings []string + object, err := r.toJSONFromRaw(ba.GetBindings().GetEmbedded()) if err != nil { err = errors.Wrapf(err, "failed: unable marshalling to JSON") @@ -425,7 +452,7 @@ func (r *ReconcilerBase) reconcileEmbedded(ba common.BaseComponent) (reconcile.R embedded.SetUnstructuredContent(object) err = r.updateEmbeddedObject(object, embedded, ba) if err != nil { - err = errors.Wrapf(err, "failed: cannot add required fields to the embedded Service Binding") + err = errors.Wrapf(err, "failed: cannot add missing information to the embedded Service Binding") return r.requeueError(ba, err) } @@ -441,6 +468,34 @@ func (r *ReconcilerBase) reconcileEmbedded(ba common.BaseComponent) (reconcile.R return r.requeueError(ba, errors.Wrapf(err, "failed: cannot create or update embedded Service Binding resource %q in namespace %q", mObj.GetName(), mObj.GetNamespace())) } + // Get binding secret and add it to status field. If binding hasn't been successful, secret won't be created and it keeps trying + key := types.NamespacedName{Name: mObj.GetName(), Namespace: mObj.GetNamespace()} + bindingSecret := &corev1.Secret{} + err = r.GetClient().Get(context.TODO(), key, bindingSecret) + if err == nil { + resolvedBindings = append(resolvedBindings, mObj.GetName()) + } else { + err = errors.Wrapf(err, "service binding dependency not satisfied: unable to find service binding secret for embedded binding %q in namespace %q", mObj.GetName(), mObj.GetNamespace()) + return r.requeueError(ba, err) + } + + retRes, retErr = r.done(ba) + defer func() { + if res, err := r.updateBindingStatus(resolvedBindings, ba); isRequeue(res, err) { + retRes, retErr = res, err + } + }() + return +} + +func (r *ReconcilerBase) updateBindingStatus(bindings []string, ba common.BaseComponent) (reconcile.Result, error) { + if !equals(bindings, ba.GetStatus().GetResolvedBindings()) { + sort.Strings(bindings) + ba.GetStatus().SetResolvedBindings(bindings) + if err := r.UpdateStatus(ba.(runtime.Object)); err != nil { + return r.requeueError(ba, errors.Wrapf(err, "unable to update status with resolved service binding information")) + } + } return r.done(ba) } @@ -516,7 +571,7 @@ func (r *ReconcilerBase) updateEmbeddedObject(object map[string]interface{}, emb // If either API Version or Kind is not set, try getting it from the Operator ConfigMap var defaultGVK schema.GroupVersionKind if !okAPIVersion || !okKind { - cmGVK := getServiceBindingGVK() + cmGVK := getOpConfigServiceBindingGVKs() if len(cmGVK) == 0 { return errors.New("failed: embedded Service Binding does not specify 'apiVersion' or 'kind' and there is no default GVK defined in the operator ConfigMap") } From 2d79ab6baa46453093cb2b39664e539074fe3acf Mon Sep 17 00:00:00 2001 From: Navid Shakibapour Date: Wed, 13 May 2020 10:45:57 -0400 Subject: [PATCH 3/8] Adding a logging message for createOrUpdate for embedded bindings --- pkg/utils/service_binding_reconciler.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/utils/service_binding_reconciler.go b/pkg/utils/service_binding_reconciler.go index b88c6428b..ca58b5f9b 100644 --- a/pkg/utils/service_binding_reconciler.go +++ b/pkg/utils/service_binding_reconciler.go @@ -501,6 +501,7 @@ func (r *ReconcilerBase) updateBindingStatus(bindings []string, ba common.BaseCo func (r *ReconcilerBase) createOrUpdateEmbedded(embedded *unstructured.Unstructured, ba common.BaseComponent) error { mObj := ba.(metav1.Object) + result := controllerutil.OperationResultNone existing := &unstructured.Unstructured{} existing.SetAPIVersion(embedded.GetAPIVersion()) existing.SetKind(embedded.GetKind()) @@ -512,7 +513,7 @@ func (r *ReconcilerBase) createOrUpdateEmbedded(embedded *unstructured.Unstructu if err != nil { return err } - + result = controllerutil.OperationResultCreated // add watcher err = r.controller.Watch(&source.Kind{Type: existing}, &handler.EnqueueRequestForOwner{ IsController: true, @@ -532,9 +533,11 @@ func (r *ReconcilerBase) createOrUpdateEmbedded(embedded *unstructured.Unstructu if err != nil { return err } + result = controllerutil.OperationResultUpdated } } + log.Info("Reconciled", "Kind", embedded.GetKind(), "Namespace", mObj.GetNamespace(), "Name", mObj.GetName(), "Status", result) return nil } From 76176f333d6fcd4c1707186606f2d1ccdab42d1e Mon Sep 17 00:00:00 2001 From: Navid Shakibapour Date: Wed, 13 May 2020 12:36:11 -0400 Subject: [PATCH 4/8] Simplified enqueue_with_matcher.go --- .../runtimecomponent/enqueue_with_matcher.go | 111 ++++++++++-------- .../runtimecomponent_controller.go | 9 +- 2 files changed, 68 insertions(+), 52 deletions(-) diff --git a/pkg/controller/runtimecomponent/enqueue_with_matcher.go b/pkg/controller/runtimecomponent/enqueue_with_matcher.go index 37b76a534..183eaac47 100644 --- a/pkg/controller/runtimecomponent/enqueue_with_matcher.go +++ b/pkg/controller/runtimecomponent/enqueue_with_matcher.go @@ -28,7 +28,7 @@ const ( // the modified resource type EnqueueRequestsForCustomIndexField struct { handler.Funcs - Matcher func(metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) + Matcher CustomMatcher } // Update implements EventHandler @@ -48,7 +48,7 @@ func (e *EnqueueRequestsForCustomIndexField) Generic(evt event.GenericEvent, q w // handle common implementation to enqueue reconcile Requests for applications func (e *EnqueueRequestsForCustomIndexField) handle(evtMeta metav1.Object, evtObj runtime.Object, q workqueue.RateLimitingInterface) { - apps, _ := e.Matcher(evtMeta) + apps, _ := e.Matcher.Match(evtMeta) for _, app := range apps { q.Add(reconcile.Request{ NamespacedName: types.NamespacedName{ @@ -58,64 +58,75 @@ func (e *EnqueueRequestsForCustomIndexField) handle(evtMeta metav1.Object, evtOb } } -// CreateImageStreamMatcher return a func that matches all applications using the input ImageStreamTag -func CreateImageStreamMatcher(clnt client.Client, watchNamespaces []string) func(metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) { - matcher := func(imageStreamTag metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) { - apps := []appstacksv1beta1.RuntimeComponent{} - var namespaces []string - if appstacksutils.IsClusterWide(watchNamespaces) { - nsList := &corev1.NamespaceList{} - if err := clnt.List(context.Background(), nsList, client.InNamespace("")); err != nil { - return nil, err - } - for _, ns := range nsList.Items { - namespaces = append(namespaces, ns.Name) - } - } else { - namespaces = watchNamespaces - } - for _, ns := range namespaces { - appList := &appstacksv1beta1.RuntimeComponentList{} - err := clnt.List(context.Background(), - appList, - client.InNamespace(ns), - client.MatchingFields{indexFieldImageStreamName: imageStreamTag.GetNamespace() + "/" + imageStreamTag.GetName()}) - if err != nil { - return nil, err - } - apps = append(apps, appList.Items...) - } - return apps, nil - } - return matcher +// CustomMatcher is an interface for matching apps that satisfy a custom logic +type CustomMatcher interface { + Match(metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) } -//CreateBindingSecretMatcher return a func that matches all applications that "could" rely on the secret as a secret binding -func CreateBindingSecretMatcher(clnt client.Client) func(metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) { - matcher := func(secret metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) { - apps := []appstacksv1beta1.RuntimeComponent{} +// ImageStreamMatcher implements CustomMatcher for Image Streams +type ImageStreamMatcher struct { + klient client.Client + watchNamespaces []string +} - // Adding apps which have this secret defined in the spec.bindings.resourceRef +// Match returns all applications using the input ImageStreamTag +func (i *ImageStreamMatcher) Match(imageStreamTag metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) { + apps := []appstacksv1beta1.RuntimeComponent{} + var namespaces []string + if appstacksutils.IsClusterWide(i.watchNamespaces) { + nsList := &corev1.NamespaceList{} + if err := i.klient.List(context.Background(), nsList, client.InNamespace("")); err != nil { + return nil, err + } + for _, ns := range nsList.Items { + namespaces = append(namespaces, ns.Name) + } + } else { + namespaces = watchNamespaces + } + for _, ns := range namespaces { appList := &appstacksv1beta1.RuntimeComponentList{} - err := clnt.List(context.Background(), + err := i.klient.List(context.Background(), appList, - client.InNamespace(secret.GetNamespace()), - client.MatchingFields{indexFieldBindingsResourceRef: secret.GetName()}) + client.InNamespace(ns), + client.MatchingFields{indexFieldImageStreamName: imageStreamTag.GetNamespace() + "/" + imageStreamTag.GetName()}) if err != nil { return nil, err } apps = append(apps, appList.Items...) + } + return apps, nil +} - // If we are able to find an app with the secret name, add the app. This is to cover the autoDetect scenario - app := &appstacksv1beta1.RuntimeComponent{} - err = clnt.Get(context.Background(), types.NamespacedName{Name: secret.GetName(), Namespace: secret.GetNamespace()}, app) - if err != nil { - if !kerrors.IsNotFound(err) { - return nil, err - } +// BindingSecretMatcher implements CustomMatcher for Binding Secrets +type BindingSecretMatcher struct { + klient client.Client +} + +// Match returns all applications that "could" rely on the secret as a secret binding by finding apps that have +// resourceRef matching the secret name OR app name matching the secret name +func (b *BindingSecretMatcher) Match(secret metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) { + apps := []appstacksv1beta1.RuntimeComponent{} + + // Adding apps which have this secret defined in the spec.bindings.resourceRef + appList := &appstacksv1beta1.RuntimeComponentList{} + err := b.klient.List(context.Background(), + appList, + client.InNamespace(secret.GetNamespace()), + client.MatchingFields{indexFieldBindingsResourceRef: secret.GetName()}) + if err != nil { + return nil, err + } + apps = append(apps, appList.Items...) + + // If we are able to find an app with the secret name, add the app. This is to cover the autoDetect scenario + app := &appstacksv1beta1.RuntimeComponent{} + err = b.klient.Get(context.Background(), types.NamespacedName{Name: secret.GetName(), Namespace: secret.GetNamespace()}, app) + if err != nil { + if !kerrors.IsNotFound(err) { + return nil, err } - apps = append(apps, *app) - return apps, nil } - return matcher + apps = append(apps, *app) + return apps, nil } diff --git a/pkg/controller/runtimecomponent/runtimecomponent_controller.go b/pkg/controller/runtimecomponent/runtimecomponent_controller.go index a00ffc80d..6359c355a 100644 --- a/pkg/controller/runtimecomponent/runtimecomponent_controller.go +++ b/pkg/controller/runtimecomponent/runtimecomponent_controller.go @@ -242,7 +242,9 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { err = c.Watch( &source.Kind{Type: &corev1.Secret{}}, &EnqueueRequestsForCustomIndexField{ - Matcher: CreateBindingSecretMatcher(mgr.GetClient()), + Matcher: &BindingSecretMatcher{ + klient: mgr.GetClient(), + }, }) if err != nil { return err @@ -253,7 +255,10 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { c.Watch( &source.Kind{Type: &imagev1.ImageStream{}}, &EnqueueRequestsForCustomIndexField{ - Matcher: CreateImageStreamMatcher(mgr.GetClient(), watchNamespaces), + Matcher: &ImageStreamMatcher{ + klient: mgr.GetClient(), + watchNamespaces: watchNamespaces, + }, }) } From 5454e9e798cfb0c67e235062644533c087b48528 Mon Sep 17 00:00:00 2001 From: Navid Shakibapour Date: Wed, 13 May 2020 13:04:52 -0400 Subject: [PATCH 5/8] Fixing empty reconcile loop issue --- .../runtimecomponent/enqueue_with_matcher.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/controller/runtimecomponent/enqueue_with_matcher.go b/pkg/controller/runtimecomponent/enqueue_with_matcher.go index 183eaac47..227e2a306 100644 --- a/pkg/controller/runtimecomponent/enqueue_with_matcher.go +++ b/pkg/controller/runtimecomponent/enqueue_with_matcher.go @@ -95,6 +95,7 @@ func (i *ImageStreamMatcher) Match(imageStreamTag metav1.Object) ([]appstacksv1b } apps = append(apps, appList.Items...) } + return apps, nil } @@ -122,11 +123,10 @@ func (b *BindingSecretMatcher) Match(secret metav1.Object) ([]appstacksv1beta1.R // If we are able to find an app with the secret name, add the app. This is to cover the autoDetect scenario app := &appstacksv1beta1.RuntimeComponent{} err = b.klient.Get(context.Background(), types.NamespacedName{Name: secret.GetName(), Namespace: secret.GetNamespace()}, app) - if err != nil { - if !kerrors.IsNotFound(err) { - return nil, err - } + if err == nil { + apps = append(apps, *app) + } else if !kerrors.IsNotFound(err) { + return nil, err } - apps = append(apps, *app) return apps, nil } From e648d5aaddf6559f1245a3f0c0b324f85263baf3 Mon Sep 17 00:00:00 2001 From: Navid Shakibapour Date: Fri, 15 May 2020 12:07:06 -0400 Subject: [PATCH 6/8] Adding '-binding' suffix to autoDetect default name and embedded name --- .../runtimecomponent/enqueue_with_matcher.go | 20 +++++++---- pkg/utils/service_binding_reconciler.go | 33 ++++++++++--------- pkg/utils/utils.go | 1 + 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/pkg/controller/runtimecomponent/enqueue_with_matcher.go b/pkg/controller/runtimecomponent/enqueue_with_matcher.go index 227e2a306..e92500b73 100644 --- a/pkg/controller/runtimecomponent/enqueue_with_matcher.go +++ b/pkg/controller/runtimecomponent/enqueue_with_matcher.go @@ -2,6 +2,7 @@ package runtimecomponent import ( "context" + "strings" appstacksv1beta1 "github.com/application-stacks/runtime-component-operator/pkg/apis/appstacks/v1beta1" appstacksutils "github.com/application-stacks/runtime-component-operator/pkg/utils" @@ -22,6 +23,7 @@ var _ handler.EventHandler = &EnqueueRequestsForCustomIndexField{} const ( indexFieldImageStreamName = "spec.applicationImage" indexFieldBindingsResourceRef = "spec.bindings.resourceRef" + bindingSecretSuffix = "-binding" ) // EnqueueRequestsForCustomIndexField enqueues reconcile Requests Runtime Components if the app is relying on @@ -120,13 +122,17 @@ func (b *BindingSecretMatcher) Match(secret metav1.Object) ([]appstacksv1beta1.R } apps = append(apps, appList.Items...) - // If we are able to find an app with the secret name, add the app. This is to cover the autoDetect scenario - app := &appstacksv1beta1.RuntimeComponent{} - err = b.klient.Get(context.Background(), types.NamespacedName{Name: secret.GetName(), Namespace: secret.GetNamespace()}, app) - if err == nil { - apps = append(apps, *app) - } else if !kerrors.IsNotFound(err) { - return nil, err + if strings.HasSuffix(secret.GetName(), bindingSecretSuffix) { + appName := strings.TrimSuffix(secret.GetName(), bindingSecretSuffix) + // If we are able to find an app with the secret name, add the app. This is to cover the autoDetect scenario + app := &appstacksv1beta1.RuntimeComponent{} + err = b.klient.Get(context.Background(), types.NamespacedName{Name: appName, Namespace: secret.GetNamespace()}, app) + if err == nil { + apps = append(apps, *app) + } else if !kerrors.IsNotFound(err) { + return nil, err + } } + return apps, nil } diff --git a/pkg/utils/service_binding_reconciler.go b/pkg/utils/service_binding_reconciler.go index ca58b5f9b..328ee8417 100644 --- a/pkg/utils/service_binding_reconciler.go +++ b/pkg/utils/service_binding_reconciler.go @@ -300,7 +300,7 @@ func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (retRes rec return r.requeueError(ba, err) } } else if ba.GetBindings() == nil || ba.GetBindings().GetAutoDetect() == nil || *ba.GetBindings().GetAutoDetect() { - bindingName := mObj.GetName() + bindingName := getDefaultServiceBindingName(ba) key := types.NamespacedName{Name: bindingName, Namespace: mObj.GetNamespace()} for _, gvk := range r.getServiceBindingGVK() { @@ -308,10 +308,8 @@ func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (retRes rec bindingObj := &unstructured.Unstructured{} bindingObj.SetGroupVersionKind(gvk) err := r.client.Get(context.Background(), key, bindingObj) - if err != nil { - if !kerrors.IsNotFound(err) { - log.Error(errors.Wrapf(err, "failed to find a service binding resource during auto-detect for GVK %q", gvk), "failed to get Service Binding CR") - } + if client.IgnoreNotFound(err) != nil { + log.Error(errors.Wrapf(err, "failed to find a service binding resource during auto-detect for GVK %q", gvk), "failed to get Service Binding CR") continue } @@ -320,7 +318,7 @@ func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (retRes rec if err == nil { resolvedBindings = append(resolvedBindings, bindingName) break - } else if err != nil && kerrors.IsNotFound(err) { + } else { err = errors.Wrapf(err, "service binding dependency not satisfied: unable to find service binding secret for external binding %q in namespace %q", bindingName, mObj.GetNamespace()) return r.requeueError(ba, err) } @@ -424,7 +422,7 @@ func (r *ReconcilerBase) cleanUpEmbeddedBindings(ba common.BaseComponent) error for _, gvk := range r.getServiceBindingGVK() { bindingObj := &unstructured.Unstructured{} bindingObj.SetGroupVersionKind(gvk) - key := types.NamespacedName{Name: mObj.GetName(), Namespace: mObj.GetNamespace()} + key := types.NamespacedName{Name: getDefaultServiceBindingName(ba), Namespace: mObj.GetNamespace()} err := r.client.Get(context.Background(), key, bindingObj) if err == nil && metav1.IsControlledBy(bindingObj, mObj) { err = r.client.Delete(context.Background(), bindingObj) @@ -438,8 +436,8 @@ func (r *ReconcilerBase) cleanUpEmbeddedBindings(ba common.BaseComponent) error return nil } +// reconcileEmbedded reconciles embedded blob in bindings.embedded to create or update Service Binding resource func (r *ReconcilerBase) reconcileEmbedded(ba common.BaseComponent) (retRes reconcile.Result, retErr error) { - mObj := ba.(metav1.Object) var resolvedBindings []string object, err := r.toJSONFromRaw(ba.GetBindings().GetEmbedded()) @@ -465,17 +463,17 @@ func (r *ReconcilerBase) reconcileEmbedded(ba common.BaseComponent) (retRes reco err = r.createOrUpdateEmbedded(embedded, ba) if err != nil { - return r.requeueError(ba, errors.Wrapf(err, "failed: cannot create or update embedded Service Binding resource %q in namespace %q", mObj.GetName(), mObj.GetNamespace())) + return r.requeueError(ba, errors.Wrapf(err, "failed: cannot create or update embedded Service Binding resource %q in namespace %q", embedded.GetName(), embedded.GetNamespace())) } // Get binding secret and add it to status field. If binding hasn't been successful, secret won't be created and it keeps trying - key := types.NamespacedName{Name: mObj.GetName(), Namespace: mObj.GetNamespace()} + key := types.NamespacedName{Name: embedded.GetName(), Namespace: embedded.GetNamespace()} bindingSecret := &corev1.Secret{} err = r.GetClient().Get(context.TODO(), key, bindingSecret) if err == nil { - resolvedBindings = append(resolvedBindings, mObj.GetName()) + resolvedBindings = append(resolvedBindings, embedded.GetName()) } else { - err = errors.Wrapf(err, "service binding dependency not satisfied: unable to find service binding secret for embedded binding %q in namespace %q", mObj.GetName(), mObj.GetNamespace()) + err = errors.Wrapf(err, "service binding dependency not satisfied: unable to find service binding secret for embedded binding %q in namespace %q", embedded.GetName(), embedded.GetNamespace()) return r.requeueError(ba, err) } @@ -500,12 +498,11 @@ func (r *ReconcilerBase) updateBindingStatus(bindings []string, ba common.BaseCo } func (r *ReconcilerBase) createOrUpdateEmbedded(embedded *unstructured.Unstructured, ba common.BaseComponent) error { - mObj := ba.(metav1.Object) result := controllerutil.OperationResultNone existing := &unstructured.Unstructured{} existing.SetAPIVersion(embedded.GetAPIVersion()) existing.SetKind(embedded.GetKind()) - key := types.NamespacedName{Name: mObj.GetName(), Namespace: mObj.GetNamespace()} + key := types.NamespacedName{Name: embedded.GetName(), Namespace: embedded.GetNamespace()} err := r.client.Get(context.TODO(), key, existing) if err != nil { if kerrors.IsNotFound(err) { @@ -537,7 +534,7 @@ func (r *ReconcilerBase) createOrUpdateEmbedded(embedded *unstructured.Unstructu } } - log.Info("Reconciled", "Kind", embedded.GetKind(), "Namespace", mObj.GetNamespace(), "Name", mObj.GetName(), "Status", result) + log.Info("Reconciled", "Kind", embedded.GetKind(), "Namespace", embedded.GetNamespace(), "Name", embedded.GetName(), "Status", result) return nil } @@ -559,7 +556,7 @@ func (r *ReconcilerBase) updateEmbeddedObject(object map[string]interface{}, emb if _, ok := object[Metadata]; ok { return errors.New("failed: embedded Service Binding must not have a 'metadata' section") } - embedded.SetName(mObj.GetName()) + embedded.SetName(getDefaultServiceBindingName(ba)) embedded.SetNamespace(mObj.GetNamespace()) embedded.SetLabels(mObj.GetLabels()) embedded.SetAnnotations(mObj.GetAnnotations()) @@ -591,3 +588,7 @@ func (r *ReconcilerBase) updateEmbeddedObject(object map[string]interface{}, emb embedded.SetAPIVersion(apiVersion.(string)) return nil } + +func getDefaultServiceBindingName(ba common.BaseComponent) string { + return (ba.(metav1.Object)).GetName() + "-bindings" +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f1938e182..a8d50e536 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) + // CustomizeDeployment ... func CustomizeDeployment(deploy *appsv1.Deployment, ba common.BaseComponent) { obj := ba.(metav1.Object) From 5a782ab92523ebf30e4e178cbe66925f3ab61f00 Mon Sep 17 00:00:00 2001 From: Navid Shakibapour Date: Fri, 15 May 2020 18:31:15 -0400 Subject: [PATCH 7/8] Fixing an issue with default binding name --- .../runtimecomponent/enqueue_with_matcher.go | 14 +++++++------- .../runtimecomponent_controller.go | 4 ++-- pkg/utils/service_binding_reconciler.go | 8 +++++--- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/pkg/controller/runtimecomponent/enqueue_with_matcher.go b/pkg/controller/runtimecomponent/enqueue_with_matcher.go index e92500b73..51b1bcb93 100644 --- a/pkg/controller/runtimecomponent/enqueue_with_matcher.go +++ b/pkg/controller/runtimecomponent/enqueue_with_matcher.go @@ -30,7 +30,7 @@ const ( // the modified resource type EnqueueRequestsForCustomIndexField struct { handler.Funcs - Matcher CustomMatcher + Matcher CustomMatcher } // Update implements EventHandler @@ -67,28 +67,28 @@ type CustomMatcher interface { // ImageStreamMatcher implements CustomMatcher for Image Streams type ImageStreamMatcher struct { - klient client.Client - watchNamespaces []string + Klient client.Client + WatchNamespaces []string } // Match returns all applications using the input ImageStreamTag func (i *ImageStreamMatcher) Match(imageStreamTag metav1.Object) ([]appstacksv1beta1.RuntimeComponent, error) { apps := []appstacksv1beta1.RuntimeComponent{} var namespaces []string - if appstacksutils.IsClusterWide(i.watchNamespaces) { + if appstacksutils.IsClusterWide(i.WatchNamespaces) { nsList := &corev1.NamespaceList{} - if err := i.klient.List(context.Background(), nsList, client.InNamespace("")); err != nil { + if err := i.Klient.List(context.Background(), nsList, client.InNamespace("")); err != nil { return nil, err } for _, ns := range nsList.Items { namespaces = append(namespaces, ns.Name) } } else { - namespaces = watchNamespaces + namespaces = i.WatchNamespaces } for _, ns := range namespaces { appList := &appstacksv1beta1.RuntimeComponentList{} - err := i.klient.List(context.Background(), + err := i.Klient.List(context.Background(), appList, client.InNamespace(ns), client.MatchingFields{indexFieldImageStreamName: imageStreamTag.GetNamespace() + "/" + imageStreamTag.GetName()}) diff --git a/pkg/controller/runtimecomponent/runtimecomponent_controller.go b/pkg/controller/runtimecomponent/runtimecomponent_controller.go index 6359c355a..81d00aeaf 100644 --- a/pkg/controller/runtimecomponent/runtimecomponent_controller.go +++ b/pkg/controller/runtimecomponent/runtimecomponent_controller.go @@ -256,8 +256,8 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error { &source.Kind{Type: &imagev1.ImageStream{}}, &EnqueueRequestsForCustomIndexField{ Matcher: &ImageStreamMatcher{ - klient: mgr.GetClient(), - watchNamespaces: watchNamespaces, + Klient: mgr.GetClient(), + WatchNamespaces: watchNamespaces, }, }) } diff --git a/pkg/utils/service_binding_reconciler.go b/pkg/utils/service_binding_reconciler.go index 328ee8417..180b83585 100644 --- a/pkg/utils/service_binding_reconciler.go +++ b/pkg/utils/service_binding_reconciler.go @@ -308,8 +308,10 @@ func (r *ReconcilerBase) reconcileExternals(ba common.BaseComponent) (retRes rec bindingObj := &unstructured.Unstructured{} bindingObj.SetGroupVersionKind(gvk) err := r.client.Get(context.Background(), key, bindingObj) - if client.IgnoreNotFound(err) != nil { - log.Error(errors.Wrapf(err, "failed to find a service binding resource during auto-detect for GVK %q", gvk), "failed to get Service Binding CR") + if err != nil { + if !kerrors.IsNotFound(err) { + log.Error(errors.Wrapf(err, "failed to find a service binding resource during auto-detect for GVK %q", gvk), "failed to get Service Binding CR") + } continue } @@ -590,5 +592,5 @@ func (r *ReconcilerBase) updateEmbeddedObject(object map[string]interface{}, emb } func getDefaultServiceBindingName(ba common.BaseComponent) string { - return (ba.(metav1.Object)).GetName() + "-bindings" + return (ba.(metav1.Object)).GetName() + "-binding" } From e38307ccf2db26472d1ecb896811b6c6d6f62f2c Mon Sep 17 00:00:00 2001 From: Navid Shakibapour Date: Mon, 18 May 2020 12:32:12 -0400 Subject: [PATCH 8/8] Removing extra new line --- pkg/utils/utils.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index a8d50e536..f1938e182 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -23,7 +23,6 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) - // CustomizeDeployment ... func CustomizeDeployment(deploy *appsv1.Deployment, ba common.BaseComponent) { obj := ba.(metav1.Object)