open-vault/.github/workflows/ci.yml
hc-github-team-secure-vault-core 59cbdcda39
[QT-589] Use the go module cache between CI and build (#21764) (#21790)
In order to reliably store Go test times in the Github Actions cache we
need to reduce our cache thrashing by not using more than 10gb over all
of our caches. This change reduces our cache usage significantly by
sharing Go module cache between our Go CI workflows and our build
workflows. We lose our per-builder cache which will result in a bit of
performance hit, but we'll enable better automatic rebalancing of our CI
workflows. Overall we should see a per branch reduction in cache sizes
from ~17gb to ~850mb.

Some preliminary investigation into this new strategy:

Prior build workflow strategy on a cache miss:
  Download modules: ~20s
  Build Vault: ~40s
  Upload cache: ~30s
  Total: ~1m30s

Prior build workflow strategy on a cache hit:
  Download and decompress modules and build cache: ~12s
  Build Vault: ~15s
  Total: ~28s

New build workflow strategy on a cache miss:
  Download modules: ~20
  Build Vault: ~40s
  Upload cache: ~6s
  Total: ~1m6s

New build workflow strategy on a cache hit:
  Download and decompress modules: ~3s
  Build Vault: ~40s
  Total: ~43s

Expected time if we used no Go caching:
  Download modules: ~20
  Build Vault: ~40s
  Total: ~1m

Signed-off-by: Ryan Cragun <me@ryan.ec>
Co-authored-by: Ryan Cragun <me@ryan.ec>
2023-07-12 19:26:00 +00:00

417 lines
18 KiB
YAML

name: CI
on:
pull_request:
# The default types for pull_request are [ opened, synchronize, reopened ].
# This is insufficient for our needs, since we're skipping stuff on PRs in
# draft mode. By adding the ready_for_review type, when a draft pr is marked
# ready, we run everything, including the stuff we'd have skipped up until now.
types: [opened, synchronize, reopened, ready_for_review]
push:
branches:
- main
- release/**
workflow_dispatch:
concurrency:
group: ${{ github.head_ref || github.run_id }}-ci
cancel-in-progress: true
jobs:
setup:
name: Setup
runs-on: ubuntu-latest
outputs:
compute-tiny: ${{ steps.setup-outputs.outputs.compute-tiny }}
compute-standard: ${{ steps.setup-outputs.outputs.compute-standard }}
compute-larger: ${{ steps.setup-outputs.outputs.compute-larger }}
compute-huge: ${{ steps.setup-outputs.outputs.compute-huge }}
enterprise: ${{ steps.setup-outputs.outputs.enterprise }}
go-tags: ${{ steps.setup-outputs.outputs.go-tags }}
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- id: setup-outputs
name: Setup outputs
run: |
github_repository="${{ github.repository }}"
if [ "${github_repository##*/}" == "vault-enterprise" ] ; then
# shellcheck disable=SC2129
echo 'compute-tiny=["self-hosted","ondemand","linux","type=m5.large"]' >> "$GITHUB_OUTPUT"
echo 'compute-standard=["self-hosted","ondemand","linux","type=m5.xlarge"]' >> "$GITHUB_OUTPUT"
echo 'compute-larger=["self-hosted","ondemand","linux","type=m5.2xlarge"]' >> "$GITHUB_OUTPUT"
echo 'compute-huge=["self-hosted","ondemand","linux","type=m5.4xlarge"]' >> "$GITHUB_OUTPUT"
echo 'enterprise=1' >> "$GITHUB_OUTPUT"
echo 'go-tags=ent,enterprise' >> "$GITHUB_OUTPUT"
else
# shellcheck disable=SC2129
echo 'compute-tiny="ubuntu-latest"' >> "$GITHUB_OUTPUT" # 2 cores, 7 GB RAM, 14 GB SSD
echo 'compute-standard="custom-linux-small-vault-latest"' >> "$GITHUB_OUTPUT" # 8 cores, 32 GB RAM, 300 GB SSD
echo 'compute-larger="custom-linux-medium-vault-latest"' >> "$GITHUB_OUTPUT" # 16 cores, 64 GB RAM, 600 GB SSD
echo 'compute-huge="custom-linux-xl-vault-latest"' >> "$GITHUB_OUTPUT" # 32-cores, 128 GB RAM, 1200 GB SSD
echo 'enterprise=' >> "$GITHUB_OUTPUT"
echo 'go-tags=' >> "$GITHUB_OUTPUT"
fi
- name: Ensure Go modules are cached
uses: ./.github/actions/set-up-go
with:
github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
no-restore: true # don't download them on a cache hit
diff-oss-ci:
name: Diff OSS
needs:
- setup
if: ${{ needs.setup.outputs.enterprise != '' && github.base_ref != '' }}
runs-on: ${{ fromJSON(needs.setup.outputs.compute-tiny) }}
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
fetch-depth: 0
- id: determine-branch
run: |
branch="${{ github.base_ref }}"
if [[ $branch = release/* ]] ; then
branch=${branch%%+ent}
# Add OSS remote
git config --global user.email "github-team-secret-vault-core@hashicorp.com"
git config --global user.name "hc-github-team-secret-vault-core"
git remote add oss https://github.com/hashicorp/vault.git
git fetch oss "$branch"
branch="oss/$branch"
else
branch="origin/$branch"
fi
echo "BRANCH=$branch" >> "$GITHUB_OUTPUT"
- id: diff
run: |
./.github/scripts/oss-diff.sh ${{ steps.determine-branch.outputs.BRANCH }} HEAD
test-go:
name: Run Go tests
needs:
- setup
# Don't run this job for PR branches starting with:
# 'ui/', 'backport/ui/', 'docs/', or 'backport/docs/'
# OR
# the 'docs' label is present
if: |
!startsWith(github.head_ref, 'ui/') &&
!startsWith(github.head_ref, 'backport/ui/') &&
!startsWith(github.head_ref, 'docs/') &&
!startsWith(github.head_ref, 'backport/docs/') &&
!contains(github.event.pull_request.labels.*.name, 'docs')
uses: ./.github/workflows/test-go.yml
with:
# The regular Go tests use an extra runner to execute the
# binary-dependent tests. We isolate them there so that the
# other tests aren't slowed down waiting for a binary build.
total-runners: 17
go-arch: amd64
go-tags: '${{ needs.setup.outputs.go-tags }},deadlock'
runs-on: ${{ needs.setup.outputs.compute-larger }}
enterprise: ${{ needs.setup.outputs.enterprise }}
secrets: inherit
test-go-race:
name: Run Go tests with data race detection
needs:
- setup
# Don't run this job for PR branches starting with:
# 'ui/', 'backport/ui/', 'docs/', or 'backport/docs/'
# OR
# the 'docs' label is present
if: |
github.event.pull_request.draft == false &&
!startsWith(github.head_ref, 'ui/') &&
!startsWith(github.head_ref, 'backport/ui/') &&
!startsWith(github.head_ref, 'docs/') &&
!startsWith(github.head_ref, 'backport/docs/') &&
!contains(github.event.pull_request.labels.*.name, 'docs')
uses: ./.github/workflows/test-go.yml
with:
total-runners: 16
env-vars: |
{
"VAULT_CI_GO_TEST_RACE": 1
}
extra-flags: '-race'
go-arch: amd64
go-tags: ${{ needs.setup.outputs.go-tags }}
runs-on: ${{ needs.setup.outputs.compute-huge }}
enterprise: ${{ needs.setup.outputs.enterprise }}
name: "-race"
secrets: inherit
test-go-fips:
name: Run Go tests with FIPS configuration
# Only run this job for the enterprise repo if the PR branch doesn't start with:
# 'ui/', 'backport/ui/', 'docs/', or 'backport/docs/'
# OR
# the 'docs' label is not present
if: |
github.event.pull_request.draft == false &&
needs.setup.outputs.enterprise == 1 &&
!startsWith(github.head_ref, 'ui/') &&
!startsWith(github.head_ref, 'backport/ui/') &&
!startsWith(github.head_ref, 'docs/') &&
!startsWith(github.head_ref, 'backport/docs/') &&
!contains(github.event.pull_request.labels.*.name, 'docs')
needs:
- setup
uses: ./.github/workflows/test-go.yml
with:
total-runners: 16
env-vars: |
{
"GOEXPERIMENT": "boringcrypto"
}
go-arch: amd64
go-tags: '${{ needs.setup.outputs.go-tags }},deadlock,cgo,fips,fips_140_2'
runs-on: ${{ needs.setup.outputs.compute-larger }}
enterprise: ${{ needs.setup.outputs.enterprise }}
name: "-fips"
secrets: inherit
test-ui:
name: Test UI
# The test-ui job is only run on:
# - pushes to main and branches starting with "release/"
# - PRs where the branch starts with "ui/", "backport/ui/", "merge", or when base branch starts with "release/"
# - PRs with the "ui" label on github
if: |
github.ref_name == 'main' ||
startsWith(github.ref_name, 'release/') ||
startsWith(github.head_ref, 'ui/') ||
startsWith(github.head_ref, 'backport/ui/') ||
startsWith(github.head_ref, 'merge') ||
contains(github.event.pull_request.labels.*.name, 'ui')
needs:
- setup
permissions:
id-token: write
contents: read
runs-on: ${{ fromJSON(needs.setup.outputs.compute-larger) }}
steps:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- uses: ./.github/actions/set-up-go
with:
github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }}
# Setup node.js without caching to allow running npm install -g yarn (next step)
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with:
node-version-file: './ui/package.json'
- id: install-yarn
run: |
npm install -g yarn
# Setup node.js with caching using the yarn.lock file
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
with:
node-version-file: './ui/package.json'
cache: yarn
cache-dependency-path: ui/yarn.lock
- id: install-browser
uses: browser-actions/setup-chrome@c485fa3bab6be59dce18dbc18ef6ab7cbc8ff5f1 # v1.2.0
- id: ui-dependencies
name: ui-dependencies
working-directory: ./ui
run: |
yarn install --frozen-lockfile
npm rebuild node-sass
- id: vault-auth
name: Authenticate to Vault
if: github.repository == 'hashicorp/vault-enterprise'
run: vault-auth
- id: secrets
name: Fetch secrets
if: github.repository == 'hashicorp/vault-enterprise'
uses: hashicorp/vault-action@130d1f5f4fe645bb6c83e4225c04d64cfb62de6e
with:
url: ${{ steps.vault-auth.outputs.addr }}
caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }}
token: ${{ steps.vault-auth.outputs.token }}
secrets: |
kv/data/github/hashicorp/vault-enterprise/github-token token | PRIVATE_REPO_GITHUB_TOKEN;
kv/data/github/hashicorp/vault-enterprise/license license_1 | VAULT_LICENSE;
- id: setup-git
name: Setup Git
if: github.repository == 'hashicorp/vault-enterprise'
run: |
git config --global url."https://${{ steps.secrets.outputs.PRIVATE_REPO_GITHUB_TOKEN }}@github.com".insteadOf https://github.com
- id: build-go-dev
name: build-go-dev
run: |
rm -rf ./pkg
mkdir ./pkg
make ci-bootstrap dev
- id: test-ui
name: test-ui
env:
VAULT_LICENSE: ${{ steps.secrets.outputs.VAULT_LICENSE }}
run: |
export PATH="${PWD}/bin:${PATH}"
if [ "${{ github.repository }}" == 'hashicorp/vault' ] ; then
export VAULT_LICENSE="${{ secrets.VAULT_LICENSE }}"
fi
# Run Ember tests
cd ui
mkdir -p test-results/qunit
yarn test:oss
- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with:
name: test-results-ui
path: ui/test-results
if: success() || failure()
- uses: test-summary/action@62bc5c68de2a6a0d02039763b8c754569df99e3f # TSCCR: no entry for repository "test-summary/action"
with:
paths: "ui/test-results/qunit/results.xml"
show: "fail"
if: always()
tests-completed:
needs:
- setup
- test-go
- test-ui
if: always()
runs-on: ${{ fromJSON(needs.setup.outputs.compute-tiny) }}
steps:
- run: |
tr -d '\n' <<< '${{ toJSON(needs.*.result) }}' | grep -q -v -E '(failure|cancelled)'
notify-tests-completed-failures-oss:
if: ${{ always() && github.repository == 'hashicorp/vault' && needs.tests-completed.result == 'failure' && (github.ref_name == 'main' || startsWith(github.ref_name, 'release/')) }}
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
needs:
- tests-completed
steps:
- name: send-notification
uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0
# We intentionally aren't using the following here since it's from an internal repo
# uses: hashicorp/cloud-gha-slack-notifier@730a033037b8e603adf99ebd3085f0fdfe75e2f4 #v1
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
with:
channel-id: "C05AABYEA9Y" # sent to #feed-vault-ci-official
payload: |
{"text":"OSS test failures on ${{ github.ref_name }}","blocks":[{"type":"header","text":{"type":"plain_text","text":":rotating_light: OSS test failures :rotating_light:","emoji":true}},{"type":"divider"},{"type":"section","text":{"type":"mrkdwn","text":"test(s) failed on ${{ github.ref_name }}"},"accessory":{"type":"button","text":{"type":"plain_text","text":"View Failing Workflow","emoji":true},"url":"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}}]}
notify-tests-completed-failures-ent:
if: ${{ always() && github.repository == 'hashicorp/vault-enterprise' && needs.tests-completed.result == 'failure' && (github.ref_name == 'main' || startsWith(github.ref_name, 'release/')) }}
runs-on: ['self-hosted', 'linux', 'small']
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
needs:
- tests-completed
steps:
- id: vault-auth
name: Vault Authenticate
run: vault-auth
- id: secrets
name: Fetch Vault Secrets
uses: hashicorp/vault-action@130d1f5f4fe645bb6c83e4225c04d64cfb62de6e
with:
url: ${{ steps.vault-auth.outputs.addr }}
caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }}
token: ${{ steps.vault-auth.outputs.token }}
secrets: |
kv/data/github/${{ github.repository }}/github_actions_notifications_bot token | SLACK_BOT_TOKEN;
- name: send-notification
uses: hashicorp/cloud-gha-slack-notifier@730a033037b8e603adf99ebd3085f0fdfe75e2f4 #v1
with:
channel-id: "C05AABYEA9Y" # sent to #feed-vault-ci-official
slack-bot-token: ${{ steps.secrets.outputs.SLACK_BOT_TOKEN }}
payload: |
{"text":"Enterprise test failures on ${{ github.ref_name }}","blocks":[{"type":"header","text":{"type":"plain_text","text":":rotating_light: Enterprise test failures :rotating_light:","emoji":true}},{"type":"divider"},{"type":"section","text":{"type":"mrkdwn","text":"test(s) failed on ${{ github.ref_name }}"},"accessory":{"type":"button","text":{"type":"plain_text","text":"View Failing Workflow","emoji":true},"url":"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}}]}
test-summary:
name: Go test failures
runs-on: ubuntu-latest
if: |
always() &&
(needs.test-go.result == 'success' ||
needs.test-go.result == 'failure' ||
needs.test-go-fips.result == 'success' ||
needs.test-go-fips.result == 'failure' ||
needs.test-go-race.result == 'success' ||
needs.test-go-race.result == 'failure')
needs:
- test-go
- test-go-fips
- test-go-race
steps:
- name: Download failure summary
uses: actions/download-artifact@v3
with:
name: failure-summary
- name: Prepare failure summary
run: |
# We will store the jq query results in a temp file
temp_file_name=temp-$(date +%s)
# The 'jq' command below filters and formats JSON data from input files to generate a failure summary report.
# The query is a bit of a nightmare, though, so let's have a closer look at it.
#
# The command takes input from files matching the pattern "failure-summary-*.json".
# The input files should contain streams of JSON objects(with no commas in between),
# one per line, representing test results.
# Each object should have the "Action" and "Package" keys.
#
# We invoke the command with two flags:
# - '-r' specifies that the output should be in raw format,
# without any JSON formatting. (I.e. no quotes).
# - '-n' tells 'jq' not to read any input from the command line.
# It is used when input is provided through the 'inputs' function or other methods.
#
# 'inputs':
# Read JSON objects from the input files specified after the 'jq' command.
# We assume that the input files contain one JSON object per line.
#
# 'select(.Action == "fail")':
# Filter JSON array to contain only objects where the value of "Action" is "fail"
#
# The remaining part of the query constructs a formatted string for each filtered JSON object`:
# - '\(.Package)' and '\(.Test // "-")' insert the values of the "Package" and "Test" keys into the string,
# respectively. If Test is missing, insert a dash character instead.
# - 'input_filename' is a special variable in 'jq' that represents the name of the input file being processed.
# - 'split("-")' splits the input filename on the hyphen ("-") character and returns an array of the
# resulting parts.
# - '.sub(".json"; "")' removes the ".json" extension from the string.
# - The third part of the filename is extracted using '.split("-") | .[2]'.
# - If the fourth part of the filename exists, it contains the test type.
# Otherwise, the default value "normal" is used.
# - The '.sub(".json"; "")' removes the ".json" extension from the string.
#
# The filtered and formatted data is outputted as rows of a Markdown table, like this:
# | pkg1 | test1 | 1 | normal |
# | pkg2 | test2 | 4 | race |
# | pkg3 | test3 | 6 | fips |
jq -r -n 'inputs | select(.Action == "fail") | "| \(.Package) | \(.Test // "-") | \(input_filename | split("-") | .[2] | sub(".json"; "")) | \(input_filename | split("-") | .[3] // "normal" | sub(".json";"") )"' failure-summary-*.json | sort >> "$temp_file_name"
# if there are test failures, present them in a format of a GH Markdown table
if [ -s "$temp_file_name" ]; then
# shellcheck disable=SC2129
# Here we create the headings for the summary table
echo "### Go test failures" >> "$GITHUB_STEP_SUMMARY"
echo "| Package | Test | Runner Index | Test Type |" >> "$GITHUB_STEP_SUMMARY"
echo "| ------- | ---- | ------------ | --------- |" >> "$GITHUB_STEP_SUMMARY"
cat "$temp_file_name" >> "$GITHUB_STEP_SUMMARY"
else
echo "### All Go tests passed! :white_check_mark:" >> "$GITHUB_STEP_SUMMARY"
fi