Skip to content

Commit

Permalink
Add credentials distribution system
Browse files Browse the repository at this point in the history
  • Loading branch information
eromanova authored and Kshatrix committed Dec 6, 2024
1 parent da882ad commit b35b0da
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 25 deletions.
7 changes: 5 additions & 2 deletions api/v1alpha1/accessmanagement_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,20 @@ 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.
ClusterTemplateChains []string `json:"clusterTemplateChains,omitempty"`
// 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"
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha1/credential_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 71 additions & 12 deletions internal/controller/accessmanagement_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
67 changes: 60 additions & 7 deletions internal/controller/accessmanagement_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,37 @@ 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"
namespace3Name = "namespace3"

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{
Expand Down Expand Up @@ -85,6 +96,7 @@ var _ = Describe("Template Management Controller", func() {
},
},
ClusterTemplateChains: []string{ctChainName},
Credentials: []string{credName},
},
{
// Target namespace: namespace1
Expand All @@ -93,6 +105,7 @@ var _ = Describe("Template Management Controller", func() {
},
ClusterTemplateChains: []string{ctChainName},
ServiceTemplateChains: []string{stChainName},
Credentials: []string{credName},
},
{
// Target namespace: namespace3
Expand All @@ -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
Expand All @@ -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())
}
}
})
Expand All @@ -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())
Expand All @@ -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,
Expand All @@ -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)
})
})
})
Loading

0 comments on commit b35b0da

Please sign in to comment.