Skip to content
Open
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
91 changes: 91 additions & 0 deletions .github/actions/create-draft-release/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Create draft GitHub Release
description: >-
Write an authorized-publication record (who released what, with SHA-256 of
every artifact), then create or update a draft GitHub Release for a pushed
tag and attach the distributions, their detached .sig signatures, and the
record. Requires release-setup to have run first. Mirrors the
authorized-pub action plus the draft-release step of
mongodb-labs/drivers-github-tools/python/post-publish@v3.

inputs:
tag:
description: Git tag to release. It must already exist on origin unless dry_run is "true".
required: true
title:
description: Release title.
required: true
product_name:
description: Product name recorded in the authorized-publication record.
required: true
dist_path:
description: Directory holding the distribution files that were published.
required: true
token:
description: GitHub token with contents:write on this repository.
required: true
dry_run:
description: When "true", build the asset set and summary but do not create a release.
default: "false"

runs:
using: composite
steps:
- name: Write authorized-publication record
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
PRODUCT_NAME: ${{ inputs.product_name }}
TAG: ${{ inputs.tag }}
DIST_PATH: ${{ inputs.dist_path }}
run: |
set -eu
: "${RELEASE_ASSETS:?run release-setup before create-draft-release}"
releaser="$(gh api "users/${GITHUB_ACTOR}" --jq '.name // .login')"
commit="$(git rev-parse HEAD 2>/dev/null || echo "$GITHUB_SHA")"
report="$RELEASE_ASSETS/authorized-publication.txt"
{
echo "Product: $PRODUCT_NAME"
echo "Version: $TAG"
echo "Commit: $commit"
echo "Releaser: $releaser"
echo "Build Source: GitHub Actions"
echo "Build Number: $GITHUB_RUN_ID"
for f in "$DIST_PATH"/*; do
[ -f "$f" ] || continue
echo "Filename: $(basename "$f")"
echo "Shasum: $(sha256sum "$f" | awk '{print $1}')"
done
} > "$report"
cat "$report"

- name: Create or update draft release
shell: bash
env:
GH_TOKEN: ${{ inputs.token }}
TAG: ${{ inputs.tag }}
TITLE: ${{ inputs.title }}
DIST_PATH: ${{ inputs.dist_path }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -eu
if [ "$DRY_RUN" = "true" ]; then
{
echo "### Dry run: release $TAG not created"
echo '```'
ls -l "$RELEASE_ASSETS" "$DIST_PATH"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

if ! gh release view "$TAG" > /dev/null 2>&1; then
gh release create "$TAG" --draft --verify-tag --title "$TITLE" \
--notes "Draft release for $TAG. Each .sig file is a detached, armored GPG signature over the artifact of the same name; see authorized-publication.txt for SHA-256 sums."
fi
gh release upload "$TAG" "$RELEASE_ASSETS"/* "$DIST_PATH"/* --clobber

{
echo "### $TITLE"
gh release view "$TAG" --json url,tagName,isDraft,assets \
--jq '"URL: \(.url)\nTag: \(.tagName)\nDraft: \(.isDraft)\nAssets:\n" + (.assets | map("- " + .name) | join("\n"))'
} >> "$GITHUB_STEP_SUMMARY"
37 changes: 37 additions & 0 deletions .github/actions/garasign-git/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Run git inside the Garasign signing container
description: >-
Run one git command inside the Garasign container with the team GPG key
loaded, so tags (and commits) can be created with -s / -S. Requires
release-setup to have run first. Mirrors
mongodb-labs/drivers-github-tools/git-sign@v3.

inputs:
command:
description: The git command to run, as a single string.
required: true
ecr_registry:
description: ECR registry that hosts the Garasign images.
default: "901841024863.dkr.ecr.us-east-1.amazonaws.com"
ecr_repository:
description: Garasign git image repository.
default: release-infrastructure/garasign-git

runs:
using: composite
steps:
- name: Run signed git command
shell: bash
env:
ECR_REGISTRY: ${{ inputs.ecr_registry }}
ECR_REPOSITORY: ${{ inputs.ecr_repository }}
COMMAND: ${{ inputs.command }}
run: |
set -eu
: "${GARASIGN_ENVFILE:?run release-setup before garasign-git}"
WORKDIR=/home/git-checkout
docker run --rm \
--env-file="$GARASIGN_ENVFILE" \
-v "$(pwd):$WORKDIR" \
-w "$WORKDIR" \
"${ECR_REGISTRY}/${ECR_REPOSITORY}" \
/bin/bash -c "git config --global --add safe.directory $WORKDIR && gpgloader && ${COMMAND}"
101 changes: 101 additions & 0 deletions .github/actions/release-setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Release setup
description: >-
Assume the AWS release role through GitHub OIDC, log in to the Garasign ECR
registry, load signing secrets from AWS Secrets Manager, and export the
environment variables used by the garasign-git, sign-artifacts, and
create-draft-release actions. Mirrors mongodb-labs/drivers-github-tools/setup@v3.

inputs:
aws_role_arn:
description: AWS IAM role (trusting this repository's OIDC subject) that can read the release secret.
required: true
aws_region:
description: AWS region of the release role and Secrets Manager secret.
required: true
aws_secret_id:
description: >-
AWS Secrets Manager secret id. Its JSON must contain garasign-username,
garasign-password, and gpg-key-id; gpg-public-url is optional.
required: true
ecr_registry_id:
description: AWS account id of the ECR registry that hosts the Garasign images.
default: "901841024863"
ecr_role_arn:
description: Read-only role used to pull the Garasign images from ECR.
default: "arn:aws:iam::901841024863:role/ecr-role-gha-ro"
ecr_region:
description: Region of the ECR registry.
default: us-east-1

runs:
using: composite
steps:
# ECR login happens first so the Docker credential survives the second
# role assumption below.
- name: Assume ECR read-only role
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
role-to-assume: ${{ inputs.ecr_role_arn }}
role-session-name: release-ecr
aws-region: ${{ inputs.ecr_region }}

- name: Log in to the Garasign ECR registry
uses: aws-actions/amazon-ecr-login@03f1aad4c6c7ffd436567f42f9384779290529bd # v2
with:
registries: ${{ inputs.ecr_registry_id }}

- name: Assume release secrets role
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
role-to-assume: ${{ inputs.aws_role_arn }}
role-session-name: release-secrets
aws-region: ${{ inputs.aws_region }}

- name: Load release secrets into the environment
uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 # v3.0.1
with:
secret-ids: ${{ inputs.aws_secret_id }}
parse-json-secrets: true

- name: Export Garasign, GPG, and release-asset variables
shell: bash
env:
AWS_SECRET_ID: ${{ inputs.aws_secret_id }}
run: |
set -eu
# aws-secretsmanager-get-secrets exports each JSON key as
# <SECRET_ID>_<KEY>, upper-cased with "-" and "/" folded to "_".
# Strip that prefix so the keys become GARASIGN_USERNAME, GPG_KEY_ID, ...
prefix="$(echo "$AWS_SECRET_ID" | tr '[:lower:]' '[:upper:]' | sed -r 's/[-/]+/_/g')_"
for var in $(compgen -A variable | grep "^${prefix}" || true); do
declare "${var#"$prefix"}=${!var}"
done
: "${GARASIGN_USERNAME:?missing garasign-username in $AWS_SECRET_ID}"
: "${GARASIGN_PASSWORD:?missing garasign-password in $AWS_SECRET_ID}"
: "${GPG_KEY_ID:?missing gpg-key-id in $AWS_SECRET_ID}"

umask 077
GARASIGN_ENVFILE=/tmp/garasign-envfile
cat > "$GARASIGN_ENVFILE" <<EOF
GRS_CONFIG_USER1_USERNAME=$GARASIGN_USERNAME
GRS_CONFIG_USER1_PASSWORD=$GARASIGN_PASSWORD
EOF

RELEASE_ASSETS=/tmp/release-assets
mkdir -p "$RELEASE_ASSETS"
echo "$GITHUB_RUN_ID" > "$RELEASE_ASSETS/release_run_id.txt"

{
echo "GARASIGN_ENVFILE=$GARASIGN_ENVFILE"
echo "GPG_KEY_ID=$GPG_KEY_ID"
echo "GPG_PUBLIC_URL=${GPG_PUBLIC_URL:-}"
echo "RELEASE_ASSETS=$RELEASE_ASSETS"
} >> "$GITHUB_ENV"

# Local (not --global) so the mounted checkout carries the identity into
# the Garasign container when it creates the annotated tag.
- name: Configure git identity for release automation
shell: bash
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
58 changes: 58 additions & 0 deletions .github/actions/sign-artifacts/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Sign artifacts with Garasign
description: >-
Create an armored, detached GPG signature (<file>.sig) for each artifact
using the Garasign container and move the signatures into $RELEASE_ASSETS.
Requires release-setup to have run first. Mirrors
mongodb-labs/drivers-github-tools/gpg-sign@v3.

inputs:
filenames:
description: Space-separated file paths or glob patterns to sign (for example "dist/*").
required: true
ecr_registry:
description: ECR registry that hosts the Garasign images.
default: "901841024863.dkr.ecr.us-east-1.amazonaws.com"
ecr_repository:
description: Garasign git image repository (it also carries gpg).
default: release-infrastructure/garasign-git

runs:
using: composite
steps:
- name: Create detached signatures
shell: bash
env:
ECR_REGISTRY: ${{ inputs.ecr_registry }}
ECR_REPOSITORY: ${{ inputs.ecr_repository }}
FILENAMES: ${{ inputs.filenames }}
run: |
set -eu
: "${GARASIGN_ENVFILE:?run release-setup before sign-artifacts}"
: "${RELEASE_ASSETS:?run release-setup before sign-artifacts}"

# Expand globs on the host so the container receives concrete paths.
files=()
for pattern in $FILENAMES; do
for f in $pattern; do
if [ -f "$f" ]; then
files+=("$f")
fi
done
done
if [ "${#files[@]}" -eq 0 ]; then
echo "::error::No files matched '$FILENAMES'"
exit 1
fi

docker run --rm \
--env-file="$GARASIGN_ENVFILE" \
-v "$(pwd):$(pwd)" \
-w "$(pwd)" \
"${ECR_REGISTRY}/${ECR_REPOSITORY}" \
/bin/bash -c "gpgloader && for f in ${files[*]}; do gpg --detach-sign --armor --output \"\$f.sig\" \"\$f\"; done"

echo "### Signed artifacts" >> "$GITHUB_STEP_SUMMARY"
for f in "${files[@]}"; do
mv "$f.sig" "$RELEASE_ASSETS/"
echo "- $(basename "$f") -> $(basename "$f").sig" >> "$GITHUB_STEP_SUMMARY"
done
Loading