diff --git a/.github/actions/setup-php-composer/action.yml b/.github/actions/setup-php-composer/action.yml index 39883b1..88f553e 100644 --- a/.github/actions/setup-php-composer/action.yml +++ b/.github/actions/setup-php-composer/action.yml @@ -15,13 +15,13 @@ runs: using: 'composite' steps: - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2 with: php-version: ${{ inputs.php-version }} extensions: json, mbstring, yaml${{ inputs.coverage == 'true' && ', xdebug' || '' }} coverage: ${{ inputs.coverage == 'true' && 'xdebug' || 'none' }} - name: Install Composer dependencies (cached) - uses: ramsey/composer-install@v3 + uses: ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520 # 3.1.1 with: composer-options: --no-interaction --no-progress --prefer-dist --no-ansi --no-scripts diff --git a/.github/workflows/bats-cloud-aws.yml b/.github/workflows/bats-cloud-aws.yml deleted file mode 100644 index ff200db..0000000 --- a/.github/workflows/bats-cloud-aws.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: BATS Cloud Tests (AWS) - -permissions: - contents: read - actions: read - -on: - workflow_run: - workflows: ["BATS VM Tests (Ubuntu 24.04)"] - types: [completed] - -jobs: - vm-gate: - name: verify-vm-success - if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository }} - runs-on: ubuntu-latest - timeout-minutes: 3 - outputs: - ready: ${{ steps.evaluate.outputs.ready }} - - steps: - - name: Verify VM job concluded with success - id: evaluate - uses: actions/github-script@v7 - with: - script: | - const { owner, repo } = context.repo; - const workflowRunId = context.payload.workflow_run.id; - const expectedJobName = "vm-tests (ubuntu24)"; - - const jobsResponse = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: workflowRunId, - per_page: 100, - }); - - const vmJob = jobsResponse.data.jobs.find((job) => job.name === expectedJobName); - const ready = vmJob?.conclusion === "success"; - core.setOutput("ready", ready ? "true" : "false"); - - if (!ready) { - core.notice( - `Skipping cloud run because '${expectedJobName}' was not successful (conclusion=${vmJob?.conclusion ?? "missing"})`, - ); - } - - cloud-tests-aws: - needs: vm-gate - name: cloud-tests (aws) - if: ${{ needs.vm-gate.outputs.ready == 'true' }} - runs-on: ubuntu-24.04 - timeout-minutes: 12 - concurrency: - group: bats-cloud-aws-${{ github.event.workflow_run.head_sha }} - cancel-in-progress: true - - env: - CI: "true" - BATS_RUN_SUFFIX: ${{ github.run_id }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.event.workflow_run.head_sha }} - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - - - name: Install BATS and dependencies - run: | - sudo apt-get update - sudo apt-get install -y bats jq curl - aws --version - - - name: Setup environment file - run: | - cat <<'EOF' > .env - ${{ secrets.DOTENV_FILE }} - EOF - - - name: Setup SSH key for cloud provisioning - run: | - mkdir -p ~/.ssh tests/bats/fixtures/keys - echo "${{ secrets.SSH_PRIVATE_KEY_B64 }}" | base64 -d > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub - cp ~/.ssh/id_ed25519.pub tests/bats/fixtures/keys/id_test.pub - - - name: Run AWS tests - run: | - set -a - # shellcheck disable=SC1091 - source .env - set +a - ./bats.sh ci cloud aws - - - name: Cleanup cloud resources (backstop) - if: always() - run: | - set -a - # shellcheck disable=SC1091 - source .env - set +a - tests/bats/lib/cloud-janitor.sh \ - --mode=targeted \ - --suffix="${BATS_RUN_SUFFIX}" \ - --providers="aws" diff --git a/.github/workflows/bats-cloud-do.yml b/.github/workflows/bats-cloud-do.yml deleted file mode 100644 index 5c23c34..0000000 --- a/.github/workflows/bats-cloud-do.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: BATS Cloud Tests (DO) - -permissions: - contents: read - actions: read - -on: - workflow_run: - workflows: ["BATS VM Tests (Ubuntu 24.04)"] - types: [completed] - -jobs: - vm-gate: - name: verify-vm-success - if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository }} - runs-on: ubuntu-latest - timeout-minutes: 3 - outputs: - ready: ${{ steps.evaluate.outputs.ready }} - - steps: - - name: Verify VM job concluded with success - id: evaluate - uses: actions/github-script@v7 - with: - script: | - const { owner, repo } = context.repo; - const workflowRunId = context.payload.workflow_run.id; - const expectedJobName = "vm-tests (ubuntu24)"; - - const jobsResponse = await github.rest.actions.listJobsForWorkflowRun({ - owner, - repo, - run_id: workflowRunId, - per_page: 100, - }); - - const vmJob = jobsResponse.data.jobs.find((job) => job.name === expectedJobName); - const ready = vmJob?.conclusion === "success"; - core.setOutput("ready", ready ? "true" : "false"); - - if (!ready) { - core.notice( - `Skipping cloud run because '${expectedJobName}' was not successful (conclusion=${vmJob?.conclusion ?? "missing"})`, - ); - } - - cloud-tests-do: - needs: vm-gate - name: cloud-tests (do) - if: ${{ needs.vm-gate.outputs.ready == 'true' }} - runs-on: ubuntu-24.04 - timeout-minutes: 12 - concurrency: - group: bats-cloud-do-${{ github.event.workflow_run.head_sha }} - cancel-in-progress: true - - env: - CI: "true" - BATS_RUN_SUFFIX: ${{ github.run_id }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.event.workflow_run.head_sha }} - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - - - name: Install BATS and dependencies - run: | - sudo apt-get update - sudo apt-get install -y bats jq curl - aws --version - - - name: Setup environment file - run: | - cat <<'EOF' > .env - ${{ secrets.DOTENV_FILE }} - EOF - - - name: Setup SSH key for cloud provisioning - run: | - mkdir -p ~/.ssh tests/bats/fixtures/keys - echo "${{ secrets.SSH_PRIVATE_KEY_B64 }}" | base64 -d > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub - cp ~/.ssh/id_ed25519.pub tests/bats/fixtures/keys/id_test.pub - - - name: Run DO tests - run: | - set -a - # shellcheck disable=SC1091 - source .env - set +a - ./bats.sh ci cloud do - - - name: Cleanup cloud resources (backstop) - if: always() - run: | - set -a - # shellcheck disable=SC1091 - source .env - set +a - tests/bats/lib/cloud-janitor.sh \ - --mode=targeted \ - --suffix="${BATS_RUN_SUFFIX}" \ - --providers="do" diff --git a/.github/workflows/bats-cloud-janitor.yml b/.github/workflows/bats-cloud-janitor.yml deleted file mode 100644 index 15c66ed..0000000 --- a/.github/workflows/bats-cloud-janitor.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: BATS Cloud Janitor - -permissions: - contents: read - actions: read - -on: - workflow_run: - workflows: ["BATS Cloud Tests (AWS)", "BATS Cloud Tests (DO)"] - types: [completed] - schedule: - - cron: "0 * * * *" - workflow_dispatch: - inputs: - mode: - description: "Cleanup mode" - required: true - default: sweep - type: choice - options: - - sweep - - targeted - suffixes: - description: "Suffix list (required for targeted mode, comma-separated)" - required: false - type: string - providers: - description: "Providers (csv: aws,do,cf)" - required: false - default: "aws,do,cf" - type: string - min_age_minutes: - description: "Sweep protection window in minutes" - required: false - default: "30" - type: string - dry_run: - description: "Print actions without deleting" - required: false - default: false - type: boolean - -jobs: - janitor: - if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.head_repository.full_name == github.repository }} - runs-on: ubuntu-24.04 - timeout-minutes: 12 - concurrency: - group: bats-cloud-janitor - cancel-in-progress: false - - env: - GITHUB_TOKEN: ${{ github.token }} - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - - - name: Install janitor dependencies - run: | - sudo apt-get update - sudo apt-get install -y jq curl - aws --version - - - name: Setup environment file - run: | - cat <<'ENVEOF' > .env - ${{ secrets.DOTENV_FILE }} - ENVEOF - - - name: Janitor cleanup after BATS workflow completion - if: ${{ github.event_name == 'workflow_run' }} - run: | - set -a - # shellcheck disable=SC1091 - source .env - set +a - tests/bats/lib/cloud-janitor.sh \ - --mode=targeted \ - --suffix="${{ github.event.workflow_run.id }}" \ - --providers="aws,do,cf" - - - name: Janitor scheduled sweep - if: ${{ github.event_name == 'schedule' }} - run: | - set -a - # shellcheck disable=SC1091 - source .env - set +a - tests/bats/lib/cloud-janitor.sh \ - --mode=sweep \ - --providers="aws,do,cf" \ - --min-age-minutes=30 - - - name: Janitor manual run - if: ${{ github.event_name == 'workflow_dispatch' }} - run: | - set -a - # shellcheck disable=SC1091 - source .env - set +a - ARGS=( - "--mode=${{ inputs.mode }}" - "--providers=${{ inputs.providers }}" - "--min-age-minutes=${{ inputs.min_age_minutes }}" - ) - - if [[ -n "${{ inputs.suffixes }}" ]]; then - ARGS+=("--suffixes=${{ inputs.suffixes }}") - fi - - if [[ "${{ inputs.dry_run }}" == "true" ]]; then - ARGS+=("--dry-run") - fi - - tests/bats/lib/cloud-janitor.sh "${ARGS[@]}" diff --git a/.github/workflows/bats-vm-ubuntu24.yml b/.github/workflows/bats-vm-ubuntu24.yml deleted file mode 100644 index fd1b193..0000000 --- a/.github/workflows/bats-vm-ubuntu24.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: BATS VM Tests (Ubuntu 24.04) - -permissions: - contents: read - actions: read - -on: - workflow_run: - workflows: ["Pest", "PHPStan", "Pint", "Rector", "CI Canary"] - types: [completed] - -jobs: - quality-gate: - name: wait-for-quality-gates - if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository }} - runs-on: ubuntu-latest - timeout-minutes: 3 - outputs: - ready: ${{ steps.evaluate.outputs.ready }} - - steps: - - name: Verify all quality workflows succeeded - id: evaluate - uses: actions/github-script@v7 - with: - script: | - const requiredWorkflows = ["Pest", "PHPStan", "Pint", "Rector", "CI Canary"]; - const headSha = context.payload.workflow_run.head_sha; - const eventName = context.payload.workflow_run.event; - const { owner, repo } = context.repo; - - const runsResponse = await github.rest.actions.listWorkflowRunsForRepo({ - owner, - repo, - event: eventName, - head_sha: headSha, - per_page: 100, - }); - - const latestRunByName = new Map(); - for (const run of runsResponse.data.workflow_runs) { - if (!requiredWorkflows.includes(run.name)) { - continue; - } - - const current = latestRunByName.get(run.name); - if (!current || run.id > current.id) { - latestRunByName.set(run.name, run); - } - } - - const missing = requiredWorkflows.filter((name) => !latestRunByName.has(name)); - const failed = requiredWorkflows - .map((name) => latestRunByName.get(name)) - .filter(Boolean) - .filter((run) => run.conclusion !== "success") - .map((run) => `${run.name}=${run.conclusion ?? "unknown"}`); - - const ready = missing.length === 0 && failed.length === 0; - core.setOutput("ready", ready ? "true" : "false"); - - if (!ready) { - core.notice( - `Quality gates not ready for ${headSha}: missing=[${missing.join(", ")}] failed=[${failed.join(", ")}]`, - ); - } - - vm-tests-ubuntu24: - needs: quality-gate - name: vm-tests (ubuntu24) - if: ${{ needs.quality-gate.outputs.ready == 'true' }} - runs-on: ubuntu-24.04 - timeout-minutes: 12 - concurrency: - group: bats-vm-ubuntu24-${{ github.event.workflow_run.head_sha }} - cancel-in-progress: true - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ github.event.workflow_run.head_sha }} - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - - - name: Install BATS and dependencies - run: | - sudo apt-get update - sudo apt-get install -y bats jq - - - name: Setup Lima - uses: lima-vm/lima-actions/setup@v1 - - - name: Setup test fixtures - run: | - mkdir -p tests/bats/fixtures/keys tests/bats/fixtures/inventory - ssh-keygen -t ed25519 -f tests/bats/fixtures/keys/id_test -N "" -C "deployer-bats-test" - - - name: Setup environment file - run: | - cat <<'EOF' > .env - ${{ secrets.DOTENV_FILE }} - EOF - - - name: Run VM tests for ubuntu24 - env: - CI: "true" - BATS_DISTRO: ubuntu24 - run: ./bats.sh ci vm ubuntu24 diff --git a/.github/workflows/ci-canary.yml b/.github/workflows/ci-canary.yml deleted file mode 100644 index 1e84e2f..0000000 --- a/.github/workflows/ci-canary.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: CI Canary - -permissions: - contents: read - -on: - pull_request: - paths: - - "tests/CICanary.php" - - "pint.json" - - "phpstan.neon" - - "rector-canary.php" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/ci-canary.yml" - push: - branches: - - main - paths: - - "tests/CICanary.php" - - "pint.json" - - "phpstan.neon" - - "rector-canary.php" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/ci-canary.yml" - -jobs: - canary: - runs-on: ubuntu-latest - timeout-minutes: 3 - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - - - name: Verify Pint fails on CICanary class - run: | - if vendor/bin/pint --test tests/CICanary.php; then - echo "ERROR: Pint should have failed on CICanary" - exit 1 - fi - - - name: Verify Rector fails on CICanary class - run: | - mkdir -p /tmp/rector - if vendor/bin/rector --dry-run tests/CICanary.php --config rector-canary.php; then - echo "ERROR: Rector should have failed on CICanary" - exit 1 - fi - - - name: Verify PHPStan fails on CICanary class - run: | - if vendor/bin/phpstan analyse tests/CICanary.php --level=10; then - echo "ERROR: PHPStan should have failed on CICanary" - exit 1 - fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..883c834 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,562 @@ +name: CI + +permissions: + contents: read + actions: read + +on: + pull_request: + paths: &ci_paths + - ".github/**/*.yml" + - "app/**" + - "bin/**" + - "playbooks/**" + - "scaffolds/**" + - "tests/**" + - "**/*.php" + - "**/*.sh" + - "composer.json" + - "composer.lock" + - "phpstan.neon" + - "phpunit.xml" + - "pint.json" + push: + branches: + - main + paths: *ci_paths + schedule: + - cron: "0 * * * *" + workflow_dispatch: + inputs: + mode: + description: "Cleanup mode" + required: true + default: sweep + type: choice + options: + - sweep + - targeted + suffixes: + description: "Suffix list (required for targeted mode, comma-separated)" + required: false + type: string + providers: + description: "Providers (csv: aws,do,cf)" + required: false + default: "aws,do,cf" + type: string + min_age_minutes: + description: "Sweep protection window in minutes" + required: false + default: "30" + type: string + dry_run: + description: "Print actions without deleting" + required: false + default: false + type: boolean + +jobs: + trust-context: + name: trust-context + runs-on: ubuntu-latest + timeout-minutes: 3 + outputs: + run_quality: ${{ steps.classify.outputs.run_quality }} + run_secret_jobs: ${{ steps.classify.outputs.run_secret_jobs }} + run_sweep: ${{ steps.classify.outputs.run_sweep }} + run_manual_janitor: ${{ steps.classify.outputs.run_manual_janitor }} + cache_scope: ${{ steps.classify.outputs.cache_scope }} + checkout_ref: ${{ steps.classify.outputs.checkout_ref }} + + steps: + - name: Classify trust context + id: classify + env: + EVENT_NAME: ${{ github.event_name }} + REF: ${{ github.ref }} + REPOSITORY: ${{ github.repository }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name || '' }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }} + run: | + run_quality=false + run_secret_jobs=false + run_sweep=false + run_manual_janitor=false + cache_scope=other + checkout_ref="$GITHUB_SHA" + + if [[ "$EVENT_NAME" == "pull_request" ]]; then + run_quality=true + checkout_ref="$PR_HEAD_SHA" + if [[ "$PR_HEAD_REPO" == "$REPOSITORY" ]]; then + run_secret_jobs=true + cache_scope=same-repo-pr + else + cache_scope=fork-pr + fi + elif [[ "$EVENT_NAME" == "push" && "$REF" == "refs/heads/main" ]]; then + run_quality=true + run_secret_jobs=true + cache_scope=main-push + elif [[ "$EVENT_NAME" == "schedule" ]]; then + run_sweep=true + cache_scope=schedule + elif [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then + run_manual_janitor=true + cache_scope=manual + fi + + { + echo "run_quality=$run_quality" + echo "run_secret_jobs=$run_secret_jobs" + echo "run_sweep=$run_sweep" + echo "run_manual_janitor=$run_manual_janitor" + echo "cache_scope=$cache_scope" + echo "checkout_ref=$checkout_ref" + } >> "$GITHUB_OUTPUT" + + ci-canary: + needs: trust-context + if: ${{ needs.trust-context.outputs.run_quality == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 3 + concurrency: + group: ci-canary-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Verify Pint fails on CICanary class + run: | + if vendor/bin/pint --test tests/CICanary.php; then + echo "ERROR: Pint should have failed on CICanary" + exit 1 + fi + + - name: Verify Rector fails on CICanary class + run: | + mkdir -p /tmp/rector + if vendor/bin/rector --dry-run tests/CICanary.php --config rector-canary.php; then + echo "ERROR: Rector should have failed on CICanary" + exit 1 + fi + + - name: Verify PHPStan fails on CICanary class + run: | + if vendor/bin/phpstan analyse tests/CICanary.php --level=10; then + echo "ERROR: PHPStan should have failed on CICanary" + exit 1 + fi + + pint: + needs: trust-context + if: ${{ needs.trust-context.outputs.run_quality == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 3 + concurrency: + group: pint-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Run code style check + run: vendor/bin/pint --test --parallel + + phpstan: + needs: trust-context + if: ${{ needs.trust-context.outputs.run_quality == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 3 + concurrency: + group: phpstan-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Run static analysis + run: vendor/bin/phpstan analyse --error-format=github + + rector: + needs: trust-context + if: ${{ needs.trust-context.outputs.run_quality == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 3 + concurrency: + group: rector-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - run: mkdir -p /tmp/rector + + - name: Cache Rector tmp dir + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: /tmp/rector + key: ${{ runner.os }}-rector-${{ needs.trust-context.outputs.cache_scope }}-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-rector-${{ needs.trust-context.outputs.cache_scope }}- + + - name: Run modern refactoring code checks + run: vendor/bin/rector --dry-run + + pest: + needs: trust-context + if: ${{ needs.trust-context.outputs.run_secret_jobs == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 3 + concurrency: + group: pest-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + with: + coverage: "true" + + - name: Run tests with coverage + run: vendor/bin/pest --parallel --coverage --coverage-text + + vm-tests-ubuntu24: + name: vm-tests (ubuntu24) + needs: [trust-context, ci-canary, pint, phpstan, rector, pest] + if: ${{ needs.trust-context.outputs.run_secret_jobs == 'true' }} + runs-on: ubuntu-24.04 + timeout-minutes: 12 + concurrency: + group: bats-vm-ubuntu24-${{ needs.trust-context.outputs.checkout_ref }} + cancel-in-progress: true + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ needs.trust-context.outputs.checkout_ref }} + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Install BATS and dependencies + run: | + sudo apt-get update + sudo apt-get install -y bats jq + + - name: Setup Lima + uses: lima-vm/lima-actions/setup@55627e31b78637bf254a8b2a14da8ea7d12564e5 # v1 + + - name: Setup test fixtures + run: | + mkdir -p tests/bats/fixtures/keys tests/bats/fixtures/inventory + ssh-keygen -t ed25519 -f tests/bats/fixtures/keys/id_test -N "" -C "deployer-bats-test" + + - name: Setup environment file + run: | + cat <<'EOF' > .env + ${{ secrets.DOTENV_FILE }} + EOF + + - name: Run VM tests for ubuntu24 + env: + CI: "true" + BATS_DISTRO: ubuntu24 + run: ./bats.sh ci vm ubuntu24 + + cloud-tests-do: + name: cloud-tests (do) + needs: [trust-context, vm-tests-ubuntu24] + if: ${{ needs.trust-context.outputs.run_secret_jobs == 'true' && needs.vm-tests-ubuntu24.result == 'success' }} + runs-on: ubuntu-24.04 + timeout-minutes: 12 + concurrency: + group: bats-cloud-do-${{ needs.trust-context.outputs.checkout_ref }} + cancel-in-progress: true + + env: + CI: "true" + BATS_RUN_SUFFIX: ${{ github.run_id }} + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ needs.trust-context.outputs.checkout_ref }} + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Install BATS and dependencies + run: | + sudo apt-get update + sudo apt-get install -y bats jq curl + aws --version + + - name: Setup environment file + run: | + cat <<'EOF' > .env + ${{ secrets.DOTENV_FILE }} + EOF + + - name: Setup SSH key for cloud provisioning + run: | + mkdir -p ~/.ssh tests/bats/fixtures/keys + echo "${{ secrets.SSH_PRIVATE_KEY_B64 }}" | base64 -d > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub + cp ~/.ssh/id_ed25519.pub tests/bats/fixtures/keys/id_test.pub + + - name: Run DO tests + run: | + set -a + # shellcheck disable=SC1091 + source .env + set +a + ./bats.sh ci cloud do + + - name: Cleanup cloud resources (backstop) + if: always() + run: | + set -a + # shellcheck disable=SC1091 + source .env + set +a + tests/bats/lib/cloud-janitor.sh \ + --mode=targeted \ + --suffix="${BATS_RUN_SUFFIX}" \ + --providers="do" + + cloud-tests-aws: + name: cloud-tests (aws) + needs: [trust-context, vm-tests-ubuntu24] + if: ${{ needs.trust-context.outputs.run_secret_jobs == 'true' && needs.vm-tests-ubuntu24.result == 'success' }} + runs-on: ubuntu-24.04 + timeout-minutes: 12 + concurrency: + group: bats-cloud-aws-${{ needs.trust-context.outputs.checkout_ref }} + cancel-in-progress: true + + env: + CI: "true" + BATS_RUN_SUFFIX: ${{ github.run_id }} + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ needs.trust-context.outputs.checkout_ref }} + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Install BATS and dependencies + run: | + sudo apt-get update + sudo apt-get install -y bats jq curl + aws --version + + - name: Setup environment file + run: | + cat <<'EOF' > .env + ${{ secrets.DOTENV_FILE }} + EOF + + - name: Setup SSH key for cloud provisioning + run: | + mkdir -p ~/.ssh tests/bats/fixtures/keys + echo "${{ secrets.SSH_PRIVATE_KEY_B64 }}" | base64 -d > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub + cp ~/.ssh/id_ed25519.pub tests/bats/fixtures/keys/id_test.pub + + - name: Run AWS tests + run: | + set -a + # shellcheck disable=SC1091 + source .env + set +a + ./bats.sh ci cloud aws + + - name: Cleanup cloud resources (backstop) + if: always() + run: | + set -a + # shellcheck disable=SC1091 + source .env + set +a + tests/bats/lib/cloud-janitor.sh \ + --mode=targeted \ + --suffix="${BATS_RUN_SUFFIX}" \ + --providers="aws" + + janitor-targeted: + name: janitor-targeted + needs: [trust-context, cloud-tests-do, cloud-tests-aws] + if: >- + ${{ + always() && + needs.trust-context.outputs.run_secret_jobs == 'true' && + (needs['cloud-tests-do'].result != 'skipped' || needs['cloud-tests-aws'].result != 'skipped') + }} + runs-on: ubuntu-24.04 + timeout-minutes: 12 + concurrency: + group: bats-cloud-janitor-targeted-${{ github.run_id }} + cancel-in-progress: false + + env: + GITHUB_TOKEN: ${{ github.token }} + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Install janitor dependencies + run: | + sudo apt-get update + sudo apt-get install -y jq curl + aws --version + + - name: Setup environment file + run: | + cat <<'ENVEOF' > .env + ${{ secrets.DOTENV_FILE }} + ENVEOF + + - name: Janitor cleanup after cloud jobs + run: | + set -a + # shellcheck disable=SC1091 + source .env + set +a + tests/bats/lib/cloud-janitor.sh \ + --mode=targeted \ + --suffix="${{ github.run_id }}" \ + --providers="aws,do,cf" + + janitor-sweep: + needs: trust-context + if: ${{ needs.trust-context.outputs.run_sweep == 'true' }} + runs-on: ubuntu-24.04 + timeout-minutes: 12 + concurrency: + group: bats-cloud-janitor + cancel-in-progress: false + + env: + GITHUB_TOKEN: ${{ github.token }} + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Install janitor dependencies + run: | + sudo apt-get update + sudo apt-get install -y jq curl + aws --version + + - name: Setup environment file + run: | + cat <<'ENVEOF' > .env + ${{ secrets.DOTENV_FILE }} + ENVEOF + + - name: Janitor scheduled sweep + run: | + set -a + # shellcheck disable=SC1091 + source .env + set +a + tests/bats/lib/cloud-janitor.sh \ + --mode=sweep \ + --providers="aws,do,cf" \ + --min-age-minutes=30 + + janitor-manual: + needs: trust-context + if: ${{ needs.trust-context.outputs.run_manual_janitor == 'true' }} + runs-on: ubuntu-24.04 + timeout-minutes: 12 + concurrency: + group: bats-cloud-janitor + cancel-in-progress: false + + env: + GITHUB_TOKEN: ${{ github.token }} + MODE: ${{ inputs.mode }} + PROVIDERS: ${{ inputs.providers }} + MIN_AGE_MINUTES: ${{ inputs.min_age_minutes }} + SUFFIXES: ${{ inputs.suffixes }} + DRY_RUN: ${{ inputs.dry_run }} + + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup PHP and Composer + uses: ./.github/actions/setup-php-composer + + - name: Install janitor dependencies + run: | + sudo apt-get update + sudo apt-get install -y jq curl + aws --version + + - name: Setup environment file + run: | + cat <<'ENVEOF' > .env + ${{ secrets.DOTENV_FILE }} + ENVEOF + + - name: Janitor manual run + run: | + set -a + # shellcheck disable=SC1091 + source .env + set +a + + ARGS=( + "--mode=${MODE}" + "--providers=${PROVIDERS}" + "--min-age-minutes=${MIN_AGE_MINUTES}" + ) + + if [[ -n "$SUFFIXES" ]]; then + ARGS+=("--suffixes=${SUFFIXES}") + fi + + if [[ "$DRY_RUN" == "true" ]]; then + ARGS+=("--dry-run") + fi + + tests/bats/lib/cloud-janitor.sh "${ARGS[@]}" diff --git a/.github/workflows/pest.yml b/.github/workflows/pest.yml deleted file mode 100644 index af8a55a..0000000 --- a/.github/workflows/pest.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Pest - -permissions: - contents: read - -on: - pull_request: - paths: - - "app/**" - - "tests/**" - - "phpunit.xml" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/pest.yml" - push: - branches: - - main - paths: - - "app/**" - - "tests/**" - - "phpunit.xml" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/pest.yml" - -jobs: - pest: - runs-on: ubuntu-latest - timeout-minutes: 3 - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - with: - coverage: "true" - - - name: Run tests with coverage - run: vendor/bin/pest --parallel --coverage --coverage-text diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml deleted file mode 100644 index b8ea45c..0000000 --- a/.github/workflows/phpstan.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: PHPStan - -permissions: - contents: read - -on: - pull_request: - paths: - - "app/**" - - "bin/**" - - "phpstan.neon" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/phpstan.yml" - push: - branches: - - main - paths: - - "app/**" - - "bin/**" - - "phpstan.neon" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/phpstan.yml" - -jobs: - phpstan: - runs-on: ubuntu-latest - timeout-minutes: 3 - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - - - name: Run static analysis - run: vendor/bin/phpstan analyse --error-format=github diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml deleted file mode 100644 index 49b10aa..0000000 --- a/.github/workflows/pint.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Pint - -permissions: - contents: read - -on: - pull_request: - paths: - - "app/**" - - "bin/**" - - "tests/**" - - "*.php" - - "pint.json" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/pint.yml" - push: - branches: - - main - paths: - - "app/**" - - "bin/**" - - "tests/**" - - "*.php" - - "pint.json" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/pint.yml" - -jobs: - pint: - runs-on: ubuntu-latest - timeout-minutes: 3 - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - - - name: Run code style check - run: vendor/bin/pint --test --parallel diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml deleted file mode 100644 index 346b2c0..0000000 --- a/.github/workflows/rector.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Rector - -permissions: - contents: read - -on: - pull_request: - paths: - - "app/**" - - "bin/**" - - "tests/**" - - "rector.php" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/rector.yml" - push: - branches: - - main - paths: - - "app/**" - - "bin/**" - - "tests/**" - - "rector.php" - - "composer.json" - - "composer.lock" - - ".github/actions/setup-php-composer/**" - - ".github/workflows/rector.yml" - -jobs: - rector: - runs-on: ubuntu-latest - timeout-minutes: 3 - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP and Composer - uses: ./.github/actions/setup-php-composer - - - run: mkdir -p /tmp/rector - - - name: Cache Rector tmp dir - uses: actions/cache@v4 - with: - path: /tmp/rector - key: ${{ runner.os }}-rector-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-rector- - - - name: Run modern refactoring code checks - run: vendor/bin/rector --dry-run