diff --git a/.github/actions/bootstrap/action.yaml b/.github/actions/bootstrap/action.yaml new file mode 100644 index 0000000..8a73a47 --- /dev/null +++ b/.github/actions/bootstrap/action.yaml @@ -0,0 +1,45 @@ +name: "Bootstrap" +description: "Bootstrap all tools and dependencies" +inputs: + go-version: + description: "Go version to install" + required: true + default: "1.21.x" + cache-key-prefix: + description: "Prefix all cache keys with this value" + required: true + default: "831180ac25" + bootstrap-apt-packages: + description: "Space delimited list of tools to install via apt" + default: "" + +runs: + using: "composite" + steps: + - uses: actions/setup-go@v4 + with: + go-version: ${{ inputs.go-version }} + + - name: Restore tool cache + id: tool-cache + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/.tool + key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Taskfile.yaml') }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Taskfile.yaml') }} + ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool + + - name: (cache-miss) Bootstrap project tools + shell: bash + run: make ci-bootstrap-tools + + - name: Bootstrap go dependencies + shell: bash + run: make ci-bootstrap-go + + - name: Install apt packages + if: inputs.bootstrap-apt-packages != '' + shell: bash + run: | + DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y ${{ inputs.bootstrap-apt-packages }} diff --git a/.github/scripts/ci-check.sh b/.github/scripts/ci-check.sh index f1f641a..0ab83a3 100755 --- a/.github/scripts/ci-check.sh +++ b/.github/scripts/ci-check.sh @@ -1 +1,11 @@ #!/usr/bin/env bash + +red=$(tput setaf 1) +bold=$(tput bold) +normal=$(tput sgr0) + +# assert we are running in CI (or die!) +if [[ -z "$CI" ]]; then + echo "${bold}${red}This step should ONLY be run in CI. Exiting...${normal}" + exit 1 +fi diff --git a/.github/scripts/go-mod-tidy-check.sh b/.github/scripts/go-mod-tidy-check.sh index 41bc639..28f22fc 100755 --- a/.github/scripts/go-mod-tidy-check.sh +++ b/.github/scripts/go-mod-tidy-check.sh @@ -4,19 +4,18 @@ set -eu ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX") TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX") -trap "cp -v ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT +trap "cp -p ${ORIGINAL_STATE_DIR}/* ./ && git update-index -q --refresh && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT -echo "Capturing original state of files..." -cp -v go.mod go.sum "${ORIGINAL_STATE_DIR}" +# capturing original state of files... +cp go.mod go.sum "${ORIGINAL_STATE_DIR}" -echo "Capturing state of go.mod and go.sum after running go mod tidy..." +# capturing state of go.mod and go.sum after running go mod tidy... go mod tidy -cp -v go.mod go.sum "${TIDY_STATE_DIR}" -echo "" +cp go.mod go.sum "${TIDY_STATE_DIR}" set +e -# Detect difference between the git HEAD state and the go mod tidy state +# detect difference between the git HEAD state and the go mod tidy state DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod") DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum") diff --git a/.github/scripts/trigger-release.sh b/.github/scripts/trigger-release.sh index 20d602b..a96a308 100755 --- a/.github/scripts/trigger-release.sh +++ b/.github/scripts/trigger-release.sh @@ -1,2 +1,53 @@ #!/usr/bin/env bash +set -eu +TOOL_DIR=.tool +GH=$TOOL_DIR/gh + +bold=$(tput bold) +normal=$(tput sgr0) + +if ! [ -x "$(command -v $GH)" ]; then + echo "The GitHub CLI could not be found." + exit 1 +fi + +$GH auth status + +# we need all of the git state to determine the next version. Since tagging is done by +# the release pipeline it is possible to not have all of the tags from previous releases. +git fetch --tags + +# populates the CHANGELOG.md and VERSION files +echo "${bold}Generating changelog...${normal}" +make changelog 2> /dev/null + +NEXT_VERSION=$(cat VERSION) + +if [[ "$NEXT_VERSION" == "" || "${NEXT_VERSION}" == "(Unreleased)" ]]; then + echo "Could not determine the next version to release. Exiting..." + exit 1 +fi + +while true; do + read -p "${bold}Do you want to trigger a release for version '${NEXT_VERSION}'?${normal} [y/n] " yn + case $yn in + [Yy]* ) echo; break;; + [Nn]* ) echo; echo "Cancelling release..."; exit;; + * ) echo "Please answer yes or no.";; + esac +done + +echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..." +echo +$GH workflow run release.yaml -f version=${NEXT_VERSION} + +echo +echo "${bold}Waiting for release to start...${normal}" +sleep 10 + +set +e + +echo "${bold}Head to the release workflow to monitor the release:${normal} $($GH run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')" +id=$($GH run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId') +$GH run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" $GH run view $id --log-failed) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index 72617d9..0000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: CI - -on: - workflow_dispatch: - push: - paths: - - '**' - - '!**.md' - - '!doc/**' - branches: - - main - pull_request: - -permissions: read-all - -jobs: - build: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.0.2 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..4fd965b --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,78 @@ +name: "Release" +on: + workflow_dispatch: + inputs: + version: + description: tag the latest commit on main with the given version (prefixed with v) + required: true + +permissions: + contents: read + +env: + FORCE_COLOR: true + +jobs: + quality-gate: + environment: release + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - name: Check if tag already exists + # note: this will fail if the tag already exists + run: | + [[ "${{ github.event.inputs.version }}" == v* ]] || (echo "version '${{ github.event.inputs.version }}' does not have a 'v' prefix" && exit 1) + git tag ${{ github.event.inputs.version }} + + - name: Check validations results + uses: fountainhead/action-wait-for-check@v1.1.0 + id: validations + with: + token: ${{ secrets.GITHUB_TOKEN }} + # This check name is defined as the github action job name (in .github/workflows/validations.yaml) + checkName: "Validations" + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Quality gate + if: steps.validations.outputs.conclusion != 'success' + run: | + echo "Validations Status: ${{ steps.validations.conclusion }}" + false + + release: + needs: [quality-gate] + runs-on: ubuntu-22.04 + permissions: + contents: write + packages: write + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + env: + FORCE_COLOR: true + + - name: Tag release + run: | + git config user.name "anchoreci" + git config user.email "anchoreci@users.noreply.github.com" + git tag -a ${{ github.event.inputs.version }} -m "Release ${{ github.event.inputs.version }}" + git push origin --tags + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & publish release artifacts + run: make ci-release + env: + # for mac signing and notarization... + QUILL_SIGN_P12: ${{ secrets.ANCHORE_APPLE_DEVELOPER_ID_CERT_CHAIN }} + QUILL_SIGN_PASSWORD: ${{ secrets.ANCHORE_APPLE_DEVELOPER_ID_CERT_PASS }} + QUILL_NOTARY_ISSUER: ${{ secrets.APPLE_NOTARY_ISSUER }} + QUILL_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }} + QUILL_NOTARY_KEY: ${{ secrets.APPLE_NOTARY_KEY }} + # for creating the release (requires write access to packages and content) + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml new file mode 100644 index 0000000..f4bc4d3 --- /dev/null +++ b/.github/workflows/validations.yaml @@ -0,0 +1,29 @@ +name: "Validations" + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +env: + FORCE_COLOR: true + +jobs: + + Validations: + # Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline + name: "Validations" + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + + - name: Run all validations + run: make pr-validations diff --git a/.goreleaser.yaml b/.goreleaser.yaml index ce4f7d8..36250f6 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -40,8 +40,6 @@ archives: - id: linux-archives builds: - linux-build - - # note: the signing process is depending on tar.gz archives. If this format changes then .github/scripts/apple-signing/*.sh will need to be adjusted - id: darwin-archives builds: - darwin-build diff --git a/Makefile b/Makefile index 846fbf8..fa9a7e5 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,11 @@ $(TASK) task: $(BINNY) ci-bootstrap-go: go mod download +.PHONY: ci-bootstrap-tools +ci-bootstrap-tools: $(BINNY) + $(BINNY) install -vvv + + # this is a bootstrapping catch-all, where if the target doesn't exist, we'll ensure the tools are installed and then try again %: make $(TASK) @@ -42,4 +47,4 @@ $(TASKS): $(TASK) @$(TASK) $@ help: $(TASK) - @$(TASK) -l \ No newline at end of file + @$(TASK) -l diff --git a/Taskfile.yaml b/Taskfile.yaml index cb91502..88765fa 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -99,11 +99,6 @@ tasks: - "{{ .TOOL_DIR }}/golangci-lint run --tests=false" - check-licenses: - # desc: Ensure transitive dependencies are compliant with the current license policy - deps: [tools] - cmd: "{{ .TOOL_DIR }}/grant check ./..." - check-go-mod-tidy: # desc: Ensure go.mod and go.sum are up to date cmds: @@ -148,8 +143,41 @@ tasks: silent: true - cmd: | cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml - echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml + echo "dist: {{ .SNAPSHOT_DIR }}" >> {{ .TMP_DIR }}/goreleaser.yaml - cmd: "{{ .TOOL_DIR }}/goreleaser release --clean --skip=publish --skip=sign --snapshot --config {{ .TMP_DIR }}/goreleaser.yaml" - ## TODO Release targets ################################# + ## Release targets ################################# + + release: + desc: Create a release + interactive: true + deps: [tools] + cmds: + - cmd: .github/scripts/trigger-release.sh + silent: true + + ## CI-only targets ################################# + + ci-check: + # desc: "[CI only] Are you in CI?" + cmds: + - cmd: .github/scripts/ci-check.sh + silent: true + + ci-validate: + # desc: "[CI only] Run all CI validations" + cmds: + - task: ci-check + - task: default + + ci-release: + # desc: "[CI only] Create a release" + deps: [tools] + cmds: + - task: ci-check + - "{{ .TOOL_DIR }}/chronicle -vvv > CHANGELOG.md" + - cmd: "cat CHANGELOG.md" + silent: true + - "{{ .TOOL_DIR }}/goreleaser release --clean --release-notes CHANGELOG.md" +