From fb19f2588e7dc78b8eea8a47cbf94ed197d6a3b0 Mon Sep 17 00:00:00 2001 From: Armel Soro Date: Wed, 27 Nov 2024 11:52:42 +0100 Subject: [PATCH] Make logic for testing the operator upgrade paths extensible (#447) With the new release-1.4 branch, we now have more possible upgrade combinations. --- .github/workflows/nightly-upgrade-test.yaml | 77 + .github/workflows/nightly.yaml | 24 +- tests/e2e/e2e_upgrade_test.go | 10 +- tests/e2e/testdata/rhdh-operator-1.3.yaml | 1518 ++++++++++++++ tests/e2e/testdata/rhdh-operator-1.4.yaml | 1977 +++++++++++++++++++ 5 files changed, 3589 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/nightly-upgrade-test.yaml create mode 100644 tests/e2e/testdata/rhdh-operator-1.3.yaml create mode 100644 tests/e2e/testdata/rhdh-operator-1.4.yaml diff --git a/.github/workflows/nightly-upgrade-test.yaml b/.github/workflows/nightly-upgrade-test.yaml new file mode 100644 index 00000000..aadf7031 --- /dev/null +++ b/.github/workflows/nightly-upgrade-test.yaml @@ -0,0 +1,77 @@ +name: Nightly checks (Upgrade) + +on: + # workflow_dispatch so that it can be triggered manually if needed + workflow_dispatch: + schedule: + - cron: "55 23 * * *" + +jobs: + e2e-upgrade-tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + from_version: + - 1.2 + - 1.3 + - 1.4 + to_branch: + - main + - release-1.4 + - release-1.3 + exclude: + - from_version: 1.3 + to_branch: release-1.3 + + - from_version: 1.4 + to_branch: release-1.3 + - from_version: 1.4 + to_branch: release-1.4 + + name: 'E2E Upgrade: ${{ matrix.from_version }} => ${{ matrix.to_branch }}' + concurrency: + group: '${{ github.workflow }}-${{ matrix.to_branch }}-${{ matrix.from_version }}' + cancel-in-progress: true + env: + CONTAINER_ENGINE: podman + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 # default branch will be checked out by default on scheduled workflows + with: + fetch-depth: 0 + + - if: ${{ matrix.to_branch != 'main' }} + name: Checkout ${{ matrix.to_branch }} branch + run: git switch ${{ matrix.to_branch }} + + - name: Setup Go + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5 + with: + go-version-file: 'go.mod' + + - name: Determine built operator image + run: | + echo "OPERATOR_IMAGE=$(make show-img)" >> $GITHUB_ENV + + - name: Check if image exists in remote registry + id: operator-image-existence-checker + run: | + echo "OPERATOR_IMAGE_EXISTS=$(if skopeo inspect "docker://${{ env.OPERATOR_IMAGE }}" > /dev/null; then echo "true"; else echo "false"; fi)" >> $GITHUB_OUTPUT + + - name: Display warning if image was not found + if: ${{ steps.operator-image-existence-checker.outputs.OPERATOR_IMAGE_EXISTS == 'false' }} + run: | + echo "::warning ::Target image ${{ env.OPERATOR_IMAGE }} not found for testing the ${{ matrix.to_branch }} branch. It might have expired. E2E tests will be skipped for ${{ matrix.to_branch }}." + + - name: Start Minikube + if: ${{ steps.operator-image-existence-checker.outputs.OPERATOR_IMAGE_EXISTS == 'true' }} + uses: medyagh/setup-minikube@d8c0eb871f6f455542491d86a574477bd3894533 # v0.0.18 + + - name: 'Run E2E tests (RHDH Operator Upgrade path: ${{ matrix.from_version }} => ${{ matrix.to_branch }})' + if: ${{ steps.operator-image-existence-checker.outputs.OPERATOR_IMAGE_EXISTS == 'true' }} + env: + BACKSTAGE_OPERATOR_TESTS_PLATFORM: minikube + PROFILE: 'rhdh' + START_VERSION_MANIFEST: '${{ github.workspace }}/tests/e2e/testdata/rhdh-operator-${{ matrix.from_version }}.yaml' + IMG: ${{ env.OPERATOR_IMAGE }} + run: make test-e2e-upgrade diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml index 5608ea3e..c2635d81 100644 --- a/.github/workflows/nightly.yaml +++ b/.github/workflows/nightly.yaml @@ -12,14 +12,14 @@ jobs: strategy: fail-fast: false matrix: - branch: [ main, release-1.4, release-1.3, 1.2.x ] - test_upgrade: [ 'true', 'false' ] - exclude: - - branch: 1.2.x - test_upgrade: 'true' - name: 'E2E Tests - ${{ matrix.branch }} - upgrade=${{ matrix.test_upgrade }}' + branch: + - main + - release-1.4 + - release-1.3 + - 1.2.x + name: 'E2E Tests on ${{ matrix.branch }}' concurrency: - group: '${{ github.workflow }}-${{ matrix.branch }}-${{ matrix.test_upgrade }}' + group: '${{ github.workflow }}-${{ matrix.branch }}' cancel-in-progress: true env: CONTAINER_TOOL: podman @@ -55,16 +55,8 @@ jobs: if: ${{ steps.operator-image-existence-checker.outputs.OPERATOR_IMAGE_EXISTS == 'true' }} uses: medyagh/setup-minikube@d8c0eb871f6f455542491d86a574477bd3894533 # v0.0.18 - - name: Run E2E tests (RHDH Operator Upgrade path) - if: ${{ matrix.test_upgrade == 'true' && steps.operator-image-existence-checker.outputs.OPERATOR_IMAGE_EXISTS == 'true' }} - env: - BACKSTAGE_OPERATOR_TESTS_PLATFORM: minikube - PROFILE: 'rhdh' - IMG: ${{ env.OPERATOR_IMAGE }} - run: make test-e2e-upgrade - - name: Run E2E tests - if: ${{ matrix.test_upgrade == 'false' && steps.operator-image-existence-checker.outputs.OPERATOR_IMAGE_EXISTS == 'true' }} + if: ${{ steps.operator-image-existence-checker.outputs.OPERATOR_IMAGE_EXISTS == 'true' }} env: BACKSTAGE_OPERATOR_TESTS_PLATFORM: minikube IMG: ${{ env.OPERATOR_IMAGE }} diff --git a/tests/e2e/e2e_upgrade_test.go b/tests/e2e/e2e_upgrade_test.go index 42363f79..91f949df 100644 --- a/tests/e2e/e2e_upgrade_test.go +++ b/tests/e2e/e2e_upgrade_test.go @@ -3,6 +3,7 @@ package e2e import ( "fmt" "io" + "os" "os/exec" "path/filepath" "time" @@ -38,7 +39,8 @@ var _ = Describe("Operator upgrade with existing instances", func() { const managerPodLabel = "control-plane=controller-manager" const crName = "my-backstage-app" - var fromDeploymentManifest = filepath.Join(projectDir, "tests", "e2e", "testdata", "rhdh-operator-1.2.yaml") + var defaultFromDeploymentManifest = filepath.Join(projectDir, "tests", "e2e", "testdata", "rhdh-operator-1.2.yaml") + var fromDeploymentManifest string BeforeEach(func() { if testMode != defaultDeployTestMode { @@ -49,6 +51,12 @@ var _ = Describe("Operator upgrade with existing instances", func() { // because this test needs to start from a previous version, then perform the upgrade. uninstallOperator() + var manifestSet bool + fromDeploymentManifest, manifestSet = os.LookupEnv("START_VERSION_MANIFEST") + if !manifestSet { + fromDeploymentManifest = defaultFromDeploymentManifest + } + cmd := exec.Command(helper.GetPlatformTool(), "apply", "-f", fromDeploymentManifest) _, err := helper.Run(cmd) Expect(err).ShouldNot(HaveOccurred()) diff --git a/tests/e2e/testdata/rhdh-operator-1.3.yaml b/tests/e2e/testdata/rhdh-operator-1.3.yaml new file mode 100644 index 00000000..e830ab52 --- /dev/null +++ b/tests/e2e/testdata/rhdh-operator-1.3.yaml @@ -0,0 +1,1518 @@ +# /!\ EDIT ONLY IF NECESSARY +# +# Generated using `make deployment-manifest` against the release-1.3 branch. +# Used to test the upgrade paths of the operator. +# So this needs to reflect exactly what a user +# would get if they deploy that version of the operator. +# +# Steps to generate an RHDH Operator manifest from the source branch: +# 1. make deployment-manifest +# 2. sed -i "s/backstage-operator/rhdh-operator/g" tests/e2e/testdata/rhdh-operator-*.yaml +# 3. sed -i "s/backstage-system/rhdh-operator/g" tests/e2e/testdata/rhdh-operator-*.yaml +# 4. sed -i "s/backstage-controller-manager/rhdh-controller-manager/g" tests/e2e/testdata/rhdh-operator-*.yaml +# + +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: system + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: namespace + app.kubernetes.io/part-of: rhdh-operator + control-plane: controller-manager + name: rhdh-operator +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: backstages.rhdh.redhat.com +spec: + group: rhdh.redhat.com + names: + kind: Backstage + listKind: BackstageList + plural: backstages + singular: backstage + scope: Namespaced + versions: + - deprecated: true + deprecationWarning: Since 1.3.0 spec.application.image, spec.application.replicas, + spec.application.imagePullSecrets are deprecated in favor of corresponding spec.deployment + fields + name: v1alpha1 + schema: + openAPIV3Schema: + description: Backstage is the Schema for the backstages API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackstageSpec defines the desired state of Backstage + properties: + application: + description: Configuration for Backstage. Optional. + properties: + appConfig: + description: |- + References to existing app-configs ConfigMap objects, that will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret, + and will be mounted inside the main application container under a specified mount directory. + Additionally, each file will be passed as a `--config /mount/path/to/configmap/key` to the + main container args in the order of the entries defined in the AppConfigs list. + But bear in mind that for a single ConfigMap element containing several filenames, + the order in which those files will be appended to the main container args cannot be guaranteed. + So if you want to pass multiple app-config files, it is recommended to pass one ConfigMap per app-config file. + properties: + configMaps: + description: |- + List of ConfigMaps storing the app-config files. Will be mounted as files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + Bear in mind not to put sensitive data in those ConfigMaps. Instead, your app-config content can reference + environment variables (which you can set with the ExtraEnvs field) and/or include extra files (see the ExtraFiles field). + More details on https://backstage.io/docs/conf/writing/. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all app-config files listed in + the ConfigMapRefs field + type: string + type: object + dynamicPluginsConfigMapName: + description: |- + Reference to an existing ConfigMap for Dynamic Plugins. + A new one will be generated with the default config if not set. + The ConfigMap object must have an existing key named: 'dynamic-plugins.yaml'. + type: string + extraEnvs: + description: Extra environment variables + properties: + configMaps: + description: |- + List of references to ConfigMaps objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be injected as additional environment variables. + Otherwise, only the specified key will be injected as an additional environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + envs: + description: List of name and value pairs to add as environment + variables. + items: + properties: + name: + description: Name of the environment variable + type: string + value: + description: Value of the environment variable + type: string + required: + - name + - value + type: object + type: array + secrets: + description: |- + List of references to Secrets objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the Secret will be injected as additional environment variables. + Otherwise, only the specified key will be injected as environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + extraFiles: + description: |- + References to existing Config objects to use as extra config files. + They will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret. + properties: + configMaps: + description: |- + List of references to ConfigMaps objects mounted as extra files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all extra configuration files + listed in the Items field + type: string + secrets: + description: |- + List of references to Secrets objects mounted as extra files under the MountPath specified. + For each item in this array, a key must be specified that will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + image: + description: |- + Custom image to use in all containers (including Init Containers). + It is your responsibility to make sure the image is from trusted sources and has been validated for security compliance + type: string + imagePullSecrets: + description: Image Pull Secrets to use in all containers (including + Init Containers) + items: + type: string + type: array + replicas: + default: 1 + description: |- + Number of desired replicas to set in the Backstage Deployment. + Defaults to 1. + format: int32 + type: integer + route: + description: Route configuration. Used for OpenShift only. + properties: + enabled: + default: true + description: Control the creation of a Route on OpenShift. + type: boolean + host: + description: |- + Host is an alias/DNS that points to the service. Optional. + Ignored if Enabled is false. + If not specified a route name will typically be automatically + chosen. Must follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + subdomain: + description: |- + Subdomain is a DNS subdomain that is requested within the ingress controller's + domain (as a subdomain). + Ignored if Enabled is false. + Example: subdomain `frontend` automatically receives the router subdomain + `apps.mycluster.com` to have a full hostname `frontend.apps.mycluster.com`. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + description: |- + The tls field provides the ability to configure certificates for the route. + Ignored if Enabled is false. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: |- + certificate provides certificate contents. This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. + type: string + externalCertificateSecretName: + description: |- + ExternalCertificateSecretName provides certificate contents as a secret reference. + This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. The secret referenced should + be present in the same namespace as that of the Route. + Forbidden when `certificate` is set. + Note that securing Routes with external certificates in TLS secrets is a Technology Preview feature in OpenShift, + and requires enabling the `RouteExternalCertificate` OpenShift Feature Gate and might not be functionally complete. + type: string + key: + description: key provides key file contents + type: string + type: object + type: object + type: object + database: + description: Configuration for database access. Optional. + properties: + authSecretName: + description: |- + Name of the secret for database authentication. Optional. + For a local database deployment (EnableLocalDb=true), a secret will be auto generated if it does not exist. + The secret shall include information used for the database access. + An example for PostgreSQL DB access: + "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_PORT": "5432" + "POSTGRES_USER": "postgres" + "POSTGRESQL_ADMIN_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_HOST": "backstage-psql-bs1" # For local database, set to "backstage-psql-". + type: string + enableLocalDb: + default: true + description: Control the creation of a local PostgreSQL DB. Set + to false if using for example an external Database for Backstage. + type: boolean + type: object + rawRuntimeConfig: + description: Raw Runtime RuntimeObjects configuration. For Advanced + scenarios. + properties: + backstageConfig: + description: Name of ConfigMap containing Backstage runtime objects + configuration + type: string + localDbConfig: + description: Name of ConfigMap containing LocalDb (PostgreSQL) + runtime objects configuration + type: string + type: object + type: object + status: + description: BackstageStatus defines the observed state of Backstage + properties: + conditions: + description: Conditions is the list of conditions describing the state + of the runtime + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} + - name: v1alpha2 + schema: + openAPIV3Schema: + description: Backstage is the Schema for the backstages API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackstageSpec defines the desired state of Backstage + properties: + application: + description: Configuration for Backstage. Optional. + properties: + appConfig: + description: |- + References to existing app-configs ConfigMap objects, that will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret, + and will be mounted inside the main application container under a specified mount directory. + Additionally, each file will be passed as a `--config /mount/path/to/configmap/key` to the + main container args in the order of the entries defined in the AppConfigs list. + But bear in mind that for a single ConfigMap element containing several filenames, + the order in which those files will be appended to the main container args cannot be guaranteed. + So if you want to pass multiple app-config files, it is recommended to pass one ConfigMap per app-config file. + properties: + configMaps: + description: |- + List of ConfigMaps storing the app-config files. Will be mounted as files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + Bear in mind not to put sensitive data in those ConfigMaps. Instead, your app-config content can reference + environment variables (which you can set with the ExtraEnvs field) and/or include extra files (see the ExtraFiles field). + More details on https://backstage.io/docs/conf/writing/. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all app-config files listed in + the ConfigMapRefs field + type: string + type: object + dynamicPluginsConfigMapName: + description: |- + Reference to an existing ConfigMap for Dynamic Plugins. + A new one will be generated with the default config if not set. + The ConfigMap object must have an existing key named: 'dynamic-plugins.yaml'. + type: string + extraEnvs: + description: Extra environment variables + properties: + configMaps: + description: |- + List of references to ConfigMaps objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be injected as additional environment variables. + Otherwise, only the specified key will be injected as an additional environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + envs: + description: List of name and value pairs to add as environment + variables. + items: + properties: + name: + description: Name of the environment variable + type: string + value: + description: Value of the environment variable + type: string + required: + - name + - value + type: object + type: array + secrets: + description: |- + List of references to Secrets objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the Secret will be injected as additional environment variables. + Otherwise, only the specified key will be injected as environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + extraFiles: + description: |- + References to existing Config objects to use as extra config files. + They will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret. + properties: + configMaps: + description: |- + List of references to ConfigMaps objects mounted as extra files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all extra configuration files + listed in the Items field + type: string + secrets: + description: |- + List of references to Secrets objects mounted as extra files under the MountPath specified. + For each item in this array, a key must be specified that will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + image: + description: |- + Custom image to use in all containers (including Init Containers). + It is your responsibility to make sure the image is from trusted sources and has been validated for security compliance + type: string + imagePullSecrets: + description: Image Pull Secrets to use in all containers (including + Init Containers) + items: + type: string + type: array + replicas: + default: 1 + description: |- + Number of desired replicas to set in the Backstage Deployment. + Defaults to 1. + format: int32 + type: integer + route: + description: Route configuration. Used for OpenShift only. + properties: + enabled: + default: true + description: Control the creation of a Route on OpenShift. + type: boolean + host: + description: |- + Host is an alias/DNS that points to the service. Optional. + Ignored if Enabled is false. + If not specified a route name will typically be automatically + chosen. Must follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + subdomain: + description: |- + Subdomain is a DNS subdomain that is requested within the ingress controller's + domain (as a subdomain). + Ignored if Enabled is false. + Example: subdomain `frontend` automatically receives the router subdomain + `apps.mycluster.com` to have a full hostname `frontend.apps.mycluster.com`. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + description: |- + The tls field provides the ability to configure certificates for the route. + Ignored if Enabled is false. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: |- + certificate provides certificate contents. This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. + type: string + externalCertificateSecretName: + description: |- + ExternalCertificateSecretName provides certificate contents as a secret reference. + This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. The secret referenced should + be present in the same namespace as that of the Route. + Forbidden when `certificate` is set. + Note that securing Routes with external certificates in TLS secrets is a Technology Preview feature in OpenShift, + and requires enabling the `RouteExternalCertificate` OpenShift Feature Gate and might not be functionally complete. + type: string + key: + description: key provides key file contents + type: string + type: object + type: object + type: object + database: + description: Configuration for database access. Optional. + properties: + authSecretName: + description: |- + Name of the secret for database authentication. Optional. + For a local database deployment (EnableLocalDb=true), a secret will be auto generated if it does not exist. + The secret shall include information used for the database access. + An example for PostgreSQL DB access: + "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_PORT": "5432" + "POSTGRES_USER": "postgres" + "POSTGRESQL_ADMIN_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_HOST": "backstage-psql-bs1" # For local database, set to "backstage-psql-". + type: string + enableLocalDb: + default: true + description: Control the creation of a local PostgreSQL DB. Set + to false if using for example an external Database for Backstage. + type: boolean + type: object + deployment: + description: |- + Valid fragment of Deployment to be merged with default/raw configuration. + Set the Deployment's metadata and|or spec fields you want to override or add. + Optional. + properties: + patch: + description: |- + Valid fragment of Deployment to be merged with default/raw configuration. + Set the Deployment's metadata and|or spec fields you want to override or add. + Optional. + x-kubernetes-preserve-unknown-fields: true + type: object + x-kubernetes-preserve-unknown-fields: true + rawRuntimeConfig: + description: Raw Runtime RuntimeObjects configuration. For Advanced + scenarios. + properties: + backstageConfig: + description: Name of ConfigMap containing Backstage runtime objects + configuration + type: string + localDbConfig: + description: Name of ConfigMap containing LocalDb (PostgreSQL) + runtime objects configuration + type: string + type: object + type: object + status: + description: BackstageStatus defines the observed state of Backstage + properties: + conditions: + description: Conditions is the list of conditions describing the state + of the runtime + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: controller-manager + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: serviceaccount + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-controller-manager + namespace: rhdh-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: leader-election-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: role + app.kubernetes.io/part-of: rhdh-operator + name: backstage-leader-election-role + namespace: rhdh-operator +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: backstage-manager-role +rules: +- apiGroups: + - "" + resources: + - configmaps + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - persistentvolumeclaims + - persistentvolumes + verbs: + - get + - list + - watch +- apiGroups: + - apps + resources: + - deployments + - statefulsets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rhdh.redhat.com + resources: + - backstages + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rhdh.redhat.com + resources: + - backstages/finalizers + verbs: + - update +- apiGroups: + - rhdh.redhat.com + resources: + - backstages/status + verbs: + - get + - patch + - update +- apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: metrics-reader + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrole + app.kubernetes.io/part-of: rhdh-operator + name: backstage-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: proxy-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrole + app.kubernetes.io/part-of: rhdh-operator + name: backstage-proxy-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: leader-election-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: rolebinding + app.kubernetes.io/part-of: rhdh-operator + name: backstage-leader-election-rolebinding + namespace: rhdh-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: backstage-leader-election-role +subjects: +- kind: ServiceAccount + name: rhdh-controller-manager + namespace: rhdh-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: manager-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/part-of: rhdh-operator + name: backstage-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: backstage-manager-role +subjects: +- kind: ServiceAccount + name: rhdh-controller-manager + namespace: rhdh-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: proxy-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/part-of: rhdh-operator + name: backstage-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: backstage-proxy-role +subjects: +- kind: ServiceAccount + name: rhdh-controller-manager + namespace: rhdh-operator +--- +apiVersion: v1 +data: + app-config.yaml: | + apiVersion: v1 + kind: ConfigMap + metadata: + name: my-backstage-config-cm1 # placeholder for -default-appconfig + data: + default.app-config.yaml: | + backend: + auth: + externalAccess: + - type: legacy + options: + subject: legacy-default-config + # This is a default value, which you should change by providing your own app-config + secret: "pl4s3Ch4ng3M3" + db-secret.yaml: |- + apiVersion: v1 + kind: Secret + metadata: + name: postgres-secrets # will be replaced + type: Opaque + #stringData: + # POSTGRES_PASSWORD: + # POSTGRES_PORT: "5432" + # POSTGRES_USER: postgres + # POSTGRESQL_ADMIN_PASSWORD: admin123 + # POSTGRES_HOST: bs1-db-service #placeholder -db-service + db-service.yaml: | + apiVersion: v1 + kind: Service + metadata: + name: backstage-psql # placeholder for 'backstage-psql-' .NOTE: For the time it is static and linked to Secret-> postgres-secrets -> OSTGRES_HOST + spec: + selector: + rhdh.redhat.com/app: backstage-psql-cr1 # placeholder for 'backstage-psql-' + clusterIP: None + ports: + - port: 5432 + db-statefulset.yaml: |- + apiVersion: apps/v1 + kind: StatefulSet + metadata: + name: backstage-psql-cr1 # placeholder for 'backstage-psql-' + spec: + podManagementPolicy: OrderedReady + replicas: 1 + selector: + matchLabels: + rhdh.redhat.com/app: backstage-psql-cr1 # placeholder for 'backstage-psql-' + serviceName: backstage-psql-cr1-hl # placeholder for 'backstage-psql--hl' + template: + metadata: + labels: + rhdh.redhat.com/app: backstage-psql-cr1 # placeholder for 'backstage-psql-' + spec: + # fsGroup does not work for Openshift + # AKS/EKS does not work w/o it + #securityContext: + # fsGroup: 26 + automountServiceAccountToken: false + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ + ## The optional .spec.persistentVolumeClaimRetentionPolicy field controls if and how PVCs are deleted during the lifecycle of a StatefulSet. + ## You must enable the StatefulSetAutoDeletePVC feature gate on the API server and the controller manager to use this field. + # persistentVolumeClaimRetentionPolicy: + # whenDeleted: Retain + # whenScaled: Retain + containers: + - env: + - name: POSTGRESQL_PORT_NUMBER + value: "5432" + - name: POSTGRESQL_VOLUME_DIR + value: /var/lib/pgsql/data + - name: PGDATA + value: /var/lib/pgsql/data/userdata + image: quay.io/fedora/postgresql-15:latest # will be replaced with the actual image + imagePullPolicy: IfNotPresent + securityContext: + # runAsUser:26 does not work for Openshift but looks work for AKS/EKS + # runAsUser: 26 + runAsGroup: 0 + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + livenessProbe: + exec: + command: + - /bin/sh + - -c + - exec pg_isready -U ${POSTGRES_USER} -h 127.0.0.1 -p 5432 + failureThreshold: 6 + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + name: postgresql + ports: + - containerPort: 5432 + name: tcp-postgresql + protocol: TCP + readinessProbe: + exec: + command: + - /bin/sh + - -c + - -e + - | + exec pg_isready -U ${POSTGRES_USER} -h 127.0.0.1 -p 5432 + failureThreshold: 6 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 250m + memory: 1024Mi + ephemeral-storage: 20Mi + volumeMounts: + - mountPath: /dev/shm + name: dshm + - mountPath: /var/lib/pgsql/data + name: data + restartPolicy: Always + serviceAccountName: default + volumes: + - emptyDir: + medium: Memory + name: dshm + updateStrategy: + rollingUpdate: + partition: 0 + type: RollingUpdate + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deployment.yaml: | + apiVersion: apps/v1 + kind: Deployment + metadata: + name: backstage # 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: + automountServiceAccountToken: false + # if securityContext not present in AKS/EKS, the error is like this: + #Error: EACCES: permission denied, open '/dynamic-plugins-root/backstage-plugin-scaffolder-backend-module-github-dynamic-0.2.2.tgz' + # fsGroup doesn not work for Openshift + #securityContext: + # fsGroup: 1001 + volumes: + - ephemeral: + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + name: dynamic-plugins-root + - name: dynamic-plugins-npmrc + secret: + defaultMode: 420 + optional: true + secretName: dynamic-plugins-npmrc + - name: dynamic-plugins-registry-auth + secret: + defaultMode: 416 + optional: true + secretName: dynamic-plugins-registry-auth + - emptyDir: {} + name: npmcacache + initContainers: + - name: install-dynamic-plugins + command: + - ./install-dynamic-plugins.sh + - /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:1.3 + imagePullPolicy: IfNotPresent + securityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - 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 + subPath: .npmrc + - mountPath: /opt/app-root/src/.config/containers + name: dynamic-plugins-registry-auth + readOnly: true + - mountPath: /opt/app-root/src/.npm/_cacache + name: npmcacache + workingDir: /opt/app-root/src + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 1000m + memory: 2.5Gi + ephemeral-storage: 5Gi + containers: + - name: backstage-backend + # image will be replaced by the value of the `RELATED_IMAGE_backstage` env var, if set + image: quay.io/rhdh/rhdh-hub-rhel9:1.3 + imagePullPolicy: IfNotPresent + args: + - "--config" + - "dynamic-plugins-root/app-config.dynamic-plugins.yaml" + securityContext: + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + runAsNonRoot: true + allowPrivilegeEscalation: false + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthcheck + port: 7007 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 2 + timeoutSeconds: 2 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthcheck + port: 7007 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + ports: + - name: backend + containerPort: 7007 + 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 + memory: 256Mi + limits: + cpu: 1000m + memory: 2.5Gi + ephemeral-storage: 5Gi + dynamic-plugins.yaml: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: default-dynamic-plugins # must be the same as (deployment.yaml).spec.template.spec.volumes.name.dynamic-plugins-conf.configMap.name + data: + "dynamic-plugins.yaml": | + includes: + - dynamic-plugins.default.yaml + plugins: [] + route.yaml: |- + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + name: route # placeholder for 'backstage-' + spec: + port: + targetPort: http-backend + path: / + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + to: + kind: Service + name: # placeholder for 'backstage-' + secret-envs.yaml: | + apiVersion: v1 + kind: Secret + metadata: + name: backend-auth-secret + stringData: + # generated with the command below (from https://janus-idp.io/docs/auth/service-to-service-auth/#setup): + # node -p 'require("crypto").randomBytes(24).toString("base64")' + BACKEND_SECRET: "R2FxRVNrcmwzYzhhN3l0V1VRcnQ3L1pLT09WaVhDNUEK" # notsecret + service.yaml: |- + apiVersion: v1 + kind: Service + metadata: + name: backstage # placeholder for 'backstage-' + spec: + type: ClusterIP + selector: + rhdh.redhat.com/app: # placeholder for 'backstage-' + ports: + - name: http-backend + port: 80 + targetPort: backend +kind: ConfigMap +metadata: + name: backstage-default-config + namespace: rhdh-operator +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: kube-rbac-proxy + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: controller-manager-metrics-service + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: service + app.kubernetes.io/part-of: rhdh-operator + control-plane: controller-manager + name: rhdh-controller-manager-metrics-service + namespace: rhdh-operator +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + control-plane: controller-manager +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: controller-manager + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: deployment + app.kubernetes.io/part-of: rhdh-operator + control-plane: controller-manager + name: rhdh-controller-manager + namespace: rhdh-operator +spec: + replicas: 1 + selector: + matchLabels: + control-plane: controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - arm64 + - ppc64le + - s390x + - key: kubernetes.io/os + operator: In + values: + - linux + automountServiceAccountToken: true + containers: + - args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --logtostderr=true + - --v=0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.13.0 + name: kube-rbac-proxy + ports: + - containerPort: 8443 + name: https + protocol: TCP + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 5m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 + - --leader-elect + command: + - /manager + env: + - name: RELATED_IMAGE_postgresql + value: quay.io/fedora/postgresql-15:latest + - name: RELATED_IMAGE_backstage + value: quay.io/rhdh/rhdh-hub-rhel9:1.3 + image: quay.io/rhdh-community/operator:0.3.2 + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 500m + ephemeral-storage: 20Mi + memory: 1Gi + requests: + cpu: 10m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + volumeMounts: + - mountPath: /default-config + name: default-config + securityContext: + runAsNonRoot: true + serviceAccountName: rhdh-controller-manager + terminationGracePeriodSeconds: 10 + volumes: + - configMap: + name: backstage-default-config + name: default-config diff --git a/tests/e2e/testdata/rhdh-operator-1.4.yaml b/tests/e2e/testdata/rhdh-operator-1.4.yaml new file mode 100644 index 00000000..8dc43798 --- /dev/null +++ b/tests/e2e/testdata/rhdh-operator-1.4.yaml @@ -0,0 +1,1977 @@ +# /!\ EDIT ONLY IF NECESSARY +# +# Generated using `make deployment-manifest` against the release-1.4 branch. +# Used to test the upgrade paths of the operator. +# So this needs to reflect exactly what a user +# would get if they deploy that version of the operator. +# +# Steps to generate an RHDH Operator manifest from the source branch: +# 1. make deployment-manifest +# 2. sed -i "s/backstage-operator/rhdh-operator/g" tests/e2e/testdata/rhdh-operator-*.yaml +# 3. sed -i "s/backstage-system/rhdh-operator/g" tests/e2e/testdata/rhdh-operator-*.yaml +# 4. sed -i "s/backstage-controller-manager/rhdh-controller-manager/g" tests/e2e/testdata/rhdh-operator-*.yaml +# + +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: system + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: namespace + app.kubernetes.io/part-of: rhdh-operator + control-plane: controller-manager + name: rhdh-operator +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: backstages.rhdh.redhat.com +spec: + group: rhdh.redhat.com + names: + kind: Backstage + listKind: BackstageList + plural: backstages + singular: backstage + scope: Namespaced + versions: + - deprecated: true + deprecationWarning: Since 1.3.0 spec.application.image, spec.application.replicas, + spec.application.imagePullSecrets are deprecated in favor of corresponding spec.deployment + fields + name: v1alpha1 + schema: + openAPIV3Schema: + description: Backstage is the Schema for the backstages API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackstageSpec defines the desired state of Backstage + properties: + application: + description: Configuration for Backstage. Optional. + properties: + appConfig: + description: |- + References to existing app-configs ConfigMap objects, that will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret, + and will be mounted inside the main application container under a specified mount directory. + Additionally, each file will be passed as a `--config /mount/path/to/configmap/key` to the + main container args in the order of the entries defined in the AppConfigs list. + But bear in mind that for a single ConfigMap element containing several filenames, + the order in which those files will be appended to the main container args cannot be guaranteed. + So if you want to pass multiple app-config files, it is recommended to pass one ConfigMap per app-config file. + properties: + configMaps: + description: |- + List of ConfigMaps storing the app-config files. Will be mounted as files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + Bear in mind not to put sensitive data in those ConfigMaps. Instead, your app-config content can reference + environment variables (which you can set with the ExtraEnvs field) and/or include extra files (see the ExtraFiles field). + More details on https://backstage.io/docs/conf/writing/. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all app-config files listed in + the ConfigMapRefs field + type: string + type: object + dynamicPluginsConfigMapName: + description: |- + Reference to an existing ConfigMap for Dynamic Plugins. + A new one will be generated with the default config if not set. + The ConfigMap object must have an existing key named: 'dynamic-plugins.yaml'. + type: string + extraEnvs: + description: Extra environment variables + properties: + configMaps: + description: |- + List of references to ConfigMaps objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be injected as additional environment variables. + Otherwise, only the specified key will be injected as an additional environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + envs: + description: List of name and value pairs to add as environment + variables. + items: + properties: + name: + description: Name of the environment variable + type: string + value: + description: Value of the environment variable + type: string + required: + - name + - value + type: object + type: array + secrets: + description: |- + List of references to Secrets objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the Secret will be injected as additional environment variables. + Otherwise, only the specified key will be injected as environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + extraFiles: + description: |- + References to existing Config objects to use as extra config files. + They will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret. + properties: + configMaps: + description: |- + List of references to ConfigMaps objects mounted as extra files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all extra configuration files + listed in the Items field + type: string + secrets: + description: |- + List of references to Secrets objects mounted as extra files under the MountPath specified. + For each item in this array, a key must be specified that will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + image: + description: |- + Custom image to use in all containers (including Init Containers). + It is your responsibility to make sure the image is from trusted sources and has been validated for security compliance + type: string + imagePullSecrets: + description: Image Pull Secrets to use in all containers (including + Init Containers) + items: + type: string + type: array + replicas: + default: 1 + description: |- + Number of desired replicas to set in the Backstage Deployment. + Defaults to 1. + format: int32 + type: integer + route: + description: Route configuration. Used for OpenShift only. + properties: + enabled: + default: true + description: Control the creation of a Route on OpenShift. + type: boolean + host: + description: |- + Host is an alias/DNS that points to the service. Optional. + Ignored if Enabled is false. + If not specified a route name will typically be automatically + chosen. Must follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + subdomain: + description: |- + Subdomain is a DNS subdomain that is requested within the ingress controller's + domain (as a subdomain). + Ignored if Enabled is false. + Example: subdomain `frontend` automatically receives the router subdomain + `apps.mycluster.com` to have a full hostname `frontend.apps.mycluster.com`. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + description: |- + The tls field provides the ability to configure certificates for the route. + Ignored if Enabled is false. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: |- + certificate provides certificate contents. This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. + type: string + externalCertificateSecretName: + description: |- + ExternalCertificateSecretName provides certificate contents as a secret reference. + This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. The secret referenced should + be present in the same namespace as that of the Route. + Forbidden when `certificate` is set. + Note that securing Routes with external certificates in TLS secrets is a Technology Preview feature in OpenShift, + and requires enabling the `RouteExternalCertificate` OpenShift Feature Gate and might not be functionally complete. + type: string + key: + description: key provides key file contents + type: string + type: object + type: object + type: object + database: + description: Configuration for database access. Optional. + properties: + authSecretName: + description: |- + Name of the secret for database authentication. Optional. + For a local database deployment (EnableLocalDb=true), a secret will be auto generated if it does not exist. + The secret shall include information used for the database access. + An example for PostgreSQL DB access: + "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_PORT": "5432" + "POSTGRES_USER": "postgres" + "POSTGRESQL_ADMIN_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_HOST": "backstage-psql-bs1" # For local database, set to "backstage-psql-". + type: string + enableLocalDb: + default: true + description: Control the creation of a local PostgreSQL DB. Set + to false if using for example an external Database for Backstage. + type: boolean + type: object + rawRuntimeConfig: + description: Raw Runtime RuntimeObjects configuration. For Advanced + scenarios. + properties: + backstageConfig: + description: Name of ConfigMap containing Backstage runtime objects + configuration + type: string + localDbConfig: + description: Name of ConfigMap containing LocalDb (PostgreSQL) + runtime objects configuration + type: string + type: object + type: object + status: + description: BackstageStatus defines the observed state of Backstage + properties: + conditions: + description: Conditions is the list of conditions describing the state + of the runtime + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} + - name: v1alpha2 + schema: + openAPIV3Schema: + description: Backstage is the Schema for the backstages API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackstageSpec defines the desired state of Backstage + properties: + application: + description: Configuration for Backstage. Optional. + properties: + appConfig: + description: |- + References to existing app-configs ConfigMap objects, that will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret, + and will be mounted inside the main application container under a specified mount directory. + Additionally, each file will be passed as a `--config /mount/path/to/configmap/key` to the + main container args in the order of the entries defined in the AppConfigs list. + But bear in mind that for a single ConfigMap element containing several filenames, + the order in which those files will be appended to the main container args cannot be guaranteed. + So if you want to pass multiple app-config files, it is recommended to pass one ConfigMap per app-config file. + properties: + configMaps: + description: |- + List of ConfigMaps storing the app-config files. Will be mounted as files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + Bear in mind not to put sensitive data in those ConfigMaps. Instead, your app-config content can reference + environment variables (which you can set with the ExtraEnvs field) and/or include extra files (see the ExtraFiles field). + More details on https://backstage.io/docs/conf/writing/. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all app-config files listed in + the ConfigMapRefs field + type: string + type: object + dynamicPluginsConfigMapName: + description: |- + Reference to an existing ConfigMap for Dynamic Plugins. + A new one will be generated with the default config if not set. + The ConfigMap object must have an existing key named: 'dynamic-plugins.yaml'. + type: string + extraEnvs: + description: Extra environment variables + properties: + configMaps: + description: |- + List of references to ConfigMaps objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be injected as additional environment variables. + Otherwise, only the specified key will be injected as an additional environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + envs: + description: List of name and value pairs to add as environment + variables. + items: + properties: + name: + description: Name of the environment variable + type: string + value: + description: Value of the environment variable + type: string + required: + - name + - value + type: object + type: array + secrets: + description: |- + List of references to Secrets objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the Secret will be injected as additional environment variables. + Otherwise, only the specified key will be injected as environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + extraFiles: + description: |- + References to existing Config objects to use as extra config files. + They will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret. + properties: + configMaps: + description: |- + List of references to ConfigMaps objects mounted as extra files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all extra configuration files + listed in the Items field + type: string + secrets: + description: |- + List of references to Secrets objects mounted as extra files under the MountPath specified. + For each item in this array, a key must be specified that will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + image: + description: |- + Custom image to use in all containers (including Init Containers). + It is your responsibility to make sure the image is from trusted sources and has been validated for security compliance + type: string + imagePullSecrets: + description: Image Pull Secrets to use in all containers (including + Init Containers) + items: + type: string + type: array + replicas: + default: 1 + description: |- + Number of desired replicas to set in the Backstage Deployment. + Defaults to 1. + format: int32 + type: integer + route: + description: Route configuration. Used for OpenShift only. + properties: + enabled: + default: true + description: Control the creation of a Route on OpenShift. + type: boolean + host: + description: |- + Host is an alias/DNS that points to the service. Optional. + Ignored if Enabled is false. + If not specified a route name will typically be automatically + chosen. Must follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + subdomain: + description: |- + Subdomain is a DNS subdomain that is requested within the ingress controller's + domain (as a subdomain). + Ignored if Enabled is false. + Example: subdomain `frontend` automatically receives the router subdomain + `apps.mycluster.com` to have a full hostname `frontend.apps.mycluster.com`. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + description: |- + The tls field provides the ability to configure certificates for the route. + Ignored if Enabled is false. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: |- + certificate provides certificate contents. This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. + type: string + externalCertificateSecretName: + description: |- + ExternalCertificateSecretName provides certificate contents as a secret reference. + This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. The secret referenced should + be present in the same namespace as that of the Route. + Forbidden when `certificate` is set. + Note that securing Routes with external certificates in TLS secrets is a Technology Preview feature in OpenShift, + and requires enabling the `RouteExternalCertificate` OpenShift Feature Gate and might not be functionally complete. + type: string + key: + description: key provides key file contents + type: string + type: object + type: object + type: object + database: + description: Configuration for database access. Optional. + properties: + authSecretName: + description: |- + Name of the secret for database authentication. Optional. + For a local database deployment (EnableLocalDb=true), a secret will be auto generated if it does not exist. + The secret shall include information used for the database access. + An example for PostgreSQL DB access: + "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_PORT": "5432" + "POSTGRES_USER": "postgres" + "POSTGRESQL_ADMIN_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_HOST": "backstage-psql-bs1" # For local database, set to "backstage-psql-". + type: string + enableLocalDb: + default: true + description: Control the creation of a local PostgreSQL DB. Set + to false if using for example an external Database for Backstage. + type: boolean + type: object + deployment: + description: |- + Configuration for Backstage Deployment resource. + Optional. + properties: + patch: + description: |- + Valid fragment of Deployment to be merged with default/raw configuration. + Set the Deployment's metadata and|or spec fields you want to override or add. + Optional. + x-kubernetes-preserve-unknown-fields: true + type: object + rawRuntimeConfig: + description: Raw Runtime RuntimeObjects configuration. For Advanced + scenarios. + properties: + backstageConfig: + description: Name of ConfigMap containing Backstage runtime objects + configuration + type: string + localDbConfig: + description: Name of ConfigMap containing LocalDb (PostgreSQL) + runtime objects configuration + type: string + type: object + type: object + status: + description: BackstageStatus defines the observed state of Backstage + properties: + conditions: + description: Conditions is the list of conditions describing the state + of the runtime + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: false + subresources: + status: {} + - name: v1alpha3 + schema: + openAPIV3Schema: + description: Backstage is the Schema for the backstages API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackstageSpec defines the desired state of Backstage + properties: + application: + description: Configuration for Backstage. Optional. + properties: + appConfig: + description: |- + References to existing app-configs ConfigMap objects, that will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret, + and will be mounted inside the main application container under a specified mount directory. + Additionally, each file will be passed as a `--config /mount/path/to/configmap/key` to the + main container args in the order of the entries defined in the AppConfigs list. + But bear in mind that for a single ConfigMap element containing several filenames, + the order in which those files will be appended to the main container args cannot be guaranteed. + So if you want to pass multiple app-config files, it is recommended to pass one ConfigMap per app-config file. + properties: + configMaps: + description: |- + List of ConfigMaps storing the app-config files. Will be mounted as files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + Bear in mind not to put sensitive data in those ConfigMaps. Instead, your app-config content can reference + environment variables (which you can set with the ExtraEnvs field) and/or include extra files (see the ExtraFiles field). + More details on https://backstage.io/docs/conf/writing/. + items: + properties: + key: + description: Key in the object + type: string + mountPath: + description: Path to mount the Object. If not specified + default-path/Name will be used + type: string + name: + description: |- + Name of the object + Supported ConfigMaps and Secrets + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all app-config files listed in + the ConfigMapRefs field + type: string + type: object + dynamicPluginsConfigMapName: + description: |- + Reference to an existing ConfigMap for Dynamic Plugins. + A new one will be generated with the default config if not set. + The ConfigMap object must have an existing key named: 'dynamic-plugins.yaml'. + type: string + extraEnvs: + description: Extra environment variables + properties: + configMaps: + description: |- + List of references to ConfigMaps objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be injected as additional environment variables. + Otherwise, only the specified key will be injected as an additional environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + envs: + description: List of name and value pairs to add as environment + variables. + items: + properties: + name: + description: Name of the environment variable + type: string + value: + description: Value of the environment variable + type: string + required: + - name + - value + type: object + type: array + secrets: + description: |- + List of references to Secrets objects to inject as additional environment variables. + For each item in this array, if a key is not specified, it means that all keys in the Secret will be injected as additional environment variables. + Otherwise, only the specified key will be injected as environment variable. + items: + properties: + key: + description: Key in the object + type: string + name: + description: |- + Name of the object + We support only ConfigMaps and Secrets. + type: string + required: + - name + type: object + type: array + type: object + extraFiles: + description: |- + References to existing Config objects to use as extra config files. + They will be mounted as files in the specified mount path. + Each element can be a reference to any ConfigMap or Secret. + properties: + configMaps: + description: |- + List of references to ConfigMaps objects mounted as extra files under the MountPath specified. + For each item in this array, if a key is not specified, it means that all keys in the ConfigMap will be mounted as files. + Otherwise, only the specified key will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + mountPath: + description: Path to mount the Object. If not specified + default-path/Name will be used + type: string + name: + description: |- + Name of the object + Supported ConfigMaps and Secrets + type: string + required: + - name + type: object + type: array + mountPath: + default: /opt/app-root/src + description: Mount path for all extra configuration files + listed in the Items field + type: string + pvcs: + description: |- + List of references to Persistent Volume Claim objects mounted as extra files + For each item in this array, a key must be specified that will be mounted as a file. + items: + properties: + mountPath: + description: Path to mount PVC. If not specified default-path/Name + will be used + type: string + name: + description: Name of the object + type: string + required: + - name + type: object + type: array + secrets: + description: |- + List of references to Secrets objects mounted as extra files under the MountPath specified. + For each item in this array, a key must be specified that will be mounted as a file. + items: + properties: + key: + description: Key in the object + type: string + mountPath: + description: Path to mount the Object. If not specified + default-path/Name will be used + type: string + name: + description: |- + Name of the object + Supported ConfigMaps and Secrets + type: string + required: + - name + type: object + type: array + type: object + image: + description: |- + Custom image to use in all containers (including Init Containers). + It is your responsibility to make sure the image is from trusted sources and has been validated for security compliance + type: string + imagePullSecrets: + description: Image Pull Secrets to use in all containers (including + Init Containers) + items: + type: string + type: array + replicas: + default: 1 + description: |- + Number of desired replicas to set in the Backstage Deployment. + Defaults to 1. + format: int32 + type: integer + route: + description: Route configuration. Used for OpenShift only. + properties: + enabled: + default: true + description: Control the creation of a Route on OpenShift. + type: boolean + host: + description: |- + Host is an alias/DNS that points to the service. Optional. + Ignored if Enabled is false. + If not specified a route name will typically be automatically + chosen. Must follow DNS952 subdomain conventions. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + subdomain: + description: |- + Subdomain is a DNS subdomain that is requested within the ingress controller's + domain (as a subdomain). + Ignored if Enabled is false. + Example: subdomain `frontend` automatically receives the router subdomain + `apps.mycluster.com` to have a full hostname `frontend.apps.mycluster.com`. + maxLength: 253 + pattern: ^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$ + type: string + tls: + description: |- + The tls field provides the ability to configure certificates for the route. + Ignored if Enabled is false. + properties: + caCertificate: + description: caCertificate provides the cert authority + certificate contents + type: string + certificate: + description: |- + certificate provides certificate contents. This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. + type: string + externalCertificateSecretName: + description: |- + ExternalCertificateSecretName provides certificate contents as a secret reference. + This should be a single serving certificate, not a certificate + chain. Do not include a CA certificate. The secret referenced should + be present in the same namespace as that of the Route. + Forbidden when `certificate` is set. + Note that securing Routes with external certificates in TLS secrets is a Technology Preview feature in OpenShift, + and requires enabling the `RouteExternalCertificate` OpenShift Feature Gate and might not be functionally complete. + type: string + key: + description: key provides key file contents + type: string + type: object + type: object + type: object + database: + description: Configuration for database access. Optional. + properties: + authSecretName: + description: |- + Name of the secret for database authentication. Optional. + For a local database deployment (EnableLocalDb=true), a secret will be auto generated if it does not exist. + The secret shall include information used for the database access. + An example for PostgreSQL DB access: + "POSTGRES_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_PORT": "5432" + "POSTGRES_USER": "postgres" + "POSTGRESQL_ADMIN_PASSWORD": "rl4s3Fh4ng3M4" + "POSTGRES_HOST": "backstage-psql-bs1" # For local database, set to "backstage-psql-". + type: string + enableLocalDb: + default: true + description: Control the creation of a local PostgreSQL DB. Set + to false if using for example an external Database for Backstage. + type: boolean + type: object + deployment: + description: |- + Valid fragment of Deployment to be merged with default/raw configuration. + Set the Deployment's metadata and|or spec fields you want to override or add. + Optional. + properties: + patch: + description: |- + Valid fragment of Deployment to be merged with default/raw configuration. + Set the Deployment's metadata and|or spec fields you want to override or add. + Optional. + x-kubernetes-preserve-unknown-fields: true + type: object + rawRuntimeConfig: + description: Raw Runtime RuntimeObjects configuration. For Advanced + scenarios. + properties: + backstageConfig: + description: Name of ConfigMap containing Backstage runtime objects + configuration + type: string + localDbConfig: + description: Name of ConfigMap containing LocalDb (PostgreSQL) + runtime objects configuration + type: string + type: object + type: object + status: + description: BackstageStatus defines the observed state of Backstage + properties: + conditions: + description: Conditions is the list of conditions describing the state + of the runtime + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: controller-manager + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: serviceaccount + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-controller-manager + namespace: rhdh-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: leader-election-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: role + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-leader-election-role + namespace: rhdh-operator +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: backstage-editor-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrole + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-backstage-editor-role +rules: +- apiGroups: + - rhdh.redhat.com + resources: + - backstages + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rhdh.redhat.com + resources: + - backstages/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: backstage-viewer-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrole + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-backstage-viewer-role +rules: +- apiGroups: + - rhdh.redhat.com + resources: + - backstages + verbs: + - get + - list + - watch +- apiGroups: + - rhdh.redhat.com + resources: + - backstages/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: rhdh-manager-role +rules: +- apiGroups: + - "" + resources: + - configmaps + - persistentvolumeclaims + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - persistentvolumes + verbs: + - get + - list + - watch +- apiGroups: + - apps + resources: + - deployments + - statefulsets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rhdh.redhat.com + resources: + - backstages + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - rhdh.redhat.com + resources: + - backstages/finalizers + verbs: + - update +- apiGroups: + - rhdh.redhat.com + resources: + - backstages/status + verbs: + - get + - patch + - update +- apiGroups: + - route.openshift.io + resources: + - routes + - routes/custom-host + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: metrics-auth-role + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: metrics-auth-role + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-metrics-auth-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: metrics-rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: metrics-reader + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrole + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: leader-election-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: rolebinding + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-leader-election-rolebinding + namespace: rhdh-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: rhdh-leader-election-role +subjects: +- kind: ServiceAccount + name: rhdh-controller-manager + namespace: rhdh-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: manager-rolebinding + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: clusterrolebinding + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: rhdh-manager-role +subjects: +- kind: ServiceAccount + name: rhdh-controller-manager + namespace: rhdh-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: metrics-auth-rolebinding + app.kubernetes.io/name: metrics-auth-rolebinding + app.kubernetes.io/part-of: rhdh-operator + name: rhdh-metrics-auth-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: rhdh-metrics-auth-role +subjects: +- kind: ServiceAccount + name: rhdh-controller-manager + namespace: rhdh-operator +--- +apiVersion: v1 +data: + app-config.yaml: | + apiVersion: v1 + kind: ConfigMap + metadata: + name: my-backstage-config-cm1 # placeholder for -default-appconfig + data: + default.app-config.yaml: | + backend: + auth: + externalAccess: + - type: legacy + options: + subject: legacy-default-config + # This is a default value, which you should change by providing your own app-config + secret: "pl4s3Ch4ng3M3" + auth: + providers: {} + db-secret.yaml: |- + apiVersion: v1 + kind: Secret + metadata: + name: postgres-secrets # will be replaced + type: Opaque + #stringData: + # POSTGRES_PASSWORD: + # POSTGRES_PORT: "5432" + # POSTGRES_USER: postgres + # POSTGRESQL_ADMIN_PASSWORD: admin123 + # POSTGRES_HOST: bs1-db-service #placeholder -db-service + db-service.yaml: | + apiVersion: v1 + kind: Service + metadata: + name: backstage-psql # placeholder for 'backstage-psql-' .NOTE: For the time it is static and linked to Secret-> postgres-secrets -> OSTGRES_HOST + spec: + selector: + rhdh.redhat.com/app: backstage-psql-cr1 # placeholder for 'backstage-psql-' + clusterIP: None + ports: + - port: 5432 + db-statefulset.yaml: |- + apiVersion: apps/v1 + kind: StatefulSet + metadata: + name: backstage-psql-cr1 # placeholder for 'backstage-psql-' + spec: + podManagementPolicy: OrderedReady + replicas: 1 + selector: + matchLabels: + rhdh.redhat.com/app: backstage-psql-cr1 # placeholder for 'backstage-psql-' + serviceName: backstage-psql-cr1-hl # placeholder for 'backstage-psql--hl' + template: + metadata: + labels: + rhdh.redhat.com/app: backstage-psql-cr1 # placeholder for 'backstage-psql-' + spec: + # fsGroup does not work for Openshift + # AKS/EKS does not work w/o it + #securityContext: + # fsGroup: 26 + automountServiceAccountToken: false + ## https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ + ## The optional .spec.persistentVolumeClaimRetentionPolicy field controls if and how PVCs are deleted during the lifecycle of a StatefulSet. + ## You must enable the StatefulSetAutoDeletePVC feature gate on the API server and the controller manager to use this field. + # persistentVolumeClaimRetentionPolicy: + # whenDeleted: Retain + # whenScaled: Retain + containers: + - env: + - name: POSTGRESQL_PORT_NUMBER + value: "5432" + - name: POSTGRESQL_VOLUME_DIR + value: /var/lib/pgsql/data + - name: PGDATA + value: /var/lib/pgsql/data/userdata + image: quay.io/fedora/postgresql-15:latest # will be replaced with the actual image + imagePullPolicy: IfNotPresent + securityContext: + # runAsUser:26 does not work for Openshift but looks work for AKS/EKS + # runAsUser: 26 + runAsGroup: 0 + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - ALL + livenessProbe: + exec: + command: + - /bin/sh + - -c + - exec pg_isready -U ${POSTGRES_USER} -h 127.0.0.1 -p 5432 + failureThreshold: 6 + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + name: postgresql + ports: + - containerPort: 5432 + name: tcp-postgresql + protocol: TCP + readinessProbe: + exec: + command: + - /bin/sh + - -c + - -e + - | + exec pg_isready -U ${POSTGRES_USER} -h 127.0.0.1 -p 5432 + failureThreshold: 6 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 250m + memory: 1024Mi + ephemeral-storage: 20Mi + volumeMounts: + - mountPath: /dev/shm + name: dshm + - mountPath: /var/lib/pgsql/data + name: data + restartPolicy: Always + serviceAccountName: default + volumes: + - emptyDir: + medium: Memory + name: dshm + updateStrategy: + rollingUpdate: + partition: 0 + type: RollingUpdate + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + deployment.yaml: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: backstage # 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: + automountServiceAccountToken: false + # if securityContext not present in AKS/EKS, the error is like this: + #Error: EACCES: permission denied, open '/dynamic-plugins-root/backstage-plugin-scaffolder-backend-module-github-dynamic-0.2.2.tgz' + # fsGroup doesn not work for Openshift + #securityContext: + # fsGroup: 1001 + volumes: + - ephemeral: + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + name: dynamic-plugins-root + - name: dynamic-plugins-npmrc + secret: + defaultMode: 420 + optional: true + secretName: dynamic-plugins-npmrc + - name: dynamic-plugins-registry-auth + secret: + defaultMode: 416 + optional: true + secretName: dynamic-plugins-registry-auth + - emptyDir: {} + name: npmcacache + initContainers: + - name: install-dynamic-plugins + command: + - ./install-dynamic-plugins.sh + - /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 + securityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + capabilities: + drop: + - 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 + subPath: .npmrc + - mountPath: /opt/app-root/src/.config/containers + name: dynamic-plugins-registry-auth + readOnly: true + - mountPath: /opt/app-root/src/.npm/_cacache + name: npmcacache + workingDir: /opt/app-root/src + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 1000m + memory: 2.5Gi + ephemeral-storage: 5Gi + containers: + - name: backstage-backend + # 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 + args: + - "--config" + - "dynamic-plugins-root/app-config.dynamic-plugins.yaml" + securityContext: + capabilities: + drop: + - ALL + seccompProfile: + type: RuntimeDefault + runAsNonRoot: true + allowPrivilegeEscalation: false + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthcheck + port: 7007 + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 2 + timeoutSeconds: 2 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthcheck + port: 7007 + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + ports: + - name: backend + containerPort: 7007 + 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 + memory: 256Mi + limits: + cpu: 1000m + memory: 2.5Gi + ephemeral-storage: 5Gi + workingDir: /opt/app-root/src + dynamic-plugins.yaml: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: default-dynamic-plugins # must be the same as (deployment.yaml).spec.template.spec.volumes.name.dynamic-plugins-conf.configMap.name + data: + "dynamic-plugins.yaml": | + includes: + - dynamic-plugins.default.yaml + plugins: [] + route.yaml: |- + apiVersion: route.openshift.io/v1 + kind: Route + metadata: + name: route # placeholder for 'backstage-' + spec: + port: + targetPort: http-backend + path: / + tls: + insecureEdgeTerminationPolicy: Redirect + termination: edge + to: + kind: Service + name: # placeholder for 'backstage-' + secret-envs.yaml: | + apiVersion: v1 + kind: Secret + metadata: + name: backend-auth-secret + stringData: + # generated with the command below (from https://janus-idp.io/docs/auth/service-to-service-auth/#setup): + # node -p 'require("crypto").randomBytes(24).toString("base64")' + BACKEND_SECRET: "R2FxRVNrcmwzYzhhN3l0V1VRcnQ3L1pLT09WaVhDNUEK" # notsecret + service.yaml: |- + apiVersion: v1 + kind: Service + metadata: + name: backstage # placeholder for 'backstage-' + spec: + type: ClusterIP + selector: + rhdh.redhat.com/app: # placeholder for 'backstage-' + ports: + - name: http-backend + port: 80 + targetPort: backend + - name: http-metrics + protocol: TCP + port: 9464 + targetPort: 9464 +kind: ConfigMap +metadata: + name: rhdh-default-config + namespace: rhdh-operator +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: metrics + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: controller-manager-metrics-service + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: service + app.kubernetes.io/part-of: rhdh-operator + control-plane: controller-manager + name: rhdh-controller-manager-metrics-service + namespace: rhdh-operator +spec: + ports: + - name: metrics + port: 8443 + protocol: TCP + targetPort: metrics + selector: + control-plane: controller-manager +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: manager + app.kubernetes.io/created-by: rhdh-operator + app.kubernetes.io/instance: controller-manager + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: deployment + app.kubernetes.io/part-of: rhdh-operator + control-plane: controller-manager + name: rhdh-controller-manager + namespace: rhdh-operator +spec: + replicas: 1 + selector: + matchLabels: + control-plane: controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/arch + operator: In + values: + - amd64 + - key: kubernetes.io/os + operator: In + values: + - linux + automountServiceAccountToken: true + containers: + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=:8443 + - --metrics-secure=true + - --leader-elect + command: + - /manager + env: + - name: OPERATOR_NAME + value: rhdh-operator + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: RELATED_IMAGE_postgresql + value: quay.io/fedora/postgresql-15:latest + - name: RELATED_IMAGE_backstage + value: quay.io/rhdh/rhdh-hub-rhel9:1.4 + image: quay.io/rhdh-community/operator:0.4.0 + livenessProbe: + httpGet: + path: /healthz + port: health + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + ports: + - containerPort: 8081 + name: health + - containerPort: 8443 + name: metrics + readinessProbe: + httpGet: + path: /readyz + port: health + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 500m + ephemeral-storage: 20Mi + memory: 1Gi + requests: + cpu: 10m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + volumeMounts: + - mountPath: /default-config + name: default-config + securityContext: + runAsNonRoot: true + serviceAccountName: rhdh-controller-manager + terminationGracePeriodSeconds: 10 + volumes: + - configMap: + name: rhdh-default-config + name: default-config