Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 0 additions & 141 deletions .github/workflows/apiserver.yml

This file was deleted.

51 changes: 0 additions & 51 deletions .github/workflows/code-reviews.yml

This file was deleted.

203 changes: 203 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Security checklists:
# https://aquilax.ai/blog/github-actions-security-hardening
# https://www.wiz.io/blog/github-actions-security-guide

name: Deploy

on:
workflow_dispatch:
inputs:
run_id:
description: "Test and build workflow run ID to deploy"
required: true
# zizmor: ignore[dangerous-triggers] Only triggered via default branch
workflow_run:
workflows:
- Test and build
branches:
- main
types:
- completed

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}

jobs:
validate:
runs-on: ubuntu-24.04
timeout-minutes: 2
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
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=$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: Test and build
EXPECTED_BRANCH: main
EXPECTED_JOB_NAME: apiserver-build
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;
}

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: ${{ needs.validate.outputs.build_head_sha }}

- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: apiserver
run-id: ${{ needs.validate.outputs.build_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 -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 }}
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"

- 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<<EOF\n${token}\nEOF\n`,
{ flag: 'a' }
);

- name: Deploy
run: >
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 }}
Loading