From fe4ab98d57a505e79285481782ef62b5f1e18b8f Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Wed, 13 May 2026 15:41:57 +0200 Subject: [PATCH 1/9] Redesign Github workflows - Have a single test workflow for testing everything, including the API server. - Split the API server workflow into a build part and a deploy part. Get rid of test steps, since that's done by the test workflow. - Syntax check Ansible. - Make test and API server build workflows compatible with pull requests. - Enforce timeouts. --- .github/workflows/apiserver-build.yml | 97 +++++++++++ .github/workflows/apiserver-deploy.yml | 156 ++++++++++++++++++ .github/workflows/apiserver.yml | 141 ---------------- .../workflows/{code-reviews.yml => test.yml} | 47 +++++- AGENTS.md | 8 + apiserver/README.md | 2 +- 6 files changed, 305 insertions(+), 146 deletions(-) create mode 100644 .github/workflows/apiserver-build.yml create mode 100644 .github/workflows/apiserver-deploy.yml delete mode 100644 .github/workflows/apiserver.yml rename .github/workflows/{code-reviews.yml => test.yml} (50%) create mode 100644 AGENTS.md diff --git a/.github/workflows/apiserver-build.yml b/.github/workflows/apiserver-build.yml new file mode 100644 index 0000000..d6f6f10 --- /dev/null +++ b/.github/workflows/apiserver-build.yml @@ -0,0 +1,97 @@ +# This workflow must be usable with third-party pull requests, +# and thus may not make use of any secrets. +# +# Security checklists: +# https://aquilax.ai/blog/github-actions-security-hardening +# https://www.wiz.io/blog/github-actions-security-guide + +name: API Server build + +on: + push: + paths: + - .github/workflows/apiserver-build.yml + - apiserver/** + pull_request: + types: + - opened + - synchronize + paths: + - .github/workflows/apiserver-build.yml + - apiserver/** + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }} + +jobs: + build: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + env: + BUNDLE_PATH: vendor/bundle + BUNDLE_WITH: ci + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + + # We don't use ruby/setup-ruby in this workflow because it produces gem binaries + # that aren't compatible with the server, which install Ruby from distro repos. + - name: Check that we're using system Ruby + run: test "$(which ruby)" = /usr/bin/ruby + + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: apiserver/vendor/bundle + key: ${{ runner.os }}-24.04-${{ runner.arch }}-gems-${{ hashFiles('apiserver/Gemfile.lock') }} + + - name: Install matching Bundler version + run: sudo gem install bundler -v $(grep -A1 "BUNDLED WITH" Gemfile.lock | tail -n1) --no-document + working-directory: apiserver + + - name: Install gem bundle + run: bundle install + working-directory: apiserver + env: + BUNDLE_DEPLOYMENT: "true" + BUNDLE_JOBS: 4 + BUNDLE_RETRY: 3 + BUNDLE_CLEAN: "true" + + - name: Detect library dependencies + run: bundle exec debendencies . -o dpkg-dependencies.txt --tee + working-directory: apiserver + + - name: Create tarball + run: > + tar + -c + --use-compress-program 'zstd -T0' + --sort name + --owner root:0 + --group root:0 + --mtime '2024-01-01 00:00Z' + --preserve-permissions + --pax-option exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime + -f apiserver-"$GITHUB_RUN_NUMBER"-$(lsb_release --id --short | tr '[:upper:]' '[:lower:]')-$(lsb_release --release --short)-$(dpkg --print-architecture).tar.zst + -C apiserver + . + + - name: Upload artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: apiserver + path: "*.tar.zst" + compression-level: 0 diff --git a/.github/workflows/apiserver-deploy.yml b/.github/workflows/apiserver-deploy.yml new file mode 100644 index 0000000..5e5de9e --- /dev/null +++ b/.github/workflows/apiserver-deploy.yml @@ -0,0 +1,156 @@ +# Security checklists: +# https://aquilax.ai/blog/github-actions-security-hardening +# https://www.wiz.io/blog/github-actions-security-guide + +name: API Server deploy + +on: + workflow_dispatch: + inputs: + run_id: + description: "API Server build workflow run ID to deploy" + required: true + workflow_run: + workflows: + - API Server build + branches: + - main + types: + - completed + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-slim + timeout-minutes: 15 + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + environment: deploy + permissions: + contents: write + id-token: write + steps: + - name: Resolve run ID + id: resolve-run-id + run: | + echo "run_id=${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || github.event.inputs.run_id }}" >> "$GITHUB_OUTPUT" + + - name: Validate run ID + id: validate-run-id + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_ID: ${{ steps.resolve-run-id.outputs.run_id }} + EXPECTED_WORKFLOW_NAME: API Server build + EXPECTED_BRANCH: main + with: + script: | + const runId = Number(process.env.RUN_ID); + if (!Number.isInteger(runId) || runId <= 0) { + core.setFailed(`Invalid run_id: ${process.env.RUN_ID}`); + return; + } + + const { owner, repo } = context.repo; + const run = ( + await github.rest.actions.getWorkflowRun({ + owner, + repo, + run_id: runId + }) + ).data; + + if (run.name !== process.env.EXPECTED_WORKFLOW_NAME) { + core.setFailed( + `run_id ${runId} is for workflow '${run.name}', expected '${process.env.EXPECTED_WORKFLOW_NAME}'` + ); + return; + } + + if (run.head_branch !== process.env.EXPECTED_BRANCH) { + core.setFailed( + `run_id ${runId} is on branch '${run.head_branch}', expected '${process.env.EXPECTED_BRANCH}'` + ); + return; + } + + if (run.conclusion !== 'success') { + core.setFailed( + `run_id ${runId} has conclusion '${run.conclusion}', expected 'success'` + ); + return; + } + + if (run.head_repository?.full_name !== `${owner}/${repo}`) { + core.setFailed( + `run_id ${runId} belongs to '${run.head_repository?.full_name}', expected '${owner}/${repo}'` + ); + return; + } + + core.setOutput('build_head_sha', run.head_sha); + core.setOutput('build_run_number', String(run.run_number)); + core.setOutput('build_url', run.html_url); + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: true + ref: ${{ steps.validate-run-id.outputs.build_head_sha }} + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: apiserver + run-id: ${{ steps.resolve-run-id.outputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create tag + run: | + { + git show -s --format=%B "$BUILD_HEAD_SHA" + echo + echo "Deploy run: $GITHUB_RUN_ID" + echo "Build run: $BUILD_RUN_ID" + echo "Build number: $BUILD_RUN_NUMBER" + echo "Build URL: $BUILD_URL" + echo "Build SHA: $BUILD_HEAD_SHA" + } > tag-message.txt + + git tag -f -a apiserver-"$GITHUB_RUN_NUMBER" -F tag-message.txt "$BUILD_HEAD_SHA" + env: + BUILD_RUN_ID: ${{ steps.resolve-run-id.outputs.run_id }} + BUILD_RUN_NUMBER: ${{ steps.validate-run-id.outputs.build_run_number }} + BUILD_URL: ${{ steps.validate-run-id.outputs.build_url }} + BUILD_HEAD_SHA: ${{ steps.validate-run-id.outputs.build_head_sha }} + + - name: Push tag + run: git push origin apiserver-"$GITHUB_RUN_NUMBER" + + - name: Create release + run: > + gh release create + apiserver-"$GITHUB_RUN_NUMBER" + apiserver-*.tar.zst + --title "apiserver v$GITHUB_RUN_NUMBER" + --notes-from-tag + --verify-tag + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + id: get-id-token + with: + script: | + const fs = require('fs'); + const token = await core.getIDToken('backend.fullstaqruby.org'); + fs.writeFileSync( + process.env.GITHUB_OUTPUT, + `id_token< + curl -fL --no-progress-meter -X POST -H "Authorization: Bearer $TOKEN" + https://apt.fullstaqruby.org/admin/upgrade_apiserver + env: + TOKEN: ${{ steps.get-id-token.outputs.id_token }} diff --git a/.github/workflows/apiserver.yml b/.github/workflows/apiserver.yml deleted file mode 100644 index aaa1a57..0000000 --- a/.github/workflows/apiserver.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: API Server CI/CD - -on: - workflow_dispatch: - inputs: - deploy: - description: "Deploy? Set to true" - required: true - push: - paths: - - .github/workflows/apiserver.yml - - apiserver/** - -jobs: - build: - runs-on: ubuntu-24.04 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "24" - - - name: Install eclint - run: npm install -g eclint - - name: Check EditorConfig compliance - run: eclint check $(git ls-files) apiserver - - - name: Check that we're using system Ruby - run: test "$(which ruby)" = /usr/bin/ruby - - - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: apiserver/vendor/bundle - key: ${{ runner.os }}-24.04-${{ runner.arch }}-gems-${{ hashFiles('apiserver/Gemfile.lock') }} - - - name: Install matching Bundler version - run: sudo gem install bundler -v $(grep -A1 "BUNDLED WITH" Gemfile.lock | tail -n1) --no-document - working-directory: apiserver - - - name: Install gem bundle - run: bundle install - working-directory: apiserver - env: - BUNDLE_DEPLOYMENT: "true" - BUNDLE_PATH: vendor/bundle - BUNDLE_JOBS: 4 - BUNDLE_RETRY: 3 - BUNDLE_WITH: ci - BUNDLE_CLEAN: "true" - - - name: Check code formatting with Rufo - run: bundle exec rufo --check . - working-directory: apiserver - env: - BUNDLE_PATH: vendor/bundle - BUNDLE_WITH: ci - - - name: Detect library dependencies - run: bundle exec debendencies . -o dpkg-dependencies.txt --tee - working-directory: apiserver - env: - BUNDLE_PATH: vendor/bundle - BUNDLE_WITH: ci - - - name: Create tarball - run: > - tar - -c - --use-compress-program 'zstd -T0' - --sort name - --owner root:0 - --group root:0 - --mtime '2024-01-01 00:00Z' - --preserve-permissions - --pax-option exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime - -f apiserver-"$GITHUB_RUN_NUMBER"-$(lsb_release --id --short | tr '[:upper:]' '[:lower:]')-$(lsb_release --release --short)-$(dpkg --print-architecture).tar.zst - -C apiserver - . - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: apiserver - path: "*.tar.zst" - compression-level: 0 - - deploy: - runs-on: ubuntu-24.04 - needs: build - if: github.ref == 'refs/heads/main' || github.event.inputs.deploy == 'true' - environment: deploy - permissions: - contents: write - id-token: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: apiserver - - - name: Create tag - run: git tag -f apiserver-"$GITHUB_RUN_NUMBER" - - - name: Push tag - run: git push origin apiserver-"$GITHUB_RUN_NUMBER" - - - name: Create release - run: > - gh release create - apiserver-"$GITHUB_RUN_NUMBER" - apiserver-*.tar.zst - --title "apiserver v$GITHUB_RUN_NUMBER" - --notes-from-tag - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - id: get-id-token - with: - script: | - const fs = require('fs'); - const token = await core.getIDToken('backend.fullstaqruby.org'); - fs.writeFileSync( - process.env.GITHUB_OUTPUT, - `id_token< - curl -fL --no-progress-meter -X POST -H "Authorization: Bearer $TOKEN" - https://apt.fullstaqruby.org/admin/upgrade_apiserver - env: - TOKEN: ${{ steps.get-id-token.outputs.id_token }} diff --git a/.github/workflows/code-reviews.yml b/.github/workflows/test.yml similarity index 50% rename from .github/workflows/code-reviews.yml rename to .github/workflows/test.yml index 7ba1852..e101619 100644 --- a/.github/workflows/code-reviews.yml +++ b/.github/workflows/test.yml @@ -1,19 +1,44 @@ # This workflow must be usable with third-party pull requests, # and thus may not make use of any secrets. +# +# Security checklists: +# https://aquilax.ai/blog/github-actions-security-hardening +# https://www.wiz.io/blog/github-actions-security-guide -name: Code reviews +name: Test on: workflow_dispatch: {} push: paths-ignore: - "**.md" - - .github/workflows/apiserver.yml - - apiserver/** + - .github/workflows/apiserver-*.yml + pull_request: + types: + - opened + - synchronize + paths-ignore: + - "**.md" + - .github/workflows/apiserver-*.yml + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }} jobs: - check: + test: runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + env: + BUNDLE_GEMFILE: ${{ github.workspace }}/apiserver/Gemfile steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -49,3 +74,17 @@ jobs: - name: Check formatting of terraform-hisec/ run: terraform fmt -check -diff -recursive working-directory: terraform-hisec + + - name: Install Ansible + run: sudo apt update && sudo apt install -y ansible + - name: Check Ansible playbook syntax + run: ansible-playbook -i hosts.ini --syntax-check main.yml + working-directory: ansible + + - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 + with: + ruby-version: "3.2" + bundler-cache: true + - name: Check code formatting with Rufo + run: bundle exec rufo --check . + working-directory: apiserver diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2a62da6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,8 @@ +This is the infra-as-code repo for Fullstaq Ruby. + +# Github Workflows review instructions (ALWAYS FOLLOW) + +- Consult security checklists: + https://aquilax.ai/blog/github-actions-security-hardening + https://www.wiz.io/blog/github-actions-security-guide +- Review transitive Github Actions dependencies too. diff --git a/apiserver/README.md b/apiserver/README.md index db03d29..6cf9ca5 100644 --- a/apiserver/README.md +++ b/apiserver/README.md @@ -14,4 +14,4 @@ curl -v -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://a ## Continuous deployment -New API server code changes, when pushed to master, are automatically deployed by the Infrastructure project's CI. +New API server code changes, when pushed to main, are automatically deployed by the Infrastructure project's CI. From 7e8cc1232c13d6761eb61e4191d4711291507e7e Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Thu, 14 May 2026 11:18:51 +0200 Subject: [PATCH 2/9] fix --- .github/workflows/test.yml | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e101619..6c2a5d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }} jobs: - test: + test-terraform: runs-on: ubuntu-24.04 timeout-minutes: 15 # Trigger only on push (internal branches) or on PRs from forks. @@ -37,8 +37,6 @@ jobs: if: > (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - env: - BUNDLE_GEMFILE: ${{ github.workspace }}/apiserver/Gemfile steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -75,16 +73,45 @@ jobs: run: terraform fmt -check -diff -recursive working-directory: terraform-hisec + test-ansible: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Install Ansible run: sudo apt update && sudo apt install -y ansible - name: Check Ansible playbook syntax run: ansible-playbook -i hosts.ini --syntax-check main.yml working-directory: ansible + test-apiserver: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + env: + BUNDLE_PATH: vendor/bundle + BUNDLE_WITH: ci + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 with: ruby-version: "3.2" bundler-cache: true + env: + BUNDLE_PATH: vendor/bundle - name: Check code formatting with Rufo run: bundle exec rufo --check . working-directory: apiserver From a6fb7aab3be021e560b149c72c4b0a4facf027ce Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Thu, 14 May 2026 11:25:00 +0200 Subject: [PATCH 3/9] fix --- .github/workflows/test.yml | 3 ++- AGENTS.md | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c2a5d3..36b9d05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -111,7 +111,8 @@ jobs: ruby-version: "3.2" bundler-cache: true env: - BUNDLE_PATH: vendor/bundle + BUNDLE_GEMFILE: apiserver/Gemfile + BUNDLE_PATH: apiserver/vendor/bundle - name: Check code formatting with Rufo run: bundle exec rufo --check . working-directory: apiserver diff --git a/AGENTS.md b/AGENTS.md index 2a62da6..3cb8e7a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,4 +5,6 @@ This is the infra-as-code repo for Fullstaq Ruby. - Consult security checklists: https://aquilax.ai/blog/github-actions-security-hardening https://www.wiz.io/blog/github-actions-security-guide +- Learn insights from the Tanstack postmortem: https://snyk.io/blog/tanstack-npm-packages-compromised/ +- Beware of cache poisoning opportunities. - Review transitive Github Actions dependencies too. From 7b2732f0cf3251cb67ea8a979a62b2b9e97bb20b Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Thu, 14 May 2026 14:47:02 +0200 Subject: [PATCH 4/9] ... --- .github/workflows/apiserver-build.yml | 97 ------- .../{apiserver-deploy.yml => deploy.yml} | 85 ++++-- .github/workflows/test-and-build.yml | 256 ++++++++++++++++++ .github/workflows/test.yml | 118 -------- AGENTS.md | 11 +- 5 files changed, 332 insertions(+), 235 deletions(-) delete mode 100644 .github/workflows/apiserver-build.yml rename .github/workflows/{apiserver-deploy.yml => deploy.yml} (63%) create mode 100644 .github/workflows/test-and-build.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/apiserver-build.yml b/.github/workflows/apiserver-build.yml deleted file mode 100644 index d6f6f10..0000000 --- a/.github/workflows/apiserver-build.yml +++ /dev/null @@ -1,97 +0,0 @@ -# This workflow must be usable with third-party pull requests, -# and thus may not make use of any secrets. -# -# Security checklists: -# https://aquilax.ai/blog/github-actions-security-hardening -# https://www.wiz.io/blog/github-actions-security-guide - -name: API Server build - -on: - push: - paths: - - .github/workflows/apiserver-build.yml - - apiserver/** - pull_request: - types: - - opened - - synchronize - paths: - - .github/workflows/apiserver-build.yml - - apiserver/** - -permissions: - contents: read - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }} - -jobs: - build: - runs-on: ubuntu-24.04 - timeout-minutes: 15 - # Trigger only on push (internal branches) or on PRs from forks. - # Avoids duplicate runs on PRs from internal branches. - if: > - (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') - || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - env: - BUNDLE_PATH: vendor/bundle - BUNDLE_WITH: ci - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "24" - - # We don't use ruby/setup-ruby in this workflow because it produces gem binaries - # that aren't compatible with the server, which install Ruby from distro repos. - - name: Check that we're using system Ruby - run: test "$(which ruby)" = /usr/bin/ruby - - - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: apiserver/vendor/bundle - key: ${{ runner.os }}-24.04-${{ runner.arch }}-gems-${{ hashFiles('apiserver/Gemfile.lock') }} - - - name: Install matching Bundler version - run: sudo gem install bundler -v $(grep -A1 "BUNDLED WITH" Gemfile.lock | tail -n1) --no-document - working-directory: apiserver - - - name: Install gem bundle - run: bundle install - working-directory: apiserver - env: - BUNDLE_DEPLOYMENT: "true" - BUNDLE_JOBS: 4 - BUNDLE_RETRY: 3 - BUNDLE_CLEAN: "true" - - - name: Detect library dependencies - run: bundle exec debendencies . -o dpkg-dependencies.txt --tee - working-directory: apiserver - - - name: Create tarball - run: > - tar - -c - --use-compress-program 'zstd -T0' - --sort name - --owner root:0 - --group root:0 - --mtime '2024-01-01 00:00Z' - --preserve-permissions - --pax-option exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime - -f apiserver-"$GITHUB_RUN_NUMBER"-$(lsb_release --id --short | tr '[:upper:]' '[:lower:]')-$(lsb_release --release --short)-$(dpkg --print-architecture).tar.zst - -C apiserver - . - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: apiserver - path: "*.tar.zst" - compression-level: 0 diff --git a/.github/workflows/apiserver-deploy.yml b/.github/workflows/deploy.yml similarity index 63% rename from .github/workflows/apiserver-deploy.yml rename to .github/workflows/deploy.yml index 5e5de9e..0843cab 100644 --- a/.github/workflows/apiserver-deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,17 +2,18 @@ # https://aquilax.ai/blog/github-actions-security-hardening # https://www.wiz.io/blog/github-actions-security-guide -name: API Server deploy +name: Deploy on: workflow_dispatch: inputs: run_id: - description: "API Server build workflow run ID to deploy" + description: "Test and build workflow run ID to deploy" required: true + # zizmor: ignore[dangerous-triggers] Only triggered via default branch workflow_run: workflows: - - API Server build + - Test and build branches: - main types: @@ -21,28 +22,35 @@ on: permissions: contents: read +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + jobs: - deploy: - runs-on: ubuntu-slim - timeout-minutes: 15 + validate: + runs-on: ubuntu-24.04 + timeout-minutes: 2 if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' - environment: deploy - permissions: - contents: write - id-token: write + outputs: + build_run_id: ${{ steps.resolve-run-id.outputs.run_id }} + build_skipped: ${{ steps.validate-run-id.outputs.build_skipped }} + build_head_sha: ${{ steps.validate-run-id.outputs.build_head_sha }} + build_run_number: ${{ steps.validate-run-id.outputs.build_run_number }} + build_url: ${{ steps.validate-run-id.outputs.build_url }} steps: - name: Resolve run ID id: resolve-run-id - run: | - echo "run_id=${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || github.event.inputs.run_id }}" >> "$GITHUB_OUTPUT" + run: echo "run_id=$RESULT" >> "$GITHUB_OUTPUT" + env: + RESULT: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || github.event.inputs.run_id }} - name: Validate run ID id: validate-run-id uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_ID: ${{ steps.resolve-run-id.outputs.run_id }} - EXPECTED_WORKFLOW_NAME: API Server build + EXPECTED_WORKFLOW_NAME: Test and build EXPECTED_BRANCH: main + EXPECTED_JOB_NAME: apiserver-build with: script: | const runId = Number(process.env.RUN_ID); @@ -88,19 +96,58 @@ jobs: return; } + const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, { + owner, + repo, + run_id: runId, + per_page: 100, + }); + + const buildJob = jobs.find((job) => job.name === process.env.EXPECTED_JOB_NAME); + if (!buildJob) { + core.setFailed( + `run_id ${runId} does not contain expected job '${process.env.EXPECTED_JOB_NAME}'` + ); + return; + } + + if (buildJob.conclusion === 'skipped') { + core.setOutput('build_skipped', 'true'); + core.info(`job '${process.env.EXPECTED_JOB_NAME}' in run_id ${runId} was skipped; nothing to deploy`); + return; + } + + if (buildJob.conclusion !== 'success') { + core.setFailed( + `job '${process.env.EXPECTED_JOB_NAME}' in run_id ${runId} has conclusion '${buildJob.conclusion}', expected 'success'` + ); + return; + } + + core.setOutput('build_skipped', 'false'); core.setOutput('build_head_sha', run.head_sha); core.setOutput('build_run_number', String(run.run_number)); core.setOutput('build_url', run.html_url); + deploy-apiserver: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + needs: validate + environment: deploy + if: needs.validate.outputs.build_skipped == 'false' + permissions: + contents: write + id-token: write + steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: true - ref: ${{ steps.validate-run-id.outputs.build_head_sha }} + ref: ${{ needs.validate.outputs.build_head_sha }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: apiserver - run-id: ${{ steps.resolve-run-id.outputs.run_id }} + run-id: ${{ needs.validate.outputs.build_run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: Create tag @@ -117,10 +164,10 @@ jobs: git tag -f -a apiserver-"$GITHUB_RUN_NUMBER" -F tag-message.txt "$BUILD_HEAD_SHA" env: - BUILD_RUN_ID: ${{ steps.resolve-run-id.outputs.run_id }} - BUILD_RUN_NUMBER: ${{ steps.validate-run-id.outputs.build_run_number }} - BUILD_URL: ${{ steps.validate-run-id.outputs.build_url }} - BUILD_HEAD_SHA: ${{ steps.validate-run-id.outputs.build_head_sha }} + BUILD_RUN_ID: ${{ needs.validate.outputs.build_run_id }} + BUILD_RUN_NUMBER: ${{ needs.validate.outputs.build_run_number }} + BUILD_URL: ${{ needs.validate.outputs.build_url }} + BUILD_HEAD_SHA: ${{ needs.validate.outputs.build_head_sha }} - name: Push tag run: git push origin apiserver-"$GITHUB_RUN_NUMBER" diff --git a/.github/workflows/test-and-build.yml b/.github/workflows/test-and-build.yml new file mode 100644 index 0000000..3d057b9 --- /dev/null +++ b/.github/workflows/test-and-build.yml @@ -0,0 +1,256 @@ +# This workflow must be usable with third-party pull requests, +# and thus may not make use of any secrets. +# +# Security checklists: +# https://aquilax.ai/blog/github-actions-security-hardening +# https://www.wiz.io/blog/github-actions-security-guide + +name: Test and build + +on: + workflow_dispatch: {} + push: + paths-ignore: + - "**.md" + pull_request: + types: + - opened + - synchronize + paths-ignore: + - "**.md" + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }} + +jobs: + test-terraform: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: "1.5.7" + + - name: Install eclint + run: npm install -g eclint + - name: Check EditorConfig compliance + run: eclint check $(git ls-files) + + - name: Initialize terraform/ + run: terraform init -backend=false + working-directory: terraform + - name: Validate terraform/ + run: terraform validate + working-directory: terraform + - name: Check formatting of terraform/ + run: terraform fmt -check -diff -recursive + working-directory: terraform + + - name: Initialize terraform-hisec/ + run: terraform init -backend=false + working-directory: terraform-hisec + - name: Validate terraform-hisec/ + run: terraform validate + working-directory: terraform-hisec + - name: Check formatting of terraform-hisec/ + run: terraform fmt -check -diff -recursive + working-directory: terraform-hisec + + test-ansible: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Install Ansible + run: sudo apt update && sudo apt install -y ansible + - name: Check Ansible playbook syntax + run: ansible-playbook -i hosts.ini --syntax-check main.yml + working-directory: ansible + + audit-workflows: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + security-events: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Run zizmor + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 + + test-apiserver: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + env: + BUNDLE_PATH: vendor/bundle + BUNDLE_WITH: ci + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 + with: + ruby-version: "3.2" + bundler-cache: true + env: + BUNDLE_GEMFILE: apiserver/Gemfile + BUNDLE_PATH: apiserver/vendor/bundle + - name: Check code formatting with Rufo + run: bundle exec rufo --check . + working-directory: apiserver + + detect-apiserver-changes: + runs-on: ubuntu-24.04 + timeout-minutes: 2 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + outputs: + changed: ${{ steps.detect.outputs.changed }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + fetch-depth: 0 + - id: detect + name: Detect apiserver/ changes + shell: bash + run: | + set -euo pipefail + + if [[ "$GITHUB_EVENT_NAME" == "pull_request" || "$GITHUB_EVENT_NAME" == "pull_request_target" ]]; then + base_sha="$GITHUB_PR_BASE_SHA" + head_sha="$GITHUB_PR_HEAD_SHA" + changed_files="$(git diff --name-only "$base_sha" "$head_sha")" + elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then + before_sha="$GITHUB_EVENT_BEFORE" + head_sha="$GITHUB_SHA" + if [[ "$before_sha" =~ ^0+$ ]]; then + changed_files="$(git diff-tree --no-commit-id --name-only -r "$head_sha")" + else + changed_files="$(git diff --name-only "$before_sha" "$head_sha")" + fi + else + if git rev-parse HEAD^ >/dev/null 2>&1; then + changed_files="$(git diff --name-only HEAD^ HEAD)" + else + changed_files="$(git diff-tree --no-commit-id --name-only -r HEAD)" + fi + fi + + if printf '%s\n' "$changed_files" | grep -q '^apiserver/'; then + echo 'changed=true' >> "$GITHUB_OUTPUT" + else + echo 'changed=false' >> "$GITHUB_OUTPUT" + fi + env: + GITHUB_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + GITHUB_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GITHUB_EVENT_BEFORE: ${{ github.event.before }} + + apiserver-build: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + needs: + - detect-apiserver-changes + - test-terraform + - test-ansible + - audit-workflows + - test-apiserver + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + needs.detect-apiserver-changes.outputs.changed == 'true' && + ( + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + ) + env: + BUNDLE_PATH: vendor/bundle + BUNDLE_WITH: ci + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: "24" + + # We don't use ruby/setup-ruby in this workflow because it produces gem binaries + # that aren't compatible with the server, which install Ruby from distro repos. + - name: Check that we're using system Ruby + run: test "$(which ruby)" = /usr/bin/ruby + + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: apiserver/vendor/bundle + key: ${{ runner.os }}-24.04-${{ runner.arch }}-gems-${{ hashFiles('apiserver/Gemfile.lock') }} + + - name: Install matching Bundler version + run: sudo gem install bundler -v $(grep -A1 "BUNDLED WITH" Gemfile.lock | tail -n1) --no-document + working-directory: apiserver + + - name: Install gem bundle + run: bundle install + working-directory: apiserver + env: + BUNDLE_DEPLOYMENT: "true" + BUNDLE_JOBS: 4 + BUNDLE_RETRY: 3 + BUNDLE_CLEAN: "true" + + - name: Detect library dependencies + run: bundle exec debendencies . -o dpkg-dependencies.txt --tee + working-directory: apiserver + + - name: Create tarball + run: > + tar + -c + --use-compress-program 'zstd -T0' + --sort name + --owner root:0 + --group root:0 + --mtime '2024-01-01 00:00Z' + --preserve-permissions + --pax-option exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime + -f apiserver-"$GITHUB_RUN_NUMBER"-$(lsb_release --id --short | tr '[:upper:]' '[:lower:]')-$(lsb_release --release --short)-$(dpkg --print-architecture).tar.zst + -C apiserver + . + + - name: Upload artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: apiserver + path: "*.tar.zst" + compression-level: 0 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 36b9d05..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,118 +0,0 @@ -# This workflow must be usable with third-party pull requests, -# and thus may not make use of any secrets. -# -# Security checklists: -# https://aquilax.ai/blog/github-actions-security-hardening -# https://www.wiz.io/blog/github-actions-security-guide - -name: Test - -on: - workflow_dispatch: {} - push: - paths-ignore: - - "**.md" - - .github/workflows/apiserver-*.yml - pull_request: - types: - - opened - - synchronize - paths-ignore: - - "**.md" - - .github/workflows/apiserver-*.yml - -permissions: - contents: read - -concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }} - -jobs: - test-terraform: - runs-on: ubuntu-24.04 - timeout-minutes: 15 - # Trigger only on push (internal branches) or on PRs from forks. - # Avoids duplicate runs on PRs from internal branches. - if: > - (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') - || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: "24" - - uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 - with: - terraform_version: "1.5.7" - - - name: Install eclint - run: npm install -g eclint - - name: Check EditorConfig compliance - run: eclint check $(git ls-files) - - - name: Initialize terraform/ - run: terraform init -backend=false - working-directory: terraform - - name: Validate terraform/ - run: terraform validate - working-directory: terraform - - name: Check formatting of terraform/ - run: terraform fmt -check -diff -recursive - working-directory: terraform - - - name: Initialize terraform-hisec/ - run: terraform init -backend=false - working-directory: terraform-hisec - - name: Validate terraform-hisec/ - run: terraform validate - working-directory: terraform-hisec - - name: Check formatting of terraform-hisec/ - run: terraform fmt -check -diff -recursive - working-directory: terraform-hisec - - test-ansible: - runs-on: ubuntu-24.04 - timeout-minutes: 15 - # Trigger only on push (internal branches) or on PRs from forks. - # Avoids duplicate runs on PRs from internal branches. - if: > - (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') - || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: Install Ansible - run: sudo apt update && sudo apt install -y ansible - - name: Check Ansible playbook syntax - run: ansible-playbook -i hosts.ini --syntax-check main.yml - working-directory: ansible - - test-apiserver: - runs-on: ubuntu-24.04 - timeout-minutes: 15 - # Trigger only on push (internal branches) or on PRs from forks. - # Avoids duplicate runs on PRs from internal branches. - if: > - (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') - || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - env: - BUNDLE_PATH: vendor/bundle - BUNDLE_WITH: ci - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 - with: - ruby-version: "3.2" - bundler-cache: true - env: - BUNDLE_GEMFILE: apiserver/Gemfile - BUNDLE_PATH: apiserver/vendor/bundle - - name: Check code formatting with Rufo - run: bundle exec rufo --check . - working-directory: apiserver diff --git a/AGENTS.md b/AGENTS.md index 3cb8e7a..7f66d72 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,16 @@ -This is the infra-as-code repo for Fullstaq Ruby. +This is the infra-as-code repo for Fullstaq Ruby. Consult README.md and `docs/` to learn more about the codebase, especially [Infrastructure overview](docs/infrastructure-overview.md). + +# Directory structure + +- `terraform-hisec/`: tier-0 infrastructure that only a limited subteam (Infra Owners) can touch. +- `terraform/`: normal infrastructure that all team members can touch. +- `ansible/`: playbook for the server backend.fullstaqruby.org which hosts the domains {apt,yum}.fullstaqruby.org. +- `apiserver/`: a Ruby HTTP API app, deployed to backend.fullstaqruby.org, with and endpoint allowing the APT/YUM publishing jobs to notify the server that new packages have been uploaded. # Github Workflows review instructions (ALWAYS FOLLOW) +Review not only for correctness, but also security (VERY IMPORTANT): + - Consult security checklists: https://aquilax.ai/blog/github-actions-security-hardening https://www.wiz.io/blog/github-actions-security-guide From 232703601429875ee687f9a72e2ebbead9e9e6b2 Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Thu, 14 May 2026 14:57:09 +0200 Subject: [PATCH 5/9] ... --- .github/workflows/test-and-build.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-and-build.yml b/.github/workflows/test-and-build.yml index 3d057b9..cb34fec 100644 --- a/.github/workflows/test-and-build.yml +++ b/.github/workflows/test-and-build.yml @@ -92,6 +92,11 @@ jobs: audit-workflows: runs-on: ubuntu-24.04 timeout-minutes: 15 + # Trigger only on push (internal branches) or on PRs from forks. + # Avoids duplicate runs on PRs from internal branches. + if: > + (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') + || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name permissions: security-events: write steps: @@ -155,7 +160,7 @@ jobs: elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then before_sha="$GITHUB_EVENT_BEFORE" head_sha="$GITHUB_SHA" - if [[ "$before_sha" =~ ^0+$ ]]; then + if [[ "$before_sha" =~ ^0+$ ]] || ! git cat-file -e "$before_sha" 2>/dev/null; then changed_files="$(git diff-tree --no-commit-id --name-only -r "$head_sha")" else changed_files="$(git diff --name-only "$before_sha" "$head_sha")" From fd8799cd1962b6944744e555c39c7205db12a7be Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 15 May 2026 07:33:28 +0200 Subject: [PATCH 6/9] Change Noah Iniguez's username, update onboarding docs --- docs/members.md | 12 ++++++------ docs/offboarding.md | 9 +++++---- docs/onboarding.md | 17 ++++++++--------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/members.md b/docs/members.md index 58e8c79..9e3354a 100644 --- a/docs/members.md +++ b/docs/members.md @@ -1,8 +1,8 @@ # Members -| Name | Roles | -| ------------------------------------------------------------------------------------------ | ----------------------------- | -| [Hongli Lai](mailto:hongli@hongli.nl) ([FooBarWidget](https://github.com/FooBarWidget)) | Infra Owner, Infra Maintainer | -| [Max Erkin](mailto:russs.max@gmail.com) ([rus-max](https://github.com/rus-max)) | Infra Maintainer | -| [Britt Treece](mailto:britt.treece@gmail.com) ([abtreece](https://github.com/abtreece)) | Infra Maintainer | -| [Noah Iniguez](mailto:niniguezg@gmail.com) ([ncispreedly](https://github.com/ncispreedly)) | Infra Maintainer | +| Name | Roles | +| ------------------------------------------------------------------------------------------------ | ----------------------------- | +| [Hongli Lai](mailto:hongli@hongli.nl) ([FooBarWidget](https://github.com/FooBarWidget)) | Infra Owner, Infra Maintainer | +| [Max Erkin](mailto:russs.max@gmail.com) ([rus-max](https://github.com/rus-max)) | Infra Maintainer | +| [Britt Treece](mailto:britt.treece@gmail.com) ([abtreece](https://github.com/abtreece)) | Infra Maintainer | +| [Noah Iniguez](mailto:niniguezg@gmail.com) ([noahssarcastic](https://github.com/noahssarcastic)) | Infra Maintainer | diff --git a/docs/offboarding.md b/docs/offboarding.md index 559e329..ed62695 100644 --- a/docs/offboarding.md +++ b/docs/offboarding.md @@ -2,13 +2,14 @@ Offboarding is to be done by someone with the [Infra Owner role](roles.md). -- [ ] Remove member from the [Github repo's members list](https://github.com/fullstaq-ruby/infra/settings/access). -- [ ] Remove member from the `fsruby-server-edition2` Google Cloud project. -- [ ] Remove member from `terraform-hisec/variables.tf`, both `infra_owners_azure_group_members` and `infra_maintainers_azure_group_members`. Apply Terraform. +- [ ] Remove from the [Github repo's members list](https://github.com/fullstaq-ruby/infra/settings/access). +- [ ] Remove from the [Infra Owners team](https://github.com/orgs/fullstaq-ruby/teams/infra-owners). +- [ ] Remove from the `fsruby-server-edition2` Google Cloud project. +- [ ] Remove from `terraform-hisec/variables.tf`, both `infra_owners_azure_group_members` and `infra_maintainers_azure_group_members`. Apply Terraform. - [ ] Rotate the shared access keys for the `fsruby2seredci1` Azure storage account. 1. In the Azure portal, go to the `fsruby2seredci1` storage account -> Access keys. 2. Click "Rotate key" for key1. 3. Refresh Terraform state: run `pushd terraform && terraform refresh; popd` 4. Install the new connection string as a Github Actions secret. See [Infrastructure bootstrapping](infrastructure-bootstrapping.md) -> "Populate Github Actions secrets and variables". -- [ ] Remove member from Entra ID. +- [ ] Remove from Entra ID. - [ ] In the [Members](members.md) document, move member to the "Alumni" section. diff --git a/docs/onboarding.md b/docs/onboarding.md index e441756..cd00aab 100644 --- a/docs/onboarding.md +++ b/docs/onboarding.md @@ -2,14 +2,13 @@ Onboarding is to be done by someone with the [Infra Owner role](roles.md). -- [ ] Add member to the [Github repo's members list](https://github.com/fullstaq-ruby/infra/settings/access). - - If member is an Infra Maintainer: assign "Maintain" role. - - If member is an Infra Owner: assign "Admin" role. -- [ ] Add member to the `fsruby-server-edition2` Google Cloud project. - - If member is an Infra Maintainer: assign roles "Editor", "Storage Admin". - - If member is an Infra Owner: assign roles "Owner", "Storage Admin". -- Add member to Entra ID. +- [ ] Give access. + - If Infra Maintainer: add to the [Github repo's members list](https://github.com/fullstaq-ruby/infra/settings/access). + - If Infra Owner: add to the [Infra Owners team](https://github.com/orgs/fullstaq-ruby/teams/infra-owners). +- [ ] Add to the `fsruby-server-edition2` Google Cloud project. + - If Infra Maintainer: assign roles "Editor", "Storage Admin". + - If Infra Owner: assign roles "Owner", "Storage Admin". +- [ ] Add to Entra ID. - [ ] Create a (regular) user. - [ ] Add object ID to `terraform-hisec/variables.tf` -> `infra_owners_azure_group_members` or `infra_maintainers_azure_group_members`. Apply Terraform. -- [ ] Add member to the [Members](members.md) document. -- [ ] Add member to the [Infra Owners team](https://github.com/orgs/fullstaq-ruby/teams/infra-owners) if applicable. +- [ ] Add to the [Members](members.md) document. From 463a1f18e0f4509b1a97e9cd200457388bc0fe34 Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 15 May 2026 07:39:58 +0200 Subject: [PATCH 7/9] caching with restore keys --- .github/workflows/test-and-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-and-build.yml b/.github/workflows/test-and-build.yml index cb34fec..fad250d 100644 --- a/.github/workflows/test-and-build.yml +++ b/.github/workflows/test-and-build.yml @@ -220,6 +220,7 @@ jobs: with: path: apiserver/vendor/bundle key: ${{ runner.os }}-24.04-${{ runner.arch }}-gems-${{ hashFiles('apiserver/Gemfile.lock') }} + restore-keys: ${{ runner.os }}-24.04-${{ runner.arch }}-gems- - name: Install matching Bundler version run: sudo gem install bundler -v $(grep -A1 "BUNDLED WITH" Gemfile.lock | tail -n1) --no-document From 2a8bb4608fec0224869cfd19b133664413bd412c Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 15 May 2026 07:40:52 +0200 Subject: [PATCH 8/9] no force tag --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0843cab..250ae5a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -162,7 +162,7 @@ jobs: echo "Build SHA: $BUILD_HEAD_SHA" } > tag-message.txt - git tag -f -a apiserver-"$GITHUB_RUN_NUMBER" -F tag-message.txt "$BUILD_HEAD_SHA" + git tag -a apiserver-"$GITHUB_RUN_NUMBER" -F tag-message.txt "$BUILD_HEAD_SHA" env: BUILD_RUN_ID: ${{ needs.validate.outputs.build_run_id }} BUILD_RUN_NUMBER: ${{ needs.validate.outputs.build_run_number }} From 10f1ae728d7ea02c136c2f6d0f018189562a3f90 Mon Sep 17 00:00:00 2001 From: Hongli Lai Date: Fri, 15 May 2026 16:59:32 +0200 Subject: [PATCH 9/9] Fix skip-apiserver-build-on-force-push --- .github/workflows/test-and-build.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-and-build.yml b/.github/workflows/test-and-build.yml index fad250d..53d59d0 100644 --- a/.github/workflows/test-and-build.yml +++ b/.github/workflows/test-and-build.yml @@ -160,8 +160,24 @@ jobs: elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then before_sha="$GITHUB_EVENT_BEFORE" head_sha="$GITHUB_SHA" - if [[ "$before_sha" =~ ^0+$ ]] || ! git cat-file -e "$before_sha" 2>/dev/null; then - changed_files="$(git diff-tree --no-commit-id --name-only -r "$head_sha")" + default_branch="$GITHUB_DEFAULT_BRANCH" + ref_name="$GITHUB_REF_NAME" + + if [[ "$before_sha" =~ ^0+$ ]] || ! git cat-file -e "$before_sha^{commit}" 2>/dev/null; then + if [[ "$ref_name" == "$default_branch" ]]; then + echo "::warning::Push to default branch with invalid before-SHA; failing open." + echo 'changed=true' >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Compare from merge-base with default branch to avoid missing changes on force-push. + if merge_base="$(git merge-base "$head_sha" "origin/$default_branch" 2>/dev/null)"; then + changed_files="$(git diff --name-only "$merge_base" "$head_sha")" + else + echo "::warning::Could not determine merge-base with default branch; failing open." + echo 'changed=true' >> "$GITHUB_OUTPUT" + exit 0 + fi else changed_files="$(git diff --name-only "$before_sha" "$head_sha")" fi @@ -182,6 +198,7 @@ jobs: GITHUB_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} GITHUB_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} GITHUB_EVENT_BEFORE: ${{ github.event.before }} + GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} apiserver-build: runs-on: ubuntu-24.04