Skip to content

Commit d92df31

Browse files
committed
fix(e2e): fix external DB tests for operator deployments
For operator deployments, patching POSTGRES_* env vars directly onto the deployment is reverted by operator reconciliation. The external DB tests (Azure DB, RDS) configure postgres-cred secret with connection details, but those values never reached the RHDH container as env vars. Fix: - Add postgres-cred to extraEnvs.secrets in the Backstage CR so the operator injects all its keys as env vars automatically - Skip direct deployment env var patching in prepareForExternalDatabase for operator installs (same pattern as schema-mode setup) Assisted-by: OpenCode
1 parent 2bec6ea commit d92df31

5 files changed

Lines changed: 128 additions & 18 deletions

File tree

e2e-tests/playwright/e2e/plugin-division-mode-schema/verify-schema-mode.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ChildProcessWithoutNullStreams, spawn } from "child_process";
1212
import { test, expect } from "@support/coverage/test";
1313

1414
import { Common } from "../../utils/common";
15-
import { resolveInstallMethod } from "../../utils/helper";
15+
import { getReleaseName, resolveInstallMethod } from "../../utils/helper";
1616
import { KubeClient } from "../../utils/kube-client";
1717
import { ensureRuntimeDeployed } from "../../utils/runtime-deploy";
1818
import { setPortForwardRestarter } from "./schema-mode-db";
@@ -80,7 +80,7 @@ function killPortForward(proc: ChildProcessWithoutNullStreams | undefined): Prom
8080

8181
test.describe("Verify pluginDivisionMode: schema", () => {
8282
const namespace = process.env.NAME_SPACE_RUNTIME ?? "showcase-runtime";
83-
const releaseName = process.env.RELEASE_NAME ?? "rhdh";
83+
const releaseName = getReleaseName();
8484
const installMethod = resolveInstallMethod();
8585

8686
let portForwardProcess: ChildProcessWithoutNullStreams | undefined;

e2e-tests/playwright/utils/helper.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ export function resolveInstallMethod(): "helm" | "operator" {
131131
return job.includes("operator") ? "operator" : "helm";
132132
}
133133

134+
/**
135+
* Canonical release name resolution. Returns the RELEASE_NAME env var if set
136+
* and non-empty, otherwise defaults to "rhdh".
137+
*/
138+
export function getReleaseName(): string {
139+
return process.env.RELEASE_NAME !== undefined && process.env.RELEASE_NAME !== ""
140+
? process.env.RELEASE_NAME
141+
: "rhdh";
142+
}
143+
134144
/** Base64-encode a string. */
135145
export function base64Encode(value: string): string {
136146
return Buffer.from(value).toString("base64");

e2e-tests/playwright/utils/kube-client/helpers.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as k8s from "@kubernetes/client-node";
22

33
import { getErrorMessage, hasErrorResponse, hasStatusCode } from "../errors";
4-
import { resolveInstallMethod } from "../helper";
4+
import { getReleaseName, resolveInstallMethod } from "../helper";
55

66
export function isRecord(value: unknown): value is Record<string, unknown> {
77
return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -135,10 +135,7 @@ export function getKubeApiErrorMessage(error: unknown): string {
135135
* then falls back to JOB_NAME pattern matching.
136136
*/
137137
export function getRhdhDeploymentName(): string {
138-
const releaseName =
139-
process.env.RELEASE_NAME !== undefined && process.env.RELEASE_NAME !== ""
140-
? process.env.RELEASE_NAME
141-
: "rhdh";
138+
const releaseName = getReleaseName();
142139
return resolveInstallMethod() === "operator"
143140
? `backstage-${releaseName}`
144141
: `${releaseName}-developer-hub`;

e2e-tests/playwright/utils/postgres-config.ts

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import { readFileSync, existsSync } from "fs";
1414

1515
import { Client } from "pg";
1616

17-
import { base64Encode } from "./helper";
17+
import { base64Encode, getReleaseName, resolveInstallMethod } from "./helper";
1818
import { KubeClient, BACKSTAGE_BACKEND_CONTAINER } from "./kube-client";
19+
import { BACKSTAGE_CR_API_VERSION } from "./runtime-config";
1920
import type { AppConfigYaml } from "./runtime-config";
2021

2122
/**
@@ -128,6 +129,15 @@ export async function configurePostgresCredentials(
128129
data,
129130
};
130131
await kubeClient.createOrUpdateSecret(secret, namespace);
132+
133+
// For operator installs, ensure the Backstage CR includes postgres-cred in
134+
// extraEnvs.secrets so the operator injects the credentials as env vars.
135+
// This is done here (not in prepareForExternalDatabase) because the secret
136+
// must contain real credentials before the operator reconciles — placeholder
137+
// values would break the DB connection and leave readiness at 503.
138+
if (resolveInstallMethod() === "operator") {
139+
await addPostgresCredToBackstageCR(kubeClient, namespace);
140+
}
131141
}
132142

133143
const SYSTEM_DATABASES = [
@@ -297,7 +307,11 @@ export async function prepareForExternalDatabase(
297307
// Schema-mode tests may have added individual secretKeyRef env vars pointing
298308
// to a *-postgresql secret. These override the bulk envFrom injection from
299309
// postgres-cred and must be removed before external DB tests.
300-
await removeSchemaModePatchedEnvVars(kubeClient, deploymentName, namespace);
310+
// Skip for operator — the operator manages env vars via extraEnvs.secrets
311+
// in the Backstage CR, so there are no direct deployment patches to remove.
312+
if (resolveInstallMethod() !== "operator") {
313+
await removeSchemaModePatchedEnvVars(kubeClient, deploymentName, namespace);
314+
}
301315

302316
// --- 2. Patch app-config ConfigMap to use external DB connection ---
303317
console.log("Patching app-config to use external database connection (env var placeholders)...");
@@ -314,11 +328,20 @@ export async function prepareForExternalDatabase(
314328
});
315329
console.log("App-config patched for external database connection");
316330

317-
// --- 3. Add POSTGRES_* env vars to the deployment via secretKeyRef ---
318-
// The deployment starts with internal DB (no postgres-cred env vars).
319-
// Add individual env vars pointing to the postgres-cred secret so the
320-
// app-config ${POSTGRES_HOST} etc. placeholders resolve correctly.
321-
await ensurePostgresCredEnvVars(kubeClient, deploymentName, namespace);
331+
// --- 3. Add POSTGRES_* env vars to the deployment ---
332+
// Helm: patch individual env vars pointing to the postgres-cred secret so
333+
// the app-config ${POSTGRES_HOST} etc. placeholders resolve correctly.
334+
// Operator: the Backstage CR is patched later by configurePostgresCredentials()
335+
// after real credentials are written to the postgres-cred secret. Patching the
336+
// CR here with placeholder values would trigger operator reconciliation and
337+
// break the DB connection (readiness 503).
338+
if (resolveInstallMethod() === "operator") {
339+
console.log(
340+
"Skipping env var setup (operator CR will be patched by configurePostgresCredentials)",
341+
);
342+
} else {
343+
await ensurePostgresCredEnvVars(kubeClient, deploymentName, namespace);
344+
}
322345
}
323346

324347
/**
@@ -371,3 +394,49 @@ async function ensurePostgresCredEnvVars(
371394
);
372395
console.log("POSTGRES_* env vars added to deployment from postgres-cred");
373396
}
397+
398+
/**
399+
* Patch the operator Backstage CR to add postgres-cred to extraEnvs.secrets.
400+
* This causes the operator to inject all keys from the postgres-cred secret
401+
* as env vars into the RHDH container.
402+
*
403+
* The postgres-cred secret is NOT included in the CR at deployment time —
404+
* it contains placeholder values that would override the operator-managed
405+
* internal PostgreSQL credentials. It is only added here, when external DB
406+
* tests need the real credentials injected.
407+
*
408+
* Uses JSON merge-patch so the secrets array is replaced wholesale.
409+
* The CR is created by generateBackstageCR() with only rhdh-runtime-config
410+
* in extraEnvs.secrets, so we set both here explicitly.
411+
*/
412+
async function addPostgresCredToBackstageCR(
413+
kubeClient: KubeClient,
414+
namespace: string,
415+
): Promise<void> {
416+
const releaseName = getReleaseName();
417+
const [group, version] = BACKSTAGE_CR_API_VERSION.split("/");
418+
419+
const patch = {
420+
spec: {
421+
application: {
422+
extraEnvs: {
423+
secrets: [{ name: "rhdh-runtime-config" }, { name: "postgres-cred" }],
424+
},
425+
},
426+
},
427+
};
428+
429+
await kubeClient.customObjectsApi.patchNamespacedCustomObject(
430+
group,
431+
version,
432+
namespace,
433+
"backstages",
434+
releaseName,
435+
patch,
436+
undefined,
437+
undefined,
438+
undefined,
439+
{ headers: { "Content-Type": "application/merge-patch+json" } },
440+
);
441+
console.log("Patched Backstage CR: added postgres-cred to extraEnvs.secrets");
442+
}

e2e-tests/playwright/utils/runtime-config.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717

1818
import * as yaml from "js-yaml";
1919

20-
import { type ImageRef, buildImageRef, imageRefToString, parseCatalogIndexImage } from "./helper";
20+
import {
21+
type ImageRef,
22+
buildImageRef,
23+
getReleaseName,
24+
imageRefToString,
25+
parseCatalogIndexImage,
26+
} from "./helper";
2127
import { BACKSTAGE_BACKEND_CONTAINER } from "./kube-client";
2228

2329
// Re-export image utilities so existing `import from "./runtime-config"`
@@ -79,7 +85,7 @@ export interface AppConfigYaml {
7985
* Build a RuntimeDeployConfig from environment variables.
8086
*/
8187
export function resolveConfig(routerBase: string): RuntimeDeployConfig {
82-
const releaseName = process.env.RELEASE_NAME ?? "rhdh";
88+
const releaseName = getReleaseName();
8389
const namespace = process.env.NAME_SPACE_RUNTIME ?? "showcase-runtime";
8490
const imageRegistry = process.env.IMAGE_REGISTRY ?? "quay.io";
8591
const imageRepo = process.env.IMAGE_REPO ?? "rhdh-community/rhdh";
@@ -301,16 +307,44 @@ export function generateAppConfigYaml(runtimeUrl: string): string {
301307
// ─── Operator dynamic-plugins ConfigMap ──────────────────────────────────────
302308

303309
/**
304-
* Generate the dynamic-plugins.yaml content for the operator path.
310+
* Generate the dynamic-plugins.yaml content.
305311
*
306312
* Runtime tests only need a basic RHDH instance (config-map changes, DB
307313
* connectivity). We set `includes: []` to prevent loading
308314
* `dynamic-plugins.default.yaml` — many of its default-enabled plugins
309315
* crash without external config (GitHub org, GitLab, LDAP, Keycloak,
310316
* ArgoCD, Kubernetes, orchestrator, etc.) and block the readiness probe.
317+
*
318+
* The homepage plugin is explicitly enabled with its frontend wiring
319+
* (dynamicRoutes) so that the DynamicHomePage component renders the
320+
* "Welcome back!" heading — external DB tests verify a successful
321+
* database connection via the UI. This explicit pluginConfig approach
322+
* is future-proof: it does not depend on dynamic-plugins.default.yaml
323+
* and works identically for both helm and operator install methods.
311324
*/
312325
export function generateDynamicPluginsYaml(): string {
313-
return yaml.dump({ includes: [] as string[], plugins: [] as unknown[] }, { lineWidth: -1 });
326+
return yaml.dump(
327+
{
328+
includes: [] as string[],
329+
plugins: [
330+
{
331+
package:
332+
"./dynamic-plugins/dist/red-hat-developer-hub-backstage-plugin-dynamic-home-page",
333+
enabled: true,
334+
pluginConfig: {
335+
dynamicPlugins: {
336+
frontend: {
337+
"red-hat-developer-hub.backstage-plugin-dynamic-home-page": {
338+
dynamicRoutes: [{ path: "/", importName: "DynamicHomePage" }],
339+
},
340+
},
341+
},
342+
},
343+
},
344+
],
345+
},
346+
{ lineWidth: -1 },
347+
);
314348
}
315349

316350
// ─── Operator Backstage CR generation ────────────────────────────────────────

0 commit comments

Comments
 (0)