Skip to content

release

release #462

Workflow file for this run

name: release
on:
workflow_dispatch:
permissions:
id-token: write
contents: write
jobs:
prereqs:
name: Prerequisites
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Set version
run: echo "version=$(cat VERSION | sed -E 's/.[0-9]+$//')" >> $GITHUB_OUTPUT
id: version
# ================================
# macOS
# ================================
create-macos-artifacts:
name: Create macOS artifacts
runs-on: macos-latest
environment: release
needs: prereqs
strategy:
matrix:
runtime: [ osx-x64, osx-arm64 ]
steps:
- uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Build
run: |
dotnet build src/osx/Installer.Mac/*.csproj \
--configuration=MacRelease --no-self-contained \
--runtime=${{ matrix.runtime }}
- name: Run macOS unit tests
run: |
dotnet test --configuration=MacRelease
- name: Lay out payload and symbols
run: |
src/osx/Installer.Mac/layout.sh \
--configuration=MacRelease --output=payload \
--symbol-output=symbols --runtime=${{ matrix.runtime }}
- name: Set up signing/notarization infrastructure
env:
A1: ${{ secrets.APPLICATION_CERTIFICATE_BASE64 }}
A2: ${{ secrets.APPLICATION_CERTIFICATE_PASSWORD }}
I1: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }}
I2: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }}
N1: ${{ secrets.APPLE_TEAM_ID }}
N2: ${{ secrets.APPLE_DEVELOPER_ID }}
N3: ${{ secrets.APPLE_DEVELOPER_PASSWORD }}
N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }}
run: |
echo "Setting up signing certificates"
security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
security default-keychain -s $RUNNER_TEMP/buildagent.keychain
security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain
echo $A1 | base64 -D > $RUNNER_TEMP/cert.p12
security import $RUNNER_TEMP/cert.p12 \
-k $RUNNER_TEMP/buildagent.keychain \
-P $A2 \
-T /usr/bin/codesign
security set-key-partition-list \
-S apple-tool:,apple:,codesign: \
-s -k pwd \
$RUNNER_TEMP/buildagent.keychain
echo $I1 | base64 -D > $RUNNER_TEMP/cert.p12
security import $RUNNER_TEMP/cert.p12 \
-k $RUNNER_TEMP/buildagent.keychain \
-P $I2 \
-T /usr/bin/productbuild
security set-key-partition-list \
-S apple-tool:,apple:,productbuild: \
-s -k pwd \
$RUNNER_TEMP/buildagent.keychain
echo "Setting up notarytool"
xcrun notarytool store-credentials \
--team-id $N1 \
--apple-id $N2 \
--password $N3 \
"$N4"
- name: Run codesign against payload
env:
A3: ${{ secrets.APPLE_APPLICATION_SIGNING_IDENTITY }}
run: |
./src/osx/Installer.Mac/codesign.sh "payload" "$A3" \
"$GITHUB_WORKSPACE/src/osx/Installer.Mac/entitlements.xml"
- name: Create component package
run: |
src/osx/Installer.Mac/pack.sh --payload="payload" \
--version="${{ needs.prereqs.outputs.version }}" \
--output="components/com.microsoft.gitcredentialmanager.component.pkg"
- name: Create and sign product archive
env:
I3: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }}
run: |
src/osx/Installer.Mac/dist.sh --package-path=components \
--version="${{ needs.prereqs.outputs.version }}" \
--runtime="${{ matrix.runtime }}" \
--output="pkg/gcm-${{ matrix.runtime }}-${{ needs.prereqs.outputs.version }}.pkg" \
--identity="$I3" || exit 1
- name: Notarize product archive
env:
N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }}
run: |
src/osx/Installer.Mac/notarize.sh \
--package="pkg/gcm-${{ matrix.runtime }}-${{ needs.prereqs.outputs.version }}.pkg" \
--keychain-profile="$N4"
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: macos-${{ matrix.runtime }}-artifacts
path: |
./pkg/*
./symbols/*
./payload/*
# ================================
# Windows
# ================================
create-windows-artifacts:
name: Create Windows Artifacts
runs-on: windows-latest
environment: release
needs: prereqs
steps:
- uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Build
run: |
dotnet build --configuration=WindowsRelease
- name: Run Windows unit tests
run: |
dotnet test --configuration=WindowsRelease
- name: Lay out Windows payload and symbols
run: |
cd $env:GITHUB_WORKSPACE\src\windows\Installer.Windows\
./layout.ps1 -Configuration WindowsRelease `
-Output $env:GITHUB_WORKSPACE\payload `
-SymbolOutput $env:GITHUB_WORKSPACE\symbols
- name: Log into Azure
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sign payload files with Azure Code Signing
uses: azure/azure-code-signing-action@v0.2.21
with:
endpoint: https://wus2.codesigning.azure.net/
code-signing-account-name: git-fundamentals-signing
certificate-profile-name: git-fundamentals-windows-signing
files-folder: ${{ github.workspace }}\payload
files-folder-filter: exe,dll
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
# The Azure Code Signing action overrides the .NET version, so we reset it.
- name: Set up .NET
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Build with signed payload
run: |
dotnet build $env:GITHUB_WORKSPACE\src\windows\Installer.Windows `
/p:PayloadPath=$env:GITHUB_WORKSPACE\payload /p:NoLayout=true `
--configuration=WindowsRelease
mkdir installers
Move-Item -Path .\out\windows\Installer.Windows\bin\Release\net472\*.exe `
-Destination $env:GITHUB_WORKSPACE\installers
- name: Sign installers with Azure Code Signing
uses: azure/azure-code-signing-action@v0.2.21
with:
endpoint: https://wus2.codesigning.azure.net/
code-signing-account-name: git-fundamentals-signing
certificate-profile-name: git-fundamentals-windows-signing
files-folder: ${{ github.workspace }}\installers
files-folder-filter: exe
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: windows-artifacts
path: |
payload
installers
symbols
# ================================
# Linux
# ================================
create-linux-artifacts:
name: Create Linux Artifacts
runs-on: ubuntu-latest
environment: release
needs: prereqs
steps:
- uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Build
run: dotnet build --configuration=LinuxRelease
- name: Run Linux unit tests
run: |
dotnet test --configuration=LinuxRelease
- name: Log into Azure
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Prepare for GPG signing
env:
AZURE_VAULT: ${{ secrets.AZURE_VAULT }}
GPG_KEY_SECRET_NAME: ${{ secrets.GPG_KEY_SECRET_NAME }}
GPG_PASSPHRASE_SECRET_NAME: ${{ secrets.GPG_PASSPHRASE_SECRET_NAME }}
GPG_KEYGRIP_SECRET_NAME: ${{ secrets.GPG_KEYGRIP_SECRET_NAME }}
run: |
# Install debsigs
sudo apt install debsigs
# Download GPG key, passphrase, and keygrip from Azure Key Vault
key=$(az keyvault secret show --name $GPG_KEY_SECRET_NAME --vault-name $AZURE_VAULT --query "value")
passphrase=$(az keyvault secret show --name $GPG_PASSPHRASE_SECRET_NAME --vault-name $AZURE_VAULT --query "value")
keygrip=$(az keyvault secret show --name $GPG_KEYGRIP_SECRET_NAME --vault-name $AZURE_VAULT --query "value")
# Remove quotes from downloaded values
key=$(sed -e 's/^"//' -e 's/"$//' <<<"$key")
passphrase=$(sed -e 's/^"//' -e 's/"$//' <<<"$passphrase")
keygrip=$(sed -e 's/^"//' -e 's/"$//' <<<"$keygrip")
# Import GPG key
echo "$key" | base64 -d | gpg --import --no-tty --batch --yes
# Configure GPG
echo "allow-preset-passphrase" > ~/.gnupg/gpg-agent.conf
gpg-connect-agent RELOADAGENT /bye
/usr/lib/gnupg2/gpg-preset-passphrase --preset "$keygrip" <<<"$passphrase"
- name: Sign Debian package and tarball
run: |
# Sign Debian package
version=${{ needs.prereqs.outputs.version }}
mv out/linux/Packaging.Linux/Release/deb/gcm-linux_amd64.$version.deb .
debsigs --sign=origin --verify --check gcm-linux_amd64.$version.deb
# Generate tarball signature file
mv -v out/linux/Packaging.Linux/Release/tar/* .
gpg --batch --yes --armor --output gcm-linux_amd64.$version.tar.gz.asc \
--detach-sig gcm-linux_amd64.$version.tar.gz
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: linux-artifacts
path: |
./*.deb
./*.asc
./*.tar.gz
# ================================
# .NET Tool
# ================================
dotnet-tool-build:
name: Build .NET tool
runs-on: ubuntu-latest
needs: prereqs
steps:
- uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Build .NET tool
run: |
src/shared/DotnetTool/layout.sh --configuration=Release
- name: Upload .NET tool artifacts
uses: actions/upload-artifact@v3
with:
name: tmp.dotnet-tool-build
path: |
out/shared/DotnetTool/nupkg/Release
dotnet-tool-payload-sign:
name: Sign .NET tool payload
# ESRP service requires signing to run on Windows
runs-on: windows-latest
environment: release
needs: dotnet-tool-build
steps:
- uses: actions/checkout@v4
- name: Download payload
uses: actions/download-artifact@v3
with:
name: tmp.dotnet-tool-build
- name: Zip unsigned payload
shell: pwsh
run: |
Compress-Archive -Path payload payload/payload.zip
cd payload
Get-ChildItem -Exclude payload.zip | Remove-Item -Recurse -Force
- name: Log into Azure
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Set up ESRP client
shell: pwsh
env:
AZURE_VAULT: ${{ secrets.AZURE_VAULT }}
AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }}
AZURE_STORAGE_CONTAINER: ${{ secrets.AZURE_STORAGE_CONTAINER }}
ESRP_TOOL: ${{ secrets.ESRP_TOOL }}
AUTH_CERT: ${{ secrets.AZURE_VAULT_AUTH_CERT_NAME }}
REQUEST_SIGNING_CERT: ${{ secrets.AZURE_VAULT_REQUEST_SIGNING_CERT_NAME }}
run: |
.github\set_up_esrp.ps1
- name: Run ESRP client
shell: pwsh
env:
AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }}
NUGET_KEY_CODE: ${{ secrets.NUGET_KEY_CODE }}
NUGET_OPERATION_CODE: ${{ secrets.NUGET_OPERATION_CODE }}
run: |
python .github\run_esrp_signing.py payload `
$env:NUGET_KEY_CODE $env:NUGET_OPERATION_CODE
- name: Lay out signed payload, images, and symbols
shell: bash
run: |
mkdir dotnet-tool-payload-sign
rm -rf payload
mv images payload.sym -t dotnet-tool-payload-sign
unzip signed/payload.zip -d dotnet-tool-payload-sign
- name: Upload signed payload
uses: actions/upload-artifact@v3
with:
name: dotnet-tool-payload-sign
path: |
dotnet-tool-payload-sign
dotnet-tool-pack:
name: Package .NET tool
runs-on: ubuntu-latest
needs: [ prereqs, dotnet-tool-payload-sign ]
steps:
- uses: actions/checkout@v4
- name: Download signed payload
uses: actions/download-artifact@v3
with:
name: dotnet-tool-payload-sign
path: signed
- name: Set up .NET
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Package tool
run: |
src/shared/DotnetTool/pack.sh --configuration=Release \
--version="${{ needs.prereqs.outputs.version }}" \
--publish-dir=$(pwd)/signed
- name: Upload unsigned package
uses: actions/upload-artifact@v3
with:
name: tmp.dotnet-tool-package-unsigned
path: |
out/shared/DotnetTool/nupkg/Release/*.nupkg
dotnet-tool-sign:
name: Sign .NET tool package
# ESRP service requires signing to run on Windows
runs-on: windows-latest
environment: release
needs: dotnet-tool-pack
steps:
- uses: actions/checkout@v4
- name: Download unsigned package
uses: actions/download-artifact@v3
with:
name: tmp.dotnet-tool-package-unsigned
path: nupkg
- name: Zip unsigned package
shell: pwsh
run: |
Compress-Archive -Path nupkg/*.nupkg nupkg/gcm-nupkg.zip
cd nupkg
Get-ChildItem -Exclude gcm-nupkg.zip | Remove-Item -Recurse -Force
- name: Log into Azure
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Set up ESRP client
shell: pwsh
env:
AZURE_VAULT: ${{ secrets.AZURE_VAULT }}
AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }}
AZURE_STORAGE_CONTAINER: ${{ secrets.AZURE_STORAGE_CONTAINER }}
ESRP_TOOL: ${{ secrets.ESRP_TOOL }}
AUTH_CERT: ${{ secrets.AZURE_VAULT_AUTH_CERT_NAME }}
REQUEST_SIGNING_CERT: ${{ secrets.AZURE_VAULT_REQUEST_SIGNING_CERT_NAME }}
run: |
.github\set_up_esrp.ps1
- name: Sign package
shell: pwsh
env:
AZURE_AAD_ID: ${{ secrets.AZURE_AAD_ID }}
NUGET_KEY_CODE: ${{ secrets.NUGET_KEY_CODE }}
NUGET_OPERATION_CODE: ${{ secrets.NUGET_OPERATION_CODE }}
run: |
python .github\run_esrp_signing.py nupkg $env:NUGET_KEY_CODE $env:NUGET_OPERATION_CODE
- name: Unzip signed package
shell: pwsh
run: |
Expand-Archive -LiteralPath signed\gcm-nupkg.zip -DestinationPath .\signed -Force
Remove-Item signed\gcm-nupkg.zip -Force
- name: Publish signed package
uses: actions/upload-artifact@v3
with:
name: dotnet-tool-sign
path: signed/*.nupkg
# ================================
# Validate
# ================================
validate:
name: Validate installers
strategy:
matrix:
component:
- os: ubuntu-latest
artifact: linux-artifacts
command: git-credential-manager
description: linux
- os: macos-latest
artifact: macos-osx-x64-artifacts
command: git-credential-manager
description: osx-x64
- os: windows-latest
artifact: windows-artifacts
# Even when a standalone GCM version is installed, GitHub actions
# runners still only recognize the version bundled with Git for
# Windows due to its placement on the PATH. For this reason, we use
# the full path to our installation to validate the Windows version.
command: "$PROGRAMFILES (x86)/Git Credential Manager/git-credential-manager.exe"
description: windows
- os: ubuntu-latest
artifact: dotnet-tool-sign
command: git-credential-manager
description: dotnet-tool
runs-on: ${{ matrix.component.os }}
needs: [ create-macos-artifacts, create-windows-artifacts, create-linux-artifacts, dotnet-tool-sign ]
steps:
- uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: ${{ matrix.component.artifact }}
- name: Install Windows
if: contains(matrix.component.description, 'windows')
shell: pwsh
run: |
$exePaths = Get-ChildItem -Path ./installers/*.exe | %{$_.FullName}
foreach ($exePath in $exePaths)
{
Start-Process -Wait -FilePath "$exePath" -ArgumentList "/SILENT /VERYSILENT /NORESTART"
}
- name: Install Linux (Debian package)
if: contains(matrix.component.description, 'linux')
run: |
debpath=$(find ./*.deb)
sudo apt install $debpath
"${{ matrix.component.command }}" configure
- name: Install Linux (tarball)
if: contains(matrix.component.description, 'linux')
run: |
# Ensure we find only the source tarball, not the symbols
tarpath=$(find . -name '*[[:digit:]].tar.gz')
tar -xvf $tarpath -C /usr/local/bin
"${{ matrix.component.command }}" configure
- name: Install macOS
if: contains(matrix.component.description, 'osx-x64')
run: |
# Only validate x64, given arm64 agents are not available
pkgpath=$(find ./pkg/*.pkg)
sudo installer -pkg $pkgpath -target /
- name: Install .NET tool
if: contains(matrix.component.description, 'dotnet-tool')
run: |
nupkgpath=$(find ./*.nupkg)
dotnet tool install -g --add-source $(dirname "$nupkgpath") git-credential-manager
"${{ matrix.component.command }}" configure
- name: Validate
shell: bash
run: |
"${{ matrix.component.command }}" --version | sed 's/+.*//' >actual
cat VERSION | sed -E 's/.[0-9]+$//' >expect
cmp expect actual || exit 1
# ================================
# Publish
# ================================
create-github-release:
name: Publish GitHub draft release
runs-on: ubuntu-latest
env:
AZURE_VAULT: ${{ secrets.AZURE_VAULT }}
GPG_PUBLIC_KEY_SECRET_NAME: ${{ secrets.GPG_PUBLIC_KEY_SECRET_NAME }}
environment: release
needs: [ prereqs, validate ]
steps:
- uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Archive macOS payload and symbols
run: |
version="${{ needs.prereqs.outputs.version }}"
mkdir osx-payload-and-symbols
tar -C macos-osx-x64-artifacts/payload -czf osx-payload-and-symbols/gcm-osx-x64-$version.tar.gz .
tar -C macos-osx-x64-artifacts/symbols -czf osx-payload-and-symbols/gcm-osx-x64-$version-symbols.tar.gz .
tar -C macos-osx-arm64-artifacts -czf osx-payload-and-symbols/gcm-osx-arm64-$version.tar.gz .
tar -C macos-osx-arm64-artifacts/symbols -czf osx-payload-and-symbols/gcm-osx-arm64-$version-symbols.tar.gz .
- name: Log into Azure
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Download GPG public key signature file
run: |
az keyvault secret show --name "$GPG_PUBLIC_KEY_SECRET_NAME" \
--vault-name "$AZURE_VAULT" --query "value" \
| sed -e 's/^"//' -e 's/"$//' | base64 -d >gcm-public.asc
mv gcm-public.asc linux-artifacts
- name: Archive Windows payload and symbols
run: |
version="${{ needs.prereqs.outputs.version }}"
mkdir win-x86-payload-and-symbols
zip -jr win-x86-payload-and-symbols/gcm-win-x86-$VERSION.zip windows-artifacts/payload
zip -jr win-x86-payload-and-symbols/gcm-win-x86-$VERSION-symbols.zip windows-artifacts/symbols
- uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const path = require('path');
const version = "${{ needs.prereqs.outputs.version }}"
var releaseMetadata = {
owner: context.repo.owner,
repo: context.repo.repo
};
// Create the release
var tagName = `v${version}`;
var createdRelease = await github.rest.repos.createRelease({
...releaseMetadata,
draft: true,
tag_name: tagName,
target_commitish: context.sha,
name: `GCM ${version}`
});
releaseMetadata.release_id = createdRelease.data.id;
// Uploads contents of directory to the release created above
async function uploadDirectoryToRelease(directory, includeExtensions=[]) {
return fs.promises.readdir(directory)
.then(async(files) => Promise.all(
files.filter(file => {
return includeExtensions.length==0 || includeExtensions.includes(path.extname(file).toLowerCase());
})
.map(async (file) => {
var filePath = path.join(directory, file);
github.rest.repos.uploadReleaseAsset({
...releaseMetadata,
name: file,
headers: {
"content-length": (await fs.promises.stat(filePath)).size
},
data: fs.createReadStream(filePath)
});
}))
);
}
await Promise.all([
// Upload Windows artifacts
uploadDirectoryToRelease('windows-artifacts/installers'),
uploadDirectoryToRelease('win-x86-payload-and-symbols'),
// Upload macOS artifacts
uploadDirectoryToRelease('macos-osx-x64-artifacts/pkg'),
uploadDirectoryToRelease('macos-osx-arm64-artifacts/pkg'),
uploadDirectoryToRelease('osx-payload-and-symbols'),
// Upload Linux artifacts
uploadDirectoryToRelease('linux-artifacts'),
// Upload .NET tool package
uploadDirectoryToRelease('dotnet-tool-sign'),
]);