Skip to content

Commit c14cbaa

Browse files
authored
Merge pull request cli#13780 from cli/niik/deploy
Harden deployment workflow
2 parents 9d95dd7 + 57a479f commit c14cbaa

5 files changed

Lines changed: 223 additions & 79 deletions

File tree

.github/workflows/deployment.yml

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: Deployment
2-
run-name: ${{ inputs.tag_name }} / ${{ inputs.environment }}
2+
run-name: ${{ inputs.tag_name }} / ${{ inputs.environment }}${{ inputs.dry_run == true && ' (dry run)' || '' }}
33

44
concurrency:
55
group: ${{ github.workflow }}-${{ github.ref_name }}
@@ -16,17 +16,24 @@ on:
1616
tag_name:
1717
required: true
1818
type: string
19+
description: "The tag name for the release (e.g. v2.100.0)."
1920
environment:
2021
default: production
2122
type: environment
23+
description: "The deployment environment."
2224
platforms:
2325
default: "linux,macos,windows"
2426
type: string
27+
description: "Comma-separated list of platforms to build."
2528
release:
26-
description: "Whether to create a GitHub Release"
29+
description: "Whether to run the final release job. the dry_run flag still blocks final submissions."
2730
type: boolean
2831
default: true
29-
32+
dry_run:
33+
description: "Perform a dry run without publishing artifacts or creating a release"
34+
type: boolean
35+
default: true
36+
3037
jobs:
3138
validate-tag-name:
3239
runs-on: ubuntu-latest
@@ -44,6 +51,7 @@ jobs:
4451
runs-on: ubuntu-latest
4552
environment: ${{ inputs.environment }}
4653
if: contains(inputs.platforms, 'linux')
54+
timeout-minutes: 20
4755
steps:
4856
- name: Checkout
4957
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -87,31 +95,55 @@ jobs:
8795
runs-on: macos-latest
8896
environment: ${{ inputs.environment }}
8997
if: contains(inputs.platforms, 'macos')
98+
timeout-minutes: 20
9099
steps:
91100
- name: Checkout
92101
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
93102
- name: Set up Go
94103
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
95104
with:
96105
go-version-file: 'go.mod'
97-
- name: Configure macOS signing
106+
107+
- name: Install code signing certificate
98108
if: inputs.environment == 'production'
109+
shell: bash
99110
env:
100-
APPLE_DEVELOPER_ID: ${{ vars.APPLE_DEVELOPER_ID }}
101-
APPLE_APPLICATION_CERT: ${{ secrets.APPLE_APPLICATION_CERT }}
102-
APPLE_APPLICATION_CERT_PASSWORD: ${{ secrets.APPLE_APPLICATION_CERT_PASSWORD }}
111+
DEVELOPER_ID_CERT: ${{ secrets.GATEWATCHER_DEVELOPER_ID_CERT }}
112+
DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.GATEWATCHER_DEVELOPER_ID_PASSWORD }}
103113
run: |
104-
keychain="$RUNNER_TEMP/buildagent.keychain"
105-
keychain_password="password1"
114+
# create a keychain for the certificate
115+
PW=pwd.${{ github.run_number }}
116+
security create-keychain -p $PW "$RUNNER_TEMP/build.keychain"
117+
security set-keychain-settings -lut 21600 "$RUNNER_TEMP/build.keychain"
118+
security default-keychain -s "$RUNNER_TEMP/build.keychain"
119+
security unlock-keychain -p $PW "$RUNNER_TEMP/build.keychain"
106120
107-
security create-keychain -p "$keychain_password" "$keychain"
108-
security default-keychain -s "$keychain"
109-
security unlock-keychain -p "$keychain_password" "$keychain"
110-
111-
base64 -D <<<"$APPLE_APPLICATION_CERT" > "$RUNNER_TEMP/cert.p12"
112-
security import "$RUNNER_TEMP/cert.p12" -k "$keychain" -P "$APPLE_APPLICATION_CERT_PASSWORD" -T /usr/bin/codesign
113-
security set-key-partition-list -S "apple-tool:,apple:,codesign:" -s -k "$keychain_password" "$keychain"
121+
# import the certificate
122+
base64 -d <<< "$DEVELOPER_ID_CERT" > "$RUNNER_TEMP/cert.p12"
123+
security import "$RUNNER_TEMP/cert.p12" -k "$RUNNER_TEMP/build.keychain" -P "$DEVELOPER_ID_CERT_PASSWORD" -T /usr/bin/codesign
124+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $PW "$RUNNER_TEMP/build.keychain"
114125
rm "$RUNNER_TEMP/cert.p12"
126+
127+
- name: Add App Store Connect API key to keychain
128+
if: inputs.environment == 'production'
129+
uses: nodeselector/setup-apple-codesign@ab275d0f6fb63ef9e20b12b42ea0d567f935723c
130+
id: setup-apple-codesign
131+
with:
132+
asset-type: "app-store-connect-api-key"
133+
app-store-connect-api-key-key-id: ${{ secrets.GATEWATCHER_APP_STORE_CONNECT_API_KEY_ID }}
134+
app-store-connect-api-key-issuer-id: ${{ secrets.GATEWATCHER_APP_STORE_CONNECT_API_ISSUER_ID }}
135+
app-store-connect-api-key-base64-private-key: ${{ secrets.GATEWATCHER_APP_STORE_CONNECT_API_BASE64_PRIVATE_KEY }}
136+
137+
- name: Configure notarization credentials
138+
if: inputs.environment == 'production'
139+
shell: bash
140+
run: |
141+
xcrun notarytool store-credentials "notarytool-password" \
142+
--key "${{ steps.setup-apple-codesign.outputs.app-store-connect-api-key-key-path }}" \
143+
--key-id "${{ steps.setup-apple-codesign.outputs.app-store-connect-api-key-key-id }}" \
144+
--issuer "${{ steps.setup-apple-codesign.outputs.app-store-connect-api-key-issuer-id }}" \
145+
--keychain "$RUNNER_TEMP/build.keychain"
146+
115147
- name: Install GoReleaser
116148
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
117149
with:
@@ -128,14 +160,16 @@ jobs:
128160
- name: Build release binaries
129161
env:
130162
TAG_NAME: ${{ inputs.tag_name }}
131-
APPLE_DEVELOPER_ID: ${{ vars.APPLE_DEVELOPER_ID }}
163+
KEYCHAIN: ${{ runner.temp }}/build.keychain
164+
DEVELOPER_ID_CERT_IDENTIFIER: ${{ vars.MAC_APP_SIGNING_IDENTITY }}
165+
DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }}
132166
run: script/release --local "$TAG_NAME" --platform macos
133167
- name: Notarize macOS archives
134168
if: inputs.environment == 'production'
135169
env:
136-
APPLE_ID: ${{ vars.APPLE_ID }}
137-
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
138-
APPLE_DEVELOPER_ID: ${{ vars.APPLE_DEVELOPER_ID }}
170+
DEVELOPER_ID_CERT_IDENTIFIER: ${{ vars.MAC_APP_SIGNING_IDENTITY }}
171+
KEYCHAIN: ${{ runner.temp }}/build.keychain
172+
DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }} # Technically redundant given the step guard above, but kept for consistency with the build step.
139173
run: |
140174
shopt -s failglob
141175
script/sign dist/gh_*_macOS_*.zip
@@ -167,6 +201,7 @@ jobs:
167201
runs-on: windows-2022
168202
environment: ${{ inputs.environment }}
169203
if: contains(inputs.platforms, 'windows')
204+
timeout-minutes: 20
170205
steps:
171206
- name: Checkout
172207
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -225,6 +260,7 @@ jobs:
225260
DLIB_PATH: ${{ runner.temp }}\acs\bin\x64\Azure.CodeSigning.Dlib.dll
226261
METADATA_PATH: ${{ runner.temp }}\acs\metadata.json
227262
TAG_NAME: ${{ inputs.tag_name }}
263+
DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }}
228264
run: script/release --local "$TAG_NAME" --platform windows
229265
- name: Set up MSBuild
230266
id: setupmsbuild
@@ -265,6 +301,7 @@ jobs:
265301
AZURE_TENANT_ID: ${{ secrets.SPN_GITHUB_CLI_SIGNING_TENANT_ID }}
266302
DLIB_PATH: ${{ runner.temp }}\acs\bin\x64\Azure.CodeSigning.Dlib.dll
267303
METADATA_PATH: ${{ runner.temp }}\acs\metadata.json
304+
DO_SIGN_ARTIFACTS: ${{ inputs.environment == 'production' }} # Technically this could just be true since we don't run this step if the environment is not production, but we keep it the same as the build step for consistency.
268305
run: |
269306
Get-ChildItem -Path .\dist -Filter *.msi | ForEach-Object {
270307
.\script\sign.ps1 $_.FullName
@@ -288,14 +325,25 @@ jobs:
288325
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
289326
- name: Merge built artifacts
290327
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
328+
- name: Generate site deploy token
329+
id: site-deploy-token
330+
if: inputs.environment == 'production'
331+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
332+
with:
333+
client-id: ${{ secrets.SITE_DEPLOY_APP_CLIENT_ID }}
334+
private-key: ${{ secrets.SITE_DEPLOY_APP_PRIVATE_KEY }}
335+
owner: github
336+
repositories: cli.github.com
291337
- name: Checkout documentation site
338+
if: ${{ inputs.environment == 'production' }}
292339
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
293340
with:
294341
repository: github/cli.github.com
295342
path: site
296343
fetch-depth: 0
297-
token: ${{ secrets.SITE_DEPLOY_PAT }}
344+
token: ${{ steps.site-deploy-token.outputs.token}}
298345
- name: Update site man pages
346+
if: ${{ inputs.environment == 'production' }}
299347
env:
300348
GIT_COMMITTER_NAME: cli automation
301349
GIT_AUTHOR_NAME: cli automation
@@ -339,32 +387,30 @@ jobs:
339387
cp script/rpmmacros ~/.rpmmacros
340388
rpmsign --addsign dist/*.rpm
341389
- name: Attest release artifacts
342-
if: inputs.environment == 'production'
390+
if: inputs.environment == 'production' && !inputs.dry_run
343391
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
344392
with:
345393
subject-path: "dist/gh_*"
346394
create-storage-record: false # (default: true)
347395
- name: Run createrepo
348-
env:
349-
GPG_SIGN: ${{ inputs.environment == 'production' }}
396+
if: ${{ inputs.environment == 'production' }}
350397
run: |
351398
mkdir -p site/packages/rpm
352399
cp dist/*.rpm site/packages/rpm/
353400
./script/createrepo.sh
354401
cp -r dist/repodata site/packages/rpm/
355402
pushd site/packages/rpm
356-
[ "$GPG_SIGN" = "false" ] || gpg --yes --detach-sign --armor repodata/repomd.xml
403+
gpg --yes --detach-sign --armor repodata/repomd.xml
357404
popd
358405
- name: Run reprepro
406+
if: ${{ inputs.environment == 'production' }}
359407
env:
360-
GPG_SIGN: ${{ inputs.environment == 'production' }}
361408
# We are no longer adding to the distribution list.
362409
# All apt distributions should use "stable" according to our install documentation.
363410
# In the future we will remove legacy distributions listed here.
364411
RELEASES: "cosmic eoan disco groovy focal stable oldstable testing sid unstable buster bullseye stretch jessie bionic trusty precise xenial hirsute impish kali-rolling"
365412
run: |
366413
mkdir -p upload
367-
[ "$GPG_SIGN" = "true" ] || sed -i.bak '/^SignWith:/d' script/distributions
368414
for release in $RELEASES; do
369415
for file in dist/*.deb; do
370416
reprepro --confdir="+b/script" includedeb "$release" "$file"
@@ -376,7 +422,7 @@ jobs:
376422
- name: Create the release
377423
env:
378424
# In non-production environments, the assets will not have been signed
379-
DO_PUBLISH: ${{ inputs.environment == 'production' }}
425+
DO_PUBLISH: ${{ inputs.environment == 'production' && !inputs.dry_run }}
380426
TAG_NAME: ${{ inputs.tag_name }}
381427
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
382428
run: |
@@ -398,8 +444,9 @@ jobs:
398444
[ "$DO_PUBLISH" = "false" ] || guard=""
399445
script/label-assets dist/gh_* | xargs $guard gh release create "${release_args[@]}" --
400446
- name: Publish site
447+
if: ${{ inputs.environment == 'production' }}
401448
env:
402-
DO_PUBLISH: ${{ inputs.environment == 'production' && !contains(inputs.tag_name, '-') }}
449+
DO_PUBLISH: ${{ inputs.environment == 'production' && !contains(inputs.tag_name, '-') && !inputs.dry_run }}
403450
TAG_NAME: ${{ inputs.tag_name }}
404451
GIT_COMMITTER_NAME: cli automation
405452
GIT_AUTHOR_NAME: cli automation
@@ -414,4 +461,4 @@ jobs:
414461
else
415462
git log --oneline @{upstream}..
416463
git diff --name-status @{upstream}..
417-
fi
464+
fi

0 commit comments

Comments
 (0)