diff --git a/ci-operator/config/quay/quay/quay-quay-master.yaml b/ci-operator/config/quay/quay/quay-quay-master.yaml index 67fccce18dbaf..f02d85b784b2b 100644 --- a/ci-operator/config/quay/quay/quay-quay-master.yaml +++ b/ci-operator/config/quay/quay/quay-quay-master.yaml @@ -10,23 +10,15 @@ build_root: tag: rhel-9-release-golang-1.23-openshift-4.18 images: items: - - dockerfile_path: Dockerfile - to: quay-server - dockerfile_literal: | - FROM src - RUN dnf module enable -y nodejs:20 && \ - dnf install -y nodejs \ - alsa-lib atk at-spi2-atk cups-libs libdrm libXcomposite \ - libXdamage libXrandr mesa-libgbm pango nss nss-util nspr \ - libxkbcommon libXfixes libXcursor libXext libXi libXtst \ - libwayland-client && \ - dnf clean all - WORKDIR /go/src/github.com/quay/quay/web - RUN npm ci - ENV PLAYWRIGHT_BROWSERS_PATH=/opt/playwright - RUN npx playwright install chromium - from: src - to: quay-playwright-runner + FROM registry.access.redhat.com/ubi9/nodejs-22:latest + USER root + RUN dnf install -y --setopt=install_weak_deps=0 --nodocs \ + nss nspr atk at-spi2-atk at-spi2-core alsa-lib cups-libs \ + libdrm libxcb libxkbcommon libX11 libXcomposite libXdamage \ + libXext libXfixes libXrandr mesa-libgbm pango cairo git \ + && dnf clean all + to: playwright-base releases: latest: candidate: @@ -43,11 +35,9 @@ resources: memory: 200Mi tests: - always_run: false - as: playwright-e2e + as: e2e steps: cluster_profile: aws-quay-qe - dependencies: - QUAY_CI_IMAGE: pipeline:quay-server env: BASE_DOMAIN: quayqe.devcluster.openshift.com COMPUTE_NODE_TYPE: m5.2xlarge @@ -55,18 +45,25 @@ tests: FEATURE_REPO_MIRROR: true FEATURE_USER_METADATA: true FEATURE_IMMUTABLE_TAGS: true + FEATURE_MAILING: true + MAIL_SERVER: mailpit.quay-enterprise.svc + MAIL_PORT: 1025 + MAIL_USE_TLS: false + MAIL_USE_AUTH: false + MAIL_DEFAULT_SENDER: noreply@quay.io SUPER_USERS: - admin GLOBAL_READONLY_SUPER_USERS: - readonly - QUAY_OPERATOR_CHANNEL: stable-3.16 + QUAY_OPERATOR_CHANNEL: stable-3.17 QUAY_OPERATOR_SOURCE: redhat-operators test: + - ref: quay-deploy-test-services + - ref: quay-tests-enable-quay-catalogsource - ref: quay-tests-deploy-quay-aws-s3 - - ref: quay-tests-deploy-quay-custom-image - - ref: quay-tests-test-quay-playwright + - ref: quay-test-playwright workflow: cucushift-installer-rehearse-aws-ipi - timeout: 4h0m0s + timeout: 5h0m0s zz_generated_metadata: branch: master org: quay diff --git a/ci-operator/jobs/quay/quay/quay-quay-master-presubmits.yaml b/ci-operator/jobs/quay/quay/quay-quay-master-presubmits.yaml index a0f7f75f0accb..a36b98062acd2 100644 --- a/ci-operator/jobs/quay/quay/quay-quay-master-presubmits.yaml +++ b/ci-operator/jobs/quay/quay/quay-quay-master-presubmits.yaml @@ -1,38 +1,56 @@ presubmits: quay/quay: - agent: kubernetes - always_run: true + always_run: false branches: - ^master$ - ^master- - cluster: build03 - context: ci/prow/images + cluster: build01 + context: ci/prow/e2e decorate: true decoration_config: - sparse_checkout_files: - - Dockerfile + skip_cloning: true + timeout: 5h0m0s labels: + ci-operator.openshift.io/cloud: aws + ci-operator.openshift.io/cloud-cluster-profile: aws-quay-qe ci.openshift.io/generator: prowgen job-release: "4.18" pj-rehearse.openshift.io/can-be-rehearsed: "true" - name: pull-ci-quay-quay-master-images - rerun_command: /test images + name: pull-ci-quay-quay-master-e2e + rerun_command: /test e2e spec: containers: - args: - --gcs-upload-secret=/secrets/gcs/service-account.json - --image-import-pull-secret=/etc/pull-secret/.dockerconfigjson + - --lease-server-credentials-file=/etc/boskos/credentials - --report-credentials-file=/etc/report/credentials - - --target=[images] + - --secret-dir=/secrets/ci-pull-credentials + - --target=e2e command: - ci-operator + env: + - name: HTTP_SERVER_IP + valueFrom: + fieldRef: + fieldPath: status.podIP image: quay-proxy.ci.openshift.org/openshift/ci:ci_ci-operator_latest imagePullPolicy: Always name: "" + ports: + - containerPort: 8080 + name: http resources: requests: cpu: 10m volumeMounts: + - mountPath: /etc/boskos + name: boskos + readOnly: true + - mountPath: /secrets/ci-pull-credentials + name: ci-pull-credentials + readOnly: true - mountPath: /secrets/gcs name: gcs-credentials readOnly: true @@ -47,6 +65,15 @@ presubmits: readOnly: true serviceAccountName: ci-operator volumes: + - name: boskos + secret: + items: + - key: credentials + path: credentials + secretName: boskos-credentials + - name: ci-pull-credentials + secret: + secretName: ci-pull-credentials - name: manifest-tool-local-pusher secret: secretName: manifest-tool-local-pusher @@ -56,59 +83,39 @@ presubmits: - name: result-aggregator secret: secretName: result-aggregator - trigger: (?m)^/test( | .* )images,?($|\s.*) + trigger: (?m)^/test( | .* )(e2e|remaining-required),?($|\s.*) - agent: kubernetes - always_run: false + always_run: true branches: - ^master$ - ^master- - cluster: build01 - context: ci/prow/playwright-e2e + cluster: build03 + context: ci/prow/images decorate: true decoration_config: - sparse_checkout_files: - - Dockerfile - timeout: 4h0m0s + skip_cloning: true labels: - ci-operator.openshift.io/cloud: aws - ci-operator.openshift.io/cloud-cluster-profile: aws-quay-qe ci.openshift.io/generator: prowgen job-release: "4.18" pj-rehearse.openshift.io/can-be-rehearsed: "true" - name: pull-ci-quay-quay-master-playwright-e2e - rerun_command: /test playwright-e2e + name: pull-ci-quay-quay-master-images + rerun_command: /test images spec: containers: - args: - --gcs-upload-secret=/secrets/gcs/service-account.json - --image-import-pull-secret=/etc/pull-secret/.dockerconfigjson - - --lease-server-credentials-file=/etc/boskos/credentials - --report-credentials-file=/etc/report/credentials - - --secret-dir=/secrets/ci-pull-credentials - - --target=playwright-e2e + - --target=[images] command: - ci-operator - env: - - name: HTTP_SERVER_IP - valueFrom: - fieldRef: - fieldPath: status.podIP image: quay-proxy.ci.openshift.org/openshift/ci:ci_ci-operator_latest imagePullPolicy: Always name: "" - ports: - - containerPort: 8080 - name: http resources: requests: cpu: 10m volumeMounts: - - mountPath: /etc/boskos - name: boskos - readOnly: true - - mountPath: /secrets/ci-pull-credentials - name: ci-pull-credentials - readOnly: true - mountPath: /secrets/gcs name: gcs-credentials readOnly: true @@ -123,15 +130,6 @@ presubmits: readOnly: true serviceAccountName: ci-operator volumes: - - name: boskos - secret: - items: - - key: credentials - path: credentials - secretName: boskos-credentials - - name: ci-pull-credentials - secret: - secretName: ci-pull-credentials - name: manifest-tool-local-pusher secret: secretName: manifest-tool-local-pusher @@ -141,4 +139,4 @@ presubmits: - name: result-aggregator secret: secretName: result-aggregator - trigger: (?m)^/test( | .* )(playwright-e2e|remaining-required),?($|\s.*) + trigger: (?m)^/test( | .* )images,?($|\s.*) diff --git a/ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-commands.sh b/ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-commands.sh deleted file mode 100755 index 95a6ed46b2a74..0000000000000 --- a/ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-commands.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -set -euo pipefail -set -x - -ARTIFACT_DIR=${ARTIFACT_DIR:=/tmp/artifacts} -mkdir -p "${ARTIFACT_DIR}" - -# Read the Quay route written by the deploy step -QUAY_ROUTE=$(cat "${SHARED_DIR}/quayroute") -if [[ -z "${QUAY_ROUTE}" ]]; then - echo "ERROR: quayroute not found in SHARED_DIR" >&2 - exit 1 -fi -echo "Quay route: ${QUAY_ROUTE}" - -# Read credentials -# Disable tracing due to password handling -[[ $- == *x* ]] && WAS_TRACING=true || WAS_TRACING=false -set +x -QUAY_USERNAME=$(cat /var/run/quay-qe-quay-secret/username) -QUAY_PASSWORD=$(cat /var/run/quay-qe-quay-secret/password) -$WAS_TRACING && set -x - -# Configure Playwright environment -# PLAYWRIGHT_BASE_URL: browser navigation URL (Quay UI) -# REACT_QUAY_APP_API_URL: backend API URL (same as UI on OCP) -export PLAYWRIGHT_BASE_URL="${QUAY_ROUTE}" -export REACT_QUAY_APP_API_URL="${QUAY_ROUTE}" -export PLAYWRIGHT_JUNIT_OUTPUT_NAME="${ARTIFACT_DIR}/junit_playwright.xml" -export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright -export QUAY_USERNAME -export QUAY_PASSWORD -export CI=true - -# WORKDIR is already set to the web/ directory by the quay-playwright-runner image - -function copyArtifacts { - echo "Copying test artifacts..." - cp -r test-results/* "${ARTIFACT_DIR}/" 2>/dev/null || true - # Rename JUnit reports with junit_ prefix for Prow - for file in "${ARTIFACT_DIR}"/*.xml; do - if [[ -f "${file}" ]] && [[ ! "$(basename "${file}")" =~ ^junit_ ]]; then - mv "${file}" "${ARTIFACT_DIR}/junit_$(basename "${file}")" - fi - done - cp -r playwright-report/* "${ARTIFACT_DIR}/" 2>/dev/null || true -} -trap copyArtifacts EXIT - -echo "Running Playwright tests..." -npx playwright test \ - --reporter=junit,html \ - 2>&1 | tee "${ARTIFACT_DIR}/playwright-output.log" diff --git a/ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-ref.yaml b/ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-ref.yaml deleted file mode 100644 index aa72816cd3747..0000000000000 --- a/ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-ref.yaml +++ /dev/null @@ -1,17 +0,0 @@ -ref: - as: quay-tests-test-quay-playwright - cli: latest - from: quay-playwright-runner - commands: quay-tests-test-quay-playwright-commands.sh - resources: - requests: - cpu: "2" - memory: 4Gi - timeout: 2h0m0s - grace_period: 15m0s - credentials: - - namespace: test-credentials - name: quay-qe-quay-secret - mount_path: /var/run/quay-qe-quay-secret - documentation: |- - Execute Quay Playwright e2e tests against a deployed Quay instance. diff --git a/ci-operator/step-registry/quay-tests/test-quay-playwright/OWNERS b/ci-operator/step-registry/quay/deploy-test-services/OWNERS similarity index 100% rename from ci-operator/step-registry/quay-tests/test-quay-playwright/OWNERS rename to ci-operator/step-registry/quay/deploy-test-services/OWNERS diff --git a/ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-commands.sh b/ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-commands.sh new file mode 100644 index 0000000000000..438083629957b --- /dev/null +++ b/ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-commands.sh @@ -0,0 +1,587 @@ +#!/bin/bash + +set -euo pipefail +set -x + +NAMESPACE="quay-enterprise" +ARTIFACT_DIR=${ARTIFACT_DIR:=/tmp/artifacts} +mkdir -p "${ARTIFACT_DIR}" + +oc create namespace "${NAMESPACE}" --dry-run=client -o yaml | oc apply -f - + +# --------------------------------------------------------------------------- +# Keycloak (OIDC provider) +# --------------------------------------------------------------------------- + +# Wildcard redirect URIs — Keycloak dev mode accepts any origin. +# The Quay route isn't known yet (deploy-quay-aws-s3 runs after this step). +cat > /tmp/quay-realm.json <<'REALM_EOF' +{ + "realm": "quay", + "enabled": true, + "sslRequired": "none", + "registrationAllowed": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "rememberMe": false, + "verifyEmail": false, + "loginTheme": "keycloak", + "accessTokenLifespan": 300, + "clients": [ + { + "clientId": "quay-ui", + "enabled": true, + "protocol": "openid-connect", + "publicClient": true, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": true, + "redirectUris": [ + "*" + ], + "webOrigins": ["+"], + "attributes": { + "pkce.code.challenge.method": "S256", + "post.logout.redirect.uris": "+" + } + } + ], + "users": [ + { + "username": "admin_oidc", + "enabled": true, + "emailVerified": true, + "email": "admin_oidc@example.com", + "firstName": "Admin", + "lastName": "OIDC", + "credentials": [ + { + "type": "password", + "value": "password", + "temporary": false + } + ] + }, + { + "username": "testuser_oidc", + "enabled": true, + "emailVerified": true, + "email": "testuser_oidc@example.com", + "firstName": "Test", + "lastName": "OIDC", + "credentials": [ + { + "type": "password", + "value": "password", + "temporary": false + } + ] + }, + { + "username": "readonly_oidc", + "enabled": true, + "emailVerified": true, + "email": "readonly_oidc@example.com", + "firstName": "Readonly", + "lastName": "OIDC", + "credentials": [ + { + "type": "password", + "value": "password", + "temporary": false + } + ] + } + ] +} +REALM_EOF + +oc -n "${NAMESPACE}" create configmap keycloak-realm \ + --from-file=quay-realm.json=/tmp/quay-realm.json \ + --dry-run=client -o yaml | oc apply -f - + +cat < /tmp/base.ldif <<'LDIF_EOF' +dn: dc=example,dc=org +objectClass: top +objectClass: domain +dc: example + +dn: ou=users,dc=example,dc=org +objectClass: organizationalUnit +ou: users + +dn: uid=admin,ou=users,dc=example,dc=org +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: admin +sn: Admin +givenName: Admin +cn: Admin User +displayName: Admin User +uidNumber: 10000 +gidNumber: 10000 +userPassword: password +loginShell: /bin/bash +homeDirectory: /home/admin +mail: admin@example.com + +dn: uid=user1,ou=users,dc=example,dc=org +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: user1 +sn: One +givenName: User +cn: User One +displayName: User One +uidNumber: 10001 +gidNumber: 10001 +userPassword: password +loginShell: /bin/bash +homeDirectory: /home/user1 +mail: user1@example.com + +dn: uid=quayadmin,ou=users,dc=example,dc=org +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: quayadmin +sn: Admin +givenName: Quay +cn: Quay Admin +displayName: Quay Admin +uidNumber: 10002 +gidNumber: 10002 +userPassword: password +loginShell: /bin/bash +homeDirectory: /home/quayadmin +mail: quayadmin@example.com + +dn: uid=readonly,ou=users,dc=example,dc=org +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: readonly +sn: Only +givenName: Read +cn: Read Only +displayName: Read Only +uidNumber: 10003 +gidNumber: 10003 +userPassword: password +loginShell: /bin/bash +homeDirectory: /home/readonly +mail: readonly@example.com + +dn: uid=testuser,ou=users,dc=example,dc=org +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: testuser +sn: User +givenName: Test +cn: Test User +displayName: Test User +uidNumber: 10004 +gidNumber: 10004 +userPassword: password +loginShell: /bin/bash +homeDirectory: /home/testuser +mail: testuser@example.com + +dn: uid=admin_ldap,ou=users,dc=example,dc=org +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +objectClass: quayUser +uid: admin_ldap +sn: Ldap +givenName: Admin +cn: Admin Ldap +displayName: Admin Ldap +uidNumber: 10005 +gidNumber: 10005 +userPassword: password +loginShell: /bin/bash +homeDirectory: /home/admin_ldap +mail: admin_ldap@example.com +quayMemberOf: cn=test_ldap_group,ou=groups,dc=example,dc=org + +dn: uid=testuser_ldap,ou=users,dc=example,dc=org +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +objectClass: quayUser +uid: testuser_ldap +sn: Ldap +givenName: Testuser +cn: Testuser Ldap +displayName: Testuser Ldap +uidNumber: 10006 +gidNumber: 10006 +userPassword: password +loginShell: /bin/bash +homeDirectory: /home/testuser_ldap +mail: testuser_ldap@example.com +quayMemberOf: cn=test_ldap_group,ou=groups,dc=example,dc=org + +dn: uid=readonly_ldap,ou=users,dc=example,dc=org +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: readonly_ldap +sn: Ldap +givenName: Readonly +cn: Readonly Ldap +displayName: Readonly Ldap +uidNumber: 10007 +gidNumber: 10007 +userPassword: password +loginShell: /bin/bash +homeDirectory: /home/readonly_ldap +mail: readonly_ldap@example.com + +dn: ou=groups,dc=example,dc=org +objectClass: organizationalUnit +ou: groups + +dn: cn=test_ldap_group,ou=groups,dc=example,dc=org +objectClass: groupOfUniqueNames +cn: test_ldap_group +uniqueMember: uid=testuser_ldap,ou=users,dc=example,dc=org +uniqueMember: uid=admin_ldap,ou=users,dc=example,dc=org +LDIF_EOF + +cat > /tmp/init-389ds.sh <<'INIT_EOF' +#!/bin/bash +set -e + +LDAPI_URI="ldapi://%2Fdata%2Frun%2Fslapd-localhost.socket" + +echo "Waiting for 389 DS to start..." +timeout=60 +while [ $timeout -gt 0 ]; do + if ldapsearch -x -H ldap://localhost:3389 -b "" -s base &>/dev/null; then + echo "389 DS is ready!" + break + fi + sleep 1 + timeout=$((timeout - 1)) +done + +if [ $timeout -eq 0 ]; then + echo "ERROR: 389 DS failed to start" + exit 1 +fi + +if dsconf localhost backend suffix list | grep -q "dc=example,dc=org"; then + echo "Backend already exists, skipping creation" +else + echo "Creating backend for dc=example,dc=org..." + dsconf localhost backend create --suffix "dc=example,dc=org" --be-name userroot +fi + +echo "Adding custom schema for Quay group membership queries..." +ldapmodify -H "$LDAPI_URI" -Y EXTERNAL << 'SCHEMA_EOF' +dn: cn=schema +changetype: modify +add: attributeTypes +attributeTypes: ( 1.3.6.1.4.1.99999.1 NAME 'quayMemberOf' DESC 'Quay group membership reference' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) +- +add: objectClasses +objectClasses: ( 1.3.6.1.4.1.99999.2 NAME 'quayUser' DESC 'Quay user auxiliary class' SUP top AUXILIARY MAY ( quayMemberOf ) ) +SCHEMA_EOF + +if ldapsearch -x -H ldap://localhost:3389 -D "cn=Directory Manager" -w "$DS_DM_PASSWORD" -b "ou=users,dc=example,dc=org" -s base &>/dev/null; then + echo "Base DN already populated, skipping LDIF import" +else + echo "Importing LDIF from /ldif-import/base.ldif..." + ldapadd -c -H "$LDAPI_URI" -Y EXTERNAL -f /ldif-import/base.ldif + echo "LDIF imported successfully!" +fi + +echo "389 DS initialization complete!" +INIT_EOF + +oc -n "${NAMESPACE}" create configmap ldap-config \ + --from-file=base.ldif=/tmp/base.ldif \ + --from-file=init-389ds.sh=/tmp/init-389ds.sh \ + --dry-run=client -o yaml | oc apply -f - + +cat </dev/null; then + echo "LDAP is initialized and accepting queries" + break + fi + if [[ $i -eq 60 ]]; then + echo "ERROR: LDAP failed to initialize within 60 attempts" >&2 + oc -n "${NAMESPACE}" logs "${LDAP_POD}" > "${ARTIFACT_DIR}/ldap-pod.log" 2>&1 || true + exit 1 + fi + sleep 5 +done + +# Save Keycloak route for the test step (poll until route is admitted) +KEYCLOAK_ROUTE="" +for i in $(seq 1 30); do + KEYCLOAK_ROUTE=$(oc -n "${NAMESPACE}" get route keycloak -o jsonpath='{.status.ingress[0].host}' 2>/dev/null || true) + [[ -n "${KEYCLOAK_ROUTE}" ]] && break + sleep 2 +done +if [[ -z "${KEYCLOAK_ROUTE}" ]]; then + echo "ERROR: Could not determine Keycloak route" >&2 + exit 1 +fi +echo "https://${KEYCLOAK_ROUTE}" > "${SHARED_DIR}/keycloak_route" +echo "Keycloak route: https://${KEYCLOAK_ROUTE}" + +# Save Mailpit route +MAILPIT_ROUTE="" +for i in $(seq 1 30); do + MAILPIT_ROUTE=$(oc -n "${NAMESPACE}" get route mailpit -o jsonpath='{.status.ingress[0].host}' 2>/dev/null || true) + [[ -n "${MAILPIT_ROUTE}" ]] && break + sleep 2 +done +if [[ -z "${MAILPIT_ROUTE}" ]]; then + echo "ERROR: Could not determine Mailpit route" >&2 + exit 1 +fi +echo "https://${MAILPIT_ROUTE}" > "${SHARED_DIR}/mailpit_route" +echo "Mailpit route: https://${MAILPIT_ROUTE}" + +echo "All test services deployed successfully" diff --git a/ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-ref.metadata.json b/ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-ref.metadata.json similarity index 54% rename from ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-ref.metadata.json rename to ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-ref.metadata.json index 72ccc29eded9c..55268e9795951 100644 --- a/ci-operator/step-registry/quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-ref.metadata.json +++ b/ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-ref.metadata.json @@ -1,5 +1,5 @@ { - "path": "quay-tests/test-quay-playwright/quay-tests-test-quay-playwright-ref.yaml", + "path": "quay/deploy-test-services/quay-deploy-test-services-ref.yaml", "owners": { "approvers": [ "quay-approvers" diff --git a/ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-ref.yaml b/ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-ref.yaml new file mode 100644 index 0000000000000..25ea7bb81e9e5 --- /dev/null +++ b/ci-operator/step-registry/quay/deploy-test-services/quay-deploy-test-services-ref.yaml @@ -0,0 +1,17 @@ +ref: + as: quay-deploy-test-services + cli: latest + from_image: + name: quay-test-console + namespace: ci + tag: latest + commands: quay-deploy-test-services-commands.sh + resources: + requests: + cpu: 10m + memory: 100Mi + timeout: 30m0s + documentation: |- + Deploy Keycloak (OIDC), 389 Directory Server (LDAP), and Mailpit (email) + into the quay-enterprise namespace for Playwright e2e tests. + Writes $SHARED_DIR/keycloak_route with the Keycloak Route URL. diff --git a/ci-operator/step-registry/quay/test-playwright/OWNERS b/ci-operator/step-registry/quay/test-playwright/OWNERS new file mode 120000 index 0000000000000..ec405d65a79df --- /dev/null +++ b/ci-operator/step-registry/quay/test-playwright/OWNERS @@ -0,0 +1 @@ +../OWNERS \ No newline at end of file diff --git a/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-commands.sh b/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-commands.sh new file mode 100644 index 0000000000000..5bb67b3ef99cd --- /dev/null +++ b/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-commands.sh @@ -0,0 +1,237 @@ +#!/bin/bash + +set -euo pipefail +set -x + +NAMESPACE="quay-enterprise" +ARTIFACT_DIR=${ARTIFACT_DIR:=/tmp/artifacts} +mkdir -p "${ARTIFACT_DIR}" + +QUAY_ROUTE=$(cat "${SHARED_DIR}/quayroute") +if [[ -z "${QUAY_ROUTE}" ]]; then + echo "ERROR: quayroute not found in SHARED_DIR" >&2 + exit 1 +fi +echo "Quay route: ${QUAY_ROUTE}" + +KEYCLOAK_ROUTE=$(cat "${SHARED_DIR}/keycloak_route") +if [[ -z "${KEYCLOAK_ROUTE}" ]]; then + echo "ERROR: keycloak_route not found in SHARED_DIR" >&2 + exit 1 +fi +echo "Keycloak route: ${KEYCLOAK_ROUTE}" + +# Disable tracing due to password handling +[[ $- == *x* ]] && WAS_TRACING=true || WAS_TRACING=false +set +x +QUAY_USERNAME=$(cat /var/run/quay-qe-quay-secret/username) +QUAY_PASSWORD=$(cat /var/run/quay-qe-quay-secret/password) +$WAS_TRACING && set -x + +export PLAYWRIGHT_BASE_URL="${QUAY_ROUTE}" +export REACT_QUAY_APP_API_URL="${QUAY_ROUTE}" +export QUAY_USERNAME +export QUAY_PASSWORD +export CI=true +export OPENSHIFT_CI=true +export NODE_TLS_REJECT_UNAUTHORIZED=0 + +# --------------------------------------------------------------------------- +# Clone quay/quay at the specified commit and install Playwright +# --------------------------------------------------------------------------- +echo "Cloning quay/quay at ${QUAY_SRC_COMMIT}..." +git init /tmp/quay +cd /tmp/quay +git fetch --depth 1 https://github.com/quay/quay.git "${QUAY_SRC_COMMIT}" +git checkout FETCH_HEAD + +cd /tmp/quay/web + +echo "Installing test dependencies..." +npm ci --ignore-scripts + +echo "Installing Chromium browser..." +npx playwright install chromium + +# Download pinned yq for YAML config merging +YQ_VERSION="v4.44.3" +YQ_ARCH="$(uname -m | sed 's/aarch64/arm64/;s/x86_64/amd64/')" +curl -sSfL "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_${YQ_ARCH}" -o /tmp/yq +chmod +x /tmp/yq + +# Mailpit API via Route (mailpit.ts reads MAILPIT_API_URL) +MAILPIT_ROUTE=$(cat "${SHARED_DIR}/mailpit_route") +export MAILPIT_API_URL="${MAILPIT_ROUTE}/api/v1" +echo "Mailpit API: ${MAILPIT_API_URL}" + +echo "Waiting for Mailpit to be reachable..." +curl -skf --retry 30 --retry-delay 1 --retry-all-errors \ + "${MAILPIT_API_URL}/messages" > /dev/null +echo "Mailpit is ready" + +function cleanup { + echo "Merging blob reports into combined HTML + JUnit..." + mkdir -p all-blob-reports + find blob-results -name '*.zip' -exec cp {} all-blob-reports/ \; 2>/dev/null || true + if [ "$(ls -A all-blob-reports 2>/dev/null)" ]; then + PLAYWRIGHT_JUNIT_OUTPUT_NAME="${ARTIFACT_DIR}/junit_playwright.xml" \ + npx playwright merge-reports --reporter=html,junit all-blob-reports || true + fi + + cp -r test-results/* "${ARTIFACT_DIR}/" 2>/dev/null || true + cp -r playwright-report/* "${ARTIFACT_DIR}/" 2>/dev/null || true +} +trap cleanup EXIT + +# --------------------------------------------------------------------------- +# Helper: swap Quay auth config and restart +# --------------------------------------------------------------------------- +swap_quay_config() { + local overlay_file="$1" + echo "Swapping Quay config with overlay: ${overlay_file}" + + oc -n "${NAMESPACE}" get secret config-bundle-secret \ + -o jsonpath='{.data.config\.yaml}' | base64 -d > /tmp/current-config.yaml + + /tmp/yq eval-all 'select(fileIndex == 0) *+ select(fileIndex == 1)' \ + /tmp/current-config.yaml "${overlay_file}" > /tmp/merged-config.yaml + + oc -n "${NAMESPACE}" create secret generic config-bundle-secret \ + --from-file=config.yaml=/tmp/merged-config.yaml \ + --dry-run=client -o yaml | oc apply -f - + + QUAY_DEPLOY=$(oc -n "${NAMESPACE}" get deployment -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep 'quay-app' | head -n1) + if [[ -z "${QUAY_DEPLOY}" ]]; then + echo "ERROR: Could not find quay-app deployment" >&2 + return 1 + fi + + oc -n "${NAMESPACE}" rollout restart "deployment/${QUAY_DEPLOY}" + oc -n "${NAMESPACE}" rollout status "deployment/${QUAY_DEPLOY}" --timeout=300s + + echo "Waiting for Quay to be healthy after config swap..." + curl -skf --retry 60 --retry-delay 5 --retry-all-errors \ + "${QUAY_ROUTE}/health/instance" > /dev/null + echo "Quay is healthy" +} + +OVERALL_RESULT=0 + +# --------------------------------------------------------------------------- +# Phase 1: Database auth tests +# --------------------------------------------------------------------------- +echo "=========================================" +echo "Phase 1: Database auth tests" +echo "=========================================" + +DB_GREP="--grep-invert @auth:OIDC|@auth:LDAP" +if [[ -n "${PLAYWRIGHT_GREP}" ]]; then + DB_GREP="${DB_GREP} --grep ${PLAYWRIGHT_GREP}" +fi + +PLAYWRIGHT_BLOB_OUTPUT_DIR=blob-results/db \ +npx playwright test \ + ${DB_GREP} \ + --workers=4 \ + --reporter=blob \ + 2>&1 | tee "${ARTIFACT_DIR}/playwright-db-output.log" || OVERALL_RESULT=1 + +# --------------------------------------------------------------------------- +# Phase 2: OIDC auth tests +# --------------------------------------------------------------------------- +echo "=========================================" +echo "Phase 2: OIDC auth tests" +echo "=========================================" + +cat > /tmp/oidc-overlay.yaml <&1 | tee "${ARTIFACT_DIR}/playwright-oidc-output.log" || OVERALL_RESULT=1 + +# --------------------------------------------------------------------------- +# Phase 3: LDAP auth tests +# --------------------------------------------------------------------------- +echo "=========================================" +echo "Phase 3: LDAP auth tests" +echo "=========================================" + +cat > /tmp/ldap-overlay.yaml <<'LDAP_EOF' +AUTHENTICATION_TYPE: LDAP +FEATURE_TEAM_SYNCING: true +FEATURE_NONSUPERUSER_TEAM_SYNCING_SETUP: true +STAGGER_WORKERS: false +LDAP_ADMIN_DN: cn=Directory Manager +LDAP_ADMIN_PASSWD: admin +LDAP_ALLOW_INSECURE_FALLBACK: true +LDAP_BASE_DN: + - dc=example + - dc=org +LDAP_EMAIL_ATTR: mail +LDAP_MEMBEROF_ATTR: quayMemberOf +LDAP_UID_ATTR: uid +LDAP_URI: ldap://ldap.quay-enterprise.svc:3389 +LDAP_USER_RDN: + - ou=users +SUPER_USERS: + - admin + - admin_ldap +GLOBAL_READONLY_SUPER_USERS: + - readonly + - readonly_ldap +LDAP_EOF + +swap_quay_config /tmp/ldap-overlay.yaml + +LDAP_GREP="--grep @auth:LDAP" +if [[ -n "${PLAYWRIGHT_GREP}" ]]; then + LDAP_GREP="${LDAP_GREP} --grep ${PLAYWRIGHT_GREP}" +fi + +PLAYWRIGHT_BLOB_OUTPUT_DIR=blob-results/ldap \ +npx playwright test \ + ${LDAP_GREP} \ + --workers=4 \ + --reporter=blob \ + 2>&1 | tee "${ARTIFACT_DIR}/playwright-ldap-output.log" || OVERALL_RESULT=1 + +# --------------------------------------------------------------------------- +# Final result +# --------------------------------------------------------------------------- +if [[ "${OVERALL_RESULT}" -ne 0 ]]; then + echo "One or more test phases failed" + exit 1 +fi +echo "All test phases passed" diff --git a/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-ref.metadata.json b/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-ref.metadata.json new file mode 100644 index 0000000000000..5887b242fbaa9 --- /dev/null +++ b/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-ref.metadata.json @@ -0,0 +1,11 @@ +{ + "path": "quay/test-playwright/quay-test-playwright-ref.yaml", + "owners": { + "approvers": [ + "quay-approvers" + ], + "reviewers": [ + "quay-approvers" + ] + } +} \ No newline at end of file diff --git a/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-ref.yaml b/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-ref.yaml new file mode 100644 index 0000000000000..ddaffc175a028 --- /dev/null +++ b/ci-operator/step-registry/quay/test-playwright/quay-test-playwright-ref.yaml @@ -0,0 +1,33 @@ +ref: + as: quay-test-playwright + cli: latest + from: playwright-base + commands: quay-test-playwright-commands.sh + resources: + requests: + cpu: "2" + memory: 4Gi + limits: + memory: 8Gi + timeout: 3h0m0s + grace_period: 15m0s + credentials: + - namespace: test-credentials + name: quay-qe-quay-secret + mount_path: /var/run/quay-qe-quay-secret + documentation: |- + Clone quay/quay at QUAY_SRC_COMMIT, install Playwright, and run + e2e tests in three phases: Database auth, OIDC auth (via Keycloak), + and LDAP auth (via 389ds). Requires quay-deploy-test-services to + have run first. + env: + - name: QUAY_SRC_COMMIT + documentation: |- + The quay/quay commit SHA to clone tests from. For Konflux + triggers this is the source commit the image was built from. + default: "master" + - name: PLAYWRIGHT_GREP + documentation: |- + Playwright --grep filter passed to each phase. Overrides the + default phase filtering when set. + default: ""