From 54b82e241cde6b03ee2b42c983991372c4272dc6 Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Mon, 27 May 2024 15:27:03 +0200 Subject: [PATCH] feat: Change types for infrastructure stack CRD files/environment (#988) --- ...yments.plural.sh_infrastructurestacks.yaml | 91 ++++++++++++------- .../api/v1alpha1/infrastructurestack_types.go | 20 ++-- .../api/v1alpha1/zz_generated.deepcopy.go | 24 +++-- ...yments.plural.sh_infrastructurestacks.yaml | 91 ++++++++++++------- ...loyments_v1alpha1_infrastructurestack.yaml | 18 ++-- .../infrastructurestack_controller.go | 59 +++++++----- .../controller/infrastructurestack_test.go | 43 ++++++++- ...yments.plural.sh_infrastructurestacks.yaml | 91 ++++++++++++------- 8 files changed, 295 insertions(+), 142 deletions(-) diff --git a/charts/controller/crds/deployments.plural.sh_infrastructurestacks.yaml b/charts/controller/crds/deployments.plural.sh_infrastructurestacks.yaml index 152fbdc0bb..bcb0280022 100644 --- a/charts/controller/crds/deployments.plural.sh_infrastructurestacks.yaml +++ b/charts/controller/crds/deployments.plural.sh_infrastructurestacks.yaml @@ -161,33 +161,79 @@ spec: environment: items: properties: + configMapRef: + description: Selects a key from a ConfigMap. + properties: + key: + description: The key to select. + type: string + 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 + optional: + description: Specify whether the ConfigMap or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic name: type: string - secret: - type: boolean + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + 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 + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic value: type: string required: - name - - value type: object type: array files: - description: Files reference to ConfigMaps with a key as a path and - value as a content + description: Files reference to Secret with a key as a part of mount + path and value as a content items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. 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? + mountPath: type: string + secretRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + x-kubernetes-map-type: atomic + required: + - mountPath + - secretRef type: object - x-kubernetes-map-type: atomic type: array git: description: Git reference w/in the repository where the IaC lives @@ -318,23 +364,6 @@ spec: type: string type: object x-kubernetes-map-type: atomic - secretFiles: - description: SecretFiles reference to Secrets with a key as a path - and value as a content - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - 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 - x-kubernetes-map-type: atomic - type: array type: description: Type specifies the tool to use to apply it enum: diff --git a/controller/api/v1alpha1/infrastructurestack_types.go b/controller/api/v1alpha1/infrastructurestack_types.go index d38d52c7fa..2a32eaebac 100644 --- a/controller/api/v1alpha1/infrastructurestack_types.go +++ b/controller/api/v1alpha1/infrastructurestack_types.go @@ -68,12 +68,9 @@ type InfrastructureStackSpec struct { // +kubebuilder:validation:Optional Environment []StackEnvironment `json:"environment,omitempty"` - // Files reference to ConfigMaps with a key as a path and value as a content + // Files reference to Secret with a key as a part of mount path and value as a content // +kubebuilder:validation:Optional - Files []corev1.LocalObjectReference `json:"files,omitempty"` - // SecretFiles reference to Secrets with a key as a path and value as a content - // +kubebuilder:validation:Optional - SecretFiles []corev1.LocalObjectReference `json:"secretFiles,omitempty"` + Files []StackFile `json:"files,omitempty"` // Detach determined if user want to delete or detach stack Detach bool `json:"detach"` @@ -105,8 +102,8 @@ func init() { } type StackFile struct { - Path string `json:"path"` - Content string `json:"content"` + MountPath string `json:"mountPath"` + SecretRef corev1.LocalObjectReference `json:"secretRef"` } type StackConfiguration struct { @@ -118,10 +115,13 @@ type StackConfiguration struct { } type StackEnvironment struct { - Name string `json:"name"` - Value string `json:"value"` + Name string `json:"name"` + // +kubebuilder:validation:Optional + Value *string `json:"value,omitempty"` + // +kubebuilder:validation:Optional + SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"` // +kubebuilder:validation:Optional - Secret *bool `json:"secret,omitempty"` + ConfigMapRef *corev1.ConfigMapKeySelector `json:"configMapRef,omitempty"` } func (p *InfrastructureStack) StackName() string { diff --git a/controller/api/v1alpha1/zz_generated.deepcopy.go b/controller/api/v1alpha1/zz_generated.deepcopy.go index 6935cd70f3..d456d3ec04 100644 --- a/controller/api/v1alpha1/zz_generated.deepcopy.go +++ b/controller/api/v1alpha1/zz_generated.deepcopy.go @@ -1153,12 +1153,7 @@ func (in *InfrastructureStackSpec) DeepCopyInto(out *InfrastructureStackSpec) { } if in.Files != nil { in, out := &in.Files, &out.Files - *out = make([]v1.LocalObjectReference, len(*in)) - copy(*out, *in) - } - if in.SecretFiles != nil { - in, out := &in.SecretFiles, &out.SecretFiles - *out = make([]v1.LocalObjectReference, len(*in)) + *out = make([]StackFile, len(*in)) copy(*out, *in) } } @@ -2925,11 +2920,21 @@ func (in *StackConfiguration) DeepCopy() *StackConfiguration { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StackEnvironment) DeepCopyInto(out *StackEnvironment) { *out = *in - if in.Secret != nil { - in, out := &in.Secret, &out.Secret - *out = new(bool) + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(string) **out = **in } + if in.SecretKeyRef != nil { + in, out := &in.SecretKeyRef, &out.SecretKeyRef + *out = new(v1.SecretKeySelector) + (*in).DeepCopyInto(*out) + } + if in.ConfigMapRef != nil { + in, out := &in.ConfigMapRef, &out.ConfigMapRef + *out = new(v1.ConfigMapKeySelector) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StackEnvironment. @@ -2945,6 +2950,7 @@ func (in *StackEnvironment) DeepCopy() *StackEnvironment { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StackFile) DeepCopyInto(out *StackFile) { *out = *in + out.SecretRef = in.SecretRef } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StackFile. diff --git a/controller/config/crd/bases/deployments.plural.sh_infrastructurestacks.yaml b/controller/config/crd/bases/deployments.plural.sh_infrastructurestacks.yaml index 152fbdc0bb..bcb0280022 100644 --- a/controller/config/crd/bases/deployments.plural.sh_infrastructurestacks.yaml +++ b/controller/config/crd/bases/deployments.plural.sh_infrastructurestacks.yaml @@ -161,33 +161,79 @@ spec: environment: items: properties: + configMapRef: + description: Selects a key from a ConfigMap. + properties: + key: + description: The key to select. + type: string + 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 + optional: + description: Specify whether the ConfigMap or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic name: type: string - secret: - type: boolean + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + 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 + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic value: type: string required: - name - - value type: object type: array files: - description: Files reference to ConfigMaps with a key as a path and - value as a content + description: Files reference to Secret with a key as a part of mount + path and value as a content items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. 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? + mountPath: type: string + secretRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + x-kubernetes-map-type: atomic + required: + - mountPath + - secretRef type: object - x-kubernetes-map-type: atomic type: array git: description: Git reference w/in the repository where the IaC lives @@ -318,23 +364,6 @@ spec: type: string type: object x-kubernetes-map-type: atomic - secretFiles: - description: SecretFiles reference to Secrets with a key as a path - and value as a content - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - 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 - x-kubernetes-map-type: atomic - type: array type: description: Type specifies the tool to use to apply it enum: diff --git a/controller/config/samples/deployments_v1alpha1_infrastructurestack.yaml b/controller/config/samples/deployments_v1alpha1_infrastructurestack.yaml index 45038106fb..3bbbd669bf 100644 --- a/controller/config/samples/deployments_v1alpha1_infrastructurestack.yaml +++ b/controller/config/samples/deployments_v1alpha1_infrastructurestack.yaml @@ -4,7 +4,7 @@ metadata: name: existing namespace: default spec: - handle: mgmt + handle: lukasz --- apiVersion: deployments.plural.sh/v1alpha1 kind: GitRepository @@ -15,13 +15,12 @@ spec: url: https://github.com/zreigz/tf-hello.git --- apiVersion: v1 -kind: ConfigMap +kind: Secret metadata: name: infrastructurestack namespace: default data: - your-file.json: | - {key1: value1, key2: value2, keyN: valueN} + secret: dGVzdA== --- apiVersion: deployments.plural.sh/v1alpha1 kind: InfrastructureStack @@ -50,8 +49,15 @@ spec: ref: main folder: terraform files: - - name: infrastructurestack + - mountPath: /tmp + secretRef: + name: infrastructurestack environment: - - name: test + - name: TEST value: test + - name: SECRET + secretKeyRef: + name: infrastructurestack + key: secret + diff --git a/controller/internal/controller/infrastructurestack_controller.go b/controller/internal/controller/infrastructurestack_controller.go index 1e4fcb1bb9..1b703ee907 100644 --- a/controller/internal/controller/infrastructurestack_controller.go +++ b/controller/internal/controller/infrastructurestack_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "fmt" console "github.com/pluralsh/console-client-go" "github.com/pluralsh/console/controller/api/v1alpha1" @@ -233,40 +234,56 @@ func (r *InfrastructureStackReconciler) getStackAttributes(ctx context.Context, } for _, file := range stack.Spec.Files { - configMap := &corev1.ConfigMap{} - name := types.NamespacedName{Name: file.Name, Namespace: stack.GetNamespace()} - if err := r.Get(ctx, name, configMap); err != nil { - return nil, err - } - for k, v := range configMap.Data { - attr.Files = append(attr.Files, &console.StackFileAttributes{ - Path: k, - Content: v, - }) - } - } - for _, file := range stack.Spec.SecretFiles { secret := &corev1.Secret{} - name := types.NamespacedName{Name: file.Name, Namespace: stack.GetNamespace()} + name := types.NamespacedName{Name: file.SecretRef.Name, Namespace: stack.GetNamespace()} if err := r.Get(ctx, name, secret); err != nil { return nil, err } for k, v := range secret.Data { attr.Files = append(attr.Files, &console.StackFileAttributes{ - Path: k, + Path: fmt.Sprintf("%s/%s", file.MountPath, k), Content: string(v), }) } } - attr.Environment = algorithms.Map(stack.Spec.Environment, - func(b v1alpha1.StackEnvironment) *console.StackEnvironmentAttributes { - return &console.StackEnvironmentAttributes{ - Name: b.Name, - Value: b.Value, - Secret: b.Secret, + for _, env := range stack.Spec.Environment { + var isSecret *bool + var value string + + if env.Value != nil { + value = *env.Value + } else if env.SecretKeyRef != nil { + secret := &corev1.Secret{} + name := types.NamespacedName{Name: env.SecretKeyRef.Name, Namespace: stack.GetNamespace()} + if err := r.Get(ctx, name, secret); err != nil { + return nil, err } + isSecret = lo.ToPtr(true) + rawData, ok := secret.Data[env.SecretKeyRef.Key] + if !ok { + return nil, fmt.Errorf("can not find secret data for the key %s", env.SecretKeyRef.Key) + } + value = string(rawData) + } else if env.ConfigMapRef != nil { + configMap := &corev1.ConfigMap{} + name := types.NamespacedName{Name: env.ConfigMapRef.Name, Namespace: stack.GetNamespace()} + if err := r.Get(ctx, name, configMap); err != nil { + return nil, err + } + rawData, ok := configMap.Data[env.ConfigMapRef.Key] + if !ok { + return nil, fmt.Errorf("can not find secret data for the key %s", env.ConfigMapRef.Key) + } + value = rawData + } + + attr.Environment = append(attr.Environment, &console.StackEnvironmentAttributes{ + Name: env.Name, + Value: value, + Secret: isSecret, }) + } jobSpec, err := gateJobAttributes(stack.Spec.JobSpec) if err != nil { diff --git a/controller/internal/controller/infrastructurestack_test.go b/controller/internal/controller/infrastructurestack_test.go index 9775b13385..c559f80fe5 100644 --- a/controller/internal/controller/infrastructurestack_test.go +++ b/controller/internal/controller/infrastructurestack_test.go @@ -21,6 +21,7 @@ import ( var _ = Describe("Infrastructure Stack Controller", Ordered, func() { Context("When reconciling a resource", func() { const ( + secretName = "stack-secret" stackName = "stack-test" clusterName = "cluster-test" repoName = "repo-test" @@ -37,6 +38,13 @@ var _ = Describe("Infrastructure Stack Controller", Ordered, func() { } BeforeAll(func() { + By("creating the configuration secret") + Expect(common.MaybeCreate(k8sClient, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: namespace}, + Data: map[string][]byte{ + "secret": []byte("secret"), + }, + }, nil)).To(Succeed()) By("creating the custom resource for the Kind Cluster") Expect(common.MaybeCreate(k8sClient, &v1alpha1.Cluster{ ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: namespace}, @@ -78,14 +86,43 @@ var _ = Describe("Infrastructure Stack Controller", Ordered, func() { Configuration: v1alpha1.StackConfiguration{ Version: "v0.0.1", }, + Environment: []v1alpha1.StackEnvironment{ + { + Name: "testSecret", + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secretName, + }, + Key: "secret", + }, + }, + { + Name: "testValue", + Value: lo.ToPtr("testValue"), + }, + }, + Files: []v1alpha1.StackFile{ + { + MountPath: "/opt/mnt", + SecretRef: corev1.LocalObjectReference{ + Name: secretName, + }, + }, + }, }, }, nil)).To(Succeed()) }) AfterAll(func() { + secret := &corev1.Secret{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, secret) + Expect(err).NotTo(HaveOccurred()) + By("Cleanup the secret") + Expect(k8sClient.Delete(ctx, secret)).To(Succeed()) + resource := &v1alpha1.Cluster{} - err := k8sClient.Get(ctx, types.NamespacedName{Name: clusterName, Namespace: namespace}, resource) + err = k8sClient.Get(ctx, types.NamespacedName{Name: clusterName, Namespace: namespace}, resource) Expect(err).NotTo(HaveOccurred()) By("Cleanup the specific resource instance Cluster") Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) @@ -112,7 +149,7 @@ var _ = Describe("Infrastructure Stack Controller", Ordered, func() { }{ expectedStatus: v1alpha1.Status{ ID: lo.ToPtr(id), - SHA: lo.ToPtr("32YZZXF6HMFYYT4XLB647U2ROKV75U54EMPOUSDBI55ZWYK2Y2QQ===="), + SHA: lo.ToPtr("RPLCWEQWMYPIT2FYGYK74NJLOME2Z2CGCSGNGVEFDTYQNSOGOISQ===="), Conditions: []metav1.Condition{ { Type: v1alpha1.ReadyConditionType.String(), @@ -159,7 +196,7 @@ var _ = Describe("Infrastructure Stack Controller", Ordered, func() { }{ expectedStatus: v1alpha1.Status{ ID: lo.ToPtr(id), - SHA: lo.ToPtr("SSJWBR7QSBV72ZZCUX7FESBBFSC5BCZYW6EWZY5RMTTHNR5Q2MCQ===="), + SHA: lo.ToPtr("RGMH2ZJ63YETRBVEGA2FIEX33F43TY2NSGZQ665GN3UW2KKRHQFQ===="), Conditions: []metav1.Condition{ { Type: v1alpha1.ReadyConditionType.String(), diff --git a/plural/helm/console/crds/deployments.plural.sh_infrastructurestacks.yaml b/plural/helm/console/crds/deployments.plural.sh_infrastructurestacks.yaml index 152fbdc0bb..bcb0280022 100644 --- a/plural/helm/console/crds/deployments.plural.sh_infrastructurestacks.yaml +++ b/plural/helm/console/crds/deployments.plural.sh_infrastructurestacks.yaml @@ -161,33 +161,79 @@ spec: environment: items: properties: + configMapRef: + description: Selects a key from a ConfigMap. + properties: + key: + description: The key to select. + type: string + 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 + optional: + description: Specify whether the ConfigMap or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic name: type: string - secret: - type: boolean + secretKeyRef: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + 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 + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic value: type: string required: - name - - value type: object type: array files: - description: Files reference to ConfigMaps with a key as a path and - value as a content + description: Files reference to Secret with a key as a part of mount + path and value as a content items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. 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? + mountPath: type: string + secretRef: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + 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 + x-kubernetes-map-type: atomic + required: + - mountPath + - secretRef type: object - x-kubernetes-map-type: atomic type: array git: description: Git reference w/in the repository where the IaC lives @@ -318,23 +364,6 @@ spec: type: string type: object x-kubernetes-map-type: atomic - secretFiles: - description: SecretFiles reference to Secrets with a key as a path - and value as a content - items: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - 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 - x-kubernetes-map-type: atomic - type: array type: description: Type specifies the tool to use to apply it enum: