From 0090d66b7352dfd97e1c13bc3ab6480282ca3b1b Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 7 May 2026 17:24:04 -0600 Subject: [PATCH 1/2] feat(ci): add GitHub-hosted parallel integration test jobs Adds -gh variants of all integration test jobs (integration-test-gh, drupal-plain-gh, drupal-issue-fork-gh, contrib-plain-gh, contrib-issue-fork-gh) running on ubuntu-latest alongside the existing self-hosted sysbox jobs. New jobs use coder/setup-action@v1 and omit the local host-directory check (requires server-side ci-reader setup, added in a follow-up). Workspace and template version names use gd-/gc-/ gh- prefixes to avoid collisions with the self-hosted jobs running in the same workflow run. Also documents the upstream/main rule in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 --- .../drupal-contrib-integration-test.yml | 285 ++++++++++++++++++ .github/workflows/drupal-integration-test.yml | 204 +++++++++++++ .github/workflows/integration-test.yml | 201 ++++++++++++ CLAUDE.md | 1 + 4 files changed, 691 insertions(+) diff --git a/.github/workflows/drupal-contrib-integration-test.yml b/.github/workflows/drupal-contrib-integration-test.yml index a696585..2e8a089 100644 --- a/.github/workflows/drupal-contrib-integration-test.yml +++ b/.github/workflows/drupal-contrib-integration-test.yml @@ -379,3 +379,288 @@ jobs: - name: Archive CI template version if: always() run: coder templates versions archive drupal-contrib cc-${{ github.run_number }}-${{ github.run_attempt }} --yes || true + + contrib-plain-gh: + name: Contrib ${{ matrix.project }} D${{ matrix.drupal_version }} (plain, GH) + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - project: token + drupal_version: "11" + - project: pathauto + drupal_version: "11" + fail-fast: false + defaults: + run: + shell: bash -euo pipefail {0} + env: + WORKSPACE_NAME: gc-${{ matrix.project }}-d${{ matrix.drupal_version }}-${{ github.run_number }}-${{ github.run_attempt }} + CI: "true" + DDEV_NONINTERACTIVE: "true" + NO_COLOR: "1" + + steps: + - uses: actions/checkout@v6 + + - name: Load 1Password secrets + uses: 1password/load-secrets-action@v4 + with: + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + TEST_CODER_SESSION_TOKEN: "op://test-secrets/TEST_CODER_SESSION_TOKEN/credential" + + - uses: coder/setup-action@v1 + with: + access_url: ${{ vars.TEST_CODER_URL }} + coder_session_token: ${{ env.TEST_CODER_SESSION_TOKEN }} + + - name: Copy VERSION into template directory + run: cp VERSION drupal-contrib/VERSION + + - name: Push template (inactive) + run: | + coder templates push drupal-contrib \ + --directory drupal-contrib \ + --activate=false \ + --name ${{ env.WORKSPACE_NAME }} \ + --yes \ + --variable workspace_image_registry=index.docker.io/ddev/coder-ddev + + - name: Create workspace + run: | + coder create ${{ env.WORKSPACE_NAME }} \ + --template drupal-contrib \ + --template-version ${{ env.WORKSPACE_NAME }} \ + --parameter "vscode_extensions=[]" \ + --parameter project_name=${{ matrix.project }} \ + --parameter project_type=module \ + --parameter drupal_version=${{ matrix.drupal_version }} \ + --parameter issue_fork= \ + --parameter issue_branch= \ + --parameter install_profile=minimal \ + --parameter share_drupal_site=owner \ + --yes + + - name: Verify workspace — agent connected + run: coder ssh ${{ env.WORKSPACE_NAME }} --wait=yes -- echo "Agent connected" + + - name: Verify workspace — Docker daemon running + run: coder ssh ${{ env.WORKSPACE_NAME }} -- docker ps + + - name: Verify workspace — DDEV installed + run: coder ssh ${{ env.WORKSPACE_NAME }} -- ddev --version + + - name: Verify workspace — project repo cloned + run: coder ssh ${{ env.WORKSPACE_NAME }} -- test -d /home/coder/${{ matrix.project }}/.git + + - name: Verify workspace — Drupal web root exists + run: coder ssh ${{ env.WORKSPACE_NAME }} -- test -f /home/coder/${{ matrix.project }}/web/index.php + + - name: Verify workspace — Drush DB connected + run: | + coder ssh ${{ env.WORKSPACE_NAME }} -- env -C /home/coder/${{ matrix.project }} ddev drush status --fields=db-status \ + | grep -i connected + + - name: Dump setup status + if: always() + run: | + coder ssh ${{ env.WORKSPACE_NAME }} -- cat /home/coder/SETUP_STATUS.txt || true + echo "--- last 30 lines of setup log ---" + coder ssh ${{ env.WORKSPACE_NAME }} -- tail -30 /tmp/drupal-setup.log || true + + - name: Verify workspace — module enabled + run: | + MODULES=$(coder ssh ${{ env.WORKSPACE_NAME }} -- env -C /home/coder/${{ matrix.project }} ddev drush pm:list --status=enabled --format=list) + echo "--- enabled modules ---" + echo "$MODULES" + echo "--- end ---" + echo "$MODULES" | grep -iw "${{ matrix.project }}" + + - name: Record owner + run: | + OWNER=$(coder whoami --output json | jq -r 'if type == "array" then .[0].username else .username end') + echo "OWNER=$OWNER" >> "$GITHUB_ENV" + + - name: Verify workspace — Drupal site externally accessible + run: | + CODER_DOMAIN="${{ vars.TEST_CODER_URL }}" + CODER_DOMAIN="${CODER_DOMAIN#https://}" + SITE_URL="https://drupal-site--${{ env.WORKSPACE_NAME }}--${OWNER}.${CODER_DOMAIN}" + echo "Checking $SITE_URL" + HTTP_STATUS=$(curl -sL --max-time 30 --retry 3 --retry-delay 5 \ + -H "Coder-Session-Token: ${TEST_CODER_SESSION_TOKEN}" \ + -o /tmp/drupal-response.html \ + -w "%{http_code}" \ + "$SITE_URL") + echo "HTTP status: $HTTP_STATUS" + if [[ ! "$HTTP_STATUS" =~ ^[23] ]]; then + echo "ERROR: unexpected HTTP status $HTTP_STATUS" >&2 + head -50 /tmp/drupal-response.html >&2 || true + exit 1 + fi + if ! grep -qi "log in" /tmp/drupal-response.html; then + echo "ERROR: response does not contain 'log in' — Drupal may not have started" >&2 + head -50 /tmp/drupal-response.html >&2 + exit 1 + fi + echo "OK: Drupal site is accessible and shows login page" + + - name: Delete workspace + if: always() + run: coder delete ${{ env.WORKSPACE_NAME }} --yes || true + + - name: Archive CI template version + if: always() + run: coder templates versions archive drupal-contrib ${{ env.WORKSPACE_NAME }} --yes || true + + contrib-issue-fork-gh: + name: Contrib ${{ vars.CONTRIB_TEST_PROJECT || 'token' }} issue fork (GH) + if: ${{ vars.CONTRIB_TEST_ISSUE_FORK != '' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner) }} + runs-on: ubuntu-latest + defaults: + run: + shell: bash -euo pipefail {0} + env: + WORKSPACE_NAME: gc-fork-${{ github.run_number }}-${{ github.run_attempt }} + CI: "true" + DDEV_NONINTERACTIVE: "true" + NO_COLOR: "1" + CONTRIB_PROJECT: ${{ vars.CONTRIB_TEST_PROJECT || 'token' }} + ISSUE_FORK: ${{ vars.CONTRIB_TEST_ISSUE_FORK }} + CONTRIB_TEST_DRUPAL_VERSION: ${{ vars.CONTRIB_TEST_DRUPAL_VERSION || '11' }} + ISSUE_BRANCH: "" + ISSUE_NUMBER: "" + ISSUE_FORK_VERSION: "" + + steps: + - uses: actions/checkout@v6 + + - name: Load 1Password secrets + uses: 1password/load-secrets-action@v4 + with: + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + TEST_CODER_SESSION_TOKEN: "op://test-secrets/TEST_CODER_SESSION_TOKEN/credential" + + - uses: coder/setup-action@v1 + with: + access_url: ${{ vars.TEST_CODER_URL }} + coder_session_token: ${{ env.TEST_CODER_SESSION_TOKEN }} + + - name: Resolve issue branch from issue number + run: | + ISSUE_NUMBER="${ISSUE_FORK#${CONTRIB_PROJECT}-}" + FORK_SLUG="${CONTRIB_PROJECT}-${ISSUE_NUMBER}" + ISSUE_BRANCH=$(curl -sf \ + "https://git.drupalcode.org/api/v4/projects/issue%2F${FORK_SLUG}" \ + | jq -r '.default_branch') + if [[ -z "$ISSUE_BRANCH" || "$ISSUE_BRANCH" == "null" ]]; then + echo "ERROR: Could not resolve default branch for issue fork ${FORK_SLUG}" >&2 + exit 1 + fi + DRUPAL_VERSION="${CONTRIB_TEST_DRUPAL_VERSION:-11}" + echo "Resolved: fork=${FORK_SLUG} branch=$ISSUE_BRANCH drupal_version=$DRUPAL_VERSION" + echo "ISSUE_NUMBER=$ISSUE_NUMBER" >> "$GITHUB_ENV" + echo "ISSUE_BRANCH=$ISSUE_BRANCH" >> "$GITHUB_ENV" + echo "ISSUE_FORK_VERSION=$DRUPAL_VERSION" >> "$GITHUB_ENV" + + - name: Copy VERSION into template directory + run: cp VERSION drupal-contrib/VERSION + + - name: Push template (inactive) + run: | + coder templates push drupal-contrib \ + --directory drupal-contrib \ + --activate=false \ + --name gc-${{ github.run_number }}-${{ github.run_attempt }} \ + --yes \ + --variable workspace_image_registry=index.docker.io/ddev/coder-ddev + + - name: Create workspace + run: | + coder create ${{ env.WORKSPACE_NAME }} \ + --template drupal-contrib \ + --template-version gc-${{ github.run_number }}-${{ github.run_attempt }} \ + --parameter "vscode_extensions=[]" \ + --parameter project_name=${{ env.CONTRIB_PROJECT }} \ + --parameter project_type=module \ + --parameter drupal_version=${{ env.ISSUE_FORK_VERSION }} \ + --parameter issue_fork=${{ env.ISSUE_NUMBER }} \ + --parameter issue_branch=${{ env.ISSUE_BRANCH }} \ + --parameter install_profile=minimal \ + --parameter share_drupal_site=owner \ + --yes + + - name: Verify workspace — agent connected + run: coder ssh ${{ env.WORKSPACE_NAME }} --wait=yes -- echo "Agent connected" + + - name: Verify workspace — Docker daemon running + run: coder ssh ${{ env.WORKSPACE_NAME }} -- docker ps + + - name: Verify workspace — DDEV installed + run: coder ssh ${{ env.WORKSPACE_NAME }} -- ddev --version + + - name: Verify workspace — project repo cloned + run: coder ssh ${{ env.WORKSPACE_NAME }} -- test -d /home/coder/${{ env.CONTRIB_PROJECT }}/.git + + - name: Verify workspace — Drupal web root exists + run: coder ssh ${{ env.WORKSPACE_NAME }} -- test -f /home/coder/${{ env.CONTRIB_PROJECT }}/web/index.php + + - name: Verify workspace — Drush DB connected + run: | + coder ssh ${{ env.WORKSPACE_NAME }} -- env -C /home/coder/${{ env.CONTRIB_PROJECT }} ddev drush status --fields=db-status \ + | grep -i connected + + - name: Dump setup status + if: always() + run: | + coder ssh ${{ env.WORKSPACE_NAME }} -- cat /home/coder/SETUP_STATUS.txt || true + echo "--- last 30 lines of setup log ---" + coder ssh ${{ env.WORKSPACE_NAME }} -- tail -30 /tmp/drupal-setup.log || true + + - name: Verify workspace — issue branch checked out + run: | + CURRENT=$(coder ssh ${{ env.WORKSPACE_NAME }} -- git -C /home/coder/${{ env.CONTRIB_PROJECT }} branch --show-current | tr -d '\r\n') + echo "Current branch: $CURRENT Expected: ${{ env.ISSUE_BRANCH }}" + [[ "$CURRENT" == "${{ env.ISSUE_BRANCH }}" ]] || { echo "ERROR: wrong branch" >&2; exit 1; } + + - name: Record owner + run: | + OWNER=$(coder whoami --output json | jq -r 'if type == "array" then .[0].username else .username end') + echo "OWNER=$OWNER" >> "$GITHUB_ENV" + + - name: Verify workspace — Drupal site externally accessible + run: | + CODER_DOMAIN="${{ vars.TEST_CODER_URL }}" + CODER_DOMAIN="${CODER_DOMAIN#https://}" + SITE_URL="https://drupal-site--${{ env.WORKSPACE_NAME }}--${OWNER}.${CODER_DOMAIN}" + echo "Checking $SITE_URL" + HTTP_STATUS=$(curl -sL --max-time 30 --retry 3 --retry-delay 5 \ + -H "Coder-Session-Token: ${TEST_CODER_SESSION_TOKEN}" \ + -o /tmp/drupal-response.html \ + -w "%{http_code}" \ + "$SITE_URL") + echo "HTTP status: $HTTP_STATUS" + if [[ ! "$HTTP_STATUS" =~ ^[23] ]]; then + echo "ERROR: unexpected HTTP status $HTTP_STATUS" >&2 + head -50 /tmp/drupal-response.html >&2 || true + exit 1 + fi + if ! grep -qi "log in" /tmp/drupal-response.html; then + echo "ERROR: response does not contain 'log in' — Drupal may not have started" >&2 + head -50 /tmp/drupal-response.html >&2 + exit 1 + fi + echo "OK: Drupal site is accessible and shows login page" + + - name: Delete workspace + if: always() + run: coder delete ${{ env.WORKSPACE_NAME }} --yes || true + + - name: Archive CI template version + if: always() + run: coder templates versions archive drupal-contrib gc-${{ github.run_number }}-${{ github.run_attempt }} --yes || true diff --git a/.github/workflows/drupal-integration-test.yml b/.github/workflows/drupal-integration-test.yml index 9e469ba..f9a53d3 100644 --- a/.github/workflows/drupal-integration-test.yml +++ b/.github/workflows/drupal-integration-test.yml @@ -193,6 +193,78 @@ jobs: if: always() run: coder templates versions archive drupal-core ${{ env.WORKSPACE_NAME }} --yes || true + drupal-plain-gh: + name: Drupal ${{ matrix.drupal_version }} (plain, GH) + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }} + runs-on: ubuntu-latest + strategy: + matrix: + drupal_version: ["12", "11"] + fail-fast: false + defaults: + run: + shell: bash -euo pipefail {0} + env: + WORKSPACE_NAME: gd-${{ matrix.drupal_version }}-${{ github.run_number }}-${{ github.run_attempt }} + CI: "true" + DDEV_NONINTERACTIVE: "true" + NO_COLOR: "1" + + steps: + - uses: actions/checkout@v6 + + - name: Load 1Password secrets + uses: 1password/load-secrets-action@v4 + with: + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + TEST_CODER_SESSION_TOKEN: "op://test-secrets/TEST_CODER_SESSION_TOKEN/credential" + + - uses: coder/setup-action@v1 + with: + access_url: ${{ vars.TEST_CODER_URL }} + coder_session_token: ${{ env.TEST_CODER_SESSION_TOKEN }} + + - name: Copy VERSION into template directory + run: cp VERSION drupal-core/VERSION + + - name: Push template (inactive) + run: | + coder templates push drupal-core \ + --directory drupal-core \ + --activate=false \ + --name ${{ env.WORKSPACE_NAME }} \ + --yes \ + --variable workspace_image_registry=index.docker.io/ddev/coder-ddev \ + --variable cache_path=/tmp/ci-no-cache + + - name: Create workspace + run: | + coder create ${{ env.WORKSPACE_NAME }} \ + --template drupal-core \ + --template-version ${{ env.WORKSPACE_NAME }} \ + --parameter "vscode_extensions=[]" \ + --parameter drupal_version=${{ matrix.drupal_version }} \ + --parameter issue_fork= \ + --parameter issue_branch= \ + --parameter install_profile=minimal \ + --parameter share_drupal_site=owner \ + --yes + + - *verify-agent + - *verify-docker + - *verify-ddev + - *verify-webroot + - *verify-drush + - *record-host-dir + - *verify-http + - *delete-workspace + + - name: Archive CI template version + if: always() + run: coder templates versions archive drupal-core ${{ env.WORKSPACE_NAME }} --yes || true + drupal-issue-fork: name: Drupal issue fork ${{ vars.DRUPAL_TEST_ISSUE_FORK || '3585397' }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }} @@ -353,3 +425,135 @@ jobs: - name: Archive CI template version if: always() run: coder templates versions archive drupal-core cd-${{ github.run_number }}-${{ github.run_attempt }} --yes || true + + drupal-issue-fork-gh: + name: Drupal issue fork ${{ vars.DRUPAL_TEST_ISSUE_FORK || '3585397' }} (GH) + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }} + runs-on: ubuntu-latest + defaults: + run: + shell: bash -euo pipefail {0} + env: + WORKSPACE_NAME: gd-fork-${{ github.run_number }}-${{ github.run_attempt }} + CI: "true" + DDEV_NONINTERACTIVE: "true" + NO_COLOR: "1" + ISSUE_FORK: ${{ vars.DRUPAL_TEST_ISSUE_FORK || '3585397' }} + + steps: + - uses: actions/checkout@v6 + + - name: Load 1Password secrets + uses: 1password/load-secrets-action@v4 + with: + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + TEST_CODER_SESSION_TOKEN: "op://test-secrets/TEST_CODER_SESSION_TOKEN/credential" + + - uses: coder/setup-action@v1 + with: + access_url: ${{ vars.TEST_CODER_URL }} + coder_session_token: ${{ env.TEST_CODER_SESSION_TOKEN }} + + - name: Resolve issue branch and Drupal version from issue number + run: | + ISSUE_NUM="${ISSUE_FORK#drupal-}" + ISSUE_BRANCH=$(curl -sf \ + "https://git.drupalcode.org/api/v4/projects/issue%2Fdrupal-${ISSUE_NUM}" \ + | jq -r '.default_branch') + if [[ -z "$ISSUE_BRANCH" || "$ISSUE_BRANCH" == "null" ]]; then + echo "ERROR: Could not resolve default branch for issue fork $ISSUE_NUM" >&2 + exit 1 + fi + RAW_VERSION=$(curl -sf \ + "https://www.drupal.org/api-d7/node/${ISSUE_NUM}.json" \ + | jq -r '.field_issue_version // ""') + DRUPAL_VERSION=$(echo "$RAW_VERSION" | grep -oE '^[0-9]+' || echo "12") + echo "Resolved: branch=$ISSUE_BRANCH drupal_version=$DRUPAL_VERSION" + echo "ISSUE_BRANCH=$ISSUE_BRANCH" >> "$GITHUB_ENV" + echo "ISSUE_FORK_VERSION=$DRUPAL_VERSION" >> "$GITHUB_ENV" + + - name: Copy VERSION into template directory + run: cp VERSION drupal-core/VERSION + + - name: Push template (inactive) + run: | + coder templates push drupal-core \ + --directory drupal-core \ + --activate=false \ + --name gd-${{ github.run_number }}-${{ github.run_attempt }} \ + --yes \ + --variable workspace_image_registry=index.docker.io/ddev/coder-ddev \ + --variable cache_path=/tmp/ci-no-cache + + - name: Create workspace + run: | + coder create ${{ env.WORKSPACE_NAME }} \ + --template drupal-core \ + --template-version gd-${{ github.run_number }}-${{ github.run_attempt }} \ + --parameter "vscode_extensions=[]" \ + --parameter drupal_version=${{ env.ISSUE_FORK_VERSION }} \ + --parameter issue_fork=${{ env.ISSUE_FORK }} \ + --parameter issue_branch=${{ env.ISSUE_BRANCH }} \ + --parameter install_profile=minimal \ + --parameter share_drupal_site=owner \ + --yes + + - name: Verify workspace — agent connected + run: coder ssh ${{ env.WORKSPACE_NAME }} --wait=yes -- echo "Agent connected" + + - name: Verify workspace — Docker daemon running + run: coder ssh ${{ env.WORKSPACE_NAME }} -- docker ps + + - name: Verify workspace — DDEV installed + run: coder ssh ${{ env.WORKSPACE_NAME }} -- ddev --version + + - name: Verify workspace — web root exists + run: coder ssh ${{ env.WORKSPACE_NAME }} -- test -f /home/coder/drupal-core/web/index.php + + - name: Verify workspace — issue branch checked out + run: | + CURRENT=$(coder ssh ${{ env.WORKSPACE_NAME }} -- git -C /home/coder/drupal-core/repos/drupal branch --show-current | tr -d '\r\n') + echo "Current branch: $CURRENT Expected: ${{ env.ISSUE_BRANCH }}" + [[ "$CURRENT" == "${{ env.ISSUE_BRANCH }}" ]] || { echo "ERROR: wrong branch" >&2; exit 1; } + + - name: Verify workspace — Drush DB connected + run: coder ssh ${{ env.WORKSPACE_NAME }} -- ddev drush status --fields=db-status | grep -i connected + + - name: Record owner + run: | + OWNER=$(coder whoami --output json | jq -r 'if type == "array" then .[0].username else .username end') + echo "OWNER=$OWNER" >> "$GITHUB_ENV" + + - name: Verify workspace — Drupal site externally accessible + run: | + CODER_DOMAIN="${{ vars.TEST_CODER_URL }}" + CODER_DOMAIN="${CODER_DOMAIN#https://}" + SITE_URL="https://drupal-site--${{ env.WORKSPACE_NAME }}--${OWNER}.${CODER_DOMAIN}" + echo "Checking $SITE_URL" + HTTP_STATUS=$(curl -sL --max-time 30 --retry 3 --retry-delay 5 \ + -H "Coder-Session-Token: ${TEST_CODER_SESSION_TOKEN}" \ + -o /tmp/drupal-response.html \ + -w "%{http_code}" \ + "$SITE_URL") + echo "HTTP status: $HTTP_STATUS" + if [[ ! "$HTTP_STATUS" =~ ^[23] ]]; then + echo "ERROR: unexpected HTTP status $HTTP_STATUS" >&2 + head -50 /tmp/drupal-response.html >&2 || true + exit 1 + fi + if ! grep -qi "log in" /tmp/drupal-response.html; then + echo "ERROR: response does not contain 'log in' — Drupal may not have started" >&2 + head -50 /tmp/drupal-response.html >&2 + exit 1 + fi + echo "OK: Drupal site is accessible and shows login page" + + - name: Delete workspace + if: always() + run: coder delete ${{ env.WORKSPACE_NAME }} --yes || true + + - name: Archive CI template version + if: always() + run: coder templates versions archive drupal-core gd-${{ github.run_number }}-${{ github.run_attempt }} --yes || true diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 3db9a02..7afed0c 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -284,3 +284,204 @@ jobs: - name: Archive CI template version if: always() run: coder templates versions archive ${{ matrix.template }} ci-${{ env.CI_TAG }} --yes || true + + integration-test-gh: + name: Integration test GH (${{ matrix.template }}) + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - template: user-defined-web + ws_name: udw + extra_vars: "" + extra_params: "" + app_slug: "ddev-web" + - template: freeform + ws_name: ff + extra_vars: "" + extra_params: "" + app_slug: "" + fail-fast: false + defaults: + run: + shell: bash -euo pipefail {0} + env: + CI_TAG: ${{ github.run_number }}-${{ github.run_attempt }} + WORKSPACE_NAME: gh-${{ matrix.ws_name }}-${{ github.run_number }}-${{ github.run_attempt }} + CI: "true" + DDEV_NONINTERACTIVE: "true" + NO_COLOR: "1" + + steps: + - uses: actions/checkout@v6 + + - name: Load 1Password secrets + uses: 1password/load-secrets-action@v4 + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == github.repository_owner }} + with: + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + TEST_CODER_SESSION_TOKEN: "op://test-secrets/TEST_CODER_SESSION_TOKEN/credential" + + - uses: coder/setup-action@v1 + with: + access_url: ${{ vars.TEST_CODER_URL }} + coder_session_token: ${{ env.TEST_CODER_SESSION_TOKEN }} + + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + with: + limit-access-to-actor: true + github-token: ${{ secrets.GITHUB_TOKEN }} + if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }} + + - name: Copy VERSION into template directory + run: cp VERSION ${{ matrix.template }}/VERSION + + - name: Push template (inactive) + run: | + coder templates push ${{ matrix.template }} \ + --directory ${{ matrix.template }} \ + --activate=false \ + --name ci-gh-${{ env.CI_TAG }} \ + --yes \ + --variable workspace_image_registry=index.docker.io/ddev/coder-ddev \ + ${{ matrix.extra_vars }} + + - name: Create workspace + if: ${{ matrix.template != 'freeform' }} + run: | + coder create ${{ env.WORKSPACE_NAME }} \ + --template ${{ matrix.template }} \ + --template-version ci-gh-${{ env.CI_TAG }} \ + --parameter "vscode_extensions=[]" \ + ${{ matrix.extra_params }} \ + --yes + + - name: Create freeform workspace (with project names) + if: ${{ matrix.template == 'freeform' }} + run: | + cat > /tmp/freeform-params-gh-${{ env.CI_TAG }}.yaml << EOF + project_names: "ci-site1-${{ env.CI_TAG }},ci-site2-${{ env.CI_TAG }}" + vscode_extensions: "[]" + EOF + coder create ${{ env.WORKSPACE_NAME }} \ + --template ${{ matrix.template }} \ + --template-version ci-gh-${{ env.CI_TAG }} \ + --rich-parameter-file /tmp/freeform-params-gh-${{ env.CI_TAG }}.yaml \ + --use-parameter-defaults \ + --yes + + - name: Verify workspace — agent connected + run: coder ssh ${{ env.WORKSPACE_NAME }} --wait=yes -- echo "Agent connected" + + - name: Verify workspace — Docker daemon running + run: coder ssh ${{ env.WORKSPACE_NAME }} -- docker ps + + - name: Verify workspace — DDEV installed + run: coder ssh ${{ env.WORKSPACE_NAME }} -- ddev --version + + - name: Record owner + run: | + OWNER=$(coder whoami --output json | jq -r 'if type == "array" then .[0].username else .username end') + echo "OWNER=$OWNER" >> "$GITHUB_ENV" + + - name: Verify workspace — DDEV can start a project + if: ${{ matrix.app_slug != '' }} + run: | + cat > /tmp/ci-ddev-start-gh-${{ env.CI_TAG }}.sh << 'EOF' + set -euo pipefail + TESTDIR=/tmp/ci-ddev-gh-${{ env.CI_TAG }} + mkdir -p "$TESTDIR/web" && cd "$TESTDIR" + ddev config --project-type=php --docroot=web + ddev start -y + EOF + scp \ + -o StrictHostKeyChecking=no \ + -o UserKnownHostsFile=/dev/null \ + -o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \ + /tmp/ci-ddev-start-gh-${{ env.CI_TAG }}.sh \ + coder@workspace:/tmp/ci-ddev-start-gh-${{ env.CI_TAG }}.sh + coder ssh ${{ env.WORKSPACE_NAME }} -- \ + env CI=${{ env.CI }} DDEV_NONINTERACTIVE=${{ env.DDEV_NONINTERACTIVE }} NO_COLOR=${{ env.NO_COLOR }} \ + bash /tmp/ci-ddev-start-gh-${{ env.CI_TAG }}.sh < /dev/null + + - name: Verify workspace — DDEV web externally accessible + if: ${{ matrix.app_slug != '' }} + run: | + CODER_DOMAIN="${{ vars.TEST_CODER_URL }}" + CODER_DOMAIN="${CODER_DOMAIN#https://}" + SITE_URL="https://${{ matrix.app_slug }}--${{ env.WORKSPACE_NAME }}--${OWNER}.${CODER_DOMAIN}" + echo "Checking $SITE_URL" + STATUS=$(curl -s --max-time 30 -o /dev/null -w "%{http_code}" "$SITE_URL") + echo "HTTP status: $STATUS" + [[ "$STATUS" =~ ^[2-4] ]] || { echo "ERROR: unexpected HTTP status $STATUS" >&2; exit 1; } + + - name: Cleanup DDEV test project + if: ${{ matrix.app_slug != '' }} + run: | + coder ssh ${{ env.WORKSPACE_NAME }} -- ddev delete ci-ddev-gh-${{ env.CI_TAG }} --omit-snapshot -y < /dev/null || true + coder ssh ${{ env.WORKSPACE_NAME }} -- rm -rf /tmp/ci-ddev-gh-${{ env.CI_TAG }} < /dev/null || true + + - name: Inject current-branch DDEV scripts into freeform workspace + if: ${{ matrix.template == 'freeform' }} + run: | + coder ssh ${{ env.WORKSPACE_NAME }} -- mkdir -p /home/coder/.ddev/commands/host < /dev/null + for script in coder-routes coder-setup launch; do + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \ + "image/scripts/.ddev/commands/host/${script}" \ + "coder@workspace:/home/coder/.ddev/commands/host/${script}" + done + coder ssh ${{ env.WORKSPACE_NAME }} -- chmod +x /home/coder/.ddev/commands/host/coder-routes /home/coder/.ddev/commands/host/coder-setup /home/coder/.ddev/commands/host/launch < /dev/null + + - name: Start two PHP projects — freeform routing test + if: ${{ matrix.template == 'freeform' }} + run: | + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \ + freeform/scripts/test-freeform-start.sh \ + coder@workspace:/tmp/test-freeform-start.sh + coder ssh ${{ env.WORKSPACE_NAME }} -- \ + env CI=${{ env.CI }} DDEV_NONINTERACTIVE=${{ env.DDEV_NONINTERACTIVE }} NO_COLOR=${{ env.NO_COLOR }} \ + bash /tmp/test-freeform-start.sh "${{ env.CI_TAG }}" < /dev/null + + - name: Verify freeform — ddev launch and describe per-project URLs + if: ${{ matrix.template == 'freeform' }} + run: | + CODER_DOMAIN="${{ vars.TEST_CODER_URL }}" + CODER_DOMAIN="${CODER_DOMAIN#https://}" + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \ + freeform/scripts/test-freeform-verify.sh \ + coder@workspace:/tmp/test-freeform-verify.sh + coder ssh ${{ env.WORKSPACE_NAME }} -- \ + env DDEV_NONINTERACTIVE=${{ env.DDEV_NONINTERACTIVE }} NO_COLOR=${{ env.NO_COLOR }} \ + bash /tmp/test-freeform-verify.sh \ + "${{ env.CI_TAG }}" "${{ env.WORKSPACE_NAME }}" "${OWNER}" "${CODER_DOMAIN}" \ + < /dev/null + + - name: Cleanup freeform PHP projects + if: ${{ always() && matrix.template == 'freeform' }} + run: | + coder show "${{ env.WORKSPACE_NAME }}" > /dev/null 2>&1 || { + echo "Workspace ${{ env.WORKSPACE_NAME }} not found — skipping DDEV cleanup" + exit 0 + } + scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \ + -o ProxyCommand="coder ssh --stdio ${{ env.WORKSPACE_NAME }}" \ + freeform/scripts/test-freeform-cleanup.sh \ + coder@workspace:/tmp/test-freeform-cleanup.sh + coder ssh ${{ env.WORKSPACE_NAME }} -- \ + env DDEV_NONINTERACTIVE=${{ env.DDEV_NONINTERACTIVE }} \ + bash /tmp/test-freeform-cleanup.sh "${{ env.CI_TAG }}" < /dev/null + + - name: Delete workspace + if: always() + run: coder delete ${{ env.WORKSPACE_NAME }} --yes || true + + - name: Archive CI template version + if: always() + run: coder templates versions archive ${{ matrix.template }} ci-gh-${{ env.CI_TAG }} --yes || true diff --git a/CLAUDE.md b/CLAUDE.md index f5d9f4e..cf313dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -261,6 +261,7 @@ Key template variables in `user-defined-web/template.tf`: ### Git Workflow - This is an **infrastructure repository** managing Coder templates - Use feature branches for changes +- **Never use the local `main` branch** — always `git fetch upstream` and base branches on `upstream/main`. Use `upstream/main` for comparisons (e.g. `git diff upstream/main...HEAD`), not local `main`. - Always use OpenSpec for architectural changes (see AGENTS.md) ### OpenSpec Integration From 58d72ae9071fcca0c57a74dd234eb4463e667d62 Mon Sep 17 00:00:00 2001 From: Randy Fay Date: Thu, 7 May 2026 17:27:09 -0600 Subject: [PATCH 2/2] fix(ci): serialize -gh matrix jobs to avoid Coder API rate limit Adding max-parallel: 1 to the three new -gh matrix jobs prevents them from doubling the concurrent template push requests and hitting the 12 req/min rate limit on staging-coder.ddev.com. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/drupal-contrib-integration-test.yml | 1 + .github/workflows/drupal-integration-test.yml | 1 + .github/workflows/integration-test.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/drupal-contrib-integration-test.yml b/.github/workflows/drupal-contrib-integration-test.yml index 2e8a089..aeef9e7 100644 --- a/.github/workflows/drupal-contrib-integration-test.yml +++ b/.github/workflows/drupal-contrib-integration-test.yml @@ -392,6 +392,7 @@ jobs: - project: pathauto drupal_version: "11" fail-fast: false + max-parallel: 1 defaults: run: shell: bash -euo pipefail {0} diff --git a/.github/workflows/drupal-integration-test.yml b/.github/workflows/drupal-integration-test.yml index f9a53d3..370676a 100644 --- a/.github/workflows/drupal-integration-test.yml +++ b/.github/workflows/drupal-integration-test.yml @@ -201,6 +201,7 @@ jobs: matrix: drupal_version: ["12", "11"] fail-fast: false + max-parallel: 1 defaults: run: shell: bash -euo pipefail {0} diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 7afed0c..bfe5a9b 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -303,6 +303,7 @@ jobs: extra_params: "" app_slug: "" fail-fast: false + max-parallel: 1 defaults: run: shell: bash -euo pipefail {0}