From ffdd2520013419c9ae48dc2334c0cab82eec3aa0 Mon Sep 17 00:00:00 2001 From: Chai Bot Date: Fri, 11 Sep 2026 09:33:07 +0000 Subject: [PATCH 1/3] Add external OIDC (Keycloak) support for Playwright e2e tests Enable the console Playwright e2e suite to run against clusters configured with external OIDC authentication (Keycloak) instead of the default OpenShift OAuth (kubeadmin + htpasswd) model. This supports the new e2e-aws-external-oidc-console CI job that uses the idp-external-oidc-keycloak-aws workflow in openshift/release. When KEYCLOAK_ISSUER is set in the CI environment: - Shell scripts source $SHARED_DIR/runtime_env, parse KEYCLOAK_TEST_USERS for admin and developer personas, export the credentials into the env vars the login helper expects, grant cluster-admin to the admin user's OIDC identity, and skip contrib/create-user.sh (htpasswd IDP setup is not possible when the OpenShift OAuth server is disabled). - login-helper.ts detects BRIDGE_AUTH_TYPE=oidc and drives the Keycloak login form (#username/#password/#kc-login) instead of the OpenShift OAuth form. - playwright.config.ts enables developer-persona test projects under OIDC as well as under htpasswd. - kubernetes-client.ts guards getOAuthToken()/generateKubeconfig() with a clear error under OIDC instead of silently calling the non-existent OAuth server. - The auto-re-auth redirect detection in fixtures/index.ts now also matches Keycloak OIDC redirect URLs. The existing OAuth path is fully preserved when KEYCLOAK_ISSUER is unset. Co-Authored-By: Claude Opus 4.6 --- frontend/e2e/clients/kubernetes-client.ts | 12 +++++ frontend/e2e/fixtures/index.ts | 5 +- frontend/e2e/setup/login-helper.ts | 47 +++++++++++++++-- frontend/integration-tests/test-playwright.sh | 39 +++++++++++++- frontend/playwright.config.ts | 3 +- test-prow-e2e.sh | 52 +++++++++++++++++-- 6 files changed, 146 insertions(+), 12 deletions(-) diff --git a/frontend/e2e/clients/kubernetes-client.ts b/frontend/e2e/clients/kubernetes-client.ts index f928f55ed81..3066a131e13 100644 --- a/frontend/e2e/clients/kubernetes-client.ts +++ b/frontend/e2e/clients/kubernetes-client.ts @@ -102,6 +102,12 @@ export default class KubernetesClient { username: string, password: string, ): Promise { + if (process.env.BRIDGE_AUTH_TYPE === 'oidc') { + throw new Error( + 'getOAuthToken is not available under external OIDC authentication. ' + + 'The OpenShift OAuth server is disabled when using an external OIDC provider.', + ); + } const oauthServerUrl = await KubernetesClient.getOAuthServerUrl(clusterUrl); return new Promise((resolve, reject) => { const authHeader = Buffer.from(`${username}:${password}`).toString('base64'); @@ -185,6 +191,12 @@ export default class KubernetesClient { password: string, outputPath: string, ): Promise { + if (process.env.BRIDGE_AUTH_TYPE === 'oidc') { + throw new Error( + 'generateKubeconfig via OAuth is not available under external OIDC authentication. ' + + 'Use the existing kubeconfig provided by the CI environment instead.', + ); + } const token = await KubernetesClient.getOAuthToken(clusterUrl, username, password); const kubeconfigYaml = [ 'apiVersion: v1', diff --git a/frontend/e2e/fixtures/index.ts b/frontend/e2e/fixtures/index.ts index 27acba18697..1bc81071102 100644 --- a/frontend/e2e/fixtures/index.ts +++ b/frontend/e2e/fixtures/index.ts @@ -11,8 +11,9 @@ import { createCleanupFixture } from './cleanup-fixture'; // URLs the console redirects to when a shared storageState session expires or is // invalidated (e.g. by a console rollout in another spec). Matches the OAuth -// server and the console's own login route. -const OAUTH_REDIRECT_RE = /\/oauth\/|oauth-openshift|\/auth\/login\b/; +// server, the console's own login route, and Keycloak OIDC redirect URLs. +const OAUTH_REDIRECT_RE = + /\/oauth\/|oauth-openshift|\/auth\/login\b|\/realms\/|\/protocol\/openid-connect\//; export interface SharedTestConfig { testNamespace: string; diff --git a/frontend/e2e/setup/login-helper.ts b/frontend/e2e/setup/login-helper.ts index 1e6676b8351..711b1dc45e3 100644 --- a/frontend/e2e/setup/login-helper.ts +++ b/frontend/e2e/setup/login-helper.ts @@ -27,6 +27,12 @@ export async function performLogin( } const userMenu = page.getByTestId('user-dropdown-toggle'); + + if (process.env.BRIDGE_AUTH_TYPE === 'oidc') { + await performKeycloakLogin(page, userMenu, username, password); + return; + } + const loginForm = page.locator('[data-test-id="login"]').or(page.locator('#inputUsername')); // The context may already be authenticated (e.g. a reused storageState). In that @@ -52,12 +58,45 @@ export async function performLogin( await expect(userMenu).toBeVisible({ timeout: 60_000 }); } +/** + * Drive a Keycloak login form. The console redirects to the Keycloak realm login + * page when external OIDC is configured. No IDP selector is shown — the form + * renders directly with username/password fields. + */ +async function performKeycloakLogin( + page: Page, + userMenu: ReturnType, + username: string, + password: string, +): Promise { + // Keycloak form selectors — use .or() for robustness across themes + const usernameField = page.locator('#username').or(page.locator('input[name="username"]')); + const passwordField = page.locator('#password').or(page.locator('input[name="password"]')); + const submitButton = page.locator('#kc-login').or(page.locator('input[type="submit"]')); + + // Wait for either the Keycloak login form or an already-authenticated session + await expect(userMenu.or(usernameField).first()).toBeVisible({ timeout: 60_000 }); + if (await userMenu.isVisible().catch(() => false)) { + return; + } + + await expect(usernameField).toBeVisible({ timeout: 30_000 }); + await usernameField.fill(username); + await passwordField.fill(password); + await submitButton.click(); + + await expect(userMenu).toBeVisible({ timeout: 60_000 }); +} + /** * Log in using the credentials configured via environment variables for the * given persona. Admin uses the kubeadmin / kube:admin identity provider; - * developer uses the htpasswd identity provider. Used both by the auth setup - * projects and as a re-authentication fallback for specs whose shared - * storageState session has expired or been invalidated mid-run. + * developer uses the htpasswd identity provider. When BRIDGE_AUTH_TYPE=oidc + * (external OIDC / Keycloak), admin and developer credentials are read from + * the same env vars but the Keycloak login form is driven instead. + * Used both by the auth setup projects and as a re-authentication fallback + * for specs whose shared storageState session has expired or been invalidated + * mid-run. */ export async function loginFromEnv( page: Page, @@ -72,6 +111,7 @@ export async function loginFromEnv( 'Developer credentials (BRIDGE_HTPASSWD_USERNAME/PASSWORD) are not configured', ); } + // Under OIDC the IDP selector is not shown; performLogin branches internally const idpName = process.env.BRIDGE_HTPASSWD_IDP || username; await performLogin(page, baseURL, username, password, idpName); return; @@ -79,6 +119,7 @@ export async function loginFromEnv( const username = process.env.OPENSHIFT_USERNAME || 'kubeadmin'; const password = process.env.BRIDGE_KUBEADMIN_PASSWORD || ''; + // Under OIDC, performLogin drives the Keycloak form; idpName is unused await performLogin(page, baseURL, username, password, 'kube:admin'); } diff --git a/frontend/integration-tests/test-playwright.sh b/frontend/integration-tests/test-playwright.sh index e236c289e0f..69e3343f23b 100755 --- a/frontend/integration-tests/test-playwright.sh +++ b/frontend/integration-tests/test-playwright.sh @@ -35,6 +35,14 @@ fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +# Source CI runtime environment (e.g. Keycloak OIDC credentials) if available +if [ -f "${SHARED_DIR:-}/runtime_env" ]; then + set +x + # shellcheck disable=SC1091 + source "${SHARED_DIR}/runtime_env" + set -x +fi + RUN_CREATE_USER=false while getopts "c" flag; do @@ -74,7 +82,36 @@ BRIDGE_BASE_PATH=${BRIDGE_BASE_PATH:-/} export BRIDGE_BASE_PATH export WEB_CONSOLE_URL="${WEB_CONSOLE_URL:-${BRIDGE_BASE_ADDRESS}${BRIDGE_BASE_PATH}}" -if [ "$RUN_CREATE_USER" = true ]; then +if [ -n "${KEYCLOAK_ISSUER:-}" ]; then + # --- External OIDC (Keycloak) mode --- + export BRIDGE_AUTH_TYPE="oidc" + + # Parse admin (first) and developer (second) users from KEYCLOAK_TEST_USERS + set +x + IFS=',' read -ra _kc_users <<< "${KEYCLOAK_TEST_USERS}" + _kc_admin_entry="${_kc_users[0]}" + _kc_dev_entry="${_kc_users[1]:-}" + _kc_admin_user="${_kc_admin_entry%%:*}" + _kc_admin_pass="${_kc_admin_entry#*:}" + + export OPENSHIFT_USERNAME="${OPENSHIFT_USERNAME:-${_kc_admin_user}}" + export BRIDGE_KUBEADMIN_PASSWORD="${BRIDGE_KUBEADMIN_PASSWORD:-${_kc_admin_pass}}" + + if [ -n "${_kc_dev_entry}" ]; then + _kc_dev_user="${_kc_dev_entry%%:*}" + _kc_dev_pass="${_kc_dev_entry#*:}" + export BRIDGE_HTPASSWD_USERNAME="${BRIDGE_HTPASSWD_USERNAME:-${_kc_dev_user}}" + export BRIDGE_HTPASSWD_PASSWORD="${BRIDGE_HTPASSWD_PASSWORD:-${_kc_dev_pass}}" + fi + set -x + + # Grant cluster-admin to the admin Keycloak user's OIDC identity + _oidc_identity="oidc-user-test:${OPENSHIFT_USERNAME}@example.com" + oc adm policy add-cluster-role-to-user cluster-admin "${_oidc_identity}" || true + + unset _kc_users _kc_admin_entry _kc_dev_entry _kc_admin_user _kc_admin_pass + unset _kc_dev_user _kc_dev_pass _oidc_identity +elif [ "$RUN_CREATE_USER" = true ]; then "${REPO_ROOT}/contrib/create-user.sh" export BRIDGE_HTPASSWD_IDP="${BRIDGE_HTPASSWD_IDP:-test}" export BRIDGE_HTPASSWD_USERNAME="${BRIDGE_HTPASSWD_USERNAME:-test}" diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index cd0f42d3709..0797a51a41d 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -16,7 +16,8 @@ const baseURL = process.env.WEB_CONSOLE_URL || 'http://localhost:9000'; // import.meta (ESM) while Playwright loads this config as CommonJS. const adminStorageState = path.resolve(__dirname, 'e2e', '.auth', 'kubeadmin.json'); const developerStorageState = path.resolve(__dirname, 'e2e', '.auth', 'developer.json'); -const hasDeveloper = !!process.env.BRIDGE_HTPASSWD_USERNAME; +const hasDeveloper = + !!process.env.BRIDGE_HTPASSWD_USERNAME || process.env.BRIDGE_AUTH_TYPE === 'oidc'; const packages = [ 'smoke', diff --git a/test-prow-e2e.sh b/test-prow-e2e.sh index f15e4335061..b72c3cf5f6f 100755 --- a/test-prow-e2e.sh +++ b/test-prow-e2e.sh @@ -35,13 +35,55 @@ esac export ARTIFACT_DIR INSTALLER_DIR mkdir -p "${ARTIFACT_DIR}" -# don't log kubeadmin-password -set +x -export BRIDGE_KUBEADMIN_PASSWORD="$(cat "${KUBEADMIN_PASSWORD_FILE:-${INSTALLER_DIR}/auth/kubeadmin-password}")" -set -x +# Source CI runtime environment (e.g. Keycloak OIDC credentials) if available +if [ -f "${SHARED_DIR:-}/runtime_env" ]; then + set +x + # shellcheck disable=SC1091 + source "${SHARED_DIR}/runtime_env" + set -x +fi + export BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')" -./contrib/create-user.sh +if [ -n "${KEYCLOAK_ISSUER:-}" ]; then + # --- External OIDC (Keycloak) mode --- + export BRIDGE_AUTH_TYPE="oidc" + + # Parse admin (first) and developer (second) users from KEYCLOAK_TEST_USERS + # Format: "user1:pass1,user2:pass2,..." + set +x + IFS=',' read -ra _kc_users <<< "${KEYCLOAK_TEST_USERS}" + _kc_admin_entry="${_kc_users[0]}" + _kc_dev_entry="${_kc_users[1]:-}" + _kc_admin_user="${_kc_admin_entry%%:*}" + _kc_admin_pass="${_kc_admin_entry#*:}" + + export OPENSHIFT_USERNAME="${_kc_admin_user}" + export BRIDGE_KUBEADMIN_PASSWORD="${_kc_admin_pass}" + + if [ -n "${_kc_dev_entry}" ]; then + _kc_dev_user="${_kc_dev_entry%%:*}" + _kc_dev_pass="${_kc_dev_entry#*:}" + export BRIDGE_HTPASSWD_USERNAME="${_kc_dev_user}" + export BRIDGE_HTPASSWD_PASSWORD="${_kc_dev_pass}" + fi + set -x + + # Grant cluster-admin to the admin Keycloak user's OIDC identity + _oidc_identity="oidc-user-test:${_kc_admin_user}@example.com" + oc adm policy add-cluster-role-to-user cluster-admin "${_oidc_identity}" || true + + unset _kc_users _kc_admin_entry _kc_dev_entry _kc_admin_user _kc_admin_pass + unset _kc_dev_user _kc_dev_pass _oidc_identity +else + # --- Standard OAuth mode --- + # don't log kubeadmin-password + set +x + export BRIDGE_KUBEADMIN_PASSWORD="$(cat "${KUBEADMIN_PASSWORD_FILE:-${INSTALLER_DIR}/auth/kubeadmin-password}")" + set -x + + ./contrib/create-user.sh +fi export WORKERS="${WORKERS:-2}" export GLOBAL_TIMEOUT_MS="${GLOBAL_TIMEOUT_MS:-6600000}" From 535b63c1dfa6baa9b11570a8f077f5f70fdaf646 Mon Sep 17 00:00:00 2001 From: Chai Bot Date: Mon, 21 Sep 2026 10:12:03 +0000 Subject: [PATCH 2/3] Address review feedback: fix hasDeveloper edge case and guard KEYCLOAK_TEST_USERS Co-Authored-By: Claude Opus 4.6 --- frontend/integration-tests/test-playwright.sh | 5 +++++ frontend/playwright.config.ts | 3 +-- test-prow-e2e.sh | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/integration-tests/test-playwright.sh b/frontend/integration-tests/test-playwright.sh index 69e3343f23b..0aeb11183a2 100755 --- a/frontend/integration-tests/test-playwright.sh +++ b/frontend/integration-tests/test-playwright.sh @@ -84,6 +84,11 @@ export WEB_CONSOLE_URL="${WEB_CONSOLE_URL:-${BRIDGE_BASE_ADDRESS}${BRIDGE_BASE_P if [ -n "${KEYCLOAK_ISSUER:-}" ]; then # --- External OIDC (Keycloak) mode --- + if [ -z "${KEYCLOAK_TEST_USERS:-}" ]; then + echo "ERROR: KEYCLOAK_ISSUER is set but KEYCLOAK_TEST_USERS is missing or empty" >&2 + exit 1 + fi + export BRIDGE_AUTH_TYPE="oidc" # Parse admin (first) and developer (second) users from KEYCLOAK_TEST_USERS diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index 0797a51a41d..cd0f42d3709 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -16,8 +16,7 @@ const baseURL = process.env.WEB_CONSOLE_URL || 'http://localhost:9000'; // import.meta (ESM) while Playwright loads this config as CommonJS. const adminStorageState = path.resolve(__dirname, 'e2e', '.auth', 'kubeadmin.json'); const developerStorageState = path.resolve(__dirname, 'e2e', '.auth', 'developer.json'); -const hasDeveloper = - !!process.env.BRIDGE_HTPASSWD_USERNAME || process.env.BRIDGE_AUTH_TYPE === 'oidc'; +const hasDeveloper = !!process.env.BRIDGE_HTPASSWD_USERNAME; const packages = [ 'smoke', diff --git a/test-prow-e2e.sh b/test-prow-e2e.sh index b72c3cf5f6f..36d7b860b66 100755 --- a/test-prow-e2e.sh +++ b/test-prow-e2e.sh @@ -47,6 +47,11 @@ export BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jso if [ -n "${KEYCLOAK_ISSUER:-}" ]; then # --- External OIDC (Keycloak) mode --- + if [ -z "${KEYCLOAK_TEST_USERS:-}" ]; then + echo "ERROR: KEYCLOAK_ISSUER is set but KEYCLOAK_TEST_USERS is missing or empty" >&2 + exit 1 + fi + export BRIDGE_AUTH_TYPE="oidc" # Parse admin (first) and developer (second) users from KEYCLOAK_TEST_USERS From 216288d0c0e927f80dfaeeeb9c69af5dd1bd8949 Mon Sep 17 00:00:00 2001 From: Chai Bot Date: Mon, 21 Sep 2026 20:06:35 +0000 Subject: [PATCH 3/3] Add lightweight OIDC auth test and dedicated test script Co-Authored-By: Claude Opus 4.6 --- frontend/e2e/tests/oidc/oidc-auth.spec.ts | 114 ++++++++++++++++++++++ test-prow-e2e-oidc.sh | 98 +++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 frontend/e2e/tests/oidc/oidc-auth.spec.ts create mode 100755 test-prow-e2e-oidc.sh diff --git a/frontend/e2e/tests/oidc/oidc-auth.spec.ts b/frontend/e2e/tests/oidc/oidc-auth.spec.ts new file mode 100644 index 00000000000..3364b982949 --- /dev/null +++ b/frontend/e2e/tests/oidc/oidc-auth.spec.ts @@ -0,0 +1,114 @@ +import { test, expect } from '../../fixtures'; +import { performLogin } from '../../setup/login-helper'; + +test.describe('External OIDC Authentication', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test.beforeEach(() => { + test.skip( + process.env.BRIDGE_AUTH_TYPE !== 'oidc', + 'Requires BRIDGE_AUTH_TYPE=oidc (external OIDC cluster)', + ); + }); + + test('logs in as admin via Keycloak and verifies dashboard', async ({ page }) => { + const username = process.env.OPENSHIFT_USERNAME; + const password = process.env.BRIDGE_KUBEADMIN_PASSWORD; + const baseURL = process.env.WEB_CONSOLE_URL || 'http://localhost:9000'; + + test.skip(!username || !password, 'Admin credentials not configured'); + + await performLogin(page, baseURL, username!, password!); + + await test.step('Verify user menu shows logged-in username', async () => { + const userMenu = page.getByTestId('user-dropdown-toggle'); + await expect(userMenu).toBeVisible({ timeout: 30_000 }); + }); + + await test.step('Verify console dashboard loads', async () => { + await expect(page.getByTestId('loading-indicator')).not.toBeAttached({ timeout: 30_000 }); + }); + }); + + test('admin user has cluster-admin privileges', async ({ page }) => { + const username = process.env.OPENSHIFT_USERNAME; + const password = process.env.BRIDGE_KUBEADMIN_PASSWORD; + const baseURL = process.env.WEB_CONSOLE_URL || 'http://localhost:9000'; + + test.skip(!username || !password, 'Admin credentials not configured'); + + await performLogin(page, baseURL, username!, password!); + + await test.step('Verify Administration section is visible', async () => { + const sidebar = page.locator('#page-sidebar'); + await expect(sidebar.getByRole('button', { name: 'Administration' })).toBeVisible({ + timeout: 30_000, + }); + }); + + await test.step('Navigate to Cluster Settings', async () => { + const sidebar = page.locator('#page-sidebar'); + const adminSection = sidebar.getByRole('button', { name: 'Administration' }); + await adminSection.click(); + await sidebar.getByRole('link', { name: 'Cluster Settings' }).click(); + await expect(page.getByTestId('cluster-settings-page-heading')).toBeVisible({ + timeout: 30_000, + }); + }); + }); + + test('logs in as developer via Keycloak and verifies developer perspective', async ({ page }) => { + const username = process.env.BRIDGE_HTPASSWD_USERNAME; + const password = process.env.BRIDGE_HTPASSWD_PASSWORD; + const baseURL = process.env.WEB_CONSOLE_URL || 'http://localhost:9000'; + + test.skip(!username || !password, 'Developer credentials not configured'); + + await performLogin(page, baseURL, username!, password!); + + await test.step('Verify username is displayed', async () => { + await expect(page.getByTestId('user-dropdown-toggle')).toHaveText(username!, { + timeout: 30_000, + }); + }); + + await test.step('Switch to Developer perspective', async () => { + const toggle = page.getByTestId('perspective-switcher-toggle'); + await toggle.click(); + const devOption = page + .getByTestId('perspective-switcher-menu-option') + .filter({ hasText: 'Developer' }); + await devOption.click(); + await expect(toggle).toContainText('Developer', { timeout: 30_000 }); + }); + }); + + test('logout redirects to login page', async ({ page }) => { + const username = process.env.OPENSHIFT_USERNAME; + const password = process.env.BRIDGE_KUBEADMIN_PASSWORD; + const baseURL = process.env.WEB_CONSOLE_URL || 'http://localhost:9000'; + + test.skip(!username || !password, 'Admin credentials not configured'); + + await performLogin(page, baseURL, username!, password!); + + const userMenu = page.getByTestId('user-dropdown-toggle'); + await expect(userMenu).toBeVisible({ timeout: 30_000 }); + + await test.step('Click user menu and log out', async () => { + await userMenu.click(); + const logoutButton = page.getByTestId('log-out'); + await expect(logoutButton).toBeVisible({ timeout: 10_000 }); + await logoutButton.click(); + }); + + await test.step('Verify redirect to Keycloak or login page', async () => { + // After logout, the browser should redirect to the Keycloak login page + // or the console login route. Wait for a URL containing a Keycloak realm + // path or the console's auth route. + await page.waitForURL(/\/realms\/|\/protocol\/openid-connect\/|\/auth\/login\b|\/oauth\//, { + timeout: 60_000, + }); + }); + }); +}); diff --git a/test-prow-e2e-oidc.sh b/test-prow-e2e-oidc.sh new file mode 100755 index 00000000000..6ca02a95370 --- /dev/null +++ b/test-prow-e2e-oidc.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# +# Prow / CI entrypoint for Playwright E2E tests on external-OIDC (Keycloak) clusters. +# This script is OIDC-only — it fails if KEYCLOAK_ISSUER is not set. +# +# Run from the openshift/console repository root. +# +# Environment (set by the idp-external-oidc-keycloak-aws workflow): +# KEYCLOAK_ISSUER — Keycloak realm issuer URL (required) +# KEYCLOAK_TEST_USERS — "user1:pass1,user2:pass2,..." (required) +# SHARED_DIR — CI shared directory containing runtime_env +# ARTIFACT_DIR, INSTALLER_DIR — Prow artifact paths +# + +set -exuo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "${REPO_ROOT}" + +ARTIFACT_DIR=${ARTIFACT_DIR:-/tmp/artifacts} +INSTALLER_DIR=${INSTALLER_DIR:=${ARTIFACT_DIR}/installer} + +# Validate ARTIFACT_DIR is set and is an absolute path +if [ -z "$ARTIFACT_DIR" ]; then + echo "Error: ARTIFACT_DIR is not set" >&2 + exit 1 +fi +case "$ARTIFACT_DIR" in + /) echo "Error: ARTIFACT_DIR must not be '/'" >&2; exit 1 ;; + /*) ;; # absolute path, OK + *) echo "Error: ARTIFACT_DIR must be an absolute path, got: $ARTIFACT_DIR" >&2; exit 1 ;; +esac + +export ARTIFACT_DIR INSTALLER_DIR +mkdir -p "${ARTIFACT_DIR}" + +# Source CI runtime environment (Keycloak OIDC credentials) +if [ -f "${SHARED_DIR:-}/runtime_env" ]; then + set +x + # shellcheck disable=SC1091 + source "${SHARED_DIR}/runtime_env" + set -x +fi + +# This script is OIDC-only — fail fast if not configured +if [ -z "${KEYCLOAK_ISSUER:-}" ]; then + echo "ERROR: KEYCLOAK_ISSUER is not set. This script requires an external-OIDC cluster." >&2 + exit 1 +fi + +if [ -z "${KEYCLOAK_TEST_USERS:-}" ]; then + echo "ERROR: KEYCLOAK_ISSUER is set but KEYCLOAK_TEST_USERS is missing or empty" >&2 + exit 1 +fi + +export BRIDGE_AUTH_TYPE="oidc" + +# Parse admin (first) and developer (second) users from KEYCLOAK_TEST_USERS +# Format: "user1:pass1,user2:pass2,..." +set +x +IFS=',' read -ra _kc_users <<< "${KEYCLOAK_TEST_USERS}" +_kc_admin_entry="${_kc_users[0]}" +_kc_dev_entry="${_kc_users[1]:-}" +_kc_admin_user="${_kc_admin_entry%%:*}" +_kc_admin_pass="${_kc_admin_entry#*:}" + +export OPENSHIFT_USERNAME="${_kc_admin_user}" +export BRIDGE_KUBEADMIN_PASSWORD="${_kc_admin_pass}" + +if [ -n "${_kc_dev_entry}" ]; then + _kc_dev_user="${_kc_dev_entry%%:*}" + _kc_dev_pass="${_kc_dev_entry#*:}" + export BRIDGE_HTPASSWD_USERNAME="${_kc_dev_user}" + export BRIDGE_HTPASSWD_PASSWORD="${_kc_dev_pass}" +fi +set -x + +# Grant cluster-admin to the admin Keycloak user's OIDC identity +_oidc_identity="oidc-user-test:${_kc_admin_user}@example.com" +oc adm policy add-cluster-role-to-user cluster-admin "${_oidc_identity}" || true + +unset _kc_users _kc_admin_entry _kc_dev_entry _kc_admin_user _kc_admin_pass +unset _kc_dev_user _kc_dev_pass _oidc_identity + +export BRIDGE_BASE_ADDRESS="$(oc get consoles.config.openshift.io cluster -o jsonpath='{.status.consoleURL}')" + +export WORKERS="${WORKERS:-2}" +export GLOBAL_TIMEOUT_MS="${GLOBAL_TIMEOUT_MS:-6600000}" + +pushd frontend + +if [ ! -d node_modules ]; then + yarn install +fi + +./integration-tests/test-playwright.sh -- e2e/tests/oidc/oidc-auth.spec.ts "$@" + +popd