diff --git a/api/v1alpha1/types_etcd.go b/api/v1alpha1/types_etcd.go index 21dfc3c66..8c2bf2e97 100644 --- a/api/v1alpha1/types_etcd.go +++ b/api/v1alpha1/types_etcd.go @@ -470,3 +470,8 @@ func (e *Etcd) GetAsOwnerReference() metav1.OwnerReference { BlockOwnerDeletion: pointer.Bool(true), } } + +// GetRoleName returns the role name for the Etcd +func (e *Etcd) GetRoleName() string { + return fmt.Sprintf("druid.gardener.cloud:etcd:%s", e.Name) +} diff --git a/api/v1alpha1/types_etcd_test.go b/api/v1alpha1/types_etcd_test.go index 5e75a2afd..396c715cf 100644 --- a/api/v1alpha1/types_etcd_test.go +++ b/api/v1alpha1/types_etcd_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" . "github.com/gardener/etcd-druid/api/v1alpha1" ) @@ -42,17 +43,9 @@ var _ = Describe("Etcd", func() { ) BeforeEach(func() { - // Add any setup steps that needs to be executed before each test + created = getEtcd("foo", "default") }) - AfterEach(func() { - // Add any teardown steps that needs to be executed after each test - }) - - // Add Tests for OpenAPI validation (or additional CRD features) specified in - // your API definition. - // Avoid adding tests for vanilla CRUD operations because they would - // test Kubernetes API server, which isn't the goal here. Context("Create API", func() { It("should create an object successfully", func() { @@ -61,7 +54,6 @@ var _ = Describe("Etcd", func() { Name: "foo", Namespace: "default", } - created = getEtcd("foo", "default") By("creating an API obj") Expect(k8sClient.Create(context.Background(), created)).To(Succeed()) @@ -77,6 +69,85 @@ var _ = Describe("Etcd", func() { }) + Context("GetPeerServiceName", func() { + It("should return the correct peer service name", func() { + Expect(created.GetPeerServiceName()).To(Equal("foo-peer")) + }) + }) + + Context("GetClientServiceName", func() { + It("should return the correct client service name", func() { + Expect(created.GetClientServiceName()).To(Equal("foo-client")) + }) + }) + + Context("GetServiceAccountName", func() { + It("should return the correct service account name", func() { + Expect(created.GetServiceAccountName()).To(Equal("foo")) + }) + }) + + Context("GetConfigmapName", func() { + It("should return the correct configmap name", func() { + Expect(created.GetConfigmapName()).To(Equal("etcd-bootstrap-123456")) + }) + }) + + Context("GetCompactionJobName", func() { + It("should return the correct compaction job name", func() { + Expect(created.GetCompactionJobName()).To(Equal("123456-compact-job")) + }) + }) + + Context("GetOrdinalPodName", func() { + It("should return the correct ordinal pod name", func() { + Expect(created.GetOrdinalPodName(0)).To(Equal("foo-0")) + }) + }) + + Context("GetDeltaSnapshotLeaseName", func() { + It("should return the correct delta snapshot lease name", func() { + Expect(created.GetDeltaSnapshotLeaseName()).To(Equal("foo-delta-snap")) + }) + }) + + Context("GetFullSnapshotLeaseName", func() { + It("should return the correct full snapshot lease name", func() { + Expect(created.GetFullSnapshotLeaseName()).To(Equal("foo-full-snap")) + }) + }) + + Context("GetDefaultLabels", func() { + It("should return the default labels for etcd", func() { + expected := map[string]string{ + "name": "etcd", + "instance": "foo", + } + Expect(created.GetDefaultLabels()).To(Equal(expected)) + }) + }) + + Context("GetAsOwnerReference", func() { + It("should return an OwnerReference object that represents the current Etcd instance", func() { + + expected := metav1.OwnerReference{ + APIVersion: GroupVersion.String(), + Kind: "Etcd", + Name: "foo", + UID: "123456", + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + } + Expect(created.GetAsOwnerReference()).To(Equal(expected)) + }) + }) + + Context("GetRoleName", func() { + It("should return the role name for the Etcd", func() { + expected := "druid.gardener.cloud:etcd:foo" + Expect(created.GetRoleName()).To(Equal(expected)) + }) + }) }) func getEtcd(name, namespace string) *Etcd { @@ -135,6 +206,7 @@ func getEtcd(name, namespace string) *Etcd { ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, + UID: "123456", }, Spec: EtcdSpec{ Annotations: map[string]string{ diff --git a/charts/etcd/templates/etcd-role.yaml b/charts/etcd/templates/etcd-role.yaml deleted file mode 100644 index 518ba6dac..000000000 --- a/charts/etcd/templates/etcd-role.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ .Values.roleName }} - namespace: {{ .Release.Namespace }} - ownerReferences: - - apiVersion: druid.gardener.cloud/v1alpha1 - blockOwnerDeletion: true - controller: true - kind: Etcd - name: {{ .Values.name }} - uid: {{ .Values.uid }} - labels: - name: etcd - instance: {{ .Values.name }} -{{- if .Values.labels }} -{{ toYaml .Values.labels | indent 4 }} -{{- end }} -rules: -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - list - - get - - update - - patch - - watch -- apiGroups: - - apps - resources: - - statefulsets - verbs: - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - - pods - verbs: - - get - - list - - watch \ No newline at end of file diff --git a/controllers/etcd/reconciler.go b/controllers/etcd/reconciler.go index 5c37789f1..8b7bd5bec 100644 --- a/controllers/etcd/reconciler.go +++ b/controllers/etcd/reconciler.go @@ -26,6 +26,7 @@ import ( componentconfigmap "github.com/gardener/etcd-druid/pkg/component/etcd/configmap" componentlease "github.com/gardener/etcd-druid/pkg/component/etcd/lease" componentpdb "github.com/gardener/etcd-druid/pkg/component/etcd/poddisruptionbudget" + componentrole "github.com/gardener/etcd-druid/pkg/component/etcd/role" componentservice "github.com/gardener/etcd-druid/pkg/component/etcd/service" componentserviceaccount "github.com/gardener/etcd-druid/pkg/component/etcd/serviceaccount" componentsts "github.com/gardener/etcd-druid/pkg/component/etcd/statefulset" @@ -271,6 +272,14 @@ func (r *Reconciler) delete(ctx context.Context, etcd *druidv1alpha1.Etcd) (ctrl }, err } + roleValues := componentrole.GenerateValues(etcd) + roleDeployer := componentrole.New(r.Client, roleValues) + if err := roleDeployer.Destroy(ctx); err != nil { + return ctrl.Result{ + Requeue: true, + }, err + } + if sets.NewString(etcd.Finalizers...).Has(common.FinalizerName) { logger.Info("Removing finalizer") if err := controllerutils.RemoveFinalizers(ctx, r.Client, etcd, common.FinalizerName); client.IgnoreNotFound(err) != nil { @@ -283,39 +292,6 @@ func (r *Reconciler) delete(ctx context.Context, etcd *druidv1alpha1.Etcd) (ctrl return ctrl.Result{}, nil } -func (r *Reconciler) reconcileRole(ctx context.Context, logger logr.Logger, etcd *druidv1alpha1.Etcd, values map[string]interface{}) error { - logger.Info("Reconciling role") - var err error - - decoded, err := r.chart.decodeRole(etcd.Name, etcd.Namespace, values) - if err != nil { - return err - } - - roleObj := &rbac.Role{} - key := client.ObjectKeyFromObject(decoded) - if err := r.Get(ctx, key, roleObj); err != nil { - if !apierrors.IsNotFound(err) { - return err - } - if err := r.Create(ctx, decoded); err != nil { - return err - } - logger.Info("Creating role", "role", kutil.Key(decoded.Namespace, decoded.Name).String()) - return nil - } - - if !reflect.DeepEqual(decoded.Rules, roleObj.Rules) { - roleObjCopy := roleObj.DeepCopy() - roleObjCopy.Rules = decoded.Rules - if err := r.Patch(ctx, roleObjCopy, client.MergeFrom(roleObj)); err != nil { - return err - } - } - - return nil -} - func (r *Reconciler) reconcileRoleBinding(ctx context.Context, logger logr.Logger, etcd *druidv1alpha1.Etcd, values map[string]interface{}) error { logger.Info("Reconciling rolebinding") var err error @@ -398,17 +374,18 @@ func (r *Reconciler) reconcileEtcd(ctx context.Context, logger logr.Logger, etcd return reconcileResult{err: err} } - roleValues, err := r.getMapFromEtcd(etcd, r.config.DisableEtcdServiceAccountAutomount) + roleValues := componentrole.GenerateValues(etcd) + roleDeployer := componentrole.New(r.Client, roleValues) + err = roleDeployer.Deploy(ctx) if err != nil { return reconcileResult{err: err} } - err = r.reconcileRole(ctx, logger, etcd, roleValues) + roleBindingValues, err := r.getMapFromEtcd(etcd, r.config.DisableEtcdServiceAccountAutomount) if err != nil { return reconcileResult{err: err} } - - err = r.reconcileRoleBinding(ctx, logger, etcd, roleValues) + err = r.reconcileRoleBinding(ctx, logger, etcd, roleBindingValues) if err != nil { return reconcileResult{err: err} } diff --git a/pkg/component/etcd/role/role.go b/pkg/component/etcd/role/role.go new file mode 100644 index 000000000..3f5f14195 --- /dev/null +++ b/pkg/component/etcd/role/role.go @@ -0,0 +1,65 @@ +// Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package role + +import ( + "context" + + "github.com/gardener/gardener/pkg/controllerutils" + + gardenercomponent "github.com/gardener/gardener/pkg/operation/botanist/component" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type component struct { + client client.Client + values *Values +} + +func (c component) Deploy(ctx context.Context) error { + role := c.emptyRole() + _, err := controllerutils.GetAndCreateOrStrategicMergePatch(ctx, c.client, role, func() error { + role.Name = c.values.Name + role.Namespace = c.values.Namespace + role.Labels = c.values.Labels + role.OwnerReferences = c.values.OwnerReferences + role.Rules = c.values.Rules + return nil + }) + return err +} + +func (c component) Destroy(ctx context.Context) error { + return client.IgnoreNotFound(c.client.Delete(ctx, c.emptyRole())) +} + +func (c component) emptyRole() *rbacv1.Role { + return &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: c.values.Name, + Namespace: c.values.Namespace, + }, + } +} + +// New creates a new role deployer instance. +func New(c client.Client, values *Values) gardenercomponent.Deployer { + return &component{ + client: c, + values: values, + } +} diff --git a/pkg/component/etcd/role/role_suite_test.go b/pkg/component/etcd/role/role_suite_test.go new file mode 100644 index 000000000..98656d090 --- /dev/null +++ b/pkg/component/etcd/role/role_suite_test.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package role + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestService(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Role Component Suite") +} diff --git a/pkg/component/etcd/role/role_test.go b/pkg/component/etcd/role/role_test.go new file mode 100644 index 000000000..77034efa2 --- /dev/null +++ b/pkg/component/etcd/role/role_test.go @@ -0,0 +1,201 @@ +// Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package role_test + +import ( + "context" + + "github.com/gardener/etcd-druid/api/v1alpha1" + "github.com/gardener/etcd-druid/pkg/client/kubernetes" + "github.com/gardener/etcd-druid/pkg/component/etcd/role" + + "github.com/gardener/gardener/pkg/operation/botanist/component" + . "github.com/gardener/gardener/pkg/utils/test/matchers" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + + testutils "github.com/gardener/etcd-druid/test/utils" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var _ = Describe("Role Component", Ordered, func() { + var ( + ctx context.Context + c client.Client + values *role.Values + roleComponent component.Deployer + ) + + BeforeEach(func() { + ctx = context.Background() + c = fake.NewClientBuilder().WithScheme(kubernetes.Scheme).Build() + values = getTestRoleValues() + roleComponent = role.New(c, values) + }) + + Describe("#Deploy", func() { + It("should create the Role with the expected values", func() { + By("creating a Role") + err := roleComponent.Deploy(ctx) + Expect(err).NotTo(HaveOccurred()) + + By("verifying that the Role is created on the K8s cluster as expected") + created := &rbacv1.Role{} + err = c.Get(ctx, getRoleKeyFromValue(values), created) + Expect(err).NotTo(HaveOccurred()) + verifyRoleValues(created, values) + }) + It("should update the Role with the expected values", func() { + By("updating the Role") + values.Labels["new"] = "label" + err := roleComponent.Deploy(ctx) + Expect(err).NotTo(HaveOccurred()) + + By("verifying that the Role is updated on the K8s cluster as expected") + updated := &rbacv1.Role{} + err = c.Get(ctx, getRoleKeyFromValue(values), updated) + Expect(err).NotTo(HaveOccurred()) + verifyRoleValues(updated, values) + }) + It("should not return an error when there is nothing to update the Role", func() { + err := roleComponent.Deploy(ctx) + Expect(err).NotTo(HaveOccurred()) + updated := &rbacv1.Role{} + err = c.Get(ctx, getRoleKeyFromValue(values), updated) + Expect(err).NotTo(HaveOccurred()) + verifyRoleValues(updated, values) + }) + It("should return an error when the update fails", func() { + values.Name = "" + err := roleComponent.Deploy(ctx) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Required value: name is required")) + }) + }) + + Describe("#Destroy", func() { + It("should delete the Role", func() { + By("deleting the Role") + err := roleComponent.Destroy(ctx) + Expect(err).NotTo(HaveOccurred()) + + By("verifying that the Role is deleted from the K8s cluster as expected") + role := &rbacv1.Role{} + Expect(c.Get(ctx, getRoleKeyFromValue(values), role)).To(BeNotFoundError()) + }) + It("should not return an error when there is nothing to delete", func() { + err := roleComponent.Destroy(ctx) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) + +func verifyRoleValues(expected *rbacv1.Role, values *role.Values) { + Expect(expected.Name).To(Equal(values.Name)) + Expect(expected.Labels).To(Equal(values.Labels)) + Expect(expected.Namespace).To(Equal(values.Namespace)) + Expect(expected.OwnerReferences).To(Equal(values.OwnerReferences)) + Expect(expected.Rules).To(MatchAllElements(testutils.RuleIterator, Elements{ + "coordination.k8s.io": MatchFields(IgnoreExtras, Fields{ + "APIGroups": MatchAllElements(testutils.StringArrayIterator, Elements{ + "coordination.k8s.io": Equal("coordination.k8s.io"), + }), + "Resources": MatchAllElements(testutils.StringArrayIterator, Elements{ + "leases": Equal("leases"), + }), + "Verbs": MatchAllElements(testutils.StringArrayIterator, Elements{ + "list": Equal("list"), + "get": Equal("get"), + "update": Equal("update"), + "patch": Equal("patch"), + "watch": Equal("watch"), + }), + }), + "apps": MatchFields(IgnoreExtras, Fields{ + "APIGroups": MatchAllElements(testutils.StringArrayIterator, Elements{ + "apps": Equal("apps"), + }), + "Resources": MatchAllElements(testutils.StringArrayIterator, Elements{ + "statefulsets": Equal("statefulsets"), + }), + "Verbs": MatchAllElements(testutils.StringArrayIterator, Elements{ + "list": Equal("list"), + "get": Equal("get"), + "update": Equal("update"), + "patch": Equal("patch"), + "watch": Equal("watch"), + }), + }), + "": MatchFields(IgnoreExtras, Fields{ + "APIGroups": MatchAllElements(testutils.StringArrayIterator, Elements{ + "": Equal(""), + }), + "Resources": MatchAllElements(testutils.StringArrayIterator, Elements{ + "pods": Equal("pods"), + }), + "Verbs": MatchAllElements(testutils.StringArrayIterator, Elements{ + "list": Equal("list"), + "get": Equal("get"), + "watch": Equal("watch"), + }), + }), + })) +} +func getRoleKeyFromValue(values *role.Values) types.NamespacedName { + return client.ObjectKey{Name: values.Name, Namespace: values.Namespace} +} + +func getTestRoleValues() *role.Values { + return &role.Values{ + Name: "test-role", + Namespace: "test-namespace", + Labels: map[string]string{ + "foo": "bar", + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"coordination.k8s.io"}, + Resources: []string{"leases"}, + Verbs: []string{"get", "list", "patch", "update", "watch"}, + }, + { + APIGroups: []string{"apps"}, + Resources: []string{"statefulsets"}, + Verbs: []string{"get", "list", "patch", "update", "watch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: v1alpha1.GroupVersion.String(), + Kind: "etcd", + Name: "test-etcd", + UID: "123-456-789", + Controller: pointer.Bool(true), + BlockOwnerDeletion: pointer.Bool(true), + }, + }, + } +} diff --git a/pkg/component/etcd/role/values.go b/pkg/component/etcd/role/values.go new file mode 100644 index 000000000..65a258dad --- /dev/null +++ b/pkg/component/etcd/role/values.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package role + +import ( + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Values defines the fields used to create a Role for Etcd. +type Values struct { + // Name is the name of the Role. + Name string + // Namespace is the namespace of the Role. + Namespace string + // Rules holds all the PolicyRules for this Role + Rules []rbacv1.PolicyRule + // OwnerReferences are the OwnerReferences of the Role. + OwnerReferences []metav1.OwnerReference + // Labels are the labels of the Role. + Labels map[string]string +} diff --git a/pkg/component/etcd/role/values_helper.go b/pkg/component/etcd/role/values_helper.go new file mode 100644 index 000000000..51cfe11b4 --- /dev/null +++ b/pkg/component/etcd/role/values_helper.go @@ -0,0 +1,50 @@ +// Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package role + +import ( + druidv1alpha1 "github.com/gardener/etcd-druid/api/v1alpha1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GenerateValues generates `role.Values` for the role component with the given `etcd` object. +func GenerateValues(etcd *druidv1alpha1.Etcd) *Values { + return &Values{ + Name: etcd.GetRoleName(), + Namespace: etcd.Namespace, + Labels: etcd.GetDefaultLabels(), + OwnerReferences: []metav1.OwnerReference{ + etcd.GetAsOwnerReference(), + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"coordination.k8s.io"}, + Resources: []string{"leases"}, + Verbs: []string{"get", "list", "patch", "update", "watch"}, + }, + { + APIGroups: []string{"apps"}, + Resources: []string{"statefulsets"}, + Verbs: []string{"get", "list", "patch", "update", "watch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + } +} diff --git a/pkg/component/etcd/role/values_helper_test.go b/pkg/component/etcd/role/values_helper_test.go new file mode 100644 index 000000000..67100ab00 --- /dev/null +++ b/pkg/component/etcd/role/values_helper_test.go @@ -0,0 +1,81 @@ +// Copyright (c) 2023 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package role_test + +import ( + "github.com/gardener/etcd-druid/api/v1alpha1" + "github.com/gardener/etcd-druid/pkg/component/etcd/role" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("Role", func() { + var ( + etcd = &v1alpha1.Etcd{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.GroupVersion.String(), + Kind: "Etcd", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "etcd-name", + Namespace: "etcd-namespace", + UID: "etcd-uid", + }, + Spec: v1alpha1.EtcdSpec{ + Labels: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + }, + } + expected = &role.Values{ + Name: etcd.GetRoleName(), + Namespace: etcd.Namespace, + Labels: map[string]string{ + "name": "etcd", + "instance": etcd.Name, + }, + OwnerReferences: []metav1.OwnerReference{ + etcd.GetAsOwnerReference(), + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{"coordination.k8s.io"}, + Resources: []string{"leases"}, + Verbs: []string{"get", "list", "patch", "update", "watch"}, + }, + { + APIGroups: []string{"apps"}, + Resources: []string{"statefulsets"}, + Verbs: []string{"get", "list", "patch", "update", "watch"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"get", "list", "watch"}, + }, + }, + } + ) + + Context("Generate Values", func() { + It("should generate correct values", func() { + values := role.GenerateValues(etcd) + Expect(values).To(Equal(expected)) + }) + }) +}) diff --git a/test/integration/controllers/etcd/reconciler_test.go b/test/integration/controllers/etcd/reconciler_test.go index 7006c5aae..b13eb32a8 100644 --- a/test/integration/controllers/etcd/reconciler_test.go +++ b/test/integration/controllers/etcd/reconciler_test.go @@ -424,7 +424,7 @@ var _ = Describe("Multinode ETCD", func() { func validateRole(instance *druidv1alpha1.Etcd, role *rbac.Role) { Expect(*role).To(MatchFields(IgnoreExtras, Fields{ "ObjectMeta": MatchFields(IgnoreExtras, Fields{ - "Name": Equal(fmt.Sprintf("druid.gardener.cloud:etcd:%s", instance.Name)), + "Name": Equal(instance.GetRoleName()), "Namespace": Equal(instance.Namespace), "Labels": MatchKeys(IgnoreExtras, Keys{ "name": Equal("etcd"),