diff --git a/.ci/images/Dockerfile b/.ci/images/Dockerfile index 4f6bdd8d65..07c505f35f 100644 --- a/.ci/images/Dockerfile +++ b/.ci/images/Dockerfile @@ -1,6 +1,10 @@ -# Base image from Microsoft Playwright -# Playwright v1.59.1-noble includes Node 24.14.1 -FROM mcr.microsoft.com/playwright:v1.61.1-noble@sha256:824f1a789072e648c62541c2cfa4479c4061a290d5c27766d67dc1dcbc19b321 +# Base image from Microsoft Playwright (multi-arch: amd64 + arm64) +# Playwright v1.61.1-noble includes Node 24.14.1 +# Pinned to manifest list digest to support both platforms +FROM mcr.microsoft.com/playwright:v1.61.1-noble@sha256:5b8f294aff9041b7191c34a4bab3ac270157a28774d4b0660e9743297b697e48 + +# Automatically set by buildx/podman based on --platform (amd64 or arm64) +ARG TARGETARCH # Set environment variables for the container ENV CI=1 \ @@ -9,12 +13,9 @@ ENV CI=1 \ _MITSHM=0 \ NODE_PATH=/usr/local/lib/node_modules \ HELM_VERSION="v3.17.2" \ - OC_VERSION="4.19.17" \ + OC_VERSION="4.22.3" \ OCM_VERSION="0.1.76" \ - GO_VERSION="1.19" \ - GO_SHA256="464b6b66591f6cf055bc5df90a9750bf5fbc9d038722bb84a9d56a2bea974be6" \ - GOPATH="/go" \ - PATH="$GOPATH/bin:/usr/local/go/bin:$PATH" + UMOCI_VERSION="v0.6.0" # Copy Yarn configuration files to correctly use the project-defined Yarn version COPY .yarn /root/.yarn @@ -52,15 +53,22 @@ RUN echo "whoami: $(whoami)" && \ # Check where Node.js loads required modules node -p 'module.paths' -# Install Helm, OpenShift CLI, ocm-cli, and yq -RUN curl -fsSL -o /tmp/helm.tar.gz "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz" && \ - tar -xzvf /tmp/helm.tar.gz -C /tmp && mv /tmp/linux-amd64/helm /usr/local/bin/helm && \ - curl -fsSL -o /tmp/openshift-client-linux.tar.gz "https://mirror.openshift.com/pub/openshift-v4/clients/ocp/${OC_VERSION}/openshift-client-linux-${OC_VERSION}.tar.gz" && \ +# Install Helm, OpenShift CLI, oc-mirror, ocm-cli, and yq +# OC client: amd64 has no arch suffix (default), arm64 needs "-arm64". +# oc-mirror: arm64 lives under "openshift-v4/aarch64/" mirror tree. +RUN OC_ARCH_SUFFIX=$([ "${TARGETARCH}" = "arm64" ] && echo "-arm64" || echo "") && \ + OC_MIRROR_TREE=$([ "${TARGETARCH}" = "arm64" ] && echo "openshift-v4/aarch64" || echo "openshift-v4") && \ + curl -fsSL -o /tmp/helm.tar.gz "https://get.helm.sh/helm-${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" && \ + tar -xzvf /tmp/helm.tar.gz -C /tmp && mv /tmp/linux-${TARGETARCH}/helm /usr/local/bin/helm && \ + curl -fsSL -o /tmp/openshift-client-linux.tar.gz "https://mirror.openshift.com/pub/openshift-v4/clients/ocp/${OC_VERSION}/openshift-client-linux${OC_ARCH_SUFFIX}-${OC_VERSION}.tar.gz" && \ tar -xzvf /tmp/openshift-client-linux.tar.gz -C /usr/local/bin oc kubectl && \ - curl -Lo /usr/local/bin/ocm "https://github.com/openshift-online/ocm-cli/releases/download/v${OCM_VERSION}/ocm-linux-amd64" && \ + curl -fsSL -o /tmp/oc-mirror.tar.gz "https://mirror.openshift.com/pub/${OC_MIRROR_TREE}/clients/ocp/${OC_VERSION}/oc-mirror.tar.gz" && \ + tar -xzvf /tmp/oc-mirror.tar.gz -C /usr/local/bin oc-mirror && \ + chmod +x /usr/local/bin/oc-mirror && \ + curl -Lo /usr/local/bin/ocm "https://github.com/openshift-online/ocm-cli/releases/download/v${OCM_VERSION}/ocm-linux-${TARGETARCH}" && \ chmod +x /usr/local/bin/ocm && \ - curl -fsSL "https://github.com/mikefarah/yq/releases/download/v4.43.1/yq_linux_amd64.tar.gz" | tar -xz && \ - mv yq_linux_amd64 /usr/local/bin/yq && \ + curl -fsSL "https://github.com/mikefarah/yq/releases/download/v4.43.1/yq_linux_${TARGETARCH}.tar.gz" | tar -xz && \ + mv yq_linux_${TARGETARCH} /usr/local/bin/yq && \ rm -rf /tmp/* /var/tmp/* # Install Azure CLI @@ -74,29 +82,31 @@ RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages. apt-get install google-cloud-cli google-cloud-sdk-gke-gcloud-auth-plugin -y && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Install AWS CLI -RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \ +# Install AWS CLI (uname -m returns x86_64 on amd64, aarch64 on arm64 — matching AWS URL naming) +RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m).zip" -o "awscliv2.zip" && \ unzip awscliv2.zip && \ - ./aws/install + ./aws/install && \ + rm -rf awscliv2.zip aws/ -# Install skopeo +# Install skopeo and podman RUN apt-get update -y && \ - apt-get install -y skopeo + apt-get install -y --no-install-recommends skopeo podman && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # Install PostgreSQL CLI (psql only) RUN apt-get update && \ apt-get install -y --no-install-recommends postgresql-client && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Install umoci -RUN curl -LO "https://github.com/opencontainers/umoci/releases/download/v0.4.7/umoci.amd64" && \ - chmod +x umoci.amd64 && \ - mv umoci.amd64 /usr/local/bin/umoci +# Install umoci (v0.6.0+ provides multi-arch binaries) +RUN curl -LO "https://github.com/opencontainers/umoci/releases/download/${UMOCI_VERSION}/umoci.linux.${TARGETARCH}" && \ + chmod +x "umoci.linux.${TARGETARCH}" && \ + mv "umoci.linux.${TARGETARCH}" /usr/local/bin/umoci # Install opm -RUN curl -LO "https://github.com/operator-framework/operator-registry/releases/download/v1.47.0/linux-amd64-opm" && \ - chmod +x linux-amd64-opm && \ - mv linux-amd64-opm /usr/local/bin/opm +RUN curl -LO "https://github.com/operator-framework/operator-registry/releases/download/v1.47.0/linux-${TARGETARCH}-opm" && \ + chmod +x "linux-${TARGETARCH}-opm" && \ + mv "linux-${TARGETARCH}-opm" /usr/local/bin/opm # Install Operator SDK CLI (required to install OLM on K8s clusters) RUN export ARCH=$(case $(uname -m) in x86_64) echo -n amd64 ;; aarch64) echo -n arm64 ;; *) echo -n $(uname -m) ;; esac) && \ diff --git a/.github/workflows/push-e2e-runner.yaml b/.github/workflows/push-e2e-runner.yaml index 5c86ec2614..2d249926fe 100644 --- a/.github/workflows/push-e2e-runner.yaml +++ b/.github/workflows/push-e2e-runner.yaml @@ -1,10 +1,14 @@ name: Build & Push e2e-runner Image to Quay.io on: + pull_request: + paths: + - '.ci/images/Dockerfile' + - '.yarnrc.yml' push: branches: - main - - 'release-1.*' + - 'release-*' paths: - '.ci/images/Dockerfile' - '.yarnrc.yml' @@ -16,7 +20,7 @@ on: default: 'NONE' concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.number || github.ref }} cancel-in-progress: true env: @@ -25,12 +29,17 @@ env: jobs: build-image: - name: Build & Push e2e-runner Image + name: Build e2e-runner (${{ matrix.os }}) + env: + HAS_QUAY_AUTH: ${{ secrets.QUAY_USERNAME != '' && secrets.QUAY_TOKEN != '' }} strategy: fail-fast: false matrix: - os: [ubuntu-24.04] + os: + - ubuntu-24.04 + - ubuntu-24.04-arm runs-on: ${{ matrix.os }} + timeout-minutes: 120 permissions: contents: read packages: write @@ -44,14 +53,21 @@ jobs: - name: Prepare Environment Variables env: INPUT_BRANCH: ${{ inputs.branch }} + PR_NUMBER: ${{ github.event.number }} run: | - echo "PLATFORM=linux/amd64" >> $GITHUB_ENV + # Detect platform from runner's native architecture + arch=$(dpkg --print-architecture) + echo "PLATFORM=linux/${arch}" >> $GITHUB_ENV + echo "PLATFORM_PAIR=linux-${arch}" >> $GITHUB_ENV + echo "PLATFORM_ARCH=${arch}" >> $GITHUB_ENV - # create image tag from the correct branch (either from a push or a workflow_dispatch trigger) + # Create image tag from the correct branch (either from a push or a workflow_dispatch trigger) if [[ "$INPUT_BRANCH" ]] && [[ "$INPUT_BRANCH" != "NONE" ]]; then echo "Switch to $INPUT_BRANCH" git checkout "$INPUT_BRANCH" IMAGE_TAG="$INPUT_BRANCH" + elif [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + IMAGE_TAG="pr-${PR_NUMBER}" else echo "Use current branch $GITHUB_REF" IMAGE_TAG=$(git rev-parse --abbrev-ref HEAD) @@ -59,6 +75,14 @@ jobs: echo "Use IMAGE_TAG = $IMAGE_TAG" echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV + - name: Check Quay credentials + if: github.event_name != 'pull_request' + run: | + if [ "$HAS_QUAY_AUTH" != "true" ]; then + echo "::error::Missing QUAY_USERNAME or QUAY_TOKEN secrets" + exit 1 + fi + - name: Get the last commit short SHA uses: ./.github/actions/get-sha @@ -66,6 +90,7 @@ jobs: uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Login to Quay + if: github.event_name != 'pull_request' uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ${{ env.REGISTRY }} @@ -73,12 +98,96 @@ jobs: password: ${{ secrets.QUAY_TOKEN }} - name: Build and Push Image + id: build-push uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 with: context: . file: .ci/images/Dockerfile - push: true + push: ${{ github.event_name != 'pull_request' }} tags: | - ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ env.IMAGE_TAG }} - ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ env.IMAGE_TAG }}-${{ env.SHORT_SHA }} + ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ env.IMAGE_TAG }}-${{ env.PLATFORM_ARCH }} + ${{ env.REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ env.IMAGE_TAG }}-${{ env.SHORT_SHA }}-${{ env.PLATFORM_ARCH }} + labels: | + quay.expires-after=1w platforms: ${{ env.PLATFORM }} + + - name: Export digest + if: github.event_name != 'pull_request' + env: + DIGEST: ${{ steps.build-push.outputs.digest }} + run: | + mkdir -p /tmp/digests + digest="$DIGEST" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: Create multi-arch manifest + if: github.event_name != 'pull_request' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + needs: build-image + permissions: + contents: read + packages: write + + steps: + - name: Checkout Repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + fetch-depth: 0 + + - name: Prepare Environment Variables + env: + INPUT_BRANCH: ${{ inputs.branch }} + run: | + if [[ "$INPUT_BRANCH" ]] && [[ "$INPUT_BRANCH" != "NONE" ]]; then + git checkout "$INPUT_BRANCH" + IMAGE_TAG="$INPUT_BRANCH" + else + IMAGE_TAG=$(git rev-parse --abbrev-ref HEAD) + fi + echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV + + - name: Get the last commit short SHA + uses: ./.github/actions/get-sha + + - name: Download digests + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - name: Login to Quay + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.QUAY_USERNAME }} + password: ${{ secrets.QUAY_TOKEN }} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + for tag in \ + "${REGISTRY}/${REGISTRY_IMAGE}:${IMAGE_TAG}" \ + "${REGISTRY}/${REGISTRY_IMAGE}:${IMAGE_TAG}-${SHORT_SHA}"; do + echo "Creating multi-arch manifest for: $tag" + docker buildx imagetools create -t "$tag" \ + $(printf "${REGISTRY}/${REGISTRY_IMAGE}@sha256:%s " *) + done + + - name: Inspect image + run: | + docker buildx imagetools inspect "${REGISTRY}/${REGISTRY_IMAGE}:${IMAGE_TAG}" diff --git a/e2e-tests/container-init.sh b/e2e-tests/container-init.sh index fd2900637d..7b208b2b52 100644 --- a/e2e-tests/container-init.sh +++ b/e2e-tests/container-init.sh @@ -23,7 +23,8 @@ set -e if ! command -v vault &> /dev/null; then VAULT_VERSION="${VAULT_VERSION:-1.15.4}" log::info "Installing vault ${VAULT_VERSION}..." - curl -fsSL "https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip" -o /tmp/vault.zip + VAULT_ARCH=$(dpkg --print-architecture) + curl -fsSL "https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_${VAULT_ARCH}.zip" -o /tmp/vault.zip unzip -q /tmp/vault.zip -d /usr/local/bin/ rm /tmp/vault.zip fi diff --git a/e2e-tests/local-run.sh b/e2e-tests/local-run.sh index 5f301bf1ed..674dbddd79 100755 --- a/e2e-tests/local-run.sh +++ b/e2e-tests/local-run.sh @@ -2,7 +2,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -RUNNER_IMAGE="quay.io/rhdh-community/rhdh-e2e-runner:main" +RUNNER_IMAGE="${RUNNER_IMAGE:-quay.io/rhdh-community/rhdh-e2e-runner:main}" RUN_CONFIG_FILE="$SCRIPT_DIR/.local-test/run-config.env" # Source logging library @@ -23,6 +23,8 @@ Options: -r, --repo IMAGE_REPO Image repository (e.g., rhdh/rhdh-hub-rhel9) -t, --tag TAG_NAME Image tag (e.g., next, latest, 1.5) -p, --pr PR_NUMBER PR number (sets repo to rhdh-community/rhdh, tag to pr-) + -i, --runner-image IMG Override the e2e runner container image + (default: quay.io/rhdh-community/rhdh-e2e-runner:main) -s, --skip-tests Deploy only, skip running tests -h, --help Show this help message @@ -45,6 +47,9 @@ Examples: # Run on GKE ./local-run.sh -j periodic-ci-gke-helm-nightly -r rhdh/rhdh-hub-rhel9 -t next -s + # Use a locally built runner image + ./local-run.sh --runner-image localhost/rhdh-e2e-runner:test + EOF exit 0 } @@ -73,6 +78,10 @@ while [[ $# -gt 0 ]]; do CLI_TAG_NAME="pr-$2" shift 2 ;; + -i | --runner-image) + RUNNER_IMAGE="$2" + shift 2 + ;; -s | --skip-tests) CLI_SKIP_TESTS="true" shift @@ -357,9 +366,16 @@ if [[ "$CLI_MODE" == "false" ]]; then echo "" fi -# Pull runner image first (can take a while) +# Pull runner image (always attempt; fall back to local copy if pull fails) log::section "Pulling runner container image" -podman pull "$RUNNER_IMAGE" --platform=linux/amd64 +if ! podman pull "$RUNNER_IMAGE"; then + if podman image exists "$RUNNER_IMAGE" 2>/dev/null; then + log::info "Pull failed but image exists locally: $RUNNER_IMAGE" + else + log::error "Failed to pull image and no local copy: $RUNNER_IMAGE" + exit 1 + fi +fi export VAULT_ADDR='https://vault.ci.openshift.org' @@ -434,9 +450,10 @@ log::info "Container log: $CONTAINER_LOG" echo "" CONTAINER_EXIT_CODE=0 +# no -t: stdout is piped to tee and CI has no TTY podman run -v "$WORK_DIR":/tmp/rhdh \ -v "$SCRIPT_DIR/container-init.sh":/tmp/container-init.sh:ro \ - -it -u root --privileged \ + -i -u root --privileged \ --mount type=tmpfs,destination=/tmp/secrets \ -e VAULT_ADDR="$VAULT_ADDR" \ -e VAULT_TOKEN="$VAULT_TOKEN" \