diff --git a/api/v1beta1/rabbitmqcluster_status.go b/api/v1beta1/rabbitmqcluster_status.go index f750d9301..9f87c0264 100644 --- a/api/v1beta1/rabbitmqcluster_status.go +++ b/api/v1beta1/rabbitmqcluster_status.go @@ -15,6 +15,11 @@ type RabbitmqClusterStatus struct { // Identifying information on internal resources DefaultUser *RabbitmqClusterDefaultUser `json:"defaultUser,omitempty"` + + // Binding exposes a secret containing the binding information for this + // RabbitmqCluster. It implements the service binding Provisioned Service + // duck type. See: https://k8s-service-bindings.github.io/spec/#provisioned-service + Binding *corev1.LocalObjectReference `json:"binding,omitempty"` } // Contains references to resources created with the RabbitmqCluster resource. diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 351779361..3e0fb625e 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -403,6 +403,11 @@ func (in *RabbitmqClusterStatus) DeepCopyInto(out *RabbitmqClusterStatus) { *out = new(RabbitmqClusterDefaultUser) (*in).DeepCopyInto(*out) } + if in.Binding != nil { + in, out := &in.Binding, &out.Binding + *out = new(v1.LocalObjectReference) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RabbitmqClusterStatus. diff --git a/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml b/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml index 8026d2cf8..34cabbedd 100644 --- a/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml +++ b/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml @@ -3617,6 +3617,13 @@ spec: status: description: Status presents the observed state of RabbitmqCluster properties: + binding: + description: 'Binding exposes a secret containing the binding information for this RabbitmqCluster. It implements the service binding Provisioned Service duck type. See: https://k8s-service-bindings.github.io/spec/#provisioned-service' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object clusterStatus: description: Unused. type: string diff --git a/controllers/rabbitmqcluster_controller.go b/controllers/rabbitmqcluster_controller.go index 6ff5f2eed..d1593f6c3 100644 --- a/controllers/rabbitmqcluster_controller.go +++ b/controllers/rabbitmqcluster_controller.go @@ -162,7 +162,7 @@ func (r *RabbitmqClusterReconciler) Reconcile(ctx context.Context, req ctrl.Requ } // only StatefulSetBuilder returns true - if builder.UpdateMayRequireStsRecreate() { + if builder.UpdateMayRequireStsRecreate() { if err = r.reconcilePVC(ctx, builder, rabbitmqCluster, resource); err != nil { rabbitmqCluster.Status.SetCondition(status.ReconcileSuccess, corev1.ConditionFalse, "FailedReconcilePVC", err.Error()) if statusErr := r.Status().Update(ctx, rabbitmqCluster); statusErr != nil { @@ -201,6 +201,9 @@ func (r *RabbitmqClusterReconciler) Reconcile(ctx context.Context, req ctrl.Requ if err := r.setDefaultUserStatus(ctx, rabbitmqCluster); err != nil { return ctrl.Result{}, err } + if err := r.setBinding(ctx, rabbitmqCluster); err != nil { + return ctrl.Result{}, err + } // By this point the StatefulSet may have finished deploying. Run any // post-deploy steps if so, or requeue until the deployment is finished. diff --git a/controllers/reconcile_status.go b/controllers/reconcile_status.go index ce9296cf0..6de2fe134 100644 --- a/controllers/reconcile_status.go +++ b/controllers/reconcile_status.go @@ -4,6 +4,7 @@ import ( "context" rabbitmqv1beta1 "github.com/rabbitmq/cluster-operator/api/v1beta1" "github.com/rabbitmq/cluster-operator/internal/resource" + corev1 "k8s.io/api/core/v1" "reflect" ) @@ -36,3 +37,20 @@ func (r *RabbitmqClusterReconciler) setDefaultUserStatus(ctx context.Context, rm return nil } + +// Status.Binding exposes the default user secret which contains the binding +// information for this RabbitmqCluster. +// Default user secret implements the service binding Provisioned Service +// See: https://k8s-service-bindings.github.io/spec/#provisioned-service +func (r *RabbitmqClusterReconciler) setBinding(ctx context.Context, rmq *rabbitmqv1beta1.RabbitmqCluster) error { + binding := &corev1.LocalObjectReference{ + Name: rmq.ChildResourceName(resource.DefaultUserSecretName), + } + if !reflect.DeepEqual(rmq.Status.Binding, binding) { + rmq.Status.Binding = binding + if err := r.Status().Update(ctx, rmq); err != nil { + return err + } + } + return nil +} diff --git a/controllers/reconcile_status_test.go b/controllers/reconcile_status_test.go index aa0de50a0..b2ab319f3 100644 --- a/controllers/reconcile_status_test.go +++ b/controllers/reconcile_status_test.go @@ -3,6 +3,7 @@ package controllers_test import ( rabbitmqv1beta1 "github.com/rabbitmq/cluster-operator/api/v1beta1" "github.com/rabbitmq/cluster-operator/internal/resource" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" . "github.com/onsi/ginkgo" @@ -71,5 +72,19 @@ var _ = Describe("Reconcile status", func() { Expect(serviceRef.Name).To(Equal(rmq.ChildResourceName(""))) Expect(serviceRef.Namespace).To(Equal(rmq.Namespace)) + + By("setting Status.Binding") + rmq = &rabbitmqv1beta1.RabbitmqCluster{} + binding := &corev1.LocalObjectReference{} + Eventually(func() *corev1.LocalObjectReference { + client.Get(ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, rmq) + if rmq.Status.Binding != nil { + binding = rmq.Status.Binding + return binding + } + return nil + }, 5).ShouldNot(BeNil()) + + Expect(binding.Name).To(Equal(rmq.ChildResourceName(resource.DefaultUserSecretName))) }) }) diff --git a/docs/api/rabbitmq.com.ref.asciidoc b/docs/api/rabbitmq.com.ref.asciidoc index 169705dc6..c6da39d74 100644 --- a/docs/api/rabbitmq.com.ref.asciidoc +++ b/docs/api/rabbitmq.com.ref.asciidoc @@ -328,6 +328,7 @@ Status presents the observed state of RabbitmqCluster | *`clusterStatus`* __string__ | Unused. | *`conditions`* __xref:{anchor_prefix}-github.aaakk.us.kg-rabbitmq-cluster-operator-internal-status-rabbitmqclustercondition[$$RabbitmqClusterCondition$$] array__ | Set of Conditions describing the current state of the RabbitmqCluster | *`defaultUser`* __xref:{anchor_prefix}-github.aaakk.us.kg-rabbitmq-cluster-operator-api-v1beta1-rabbitmqclusterdefaultuser[$$RabbitmqClusterDefaultUser$$]__ | Identifying information on internal resources +| *`binding`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | Binding exposes a secret containing the binding information for this RabbitmqCluster. It implements the service binding Provisioned Service duck type. See: https://k8s-service-bindings.github.io/spec/#provisioned-service |=== diff --git a/internal/resource/default_user_secret.go b/internal/resource/default_user_secret.go index 6571938dd..d066ee858 100644 --- a/internal/resource/default_user_secret.go +++ b/internal/resource/default_user_secret.go @@ -26,6 +26,8 @@ import ( const ( DefaultUserSecretName = "default-user" + bindingProvider = "rabbitmq" + bindingType = "rabbitmq" ) type DefaultUserSecretBuilder struct { @@ -62,7 +64,11 @@ func (builder *DefaultUserSecretBuilder) Build() (client.Object, error) { Namespace: builder.Instance.Namespace, }, Type: corev1.SecretTypeOpaque, + // Default user secret implements the service binding Provisioned Service + // See: https://k8s-service-bindings.github.io/spec/#provisioned-service Data: map[string][]byte{ + "provider": []byte(bindingProvider), + "type": []byte(bindingType), "username": []byte(username), "password": []byte(password), "default_user.conf": defaultUserConf, diff --git a/internal/resource/default_user_secret_test.go b/internal/resource/default_user_secret_test.go index 0c198086a..8e6bcc351 100644 --- a/internal/resource/default_user_secret_test.go +++ b/internal/resource/default_user_secret_test.go @@ -99,6 +99,18 @@ var _ = Describe("DefaultUserSecret", func() { Expect(cfg.Section("").Key("default_user").Value()).To(Equal(string(username))) Expect(cfg.Section("").Key("default_pass").Value()).To(Equal(string(password))) }) + + By("setting 'data.provider' to 'rabbitmq' ", func() { + provider, ok := secret.Data["provider"] + Expect(ok).NotTo(BeFalse(), "Failed to find key 'provider' ") + Expect(string(provider)).To(Equal("rabbitmq")) + }) + + By("setting 'data.type' to 'rabbitmq' ", func() { + t, ok := secret.Data["type"] + Expect(ok).NotTo(BeFalse(), "Failed to find key 'type' ") + Expect(string(t)).To(Equal("rabbitmq")) + }) }) }) diff --git a/internal/resource/role.go b/internal/resource/role.go index 2e5b11168..d1fdc76e4 100644 --- a/internal/resource/role.go +++ b/internal/resource/role.go @@ -46,7 +46,6 @@ func (builder *RoleBuilder) Build() (client.Object, error) { }, nil } - func (builder *RoleBuilder) UpdateMayRequireStsRecreate() bool { return false } diff --git a/internal/resource/service.go b/internal/resource/service.go index 2fbb1ec4e..32a6db054 100644 --- a/internal/resource/service.go +++ b/internal/resource/service.go @@ -50,7 +50,6 @@ func (builder *ServiceBuilder) Build() (client.Object, error) { }, nil } - func (builder *ServiceBuilder) UpdateMayRequireStsRecreate() bool { return false } diff --git a/internal/resource/service_account.go b/internal/resource/service_account.go index 64aa525fd..19b936e1c 100644 --- a/internal/resource/service_account.go +++ b/internal/resource/service_account.go @@ -46,7 +46,6 @@ func (builder *ServiceAccountBuilder) Build() (client.Object, error) { }, nil } - func (builder *ServiceAccountBuilder) UpdateMayRequireStsRecreate() bool { return false } diff --git a/system_tests/system_tests.go b/system_tests/system_tests.go index 75cfa3e48..7fcfb3118 100644 --- a/system_tests/system_tests.go +++ b/system_tests/system_tests.go @@ -286,7 +286,7 @@ CONSOLE_LOG=new` }) Context("Persistence expansion", func() { - var cluster *rabbitmqv1beta1.RabbitmqCluster + var cluster *rabbitmqv1beta1.RabbitmqCluster AfterEach(func() { Expect(rmqClusterClient.Delete(context.TODO(), cluster)).To(Succeed())