From 24edc52ac72fb298a039b2d0bdf9ab1deddf48a7 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Tue, 17 Dec 2024 16:14:04 +0200 Subject: [PATCH 1/7] Add containers annotation to default PVC Signed-off-by: gazarenkov --- api/v1alpha1/zz_generated.deepcopy.go | 2 +- api/v1alpha2/zz_generated.deepcopy.go | 2 +- api/v1alpha3/zz_generated.deepcopy.go | 2 +- docs/configuration.md | 24 +++++++++- pkg/model/deployment.go | 22 ++++++++++ pkg/model/pvcs.go | 22 +++++++--- pkg/model/pvcs_test.go | 25 +++++++++++ pkg/model/runtime.go | 1 + pkg/model/testdata/multi-pvc-containers.yaml | 44 +++++++++++++++++++ .../testdata/multicontainer-deployment.yaml | 24 ++++++++++ pkg/utils/utils.go | 20 +++++++++ pkg/utils/utils_test.go | 19 ++++++++ 12 files changed, 197 insertions(+), 10 deletions(-) create mode 100644 pkg/model/testdata/multi-pvc-containers.yaml create mode 100644 pkg/model/testdata/multicontainer-deployment.yaml diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 9b7acdd3..ca4d7a34 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ package v1alpha1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index c3ecd6b2..1357c08b 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ package v1alpha2 import ( - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/api/v1alpha3/zz_generated.deepcopy.go b/api/v1alpha3/zz_generated.deepcopy.go index b315b789..6613a889 100644 --- a/api/v1alpha3/zz_generated.deepcopy.go +++ b/api/v1alpha3/zz_generated.deepcopy.go @@ -5,7 +5,7 @@ package v1alpha3 import ( - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/docs/configuration.md b/docs/configuration.md index b729b671..99619990 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -42,7 +42,7 @@ Some objects, such as: app-config, configmap-files, secret-files, dynamic-plugin #### Object annotation for mounting a volume to a specific path -Using **rhdh.redhat.com/mount-path** annotation it is possible to define the directory where **PersistentVolumeClaim** object will be mounted to Backstage Container. +Using **rhdh.redhat.com/mount-path** annotation it is possible to define the directory where **PersistentVolumeClaim** object will be mounted. ```yaml apiVersion: v1 @@ -56,6 +56,28 @@ metadata: In the example above the PVC called **myclaim** will be mounted to **/mount/path/from/annotation** directory +#### Object annotation for mounting a volume to specific container(s) + +Using **rhdh.redhat.com/containers** annotation it is possible to define the containers where **PersistentVolumeClaim** object will be mounted. + +Options: + +* No or empty annotation means the volume will be mounted to the Backstage container only +* \* (asterik) means the volume will be mounted to all the containers +* Otherwise comma separated names of container will be used + +```yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: myclaim + annotations: + rhdh.redhat.com/mount-path: "init-dynamic-plugins,backstage-backend" +... +``` +In the example above the PVC called **myclaim** will be mounted to **init-dynamic-plugins** and **backstage-backend** containers + + ### Metadata Generation For Backstage to function consistently at runtime, certain metadata values need to be predictable. Therefore, the Operator generates values according to the following rules. Any value for these fields specified in either Default or Raw Configuration will be replaced by the generated values. diff --git a/pkg/model/deployment.go b/pkg/model/deployment.go index d1632f7d..ca8ec40b 100644 --- a/pkg/model/deployment.go +++ b/pkg/model/deployment.go @@ -134,6 +134,28 @@ func (b *BackstageDeployment) container() *corev1.Container { return &b.deployment.Spec.Template.Spec.Containers[BackstageContainerIndex(b.deployment)] } +func (b *BackstageDeployment) containerByName(name string) *corev1.Container { + for i, c := range b.deployment.Spec.Template.Spec.Containers { + if c.Name == name { + return &b.deployment.Spec.Template.Spec.Containers[i] + } + } + for i, c := range b.deployment.Spec.Template.Spec.InitContainers { + if c.Name == name { + return &b.deployment.Spec.Template.Spec.InitContainers[i] + } + } + return nil +} + +func (b *BackstageDeployment) allContainers() []corev1.Container { + containers := []corev1.Container{} + spec := b.deployment.Spec.Template.Spec + containers = append(containers, spec.InitContainers...) + containers = append(containers, spec.Containers...) + return containers +} + func (b *BackstageDeployment) podSpec() *corev1.PodSpec { return &b.deployment.Spec.Template.Spec } diff --git a/pkg/model/pvcs.go b/pkg/model/pvcs.go index 43ba5313..72a3619b 100644 --- a/pkg/model/pvcs.go +++ b/pkg/model/pvcs.go @@ -39,7 +39,7 @@ func addPvcsFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) { subPath = utils.ToRFC1123Label(pvcSpec.Name) } - addPvc(model.backstageDeployment, pvcSpec.Name, mountPath, subPath) + addPvc(model.backstageDeployment, pvcSpec.Name, mountPath, subPath, nil) } } @@ -84,8 +84,9 @@ func (b *BackstagePvcs) updateAndValidate(m *BackstageModel, _ bsv1.Backstage) e mountPath = filepath.Join(m.backstageDeployment.defaultMountPath(), volName) subPath = volName } - addPvc(m.backstageDeployment, pvc.Name, mountPath, subPath) + containers := utils.FilterContainers(m.backstageDeployment.allContainers(), pvc.GetAnnotations()[ContainersAnnotation]) + addPvc(m.backstageDeployment, pvc.Name, mountPath, subPath, containers) } return nil } @@ -99,7 +100,7 @@ func (b *BackstagePvcs) setMetaInfo(backstage bsv1.Backstage, scheme *runtime.Sc } } -func addPvc(bsd *BackstageDeployment, pvcName, mountPath, subPath string) { +func addPvc(bsd *BackstageDeployment, pvcName, mountPath, subPath string, affectedContainers []corev1.Container) { volName := utils.ToRFC1123Label(pvcName) volSrc := corev1.VolumeSource{ @@ -110,7 +111,16 @@ func addPvc(bsd *BackstageDeployment, pvcName, mountPath, subPath string) { bsd.deployment.Spec.Template.Spec.Volumes = append(bsd.deployment.Spec.Template.Spec.Volumes, corev1.Volume{Name: volName, VolumeSource: volSrc}) - bsd.container().VolumeMounts = append(bsd.container().VolumeMounts, - corev1.VolumeMount{Name: volName, MountPath: mountPath, SubPath: subPath}) - + if affectedContainers == nil { + // if nothing specified mount to the Backstage container only + bsd.container().VolumeMounts = append(bsd.container().VolumeMounts, + corev1.VolumeMount{Name: volName, MountPath: mountPath, SubPath: subPath}) + } else { + // else mount to the affectedContainers + for _, c := range affectedContainers { + update := bsd.containerByName(c.Name) + update.VolumeMounts = append(update.VolumeMounts, + corev1.VolumeMount{Name: volName, MountPath: mountPath, SubPath: subPath}) + } + } } diff --git a/pkg/model/pvcs_test.go b/pkg/model/pvcs_test.go index 5d8975c0..c682c217 100644 --- a/pkg/model/pvcs_test.go +++ b/pkg/model/pvcs_test.go @@ -62,6 +62,31 @@ func TestDefaultPvcs(t *testing.T) { } +func TestMultiContainersPvc(t *testing.T) { + bs := bsv1.Backstage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-pvc", + }, + } + + testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("deployment.yaml", "multicontainer-deployment.yaml").addToDefaultConfig("pvcs.yaml", "multi-pvc-containers.yaml") + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, true, testObj.scheme) + assert.NoError(t, err) + assert.NotNil(t, model) + assert.Equal(t, 4, len(model.backstageDeployment.allContainers())) + + assert.Equal(t, 3, len(model.backstageDeployment.podSpec().Volumes)) + // myclaim1(default), myclaim2(listed), myclaim3(*) + assert.Equal(t, 3, len(model.backstageDeployment.containerByName("backstage-backend").VolumeMounts)) + // myclaim2(listed), myclaim3(*) + assert.Equal(t, 2, len(model.backstageDeployment.containerByName("install-dynamic-plugins").VolumeMounts)) + // myclaim3(*) + assert.Equal(t, 1, len(model.backstageDeployment.containerByName("another-container").VolumeMounts)) + // myclaim3(*) + assert.Equal(t, 1, len(model.backstageDeployment.containerByName("another-init-container").VolumeMounts)) + +} + func TestSpecifiedPvcs(t *testing.T) { bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/model/runtime.go b/pkg/model/runtime.go index acd56857..5489f68d 100644 --- a/pkg/model/runtime.go +++ b/pkg/model/runtime.go @@ -25,6 +25,7 @@ import ( const BackstageAppLabel = "rhdh.redhat.com/app" const ConfiguredNameAnnotation = "rhdh.redhat.com/configured-name" const DefaultMountPathAnnotation = "rhdh.redhat.com/mount-path" +const ContainersAnnotation = "rhdh.redhat.com/containers" // Backstage configuration scaffolding with empty BackstageObjects. // There are all possible objects for configuration diff --git a/pkg/model/testdata/multi-pvc-containers.yaml b/pkg/model/testdata/multi-pvc-containers.yaml new file mode 100644 index 00000000..99e7aaed --- /dev/null +++ b/pkg/model/testdata/multi-pvc-containers.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: myclaim1 +spec: + accessModes: + - ReadWriteOnce + volumeMode: Filesystem + resources: + requests: + storage: 8Gi + storageClassName: slow +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: myclaim2 + annotations: + rhdh.redhat.com/mount-path: /mount/path/from/annotation + rhdh.redhat.com/containers: "backstage-backend,install-dynamic-plugins" +spec: + accessModes: + - ReadWriteOnce + volumeMode: Filesystem + resources: + requests: + storage: 8Gi + storageClassName: slow +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: myclaim3 + annotations: + rhdh.redhat.com/mount-path: /mount/path/from/annotation2 + rhdh.redhat.com/containers: "*" +spec: + accessModes: + - ReadWriteOnce + volumeMode: Filesystem + resources: + requests: + storage: 8Gi + storageClassName: slow \ No newline at end of file diff --git a/pkg/model/testdata/multicontainer-deployment.yaml b/pkg/model/testdata/multicontainer-deployment.yaml new file mode 100644 index 00000000..042c0c7a --- /dev/null +++ b/pkg/model/testdata/multicontainer-deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: # placeholder for 'backstage-' +spec: + replicas: 1 + selector: + matchLabels: + rhdh.redhat.com/app: # placeholder for 'backstage-' + template: + metadata: + labels: + rhdh.redhat.com/app: # placeholder for 'backstage-' + spec: + initContainers: + - image: 'quay.io/rhdh/rhdh-hub-rhel9:next' + name: install-dynamic-plugins + - image: 'quay.io/rhdh/rhdh-hub-rhel9:next' + name: another-init-container + containers: + - name: backstage-backend + image: quay.io/rhdh/rhdh-hub-rhel9:next + - name: another-container + image: quay.io/rhdh/rhdh-hub-rhel9:next diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 0f39787d..bddd0234 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -14,6 +14,8 @@ import ( "strconv" "strings" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime" @@ -212,3 +214,21 @@ func BoolEnvVar(envvar string, def bool) bool { } return def } + +func FilterContainers(allContainers []corev1.Container, filter string) []corev1.Container { + if filter == "*" { + return allContainers + } else if filter == "" { + return nil + } + + filtered := []corev1.Container{} + for _, c := range allContainers { + for _, cname := range strings.Split(filter, ",") { + if c.Name == strings.TrimSpace(cname) { + filtered = append(filtered, c) + } + } + } + return filtered +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index bfbd4195..65d48301 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -140,3 +140,22 @@ func TestBoolEnvVar(t *testing.T) { t.Setenv("MyVar", "anything") assert.True(t, BoolEnvVar("anything", true)) } + +func TestFilterContainers(t *testing.T) { + + containers := []corev1.Container{{Name: "c1"}, {Name: "c2"}, {Name: "c3"}} + + cs := FilterContainers(containers, "") + assert.Nil(t, cs) + + cs = FilterContainers(containers, "*") + assert.Equal(t, 3, len(cs)) + + cs = FilterContainers(containers, "c123") + assert.Equal(t, 0, len(cs)) + + cs = FilterContainers(containers, "c1,c2") + assert.Equal(t, 2, len(cs)) + assert.Equal(t, "c1", cs[0].Name) + +} From 62f9d1f181498ef351562fa0cb5ce9d2d673746a Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Wed, 18 Dec 2024 10:58:55 +0200 Subject: [PATCH 2/7] use PVC for dynamic-plugins volume in RHDH default config Signed-off-by: gazarenkov --- .../rhdh/default-config/deployment.yaml | 16 +------- integration_tests/rhdh-config_test.go | 38 +++++++++---------- pkg/utils/pod-mutator.go | 2 +- 3 files changed, 20 insertions(+), 36 deletions(-) diff --git a/config/profile/rhdh/default-config/deployment.yaml b/config/profile/rhdh/default-config/deployment.yaml index 608e6221..142a38a6 100644 --- a/config/profile/rhdh/default-config/deployment.yaml +++ b/config/profile/rhdh/default-config/deployment.yaml @@ -19,15 +19,6 @@ spec: #securityContext: # fsGroup: 1001 volumes: - - ephemeral: - volumeClaimTemplate: - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 2Gi - name: dynamic-plugins-root - name: dynamic-plugins-npmrc secret: defaultMode: 420 @@ -44,7 +35,7 @@ spec: - name: install-dynamic-plugins command: - ./install-dynamic-plugins.sh - - /dynamic-plugins-root + - /opt/app-root/src/dynamic-plugins-root # image will be replaced by the value of the `RELATED_IMAGE_backstage` env var, if set image: quay.io/rhdh/rhdh-hub-rhel9:next imagePullPolicy: IfNotPresent @@ -60,8 +51,6 @@ spec: - name: NPM_CONFIG_USERCONFIG value: /opt/app-root/src/.npmrc.dynamic-plugins volumeMounts: - - mountPath: /dynamic-plugins-root - name: dynamic-plugins-root - mountPath: /opt/app-root/src/.npmrc.dynamic-plugins name: dynamic-plugins-npmrc readOnly: true @@ -142,9 +131,6 @@ spec: env: - name: APP_CONFIG_backend_listen_port value: "7007" - volumeMounts: - - mountPath: /opt/app-root/src/dynamic-plugins-root - name: dynamic-plugins-root resources: requests: cpu: 250m diff --git a/integration_tests/rhdh-config_test.go b/integration_tests/rhdh-config_test.go index cb307f50..70091c4c 100644 --- a/integration_tests/rhdh-config_test.go +++ b/integration_tests/rhdh-config_test.go @@ -45,18 +45,18 @@ var _ = When("create default rhdh", func() { _, initCont := model.DynamicPluginsInitContainer(deploy.Spec.Template.Spec.InitContainers) g.Expect(initCont.VolumeMounts).To(HaveLen(5)) - g.Expect(initCont.VolumeMounts[0].MountPath).To(Equal("/dynamic-plugins-root")) - g.Expect(initCont.VolumeMounts[0].SubPath).To(BeEmpty()) - g.Expect(initCont.VolumeMounts[1].MountPath).To(Equal("/opt/app-root/src/.npmrc.dynamic-plugins")) - g.Expect(initCont.VolumeMounts[1].SubPath).To(Equal(".npmrc")) - g.Expect(initCont.VolumeMounts[2].MountPath).To(Equal("/opt/app-root/src/.config/containers")) - g.Expect(initCont.VolumeMounts[3].MountPath).To(Equal("/opt/app-root/src/.npm/_cacache")) - g.Expect(initCont.VolumeMounts[3].SubPath).To(BeEmpty()) - g.Expect(initCont.VolumeMounts[4].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins.yaml")) - g.Expect(initCont.VolumeMounts[4].SubPath).To(Equal("dynamic-plugins.yaml")) - g.Expect(initCont.VolumeMounts[4].Name). + g.Expect(initCont.VolumeMounts[4].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins-root")) + g.Expect(initCont.VolumeMounts[4].SubPath).To(BeEmpty()) + g.Expect(initCont.VolumeMounts[0].MountPath).To(Equal("/opt/app-root/src/.npmrc.dynamic-plugins")) + g.Expect(initCont.VolumeMounts[0].SubPath).To(Equal(".npmrc")) + g.Expect(initCont.VolumeMounts[1].MountPath).To(Equal("/opt/app-root/src/.config/containers")) + g.Expect(initCont.VolumeMounts[2].MountPath).To(Equal("/opt/app-root/src/.npm/_cacache")) + g.Expect(initCont.VolumeMounts[2].SubPath).To(BeEmpty()) + g.Expect(initCont.VolumeMounts[3].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins.yaml")) + g.Expect(initCont.VolumeMounts[3].SubPath).To(Equal("dynamic-plugins.yaml")) + g.Expect(initCont.VolumeMounts[3].Name). To(Equal(utils.GenerateVolumeNameFromCmOrSecret(model.DynamicPluginsDefaultName(backstageName)))) - g.Expect(initCont.VolumeMounts[4].SubPath).To(Equal(model.DynamicPluginsFile)) + g.Expect(initCont.VolumeMounts[3].SubPath).To(Equal(model.DynamicPluginsFile)) g.Expect(initCont.Env[0].Name).To(Equal("NPM_CONFIG_USERCONFIG")) g.Expect(initCont.Env[0].Value).To(Equal("/opt/app-root/src/.npmrc.dynamic-plugins")) @@ -71,10 +71,10 @@ var _ = When("create default rhdh", func() { g.Expect(mainCont.Args[3]).To(Equal("/opt/app-root/src/default.app-config.yaml")) g.Expect(mainCont.VolumeMounts).To(HaveLen(2)) - g.Expect(mainCont.VolumeMounts[0].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins-root")) - g.Expect(mainCont.VolumeMounts[0].SubPath).To(BeEmpty()) - g.Expect(mainCont.VolumeMounts[1].MountPath).To(Equal("/opt/app-root/src/default.app-config.yaml")) - g.Expect(mainCont.VolumeMounts[1].SubPath).To(Equal("default.app-config.yaml")) + g.Expect(mainCont.VolumeMounts[1].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins-root")) + g.Expect(mainCont.VolumeMounts[1].SubPath).To(BeEmpty()) + g.Expect(mainCont.VolumeMounts[0].MountPath).To(Equal("/opt/app-root/src/default.app-config.yaml")) + g.Expect(mainCont.VolumeMounts[0].SubPath).To(Equal("default.app-config.yaml")) //g.Expect(mainCont.VolumeMounts[3].MountPath).To(Equal(fmt.Sprintf("/opt/app-root/src/backstage-%s-dynamic-plugins", backstageName))) @@ -85,10 +85,7 @@ var _ = When("create default rhdh", func() { It("replaces dynamic-plugins-root volume", func() { - // This test relies on the fact that RHDH default config for deployment contains - // volumes: - // - ephemeral: - // name: dynamic-plugins-root + // This test relies on the fact that RHDH default deployment config contains dynamic-plugins-root volume // and check if it replaced with one defined in spec.deployment ctx := context.Background() @@ -99,6 +96,7 @@ var _ = When("create default rhdh", func() { Expect(err).To(Not(HaveOccurred())) backstageName := createAndReconcileBackstage(ctx, ns, bs2.Spec, "") + volumeName := model.PvcsName(backstageName, "dynamic-plugins-root") Eventually(func(g Gomega) { By("getting the Deployment ") @@ -109,7 +107,7 @@ var _ = When("create default rhdh", func() { var bsvolume *corev1.Volume for _, v := range deploy.Spec.Template.Spec.Volumes { - if v.Name == "dynamic-plugins-root" { + if v.Name == volumeName { bsvolume = &v break } diff --git a/pkg/utils/pod-mutator.go b/pkg/utils/pod-mutator.go index 79300953..fddde456 100644 --- a/pkg/utils/pod-mutator.go +++ b/pkg/utils/pod-mutator.go @@ -29,7 +29,7 @@ type PodMutator struct { // mountPath - mount path, default one or as it specified in BackstageCR.spec.Application.AppConfig|ExtraFiles // fileName - file name which fits one of the object's key, otherwise error will be returned. // withSubPath - if true will be mounted file-by-file with subpath, otherwise will be mounted as directory to specified path -// data - key:value pairs from the object. should be specified if fileName specified +// dataKeys - keys for ConfigMap/Secret data func MountFilesFrom(podSpec *corev1.PodSpec, container *corev1.Container, kind ObjectKind, objectName, mountPath, fileName string, withSubPath bool, dataKeys []string) { volName := GenerateVolumeNameFromCmOrSecret(objectName) From d1c33def6bbcf689e0c0aac2bd1d058e69b39844 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Wed, 18 Dec 2024 11:04:47 +0200 Subject: [PATCH 3/7] regenerate rhdh default configmap Signed-off-by: gazarenkov --- ...backstage-operator.clusterserviceversion.yaml | 2 +- .../rhdh-default-config_v1_configmap.yaml | 16 +--------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml index 1f3a115d..b5f63bdc 100644 --- a/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml @@ -39,7 +39,7 @@ metadata: categories: Developer Tools certified: "true" containerImage: registry-proxy.engineering.redhat.com/rh-osbs/rhdh-rhdh-rhel9-operator:1.3 - createdAt: "2024-12-13T14:46:16Z" + createdAt: "2024-12-18T09:01:35Z" description: Red Hat Developer Hub is a Red Hat supported version of Backstage. It comes with pre-built plug-ins and configuration settings, supports use of an external database, and can help streamline the process of setting up a self-managed diff --git a/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml b/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml index 41284e85..6d356849 100644 --- a/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml +++ b/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml @@ -174,15 +174,6 @@ data: #securityContext: # fsGroup: 1001 volumes: - - ephemeral: - volumeClaimTemplate: - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 2Gi - name: dynamic-plugins-root - name: dynamic-plugins-npmrc secret: defaultMode: 420 @@ -199,7 +190,7 @@ data: - name: install-dynamic-plugins command: - ./install-dynamic-plugins.sh - - /dynamic-plugins-root + - /opt/app-root/src/dynamic-plugins-root # image will be replaced by the value of the `RELATED_IMAGE_backstage` env var, if set image: quay.io/rhdh/rhdh-hub-rhel9:next imagePullPolicy: IfNotPresent @@ -215,8 +206,6 @@ data: - name: NPM_CONFIG_USERCONFIG value: /opt/app-root/src/.npmrc.dynamic-plugins volumeMounts: - - mountPath: /dynamic-plugins-root - name: dynamic-plugins-root - mountPath: /opt/app-root/src/.npmrc.dynamic-plugins name: dynamic-plugins-npmrc readOnly: true @@ -297,9 +286,6 @@ data: env: - name: APP_CONFIG_backend_listen_port value: "7007" - volumeMounts: - - mountPath: /opt/app-root/src/dynamic-plugins-root - name: dynamic-plugins-root resources: requests: cpu: 250m From 1faa96eae0f77133df8d708142fc53805beae131 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Thu, 19 Dec 2024 19:20:53 +0200 Subject: [PATCH 4/7] rollback rhdh config Signed-off-by: gazarenkov --- ...kstage-operator.clusterserviceversion.yaml | 2 +- .../rhdh-default-config_v1_configmap.yaml | 18 ++++++++- .../rhdh/default-config/deployment.yaml | 18 ++++++++- integration_tests/rhdh-config_test.go | 38 ++++++++++--------- 4 files changed, 53 insertions(+), 23 deletions(-) diff --git a/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml b/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml index b5f63bdc..58b5e431 100644 --- a/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml +++ b/bundle/rhdh/manifests/backstage-operator.clusterserviceversion.yaml @@ -39,7 +39,7 @@ metadata: categories: Developer Tools certified: "true" containerImage: registry-proxy.engineering.redhat.com/rh-osbs/rhdh-rhdh-rhel9-operator:1.3 - createdAt: "2024-12-18T09:01:35Z" + createdAt: "2024-12-19T17:20:07Z" description: Red Hat Developer Hub is a Red Hat supported version of Backstage. It comes with pre-built plug-ins and configuration settings, supports use of an external database, and can help streamline the process of setting up a self-managed diff --git a/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml b/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml index 6d356849..3e247e6d 100644 --- a/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml +++ b/bundle/rhdh/manifests/rhdh-default-config_v1_configmap.yaml @@ -174,6 +174,15 @@ data: #securityContext: # fsGroup: 1001 volumes: + - ephemeral: + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + name: dynamic-plugins-root - name: dynamic-plugins-npmrc secret: defaultMode: 420 @@ -190,7 +199,7 @@ data: - name: install-dynamic-plugins command: - ./install-dynamic-plugins.sh - - /opt/app-root/src/dynamic-plugins-root + - /dynamic-plugins-root # image will be replaced by the value of the `RELATED_IMAGE_backstage` env var, if set image: quay.io/rhdh/rhdh-hub-rhel9:next imagePullPolicy: IfNotPresent @@ -201,11 +210,13 @@ data: type: RuntimeDefault capabilities: drop: - - ALL + - ALL env: - name: NPM_CONFIG_USERCONFIG value: /opt/app-root/src/.npmrc.dynamic-plugins volumeMounts: + - mountPath: /dynamic-plugins-root + name: dynamic-plugins-root - mountPath: /opt/app-root/src/.npmrc.dynamic-plugins name: dynamic-plugins-npmrc readOnly: true @@ -286,6 +297,9 @@ data: env: - name: APP_CONFIG_backend_listen_port value: "7007" + volumeMounts: + - mountPath: /opt/app-root/src/dynamic-plugins-root + name: dynamic-plugins-root resources: requests: cpu: 250m diff --git a/config/profile/rhdh/default-config/deployment.yaml b/config/profile/rhdh/default-config/deployment.yaml index 142a38a6..c9c54d2c 100644 --- a/config/profile/rhdh/default-config/deployment.yaml +++ b/config/profile/rhdh/default-config/deployment.yaml @@ -19,6 +19,15 @@ spec: #securityContext: # fsGroup: 1001 volumes: + - ephemeral: + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + name: dynamic-plugins-root - name: dynamic-plugins-npmrc secret: defaultMode: 420 @@ -35,7 +44,7 @@ spec: - name: install-dynamic-plugins command: - ./install-dynamic-plugins.sh - - /opt/app-root/src/dynamic-plugins-root + - /dynamic-plugins-root # image will be replaced by the value of the `RELATED_IMAGE_backstage` env var, if set image: quay.io/rhdh/rhdh-hub-rhel9:next imagePullPolicy: IfNotPresent @@ -46,11 +55,13 @@ spec: type: RuntimeDefault capabilities: drop: - - ALL + - ALL env: - name: NPM_CONFIG_USERCONFIG value: /opt/app-root/src/.npmrc.dynamic-plugins volumeMounts: + - mountPath: /dynamic-plugins-root + name: dynamic-plugins-root - mountPath: /opt/app-root/src/.npmrc.dynamic-plugins name: dynamic-plugins-npmrc readOnly: true @@ -131,6 +142,9 @@ spec: env: - name: APP_CONFIG_backend_listen_port value: "7007" + volumeMounts: + - mountPath: /opt/app-root/src/dynamic-plugins-root + name: dynamic-plugins-root resources: requests: cpu: 250m diff --git a/integration_tests/rhdh-config_test.go b/integration_tests/rhdh-config_test.go index 70091c4c..cb307f50 100644 --- a/integration_tests/rhdh-config_test.go +++ b/integration_tests/rhdh-config_test.go @@ -45,18 +45,18 @@ var _ = When("create default rhdh", func() { _, initCont := model.DynamicPluginsInitContainer(deploy.Spec.Template.Spec.InitContainers) g.Expect(initCont.VolumeMounts).To(HaveLen(5)) - g.Expect(initCont.VolumeMounts[4].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins-root")) - g.Expect(initCont.VolumeMounts[4].SubPath).To(BeEmpty()) - g.Expect(initCont.VolumeMounts[0].MountPath).To(Equal("/opt/app-root/src/.npmrc.dynamic-plugins")) - g.Expect(initCont.VolumeMounts[0].SubPath).To(Equal(".npmrc")) - g.Expect(initCont.VolumeMounts[1].MountPath).To(Equal("/opt/app-root/src/.config/containers")) - g.Expect(initCont.VolumeMounts[2].MountPath).To(Equal("/opt/app-root/src/.npm/_cacache")) - g.Expect(initCont.VolumeMounts[2].SubPath).To(BeEmpty()) - g.Expect(initCont.VolumeMounts[3].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins.yaml")) - g.Expect(initCont.VolumeMounts[3].SubPath).To(Equal("dynamic-plugins.yaml")) - g.Expect(initCont.VolumeMounts[3].Name). + g.Expect(initCont.VolumeMounts[0].MountPath).To(Equal("/dynamic-plugins-root")) + g.Expect(initCont.VolumeMounts[0].SubPath).To(BeEmpty()) + g.Expect(initCont.VolumeMounts[1].MountPath).To(Equal("/opt/app-root/src/.npmrc.dynamic-plugins")) + g.Expect(initCont.VolumeMounts[1].SubPath).To(Equal(".npmrc")) + g.Expect(initCont.VolumeMounts[2].MountPath).To(Equal("/opt/app-root/src/.config/containers")) + g.Expect(initCont.VolumeMounts[3].MountPath).To(Equal("/opt/app-root/src/.npm/_cacache")) + g.Expect(initCont.VolumeMounts[3].SubPath).To(BeEmpty()) + g.Expect(initCont.VolumeMounts[4].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins.yaml")) + g.Expect(initCont.VolumeMounts[4].SubPath).To(Equal("dynamic-plugins.yaml")) + g.Expect(initCont.VolumeMounts[4].Name). To(Equal(utils.GenerateVolumeNameFromCmOrSecret(model.DynamicPluginsDefaultName(backstageName)))) - g.Expect(initCont.VolumeMounts[3].SubPath).To(Equal(model.DynamicPluginsFile)) + g.Expect(initCont.VolumeMounts[4].SubPath).To(Equal(model.DynamicPluginsFile)) g.Expect(initCont.Env[0].Name).To(Equal("NPM_CONFIG_USERCONFIG")) g.Expect(initCont.Env[0].Value).To(Equal("/opt/app-root/src/.npmrc.dynamic-plugins")) @@ -71,10 +71,10 @@ var _ = When("create default rhdh", func() { g.Expect(mainCont.Args[3]).To(Equal("/opt/app-root/src/default.app-config.yaml")) g.Expect(mainCont.VolumeMounts).To(HaveLen(2)) - g.Expect(mainCont.VolumeMounts[1].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins-root")) - g.Expect(mainCont.VolumeMounts[1].SubPath).To(BeEmpty()) - g.Expect(mainCont.VolumeMounts[0].MountPath).To(Equal("/opt/app-root/src/default.app-config.yaml")) - g.Expect(mainCont.VolumeMounts[0].SubPath).To(Equal("default.app-config.yaml")) + g.Expect(mainCont.VolumeMounts[0].MountPath).To(Equal("/opt/app-root/src/dynamic-plugins-root")) + g.Expect(mainCont.VolumeMounts[0].SubPath).To(BeEmpty()) + g.Expect(mainCont.VolumeMounts[1].MountPath).To(Equal("/opt/app-root/src/default.app-config.yaml")) + g.Expect(mainCont.VolumeMounts[1].SubPath).To(Equal("default.app-config.yaml")) //g.Expect(mainCont.VolumeMounts[3].MountPath).To(Equal(fmt.Sprintf("/opt/app-root/src/backstage-%s-dynamic-plugins", backstageName))) @@ -85,7 +85,10 @@ var _ = When("create default rhdh", func() { It("replaces dynamic-plugins-root volume", func() { - // This test relies on the fact that RHDH default deployment config contains dynamic-plugins-root volume + // This test relies on the fact that RHDH default config for deployment contains + // volumes: + // - ephemeral: + // name: dynamic-plugins-root // and check if it replaced with one defined in spec.deployment ctx := context.Background() @@ -96,7 +99,6 @@ var _ = When("create default rhdh", func() { Expect(err).To(Not(HaveOccurred())) backstageName := createAndReconcileBackstage(ctx, ns, bs2.Spec, "") - volumeName := model.PvcsName(backstageName, "dynamic-plugins-root") Eventually(func(g Gomega) { By("getting the Deployment ") @@ -107,7 +109,7 @@ var _ = When("create default rhdh", func() { var bsvolume *corev1.Volume for _, v := range deploy.Spec.Template.Spec.Volumes { - if v.Name == volumeName { + if v.Name == "dynamic-plugins-root" { bsvolume = &v break } From 5516728390d354c6e41aeddf5399adc3d43514be Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Mon, 23 Dec 2024 14:00:21 +0200 Subject: [PATCH 5/7] docs refinement Signed-off-by: gazarenkov --- docs/configuration.md | 44 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 99619990..d995f9c2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,21 +12,21 @@ The Default Configuration defines the structure of all Backstage instances withi ### Default Configuration Files -| Key/File Name | Object Kind | Object Name | Mandatory | Multi| Version | Notes | -|-----------------------------|------------------------------|-------------------------------------|--------------|-----|---------|----------------------------------------------------------| -| deployment.yaml | appsv1.Deployment | backstage- | Yes | No | >=0.1.x | Backstage deployment | -| service.yaml | corev1.Service | backstage- | Yes | No | >=0.1.x | Backstage Service | -| db-statefulset.yaml | appsv1.StatefulSet | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL StatefulSet | -| db-service.yaml | corev1.Service | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL Service | -| db-secret.yaml | corev1.Secret | backstage-psql-secret- | For local DB | No | >=0.1.x | Secret to connect Backstage to PGSQL | -| route.yaml | openshift.Route | backstage- | No (for OCP) | No | >=0.1.x | Route exposing Backstage service | -| app-config.yaml | corev1.ConfigMap | backstage-appconfig- | No | No | >=0.2.x | Backstage app-config.yaml | -| configmap-files.yaml | corev1.ConfigMap | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from configMap | -| configmap-envs.yaml | corev1.ConfigMap | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from ConfigMap | -| secret-files.yaml | corev1.Secret | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from Secret | -| secret-envs.yaml | corev1.Secret | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from Secret | -| dynamic-plugins.yaml | corev1.ConfigMap | backstage-dynamic-plugins- | No | No | >=0.2.x | Dynamic plugins configuration | -| pvcs.yaml | corev1.PersistentVolumeClaim | backstage-<cr-name>-<pvc-name> | No | Yes | >=0.4.x | List of PVC objects to be mounted to Backstage container | +| Key/File Name | Object Kind | Object Name | Mandatory | Multi| Version | Notes | +|----------------------|------------------------------|--------------------------------------------|--------------|-----|---------|----------------------------------------------------------| +| deployment.yaml | appsv1.Deployment | backstage- | Yes | No | >=0.1.x | Backstage deployment | +| service.yaml | corev1.Service | backstage- | Yes | No | >=0.1.x | Backstage Service | +| db-statefulset.yaml | appsv1.StatefulSet | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL StatefulSet | +| db-service.yaml | corev1.Service | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL Service | +| db-secret.yaml | corev1.Secret | backstage-psql-secret- | For local DB | No | >=0.1.x | Secret to connect Backstage to PGSQL | +| route.yaml | openshift.Route | backstage- | No (for OCP) | No | >=0.1.x | Route exposing Backstage service | +| app-config.yaml | corev1.ConfigMap | backstage-appconfig- | No | No | >=0.2.x | Backstage app-config.yaml | +| configmap-files.yaml | corev1.ConfigMap | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from configMap | +| configmap-envs.yaml | corev1.ConfigMap | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from ConfigMap | +| secret-files.yaml | corev1.Secret | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from Secret | +| secret-envs.yaml | corev1.Secret | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from Secret | +| dynamic-plugins.yaml | corev1.ConfigMap | backstage-dynamic-plugins- | No | No | >=0.2.x | Dynamic plugins configuration | +| pvcs.yaml | corev1.PersistentVolumeClaim | backstage-<cr-name>-<pvc-name> | No | Yes | >=0.4.x | List of PVC objects to be mounted to Backstage container | **Meanings of "Mandatory" Column:** - **Yes** - Must be configured; deployment will fail otherwise. @@ -40,10 +40,11 @@ You can see examples of default configurations as part of the [Operator Profiles Some objects, such as: app-config, configmap-files, secret-files, dynamic-plugins, pvcs, are mounted to the Backstage Container as files or directories. Default mount path is Container's WorkingDir, if not defined it falls to "/opt/app-root/src". -#### Object annotation for mounting a volume to a specific path +#### Object annotation for mounting a PVC volume to a specific path -Using **rhdh.redhat.com/mount-path** annotation it is possible to define the directory where **PersistentVolumeClaim** object will be mounted. +Use **rhdh.redhat.com/mount-path** annotation to configure mount path for **PersistentVolumeClaim** volume. +_**pvcs.yaml**_ ```yaml apiVersion: v1 kind: PersistentVolumeClaim @@ -56,23 +57,24 @@ metadata: In the example above the PVC called **myclaim** will be mounted to **/mount/path/from/annotation** directory -#### Object annotation for mounting a volume to specific container(s) +#### Object annotation for mounting a PVC volume to specific container(s) -Using **rhdh.redhat.com/containers** annotation it is possible to define the containers where **PersistentVolumeClaim** object will be mounted. +Use **rhdh.redhat.com/containers** annotation to configure containers where **PersistentVolumeClaim** volume will be mounted. Options: * No or empty annotation means the volume will be mounted to the Backstage container only -* \* (asterik) means the volume will be mounted to all the containers +* \* (asterisk) means the volume will be mounted to all the containers * Otherwise comma separated names of container will be used +_**pvcs.yaml**_ ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim annotations: - rhdh.redhat.com/mount-path: "init-dynamic-plugins,backstage-backend" + rhdh.redhat.com/containers: "init-dynamic-plugins,backstage-backend" ... ``` In the example above the PVC called **myclaim** will be mounted to **init-dynamic-plugins** and **backstage-backend** containers From 60d241e2fdecc06a0832a0f40fef655707707747 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Mon, 23 Dec 2024 15:50:11 +0200 Subject: [PATCH 6/7] docs refinement Signed-off-by: gazarenkov --- docs/configuration.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index d995f9c2..fcc3a07d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,21 +12,21 @@ The Default Configuration defines the structure of all Backstage instances withi ### Default Configuration Files -| Key/File Name | Object Kind | Object Name | Mandatory | Multi| Version | Notes | -|----------------------|------------------------------|--------------------------------------------|--------------|-----|---------|----------------------------------------------------------| -| deployment.yaml | appsv1.Deployment | backstage- | Yes | No | >=0.1.x | Backstage deployment | -| service.yaml | corev1.Service | backstage- | Yes | No | >=0.1.x | Backstage Service | -| db-statefulset.yaml | appsv1.StatefulSet | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL StatefulSet | -| db-service.yaml | corev1.Service | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL Service | -| db-secret.yaml | corev1.Secret | backstage-psql-secret- | For local DB | No | >=0.1.x | Secret to connect Backstage to PGSQL | -| route.yaml | openshift.Route | backstage- | No (for OCP) | No | >=0.1.x | Route exposing Backstage service | -| app-config.yaml | corev1.ConfigMap | backstage-appconfig- | No | No | >=0.2.x | Backstage app-config.yaml | -| configmap-files.yaml | corev1.ConfigMap | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from configMap | -| configmap-envs.yaml | corev1.ConfigMap | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from ConfigMap | -| secret-files.yaml | corev1.Secret | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from Secret | -| secret-envs.yaml | corev1.Secret | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from Secret | -| dynamic-plugins.yaml | corev1.ConfigMap | backstage-dynamic-plugins- | No | No | >=0.2.x | Dynamic plugins configuration | -| pvcs.yaml | corev1.PersistentVolumeClaim | backstage-<cr-name>-<pvc-name> | No | Yes | >=0.4.x | List of PVC objects to be mounted to Backstage container | +| Key/File Name | Object Kind | Object Name | Mandatory | Multi| Version | Notes | +|----------------------|------------------------------|--------------------------------------------|--------------|-----|---------|-------------------------------------------------| +| deployment.yaml | appsv1.Deployment | backstage- | Yes | No | >=0.1.x | Backstage deployment | +| service.yaml | corev1.Service | backstage- | Yes | No | >=0.1.x | Backstage Service | +| db-statefulset.yaml | appsv1.StatefulSet | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL StatefulSet | +| db-service.yaml | corev1.Service | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL Service | +| db-secret.yaml | corev1.Secret | backstage-psql-secret- | For local DB | No | >=0.1.x | Secret to connect Backstage to PGSQL | +| route.yaml | openshift.Route | backstage- | No (for OCP) | No | >=0.1.x | Route exposing Backstage service | +| app-config.yaml | corev1.ConfigMap | backstage-appconfig- | No | No | >=0.2.x | Backstage app-config.yaml | +| configmap-files.yaml | corev1.ConfigMap | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from configMap | +| configmap-envs.yaml | corev1.ConfigMap | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from ConfigMap | +| secret-files.yaml | corev1.Secret | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from Secret | +| secret-envs.yaml | corev1.Secret | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from Secret | +| dynamic-plugins.yaml | corev1.ConfigMap | backstage-dynamic-plugins- | No | No | >=0.2.x | Dynamic plugins configuration | +| pvcs.yaml | corev1.PersistentVolumeClaim | backstage-<cr-name>-<pvc-name> | No | Yes | >=0.4.x | List of PVC objects to be mounted to containers | **Meanings of "Mandatory" Column:** - **Yes** - Must be configured; deployment will fail otherwise. @@ -65,7 +65,7 @@ Options: * No or empty annotation means the volume will be mounted to the Backstage container only * \* (asterisk) means the volume will be mounted to all the containers -* Otherwise comma separated names of container will be used +* Otherwise, container names separated by commas will be used _**pvcs.yaml**_ ```yaml From 703462f80c80db88d6941a583c66e009dd3757d1 Mon Sep 17 00:00:00 2001 From: gazarenkov Date: Mon, 30 Dec 2024 13:10:00 +0200 Subject: [PATCH 7/7] add default multi-secret and annotations Signed-off-by: gazarenkov --- docs/configuration.md | 89 +++++++++----- pkg/model/appconfig.go | 14 +-- pkg/model/appconfig_test.go | 1 - pkg/model/configmapenvs.go | 4 +- pkg/model/configmapfiles.go | 4 +- pkg/model/db-statefulset.go | 12 +- pkg/model/deployment.go | 146 +++++++++++++++++++++-- pkg/model/dynamic-plugins.go | 8 +- pkg/model/pvcs.go | 28 +---- pkg/model/secretenvs.go | 57 ++++++--- pkg/model/secretenvs_test.go | 36 +++++- pkg/model/secretfiles.go | 47 ++++++-- pkg/model/secretfiles_test.go | 37 +++++- pkg/model/testdata/raw-multi-secret.yaml | 29 +++++ pkg/utils/pod-mutator.go | 109 ----------------- pkg/utils/utils.go | 8 +- pkg/utils/utils_test.go | 4 +- 17 files changed, 411 insertions(+), 222 deletions(-) create mode 100644 pkg/model/testdata/raw-multi-secret.yaml diff --git a/docs/configuration.md b/docs/configuration.md index fcc3a07d..0bc50854 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,21 +12,21 @@ The Default Configuration defines the structure of all Backstage instances withi ### Default Configuration Files -| Key/File Name | Object Kind | Object Name | Mandatory | Multi| Version | Notes | -|----------------------|------------------------------|--------------------------------------------|--------------|-----|---------|-------------------------------------------------| -| deployment.yaml | appsv1.Deployment | backstage- | Yes | No | >=0.1.x | Backstage deployment | -| service.yaml | corev1.Service | backstage- | Yes | No | >=0.1.x | Backstage Service | -| db-statefulset.yaml | appsv1.StatefulSet | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL StatefulSet | -| db-service.yaml | corev1.Service | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL Service | -| db-secret.yaml | corev1.Secret | backstage-psql-secret- | For local DB | No | >=0.1.x | Secret to connect Backstage to PGSQL | -| route.yaml | openshift.Route | backstage- | No (for OCP) | No | >=0.1.x | Route exposing Backstage service | -| app-config.yaml | corev1.ConfigMap | backstage-appconfig- | No | No | >=0.2.x | Backstage app-config.yaml | -| configmap-files.yaml | corev1.ConfigMap | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from configMap | -| configmap-envs.yaml | corev1.ConfigMap | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from ConfigMap | -| secret-files.yaml | corev1.Secret | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from Secret | -| secret-envs.yaml | corev1.Secret | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from Secret | -| dynamic-plugins.yaml | corev1.ConfigMap | backstage-dynamic-plugins- | No | No | >=0.2.x | Dynamic plugins configuration | -| pvcs.yaml | corev1.PersistentVolumeClaim | backstage-<cr-name>-<pvc-name> | No | Yes | >=0.4.x | List of PVC objects to be mounted to containers | +| Key/File Name | Object Kind | Object Name | Mandatory | Multi | Version | Notes | +|----------------------|--------------------------------|--------------------------------------------|--------------|-------|---------|-------------------------------------------------| +| deployment.yaml | appsv1.Deployment | backstage- | Yes | No | >=0.1.x | Backstage deployment | +| service.yaml | corev1.Service | backstage- | Yes | No | >=0.1.x | Backstage Service | +| db-statefulset.yaml | appsv1.StatefulSet | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL StatefulSet | +| db-service.yaml | corev1.Service | backstage-psql- | For local DB | No | >=0.1.x | PostgreSQL Service | +| db-secret.yaml | corev1.Secret | backstage-psql-secret- | For local DB | No | >=0.1.x | Secret to connect Backstage to PGSQL | +| route.yaml | openshift.Route | backstage- | No (for OCP) | No | >=0.1.x | Route exposing Backstage service | +| app-config.yaml | corev1.ConfigMap | backstage-appconfig- | No | No | >=0.2.x | Backstage app-config.yaml | +| configmap-files.yaml | corev1.ConfigMap | backstage-files- | No | No | >=0.2.x | Backstage config file inclusions from configMap | +| configmap-envs.yaml | corev1.ConfigMap | backstage-envs- | No | No | >=0.2.x | Backstage environment variables from ConfigMap | +| secret-files.yaml | []corev1.Secret | backstage-files- | No | Yes | >=0.2.x | Backstage config file inclusions from Secret | +| secret-envs.yaml | []corev1.Secret | backstage-envs- | No | Yes | >=0.2.x | Backstage environment variables from Secret | +| dynamic-plugins.yaml | corev1.ConfigMap | backstage-dynamic-plugins- | No | No | >=0.2.x | Dynamic plugins configuration | +| pvcs.yaml | []corev1.PersistentVolumeClaim | backstage-<cr-name>-<pvc-name> | No | Yes | >=0.4.x | List of PVC objects to be mounted to containers | **Meanings of "Mandatory" Column:** - **Yes** - Must be configured; deployment will fail otherwise. @@ -36,13 +36,22 @@ The Default Configuration defines the structure of all Backstage instances withi You can see examples of default configurations as part of the [Operator Profiles](../config/profile) in the **default-config** directory. -#### Default mount path +### Default mount path Some objects, such as: app-config, configmap-files, secret-files, dynamic-plugins, pvcs, are mounted to the Backstage Container as files or directories. Default mount path is Container's WorkingDir, if not defined it falls to "/opt/app-root/src". -#### Object annotation for mounting a PVC volume to a specific path +### Annotations -Use **rhdh.redhat.com/mount-path** annotation to configure mount path for **PersistentVolumeClaim** volume. +We use annotations to configure some objects. The following annotations are supported: + +#### rhdh.redhat.com/mount-path to configure mount path. + +If specified, the object will be mounted to the specified path, otherwise [Default mount path](#default-mount-path) will ve used. +It is possible to specify relative path, which will be appended to the default mount path. + +Supported objects: **pvcs, secret-files**. + +Examples: _**pvcs.yaml**_ ```yaml @@ -57,16 +66,30 @@ metadata: In the example above the PVC called **myclaim** will be mounted to **/mount/path/from/annotation** directory -#### Object annotation for mounting a PVC volume to specific container(s) +_**secret-files.yaml**_ +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: mysecret + annotations: + rhdh.redhat.com/mount-path: /mount/path/from/annotation +... +``` +In the example above the Secret called **mysecret** will be mounted to **/mount/path/from/annotation** directory + +#### rhdh.redhat.com/containers for mounting volume to specific container(s) -Use **rhdh.redhat.com/containers** annotation to configure containers where **PersistentVolumeClaim** volume will be mounted. +Supported objects: **pvcs, secret-files, secret-envs**. Options: -* No or empty annotation means the volume will be mounted to the Backstage container only -* \* (asterisk) means the volume will be mounted to all the containers +* No or empty annotation: the volume will be mounted to the Backstage container only +* \* (asterisk): the volume will be mounted to all the containers * Otherwise, container names separated by commas will be used +Examples: + _**pvcs.yaml**_ ```yaml apiVersion: v1 @@ -79,6 +102,18 @@ metadata: ``` In the example above the PVC called **myclaim** will be mounted to **init-dynamic-plugins** and **backstage-backend** containers +_**secret-envs.yaml**_ + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: mysecret + annotations: + rhdh.redhat.com/containers: "*" +... +``` +In the example above the PVC called **myclaim** will be mounted to all the containers ### Metadata Generation @@ -138,7 +173,7 @@ spec: The desired state of resources created by the Backstage Operator is defined in the Backstage Custom Resource Spec. Here’s an example of a simple Backstage CR: ```yaml -apiVersion: rhdh.redhat.com/v1alpha2 +apiVersion: rhdh.redhat.com/v1alpha3 kind: Backstage metadata: name: mybackstage @@ -160,7 +195,7 @@ For API version **v1alpha2** (Operator version **0.3.x**), the Backstage CR Spec * [application](#application-configuration) * [deployment](#deployment-configuration) -* [database](#local-database-configuration) +* [database](#database-configuration) * [rawRuntimeConfig](#raw-configuration) ### Application Configuration @@ -209,7 +244,7 @@ The ConfigMap key/value defines the file name and content, and this app-config w **Note**: It is possible to define several **app-config** files inside one ConfigMap (even if there are no visible reasons for it) but since it is a Map, the order of how they are applied is not guaranteed. On the other hand, Backstage application merges the chain of **app-config** files from first to last, so order is important. Taking this into account, keeping several **app-config** files inside one ConfigMap is **NOT recommended**. For this case consider defining several one-entry ConfigMaps instead. -[Includes and Dynamic Data](https://backstage.io/docs/conf/writing/#includes-and-dynamic-data) (including [extra files](#extra-files) and [extra environment variables](#extra-env-variables)) support configuring additional ConfigMaps and Secrets. +[Includes and Dynamic Data](https://backstage.io/docs/conf/writing/#includes-and-dynamic-data) (including [extra files](#extra-files) and [extra environment variables](#extra-environment-variables)) support configuring additional ConfigMaps and Secrets. #### Extra Files @@ -417,7 +452,7 @@ MY_VAR = my-value #### Dynamic Plugins -The Operator can configure [Dynamic Plugins](https://github.com/janus-idp/backstage-showcase/blob/main/docs/dynamic-plugins.md). To support Dynamic Plugins, the Backstage deployment should contain a dedicated initContainer called **install-dynamic-plugins** (see [RHDH deployment.yaml](../config/manager/default-config/deployment.yaml)). To enable the Operator to configure Dynamic Plugins for a specific Backstage instance (CR), the user must create a ConfigMap with an entry called **dynamic-plugins.yaml**. +The Operator can configure [Dynamic Plugins](https://github.com/janus-idp/backstage-showcase/blob/main/docs/dynamic-plugins.md). To support Dynamic Plugins, the Backstage deployment should contain a dedicated initContainer called **install-dynamic-plugins** (see [RHDH deployment.yaml](../config/profile/rhdh/default-config/deployment.yaml)). To enable the Operator to configure Dynamic Plugins for a specific Backstage instance (CR), the user must create a ConfigMap with an entry called **dynamic-plugins.yaml**. For example, the **dynamic-plugins-config** ConfigMap contains a simple Dynamic Plugins configuration, which includes predefined default plugins in **dynamic-plugins.default.yaml** and the GitHub plugin provided in the package located at `./dynamic-plugins/dist/backstage-plugin-catalog-backend-module-github-dynamic`. @@ -543,7 +578,7 @@ This is dictated by the following configuration: ```yaml spec: database: - enableLocalDb: [true]|false + enableLocalDb: [true] or false ``` If local DB is enabled (which is simpler but not recommended for production), the Operator will: diff --git a/pkg/model/appconfig.go b/pkg/model/appconfig.go index a1a79bc3..75eff96a 100644 --- a/pkg/model/appconfig.go +++ b/pkg/model/appconfig.go @@ -5,8 +5,6 @@ import ( "golang.org/x/exp/maps" - appsv1 "k8s.io/api/apps/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "k8s.io/apimachinery/pkg/runtime" @@ -46,8 +44,8 @@ func addAppConfigsFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) error for _, specCm := range spec.Application.AppConfig.ConfigMaps { mp, wSubpath := model.backstageDeployment.mountPath(specCm.MountPath, specCm.Key, spec.Application.AppConfig.MountPath) - updatePodWithAppConfig(model.backstageDeployment.deployment, model.backstageDeployment.container(), specCm.Name, - mp, specCm.Key, wSubpath /*model.ExternalConfig.AppConfigs[specCm.Name].Data*/, model.ExternalConfig.AppConfigKeys[specCm.Name]) + updatePodWithAppConfig(model.backstageDeployment, model.backstageDeployment.container(), specCm.Name, + mp, specCm.Key, wSubpath, model.ExternalConfig.AppConfigKeys[specCm.Name]) } return nil } @@ -80,8 +78,8 @@ func (b *AppConfig) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, e } // implementation of RuntimeObject interface -func (b *AppConfig) updateAndValidate(m *BackstageModel, backstage bsv1.Backstage) error { - updatePodWithAppConfig(m.backstageDeployment.deployment, m.backstageDeployment.container(), b.ConfigMap.Name, m.backstageDeployment.defaultMountPath(), "", true, maps.Keys(b.ConfigMap.Data)) +func (b *AppConfig) updateAndValidate(m *BackstageModel, _ bsv1.Backstage) error { + updatePodWithAppConfig(m.backstageDeployment, m.backstageDeployment.container(), b.ConfigMap.Name, m.backstageDeployment.defaultMountPath(), "", true, maps.Keys(b.ConfigMap.Data)) return nil } @@ -91,8 +89,8 @@ func (b *AppConfig) setMetaInfo(backstage bsv1.Backstage, scheme *runtime.Scheme } // updatePodWithAppConfig contrubutes to Volumes, container.VolumeMounts and contaiter.Args -func updatePodWithAppConfig(deployment *appsv1.Deployment, container *corev1.Container, cmName, mountPath, key string, withSubPath bool, cmData []string) { - utils.MountFilesFrom(&deployment.Spec.Template.Spec, container, utils.ConfigMapObjectKind, +func updatePodWithAppConfig(bsd *BackstageDeployment, container *corev1.Container, cmName, mountPath, key string, withSubPath bool, cmData []string) { + bsd.mountFilesFrom([]string{container.Name}, ConfigMapObjectKind, cmName, mountPath, key, withSubPath, cmData) for _, file := range cmData { diff --git a/pkg/model/appconfig_test.go b/pkg/model/appconfig_test.go index 6d14d7f4..9e04f210 100644 --- a/pkg/model/appconfig_test.go +++ b/pkg/model/appconfig_test.go @@ -59,7 +59,6 @@ var ( func TestDefaultAppConfig(t *testing.T) { - //bs := simpleTestBackstage() bs := *appConfigTestBackstage.DeepCopy() testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("app-config.yaml", "raw-app-config.yaml") diff --git a/pkg/model/configmapenvs.go b/pkg/model/configmapenvs.go index b42313c1..04fd65f3 100644 --- a/pkg/model/configmapenvs.go +++ b/pkg/model/configmapenvs.go @@ -31,7 +31,7 @@ func addConfigMapEnvsFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) { } for _, specCm := range spec.Application.ExtraEnvs.ConfigMaps { - utils.AddEnvVarsFrom(model.backstageDeployment.container(), utils.ConfigMapObjectKind, specCm.Name, specCm.Key) + model.backstageDeployment.addEnvVarsFrom([]string{BackstageContainerName()}, ConfigMapObjectKind, specCm.Name, specCm.Key) } } @@ -63,7 +63,7 @@ func (p *ConfigMapEnvs) addToModel(model *BackstageModel, _ bsv1.Backstage) (boo // implementation of RuntimeObject interface func (p *ConfigMapEnvs) updateAndValidate(m *BackstageModel, _ bsv1.Backstage) error { - utils.AddEnvVarsFrom(m.backstageDeployment.container(), utils.ConfigMapObjectKind, + m.backstageDeployment.addEnvVarsFrom([]string{BackstageContainerName()}, ConfigMapObjectKind, p.ConfigMap.Name, "") return nil } diff --git a/pkg/model/configmapfiles.go b/pkg/model/configmapfiles.go index 38338395..a3820470 100644 --- a/pkg/model/configmapfiles.go +++ b/pkg/model/configmapfiles.go @@ -34,7 +34,7 @@ func addConfigMapFilesFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) e mp, wSubpath := model.backstageDeployment.mountPath(specCm.MountPath, specCm.Key, spec.Application.ExtraFiles.MountPath) keys := model.ExternalConfig.ExtraFileConfigMapKeys[specCm.Name].All() - utils.MountFilesFrom(&model.backstageDeployment.deployment.Spec.Template.Spec, model.backstageDeployment.container(), utils.ConfigMapObjectKind, + model.backstageDeployment.mountFilesFrom([]string{BackstageContainerName()}, ConfigMapObjectKind, specCm.Name, mp, specCm.Key, wSubpath, keys) } return nil @@ -70,7 +70,7 @@ func (p *ConfigMapFiles) addToModel(model *BackstageModel, _ bsv1.Backstage) (bo func (p *ConfigMapFiles) updateAndValidate(m *BackstageModel, _ bsv1.Backstage) error { keys := append(maps.Keys(p.ConfigMap.Data), maps.Keys(p.ConfigMap.BinaryData)...) - utils.MountFilesFrom(&m.backstageDeployment.deployment.Spec.Template.Spec, m.backstageDeployment.container(), utils.ConfigMapObjectKind, + m.backstageDeployment.mountFilesFrom([]string{BackstageContainerName()}, ConfigMapObjectKind, p.ConfigMap.Name, m.backstageDeployment.defaultMountPath(), "", true, keys) return nil diff --git a/pkg/model/db-statefulset.go b/pkg/model/db-statefulset.go index d43085d5..950b6fd3 100644 --- a/pkg/model/db-statefulset.go +++ b/pkg/model/db-statefulset.go @@ -89,9 +89,9 @@ func (b *DbStatefulSet) updateAndValidate(model *BackstageModel, backstage bsv1. } if backstage.Spec.IsAuthSecretSpecified() { - utils.SetDbSecretEnvVar(b.container(), backstage.Spec.Database.AuthSecretName) + b.setDbSecretEnvVar(b.container(), backstage.Spec.Database.AuthSecretName) } else if model.LocalDbSecret != nil { - utils.SetDbSecretEnvVar(b.container(), model.LocalDbSecret.secret.Name) + b.setDbSecretEnvVar(b.container(), model.LocalDbSecret.secret.Name) } return nil } @@ -112,3 +112,11 @@ func (b *DbStatefulSet) container() *corev1.Container { func (b *DbStatefulSet) podSpec() *corev1.PodSpec { return &b.statefulSet.Spec.Template.Spec } + +func (b *DbStatefulSet) setDbSecretEnvVar(container *corev1.Container, secretName string) { + //AddEnvVarsFrom(container, SecretObjectKind, secretName, "") + envFromSrc := corev1.EnvFromSource{} + envFromSrc.SecretRef = &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: secretName}} + container.EnvFrom = append(container.EnvFrom, envFromSrc) +} diff --git a/pkg/model/deployment.go b/pkg/model/deployment.go index ca8ec40b..d691cbef 100644 --- a/pkg/model/deployment.go +++ b/pkg/model/deployment.go @@ -27,12 +27,19 @@ import ( appsv1 "k8s.io/api/apps/v1" ) +const ( + SecretObjectKind = "Secret" + ConfigMapObjectKind = "ConfigMap" +) + const BackstageImageEnvVar = "RELATED_IMAGE_backstage" const DefaultMountDir = "/opt/app-root/src" const ExtConfigHashAnnotation = "rhdh.redhat.com/ext-config-hash" type BackstageDeploymentFactory struct{} +type ObjectKind string + func (f BackstageDeploymentFactory) newBackstageObject() RuntimeObject { return &BackstageDeployment{} } @@ -52,13 +59,17 @@ func DeploymentName(backstageName string) string { // BackstageContainerIndex returns the index of backstage container in from deployment.spec.template.spec.containers array func BackstageContainerIndex(bsd *appsv1.Deployment) int { for i, c := range bsd.Spec.Template.Spec.Containers { - if c.Name == "backstage-backend" { + if c.Name == BackstageContainerName() { return i } } return -1 } +func BackstageContainerName() string { + return "backstage-backend" +} + // implementation of RuntimeObject interface func (b *BackstageDeployment) Object() runtime.Object { return b.deployment @@ -112,9 +123,11 @@ func (b *BackstageDeployment) updateAndValidate(model *BackstageModel, backstage //DbSecret if backstage.Spec.IsAuthSecretSpecified() { - utils.SetDbSecretEnvVar(b.container(), backstage.Spec.Database.AuthSecretName) + b.addEnvVarsFrom([]string{BackstageContainerName()}, SecretObjectKind, backstage.Spec.Database.AuthSecretName, "") + //utils.SetDbSecretEnvVar(b.container(), backstage.Spec.Database.AuthSecretName) } else if model.LocalDbSecret != nil { - utils.SetDbSecretEnvVar(b.container(), model.LocalDbSecret.secret.Name) + b.addEnvVarsFrom([]string{BackstageContainerName()}, SecretObjectKind, model.LocalDbSecret.secret.Name, "") + //utils.SetDbSecretEnvVar(b.container(), model.LocalDbSecret.secret.Name) } return nil @@ -148,11 +161,15 @@ func (b *BackstageDeployment) containerByName(name string) *corev1.Container { return nil } -func (b *BackstageDeployment) allContainers() []corev1.Container { - containers := []corev1.Container{} +func (b *BackstageDeployment) allContainers() []string { + containers := []string{} spec := b.deployment.Spec.Template.Spec - containers = append(containers, spec.InitContainers...) - containers = append(containers, spec.Containers...) + for _, c := range spec.Containers { + containers = append(containers, c.Name) + } + for _, c := range spec.InitContainers { + containers = append(containers, c.Name) + } return containers } @@ -191,6 +208,8 @@ func (b *BackstageDeployment) mountPath(objectMountPath, objectKey, sharedMountP return mp, wSubpath } +// setDeployment sets the deployment object from the backstage configuration +// it merges the deployment object with the patch from the backstage configuration func (b *BackstageDeployment) setDeployment(backstage bsv1.Backstage) error { // set from backstage.Spec.Application @@ -225,6 +244,24 @@ func (b *BackstageDeployment) setDeployment(backstage bsv1.Backstage) error { return nil } +// getDefConfigMountInfo returns the mount path, subpath and the containers to mount the object (defined in default configuration) to +func (b *BackstageDeployment) getDefConfigMountInfo(obj client.Object) (mountPath string, subPath string, toContainers []string) { + mountPath, ok := obj.GetAnnotations()[DefaultMountPathAnnotation] + subPath = "" + if !ok { + volName := utils.ToRFC1123Label(obj.GetName()) + mountPath = filepath.Join(b.defaultMountPath(), volName) + subPath = volName + } + // filter containers to mount the object to + toContainers = utils.FilterContainers(b.allContainers(), obj.GetAnnotations()[ContainersAnnotation]) + // if no containers specified, mount to Backstage container only + if toContainers == nil { + toContainers = []string{BackstageContainerName()} + } + return +} + // sets the amount of replicas (used by CR config) func (b *BackstageDeployment) setReplicas(replicas *int32) { if replicas != nil { @@ -266,3 +303,98 @@ func (b *BackstageDeployment) addExtraEnvs(extraEnvs *bsv1.ExtraEnvs) { } } } + +// MountFilesFrom adds Volume to specified podSpec and related VolumeMounts to specified belonging to this podSpec container +// from ConfigMap or Secret volume source +// podSpec - PodSpec to add Volume to +// container - container to add VolumeMount(s) to +// kind - kind of source, can be ConfigMap or Secret +// objectName - name of source object +// mountPath - mount path, default one or as it specified in BackstageCR.spec.Application.AppConfig|ExtraFiles +// fileName - file name which fits one of the object's key, otherwise error will be returned. +// withSubPath - if true will be mounted file-by-file with subpath, otherwise will be mounted as directory to specified path +// dataKeys - keys for ConfigMap/Secret data +func (b *BackstageDeployment) mountFilesFrom(containers []string, kind ObjectKind, objectName, mountPath, fileName string, withSubPath bool, dataKeys []string) { + + volName := utils.GenerateVolumeNameFromCmOrSecret(objectName) + volSrc := corev1.VolumeSource{} + if kind == ConfigMapObjectKind { + volSrc.ConfigMap = &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: objectName}, + DefaultMode: ptr.To(int32(420)), + Optional: ptr.To(false), + } + } else if kind == SecretObjectKind { + volSrc.Secret = &corev1.SecretVolumeSource{ + SecretName: objectName, + DefaultMode: ptr.To(int32(420)), + Optional: ptr.To(false), + } + } + + b.podSpec().Volumes = append(b.podSpec().Volumes, corev1.Volume{Name: volName, VolumeSource: volSrc}) + + for _, c := range containers { + container := b.containerByName(c) + if !withSubPath { + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{Name: volName, MountPath: mountPath}) + continue + } + + if len(dataKeys) > 0 { + for _, file := range dataKeys { + if fileName == "" || fileName == file { + vm := corev1.VolumeMount{Name: volName, MountPath: filepath.Join(mountPath, file), SubPath: file, ReadOnly: true} + container.VolumeMounts = append(container.VolumeMounts, vm) + } + } + } else { + vm := corev1.VolumeMount{Name: volName, MountPath: filepath.Join(mountPath, fileName), SubPath: fileName, ReadOnly: true} + container.VolumeMounts = append(container.VolumeMounts, vm) + } + } + +} + +// AddEnvVarsFrom adds environment variable to specified container +// kind - kind of source, can be ConfigMap or Secret +// objectName - name of source object +// varName - name of env variable +func (b *BackstageDeployment) addEnvVarsFrom(containerNames []string, kind ObjectKind, objectName, varName string) { + + for _, c := range containerNames { + container := b.containerByName(c) + if varName == "" { + envFromSrc := corev1.EnvFromSource{} + if kind == ConfigMapObjectKind { + envFromSrc.ConfigMapRef = &corev1.ConfigMapEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: objectName}} + } else if kind == SecretObjectKind { + envFromSrc.SecretRef = &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: objectName}} + } + container.EnvFrom = append(container.EnvFrom, envFromSrc) + } else { + envVarSrc := &corev1.EnvVarSource{} + if kind == ConfigMapObjectKind { + envVarSrc.ConfigMapKeyRef = &corev1.ConfigMapKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: objectName, + }, + Key: varName, + } + } else if kind == SecretObjectKind { + envVarSrc.SecretKeyRef = &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: objectName, + }, + Key: varName, + } + } + container.Env = append(container.Env, corev1.EnvVar{ + Name: varName, + ValueFrom: envVarSrc, + }) + } + } +} diff --git a/pkg/model/dynamic-plugins.go b/pkg/model/dynamic-plugins.go index f9cbe1be..d40766b6 100644 --- a/pkg/model/dynamic-plugins.go +++ b/pkg/model/dynamic-plugins.go @@ -54,7 +54,7 @@ func addDynamicPluginsFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) e return nil } - c, ic := DynamicPluginsInitContainer(model.backstageDeployment.deployment.Spec.Template.Spec.InitContainers) + _, ic := DynamicPluginsInitContainer(model.backstageDeployment.deployment.Spec.Template.Spec.InitContainers) if ic == nil { return fmt.Errorf("validation failed, dynamic plugin name configured but no InitContainer %s defined", dynamicPluginInitContainerName) } @@ -64,7 +64,7 @@ func addDynamicPluginsFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) e return fmt.Errorf("dynamic plugin configMap expects exactly one Data key named '%s' ", DynamicPluginsFile) } - utils.MountFilesFrom(&model.backstageDeployment.deployment.Spec.Template.Spec, &model.backstageDeployment.deployment.Spec.Template.Spec.InitContainers[c], utils.ConfigMapObjectKind, + model.backstageDeployment.mountFilesFrom([]string{dynamicPluginInitContainerName}, ConfigMapObjectKind, dp.Name, ic.WorkingDir, DynamicPluginsFile, true, maps.Keys(dp.Data)) return nil @@ -103,7 +103,7 @@ func (p *DynamicPlugins) addToModel(model *BackstageModel, backstage bsv1.Backst // ConfigMap name must be the same as (deployment.yaml).spec.template.spec.volumes.name.dynamic-plugins-conf.ConfigMap.name func (p *DynamicPlugins) updateAndValidate(model *BackstageModel, _ bsv1.Backstage) error { - c, initContainer := DynamicPluginsInitContainer(model.backstageDeployment.deployment.Spec.Template.Spec.InitContainers) + _, initContainer := DynamicPluginsInitContainer(model.backstageDeployment.deployment.Spec.Template.Spec.InitContainers) if initContainer == nil { return fmt.Errorf("failed to find initContainer named %s", dynamicPluginInitContainerName) } @@ -116,7 +116,7 @@ func (p *DynamicPlugins) updateAndValidate(model *BackstageModel, _ bsv1.Backsta initContainer.Image = os.Getenv(BackstageImageEnvVar) } - utils.MountFilesFrom(&model.backstageDeployment.deployment.Spec.Template.Spec, &model.backstageDeployment.deployment.Spec.Template.Spec.InitContainers[c], utils.ConfigMapObjectKind, + model.backstageDeployment.mountFilesFrom([]string{dynamicPluginInitContainerName}, ConfigMapObjectKind, p.ConfigMap.Name, initContainer.WorkingDir, DynamicPluginsFile, true, maps.Keys(p.ConfigMap.Data)) return nil diff --git a/pkg/model/pvcs.go b/pkg/model/pvcs.go index 72a3619b..3d000c3a 100644 --- a/pkg/model/pvcs.go +++ b/pkg/model/pvcs.go @@ -39,7 +39,7 @@ func addPvcsFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) { subPath = utils.ToRFC1123Label(pvcSpec.Name) } - addPvc(model.backstageDeployment, pvcSpec.Name, mountPath, subPath, nil) + addPvc(model.backstageDeployment, pvcSpec.Name, mountPath, subPath, []string{BackstageContainerName()}) } } @@ -77,15 +77,7 @@ func (b *BackstagePvcs) updateAndValidate(m *BackstageModel, _ bsv1.Backstage) e if !ok { return fmt.Errorf("payload is not corev1.PersistentVolumeClaim: %T", o) } - mountPath, ok := pvc.GetAnnotations()[DefaultMountPathAnnotation] - subPath := "" - if !ok { - volName := utils.ToRFC1123Label(pvc.GetName()) - mountPath = filepath.Join(m.backstageDeployment.defaultMountPath(), volName) - subPath = volName - } - - containers := utils.FilterContainers(m.backstageDeployment.allContainers(), pvc.GetAnnotations()[ContainersAnnotation]) + mountPath, subPath, containers := m.backstageDeployment.getDefConfigMountInfo(o) addPvc(m.backstageDeployment, pvc.Name, mountPath, subPath, containers) } return nil @@ -100,7 +92,7 @@ func (b *BackstagePvcs) setMetaInfo(backstage bsv1.Backstage, scheme *runtime.Sc } } -func addPvc(bsd *BackstageDeployment, pvcName, mountPath, subPath string, affectedContainers []corev1.Container) { +func addPvc(bsd *BackstageDeployment, pvcName, mountPath, subPath string, affectedContainers []string) { volName := utils.ToRFC1123Label(pvcName) volSrc := corev1.VolumeSource{ @@ -110,17 +102,9 @@ func addPvc(bsd *BackstageDeployment, pvcName, mountPath, subPath string, affect } bsd.deployment.Spec.Template.Spec.Volumes = append(bsd.deployment.Spec.Template.Spec.Volumes, corev1.Volume{Name: volName, VolumeSource: volSrc}) - - if affectedContainers == nil { - // if nothing specified mount to the Backstage container only - bsd.container().VolumeMounts = append(bsd.container().VolumeMounts, + for _, c := range affectedContainers { + update := bsd.containerByName(c) + update.VolumeMounts = append(update.VolumeMounts, corev1.VolumeMount{Name: volName, MountPath: mountPath, SubPath: subPath}) - } else { - // else mount to the affectedContainers - for _, c := range affectedContainers { - update := bsd.containerByName(c.Name) - update.VolumeMounts = append(update.VolumeMounts, - corev1.VolumeMount{Name: volName, MountPath: mountPath, SubPath: subPath}) - } } } diff --git a/pkg/model/secretenvs.go b/pkg/model/secretenvs.go index 85eb39ba..515f9550 100644 --- a/pkg/model/secretenvs.go +++ b/pkg/model/secretenvs.go @@ -1,7 +1,10 @@ package model import ( + "fmt" + bsv1 "github.com/redhat-developer/rhdh-operator/api/v1alpha3" + "github.com/redhat-developer/rhdh-operator/pkg/model/multiobject" "github.com/redhat-developer/rhdh-operator/pkg/utils" "sigs.k8s.io/controller-runtime/pkg/client" @@ -11,6 +14,8 @@ import ( corev1 "k8s.io/api/core/v1" ) +const SecretEnvsObjectKey = "secret-envs.yaml" + type SecretEnvsFactory struct{} func (f SecretEnvsFactory) newBackstageObject() RuntimeObject { @@ -18,16 +23,11 @@ func (f SecretEnvsFactory) newBackstageObject() RuntimeObject { } type SecretEnvs struct { - Secret *corev1.Secret + secrets *multiobject.MultiObject } func init() { - registerConfig("secret-envs.yaml", SecretEnvsFactory{}, false) -} - -// implementation of RuntimeObject interface -func (p *SecretEnvs) Object() runtime.Object { - return p.Secret + registerConfig(SecretEnvsObjectKey, SecretEnvsFactory{}, true) } func addSecretEnvsFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) error { @@ -36,15 +36,22 @@ func addSecretEnvsFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) error } for _, specSec := range spec.Application.ExtraEnvs.Secrets { - utils.AddEnvVarsFrom(model.backstageDeployment.container(), utils.SecretObjectKind, specSec.Name, specSec.Key) + model.backstageDeployment.addEnvVarsFrom([]string{BackstageContainerName()}, SecretObjectKind, specSec.Name, specSec.Key) } return nil } +// implementation of RuntimeObject interface +func (p *SecretEnvs) Object() runtime.Object { + return p.secrets +} + +// implementation of RuntimeObject interface func (p *SecretEnvs) setObject(obj runtime.Object) { - p.Secret = nil + p.secrets = nil if obj != nil { - p.Secret = obj.(*corev1.Secret) + // p.Secret = obj.(*corev1.Secret) + p.secrets = obj.(*multiobject.MultiObject) } } @@ -55,7 +62,7 @@ func (p *SecretEnvs) EmptyObject() client.Object { // implementation of RuntimeObject interface func (p *SecretEnvs) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, error) { - if p.Secret != nil { + if p.secrets != nil { model.setRuntimeObject(p) return true, nil } @@ -64,13 +71,33 @@ func (p *SecretEnvs) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, // implementation of RuntimeObject interface func (p *SecretEnvs) updateAndValidate(m *BackstageModel, _ bsv1.Backstage) error { - utils.AddEnvVarsFrom(m.backstageDeployment.container(), utils.SecretObjectKind, - p.Secret.Name, "") + + for _, item := range p.secrets.Items { + _, ok := item.(*corev1.Secret) + if !ok { + return fmt.Errorf("payload is not corev1.Secret: %T", item) + } + toContainers := utils.FilterContainers(m.backstageDeployment.allContainers(), item.GetAnnotations()[ContainersAnnotation]) + if toContainers == nil { + toContainers = []string{BackstageContainerName()} + } + m.backstageDeployment.addEnvVarsFrom(toContainers, SecretObjectKind, item.GetName(), "") + } + return nil } // implementation of RuntimeObject interface func (p *SecretEnvs) setMetaInfo(backstage bsv1.Backstage, scheme *runtime.Scheme) { - p.Secret.SetName(utils.GenerateRuntimeObjectName(backstage.Name, "backstage-envs")) - setMetaInfo(p.Secret, backstage, scheme) + for _, item := range p.secrets.Items { + secret := item.(*corev1.Secret) + utils.AddAnnotation(secret, ConfiguredNameAnnotation, item.GetName()) + if len(p.secrets.Items) == 1 { + // keep for backward compatibility + secret.Name = utils.GenerateRuntimeObjectName(backstage.Name, "backstage-envs") + } else { + secret.Name = fmt.Sprintf("%s-%s", utils.GenerateRuntimeObjectName(backstage.Name, "backstage-envs"), secret.Name) + } + setMetaInfo(secret, backstage, scheme) + } } diff --git a/pkg/model/secretenvs_test.go b/pkg/model/secretenvs_test.go index ac12f0e7..2209dc8f 100644 --- a/pkg/model/secretenvs_test.go +++ b/pkg/model/secretenvs_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/redhat-developer/rhdh-operator/pkg/model/multiobject" + "k8s.io/utils/ptr" bsv1 "github.com/redhat-developer/rhdh-operator/api/v1alpha3" @@ -17,8 +19,7 @@ func TestDefaultSecretEnvFrom(t *testing.T) { bs := bsv1.Backstage{ ObjectMeta: metav1.ObjectMeta{ - Name: "bs", - Namespace: "ns123", + Name: "bs", }, Spec: bsv1.BackstageSpec{ Database: &bsv1.Database{ @@ -27,7 +28,7 @@ func TestDefaultSecretEnvFrom(t *testing.T) { }, } - testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("secret-envs.yaml", "raw-sec-envs.yaml") + testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig(SecretEnvsObjectKey, "raw-sec-envs.yaml") model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, false, testObj.scheme) @@ -42,6 +43,35 @@ func TestDefaultSecretEnvFrom(t *testing.T) { } +func TestDefaultMultiSecretEnv(t *testing.T) { + + bs := bsv1.Backstage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bs", + }, + Spec: bsv1.BackstageSpec{ + Database: &bsv1.Database{ + EnableLocalDb: ptr.To(false), + }, + }, + } + + testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("deployment.yaml", "multicontainer-deployment.yaml"). + addToDefaultConfig(SecretEnvsObjectKey, "raw-multi-secret.yaml") + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, false, testObj.scheme) + + assert.NoError(t, err) + assert.NotNil(t, model) + + assert.Equal(t, 4, len(model.backstageDeployment.allContainers())) + assert.Equal(t, 3, len(model.backstageDeployment.container().EnvFrom)) + assert.Equal(t, 2, len(model.backstageDeployment.containerByName("install-dynamic-plugins").EnvFrom)) + assert.Equal(t, 1, len(model.backstageDeployment.containerByName("another-container").EnvFrom)) + mo := model.getRuntimeObjectByType(&SecretEnvs{}).Object().(*multiobject.MultiObject) + assert.Equal(t, 3, len(mo.Items)) +} + func TestSpecifiedSecretEnvs(t *testing.T) { bs := bsv1.Backstage{ diff --git a/pkg/model/secretfiles.go b/pkg/model/secretfiles.go index be3d1a15..92dd8dbb 100644 --- a/pkg/model/secretfiles.go +++ b/pkg/model/secretfiles.go @@ -3,6 +3,8 @@ package model import ( "fmt" + "github.com/redhat-developer/rhdh-operator/pkg/model/multiobject" + "golang.org/x/exp/maps" "sigs.k8s.io/controller-runtime/pkg/client" @@ -15,6 +17,8 @@ import ( corev1 "k8s.io/api/core/v1" ) +const SecretFilesObjectKey = "secret-files.yaml" + type SecretFilesFactory struct{} func (f SecretFilesFactory) newBackstageObject() RuntimeObject { @@ -22,11 +26,11 @@ func (f SecretFilesFactory) newBackstageObject() RuntimeObject { } type SecretFiles struct { - Secret *corev1.Secret + secrets *multiobject.MultiObject } func init() { - registerConfig("secret-files.yaml", SecretFilesFactory{}, false) + registerConfig(SecretFilesObjectKey, SecretFilesFactory{}, true) } func addSecretFilesFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) error { @@ -42,7 +46,7 @@ func addSecretFilesFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) erro } mp, wSubpath := model.backstageDeployment.mountPath(specSec.MountPath, specSec.Key, spec.Application.ExtraFiles.MountPath) keys := model.ExternalConfig.ExtraFileSecretKeys[specSec.Name].All() - utils.MountFilesFrom(&model.backstageDeployment.deployment.Spec.Template.Spec, model.backstageDeployment.container(), utils.SecretObjectKind, + model.backstageDeployment.mountFilesFrom([]string{BackstageContainerName()}, SecretObjectKind, specSec.Name, mp, specSec.Key, wSubpath, keys) } return nil @@ -50,13 +54,14 @@ func addSecretFilesFromSpec(spec bsv1.BackstageSpec, model *BackstageModel) erro // implementation of RuntimeObject interface func (p *SecretFiles) Object() runtime.Object { - return p.Secret + return p.secrets } +// implementation of RuntimeObject interface func (p *SecretFiles) setObject(obj runtime.Object) { - p.Secret = nil + p.secrets = nil if obj != nil { - p.Secret = obj.(*corev1.Secret) + p.secrets = obj.(*multiobject.MultiObject) } } @@ -67,7 +72,7 @@ func (p *SecretFiles) EmptyObject() client.Object { // implementation of RuntimeObject interface func (p *SecretFiles) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, error) { - if p.Secret != nil { + if p.secrets != nil { model.setRuntimeObject(p) return true, nil } @@ -77,14 +82,32 @@ func (p *SecretFiles) addToModel(model *BackstageModel, _ bsv1.Backstage) (bool, // implementation of RuntimeObject interface func (p *SecretFiles) updateAndValidate(m *BackstageModel, _ bsv1.Backstage) error { - keys := append(maps.Keys(p.Secret.Data), maps.Keys(p.Secret.StringData)...) - utils.MountFilesFrom(&m.backstageDeployment.deployment.Spec.Template.Spec, m.backstageDeployment.container(), utils.SecretObjectKind, - p.Secret.Name, m.backstageDeployment.defaultMountPath(), "", true, keys) + for _, item := range p.secrets.Items { + secret, ok := item.(*corev1.Secret) + if !ok { + return fmt.Errorf("payload is not corev1.Secret: %T", item) + } + + keys := append(maps.Keys(secret.Data), maps.Keys(secret.StringData)...) + mountPath, subPath, containers := m.backstageDeployment.getDefConfigMountInfo(item) + m.backstageDeployment.mountFilesFrom(containers, SecretObjectKind, + item.GetName(), mountPath, "", subPath != "", keys) + } return nil } // implementation of RuntimeObject interface func (p *SecretFiles) setMetaInfo(backstage bsv1.Backstage, scheme *runtime.Scheme) { - p.Secret.SetName(utils.GenerateRuntimeObjectName(backstage.Name, "backstage-files")) - setMetaInfo(p.Secret, backstage, scheme) + + for _, item := range p.secrets.Items { + secret := item.(*corev1.Secret) + if len(p.secrets.Items) == 1 { + // keep for backward compatibility + secret.Name = utils.GenerateRuntimeObjectName(backstage.Name, "backstage-files") + } else { + utils.AddAnnotation(secret, ConfiguredNameAnnotation, item.GetName()) + secret.Name = fmt.Sprintf("%s-%s", utils.GenerateRuntimeObjectName(backstage.Name, "backstage-files"), secret.Name) + } + setMetaInfo(secret, backstage, scheme) + } } diff --git a/pkg/model/secretfiles_test.go b/pkg/model/secretfiles_test.go index 1a189a64..d82436dd 100644 --- a/pkg/model/secretfiles_test.go +++ b/pkg/model/secretfiles_test.go @@ -4,6 +4,9 @@ import ( "context" "testing" + "github.com/redhat-developer/rhdh-operator/pkg/model/multiobject" + "k8s.io/utils/ptr" + "github.com/redhat-developer/rhdh-operator/pkg/utils" bsv1 "github.com/redhat-developer/rhdh-operator/api/v1alpha3" @@ -34,7 +37,7 @@ func TestDefaultSecretFiles(t *testing.T) { bs := *secretFilesTestBackstage.DeepCopy() - testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("secret-files.yaml", "raw-secret-files.yaml") + testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig(SecretFilesObjectKey, "raw-secret-files.yaml") model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, false, testObj.scheme) @@ -48,6 +51,38 @@ func TestDefaultSecretFiles(t *testing.T) { } +func TestDefaultMultiSecretFiles(t *testing.T) { + + bs := bsv1.Backstage{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bs", + }, + Spec: bsv1.BackstageSpec{ + Database: &bsv1.Database{ + EnableLocalDb: ptr.To(false), + }, + }, + } + + testObj := createBackstageTest(bs).withDefaultConfig(true).addToDefaultConfig("deployment.yaml", "multicontainer-deployment.yaml"). + addToDefaultConfig(SecretFilesObjectKey, "raw-multi-secret.yaml") + + model, err := InitObjects(context.TODO(), bs, testObj.externalConfig, false, testObj.scheme) + + assert.NoError(t, err) + assert.NotNil(t, model) + + mo := model.getRuntimeObjectByType(&SecretFiles{}).Object().(*multiobject.MultiObject) + assert.Equal(t, 3, len(mo.Items)) + // data1,data2,data3+data4,data5 + assert.Equal(t, 4, len(model.backstageDeployment.container().VolumeMounts)) + // data1,data2,data5 + assert.Equal(t, 3, len(model.backstageDeployment.containerByName("install-dynamic-plugins").VolumeMounts)) + // data5 + assert.Equal(t, 1, len(model.backstageDeployment.containerByName("another-container").VolumeMounts)) + assert.Equal(t, 3, len(model.backstageDeployment.deployment.Spec.Template.Spec.Volumes)) +} + func TestSpecifiedSecretFiles(t *testing.T) { bs := *secretFilesTestBackstage.DeepCopy() diff --git a/pkg/model/testdata/raw-multi-secret.yaml b/pkg/model/testdata/raw-multi-secret.yaml new file mode 100644 index 00000000..db99a25f --- /dev/null +++ b/pkg/model/testdata/raw-multi-secret.yaml @@ -0,0 +1,29 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret1 + annotations: + rhdh.redhat.com/containers: "backstage-backend,install-dynamic-plugins" +stringData: + data1: "data1" + data2: "data2" +--- +apiVersion: v1 +kind: Secret +metadata: + annotations: + rhdh.redhat.com/mount-path: /mount/path/from/annotation + name: secret2 +stringData: + data3: "data3" + data4: "data4" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret3 + annotations: + rhdh.redhat.com/containers: "*" + rhdh.redhat.com/mount-path: /mount/path/from/annotation2 +stringData: + data5: "data5" diff --git a/pkg/utils/pod-mutator.go b/pkg/utils/pod-mutator.go index fddde456..2b4380bd 100644 --- a/pkg/utils/pod-mutator.go +++ b/pkg/utils/pod-mutator.go @@ -1,118 +1,9 @@ package utils import ( - "path/filepath" - - "k8s.io/utils/ptr" - corev1 "k8s.io/api/core/v1" ) -const ( - SecretObjectKind = "Secret" - ConfigMapObjectKind = "ConfigMap" -) - -type ObjectKind string - -type PodMutator struct { - PodSpec *corev1.PodSpec - Container *corev1.Container -} - -// MountFilesFrom adds Volume to specified podSpec and related VolumeMounts to specified belonging to this podSpec container -// from ConfigMap or Secret volume source -// podSpec - PodSpec to add Volume to -// container - container to add VolumeMount(s) to -// kind - kind of source, can be ConfigMap or Secret -// objectName - name of source object -// mountPath - mount path, default one or as it specified in BackstageCR.spec.Application.AppConfig|ExtraFiles -// fileName - file name which fits one of the object's key, otherwise error will be returned. -// withSubPath - if true will be mounted file-by-file with subpath, otherwise will be mounted as directory to specified path -// dataKeys - keys for ConfigMap/Secret data -func MountFilesFrom(podSpec *corev1.PodSpec, container *corev1.Container, kind ObjectKind, objectName, mountPath, fileName string, withSubPath bool, dataKeys []string) { - - volName := GenerateVolumeNameFromCmOrSecret(objectName) - volSrc := corev1.VolumeSource{} - if kind == ConfigMapObjectKind { - volSrc.ConfigMap = &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: objectName}, - DefaultMode: ptr.To(int32(420)), - Optional: ptr.To(false), - } - } else if kind == SecretObjectKind { - volSrc.Secret = &corev1.SecretVolumeSource{ - SecretName: objectName, - DefaultMode: ptr.To(int32(420)), - Optional: ptr.To(false), - } - } - - podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{Name: volName, VolumeSource: volSrc}) - - if !withSubPath { - container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{Name: volName, MountPath: mountPath}) - return - } - - if len(dataKeys) > 0 { - for _, file := range dataKeys { - if fileName == "" || fileName == file { - vm := corev1.VolumeMount{Name: volName, MountPath: filepath.Join(mountPath, file), SubPath: file, ReadOnly: true} - container.VolumeMounts = append(container.VolumeMounts, vm) - } - } - } else { - vm := corev1.VolumeMount{Name: volName, MountPath: filepath.Join(mountPath, fileName), SubPath: fileName, ReadOnly: true} - container.VolumeMounts = append(container.VolumeMounts, vm) - } - -} - -// AddEnvVarsFrom adds environment variable to specified container -// kind - kind of source, can be ConfigMap or Secret -// objectName - name of source object -// varName - name of env variable -func AddEnvVarsFrom(container *corev1.Container, kind ObjectKind, objectName, varName string) { - - if varName == "" { - envFromSrc := corev1.EnvFromSource{} - if kind == ConfigMapObjectKind { - envFromSrc.ConfigMapRef = &corev1.ConfigMapEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: objectName}} - } else if kind == SecretObjectKind { - envFromSrc.SecretRef = &corev1.SecretEnvSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: objectName}} - } - container.EnvFrom = append(container.EnvFrom, envFromSrc) - } else { - envVarSrc := &corev1.EnvVarSource{} - if kind == ConfigMapObjectKind { - envVarSrc.ConfigMapKeyRef = &corev1.ConfigMapKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: objectName, - }, - Key: varName, - } - } else if kind == SecretObjectKind { - envVarSrc.SecretKeyRef = &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: objectName, - }, - Key: varName, - } - } - container.Env = append(container.Env, corev1.EnvVar{ - Name: varName, - ValueFrom: envVarSrc, - }) - } -} - -func SetDbSecretEnvVar(container *corev1.Container, secretName string) { - AddEnvVarsFrom(container, SecretObjectKind, secretName, "") -} - // sets pullSecret for Pod func SetImagePullSecrets(podSpec *corev1.PodSpec, pullSecrets []string) { if pullSecrets == nil { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index bddd0234..2882fbc6 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -14,8 +14,6 @@ import ( "strconv" "strings" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime" @@ -215,17 +213,17 @@ func BoolEnvVar(envvar string, def bool) bool { return def } -func FilterContainers(allContainers []corev1.Container, filter string) []corev1.Container { +func FilterContainers(allContainers []string, filter string) []string { if filter == "*" { return allContainers } else if filter == "" { return nil } - filtered := []corev1.Container{} + filtered := []string{} for _, c := range allContainers { for _, cname := range strings.Split(filter, ",") { - if c.Name == strings.TrimSpace(cname) { + if c == strings.TrimSpace(cname) { filtered = append(filtered, c) } } diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 65d48301..10c995f6 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -143,7 +143,7 @@ func TestBoolEnvVar(t *testing.T) { func TestFilterContainers(t *testing.T) { - containers := []corev1.Container{{Name: "c1"}, {Name: "c2"}, {Name: "c3"}} + containers := []string{"c1", "c2", "c3"} cs := FilterContainers(containers, "") assert.Nil(t, cs) @@ -156,6 +156,6 @@ func TestFilterContainers(t *testing.T) { cs = FilterContainers(containers, "c1,c2") assert.Equal(t, 2, len(cs)) - assert.Equal(t, "c1", cs[0].Name) + assert.Equal(t, "c1", cs[0]) }