diff --git a/.asf.yaml b/.asf.yaml index d24b14e..844123f 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -28,6 +28,9 @@ project: programming_languages: - Rust policy: + github_repository_name: asyncband + github_compose_workflow_path: + - .github/workflows/release-compose.yml source_artifact_paths: - "apache-asyncband-*-incubating-src.tar.gz" vote_mode: email diff --git a/.github/workflows/release-compose.yml b/.github/workflows/release-compose.yml new file mode 100644 index 0000000..593f15f --- /dev/null +++ b/.github/workflows/release-compose.yml @@ -0,0 +1,145 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +name: Compose source release + +on: + push: + tags: [ "v*.*.*-rc.*" ] + pull_request: + paths: + - .github/workflows/release-compose.yml + - .asf.yaml + +permissions: + contents: read + id-token: write + +defaults: + run: + shell: bash + +env: + LC_ALL: C + +concurrency: + group: source-release-${{ github.event_name == 'pull_request' && github.event.number || 'compose' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + compose: + name: Compose source release + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.12" + - id: source + name: Archive the release candidate + run: | + set -euo pipefail + commit="$(git rev-parse HEAD)" + package_version="$(git show "${commit}:asyncband/Cargo.toml" | python3 -c 'import sys, tomllib; print(tomllib.loads(sys.stdin.read())["package"]["version"])')" + if [[ "${GITHUB_EVENT_NAME}" == push ]]; then + if [[ ! "${GITHUB_REF_NAME}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-rc\.([1-9][0-9]*)$ ]]; then + echo "::error::Expected an RC tag named vX.Y.Z-rc.N with a positive RC number." + exit 1 + fi + version="${BASH_REMATCH[1]}" + if [[ "${version}" != "${package_version}" ]]; then + echo "::error::Tag ${GITHUB_REF_NAME} does not match package version ${package_version}." + exit 1 + fi + test "$(git rev-parse "${GITHUB_REF}^{commit}")" = "${commit}" + git merge-base --is-ancestor "${commit}" origin/main + else + version="${package_version}" + fi + if [[ ! "${version}" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "::error::The package must have a stable X.Y.Z version." + exit 1 + fi + source="apache-asyncband-${version}-incubating-src" + mkdir "${RUNNER_TEMP}/source" + git -c core.attributesFile=/dev/null -c tar.umask=0022 archive \ + --format=tar --prefix="${source}/" "${commit}" \ + | gzip -n -9 > "${RUNNER_TEMP}/source/${source}.tar.gz" + cd "${RUNNER_TEMP}/source" + shasum -a 512 "${source}.tar.gz" > "${source}.tar.gz.sha512" + echo "commit=${commit}" >> "${GITHUB_OUTPUT}" + echo "version=${version}" >> "${GITHUB_OUTPUT}" + echo "archive=${source}.tar.gz" >> "${GITHUB_OUTPUT}" + { + echo "Source ref: ${GITHUB_REF}" + echo "Source commit: ${commit}" + echo + cat "${source}.tar.gz.sha512" + } >> "${GITHUB_STEP_SUMMARY}" + - id: sign + name: Sign with the ASF project key + if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && github.repository == 'apache/asyncband' }} + env: + ARCHIVE: ${{ steps.source.outputs.archive }} + SIGNING_KEY: ${{ secrets.GPG_SECRET_KEY }} + SIGNING_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + SIGNING_FINGERPRINT: ${{ vars.SOURCE_SIGNING_FINGERPRINT }} + run: | + set -euo pipefail + : "${SIGNING_KEY:?Configure the ASF project key in GPG_SECRET_KEY.}" + if [[ ! "${SIGNING_FINGERPRINT}" =~ ^([0-9A-F]{40}|[0-9A-F]{64})$ ]]; then + echo "::error::SOURCE_SIGNING_FINGERPRINT must be the full uppercase primary fingerprint." + exit 1 + fi + cd "${RUNNER_TEMP}/source" + GNUPGHOME="$(mktemp -d "${RUNNER_TEMP}/gnupg.XXXXXX")" + export GNUPGHOME + trap 'gpgconf --kill gpg-agent; rm -rf -- "${GNUPGHOME}"' EXIT + printf '%s' "${SIGNING_KEY}" | gpg --batch --import + unset SIGNING_KEY + gpg --batch --list-secret-keys "${SIGNING_FINGERPRINT}" + printf '%s' "${SIGNING_PASSPHRASE}" | gpg --batch --pinentry-mode loopback \ + --passphrase-fd 0 --armor --detach-sign --local-user "${SIGNING_FINGERPRINT}" "${ARCHIVE}" + gpg --batch --status-fd 1 --verify "${ARCHIVE}.asc" "${ARCHIVE}" \ + | awk -v fingerprint="${SIGNING_FINGERPRINT}" \ + '$2 == "VALIDSIG" && ($3 == fingerprint || $NF == fingerprint) { valid = 1 } END { exit !valid }' + - name: Upload to ATR compose + if: ${{ steps.sign.outcome == 'success' }} + uses: apache/tooling-actions/upload-to-atr@fa721a0b176d713807b574da721b96545b587eea + with: + project: asyncband + version: ${{ steps.source.outputs.version }} + src: ${{ runner.temp }}/source + - name: Record the candidate handoff + if: ${{ steps.sign.outcome == 'success' }} + env: + SOURCE_COMMIT: ${{ steps.source.outputs.commit }} + VERSION: ${{ steps.source.outputs.version }} + ARCHIVE: ${{ steps.source.outputs.archive }} + run: | + { + echo "Uploaded RC tag ${GITHUB_REF_NAME} (${SOURCE_COMMIT}) to ATR project asyncband, version ${VERSION}." + echo + cat "${RUNNER_TEMP}/source/${ARCHIVE}.sha512" + echo + echo "Inspect ATR checks and record the candidate URL and revision before starting a vote." + echo "Inspect ATR before rerunning this job after an upload failure; reruns rebuild and re-sign the same RC tag." + } >> "${GITHUB_STEP_SUMMARY}"