From 2bb1d7f2da149a1f7b8c5fa39e93d5c52417361f Mon Sep 17 00:00:00 2001 From: Phil Helm Date: Thu, 16 Jul 2026 17:50:51 +0100 Subject: [PATCH] migrate pipeline from circleci to github actions --- .circleci/config.yml | 176 ----------------- {.circleci => .github}/gpg.private.enc | Bin .github/workflows/main.yaml | 178 ++++++++++++++++++ .github/workflows/pr.yaml | 68 +++++++ .tool-versions | 2 +- Gemfile | 4 +- Gemfile.lock | 88 +++++---- README.md | 8 +- Rakefile | 122 +++++++----- config/secrets/ci/ssh.private | Bin 3265 -> 0 bytes config/secrets/ci/ssh.public | Bin 773 -> 0 bytes config/secrets/circle_ci/config.yaml | Bin 120 -> 0 bytes config/secrets/github/config.yaml | Bin 152 -> 0 bytes scripts/ci/common/configure-asdf.sh | 18 -- scripts/ci/common/configure-git.sh | 8 - .../ci/common/install-asdf-dependencies.sh | 12 -- scripts/ci/common/install-asdf.sh | 19 -- scripts/ci/common/install-docker-compose.sh | 8 - scripts/ci/common/install-docker.sh | 8 - scripts/ci/common/install-git-crypt.sh | 8 - scripts/ci/common/install-gpg-key.sh | 19 -- scripts/ci/common/install-slack-deps.sh | 8 - scripts/ci/steps/merge-pull-request.sh | 16 -- scripts/ci/steps/prerelease.sh | 18 -- scripts/ci/steps/release.sh | 19 -- scripts/ci/steps/test.sh | 17 -- 26 files changed, 373 insertions(+), 451 deletions(-) delete mode 100644 .circleci/config.yml rename {.circleci => .github}/gpg.private.enc (100%) create mode 100644 .github/workflows/main.yaml create mode 100644 .github/workflows/pr.yaml delete mode 100644 config/secrets/ci/ssh.private delete mode 100644 config/secrets/ci/ssh.public delete mode 100644 config/secrets/circle_ci/config.yaml delete mode 100644 config/secrets/github/config.yaml delete mode 100755 scripts/ci/common/configure-asdf.sh delete mode 100755 scripts/ci/common/configure-git.sh delete mode 100755 scripts/ci/common/install-asdf-dependencies.sh delete mode 100755 scripts/ci/common/install-asdf.sh delete mode 100755 scripts/ci/common/install-docker-compose.sh delete mode 100755 scripts/ci/common/install-docker.sh delete mode 100755 scripts/ci/common/install-git-crypt.sh delete mode 100755 scripts/ci/common/install-gpg-key.sh delete mode 100755 scripts/ci/common/install-slack-deps.sh delete mode 100755 scripts/ci/steps/merge-pull-request.sh delete mode 100755 scripts/ci/steps/prerelease.sh delete mode 100755 scripts/ci/steps/release.sh delete mode 100755 scripts/ci/steps/test.sh diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 08652b6..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,176 +0,0 @@ -version: 2.1 - -orbs: - slack: circleci/slack@4.13.2 - -base_container: &base_container - image: buildpack-deps:buster - -build_container: &build_container - resource_class: medium - docker: - - <<: *base_container - -slack_context: &slack_context - context: - - slack - -only_main: &only_main - filters: - branches: - only: - - main - -only_dependabot: &only_dependabot - filters: - branches: - only: - - /^dependabot.*/ - -only_main_and_dependabot: &only_main_and_dependabot - filters: - branches: - only: - - main - - /^dependabot.*/ - -commands: - notify: - steps: - - when: - condition: - matches: - pattern: "^dependabot.*" - value: << pipeline.git.branch >> - steps: - - slack/notify: - event: fail - channel: builds-dependabot - template: SLACK_FAILURE_NOTIFICATION - - slack/notify: - event: pass - channel: builds-dependabot - template: SLACK_SUCCESS_NOTIFICATION - - when: - condition: - matches: - pattern: "^(?!dependabot).*" - value: << pipeline.git.branch >> - steps: - - slack/notify: - event: fail - channel: dev - template: SLACK_FAILURE_NOTIFICATION - - slack/notify: - event: pass - channel: builds - template: SLACK_SUCCESS_NOTIFICATION - - configure_container_tools: - steps: - - run: ./scripts/ci/common/install-docker.sh - - run: ./scripts/ci/common/install-docker-compose.sh - - setup_remote_docker - - configure_build_tools: - steps: - - run: ./scripts/ci/common/install-slack-deps.sh - - restore_cache: - keys: - - asdf-dependencies-{{ arch }}-v2-{{ checksum ".tool-versions" }} - - asdf-dependencies-{{ arch }}-v2- - - run: ./scripts/ci/common/install-asdf.sh - - run: ./scripts/ci/common/configure-asdf.sh - - run: ./scripts/ci/common/install-asdf-dependencies.sh - - save_cache: - key: asdf-dependencies-{{ arch }}-v2-{{ checksum ".tool-versions" }} - paths: - - ~/.asdf - - configure_secrets_tools: - steps: - - run: ./scripts/ci/common/install-git-crypt.sh - - run: ./scripts/ci/common/install-gpg-key.sh - - run: ./scripts/ci/common/configure-git.sh - - configure_release_tools: - steps: - - add_ssh_keys: - fingerprints: - - "SHA256:KkZfw+t+fU7aGkg8QL0MA8YrZ9z7sCTcHRi2makSZBE" - - configure_tools: - steps: - - configure_container_tools - - configure_build_tools - - configure_secrets_tools - - configure_release_tools - -jobs: - test: - <<: *build_container - steps: - - checkout - - configure_tools - - run: ./scripts/ci/steps/test.sh - - notify - - prerelease: - <<: *build_container - steps: - - checkout - - configure_tools - - run: ./scripts/ci/steps/prerelease.sh - - notify - - release: - <<: *build_container - steps: - - checkout - - configure_tools - - run: ./scripts/ci/steps/release.sh - - notify - - merge_pull_request: - <<: *build_container - steps: - - checkout - - configure_tools - - run: ./scripts/ci/steps/merge-pull-request.sh - - notify - -workflows: - version: 2 - pipeline: - jobs: - - test: - <<: *only_main_and_dependabot - <<: *slack_context - - merge_pull_request: - <<: *only_dependabot - <<: *slack_context - requires: - - test - - prerelease: - <<: *only_main - <<: *slack_context - requires: - - test - - slack/on-hold: - <<: *only_main - <<: *slack_context - requires: - - prerelease - channel: release - template: SLACK_ON_HOLD_NOTIFICATION - - hold: - <<: *only_main - type: approval - requires: - - prerelease - - slack/on-hold - - release: - <<: *only_main - <<: *slack_context - requires: - - hold diff --git a/.circleci/gpg.private.enc b/.github/gpg.private.enc similarity index 100% rename from .circleci/gpg.private.enc rename to .github/gpg.private.enc diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..5f13ec8 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,178 @@ +name: Main +on: + push: + branches: + - main + +permissions: + contents: read + +# Concurrency is JOB-level, on prerelease + release only (shared group), NOT +# workflow-level: a run waiting on the release approval counts as the group's +# in-progress occupant, so a workflow-level group would freeze ALL main CI +# (check/test/prerelease of later pushes) for up to the 30-day approval +# window. The approval itself lives on the slot-free release-gate job, so a +# pending approval holds no slot. queue: max keeps every queued publish (the +# default keeps only the newest pending) so close-together merges serialise +# FIFO on tag operations instead of cancelling each other. +# 'queue: max' — GA 2026-05-07: https://github.blog/changelog/2026-05-07-github-actions-concurrency-groups-now-allow-larger-queues/ +# Gate/slot split pattern: https://github.com/orgs/community/discussions/17401 + +jobs: + skip-ci-check: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + should_skip_ci: ${{ steps.skip_ci_check.outputs.should_skip_ci }} + steps: + - id: skip_ci_check + env: + HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + run: | + if [[ "$HEAD_COMMIT_MESSAGE" == *"[no ci]"* ]] \ + || [[ "$HEAD_COMMIT_MESSAGE" == *"[skip ci]"* ]] \ + || [[ "$HEAD_COMMIT_MESSAGE" == *"[ci skip]"* ]]; then + echo "should_skip_ci=true" >> "$GITHUB_OUTPUT" + else + echo "should_skip_ci=false" >> "$GITHUB_OUTPUT" + fi + + check: + needs: [skip-ci-check] + if: needs.skip-ci-check.outputs.should_skip_ci != 'true' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Check + run: ./go test:code:check + - name: Notify Slack + if: ${{ !cancelled() }} + continue-on-error: true + run: ./go "slack:notify[${{ job.status }}]" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + test: + needs: [skip-ci-check] + if: needs.skip-ci-check.outputs.should_skip_ci != 'true' + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Ensure docker-compose is available + # Pinned + checksum-verified: this runs as root on runners that later + # hold secrets; the checksum is from the release's own .sha256 asset. + run: | + if ! command -v docker-compose >/dev/null 2>&1; then + sudo curl -fsSL \ + "https://github.com/docker/compose/releases/download/v5.3.1/docker-compose-linux-x86_64" \ + -o /usr/local/bin/docker-compose + echo "f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959 /usr/local/bin/docker-compose" \ + | sha256sum -c - + sudo chmod +x /usr/local/bin/docker-compose + fi + - name: Test + run: ./go test:integration + - name: Notify Slack + if: ${{ !cancelled() }} + continue-on-error: true + run: ./go "slack:notify[${{ job.status }}]" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + prerelease: + needs: [check, test] + runs-on: ubuntu-latest + timeout-minutes: 30 + concurrency: + group: main-tags + cancel-in-progress: false + queue: max + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Install secrets tools + run: sudo apt-get update && sudo apt-get install -y git-crypt gnupg + - name: Unlock git-crypt + run: ./go git_crypt:unlock_with_encrypted_gpg_key + env: + ENCRYPTION_PASSPHRASE: ${{ secrets.ENCRYPTION_PASSPHRASE }} + - name: Set CI git author + run: ./go repository:set_ci_author + - name: Prerelease + run: ./go "version:bump[rc]" && ./go image:publish + - name: Notify Slack of release hold + continue-on-error: true + run: ./go "slack:notify[success,on_hold]" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + - name: Notify Slack + if: ${{ !cancelled() }} + continue-on-error: true + run: ./go "slack:notify[${{ job.status }}]" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + release-gate: + # Approval-only job: carries the environment but NO concurrency group, so + # a pending approval never occupies the main-tags slot. Multiple pushes + # can await approval independently. + needs: [prerelease] + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: release + steps: + - name: Release approved + run: "true" + + release: + needs: [release-gate] + runs-on: ubuntu-latest + timeout-minutes: 30 + concurrency: + group: main-tags + cancel-in-progress: false + queue: max + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + - name: Pull latest main + # Approval can land long after the run starts; release publishes main + # as of approval time, not the tested SHA — parity with the old + # release.sh, which also pulled. prerelease.sh never pulled, so the + # prerelease job deliberately has no pull. + run: git pull + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Install secrets tools + run: sudo apt-get update && sudo apt-get install -y git-crypt gnupg + - name: Unlock git-crypt + run: ./go git_crypt:unlock_with_encrypted_gpg_key + env: + ENCRYPTION_PASSPHRASE: ${{ secrets.ENCRYPTION_PASSPHRASE }} + - name: Set CI git author + run: ./go repository:set_ci_author + - name: Release + run: ./go version:release && ./go image:publish + - name: Notify Slack + if: ${{ !cancelled() }} + continue-on-error: true + run: ./go "slack:notify[${{ job.status }}]" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml new file mode 100644 index 0000000..d04f24c --- /dev/null +++ b/.github/workflows/pr.yaml @@ -0,0 +1,68 @@ +name: Pull Request +on: + pull_request: + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Check + run: ./go test:code:check + - name: Notify Slack + if: ${{ !cancelled() }} + continue-on-error: true + run: ./go "slack:notify[${{ job.status }}]" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + test: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Ensure docker-compose is available + # Pinned + checksum-verified: this runs as root on runners that later + # hold secrets; the checksum is from the release's own .sha256 asset. + run: | + if ! command -v docker-compose >/dev/null 2>&1; then + sudo curl -fsSL \ + "https://github.com/docker/compose/releases/download/v5.3.1/docker-compose-linux-x86_64" \ + -o /usr/local/bin/docker-compose + echo "f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959 /usr/local/bin/docker-compose" \ + | sha256sum -c - + sudo chmod +x /usr/local/bin/docker-compose + fi + - name: Test + run: ./go test:integration + - name: Notify Slack + if: ${{ !cancelled() }} + continue-on-error: true + run: ./go "slack:notify[${{ job.status }}]" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + merge-pull-request: + needs: [check, test] + runs-on: ubuntu-latest + timeout-minutes: 10 + if: github.actor == 'dependabot[bot]' + permissions: + contents: write + pull-requests: write + steps: + - name: Merge pull request + run: gh pr merge --merge "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.tool-versions b/.tool-versions index e7f4c11..5bb6c9e 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 3.1.1 +ruby 3.2.11 diff --git a/Gemfile b/Gemfile index 057c973..67a7c53 100644 --- a/Gemfile +++ b/Gemfile @@ -10,13 +10,13 @@ gem 'nokogiri' gem 'octopoller' gem 'os' gem 'rake' -gem 'rake_circle_ci' gem 'rake_docker' +gem 'rake_factory' gem 'rake_git' gem 'rake_git_crypt' gem 'rake_github' gem 'rake_gpg' -gem 'rake_ssh' +gem 'rake_slack' gem 'rake_terraform' gem 'rspec' gem 'rubocop' diff --git a/Gemfile.lock b/Gemfile.lock index e236fa9..d208d0f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,18 +1,21 @@ GEM remote: https://rubygems.org/ specs: - activesupport (7.1.3.4) + activesupport (8.1.3) base64 bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) aws-eventstream (1.4.0) aws-partitions (1.1110.0) @@ -1637,12 +1640,12 @@ GEM aws-sigv2 (1.3.0) aws-sigv4 (1.12.0) aws-eventstream (~> 1, >= 1.0.2) - base64 (0.2.0) - bigdecimal (3.1.8) + base64 (0.3.0) + bigdecimal (4.1.2) childprocess (5.0.0) colored2 (3.1.2) - concurrent-ruby (1.3.3) - connection_pool (2.4.1) + concurrent-ruby (1.3.7) + connection_pool (3.0.2) diff-lcs (1.6.2) docker-api (2.4.0) excon (>= 0.64.0) @@ -1650,21 +1653,24 @@ GEM dotenv (3.1.8) down (5.4.2) addressable (~> 2.8) - drb (2.2.1) + drb (2.2.3) excon (0.112.0) - faraday (2.10.0) - faraday-net_http (>= 2.0, < 3.2) + faraday (2.14.3) + faraday-net_http (>= 2.0, < 3.5) + json logger - faraday-net_http (3.1.1) - net-http + faraday-net_http (3.4.4) + net-http (~> 0.5) + ffi (1.17.4) + ffi (1.17.4-x86_64-linux-gnu) git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) hamster (3.0.0) concurrent-ruby (~> 1.0) - i18n (1.14.5) + i18n (1.15.2) concurrent-ruby (~> 1.0) - immutable-struct (2.4.1) + immutable-struct (2.5.0) jmespath (1.6.2) json (2.12.2) language_server-protocol (3.17.0.5) @@ -1673,14 +1679,13 @@ GEM hamster (~> 3.0) open4 (~> 1.3) lint_roller (1.1.0) - logger (1.6.0) + logger (1.7.0) mini_portile2 (2.8.8) minitar (0.9) - minitest (5.24.1) + minitest (5.27.0) multi_json (1.15.0) - mutex_m (0.2.0) - net-http (0.4.1) - uri + net-http (0.9.1) + uri (>= 0.11.1) net-scp (4.1.0) net-ssh (>= 2.6.5, < 8.0.0) net-ssh (7.3.0) @@ -1688,8 +1693,9 @@ GEM nokogiri (1.18.8) mini_portile2 (~> 2.8.2) racc (~> 1.4) - octokit (8.1.0) - base64 + nokogiri (1.18.8-x86_64-linux-gnu) + racc (~> 1.4) + octokit (10.0.0) faraday (>= 1, < 3) sawyer (~> 0.9) octopoller (0.3.1) @@ -1700,15 +1706,10 @@ GEM ast (~> 2.4.1) racc prism (1.4.0) - public_suffix (6.0.1) + public_suffix (7.0.5) racc (1.8.1) rainbow (3.1.1) - rake (13.3.0) - rake_circle_ci (0.13.0) - colored2 (~> 3.1) - excon (~> 0.72) - rake_factory (~> 0.33) - sshkey (~> 2.0) + rake (13.4.2) rake_dependencies (3.9.0) down (~> 5.3) hamster (~> 3.0) @@ -1727,28 +1728,31 @@ GEM colored2 (~> 3.1) git (~> 1.13, >= 1.13.2) rake_factory (~> 0.33) - rake_git_crypt (0.3.0.pre.2) + rake_git_crypt (0.4.0) colored2 (~> 3.1) + lino (~> 4.1) rake_factory (~> 0.33) ruby_git_crypt (~> 0.1) ruby_gpg2 (~> 0.12) - rake_github (0.15.0) + rake_github (0.17.0) colored2 (~> 3.1) - octokit (>= 4.16, < 9.0) + octokit (>= 7.0, < 11.0) rake_factory (~> 0.33) + rbnacl (~> 7.1) sshkey (~> 2.0) rake_gpg (0.20.0) rake_factory (~> 0.33) ruby_gpg2 (~> 0.12) - rake_ssh (0.12.0) - colored2 (~> 3.1) + rake_slack (0.3.0) + excon (>= 0.72, < 2.0) rake_factory (~> 0.33) - sshkey (~> 2.0) rake_terraform (1.24.0) colored2 (~> 3.1) rake_dependencies (~> 3.7) rake_factory (~> 0.33) ruby-terraform (~> 1.8) + rbnacl (7.1.2) + ffi (~> 1) rchardet (1.8.0) regexp_parser (2.10.0) rspec (3.13.1) @@ -1797,9 +1801,10 @@ GEM ruby_gpg2 (0.12.0) lino (>= 4.1) rubyzip (2.3.2) - sawyer (0.9.2) + sawyer (0.9.3) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) + securerandom (0.4.1) semantic (1.6.1) serverspec (2.43.0) multi_json @@ -1819,10 +1824,11 @@ GEM unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - uri (0.13.2) + uri (1.1.1) PLATFORMS ruby + x86_64-linux DEPENDENCIES aws-sdk @@ -1833,13 +1839,13 @@ DEPENDENCIES octopoller os rake - rake_circle_ci rake_docker + rake_factory rake_git rake_git_crypt rake_github rake_gpg - rake_ssh + rake_slack rake_terraform rspec rubocop diff --git a/README.md b/README.md index ab45074..dafb2bf 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ ## Development -### Managing CircleCI keys +### Managing CI keys -To encrypt a GPG key for use by CircleCI: +To encrypt a GPG key for use by CI: ```bash openssl aes-256-cbc \ -e \ -md sha1 \ -in ./config/secrets/ci/gpg.private \ - -out ./.circleci/gpg.private.enc \ + -out ./.github/gpg.private.enc \ -k "" ``` @@ -21,6 +21,6 @@ To check decryption is working correctly: openssl aes-256-cbc \ -d \ -md sha1 \ - -in ./.circleci/gpg.private.enc \ + -in ./.github/gpg.private.enc \ -k "" ``` diff --git a/Rakefile b/Rakefile index a81a344..f88baec 100644 --- a/Rakefile +++ b/Rakefile @@ -3,13 +3,13 @@ require 'git' require 'os' require 'pathname' -require 'rake_circle_ci' require 'rake_docker' +require 'rake_factory/kernel_extensions' require 'rake_git' require 'rake_git_crypt' require 'rake_github' require 'rake_gpg' -require 'rake_ssh' +require 'rake_slack' require 'rake_terraform' require 'rspec/core/rake_task' require 'rubocop/rake_task' @@ -86,13 +86,6 @@ namespace :encryption do end namespace :keys do - namespace :deploy do - RakeSSH.define_key_tasks( - path: 'config/secrets/ci/', - comment: 'maintainers@infrablocks.io' - ) - end - namespace :gpg do RakeGPG.define_generate_key_task( output_directory: 'config/secrets/ci', @@ -118,7 +111,6 @@ namespace :secrets do desc 'Generate all generatable secrets.' task generate: %w[ encryption:passphrase:generate - keys:deploy:generate keys:gpg:generate ] @@ -142,51 +134,78 @@ namespace :library do task fix: [:'rubocop:autocorrect_all'] end -RakeCircleCI.define_project_tasks( - namespace: :circle_ci, - project_slug: 'github/infrablocks/docker-alertmanager-aws' -) do |t| - circle_ci_config = - YAML.load_file('config/secrets/circle_ci/config.yaml') - - t.api_token = circle_ci_config['circle_ci_api_token'] - t.environment_variables = { - ENCRYPTION_PASSPHRASE: - File.read('config/secrets/ci/encryption.passphrase') - .chomp - } - t.checkout_keys = [] - t.ssh_keys = [ - { - hostname: 'github.com', - private_key: File.read('config/secrets/ci/ssh.private') - } - ] -end - RakeGithub.define_repository_tasks( namespace: :github, repository: 'infrablocks/docker-alertmanager-aws' ) do |t| - github_config = - YAML.load_file('config/secrets/github/config.yaml') - - t.access_token = github_config['github_personal_access_token'] - t.deploy_keys = [ - { - title: 'CircleCI', - public_key: File.read('config/secrets/ci/ssh.public') - } + # Operator's ambient auth — the stored PAT is gone (deliberate, per D7 in + # the family migration plan). Resolve once and fail fast: a missing, + # unauthenticated, or absent gh yields an empty string, which would + # otherwise surface later as an opaque Octokit 401. + github_token = ENV.fetch('GITHUB_TOKEN') do + `gh auth token`.strip + rescue Errno::ENOENT + '' + end + if github_token.empty? + raise 'No GitHub token available: set GITHUB_TOKEN or run `gh auth login`' + end + + t.access_token = github_token + + # Actions store only: nothing in pr.yaml unlocks git-crypt, so dependabot + # runs never need the passphrase. Guard against a locked clone: without + # it, File.read returns git-crypt ciphertext and github:secrets:ensure + # silently uploads garbage that only surfaces much later as an opaque + # GPG unlock failure in the release job. + passphrase = File.binread('config/secrets/ci/encryption.passphrase') + if passphrase.start_with?("\x00GITCRYPT") + raise 'encryption.passphrase is git-crypt ciphertext — unlock the ' \ + 'clone before provisioning' + end + + t.secrets = [ + { name: 'ENCRYPTION_PASSPHRASE', value: passphrase.chomp } ] + t.environments = [ + { name: 'release', + reviewers: [{ team: 'maintainers' }] } + ] +end + +namespace :slack do + RakeSlack.define_notification_tasks do |t| + t.bot_token = ENV.fetch('SLACK_BOT_TOKEN', nil) + t.routing_rules = [ + { when: { type: 'on_hold' }, + channel: 'C038EDCRSQJ', format: :on_hold }, # release + { when: { actor: 'dependabot[bot]', outcome: 'success' }, + channel: 'C03N711HVDG', format: :success }, # builds-dependabot + { when: { actor: 'dependabot[bot]' }, + channel: 'C03N711HVDG', format: :failure }, # builds-dependabot + { when: { outcome: 'success' }, + channel: 'C023XUE76GH', format: :success }, # builds + # Failures go to builds, not team-dev (org default), to keep noise + # out of a popular channel while this pipeline beds in. + { when: {}, + channel: 'C023XUE76GH', format: :failure } # builds + ] + end +end + +namespace :repository do + desc 'Set the git author for CI' + task :set_ci_author do + sh 'git config --global user.name "InfraBlocks CI"' + sh 'git config --global user.email "ci@infrablocks.io"' + end end namespace :pipeline do - desc 'Prepare CircleCI Pipeline' + desc 'Prepare GitHub Actions pipeline' task prepare: %i[ - circle_ci:env_vars:ensure - circle_ci:checkout_keys:ensure - circle_ci:ssh_keys:ensure - github:deploy_keys:ensure + github:secrets:ensure + github:environments:ensure ] end @@ -205,9 +224,14 @@ namespace :image do t.repository_name = 'alertmanager-aws' t.repository_url = 'infrablocks/alertmanager-aws' - t.credentials = YAML.load_file( - 'config/secrets/dockerhub/credentials.yaml' - ) + t.credentials = dynamic do + if File.binread('config/secrets/dockerhub/credentials.yaml') + .start_with?("\x00GITCRYPT") + nil # tree locked: skip Docker Hub auth (base image is public) + else + YAML.load_file('config/secrets/dockerhub/credentials.yaml') + end + end t.platform = 'linux/amd64' diff --git a/config/secrets/ci/ssh.private b/config/secrets/ci/ssh.private deleted file mode 100644 index 21d5517aa09b8c539c919a058ae8ab447a62a1fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3265 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 DwX}SW diff --git a/config/secrets/circle_ci/config.yaml b/config/secrets/circle_ci/config.yaml deleted file mode 100644 index 3c893a4cf0b1341954b2c06b82698c5618525b73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 zcmV-;0EhnoM@dveQdv+`0EMU~tv*(zknzaDZ(xkbQYJkph+QCwOl* GtG*V8Axu92 diff --git a/scripts/ci/common/configure-asdf.sh b/scripts/ci/common/configure-asdf.sh deleted file mode 100755 index b71847a..0000000 --- a/scripts/ci/common/configure-asdf.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -function ensure-asdf-plugin() { - local name="$1" - local repo="$2" - - if ! asdf plugin list | grep -q "$name"; then - asdf plugin add "$name" "$repo" - fi -} - -ensure-asdf-plugin "ruby" "https://github.com/asdf-vm/asdf-ruby.git" -ensure-asdf-plugin "java" "https://github.com/halcyon/asdf-java.git" -ensure-asdf-plugin "golang" "https://github.com/asdf-community/asdf-golang.git" diff --git a/scripts/ci/common/configure-git.sh b/scripts/ci/common/configure-git.sh deleted file mode 100755 index cb11b10..0000000 --- a/scripts/ci/common/configure-git.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -git config --global user.email "circleci@infrablocks.io" -git config --global user.name "Circle CI" diff --git a/scripts/ci/common/install-asdf-dependencies.sh b/scripts/ci/common/install-asdf-dependencies.sh deleted file mode 100755 index cb01457..0000000 --- a/scripts/ci/common/install-asdf-dependencies.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" - -cd "$PROJECT_DIR" - -asdf install diff --git a/scripts/ci/common/install-asdf.sh b/scripts/ci/common/install-asdf.sh deleted file mode 100755 index 8c1ce8b..0000000 --- a/scripts/ci/common/install-asdf.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -apt-get update -apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - git - -if [ ! -f "$HOME/.asdf/asdf.sh" ]; then - echo "Installing asdf..." - git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.12.0 -fi - -# shellcheck disable=SC2016 -echo '. "$HOME/.asdf/asdf.sh"' >> "$BASH_ENV" diff --git a/scripts/ci/common/install-docker-compose.sh b/scripts/ci/common/install-docker-compose.sh deleted file mode 100755 index 7501c9b..0000000 --- a/scripts/ci/common/install-docker-compose.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -apt-get update -apt-get install -y --no-install-recommends docker-compose diff --git a/scripts/ci/common/install-docker.sh b/scripts/ci/common/install-docker.sh deleted file mode 100755 index 1010fac..0000000 --- a/scripts/ci/common/install-docker.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -apt-get update -apt-get install -y --no-install-recommends docker diff --git a/scripts/ci/common/install-git-crypt.sh b/scripts/ci/common/install-git-crypt.sh deleted file mode 100755 index 236276d..0000000 --- a/scripts/ci/common/install-git-crypt.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -apt-get update -apt-get install -y --no-install-recommends git ssh git-crypt diff --git a/scripts/ci/common/install-gpg-key.sh b/scripts/ci/common/install-gpg-key.sh deleted file mode 100755 index 52d10c2..0000000 --- a/scripts/ci/common/install-gpg-key.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" - -cd "$PROJECT_DIR" - -set +e -openssl version -openssl aes-256-cbc \ - -d \ - -md sha1 \ - -in ./.circleci/gpg.private.enc \ - -k "${ENCRYPTION_PASSPHRASE}" | gpg --import - -set -e diff --git a/scripts/ci/common/install-slack-deps.sh b/scripts/ci/common/install-slack-deps.sh deleted file mode 100755 index 70abb6b..0000000 --- a/scripts/ci/common/install-slack-deps.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -apt-get update -apt-get install -y --no-install-recommends curl jq diff --git a/scripts/ci/steps/merge-pull-request.sh b/scripts/ci/steps/merge-pull-request.sh deleted file mode 100755 index 63be3b1..0000000 --- a/scripts/ci/steps/merge-pull-request.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" - -cd "$PROJECT_DIR" - -git-crypt unlock - -CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) - -./go github:pull_requests:merge["$CURRENT_BRANCH","%s [skip ci]"] diff --git a/scripts/ci/steps/prerelease.sh b/scripts/ci/steps/prerelease.sh deleted file mode 100755 index 92ec529..0000000 --- a/scripts/ci/steps/prerelease.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -export TERM=xterm - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" - -cd "$PROJECT_DIR" - -git crypt unlock - -./go version:bump[rc] - -./go image:publish diff --git a/scripts/ci/steps/release.sh b/scripts/ci/steps/release.sh deleted file mode 100755 index ff823b0..0000000 --- a/scripts/ci/steps/release.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -export TERM=xterm - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" - -cd "$PROJECT_DIR" - -git crypt unlock -git pull - -./go version:release - -./go image:publish diff --git a/scripts/ci/steps/test.sh b/scripts/ci/steps/test.sh deleted file mode 100755 index 44d2d32..0000000 --- a/scripts/ci/steps/test.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -[ -n "$DEBUG" ] && set -x -set -e -set -o pipefail - -export TERM=xterm - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" - -cd "$PROJECT_DIR" - -git crypt unlock - -./go test:code:check -./go test:integration