Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/guide/configuration/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | - |

Expand Down
21 changes: 2 additions & 19 deletions docs/guide/deployment/operator-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 - <<EOF
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: rhdh-operator
namespace: rhdh-operator
spec:
channel: fast
name: rhdh
source: redhat-operators
sourceNamespace: openshift-marketplace
EOF
```
If you have a pre-installed operator on your cluster, set `SKIP_OPERATOR_INSTALLATION=true` to skip the automatic installation.

## Configuration

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@red-hat-developer-hub/e2e-test-utils",
"version": "1.1.41",
"version": "1.1.42",
"description": "Test utilities for RHDH E2E tests",
"license": "Apache-2.0",
"repository": {
Expand Down
54 changes: 13 additions & 41 deletions src/deployment/rhdh/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,56 +322,29 @@ export class RHDHDeployment {
this._log(`Catalog index image: ${catalogIndexImage}`);
}

this._logBoxen("Subscription", subscriptionObject);
this._logBoxen("Backstage CR", subscriptionObject);
const subscriptionFilePath = path.join(
os.tmpdir(),
`${this.deploymentConfig.namespace}-subscription.yaml`,
);
fs.writeFileSync(subscriptionFilePath, yaml.dump(subscriptionObject));

const version = this.deploymentConfig.version;
const isSemanticVersion = /^\d+(\.\d+)?$/.test(version);
await $`oc apply -f "${subscriptionFilePath}" -n "${this.deploymentConfig.namespace}"`;

// Use main branch for non-semantic versions (e.g., "next", "latest")
const branch = isSemanticVersion ? `release-${version}` : "main";

// Build version argument based on version type
let versionArg: string;
if (isSemanticVersion) {
versionArg = `-v ${version}`;
} else if (version === "next") {
versionArg = "--next";
} else {
throw new Error(
`Invalid RHDH version "${version}". Use semantic version (e.g., "1.5") or "next".`,
);
}

this._log(`Using operator branch: ${branch}, version arg: ${versionArg}`);

await $`
set -e;
curl -sf https://raw.githubusercontent.com/redhat-developer/rhdh-operator/refs/heads/${branch}/.rhdh/scripts/install-rhdh-catalog-source.sh | bash -s -- ${versionArg} --install-operator rhdh

timeout 300 bash -c '
while ! oc get crd/backstages.rhdh.redhat.com -n "${this.deploymentConfig.namespace}" >/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<void> {
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}`,
);
Expand All @@ -385,15 +358,14 @@ export class RHDHDeployment {
*/
async scaleDownAndRestart(): Promise<void> {
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<void> {
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 {
Expand Down
61 changes: 61 additions & 0 deletions src/deployment/rhdh/operator-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { $ } from "../../utils/bash.js";

export async function installRHDHOperator(): Promise<void> {
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");
}
3 changes: 2 additions & 1 deletion src/playwright/global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -86,7 +87,7 @@ export default async function globalSetup(config: FullConfig): Promise<void> {
await loadLocalVaultSecrets();
loadDotenvFromProjects(config);
await setClusterRouterBaseEnv();
await deployKeycloak();
await Promise.all([installRHDHOperator(), deployKeycloak()]);
console.log("Global setup completed successfully");
}

Expand Down
Loading