From b35b0da181ba47f5c0b9d7f503b21d5780d0d730 Mon Sep 17 00:00:00 2001 From: Ekaterina Kazakova Date: Wed, 4 Dec 2024 14:05:03 +0400 Subject: [PATCH] Add credentials distribution system --- api/v1alpha1/accessmanagement_types.go | 7 +- api/v1alpha1/credential_types.go | 2 + api/v1alpha1/zz_generated.deepcopy.go | 5 ++ .../controller/accessmanagement_controller.go | 83 ++++++++++++++++--- .../accessmanagement_controller_test.go | 67 +++++++++++++-- .../hmc.mirantis.com_accessmanagements.yaml | 22 ++++- test/objects/credential/credential.go | 9 ++ 7 files changed, 170 insertions(+), 25 deletions(-) diff --git a/api/v1alpha1/accessmanagement_types.go b/api/v1alpha1/accessmanagement_types.go index 07edf97c3..f96097374 100644 --- a/api/v1alpha1/accessmanagement_types.go +++ b/api/v1alpha1/accessmanagement_types.go @@ -42,10 +42,10 @@ type AccessManagementStatus struct { } // AccessRule is the definition of the AccessManagement access rule. Each AccessRule enforces -// Templates distribution to the TargetNamespaces +// Templates and Credentials distribution to the TargetNamespaces type AccessRule struct { // TargetNamespaces defines the namespaces where selected objects will be distributed. - // Templates will be distributed to all namespaces if unset. + // Templates and Credentials will be distributed to all namespaces if unset. TargetNamespaces TargetNamespaces `json:"targetNamespaces,omitempty"` // ClusterTemplateChains lists the names of ClusterTemplateChains whose ClusterTemplates // will be distributed to all namespaces specified in TargetNamespaces. @@ -53,6 +53,9 @@ type AccessRule struct { // ServiceTemplateChains lists the names of ServiceTemplateChains whose ServiceTemplates // will be distributed to all namespaces specified in TargetNamespaces. ServiceTemplateChains []string `json:"serviceTemplateChains,omitempty"` + // Credentials is the list of Credential names that will be distributed to all the + // namespaces specified in TargetNamespaces. + Credentials []string `json:"credentials,omitempty"` } // +kubebuilder:validation:XValidation:rule="((has(self.stringSelector) ? 1 : 0) + (has(self.selector) ? 1 : 0) + (has(self.list) ? 1 : 0)) <= 1", message="only one of spec.targetNamespaces.selector or spec.targetNamespaces.stringSelector or spec.targetNamespaces.list can be specified" diff --git a/api/v1alpha1/credential_types.go b/api/v1alpha1/credential_types.go index 31ceedb05..8a56bac9c 100644 --- a/api/v1alpha1/credential_types.go +++ b/api/v1alpha1/credential_types.go @@ -20,6 +20,8 @@ import ( ) const ( + CredentialKind = "Credential" + // CredentialReadyCondition indicates if referenced Credential exists and has Ready state CredentialReadyCondition = "CredentialReady" // CredentialPropagatedCondition indicates that CCM credentials were delivered to managed cluster diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 5b9080b34..48fbe8536 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -143,6 +143,11 @@ func (in *AccessRule) DeepCopyInto(out *AccessRule) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Credentials != nil { + in, out := &in.Credentials, &out.Credentials + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRule. diff --git a/internal/controller/accessmanagement_controller.go b/internal/controller/accessmanagement_controller.go index 00775b57a..7f0c53e2d 100644 --- a/internal/controller/accessmanagement_controller.go +++ b/internal/controller/accessmanagement_controller.go @@ -70,9 +70,14 @@ func (r *AccessManagementReconciler) Reconcile(ctx context.Context, req ctrl.Req if err != nil { return ctrl.Result{}, err } + systemCredentials, managedCredentials, err := r.getCredentials(ctx) + if err != nil { + return ctrl.Result{}, err + } keepCtChains := make(map[string]bool) keepStChains := make(map[string]bool) + keepCredentials := make(map[string]bool) var errs error for _, rule := range accessMgmt.Spec.AccessRules { @@ -105,23 +110,34 @@ func (r *AccessManagementReconciler) Reconcile(ctx context.Context, req ctrl.Req continue } } + for _, credentialName := range rule.Credentials { + keepCredentials[getNamespacedName(namespace, credentialName)] = true + if systemCredentials[credentialName] == nil { + errs = errors.Join(errs, fmt.Errorf("credential %s/%s is not found", r.SystemNamespace, credentialName)) + continue + } + errs = errors.Join(errs, r.createCredential(ctx, namespace, credentialName, systemCredentials[credentialName])) + } } } - for _, managedChain := range append(managedCtChains, managedStChains...) { + managedObjects := append(append(managedCtChains, managedStChains...), managedCredentials...) + for _, managedObject := range managedObjects { keep := false - templateNamespacedName := getNamespacedName(managedChain.GetNamespace(), managedChain.GetName()) - switch managedChain.GetObjectKind().GroupVersionKind().Kind { + namespacedName := getNamespacedName(managedObject.GetNamespace(), managedObject.GetName()) + switch managedObject.GetObjectKind().GroupVersionKind().Kind { case hmc.ClusterTemplateChainKind: - keep = keepCtChains[templateNamespacedName] + keep = keepCtChains[namespacedName] case hmc.ServiceTemplateChainKind: - keep = keepStChains[templateNamespacedName] + keep = keepStChains[namespacedName] + case hmc.CredentialKind: + keep = keepCredentials[namespacedName] default: - errs = errors.Join(errs, fmt.Errorf("invalid TemplateChain kind. Supported kinds are %s and %s", hmc.ClusterTemplateChainKind, hmc.ServiceTemplateChainKind)) + errs = errors.Join(errs, fmt.Errorf("invalid kind. Supported kinds are %s, %s and %s", hmc.ClusterTemplateChainKind, hmc.ServiceTemplateChainKind, hmc.CredentialKind)) } if !keep { - err := r.deleteTemplateChain(ctx, managedChain) + err := r.deleteManagedObject(ctx, managedObject) if err != nil { errs = errors.Join(errs, err) continue @@ -141,7 +157,7 @@ func getNamespacedName(namespace, name string) string { return fmt.Sprintf("%s/%s", namespace, name) } -func (r *AccessManagementReconciler) getCurrentTemplateChains(ctx context.Context, templateChainKind string) (map[string]templateChain, []templateChain, error) { +func (r *AccessManagementReconciler) getCurrentTemplateChains(ctx context.Context, templateChainKind string) (map[string]templateChain, []client.Object, error) { var templateChains []templateChain switch templateChainKind { case hmc.ClusterTemplateChainKind: @@ -168,7 +184,7 @@ func (r *AccessManagementReconciler) getCurrentTemplateChains(ctx context.Contex var ( systemTemplateChains = make(map[string]templateChain, len(templateChains)) - managedTemplateChains = make([]templateChain, 0, len(templateChains)) + managedTemplateChains = make([]client.Object, 0, len(templateChains)) ) for _, chain := range templateChains { if chain.GetNamespace() == r.SystemNamespace { @@ -184,6 +200,29 @@ func (r *AccessManagementReconciler) getCurrentTemplateChains(ctx context.Contex return systemTemplateChains, managedTemplateChains, nil } +func (r *AccessManagementReconciler) getCredentials(ctx context.Context) (map[string]*hmc.CredentialSpec, []client.Object, error) { + credentialList := &hmc.CredentialList{} + err := r.List(ctx, credentialList) + if err != nil { + return nil, nil, err + } + var ( + systemCredentials = make(map[string]*hmc.CredentialSpec, len(credentialList.Items)) + managedCredentials = make([]client.Object, 0, len(credentialList.Items)) + ) + for _, cred := range credentialList.Items { + if cred.Namespace == r.SystemNamespace { + systemCredentials[cred.Name] = &cred.Spec + continue + } + + if cred.GetLabels()[hmc.HMCManagedLabelKey] == hmc.HMCManagedLabelValue { + managedCredentials = append(managedCredentials, &cred) + } + } + return systemCredentials, managedCredentials, nil +} + func getTargetNamespaces(ctx context.Context, cl client.Client, targetNamespaces hmc.TargetNamespaces) ([]string, error) { if len(targetNamespaces.List) > 0 { return targetNamespaces.List, nil @@ -252,17 +291,37 @@ func (r *AccessManagementReconciler) createTemplateChain(ctx context.Context, so return nil } -func (r *AccessManagementReconciler) deleteTemplateChain(ctx context.Context, chain templateChain) error { +func (r *AccessManagementReconciler) createCredential(ctx context.Context, namespace, name string, spec *hmc.CredentialSpec) error { + l := ctrl.LoggerFrom(ctx) + + target := &hmc.Credential{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + hmc.HMCManagedLabelKey: hmc.HMCManagedLabelValue, + }, + }, + Spec: *spec, + } + if err := r.Create(ctx, target); client.IgnoreAlreadyExists(err) != nil { + return err + } + l.Info("Credential was successfully created", "namespace", namespace, "name", name) + return nil +} + +func (r *AccessManagementReconciler) deleteManagedObject(ctx context.Context, obj client.Object) error { l := ctrl.LoggerFrom(ctx) - err := r.Delete(ctx, chain) + err := r.Delete(ctx, obj) if err != nil { if apierrors.IsNotFound(err) { return nil } return err } - l.Info(chain.GetObjectKind().GroupVersionKind().Kind+" was successfully deleted", "chain namespace", chain.GetNamespace(), "chain name", chain.GetName()) + l.Info(obj.GetObjectKind().GroupVersionKind().Kind+" was successfully deleted", "namespace", obj.GetNamespace(), "name", obj.GetName()) return nil } diff --git a/internal/controller/accessmanagement_controller_test.go b/internal/controller/accessmanagement_controller_test.go index 71d4acc0a..377a8d5a8 100644 --- a/internal/controller/accessmanagement_controller_test.go +++ b/internal/controller/accessmanagement_controller_test.go @@ -28,17 +28,22 @@ import ( hmc "github.com/Mirantis/hmc/api/v1alpha1" am "github.com/Mirantis/hmc/test/objects/accessmanagement" + "github.com/Mirantis/hmc/test/objects/credential" tc "github.com/Mirantis/hmc/test/objects/templatechain" ) var _ = Describe("Template Management Controller", func() { Context("When reconciling a resource", func() { const ( - amName = "hmc-am" - ctChainName = "hmc-ct-chain" - stChainName = "hmc-st-chain" + amName = "hmc-am" + + ctChainName = "hmc-ct-chain" + stChainName = "hmc-st-chain" + credName = "test-cred" + ctChainToDeleteName = "hmc-ct-chain-to-delete" stChainToDeleteName = "hmc-st-chain-to-delete" + credToDeleteName = "test-cred-to-delete" namespace1Name = "namespace1" namespace2Name = "namespace2" @@ -46,8 +51,14 @@ var _ = Describe("Template Management Controller", func() { ctChainUnmanagedName = "ct-chain-unmanaged" stChainUnmanagedName = "st-chain-unmanaged" + credUnmanagedName = "test-cred-unmanaged" ) + credIdentityRef := &corev1.ObjectReference{ + Kind: "AWSClusterStaticIdentity", + Name: "awsclid", + } + ctx := context.Background() systemNamespace := &corev1.Namespace{ @@ -85,6 +96,7 @@ var _ = Describe("Template Management Controller", func() { }, }, ClusterTemplateChains: []string{ctChainName}, + Credentials: []string{credName}, }, { // Target namespace: namespace1 @@ -93,6 +105,7 @@ var _ = Describe("Template Management Controller", func() { }, ClusterTemplateChains: []string{ctChainName}, ServiceTemplateChains: []string{stChainName}, + Credentials: []string{credName}, }, { // Target namespace: namespace3 @@ -117,6 +130,24 @@ var _ = Describe("Template Management Controller", func() { ctChainUnmanaged := tc.NewClusterTemplateChain(tc.WithName(ctChainUnmanagedName), tc.WithNamespace(namespace1Name)) stChainUnmanaged := tc.NewServiceTemplateChain(tc.WithName(stChainUnmanagedName), tc.WithNamespace(namespace2Name)) + cred := credential.NewCredential( + credential.WithName(credName), + credential.WithNamespace(systemNamespace.Name), + credential.ManagedByHMC(), + credential.WithIdentityRef(credIdentityRef), + ) + credToDelete := credential.NewCredential( + credential.WithName(credToDeleteName), + credential.WithNamespace(namespace3Name), + credential.ManagedByHMC(), + credential.WithIdentityRef(credIdentityRef), + ) + credUnmanaged := credential.NewCredential( + credential.WithName(credUnmanagedName), + credential.WithNamespace(namespace2Name), + credential.WithIdentityRef(credIdentityRef), + ) + BeforeEach(func() { By("creating test namespaces") var err error @@ -132,14 +163,15 @@ var _ = Describe("Template Management Controller", func() { Expect(k8sClient.Create(ctx, am)).To(Succeed()) } - By("creating custom resources for the Kind ClusterTemplateChain and ServiceTemplateChain") - for _, chain := range []crclient.Object{ + By("creating custom resources for the Kind ClusterTemplateChain, ServiceTemplateChain amd Credentials") + for _, obj := range []crclient.Object{ ctChain, ctChainToDelete, ctChainUnmanaged, stChain, stChainToDelete, stChainUnmanaged, + cred, credToDelete, credUnmanaged, } { - err = k8sClient.Get(ctx, types.NamespacedName{Name: chain.GetName(), Namespace: chain.GetNamespace()}, chain) + err = k8sClient.Get(ctx, types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, obj) if err != nil && errors.IsNotFound(err) { - Expect(k8sClient.Create(ctx, chain)).To(Succeed()) + Expect(k8sClient.Create(ctx, obj)).To(Succeed()) } } }) @@ -159,6 +191,13 @@ var _ = Describe("Template Management Controller", func() { Expect(crclient.IgnoreNotFound(err)).To(Succeed()) } } + for _, c := range []*hmc.Credential{cred, credToDelete, credUnmanaged} { + for _, ns := range []*corev1.Namespace{systemNamespace, namespace1, namespace2, namespace3} { + c.Namespace = ns.Name + err := k8sClient.Delete(ctx, c) + Expect(crclient.IgnoreNotFound(err)).To(Succeed()) + } + } for _, ns := range []*corev1.Namespace{namespace1, namespace2, namespace3} { err := k8sClient.Get(ctx, types.NamespacedName{Name: ns.Name}, ns) Expect(err).NotTo(HaveOccurred()) @@ -171,10 +210,15 @@ var _ = Describe("Template Management Controller", func() { ctChainUnmanagedBefore := &hmc.ClusterTemplateChain{} err := k8sClient.Get(ctx, types.NamespacedName{Namespace: ctChainUnmanaged.Namespace, Name: ctChainUnmanaged.Name}, ctChainUnmanagedBefore) Expect(err).NotTo(HaveOccurred()) + stChainUnmanagedBefore := &hmc.ServiceTemplateChain{} err = k8sClient.Get(ctx, types.NamespacedName{Namespace: stChainUnmanaged.Namespace, Name: stChainUnmanaged.Name}, stChainUnmanagedBefore) Expect(err).NotTo(HaveOccurred()) + credUnmanagedBefore := &hmc.Credential{} + err = k8sClient.Get(ctx, types.NamespacedName{Namespace: credUnmanaged.Namespace, Name: credUnmanaged.Name}, credUnmanagedBefore) + Expect(err).NotTo(HaveOccurred()) + By("Reconciling the created resource") controllerReconciler := &AccessManagementReconciler{ Client: k8sClient, @@ -194,17 +238,26 @@ var _ = Describe("Template Management Controller", func() { * namespace2/st-chain-unmanaged - should be unchanged (unmanaged by HMC) * namespace2/hmc-ct-chain-to-delete - should be deleted * namespace3/hmc-st-chain-to-delete - should be deleted + + * namespace1/test-cred - should be created + * namespace2/test-cred - should be created + * namespace2/test-cred-unmanaged - should be unchanged (unmanaged by HMC) + * namespace3/test-cred-to delete - should be deleted */ verifyObjectCreated(ctx, namespace1Name, ctChain) verifyObjectCreated(ctx, namespace1Name, stChain) verifyObjectCreated(ctx, namespace2Name, ctChain) verifyObjectCreated(ctx, namespace3Name, stChain) + verifyObjectCreated(ctx, namespace1Name, cred) + verifyObjectCreated(ctx, namespace2Name, cred) verifyObjectUnchanged(ctx, namespace1Name, ctChainUnmanaged, ctChainUnmanagedBefore) verifyObjectUnchanged(ctx, namespace2Name, stChainUnmanaged, stChainUnmanagedBefore) + verifyObjectUnchanged(ctx, namespace2Name, credUnmanaged, credUnmanagedBefore) verifyObjectDeleted(ctx, namespace2Name, ctChainToDelete) verifyObjectDeleted(ctx, namespace3Name, stChainToDelete) + verifyObjectDeleted(ctx, namespace3Name, credToDelete) }) }) }) diff --git a/templates/provider/hmc/templates/crds/hmc.mirantis.com_accessmanagements.yaml b/templates/provider/hmc/templates/crds/hmc.mirantis.com_accessmanagements.yaml index ccf8f5970..e60ab46fa 100644 --- a/templates/provider/hmc/templates/crds/hmc.mirantis.com_accessmanagements.yaml +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_accessmanagements.yaml @@ -48,7 +48,7 @@ spec: items: description: |- AccessRule is the definition of the AccessManagement access rule. Each AccessRule enforces - Templates distribution to the TargetNamespaces + Templates and Credentials distribution to the TargetNamespaces properties: clusterTemplateChains: description: |- @@ -57,6 +57,13 @@ spec: items: type: string type: array + credentials: + description: |- + Credentials is the list of Credential names that will be distributed to all the + namespaces specified in TargetNamespaces. + items: + type: string + type: array serviceTemplateChains: description: |- ServiceTemplateChains lists the names of ServiceTemplateChains whose ServiceTemplates @@ -67,7 +74,7 @@ spec: targetNamespaces: description: |- TargetNamespaces defines the namespaces where selected objects will be distributed. - Templates will be distributed to all namespaces if unset. + Templates and Credentials will be distributed to all namespaces if unset. properties: list: description: |- @@ -146,7 +153,7 @@ spec: items: description: |- AccessRule is the definition of the AccessManagement access rule. Each AccessRule enforces - Templates distribution to the TargetNamespaces + Templates and Credentials distribution to the TargetNamespaces properties: clusterTemplateChains: description: |- @@ -155,6 +162,13 @@ spec: items: type: string type: array + credentials: + description: |- + Credentials is the list of Credential names that will be distributed to all the + namespaces specified in TargetNamespaces. + items: + type: string + type: array serviceTemplateChains: description: |- ServiceTemplateChains lists the names of ServiceTemplateChains whose ServiceTemplates @@ -165,7 +179,7 @@ spec: targetNamespaces: description: |- TargetNamespaces defines the namespaces where selected objects will be distributed. - Templates will be distributed to all namespaces if unset. + Templates and Credentials will be distributed to all namespaces if unset. properties: list: description: |- diff --git a/test/objects/credential/credential.go b/test/objects/credential/credential.go index 8cc11ba5e..4e495fa8b 100644 --- a/test/objects/credential/credential.go +++ b/test/objects/credential/credential.go @@ -64,3 +64,12 @@ func WithReady(ready bool) Opt { p.Status.Ready = ready } } + +func ManagedByHMC() Opt { + return func(t *v1alpha1.Credential) { + if t.Labels == nil { + t.Labels = make(map[string]string) + } + t.Labels[v1alpha1.HMCManagedLabelKey] = v1alpha1.HMCManagedLabelValue + } +}