Skip to content

feat(init): §1.5.1 secret-ingress hardening for jtk + cfl init (#390)… #110

feat(init): §1.5.1 secret-ingress hardening for jtk + cfl init (#390)…

feat(init): §1.5.1 secret-ingress hardening for jtk + cfl init (#390)… #110

Workflow file for this run

name: Release jtk
on:
push:
tags:
- 'jtk-v*'
permissions:
contents: write
jobs:
goreleaser:
# INT-450: darwin must build with cgo (Keychain backend). cgo+darwin
# cannot cross-compile from Linux, so this job runs on macOS. Pinned
# image (not the moving macos-latest label) for a reproducible release.
runs-on: macos-15
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get version from tag
id: get_version
run: |
VERSION=${GITHUB_REF#refs/tags/jtk-v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
- uses: actions/setup-go@v5
with:
go-version: '1.26'
cache-dependency-path: tools/jtk/go.sum
- name: Install GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: "~> v2"
install-only: true
- name: GoReleaser check
run: goreleaser check -f .goreleaser-jtk.yml
- name: Create temporary semver tag for GoReleaser
run: |
git tag v${{ steps.get_version.outputs.version }}
- name: Build (snapshot, no publish)
env:
GORELEASER_CURRENT_TAG: v${{ steps.get_version.outputs.version }}
run: goreleaser release --snapshot --clean -f .goreleaser-jtk.yml
# INT-450 pre-publish gate: prove the darwin binaries actually carry
# the Keychain backend BEFORE anything is published. A CGO_ENABLED=0
# darwin build links no Security.framework and fails closed at
# runtime; this gate makes that impossible to ship silently.
- name: Pre-publish gate — darwin Keychain backend present
run: |
set -euo pipefail
art=dist/artifacts.json
arm_bin=$(jq -r '.[]|select(.type=="Binary" and .goos=="darwin" and .goarch=="arm64")|.path' "$art")
amd_bin=$(jq -r '.[]|select(.type=="Binary" and .goos=="darwin" and .goarch=="amd64")|.path' "$art")
[ -n "$arm_bin" ] && [ -n "$amd_bin" ] || { echo "missing a darwin binary in artifacts.json"; exit 1; }
# darwin archives: exactly one per arch, no duplicate names
tot=$(jq '[.[]|select(.type=="Archive" and .goos=="darwin")|.name]|length' "$art")
uniq=$(jq '[.[]|select(.type=="Archive" and .goos=="darwin")|.name]|unique|length' "$art")
[ "$tot" = "$uniq" ] || { echo "duplicate darwin archive names"; exit 1; }
[ "$(jq '[.[]|select(.type=="Archive" and .goos=="darwin" and .goarch=="arm64")]|length' "$art")" = 1 ] || { echo "expected exactly one darwin/arm64 archive"; exit 1; }
[ "$(jq '[.[]|select(.type=="Archive" and .goos=="darwin" and .goarch=="amd64")]|length' "$art")" = 1 ] || { echo "expected exactly one darwin/amd64 archive"; exit 1; }
# Mach-O arch sanity (both slices)
file "$arm_bin" | grep -q 'arm64' || { echo "arm64 binary is not arm64 Mach-O"; exit 1; }
file "$amd_bin" | grep -q 'x86_64' || { echo "amd64 binary is not x86_64 Mach-O"; exit 1; }
lipo -archs "$arm_bin" | grep -qw arm64 || { echo "lipo: arm64 slice missing"; exit 1; }
lipo -archs "$amd_bin" | grep -qw x86_64 || { echo "lipo: x86_64 slice missing"; exit 1; }
# amd64 cannot run on the arm64 runner: assert Security.framework
# is linked. CGO_ENABLED=0 omits it entirely, so its presence is a
# sound *necessary* cgo signal for the slice we can't execute.
otool -L "$amd_bin" | grep -q '/System/Library/Frameworks/Security.framework' \
|| { echo "amd64 binary not linked against Security.framework (cgo missing)"; exit 1; }
# arm64 authoritative functional check: with no backend override
# and isolated HOME/XDG, credstore must auto-select the Keychain.
# jtk config show emits a pipe-delimited table; we grep for the
# keyring_backend row containing "keychain (auto)".
tmp=$(mktemp -d)
mkdir -p "$tmp/Library/Application Support/atlassian-cli"
out=$(env -u ATLASSIAN_CLI_KEYRING_BACKEND \
-u JIRA_API_TOKEN -u ATLASSIAN_API_TOKEN \
HOME="$tmp" XDG_CONFIG_HOME="$tmp/xdg" \
"$arm_bin" config show 2>/dev/null)
echo "$out"
echo "$out" | grep -q 'keychain (auto)' \
|| { echo "GATE FAIL: keychain (auto) not found in jtk config show output"; exit 1; }
echo "GATE OK: darwin/arm64 reports keychain backend auto-selected; darwin/amd64 Security.framework linked"
- name: Release notes
run: |
set -euo pipefail
cat > "$RUNNER_TEMP/release-notes.md" <<'EOF'
### macOS Keychain storage restored
Builds since the credential-store migration were compiled without
cgo and failed closed on macOS (no Keychain backend). This release
builds the darwin binaries with cgo enabled, restoring native
macOS Keychain storage. Upgrade and re-run your normal commands;
no other action is required.
EOF
- name: Release (publish)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
GORELEASER_CURRENT_TAG: v${{ steps.get_version.outputs.version }}
run: goreleaser release --clean -f .goreleaser-jtk.yml --release-notes="$RUNNER_TEMP/release-notes.md"
- name: Fix release tag
run: |
VERSION=${{ steps.get_version.outputs.version }}
gh release edit "v${VERSION}" --tag "jtk-v${VERSION}" --title "jtk v${VERSION}"
git push origin :refs/tags/v${VERSION}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify release notes published
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
VERSION=${{ steps.get_version.outputs.version }}
body=$(gh release view "jtk-v${VERSION}" --repo "$GITHUB_REPOSITORY" --json body -q .body)
if ! printf '%s' "$body" | grep -q 'macOS Keychain'; then
gh release edit "jtk-v${VERSION}" --repo "$GITHUB_REPOSITORY" --notes-file "$RUNNER_TEMP/release-notes.md"
fi
- name: Update jira-ticket-cli alias cask
run: |
VERSION=${{ steps.get_version.outputs.version }}
git clone https://x-access-token:${TAP_TOKEN}@github.com/open-cli-collective/homebrew-tap.git tap
cd tap
sed -e 's/cask "jtk"/cask "jira-ticket-cli"/' \
-e 's/name "jtk"/name "jira-ticket-cli"/' \
Casks/jtk.rb > Casks/jira-ticket-cli.rb
git add Casks/jira-ticket-cli.rb
git diff --cached --quiet && exit 0
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "Update jira-ticket-cli alias cask for v${VERSION}"
git push
env:
TAP_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }}
# chocolatey-publish-jtk.yml and winget-publish-jtk.yml are dispatched
# automatically by the trigger-publish job below. They remain in separate
# workflow files (workflow_dispatch only) so Microsoft-hosted service
# flakiness in wingetcreate fork-sync or choco push does not gate the
# binary/Homebrew artifacts.
trigger-publish:
needs: goreleaser
runs-on: ubuntu-latest
continue-on-error: true
permissions:
actions: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.goreleaser.outputs.version }}
steps:
- name: Trigger Chocolatey publish
continue-on-error: true
run: gh workflow run chocolatey-publish-jtk.yml -f version="$VERSION" --ref main --repo "$GITHUB_REPOSITORY"
- name: Trigger Winget publish
continue-on-error: true
run: gh workflow run winget-publish-jtk.yml -f version="$VERSION" --ref main --repo "$GITHUB_REPOSITORY"
linux-packages:
needs: goreleaser
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Trigger linux-packages repo update
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.LINUX_PACKAGES_DISPATCH_TOKEN }}
repository: open-cli-collective/linux-packages
event-type: package-release
client-payload: |-
{
"package": "jtk",
"version": "${{ github.ref_name }}",
"repo": "open-cli-collective/atlassian-cli"
}