Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configure other than Backstage container to mount PVC volume to #582

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

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

2 changes: 1 addition & 1 deletion api/v1alpha2/zz_generated.deepcopy.go

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

2 changes: 1 addition & 1 deletion api/v1alpha3/zz_generated.deepcopy.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -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-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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ data:
type: RuntimeDefault
capabilities:
drop:
- ALL
- ALL
env:
- name: NPM_CONFIG_USERCONFIG
value: /opt/app-root/src/.npmrc.dynamic-plugins
Expand Down
2 changes: 1 addition & 1 deletion config/profile/rhdh/default-config/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ spec:
type: RuntimeDefault
capabilities:
drop:
- ALL
- ALL
env:
- name: NPM_CONFIG_USERCONFIG
value: /opt/app-root/src/.npmrc.dynamic-plugins
Expand Down
58 changes: 41 additions & 17 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<cr-name> | Yes | No | >=0.1.x | Backstage deployment |
| service.yaml | corev1.Service | backstage-<cr-name> | Yes | No | >=0.1.x | Backstage Service |
| db-statefulset.yaml | appsv1.StatefulSet | backstage-psql-<cr-name> | For local DB | No | >=0.1.x | PostgreSQL StatefulSet |
| db-service.yaml | corev1.Service | backstage-psql-<cr-name> | For local DB | No | >=0.1.x | PostgreSQL Service |
| db-secret.yaml | corev1.Secret | backstage-psql-secret-<cr-name> | For local DB | No | >=0.1.x | Secret to connect Backstage to PGSQL |
| route.yaml | openshift.Route | backstage-<cr-name> | No (for OCP) | No | >=0.1.x | Route exposing Backstage service |
| app-config.yaml | corev1.ConfigMap | backstage-appconfig-<cr-name> | No | No | >=0.2.x | Backstage app-config.yaml |
| configmap-files.yaml | corev1.ConfigMap | backstage-files-<cr-name> | No | No | >=0.2.x | Backstage config file inclusions from configMap |
| configmap-envs.yaml | corev1.ConfigMap | backstage-envs-<cr-name> | No | No | >=0.2.x | Backstage environment variables from ConfigMap |
| secret-files.yaml | corev1.Secret | backstage-files-<cr-name> | No | No | >=0.2.x | Backstage config file inclusions from Secret |
| secret-envs.yaml | corev1.Secret | backstage-envs-<cr-name> | No | No | >=0.2.x | Backstage environment variables from Secret |
| dynamic-plugins.yaml | corev1.ConfigMap | backstage-dynamic-plugins-<cr-name> | No | No | >=0.2.x | Dynamic plugins configuration |
| pvcs.yaml | corev1.PersistentVolumeClaim | backstage-&lt;cr-name&gt;-&lt;pvc-name&gt; | 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-<cr-name> | Yes | No | >=0.1.x | Backstage deployment |
| service.yaml | corev1.Service | backstage-<cr-name> | Yes | No | >=0.1.x | Backstage Service |
| db-statefulset.yaml | appsv1.StatefulSet | backstage-psql-<cr-name> | For local DB | No | >=0.1.x | PostgreSQL StatefulSet |
| db-service.yaml | corev1.Service | backstage-psql-<cr-name> | For local DB | No | >=0.1.x | PostgreSQL Service |
| db-secret.yaml | corev1.Secret | backstage-psql-secret-<cr-name> | For local DB | No | >=0.1.x | Secret to connect Backstage to PGSQL |
| route.yaml | openshift.Route | backstage-<cr-name> | No (for OCP) | No | >=0.1.x | Route exposing Backstage service |
| app-config.yaml | corev1.ConfigMap | backstage-appconfig-<cr-name> | No | No | >=0.2.x | Backstage app-config.yaml |
| configmap-files.yaml | corev1.ConfigMap | backstage-files-<cr-name> | No | No | >=0.2.x | Backstage config file inclusions from configMap |
| configmap-envs.yaml | corev1.ConfigMap | backstage-envs-<cr-name> | No | No | >=0.2.x | Backstage environment variables from ConfigMap |
| secret-files.yaml | corev1.Secret | backstage-files-<cr-name> | No | No | >=0.2.x | Backstage config file inclusions from Secret |
| secret-envs.yaml | corev1.Secret | backstage-envs-<cr-name> | No | No | >=0.2.x | Backstage environment variables from Secret |
| dynamic-plugins.yaml | corev1.ConfigMap | backstage-dynamic-plugins-<cr-name> | No | No | >=0.2.x | Dynamic plugins configuration |
| pvcs.yaml | corev1.PersistentVolumeClaim | backstage-&lt;cr-name&gt;-&lt;pvc-name&gt; | 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.
Expand All @@ -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 to Backstage Container.
Use **rhdh.redhat.com/mount-path** annotation to configure mount path for **PersistentVolumeClaim** volume.

_**pvcs.yaml**_
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
Expand All @@ -56,6 +57,29 @@ 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)

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
* \* (asterisk) means the volume will be mounted to all the containers
* Otherwise, container names separated by commas will be used

_**pvcs.yaml**_
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
annotations:
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


### 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.
Expand Down
22 changes: 22 additions & 0 deletions pkg/model/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
22 changes: 16 additions & 6 deletions pkg/model/pvcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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{
Expand All @@ -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})
}
}
}
25 changes: 25 additions & 0 deletions pkg/model/pvcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
1 change: 1 addition & 0 deletions pkg/model/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions pkg/model/testdata/multi-pvc-containers.yaml
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions pkg/model/testdata/multicontainer-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: <to_be_replaced> # placeholder for 'backstage-<cr-name>'
spec:
replicas: 1
selector:
matchLabels:
rhdh.redhat.com/app: # placeholder for 'backstage-<cr-name>'
template:
metadata:
labels:
rhdh.redhat.com/app: # placeholder for 'backstage-<cr-name>'
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
2 changes: 1 addition & 1 deletion pkg/utils/pod-mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 20 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"strconv"
"strings"

corev1 "k8s.io/api/core/v1"

"k8s.io/apimachinery/pkg/runtime/schema"

"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -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
}
19 changes: 19 additions & 0 deletions pkg/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

}
Loading