diff --git a/docs/changelog.md b/docs/changelog.md index b194df9..e74d723 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,13 +2,27 @@ All notable changes to this project will be documented in this file. -## [1.1.41] - Current +## [1.1.42] - Current + +### Changed + +- **Operator installation moved to global setup**: RHDH operator installation now runs once during global setup in parallel with Keycloak deployment, instead of per-project deployment. `_deployWithOperator()` now only applies the Backstage CR per-namespace. + +### Fixed + +- **Version args passed incorrectly to install-rhdh-catalog-source.sh**: Fixed zx quoting bug where version args were passed as a single string instead of an array. + +### Added + +- **`SKIP_OPERATOR_INSTALLATION`**: New env var to skip operator installation in global setup when the operator is already installed. + +## [1.1.41] ### Added - **`useNewFrontendSystem`** — Backstage **app-next** / new frontend system: merges NFS layers from `config/new-frontend-system/` (secrets, default **app-auth** and **app-integrations** plugins, Helm `value_file.yaml`) into the same merge pipelines as common/auth/user. Secrets are merged with other layers **before** a single `envsubst` pass. NFS dynamic plugins act as **defaults** (workspace `tests/config/dynamic-plugins.yaml` overrides). **Auto-detection:** enabled when the namespace ends with `-app-next` or `USE_NEW_FRONTEND_SYSTEM=true`, unless `useNewFrontendSystem: false` is passed. Optional workspace `tests/config/value_file-app-next.yaml` is still merged last for Helm. -## [1.1.40] - Current +## [1.1.40] ### Changed diff --git a/docs/guide/configuration/environment-variables.md b/docs/guide/configuration/environment-variables.md index f4d9d44..10f74f2 100644 --- a/docs/guide/configuration/environment-variables.md +++ b/docs/guide/configuration/environment-variables.md @@ -34,6 +34,7 @@ These are set automatically during deployment: | `CI` | Enables auto-cleanup | - | | `CHART_URL` | Custom Helm chart URL | `oci://quay.io/rhdh/chart` | | `SKIP_KEYCLOAK_DEPLOYMENT` | Skip Keycloak auto-deploy | `false` | +| `SKIP_OPERATOR_INSTALLATION` | Skip operator installation in global setup | - | | `RHDH_SKIP_PLUGIN_METADATA_INJECTION` | Disable plugin metadata injection (local only, ignored in CI) | - | | `USE_NEW_FRONTEND_SYSTEM` | When `"true"`, enables new-frontend-system (app-next) merges when `useNewFrontendSystem` is not set in `configure()` options | - | diff --git a/docs/guide/deployment/operator-deployment.md b/docs/guide/deployment/operator-deployment.md index 87256b8..10fb161 100644 --- a/docs/guide/deployment/operator-deployment.md +++ b/docs/guide/deployment/operator-deployment.md @@ -4,26 +4,9 @@ Deploy RHDH using the RHDH Operator. ## Prerequisites -The RHDH Operator must be installed on your cluster. Install it via OperatorHub or the command line: +The RHDH Operator is **automatically installed** during global setup when `INSTALLATION_METHOD="operator"`. The operator installation runs once in parallel with Keycloak deployment before any tests execute. -```bash -# Create operator namespace -oc create namespace rhdh-operator - -# Install the operator -oc apply -f - </dev/null 2>&1; do - echo "Waiting for Backstage CRD to be created..." - sleep 20 - done - echo "Backstage CRD is created." - ' || echo "Error: Timed out waiting for Backstage CRD creation." - - oc apply -f "${subscriptionFilePath}" -n "${this.deploymentConfig.namespace}" - `; + this._log("Backstage CR applied successfully."); + } - this._log("Operator deployment executed successfully."); + private get _labelSelector(): string { + return this.deploymentConfig.method === "operator" + ? "rhdh.redhat.com/app=backstage-developer-hub" + : "app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)"; } async rolloutRestart(): Promise { this._log( `Restarting RHDH deployment in namespace ${this.deploymentConfig.namespace}...`, ); - await $`oc rollout restart deployment -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' -n ${this.deploymentConfig.namespace}`; + await $`oc rollout restart deployment -l '${this._labelSelector}' -n ${this.deploymentConfig.namespace}`; this._log( `RHDH deployment restarted successfully in namespace ${this.deploymentConfig.namespace}`, ); @@ -385,15 +358,14 @@ export class RHDHDeployment { */ async scaleDownAndRestart(): Promise { const namespace = this.deploymentConfig.namespace; - await $`oc scale deployment -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' --replicas=0 -n ${namespace}`; - await $`oc wait --for=delete pod -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub),app.kubernetes.io/name!=postgresql' -n ${namespace} --timeout=120s || true`; - await $`oc scale deployment -l 'app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)' --replicas=1 -n ${namespace}`; + await $`oc scale deployment -l '${this._labelSelector}' --replicas=0 -n ${namespace}`; + await $`oc wait --for=delete pod -l '${this._labelSelector}' -n ${namespace} --timeout=120s || true`; + await $`oc scale deployment -l '${this._labelSelector}' --replicas=1 -n ${namespace}`; } async waitUntilReady(timeout: number = 500): Promise { const namespace = this.deploymentConfig.namespace; - const labelSelector = - "app.kubernetes.io/instance in (redhat-developer-hub,developer-hub)"; + const labelSelector = this._labelSelector; const startTime = Date.now(); try { diff --git a/src/deployment/rhdh/operator-setup.ts b/src/deployment/rhdh/operator-setup.ts new file mode 100644 index 0000000..c06571c --- /dev/null +++ b/src/deployment/rhdh/operator-setup.ts @@ -0,0 +1,61 @@ +import { $ } from "../../utils/bash.js"; + +export async function installRHDHOperator(): Promise { + if (process.env.INSTALLATION_METHOD !== "operator") { + return; + } + + if (process.env.SKIP_OPERATOR_INSTALLATION === "true") { + console.log("Skipping RHDH operator installation"); + return; + } + + const version = process.env.RHDH_VERSION ?? "next"; + const isSemanticVersion = /^\d+(\.\d+)?$/.test(version); + const branch = isSemanticVersion ? `release-${version}` : "main"; + + let versionArgs: string[]; + if (isSemanticVersion) { + versionArgs = ["-v", version]; + } else if (version === "next") { + versionArgs = ["--next"]; + } else { + throw new Error( + `Invalid RHDH version "${version}". Use semantic version (e.g., "1.5") or "next".`, + ); + } + + console.log( + `Installing RHDH operator (branch: ${branch}, version: ${versionArgs.join(" ")})...`, + ); + + const scriptUrl = `https://raw.githubusercontent.com/redhat-developer/rhdh-operator/refs/heads/${branch}/.rhdh/scripts/install-rhdh-catalog-source.sh`; + + try { + await $`curl -sf ${scriptUrl} | bash -s -- ${versionArgs} --install-operator rhdh`; + } catch (error) { + throw new Error( + `install-rhdh-catalog-source.sh failed (branch: ${branch}, args: ${versionArgs.join(" ")} --install-operator rhdh).\nScript URL: ${scriptUrl}\n${error instanceof Error ? error.message : error}`, + { cause: error }, + ); + } + + try { + await $` + timeout 300 bash -c ' + while ! oc get crd backstages.rhdh.redhat.com >/dev/null 2>&1; do + echo "Waiting for Backstage CRD to be created..." + sleep 10 + done + echo "Backstage CRD is created." + ' + `; + } catch (error) { + throw new Error( + "Timed out waiting for Backstage CRD (backstages.rhdh.redhat.com) after 300s. The operator may not have installed correctly.", + { cause: error }, + ); + } + + console.log("RHDH operator installation completed"); +} diff --git a/src/playwright/global-setup.ts b/src/playwright/global-setup.ts index d79f307..792485b 100644 --- a/src/playwright/global-setup.ts +++ b/src/playwright/global-setup.ts @@ -9,6 +9,7 @@ import { resolve } from "path"; import { KubernetesClientHelper } from "../utils/kubernetes-client.js"; import { $ } from "../utils/bash.js"; import { KeycloakHelper } from "../deployment/keycloak/index.js"; +import { installRHDHOperator } from "../deployment/rhdh/operator-setup.js"; import { DEFAULT_KEYCLOAK_CONFIG, DEFAULT_RHDH_CLIENT, @@ -86,7 +87,7 @@ export default async function globalSetup(config: FullConfig): Promise { await loadLocalVaultSecrets(); loadDotenvFromProjects(config); await setClusterRouterBaseEnv(); - await deployKeycloak(); + await Promise.all([installRHDHOperator(), deployKeycloak()]); console.log("Global setup completed successfully"); }