Skip to content

Commit

Permalink
REC-55: refactor release workflow (#53)
Browse files Browse the repository at this point in the history
Previously, it was one job that ran on Linux with CI runners.
We need to sign and notarize macOS binaries though, and we can
only do that on a macOS machine.

So this change splits the workflow into multiple jobs. The first
job checks the version string, next we build binaries (signing
not done yet), last we create the release.
  • Loading branch information
jayconrod authored Oct 21, 2024
1 parent 2d27df2 commit c972047
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 144 deletions.
129 changes: 124 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,146 @@ permissions:
contents: write

env:
# TODO(REC-55): use real certificate.
APPLE_CERT_BASE64: ${{ secrets.APPLE_DEV_CERT_BASE64 }}
# Recommended here: https://github.com/bazelbuild/bazelisk/issues/88#issuecomment-625178467
BAZELISK_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLUSTER_HOST: opal.cluster.engflow.com
CRED_HELPER_TOKEN: ${{ secrets.OPAL_CRED_HELPER_TOKEN }}
DRY_RUN: ${{ inputs.dry_run }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_VERSION: ${{ inputs.version }}

jobs:
release:
check-version:
runs-on:
- self-hosted
- os=linux
- arch=x64
- "engflow-container-image=docker://645088952840.dkr.ecr.eu-west-1.amazonaws.com/engflow-ci/debian12-dind-x64@sha256:763903935682de148b4e09fe1d7ef3bbc4ec829d59c3f41cb9519984639eaa06"
- "engflow-pool=ci_sysbox_x64"
- "engflow-runtime=sysbox-runc"
- "engflow-runner-id=${{ github.repository_id }}_check-version_${{ github.run_id }}_${{ github.run_number }}_${{ github.run_attempt }}"
timeout-minutes: 1

steps:
- uses: actions/checkout@v4
- run: infra/release-check.sh

build-linux-artifacts:
# Linux is separate because we execute with CI runners, which don't support
# macOS or Windows yet. We use a Debian 11 image because binaries built on
# newer versions are incompatible with older version due to the libc runtime
# dependency.
runs-on:
- self-hosted
- os=linux
- arch=x64
- "engflow-container-image=docker://645088952840.dkr.ecr.eu-west-1.amazonaws.com/engflow-ci/debian11-dind-x64@sha256:604855f1ecd6edad3f45f513ed0112b9e8e32589871133767a1a51944b07f487"
- "engflow-pool=ci_sysbox_x64"
- "engflow-runtime=sysbox-runc"
- "engflow-runner-id=${{ github.repository_id }}_release_${{ github.run_id }}_${{ github.run_number }}_${{ github.run_attempt }}"
- "engflow-runner-id=${{ github.repository_id }}_build-linux-artifacts_${{ github.run_id }}_${{ github.run_number }}_${{ github.run_attempt }}"
timeout-minutes: 10
needs:
- check-version

env:
ARCH: "x64"
OS: "linux"

steps:
- uses: actions/checkout@v4

- name: Log in
run: infra/login.sh

- name: Build release artifacts
if: success()
run: infra/release-build.sh

- uses: actions/upload-artifact@v4
if: success()
with:
path: _out
if-no-files-found: error
retention-days: 1
overwrite: true

- name: Log out
run: infra/logout.sh

build-other-artifacts:
runs-on:
- self-hosted
- "os=${{ matrix.os }}"
- "arch=${{ matrix.arch }}"
- "os_distribution=${{ matrix.os_distribution }}"
- "os_version=${{ matrix.os_version }}"
- "revision=d04e89854b3931f4aaced77aa3a2fcad5834b3a6"
timeout-minutes: 10
needs:
- check-version

strategy:
fail-fast: false
matrix:
include:
- os: "macos"
arch: "arm64"
os_distribution: "monterey"
os_version: "12"

- os: "windows"
arch: "x64"
os_distribution: "server"
os_version: "2022"

env:
ARCH: ${{ matrix.arch }}
OS: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- name: release
- name: Log in
run: infra/login.sh
shell: bash

- name: Build release artifacts
if: success()
run: infra/release-build.sh
shell: bash

- uses: actions/upload-artifact@v4
if: success()
run: |
infra/release.sh "${{ inputs.version }}"
with:
path: _out
if-no-files-found: error
retention-days: 1
overwrite: true

- name: Log out
run: infra/logout.sh
shell: bash

create-release:
runs-on:
- self-hosted
- os=linux
- arch=x64
- "engflow-container-image=docker://645088952840.dkr.ecr.eu-west-1.amazonaws.com/engflow-ci/debian12-dind-x64@sha256:763903935682de148b4e09fe1d7ef3bbc4ec829d59c3f41cb9519984639eaa06"
- "engflow-pool=ci_sysbox_x64"
- "engflow-runtime=sysbox-runc"
- "engflow-runner-id=${{ github.repository_id }}_create-release_${{ github.run_id }}_${{ github.run_number }}_${{ github.run_attempt }}"
timeout-minutes: 10
needs:
- build-other-artifacts
- build-linux-artifacts

steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: _out
merge-multiple: true
- run: infra/release-create.sh
108 changes: 108 additions & 0 deletions infra/release-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env bash

# Copyright 2024 EngFlow Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o nounset -o pipefail -o errexit
[[ "${SCRIPT_DEBUG:-"off"}" == "on" ]] && set -o xtrace

if [[ -z "${OS:-}" ]]; then
echo >&2 "OS not set"
exit 1
fi
if [[ "${OS}" == 'macos' && -z "${APPLE_CERT_BASE64:-}" ]]; then
echo >&2 "APPLE_CERT_BASE64 not set"
exit 1
fi
if [[ -z "${RELEASE_VERSION:-}" ]]; then
echo >&2 "RELEASE_VERSION not set"
exit 1
fi

APPLE_CERT_ID=
install_cert () {
if [[ "${OS}" != 'macos' ]]; then
return
fi
# Files in $RUNNER_TEMP are automatically removed on completion.
local p12_path="${RUNNER_TEMP}/build_certificate.p12"
base64 --decode <<<"${APPLE_CERT_BASE64}" >"${p12_path}"
local keychain_path="${RUNNER_TEMP}/dev.keychain"
security create-keychain -p '' "${keychain_path}"
security set-keychain-settings "${keychain_path}"
security unlock-keychain -p '' "${keychain_path}"
security import "${p12_path}" -P '' -A -t cert -f pkcs12 -k "${keychain_path}"
# Allow productsign to access the identity
# This prints some info from the keychain, not sure if it's sensitive.
security >/dev/null set-key-partition-list -S apple-tool:,apple:,codesign:,productsign: -s -k '' "${keychain_path}"
# Overwrite the keychain search list with the new keychain
security list-keychain -d user -s "${keychain_path}"
local identities_path="${RUNNER_TEMP}/identities.txt"
security find-identity -v | tee "${identities_path}"
if ! grep --quiet '1 valid identities found' "${identities_path}"; then
echo >&2 "did not find exactly 1 identity"
return 1
fi
APPLE_CERT_ID=$(grep --extended-regexp --only '\b[0-9A-F]{40}\b' "${identities_path}")
echo >&2 "Installed certificate with identity ${APPLE_CERT_ID}"
}

uninstall_cert () {
if [[ "${OS}" != 'macos' ]]; then
return
fi
security list-keychains -s ~/Library/Keychains/login.keychain
security delete-keychain "${RUNNER_TEMP}/dev.keychain"
}

sign_and_notarize_binary () {
# TODO(REC-55): implement signing and notarization for macOS and Windows.
return
}

case "${OS}" in
macos)
TARGETS=(
//cmd/engflow_auth:engflow_auth_macos_arm64
//cmd/engflow_auth:engflow_auth_macos_x64
)
;;
linux)
TARGETS=(
//cmd/engflow_auth:engflow_auth_linux_arm64
//cmd/engflow_auth:engflow_auth_linux_x64
)
;;
windows)
TARGETS=(
//cmd/engflow_auth:engflow_auth_windows_x64
)
;;
esac

mkdir _out
BUILD_RELEASE_VERSION="${RELEASE_VERSION}" \
bazel build \
--config=release \
-- \
"${TARGETS[@]}"

install_cert
trap uninstall_cert EXIT

for target in "${TARGETS[@]}"; do
target_file=$(bazel cquery --output=files "${target}")
sign_and_notarize_binary "${target_file}"
cp "${target_file}" _out/
done
45 changes: 45 additions & 0 deletions infra/release-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash

# Copyright 2024 EngFlow Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o nounset -o pipefail -o errexit
[[ "${SCRIPT_DEBUG:-"off"}" == "on" ]] && set -o xtrace

if [[ -z "${RELEASE_VERSION:-}" ]]; then
echo >&2 "RELEASE_VERSION not set"
exit 1
fi

# Taken from https://semver.org/, with a `v` prepended
readonly SEMVER_REGEX='^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?$'

# Supplied version string must follow semver
if ! grep --quiet --extended-regexp "${SEMVER_REGEX}" <<<${RELEASE_VERSION}; then
echo "Supplied version string '${RELEASE_VERSION}' does not follow semver; exiting"
exit 1
fi

# If this is a release version (not a prerelease), the commit must be on main or
# the correct release branch (e.g., release/v1.2). This constraint doesn't apply
# to prereleases so we can test this workflow.
if [[ "${RELEASE_VERSION}" != *-* ]]; then
readonly EXPECTED_RELEASE_BRANCH="$(sed --regexp-extended 's|(^v[0-9]+\.[0-9]+)\..*$|release/\1|' <<<${RELEASE_VERSION})"
if ! git branch \
--contains "$(git rev-parse HEAD)" \
| grep --quiet --extended-regexp "main|${EXPECTED_RELEASE_BRANCH}"; then
echo "Commit $(git rev-parse HEAD) is not on main or release branch ${EXPECTED_RELEASE_BRANCH}; exiting"
exit 1
fi
fi
74 changes: 74 additions & 0 deletions infra/release-create.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash

# Copyright 2024 EngFlow Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o nounset -o pipefail -o errexit
[[ "${SCRIPT_DEBUG:-"off"}" == "on" ]] && set -o xtrace

if [[ -z "${RELEASE_VERSION:-}" ]]; then
echo >&2 "RELEASE_VERSION not set"
exit 1
fi

readonly ARTIFACTS_DIR=_out
readonly GH_CLI_DIR="$(mktemp -d -t 'gh_cli_XXXXXXXX')"
readonly GH_CLI_URL='https://storage.googleapis.com/engflow-tools-public/github.com/cli/cli/releases/download/v2.52.0/gh_2.52.0_linux_amd64.tar.gz'
readonly GH_CLI_EXPECTED_SHA256='3ea6ed8b2585f406a064cecd7e1501e58f56c8e7ca764ae1f3483d1b8ed68826'
readonly GH_CLI="${GH_CLI_DIR}/bin/gh"

function cleanup {
echo "[START] Temp directory cleanup"
rm -rf "${GH_CLI_DIR}"
echo "[FINISH] Temp directory cleanup"
}
trap 'cleanup' EXIT

# TODO(CUS-353): Remove this after installing Github CLI in the self-hosted
# environment (run via Docker?)
echo "[START] Downloading gh CLI"
curl --silent --location "${GH_CLI_URL}" \
| tee >(sha256sum - > "${GH_CLI_DIR}/archive_checksum.txt") \
| tar \
-C "${GH_CLI_DIR}" \
--strip-components 1 \
-xzf \
-
readonly GH_CLI_ACTUAL_SHA256="$(cat ${GH_CLI_DIR}/archive_checksum.txt | awk '{ print $1 }')"
if [[ "${GH_CLI_ACTUAL_SHA256}" != "${GH_CLI_EXPECTED_SHA256}" ]]; then
echo "SHA256 for Github CLI tarball ${GH_CLI_ACTUAL_SHA256} doesn't match expected value ${GH_CLI_ACTUAL_SHA256}; exiting"
exit 1
fi
echo "[FINISH] Downloading gh CLI"

RELEASE_ARGS=()
for file in $(find "${ARTIFACTS_DIR}" -type f); do
os=$(echo "$file" | sed -E -e 's,^.*_([^_]*)_[^_]*$,\1,' -e 's,macos,macOS,' -e 's,windows,Windows,')
arch=$(echo "$file" | sed -E -e 's,^.*_([^_]*),\1,')
arg="${file}#engflow_auth (${os}, ${arch})"
RELEASE_ARGS+=("${arg}")
done
if [[ "${DRY_RUN}" == true ]]; then
echo "Release arguments:"
for arg in "${RELEASE_ARGS[@]}"; do
echo "${arg}"
done
echo "[SKIP] Creating release"
exit 0
fi
"${GH_CLI}" release create \
"${RELEASE_VERSION}" \
--generate-notes \
"${RELEASE_ARGS[@]}"
echo "[FINISH] Creating release"
Loading

0 comments on commit c972047

Please sign in to comment.