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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ jobs:
- name: Test
if: runner.os != 'Linux'
run: go test -race -shuffle=on ./...
- name: Test native macOS Keychain integration
if: runner.os == 'macOS'
env:
CGO_ENABLED: "1"
CONED_KEYCHAIN_TEST: "1"
run: go test -race -shuffle=on ./internal/securestore -run '^TestDarwinKeyringIntegration' -count=1
- run: go vet ./...
- run: git diff --check
lint:
Expand Down
331 changes: 319 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ on:
push:
tags: ["v*"]
permissions:
contents: write
id-token: write
attestations: write
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
goreleaser:
preflight:
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 30
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand All @@ -19,11 +20,36 @@ jobs:
with:
go-version-file: go.mod
cache: true
- name: Validate native release layout
shell: bash
run: |
set -euo pipefail
if grep -Fq 'goos: [linux, darwin]' .goreleaser.yml && grep -Fq 'CGO_ENABLED=0' .goreleaser.yml; then
echo 'Darwin is still configured as a CGO-disabled Ubuntu cross-build' >&2
exit 1
fi
test -f .goreleaser.darwin-amd64.yml
test -f .goreleaser.darwin-arm64.yml
grep -Fq 'goos: [linux]' .goreleaser.yml
grep -Fq 'goarch: [amd64, arm64]' .goreleaser.yml
grep -Fq 'env: [CGO_ENABLED=0]' .goreleaser.yml
grep -Fq 'goos: [darwin]' .goreleaser.darwin-amd64.yml
grep -Fq 'goarch: [amd64]' .goreleaser.darwin-amd64.yml
grep -Fq 'env: [CGO_ENABLED=1]' .goreleaser.darwin-amd64.yml
grep -Fq 'goos: [darwin]' .goreleaser.darwin-arm64.yml
grep -Fq 'goarch: [arm64]' .goreleaser.darwin-arm64.yml
grep -Fq 'env: [CGO_ENABLED=1]' .goreleaser.darwin-arm64.yml
grep -Fxq ' runs-on: macos-15-intel' .github/workflows/release.yml
grep -Fxq ' runs-on: macos-15' .github/workflows/release.yml
- name: Validate release tag
shell: bash
run: |
[[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]
test "$(git cat-file -t "$GITHUB_REF_NAME")" = tag
set -euo pipefail
tag="$GITHUB_REF_NAME"
[[ "$GITHUB_REF" == "refs/tags/$tag" ]]
[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]
test "$(git cat-file -t "$tag")" = tag
test "$(git rev-parse "$tag^{commit}")" = "$GITHUB_SHA"
- run: go mod verify
- name: Check formatting
shell: bash
Expand All @@ -32,18 +58,299 @@ jobs:
test -z "$unformatted"
- run: go test -race -shuffle=on ./...
- run: go vet ./...
- run: go install golang.org/x/vuln/cmd/govulncheck@v1.6.0
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: check --config .goreleaser.yml
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: check --config .goreleaser.darwin-amd64.yml
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: check --config .goreleaser.darwin-arm64.yml
- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@v1.6.0
- run: govulncheck ./...

build-linux:
needs: preflight
runs-on: ubuntu-latest
timeout-minutes: 20
env:
CGO_ENABLED: "0"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: go.mod
cache: true
- name: Verify Linux build host
shell: bash
run: |
set -euo pipefail
test "$(uname -s)" = Linux
test "$(uname -m)" = x86_64
test "$CGO_ENABLED" = 0
test "$(go env GOOS)" = linux
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: release --clean
args: release --clean --skip=publish --config .goreleaser.yml
- name: Verify Linux archives
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
archives=(dist/coned-cli_*_linux_*.tar.gz)
test "${#archives[@]}" -eq 2
test "$(find dist -maxdepth 1 -type f -name '*_linux_amd64.tar.gz' | wc -l | tr -d ' ')" = 1
test "$(find dist -maxdepth 1 -type f -name '*_linux_arm64.tar.gz' | wc -l | tr -d ' ')" = 1
test ! -e dist/checksums.txt
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coned-release-linux-amd64
path: dist/coned-cli_*_linux_amd64.tar.gz
if-no-files-found: error
retention-days: 1
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coned-release-linux-arm64
path: dist/coned-cli_*_linux_arm64.tar.gz
if-no-files-found: error
retention-days: 1

build-darwin-amd64:
needs: preflight
runs-on: macos-15-intel
timeout-minutes: 20
env:
CGO_ENABLED: "1"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: go.mod
cache: true
- name: Verify native Darwin amd64 build host
shell: bash
run: |
set -euo pipefail
test "$(uname -s)" = Darwin
test "$(uname -m)" = x86_64
test "$CGO_ENABLED" = 1
test "$(go env GOOS)" = darwin
test "$(go env GOARCH)" = amd64
test "$(go env CGO_ENABLED)" = 1
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: release --clean --skip=publish --config .goreleaser.darwin-amd64.yml
- name: Verify Darwin amd64 archive
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
archives=(dist/coned-cli_*_darwin_amd64.tar.gz)
test "${#archives[@]}" -eq 1
test ! -e dist/checksums.txt
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coned-release-darwin-amd64
path: dist/coned-cli_*_darwin_amd64.tar.gz
if-no-files-found: error
retention-days: 1

build-darwin-arm64:
needs: preflight
runs-on: macos-15
timeout-minutes: 20
env:
CGO_ENABLED: "1"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: go.mod
cache: true
- name: Verify native Darwin arm64 build host
shell: bash
run: |
set -euo pipefail
test "$(uname -s)" = Darwin
test "$(uname -m)" = arm64
test "$CGO_ENABLED" = 1
test "$(go env GOOS)" = darwin
test "$(go env GOARCH)" = arm64
test "$(go env CGO_ENABLED)" = 1
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: release --clean --skip=publish --config .goreleaser.darwin-arm64.yml
- name: Verify Darwin arm64 archive
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
archives=(dist/coned-cli_*_darwin_arm64.tar.gz)
test "${#archives[@]}" -eq 1
test ! -e dist/checksums.txt
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coned-release-darwin-arm64
path: dist/coned-cli_*_darwin_arm64.tar.gz
if-no-files-found: error
retention-days: 1

publish:
needs: [preflight, build-linux, build-darwin-amd64, build-darwin-arm64]
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- name: Validate prerequisites and release tag
if: ${{ always() }}
env:
PREFLIGHT_RESULT: ${{ needs.preflight.result }}
LINUX_RESULT: ${{ needs.build-linux.result }}
DARWIN_AMD64_RESULT: ${{ needs.build-darwin-amd64.result }}
DARWIN_ARM64_RESULT: ${{ needs.build-darwin-arm64.result }}
RELEASE_TAG: ${{ github.ref_name }}
shell: bash
run: |
set -euo pipefail
test "$PREFLIGHT_RESULT" = success
test "$LINUX_RESULT" = success
test "$DARWIN_AMD64_RESULT" = success
test "$DARWIN_ARM64_RESULT" = success
[[ "$GITHUB_REF" == "refs/tags/$RELEASE_TAG" ]]
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]
test "$(git cat-file -t "$RELEASE_TAG")" = tag
test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$GITHUB_SHA"
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: coned-release-linux-amd64
path: dist
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: coned-release-linux-arm64
path: dist
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: coned-release-darwin-amd64
path: dist
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: coned-release-darwin-arm64
path: dist
- name: Validate exact archive set, contents, and metadata
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Attest release archives
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_SHA: ${{ github.sha }}
shell: bash
run: |
set -euo pipefail
shopt -s nullglob
version="${RELEASE_TAG#v}"
expected_names=(
"coned-cli_${version}_linux_amd64.tar.gz"
"coned-cli_${version}_linux_arm64.tar.gz"
"coned-cli_${version}_darwin_amd64.tar.gz"
"coned-cli_${version}_darwin_arm64.tar.gz"
)
expected_paths=()
for name in "${expected_names[@]}"; do
expected_paths+=("dist/$name")
done
mapfile -t actual_paths < <(printf '%s\n' dist/* | LC_ALL=C sort)
mapfile -t sorted_expected_paths < <(printf '%s\n' "${expected_paths[@]}" | LC_ALL=C sort)
test "${#actual_paths[@]}" -eq 4
test "${actual_paths[*]}" = "${sorted_expected_paths[*]}"

expected_entries=(coned LICENSE NOTICE README.md)
while IFS= read -r license; do
expected_entries+=("$license")
done < <(find third_party_licenses -type f -print | LC_ALL=C sort)
mapfile -t sorted_expected_entries < <(printf '%s\n' "${expected_entries[@]}" | LC_ALL=C sort)
expected_commit="$RELEASE_SHA"
# GoReleaser .CommitTimestamp and internal/build.Date are Unix seconds.
expected_timestamp="$(git show -s --format=%ct "$RELEASE_TAG^{commit}")"
for archive in "${actual_paths[@]}"; do
mapfile -t actual_entries < <(tar -tzf "$archive" | sed 's#/$##' | LC_ALL=C sort)
test "${actual_entries[*]}" = "${sorted_expected_entries[*]}"
workdir="$(mktemp -d)"
tar -xzf "$archive" -C "$workdir"
test -f "$workdir/coned"
description="$(file -b "$workdir/coned")"
case "$archive" in
*_linux_amd64.tar.gz) [[ "$description" == *ELF* && "$description" == *x86-64* ]] ;;
*_linux_arm64.tar.gz) [[ "$description" == *ELF* && "$description" == *aarch64* ]] ;;
*_darwin_amd64.tar.gz) [[ "$description" == *Mach-O* && "$description" == *x86_64* ]] ;;
*_darwin_arm64.tar.gz) [[ "$description" == *Mach-O* && "$description" == *arm64* ]] ;;
*) echo "unexpected archive target" >&2; exit 1 ;;
esac
strings "$workdir/coned" > "$workdir/strings"
grep -Fq "$version" "$workdir/strings"
grep -Fq "$expected_commit" "$workdir/strings"
# Match the standalone internal/build.Date value, not vcs.time=... from Go build info.
grep -Fxq "$expected_timestamp" "$workdir/strings"
grep -Fxq false "$workdir/strings"
grep -Fq 'vcs.modified=false' "$workdir/strings"
rm -rf "$workdir"
done
- name: Generate exactly one checksum manifest
shell: bash
run: |
set -euo pipefail
test ! -e dist/checksums.txt
(
cd dist
LC_ALL=C sha256sum coned-cli_*.tar.gz > checksums.txt
)
test "$(find dist -maxdepth 1 -type f -name 'checksums.txt' | wc -l | tr -d ' ')" = 1
(
cd dist
sha256sum -c checksums.txt
)
- name: Attest all release subjects
uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4
with:
subject-path: |
dist/*.tar.gz
dist/checksums.txt
- name: Publish exactly one GitHub release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.ref_name }}
shell: bash
run: |
set -euo pipefail
release_args=(--verify-tag --title "$RELEASE_TAG" --generate-notes)
if [[ "$RELEASE_TAG" == *-* ]]; then
release_args+=(--prerelease)
fi
gh release create "$RELEASE_TAG" "${release_args[@]}" dist/*.tar.gz dist/checksums.txt
Loading