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
17 changes: 17 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
node_modules
dist
.git
.idea
.vscode
.devcontainer
.github
ci-scripts
cypress
integration-tests
problem
*.md
.env
.gitignore
.dockerignore
.prettierignore
.eslintignore
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BRIDGE_BASE_ADDRESS=https://console-openshift-console.apps.your-cluster.example.com
BRIDGE_KUBEADMIN_PASSWORD=your-password
TEST_NS=cy-test-ns
UDN_NS=udn-test-ns
HIDE_XHR=true
58 changes: 58 additions & 0 deletions .github/actions/ci-env-release/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Release CI Test Environment
description: >
Signal the ci-env-controller to tear down a test environment by patching
the trigger ConfigMap to desired-state=absent, then wait for cleanup to
complete and delete the ConfigMap.

inputs:
configmap-name:
description: Name of the trigger ConfigMap
required: true
ci-env-namespace:
description: Namespace where ci-env-controller runs
default: ci-env
timeout:
description: Max seconds to wait for cleanup to complete
default: '300'

runs:
using: composite
steps:
- name: Release environment
shell: bash
env:
CM_NAME: ${{ inputs.configmap-name }}
CM_NS: ${{ inputs.ci-env-namespace }}
TIMEOUT: ${{ inputs.timeout }}
run: |
if ! oc get configmap "${CM_NAME}" -n "${CM_NS}" &>/dev/null; then
echo "ConfigMap ${CM_NS}/${CM_NAME} not found, nothing to clean up."
exit 0
fi

oc patch configmap "${CM_NAME}" -n "${CM_NS}" \
--type merge -p '{"data":{"desired-state":"absent"}}'

echo "Waiting for controller to clean up..."
INTERVAL=5
ELAPSED=0

while true; do
STATUS="$(oc get configmap "${CM_NAME}" -n "${CM_NS}" \
-o jsonpath='{.data.status}' 2>/dev/null || echo "")"

if [[ "${STATUS}" == "cleaned" ]]; then
echo "Cleanup complete."
break
fi

if (( ELAPSED >= TIMEOUT )); then
echo "::warning::Timed out waiting for controller cleanup (status=${STATUS})"
break
fi

sleep "${INTERVAL}"
ELAPSED=$(( ELAPSED + INTERVAL ))
done

oc delete configmap "${CM_NAME}" -n "${CM_NS}" 2>/dev/null || true
124 changes: 124 additions & 0 deletions .github/actions/ci-env-request/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Request CI Test Environment
description: >
Create a trigger ConfigMap for the ci-env-controller and wait until the
test environment (namespace, console, plugin) is provisioned and ready.

inputs:
plugin-image:
description: Plugin container image to deploy
required: true
test-namespace:
description: Kubernetes namespace for the test environment
required: true
configmap-name:
description: Name of the trigger ConfigMap
required: true
ci-env-namespace:
description: Namespace where ci-env-controller runs
default: ci-env
timeout:
description: Max seconds to wait for environment to become ready
default: '360'

outputs:
bridge-base-address:
description: In-cluster URL for the console bridge
value: ${{ steps.wait.outputs.bridge-base-address }}
console-route:
description: External HTTPS route for the console
value: ${{ steps.wait.outputs.console-route }}

runs:
using: composite
steps:
- name: Create trigger ConfigMap
shell: bash
run: |
cat <<EOF | oc create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: ${{ inputs.configmap-name }}
namespace: ${{ inputs.ci-env-namespace }}
labels:
ci.networking-console-plugin/type: test-environment
data:
desired-state: "present"
plugin-image: "${{ inputs.plugin-image }}"
test-namespace: "${{ inputs.test-namespace }}"
EOF
echo "Created trigger ConfigMap ${{ inputs.configmap-name }} in ${{ inputs.ci-env-namespace }}"

- name: Wait for environment ready
id: wait
shell: bash
env:
CM_NAME: ${{ inputs.configmap-name }}
CM_NS: ${{ inputs.ci-env-namespace }}
TIMEOUT: ${{ inputs.timeout }}
run: |
echo "Waiting for ci-env-controller to provision the test environment..."
INTERVAL=10
ELAPSED=0

while true; do
STATUS="$(oc get configmap "${CM_NAME}" -n "${CM_NS}" \
-o jsonpath='{.data.status}' 2>/dev/null || echo "")"

case "${STATUS}" in
ready)
echo "Environment is ready."
break
;;
error)
ERR_MSG="$(oc get configmap "${CM_NAME}" -n "${CM_NS}" \
-o jsonpath='{.data.error-message}' 2>/dev/null || echo "unknown error")"
echo "::error::Environment provisioning failed: ${ERR_MSG}"
exit 1
;;
*)
if (( ELAPSED >= TIMEOUT )); then
echo "::error::Timed out waiting for environment (status=${STATUS:-pending})"
exit 1
fi
echo " status=${STATUS:-pending} (${ELAPSED}s / ${TIMEOUT}s)..."
sleep "${INTERVAL}"
ELAPSED=$(( ELAPSED + INTERVAL ))
;;
esac
done

BRIDGE_BASE_ADDRESS="$(oc get configmap "${CM_NAME}" -n "${CM_NS}" \
-o jsonpath='{.data.bridge-base-address}')"
CONSOLE_ROUTE="$(oc get configmap "${CM_NAME}" -n "${CM_NS}" \
-o jsonpath='{.data.console-route}' 2>/dev/null || echo "")"

echo "bridge-base-address=${BRIDGE_BASE_ADDRESS}" >> "${GITHUB_OUTPUT}"
echo "console-route=${CONSOLE_ROUTE}" >> "${GITHUB_OUTPUT}"

- name: Write job summary
shell: bash
env:
CM_NAME: ${{ inputs.configmap-name }}
CM_NS: ${{ inputs.ci-env-namespace }}
PLUGIN_IMAGE: ${{ inputs.plugin-image }}
TEST_NS: ${{ inputs.test-namespace }}
BRIDGE: ${{ steps.wait.outputs.bridge-base-address }}
ROUTE: ${{ steps.wait.outputs.console-route }}
run: |
{
echo "<details><summary>CI Test Environment</summary>"
echo ""
echo "| Input Parameter | Value |"
echo "|------|-------|"
echo "| ConfigMap | \`${CM_NS}/${CM_NAME}\` |"
echo "| Plugin image | \`${PLUGIN_IMAGE}\` |"
echo "| Test namespace | \`${TEST_NS}\` |"
echo ""
echo "| Output Parameter | Value |"
echo "|------|-------|"
echo "| Bridge base address | \`${BRIDGE}\` |"
echo "| Console route | \`${ROUTE}\` |"
echo ""
echo "</details>"
} >> "${GITHUB_STEP_SUMMARY}"
47 changes: 47 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: E2E Tests

on:
pull_request:
branches: [main, release-*]
workflow_dispatch:

concurrency:
group: e2e-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
e2e:
name: Cypress E2E
runs-on: ubuntu-latest
timeout-minutes: 30

env:
BRIDGE_BASE_ADDRESS: ${{ secrets.CONSOLE_URL }}
BRIDGE_KUBEADMIN_PASSWORD: ${{ secrets.KUBEADMIN_PASSWORD }}
BRIDGE_E2E_BROWSER_NAME: electron
TEST_NS: ${{ secrets.TEST_NS || 'cy-test-ns' }}
UDN_NS: udn-test-ns

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: npm

- name: Install dependencies
run: npm ci

- name: Run E2E tests
run: npm run test-e2e

- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-results
path: cypress/gui-test-screenshots/
retention-days: 7
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ integration-tests/screenshots
integration-tests/.DS_Store
yarn-error.log
.DS_Store
cypress/gui-test-screenshots/
.env
75 changes: 75 additions & 0 deletions ci-scripts/_cluster-helpers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/bash
#
# Resolve CLI binary download URLs from a live OpenShift cluster.
#
# Source this file; do not execute it directly.
# source "$(dirname "${BASH_SOURCE[0]}")/_cluster-helpers.sh"
#
# After sourcing, call:
# resolve_cli_downloads # populates OC_URL
# resolve_oc_version # populates OC_VERSION (if not already set)
# resolve_internal_registry # populates INTERNAL_REGISTRY

verify_oc() {
if ! oc get clusterversion version &>/dev/null; then
echo "ERROR: OpenShift cluster required (clusterversion.version not found)."
exit 1
fi
}

_route_url_to_internal() {
local url="${1}"
[[ -z "${url}" ]] && return
local host path route_info ns svc svc_port
host=$(echo "${url}" | sed -E 's|https://([^/]+).*|\1|')
path=$(echo "${url}" | sed -E 's|https://[^/]+(/.*)?|\1|' || echo '/')
route_info=$(echo "${_ALL_ROUTES_JSON}" \
| jq -r --arg h "${host}" \
'.items[] | select(.spec.host == $h) | "\(.metadata.namespace) \(.spec.to.name)"' \
| head -1)
if [[ -n "${route_info}" ]]; then
read -r ns svc <<< "${route_info}"
svc_port=$(oc get service "${svc}" -n "${ns}" \
-o jsonpath='{.spec.ports[0].port}' 2>/dev/null || echo "8080")
echo "http://${svc}.${ns}.svc.cluster.local:${svc_port}${path}"
else
echo "${url}"
fi
}

resolve_oc_version() {
if [[ -n "${OC_VERSION:-}" ]]; then
return
fi
OC_VERSION=$(oc version --output json 2>/dev/null \
| jq -r '.openshiftVersion | split(".") | .[0:2] | join(".") // empty') || true
OC_VERSION="${OC_VERSION:-4.20}"
}

resolve_internal_registry() {
INTERNAL_REGISTRY="$(oc get image.config.openshift.io/cluster \
-o jsonpath='{.status.internalRegistryHostname}' 2>/dev/null \
|| echo 'image-registry.openshift-image-registry.svc:5000')"
}

resolve_cli_downloads() {
OC_URL=""

if ! command -v jq &>/dev/null; then
return
fi

local cli_json
cli_json=$(oc get consoleclidownload -o json 2>/dev/null || true)
if [[ -z "${cli_json}" ]]; then
return
fi

OC_URL=$(echo "${cli_json}" \
| jq -r '.items[].spec.links[] | select(.text | test("oc.*linux.*x86_64|oc.*linux.*amd64"; "i")) | .href' \
| head -1)

_ALL_ROUTES_JSON=$(oc get route --all-namespaces -o json 2>/dev/null || true)
[[ -n "${OC_URL}" ]] && OC_URL=$(_route_url_to_internal "${OC_URL}")
unset _ALL_ROUTES_JSON
}
Loading