From 95f57a3e2d002d645d40085029fcca7afb34e224 Mon Sep 17 00:00:00 2001 From: Phil Helm Date: Wed, 22 Jul 2026 20:13:42 +0100 Subject: [PATCH] migrate pipeline from circleci to github actions --- .circleci/config.yml | 183 ------------------ {.circleci => .github}/gpg.private.enc | Bin .github/workflows/main.yaml | 135 +++++++++++++ .github/workflows/pr.yaml | 115 +++++++++++ .rubocop.yml | 1 + Gemfile.lock | 83 ++++---- README.md | 8 +- Rakefile | 157 ++++++++++----- 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 rake_process_manager.gemspec | 3 +- scripts/ci/common/configure-asdf.sh | 18 -- scripts/ci/common/configure-git.sh | 8 - scripts/ci/common/configure-rubygems.sh | 2 - .../ci/common/install-asdf-dependencies.sh | 12 -- scripts/ci/common/install-asdf.sh | 19 -- scripts/ci/common/install-git-crypt.sh | 4 +- scripts/ci/common/install-gpg-key.sh | 19 -- scripts/ci/common/install-slack-deps.sh | 8 - scripts/ci/steps/build.sh | 12 -- scripts/ci/steps/merge-pull-request.sh | 16 -- scripts/ci/steps/prerelease.sh | 16 -- scripts/ci/steps/release.sh | 18 -- scripts/ci/steps/test.sh | 12 -- 26 files changed, 410 insertions(+), 439 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-gpg-key.sh delete mode 100755 scripts/ci/common/install-slack-deps.sh delete mode 100755 scripts/ci/steps/build.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 8c27d28..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,183 +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: arm.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_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:YqbFh6l2eEtwPobkPW303NKNzbvbw4rq6sAKBOH+DWI" - - run: ./scripts/ci/common/configure-rubygems.sh - - configure_tools: - steps: - - configure_build_tools - - configure_secrets_tools - - configure_release_tools - -jobs: - build: - <<: *build_container - steps: - - checkout - - configure_tools - - run: ./scripts/ci/steps/build.sh - - notify - - 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: - - build: - <<: *only_main_and_dependabot - <<: *slack_context - - test: - <<: *only_main_and_dependabot - <<: *slack_context - requires: - - build - - 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..e1edf84 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,135 @@ +name: Main +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Check + run: ./go library: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: 15 + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Test + run: ./go test:unit + - 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 + # Job-level group (here and on release): a run awaiting release approval + # holds the slot, and a workflow-level group would freeze all main CI for + # the approval window. queue: max keeps every queued run; the default + # cancels all but the newest. + concurrency: + group: main + cancel-in-progress: false + queue: max + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Install secrets tools + run: ./scripts/ci/common/install-git-crypt.sh + - name: Unlock git-crypt + run: ./go git_crypt:unlock_with_encrypted_gpg_key + env: + ENCRYPTION_PASSPHRASE: ${{ secrets.ENCRYPTION_PASSPHRASE }} + - name: Configure RubyGems credentials + run: ./scripts/ci/common/configure-rubygems.sh + - name: Set CI git author + run: ./go repository:set_ci_author + - name: Bump version + run: ./go "version:bump[pre]" + - name: Release + run: ./go release + - name: Push commits + run: git push + - name: Push tags + run: git push --tags + - 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: + needs: [prerelease] + runs-on: ubuntu-latest + timeout-minutes: 30 + environment: release + concurrency: + group: main + cancel-in-progress: false + queue: max + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: main + - name: Pull latest main + # Publishes main as of approval time, not the SHA that triggered the + # run; --ff-only so a non-fast-forward fails loudly + run: git pull --ff-only + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Install secrets tools + run: ./scripts/ci/common/install-git-crypt.sh + - name: Unlock git-crypt + run: ./go git_crypt:unlock_with_encrypted_gpg_key + env: + ENCRYPTION_PASSPHRASE: ${{ secrets.ENCRYPTION_PASSPHRASE }} + - name: Configure RubyGems credentials + run: ./scripts/ci/common/configure-rubygems.sh + - name: Set CI git author + run: ./go repository:set_ci_author + - name: Bump version + run: ./go "version:bump[minor]" + - name: Release + run: ./go release + - name: Push commits + run: git push + - name: Push tags + run: git push --tags + - 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..95916d8 --- /dev/null +++ b/.github/workflows/pr.yaml @@ -0,0 +1,115 @@ +name: Pull Request +on: + pull_request: + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Check + run: ./go library: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: 15 + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Test + run: ./go test:unit + - name: Notify Slack + if: ${{ !cancelled() }} + continue-on-error: true + run: ./go "slack:notify[${{ job.status }}]" + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} + + build: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Build + run: ./go library:build + - 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, build] + runs-on: ubuntu-latest + timeout-minutes: 30 + # Same-repo human PRs only: fork and Dependabot PRs carry no secrets, so + # git-crypt unlock / the RubyGems publish would fail. user.login is the + # immutable PR author, not github.actor. + if: >- + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' + # Queue per PR rather than cancel a mid-flight publish + concurrency: + group: pr-prerelease-${{ github.event.pull_request.number }} + cancel-in-progress: false + steps: + - uses: actions/checkout@v4 + - name: Install tools + uses: infrablocks/github-actions/asdf_install@v1 + - name: Install secrets tools + run: ./scripts/ci/common/install-git-crypt.sh + - name: Unlock git-crypt + run: ./go git_crypt:unlock_with_encrypted_gpg_key + env: + ENCRYPTION_PASSPHRASE: ${{ secrets.ENCRYPTION_PASSPHRASE }} + - name: Configure RubyGems credentials + run: ./scripts/ci/common/configure-rubygems.sh + - name: Publish prerelease + # Facts via env, never interpolated + run: ./go "prerelease:publish[$PR_NUMBER,$RUN_NUMBER,$RUN_ATTEMPT]" + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + RUN_NUMBER: ${{ github.run_number }} + RUN_ATTEMPT: ${{ github.run_attempt }} + - 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, build] + runs-on: ubuntu-latest + timeout-minutes: 10 + if: github.event.pull_request.user.login == 'dependabot[bot]' + permissions: + contents: write + pull-requests: write + steps: + - name: Merge pull request + # --match-head-commit fails the merge if a commit landed after checks passed + # [skip ci] stops the merge commit triggering a release build + # PR title via env, never interpolated + run: gh pr merge --merge --match-head-commit "$HEAD_SHA" "$PR_URL" --subject "$PR_TITLE [skip ci]" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + PR_TITLE: ${{ github.event.pull_request.title }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.rubocop.yml b/.rubocop.yml index 9dc0b11..e1718c2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,6 +14,7 @@ Metrics/BlockLength: - context - shared_examples - it + - define_repository_tasks Style/Documentation: Enabled: false diff --git a/Gemfile.lock b/Gemfile.lock index 418102c..a60bd1d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,47 +7,51 @@ PATH GEM remote: https://rubygems.org/ specs: - activesupport (7.2.2.1) + activesupport (8.1.3) base64 - benchmark (>= 0.3) bigdecimal 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) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + uri (>= 0.13.1) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) - base64 (0.2.0) - benchmark (0.4.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.4) - connection_pool (2.4.1) + concurrent-ruby (1.3.8) + connection_pool (3.0.2) diff-lcs (1.6.2) docile (1.4.0) - drb (2.2.1) - excon (0.111.0) + drb (2.2.3) + excon (1.6.0) + logger fakefs (3.0.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) gem-release (2.2.4) git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) hamster (3.0.0) concurrent-ruby (~> 1.0) - i18n (1.14.6) + i18n (1.15.2) concurrent-ruby (~> 1.0) - immutable-struct (2.4.1) + immutable-struct (2.5.0) json (2.12.2) language_server-protocol (3.17.0.5) lino (4.1.0) @@ -55,12 +59,11 @@ GEM hamster (~> 3.0) open4 (~> 1.3) lint_roller (1.1.0) - logger (1.6.2) - minitest (5.25.4) - net-http (0.4.1) - uri - octokit (8.1.0) - base64 + logger (1.7.0) + minitest (5.27.0) + net-http (0.9.1) + uri (>= 0.11.1) + octokit (10.0.0) faraday (>= 1, < 3) sawyer (~> 0.9) open4 (1.3.4) @@ -69,15 +72,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_factory (0.33.0) activesupport (>= 4) rake (~> 13.0) @@ -85,23 +83,26 @@ GEM colored2 (~> 3.1) git (~> 1.13, >= 1.13.2) rake_factory (~> 0.33) - rake_git_crypt (0.2.0) + 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) + rbnacl (7.1.2) + ffi (~> 1) rchardet (1.8.0) regexp_parser (2.10.0) rspec (3.13.1) @@ -143,10 +144,10 @@ GEM lino (>= 4.1) ruby_gpg2 (0.12.0) lino (>= 4.1) - sawyer (0.9.2) + sawyer (0.9.3) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) - securerandom (0.4.0) + securerandom (0.4.1) semantic (1.6.1) simplecov (0.22.0) docile (~> 1.1) @@ -160,10 +161,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 activesupport @@ -171,13 +173,12 @@ DEPENDENCIES fakefs gem-release rake - rake_circle_ci rake_git rake_git_crypt rake_github rake_gpg rake_process_manager! - rake_ssh + rake_slack rspec rubocop rubocop-rake diff --git a/README.md b/README.md index 221c948..ccefa4a 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,16 @@ release a new version, update the version number in `version.rb`, and then run git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). -### 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 "" ``` @@ -53,7 +53,7 @@ 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 4e72022..51843d1 100644 --- a/Rakefile +++ b/Rakefile @@ -1,15 +1,13 @@ # frozen_string_literal: true -require 'rake_circle_ci' require 'rake_git' require 'rake_git_crypt' require 'rake_github' require 'rake_gpg' -require 'rake_ssh' +require 'rake_slack' require 'rspec/core/rake_task' require 'rubocop/rake_task' require 'securerandom' -require 'yaml' task default: %i[ library:fix @@ -59,13 +57,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', @@ -91,7 +82,6 @@ namespace :secrets do desc 'Generate all generatable secrets.' task generate: %w[ encryption:passphrase:generate - keys:deploy:generate keys:gpg:generate ] @@ -115,57 +105,98 @@ namespace :library do desc 'Attempt to automatically fix issues with the library' task fix: [:'rubocop:autocorrect_all'] + + desc 'Build the library' + task :build do + sh 'gem build rake_process_manager.gemspec' + end end namespace :test do RSpec::Core::RakeTask.new(:unit) end -RakeCircleCI.define_project_tasks( - namespace: :circle_ci, - project_slug: 'github/infrablocks/rake_process_manager' -) 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/rake_process_manager' ) 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. 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. An empty or + # whitespace-only GITHUB_TOKEN is treated as absent so an authenticated + # operator falls through to `gh auth token` rather than hitting the raise. + github_token = ENV['GITHUB_TOKEN'].to_s.strip + if github_token.empty? + github_token = begin + `gh auth token` + rescue Errno::ENOENT + '' + end.strip + 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: dependabot runs never reach the passphrase — the + # only pr.yaml job that unlocks git-crypt (prerelease) is guarded to + # same-repo human PRs. 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_path = 'config/secrets/ci/encryption.passphrase' + unless File.exist?(passphrase_path) + raise "Passphrase file not found: #{passphrase_path} — expected a " \ + 'git-crypt-unlocked clone with the CI secrets present' + end + passphrase = File.binread(passphrase_path) + 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 @@ -181,6 +212,38 @@ task :release do sh 'gem release --tag --push' end +namespace :prerelease do + desc 'Build and push a namespaced pre-release to RubyGems ' \ + '(PR CI only; no bump, no tag, no commit, no push)' + task :publish, %i[pr_number run_number run_attempt] do |_, args| + %i[pr_number run_number run_attempt].each do |name| + raise "Missing task argument: #{name}" if args[name].to_s.empty? + end + + version_file = 'lib/rake_process_manager/version.rb' + version_pattern = /(VERSION\s*=\s*')([^']+)(')/ + source = File.read(version_file) + base = source[version_pattern, 2] + raise "Could not read VERSION from #{version_file}" unless base + + version = "#{base}.pr#{args.pr_number}" \ + ".#{args.run_number}.#{args.run_attempt}" + gem_file = "rake_process_manager-#{version}.gem" + begin + File.write(version_file, + source.sub(version_pattern, "\\1#{version}\\3")) + # Build + push directly: `gem release` aborts on the (deliberately) + # uncommitted version rewrite. PR CI must not tag, commit, or push + # (contrast the `release` task). + sh 'gem build rake_process_manager.gemspec' + sh "gem push #{gem_file}" + ensure + File.write(version_file, source) + rm_f gem_file + end + end +end + def bump_version_for(version_type) sh "gem bump --version #{version_type} " \ '&& bundle install ' \ diff --git a/config/secrets/ci/ssh.private b/config/secrets/ci/ssh.private deleted file mode 100644 index bd0475e589121a437bb9ea71d669285b34f9f5d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3265 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 diff --git a/config/secrets/ci/ssh.public b/config/secrets/ci/ssh.public deleted file mode 100644 index 9e72dfe8d8d6462b3a538680fd91694b0f015646..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 773 zcmV+g1N!^`M@dveQdv+`03W!ltL#5VCB}ND=K62o_`><`&HS>arw8 z-#N(Y!r3OY2APivwK!O~HtQf#{3vDJ)Qxn2xKM*9r?oEQJE#|MiuaaXXn;;P+7BNw z8!vAx*ZMWXYK+7uUUP!FeeW%K)pJYP9V>|e&Xzb*Egxh6WC*t{gboT^4T&IE9&F1K z*-)6|HBT0NzI4rNiC#ZxGg_WsyA~6Eg^a^K-e=^`pO5C%e`qkuEe$6At+BqTnO)kx zO@OFFJ*NGJW$%yW>~63FCAyj`VQ72zodWB4mF848n4r{~kV@6+Y4BT!VytYEtX%u0 zNw#6!*hz)nQBs-Mg$U(3ihUbGMk0;e7Y=2Y7SLHHJq2TWaO+92@d78V&^U3jIW>_U zIQPd5z0pTe@=)$vK(cxH0F$^a-lTSDipp$xExM27i+H`ETC*VdnY@vNU5hG>O;i7L z{?IZD+MP_b#vRT=c9a~{?1;psm_Kc`{8>4f9XXv6Bw1)S9A zkp@E^eHSNxTc0Eo3xi(xq0!|jUmok+e}5QH5gj}bcnnUtm}rrBnw4cCGY7O37e9zx z;XOO8OTv%Sfah2CR98>sm$Pn+w4ZGuZTQd_`EZ4(3rqdGxrC92eC^@kp&%;!1as=ajW7=aGXo98ekh7Cd52`7JQc60H|FMfGk+5_( DQ%HDT diff --git a/config/secrets/circle_ci/config.yaml b/config/secrets/circle_ci/config.yaml deleted file mode 100644 index f781ec5acc2083fd2775d579e8fc70503abf7b37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 zcmV-;0EhnoM@dveQdv+`0PLTch6eho0ns)@Q3BWy#MPSQfiX=%N_zeg+AuE!>t`2}MlN;@mPc@YpwhyPtew>U%unXP_x-2Oy{Q12fA?_&?faitjJ zI;otTU3%xy-F&3Ko-yBn(YJgH;i#e*E06(rHx}oC6c4a`)<{MinbXnh`FjayQSu|b Gr&!!Oc}K$l diff --git a/rake_process_manager.gemspec b/rake_process_manager.gemspec index 82dd330..b6fbc71 100644 --- a/rake_process_manager.gemspec +++ b/rake_process_manager.gemspec @@ -43,12 +43,11 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'fakefs' spec.add_development_dependency 'gem-release' spec.add_development_dependency 'rake' - spec.add_development_dependency 'rake_circle_ci' spec.add_development_dependency 'rake_git' spec.add_development_dependency 'rake_git_crypt' spec.add_development_dependency 'rake_github' spec.add_development_dependency 'rake_gpg' - spec.add_development_dependency 'rake_ssh' + spec.add_development_dependency 'rake_slack' spec.add_development_dependency 'rspec' spec.add_development_dependency 'rubocop' spec.add_development_dependency 'rubocop-rake' 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/configure-rubygems.sh b/scripts/ci/common/configure-rubygems.sh index 53ba65a..079e256 100755 --- a/scripts/ci/common/configure-rubygems.sh +++ b/scripts/ci/common/configure-rubygems.sh @@ -9,8 +9,6 @@ PROJECT_DIR="$( cd "$SCRIPT_DIR/../../.." && pwd )" cd "$PROJECT_DIR" -git crypt unlock - mkdir -p ~/.gem cp config/secrets/rubygems/credentials ~/.gem/credentials chmod 0600 ~/.gem/credentials 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-git-crypt.sh b/scripts/ci/common/install-git-crypt.sh index 236276d..4de784c 100755 --- a/scripts/ci/common/install-git-crypt.sh +++ b/scripts/ci/common/install-git-crypt.sh @@ -4,5 +4,5 @@ set -e set -o pipefail -apt-get update -apt-get install -y --no-install-recommends git ssh git-crypt +sudo apt-get update +sudo apt-get install -y --no-install-recommends git-crypt gnupg 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/build.sh b/scripts/ci/steps/build.sh deleted file mode 100755 index d89621c..0000000 --- a/scripts/ci/steps/build.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" - -./go library:check 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 c31cf41..0000000 --- a/scripts/ci/steps/prerelease.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" - -./go version:bump[pre] -./go release - -git status -git push diff --git a/scripts/ci/steps/release.sh b/scripts/ci/steps/release.sh deleted file mode 100755 index f1f7564..0000000 --- a/scripts/ci/steps/release.sh +++ /dev/null @@ -1,18 +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 pull - -./go version:bump[minor] -./go release - -git status -git push diff --git a/scripts/ci/steps/test.sh b/scripts/ci/steps/test.sh deleted file mode 100755 index d27b272..0000000 --- a/scripts/ci/steps/test.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" - -./go spec