diff --git a/.github/workflows/hsm-testing.yml b/.github/workflows/hsm-testing.yml new file mode 100644 index 000000000..31e31abab --- /dev/null +++ b/.github/workflows/hsm-testing.yml @@ -0,0 +1,127 @@ +name: HSM Testing +run-name: ${{ inputs.tag_name }} / go ${{ inputs.go_version }} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true + +permissions: + contents: write + +on: + workflow_dispatch: + inputs: + tag_name: + required: true + type: string + go_version: + default: "1.21" + type: string + +jobs: + windows: + runs-on: windows-latest + environment: production + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ inputs.go_version }} + - name: Install GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + version: "~1.17.1" + install-only: true + - name: Build release binaries + shell: bash + env: + TAG_NAME: ${{ inputs.tag_name }} + run: script/release-hsm --local "$TAG_NAME" --platform windows --config .goreleaser-hsm.yml + + # As official Azure HSM support for signing Windows .exe binaries is in the form of an action, + # we must unzip the archives created by GoReleaser, sign the binaries, and then re-zip them. + # This choice was due to the fact that GoReleaser produces + - name: Expand goreleaser archives for signing + shell: bash + run: | + for ZIP_FILE in dist/gh_*_windows_*.zip; do + unzip -d "${ZIP_FILE%.zip}" "$ZIP_FILE" + done + - name: Sign .exe release binaries + uses: azure/azure-code-signing-action@6c86237186b7eed50c9e8a3a6e42131bcc5e4601 + with: + azure-tenant-id: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO_TENANT_ID }} + azure-client-id: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO_CLIENT_ID }} + azure-client-secret: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO }} + endpoint: https://wus.codesigning.azure.net/ + code-signing-account-name: GitHubInc + certificate-profile-name: GitHubInc + files-folder: ${{ github.workspace }}/dist + files-folder-filter: exe + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + - name: Zip goreleaser directories + shell: bash + run: | + for DIR in dist/gh_*_windows_*; do + zip -r "$DIR.zip" "$DIR" + done + + - name: Set up MSBuild + id: setupmsbuild + uses: microsoft/setup-msbuild@v1.3.1 + - name: Build MSI + shell: bash + env: + MSBUILD_PATH: ${{ steps.setupmsbuild.outputs.msbuildPath }} + run: | + for ZIP_FILE in dist/gh_*_windows_*.zip; do + MSI_NAME="$(basename "$ZIP_FILE" ".zip")" + MSI_VERSION="$(cut -d_ -f2 <<<"$MSI_NAME" | cut -d- -f1)" + case "$MSI_NAME" in + *_386 ) + source_dir="$PWD/dist/windows_windows_386" + platform="x86" + ;; + *_amd64 ) + source_dir="$PWD/dist/windows_windows_amd64_v1" + platform="x64" + ;; + *_arm64 ) + echo "skipping building MSI for arm64 because WiX 3.11 doesn't support it: https://github.com/wixtoolset/issues/issues/6141" >&2 + continue + #source_dir="$PWD/dist/windows_windows_arm64" + #platform="arm64" + ;; + * ) + printf "unsupported architecture: %s\n" "$MSI_NAME" >&2 + exit 1 + ;; + esac + "${MSBUILD_PATH}\MSBuild.exe" ./build/windows/gh.wixproj -p:SourceDir="$source_dir" -p:OutputPath="$PWD/dist" -p:OutputName="$MSI_NAME" -p:ProductVersion="${MSI_VERSION#v}" -p:Platform="$platform" + done + - name: Sign .msi release binaries + uses: azure/azure-code-signing-action@6c86237186b7eed50c9e8a3a6e42131bcc5e4601 + with: + azure-tenant-id: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO_TENANT_ID }} + azure-client-id: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO_CLIENT_ID }} + azure-client-secret: ${{ secrets.SPN_SPN_AZURE_CODE_SIGNING_DEMO }} + endpoint: https://wus.codesigning.azure.net/ + code-signing-account-name: GitHubInc + certificate-profile-name: GitHubInc + files-folder: ${{ github.workspace }}/dist + files-folder-filter: msi + file-digest: SHA256 + timestamp-rfc3161: http://timestamp.acs.microsoft.com + timestamp-digest: SHA256 + - uses: actions/upload-artifact@v3 + with: + name: windows + if-no-files-found: error + retention-days: 7 + path: | + dist/*.zip + dist/*.msi \ No newline at end of file diff --git a/.goreleaser-hsm.yml b/.goreleaser-hsm.yml new file mode 100644 index 000000000..7d3b975a5 --- /dev/null +++ b/.goreleaser-hsm.yml @@ -0,0 +1,93 @@ +project_name: gh + +release: + prerelease: auto + draft: true # we only publish after the Windows MSI gets uploaded + name_template: "GitHub CLI {{.Version}}" + +before: + hooks: + - >- + {{ if eq .Runtime.Goos "windows" }}echo{{ end }} make manpages GH_VERSION={{.Version}} + - >- + {{ if ne .Runtime.Goos "linux" }}echo{{ end }} make completions + +builds: + - id: macos #build:macos + goos: [darwin] + goarch: [amd64, arm64] + hooks: + post: + - cmd: ./script/sign '{{ .Path }}' + output: true + binary: bin/gh + main: ./cmd/gh + ldflags: + - -s -w -X github.com/cli/cli/v2/internal/build.Version={{.Version}} -X github.com/cli/cli/v2/internal/build.Date={{time "2006-01-02"}} + + - id: linux #build:linux + goos: [linux] + goarch: [386, arm, amd64, arm64] + env: + - CGO_ENABLED=0 + binary: bin/gh + main: ./cmd/gh + ldflags: + - -s -w -X github.com/cli/cli/v2/internal/build.Version={{.Version}} -X github.com/cli/cli/v2/internal/build.Date={{time "2006-01-02"}} + + - id: windows #build:windows + goos: [windows] + goarch: [386, amd64, arm64] + binary: bin/gh + main: ./cmd/gh + ldflags: + - -s -w -X github.com/cli/cli/v2/internal/build.Version={{.Version}} -X github.com/cli/cli/v2/internal/build.Date={{time "2006-01-02"}} + +archives: + - id: linux-archive + builds: [linux] + name_template: "gh_{{ .Version }}_linux_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" + wrap_in_directory: true + format: tar.gz + rlcp: true + files: + - LICENSE + - ./share/man/man1/gh*.1 + - id: macos-archive + builds: [macos] + name_template: "gh_{{ .Version }}_macOS_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" + wrap_in_directory: true + format: zip + rlcp: true + files: + - LICENSE + - ./share/man/man1/gh*.1 + - id: windows-archive + builds: [windows] + name_template: "gh_{{ .Version }}_windows_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" + wrap_in_directory: false + format: zip + rlcp: true + files: + - LICENSE + +nfpms: #build:linux + - license: MIT + maintainer: GitHub + homepage: https://github.com/cli/cli + bindir: /usr + dependencies: + - git + description: GitHub’s official command line tool. + formats: + - deb + - rpm + contents: + - src: "./share/man/man1/gh*.1" + dst: "/usr/share/man/man1" + - src: "./share/bash-completion/completions/gh" + dst: "/usr/share/bash-completion/completions/gh" + - src: "./share/fish/vendor_completions.d/gh.fish" + dst: "/usr/share/fish/vendor_completions.d/gh.fish" + - src: "./share/zsh/site-functions/_gh" + dst: "/usr/share/zsh/site-functions/_gh" diff --git a/script/release-hsm b/script/release-hsm new file mode 100755 index 000000000..661e10853 --- /dev/null +++ b/script/release-hsm @@ -0,0 +1,120 @@ +#!/bin/bash +set -e + +print_help() { + cat < [--platform {linux|macos|windows}] [--branch ] + +To build staging binaries from the current branch: + script/release --current [--platform {linux|macos|windows}] + +To build binaries locally with goreleaser: + script/release --local --platform {linux|macos|windows} +EOF +} + +if [ $# -eq 0 ]; then + print_help >&2 + exit 1 +fi + +tag_name="" +is_local="" +do_push="" +platform="" +branch="trunk" +deploy_env="production" +goreleaser_config=".goreleaser.yml" + +while [ $# -gt 0 ]; do + case "$1" in + -h | --help ) + print_help + exit 0 + ;; + -b | --branch ) + branch="$2" + shift 2 + ;; + -c | --config ) + goreleaser_config="$2" + shift 2 + ;; + -p | --platform ) + platform="$2" + shift 2 + ;; + --local ) + is_local=1 + shift 1 + ;; + --staging ) + deploy_env="staging" + shift 1 + ;; + --current ) + deploy_env="staging" + tag_name="$(git describe --tags --abbrev=0)" + branch="$(git rev-parse --symbolic-full-name '@{upstream}' 2>/dev/null || git branch --show-current)" + branch="${branch#refs/remotes/*/}" + do_push=1 + shift 1 + ;; + -* ) + printf "unrecognized flag: %s\n" "$1" >&2 + exit 1 + ;; + * ) + tag_name="$1" + shift 1 + ;; + esac +done + +announce() { + local tmpdir="${TMPDIR:-/tmp}" + echo "$*" | sed "s:${tmpdir%/}:\$TMPDIR:" + "$@" +} + +trigger_deployment() { + announce gh workflow -R cli/cli run deployment.yml --ref "$branch" -f tag_name="$tag_name" -f environment="$deploy_env" +} + +build_local() { + local config="$goreleaser_config" + case "$platform" in + linux ) + sed '/#build:windows/,/^$/d; /#build:macos/,/^$/d' .goreleaser.yml >.goreleaser.generated.yml + config=".goreleaser.generated.yml" + ;; + macos ) + sed '/#build:windows/,/^$/d; /#build:linux/,/^$/d' .goreleaser.yml >.goreleaser.generated.yml + config=".goreleaser.generated.yml" + ;; + windows ) + sed '/#build:linux/,/^$/d; /#build:macos/,/^$/d' .goreleaser.yml >.goreleaser.generated.yml + config=".goreleaser.generated.yml" + ;; + esac + [ -z "$tag_name" ] || export GORELEASER_CURRENT_TAG="$tag_name" + announce goreleaser release -f "$config" --clean --skip-validate --skip-publish --release-notes="$(mktemp)" +} + +if [ -n "$is_local" ]; then + build_local +else + if [ -n "$do_push" ]; then + if ! git diff --quiet || ! git diff --cached --quiet; then + echo "refusing to continue due to uncomitted local changes" >&2 + exit 1 + fi + announce git push + fi + trigger_deployment + if [ "$deploy_env" = "production" ]; then + echo + echo "Go to Slack to manually approve this production deployment." + fi +fi