diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 2aacaf0..04df3c2 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -33,7 +33,7 @@ export default defineConfig({ { text: "Examples", link: "/examples/" }, { text: "Overlay Testing", link: "/overlay/" }, { - text: "v1.1.30", + text: "v1.1.33", items: [{ text: "Changelog", link: "/changelog" }], }, ], diff --git a/docs/changelog.md b/docs/changelog.md index ccace52..1ea1c57 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,7 +2,11 @@ All notable changes to this project will be documented in this file. -## [1.1.32] - Current +## [1.1.33] - Current + +### Added + +- **Automatic Vault secret loading for local development**: Set `VAULT=1` or `VAULT=true` to automatically fetch secrets from HashiCorp Vault during global setup. Handles OIDC login, fetches global and per-workspace secrets, and injects them into `process.env`. Only secret key names are logged, never values. Configurable via `VAULT_ADDR` and `VAULT_BASE_PATH` env vars. Logs a Slack channel (`#rhdh-e2e-tests`) when permission is denied. ### Fixed diff --git a/docs/examples/basic-test.md b/docs/examples/basic-test.md index 17e81b5..4bbdeb6 100644 --- a/docs/examples/basic-test.md +++ b/docs/examples/basic-test.md @@ -8,9 +8,6 @@ Minimal working test example. ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ projects: [ diff --git a/docs/guide/configuration/environment-variables.md b/docs/guide/configuration/environment-variables.md index 4cc304f..7079ef0 100644 --- a/docs/guide/configuration/environment-variables.md +++ b/docs/guide/configuration/environment-variables.md @@ -123,13 +123,7 @@ GITHUB_TOKEN=ghp_xxxxx MY_API_KEY=secret-value ``` -Load with dotenv: - -```typescript -// playwright.config.ts -import dotenv from "dotenv"; -dotenv.config({ path: `${import.meta.dirname}/.env` }); -``` +The `.env` file is automatically loaded by global setup. Variables defined here take priority over Vault secrets. ### CI/CD diff --git a/docs/guide/core-concepts/global-setup.md b/docs/guide/core-concepts/global-setup.md index 341ea74..d85e43c 100644 --- a/docs/guide/core-concepts/global-setup.md +++ b/docs/guide/core-concepts/global-setup.md @@ -4,7 +4,33 @@ The package includes a global setup function that runs once before all tests. Th ## What Global Setup Does -### 1. Binary Validation +### 1. Vault Secret Loading (Local Development) + +When `VAULT=1` or `VAULT=true` is set, global setup fetches secrets from HashiCorp Vault before anything else runs: + +- Checks that the `vault` CLI is installed +- Logs in via OIDC if not already authenticated (opens browser) +- Fetches global secrets and per-workspace secrets +- Injects all `VAULT_*` keys into `process.env` +- Only logs key names, never secret values + +```bash +# From workspace +VAULT=1 yarn test + +# From repo root +VAULT=1 ./run-e2e.sh -w argocd +``` + +If you don't have Vault access, request it in Slack: `#rhdh-e2e-tests`. + +| Variable | Description | Default | +|----------|-------------|---------| +| `VAULT` | Enable Vault secret loading (`1` or `true`) | - | +| `VAULT_ADDR` | Vault server URL | `https://vault.ci.openshift.org` | +| `VAULT_BASE_PATH` | Base path in Vault | `selfservice/rhdh-plugin-export-overlays` | + +### 2. Binary Validation Checks that required CLI tools are installed and available: @@ -16,7 +42,7 @@ Checks that required CLI tools are installed and available: If any binary is missing, tests will fail with a clear error message. -### 2. Cluster Router Base +### 3. Cluster Router Base Fetches the OpenShift ingress domain and sets the `K8S_CLUSTER_ROUTER_BASE` environment variable: @@ -27,7 +53,7 @@ K8S_CLUSTER_ROUTER_BASE=apps.cluster-abc123.example.com This is used to construct route URLs for deployed applications. -### 3. Keycloak Deployment +### 4. Keycloak Deployment Automatically deploys and configures Keycloak for OIDC authentication: diff --git a/docs/guide/core-concepts/playwright-config.md b/docs/guide/core-concepts/playwright-config.md index bf3c7a3..c47b9f5 100644 --- a/docs/guide/core-concepts/playwright-config.md +++ b/docs/guide/core-concepts/playwright-config.md @@ -146,21 +146,8 @@ export default playwrightDefineConfig({ ## Loading Environment Variables -Use `dotenv` to load environment variables from a `.env` file: +Environment variables from `.env` files are automatically loaded by the global setup. Place a `.env` file in your `e2e-tests/` directory: -```typescript -import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -// Load .env file -dotenv.config({ path: `${import.meta.dirname}/.env` }); - -export default defineConfig({ - projects: [{ name: "my-plugin" }], -}); -``` - -Create a `.env` file: ```bash RHDH_VERSION="1.5" @@ -174,9 +161,6 @@ GITHUB_TOKEN=ghp_xxxxx ```typescript import { baseConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; import { defineConfig } from "@playwright/test"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ ...baseConfig, diff --git a/docs/guide/quick-start.md b/docs/guide/quick-start.md index e8d09a0..e000eab 100644 --- a/docs/guide/quick-start.md +++ b/docs/guide/quick-start.md @@ -25,9 +25,6 @@ Create `playwright.config.ts`: ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ projects: [ diff --git a/docs/overlay/examples/basic-plugin.md b/docs/overlay/examples/basic-plugin.md index 3a11cf2..7ee1566 100644 --- a/docs/overlay/examples/basic-plugin.md +++ b/docs/overlay/examples/basic-plugin.md @@ -49,6 +49,7 @@ workspaces//e2e-tests/ "description": "E2E tests for ", "scripts": { "test": "playwright test", + "test:vault": "VAULT=1 playwright test", "report": "playwright show-report", "test:ui": "playwright test --ui", "test:headed": "playwright test --headed", @@ -62,9 +63,8 @@ workspaces//e2e-tests/ "devDependencies": { "@eslint/js": "10.0.1", "@playwright/test": "1.59.1", - "@red-hat-developer-hub/e2e-test-utils": "1.1.30", + "@red-hat-developer-hub/e2e-test-utils": "1.1.33", "@types/node": "25.5.2", - "dotenv": "17.4.1", "eslint": "10.2.0", "eslint-plugin-check-file": "3.3.1", "eslint-plugin-playwright": "2.10.1", @@ -79,9 +79,6 @@ workspaces//e2e-tests/ ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ projects: [ diff --git a/docs/overlay/examples/tech-radar.md b/docs/overlay/examples/tech-radar.md index 584a931..121be5d 100644 --- a/docs/overlay/examples/tech-radar.md +++ b/docs/overlay/examples/tech-radar.md @@ -52,6 +52,7 @@ workspaces/tech-radar/e2e-tests/ "description": "E2E tests for Tech Radar plugin", "scripts": { "test": "playwright test", + "test:vault": "VAULT=1 playwright test", "report": "playwright show-report", "test:ui": "playwright test --ui", "test:headed": "playwright test --headed", @@ -65,9 +66,8 @@ workspaces/tech-radar/e2e-tests/ "devDependencies": { "@eslint/js": "10.0.1", "@playwright/test": "1.59.1", - "@red-hat-developer-hub/e2e-test-utils": "1.1.30", + "@red-hat-developer-hub/e2e-test-utils": "1.1.33", "@types/node": "25.5.2", - "dotenv": "17.4.1", "eslint": "10.2.0", "eslint-plugin-check-file": "3.3.1", "eslint-plugin-playwright": "2.10.1", @@ -82,9 +82,6 @@ workspaces/tech-radar/e2e-tests/ ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); /** * Tech Radar plugin e2e test configuration. diff --git a/docs/overlay/getting-started.md b/docs/overlay/getting-started.md index 1bfe338..210736e 100644 --- a/docs/overlay/getting-started.md +++ b/docs/overlay/getting-started.md @@ -13,9 +13,6 @@ Create `playwright.config.ts`: ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ projects: [ diff --git a/docs/overlay/reference/environment-variables.md b/docs/overlay/reference/environment-variables.md index 359af1c..aac9ef6 100644 --- a/docs/overlay/reference/environment-variables.md +++ b/docs/overlay/reference/environment-variables.md @@ -15,6 +15,23 @@ All secrets **must** start with the `VAULT_` prefix (e.g., `VAULT_API_KEY`, `VAU For complete Vault setup instructions including paths, annotations, and access requests, see [OpenShift CI Pipeline - Vault Secrets](/overlay/tutorials/ci-pipeline#vault-secrets). +## Vault Auto-Loading (Local Development) + +Set `VAULT=1` or `VAULT=true` to automatically fetch secrets from Vault during global setup. This replaces the need to manually copy secrets into `.env` files. + +| Variable | Description | Default | +|----------|-------------|---------| +| `VAULT` | Enable automatic Vault secret loading | - | +| `VAULT_ADDR` | Vault server URL | `https://vault.ci.openshift.org` | +| `VAULT_BASE_PATH` | Base path in Vault KV store | `selfservice/rhdh-plugin-export-overlays` | + +```bash +VAULT=1 yarn test +VAULT=1 ./run-e2e.sh -w argocd +``` + +See [Running Locally - Secrets from Vault](/overlay/tutorials/running-locally#secrets-from-vault) for full details. + ## Core Variables ### RHDH Configuration diff --git a/docs/overlay/reference/run-e2e.md b/docs/overlay/reference/run-e2e.md index d07c753..17c68b0 100644 --- a/docs/overlay/reference/run-e2e.md +++ b/docs/overlay/reference/run-e2e.md @@ -168,7 +168,7 @@ Cleans all `node_modules` and `yarn.lock` to ensure fresh resolution, then runs Extracts `projects: [...]` blocks from each workspace's `playwright.config.ts` using `sed` text processing. Injects `testDir` into each project pointing to the workspace's `tests/` directory. ::: info Why sed Instead of Import? -Importing workspace configs would execute their top-level code (e.g., `dotenv.config()`, `process.env` mutations), which can pollute the environment for other workspaces. Text extraction avoids this. +Importing workspace configs would execute their top-level code (e.g., `process.env` mutations), which can pollute the environment for other workspaces. Text extraction avoids this. ::: The generated config imports `baseConfig` from the test utils package, which provides reporters, timeouts, video/screenshot/trace settings, and global setup. diff --git a/docs/overlay/reference/scripts.md b/docs/overlay/reference/scripts.md index fd29c73..01cc543 100644 --- a/docs/overlay/reference/scripts.md +++ b/docs/overlay/reference/scripts.md @@ -22,6 +22,16 @@ Equivalent to: playwright test ``` +### yarn test:vault + +Run tests with secrets automatically fetched from Vault: + +```bash +yarn test:vault +``` + +Equivalent to `VAULT=1 yarn test`. Handles OIDC login, fetches global and per-workspace secrets. See [Running Locally - Secrets from Vault](/overlay/tutorials/running-locally#secrets-from-vault) for details. + ### yarn test:headed Run tests with browser visible: @@ -139,6 +149,7 @@ Standard `package.json` scripts section: { "scripts": { "test": "playwright test", + "test:vault": "VAULT=1 playwright test", "report": "playwright show-report", "test:ui": "playwright test --ui", "test:headed": "playwright test --headed", diff --git a/docs/overlay/reference/troubleshooting.md b/docs/overlay/reference/troubleshooting.md index b58bc81..64ceffa 100644 --- a/docs/overlay/reference/troubleshooting.md +++ b/docs/overlay/reference/troubleshooting.md @@ -197,11 +197,7 @@ oc login --token= --server= **Solutions:** - Check `.env` file exists and contains variable -- Verify dotenv is loaded: - ```typescript - import dotenv from "dotenv"; - dotenv.config({ path: `${import.meta.dirname}/.env` }); - ``` +- Verify `.env` file exists in your `e2e-tests/` directory (loaded automatically by global setup) - Set in test code: ```typescript process.env.MY_VAR = "value"; diff --git a/docs/overlay/test-structure/directory-layout.md b/docs/overlay/test-structure/directory-layout.md index c8d5ee0..7e333a7 100644 --- a/docs/overlay/test-structure/directory-layout.md +++ b/docs/overlay/test-structure/directory-layout.md @@ -49,6 +49,7 @@ Defines the test package with dependencies and scripts: "packageManager": "yarn@4.12.0", "scripts": { "test": "playwright test", + "test:vault": "VAULT=1 playwright test", "report": "playwright show-report", "test:ui": "playwright test --ui", "test:headed": "playwright test --headed", @@ -62,9 +63,8 @@ Defines the test package with dependencies and scripts: "devDependencies": { "@eslint/js": "10.0.1", "@playwright/test": "1.59.1", - "@red-hat-developer-hub/e2e-test-utils": "1.1.30", + "@red-hat-developer-hub/e2e-test-utils": "1.1.33", "@types/node": "25.5.2", - "dotenv": "17.4.1", "eslint": "10.2.0", "eslint-plugin-check-file": "3.3.1", "eslint-plugin-playwright": "2.10.1", @@ -81,9 +81,6 @@ Extends the base configuration from `@red-hat-developer-hub/e2e-test-utils`: ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ projects: [ diff --git a/docs/overlay/tutorials/new-workspace.md b/docs/overlay/tutorials/new-workspace.md index 128ec34..54926aa 100644 --- a/docs/overlay/tutorials/new-workspace.md +++ b/docs/overlay/tutorials/new-workspace.md @@ -45,6 +45,7 @@ Create `package.json` with the following content: "description": "E2E tests for ", "scripts": { "test": "playwright test", + "test:vault": "VAULT=1 playwright test", "report": "playwright show-report", "test:ui": "playwright test --ui", "test:headed": "playwright test --headed", @@ -60,7 +61,6 @@ Create `package.json` with the following content: "@playwright/test": "1.59.1", "@red-hat-developer-hub/e2e-test-utils": "", "@types/node": "25.5.2", - "dotenv": "17.4.1", "eslint": "10.2.0", "eslint-plugin-check-file": "3.3.1", "eslint-plugin-playwright": "2.10.1", @@ -80,9 +80,6 @@ Replate `` with the latest available version of this library. ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ projects: [ diff --git a/docs/overlay/tutorials/running-locally.md b/docs/overlay/tutorials/running-locally.md index 9296e0c..60aab62 100644 --- a/docs/overlay/tutorials/running-locally.md +++ b/docs/overlay/tutorials/running-locally.md @@ -196,23 +196,62 @@ If you see odd module-resolution issues while testing a locally linked `e2e-test NODE_PRESERVE_SYMLINKS=1 yarn test:headed ``` +## Secrets from Vault + +Instead of manually copying secrets from the Vault UI into `.env` files, you can fetch them automatically by setting `VAULT=1`: + +```bash +# From workspace +cd workspaces/argocd/e2e-tests +yarn test:vault + +# Or equivalently +VAULT=1 yarn test + +# From repo root +VAULT=1 ./run-e2e.sh -w argocd +``` + +This will: + +1. Check that the `vault` CLI is installed +2. Log you in via OIDC if needed (opens a browser) +3. Fetch global secrets and all per-workspace secrets from Vault +4. Inject `VAULT_*` keys into `process.env` for the test run + +::: tip +If you don't have Vault access, request it in Slack: `#rhdh-e2e-tests`. +::: + +**Prerequisites:** Install the [Vault CLI](https://developer.hashicorp.com/vault/downloads). + +You can also override the Vault server or base path: + +```bash +VAULT=1 VAULT_ADDR=https://my-vault.example.com VAULT_BASE_PATH=my/path yarn test +``` + ## Environment Variables ### Using .env File -Create `.env` for local configuration: +Create `.env` for local configuration (alternative to Vault): ```bash # .env RHDH_VERSION=1.5 INSTALLATION_METHOD=helm SKIP_KEYCLOAK_DEPLOYMENT=false + +# Secrets (or use VAULT=1 instead) +VAULT_GITHUB_TOKEN=ghp_xxx ``` ### Common Variables | Variable | Description | Default | | -------------------------- | -------------------------------------------------- | --------------- | +| `VAULT` | Fetch secrets from Vault automatically (`1` or `true`) | - | | `RHDH_VERSION` | RHDH version to deploy | `next` (latest) | | `INSTALLATION_METHOD` | `helm` or `operator` | `helm` | | `SKIP_KEYCLOAK_DEPLOYMENT` | Skip Keycloak deployment entirely (for guest auth) | `false` | diff --git a/docs/overlay/tutorials/using-secrets.md b/docs/overlay/tutorials/using-secrets.md index 353780e..513fc77 100644 --- a/docs/overlay/tutorials/using-secrets.md +++ b/docs/overlay/tutorials/using-secrets.md @@ -6,6 +6,14 @@ This page explains how to consume Vault secrets in overlay E2E tests. In OpenShift CI, Vault secrets are exported as environment variables with the `VAULT_` prefix. +For **local development**, set `VAULT=1` to automatically fetch secrets from Vault instead of manually copying them into `.env` files: + +```bash +VAULT=1 yarn test +``` + +See [Running Locally](/overlay/tutorials/running-locally#secrets-from-vault) for details. + ## Vault Setup (CI) ### Secret Naming Convention diff --git a/docs/tutorials/multi-project-setup.md b/docs/tutorials/multi-project-setup.md index 44fac34..4ed4d53 100644 --- a/docs/tutorials/multi-project-setup.md +++ b/docs/tutorials/multi-project-setup.md @@ -14,9 +14,6 @@ Configure multiple Playwright projects for different test scenarios. **playwright.config.ts:** ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ projects: [ diff --git a/docs/tutorials/plugin-testing.md b/docs/tutorials/plugin-testing.md index 38aead1..59688e6 100644 --- a/docs/tutorials/plugin-testing.md +++ b/docs/tutorials/plugin-testing.md @@ -17,7 +17,7 @@ This tutorial covers: cd your-plugin-workspace mkdir e2e-tests && cd e2e-tests yarn init -y -yarn add @playwright/test @red-hat-developer-hub/e2e-test-utils typescript dotenv +yarn add @playwright/test @red-hat-developer-hub/e2e-test-utils typescript ``` ## Step 2: Configuration @@ -25,9 +25,6 @@ yarn add @playwright/test @red-hat-developer-hub/e2e-test-utils typescript doten **playwright.config.ts:** ```typescript import { defineConfig } from "@red-hat-developer-hub/e2e-test-utils/playwright-config"; -import dotenv from "dotenv"; - -dotenv.config({ path: `${import.meta.dirname}/.env` }); export default defineConfig({ projects: [ diff --git a/package.json b/package.json index b380d9c..d08a109 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@red-hat-developer-hub/e2e-test-utils", - "version": "1.1.32", + "version": "1.1.33", "description": "Test utilities for RHDH E2E tests", "license": "Apache-2.0", "repository": { @@ -107,6 +107,7 @@ "@eslint/js": "10.0.1", "@keycloak/keycloak-admin-client": "26.5.6", "@kubernetes/client-node": "1.4.0", + "dotenv": "17.4.2", "eslint": "10.2.0", "eslint-plugin-check-file": "3.3.1", "eslint-plugin-playwright": "2.10.1", diff --git a/src/playwright/global-setup.ts b/src/playwright/global-setup.ts index 271f25c..d79f307 100644 --- a/src/playwright/global-setup.ts +++ b/src/playwright/global-setup.ts @@ -3,6 +3,9 @@ * This file runs once before all tests. */ +import { type FullConfig } from "@playwright/test"; +import dotenv from "dotenv"; +import { resolve } from "path"; import { KubernetesClientHelper } from "../utils/kubernetes-client.js"; import { $ } from "../utils/bash.js"; import { KeycloakHelper } from "../deployment/keycloak/index.js"; @@ -11,6 +14,7 @@ import { DEFAULT_RHDH_CLIENT, DEFAULT_USERS, } from "../deployment/keycloak/constants.js"; +import { loadLocalVaultSecrets } from "../utils/vault.js"; const REQUIRED_BINARIES = ["oc", "kubectl", "helm"] as const; @@ -76,10 +80,27 @@ async function deployKeycloak(): Promise { }); } -export default async function globalSetup(): Promise { +export default async function globalSetup(config: FullConfig): Promise { console.log("Running global setup..."); await checkRequiredBinaries(); + await loadLocalVaultSecrets(); + loadDotenvFromProjects(config); await setClusterRouterBaseEnv(); await deployKeycloak(); console.log("Global setup completed successfully"); } + +/** + * Loads .env files from each project's e2e-tests directory. + * Uses `override: true` so local .env values take priority over Vault secrets. + */ +function loadDotenvFromProjects(config: FullConfig): void { + const seen = new Set(); + for (const project of config.projects) { + // testDir points to e2e-tests/tests, go up one level to e2e-tests/ + const e2eRoot = resolve(project.testDir, ".."); + if (seen.has(e2eRoot)) continue; + seen.add(e2eRoot); + dotenv.config({ path: resolve(e2eRoot, ".env"), override: true }); + } +} diff --git a/src/utils/vault.ts b/src/utils/vault.ts new file mode 100644 index 0000000..a4c1b26 --- /dev/null +++ b/src/utils/vault.ts @@ -0,0 +1,129 @@ +import { $ } from "./bash.js"; + +const VAULT_ADDR_DEFAULT = "https://vault.ci.openshift.org"; +const VAULT_BASE_PATH_DEFAULT = "selfservice/rhdh-plugin-export-overlays"; + +/** + * Loads secrets from HashiCorp Vault into process.env. + * Only runs when `VAULT=1` or `VAULT=true` is set. Handles OIDC login automatically. + * + * Fetches secrets from: + * - Global path: `/global` + * - Per-workspace paths: `/workspaces/` + * + * Configure via env vars: + * - `VAULT_ADDR` — Vault server URL (default: https://vault.ci.openshift.org) + * - `VAULT_BASE_PATH` — Base path in Vault (default: selfservice/rhdh-plugin-export-overlays) + * + * Security: Only key names are logged, never secret values. + */ +export async function loadLocalVaultSecrets(): Promise { + if (process.env.VAULT !== "1" && process.env.VAULT !== "true") return; + + const vaultAddr = process.env.VAULT_ADDR || VAULT_ADDR_DEFAULT; + const basePath = process.env.VAULT_BASE_PATH || VAULT_BASE_PATH_DEFAULT; + process.env.VAULT_ADDR = vaultAddr; + + // Check vault CLI is installed + const whichResult = await vaultCmd`command -v vault`; + if (whichResult.exitCode !== 0) { + throw new Error( + "vault CLI not found. Install from https://developer.hashicorp.com/vault/downloads", + ); + } + + // Check if already logged in + const tokenCheck = await vaultCmd`vault token lookup`; + if (tokenCheck.exitCode !== 0) { + console.log("Vault: not logged in, starting OIDC login..."); + // vault login needs inherited stdio for browser-based OIDC flow + await $`vault login -no-print -method=oidc`; + + const retryCheck = await vaultCmd`vault token lookup`; + if (retryCheck.exitCode !== 0) { + throw new Error( + "Vault login failed. Run manually:\n export VAULT_ADDR='" + + vaultAddr + + "'\n vault login -method=oidc", + ); + } + } + + // Check access by fetching global secrets first + const globalResult = + await vaultCmd`vault kv get -format=json -mount=kv ${basePath}/global`; + + if (globalResult.stderr.includes("permission denied")) { + console.log( + "Vault: permission denied. Request access in Slack: #rhdh-e2e-tests", + ); + return; + } + + console.log("Loading secrets from vault..."); + + // Load global secrets + loadSecretsFromResult(globalResult, "global"); + + // List and fetch per-workspace secrets + const listResult = + await vaultCmd`vault kv list -format=json -mount=kv ${basePath}/workspaces`; + + if (listResult.exitCode === 0) { + const workspaces: string[] = JSON.parse(listResult.stdout); + await Promise.all( + workspaces.map((ws) => { + const name = ws.replace(/\/$/, ""); + return exportSecretsFromPath(`${basePath}/workspaces/${name}`, name); + }), + ); + } else { + console.log(" No workspace-specific secrets found"); + } + + console.log("Vault secrets loaded successfully."); +} + +/** Runs a shell command with piped stdio and nothrow, for capturing vault CLI output. */ +const vaultCmd = $({ + stdio: ["pipe", "pipe", "pipe"], + nothrow: true, +}); + +async function exportSecretsFromPath( + vaultPath: string, + label: string, +): Promise { + const result = + await vaultCmd`vault kv get -format=json -mount=kv ${vaultPath}`; + loadSecretsFromResult(result, label); +} + +interface VaultResult { + exitCode: number | null; + stdout: string; +} + +function loadSecretsFromResult(result: VaultResult, label: string): void { + if (result.exitCode !== 0) { + console.log(` No secrets at: ${label}`); + return; + } + + const json = JSON.parse(result.stdout) as { + data?: { data?: Record }; + }; + const secrets = json?.data?.data; + if (!secrets) { + console.log(` No secrets at: ${label}`); + return; + } + + console.log(` From: ${label}`); + for (const [key, value] of Object.entries(secrets)) { + if (key.startsWith("secretsync/")) continue; + if (!key.startsWith("VAULT_")) continue; + const safeKey = key.replace(/[.\-/]/g, "_"); + process.env[safeKey] = value; + } +} diff --git a/yarn.lock b/yarn.lock index 9356924..e008a03 100644 --- a/yarn.lock +++ b/yarn.lock @@ -385,6 +385,7 @@ __metadata: "@types/lodash.mergewith": "npm:4.6.9" "@types/node": "npm:25.5.2" "@types/proper-lockfile": "npm:4.1.4" + dotenv: "npm:17.4.2" eslint: "npm:10.2.0" eslint-plugin-check-file: "npm:3.3.1" eslint-plugin-playwright: "npm:2.10.1" @@ -1173,6 +1174,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:17.4.2": + version: 17.4.2 + resolution: "dotenv@npm:17.4.2" + checksum: 10/ca1b6f54d58e39914901e1518958e86083aca8deb5aa2e7f2a51acd53291d97852479b0ab26ed949b6a45a0e9adcc7b0d1c3c72375e8ea670f7005e341c6daba + languageName: node + linkType: hard + "dunder-proto@npm:^1.0.1": version: 1.0.1 resolution: "dunder-proto@npm:1.0.1"