Skip to content

Commit 57f1603

Browse files
authored
chore: Experimental GitHub Actions based workflow for publishing releases (#402)
* Experimental release workflow * Release note generation and more improvements * Simplified release process
1 parent ccefa63 commit 57f1603

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed

.github/scripts/generate_changelog.sh

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/bin/bash
2+
3+
set -e
4+
set -u
5+
6+
function printChangelog() {
7+
local TITLE=$1
8+
shift
9+
# Skip the sentinel value.
10+
local ENTRIES=("${@:2}")
11+
if [ ${#ENTRIES[@]} -ne 0 ]; then
12+
echo "### ${TITLE}"
13+
echo ""
14+
for ((i = 0; i < ${#ENTRIES[@]}; i++))
15+
do
16+
echo "* ${ENTRIES[$i]}"
17+
done
18+
echo ""
19+
fi
20+
}
21+
22+
if [[ -z "${GITHUB_SHA}" ]]; then
23+
GITHUB_SHA="HEAD"
24+
fi
25+
26+
LAST_TAG=`git describe --tags $(git rev-list --tags --max-count=1) 2> /dev/null` || true
27+
if [[ -z "${LAST_TAG}" ]]; then
28+
echo "[INFO] No tags found. Including all commits up to ${GITHUB_SHA}."
29+
VERSION_RANGE="${GITHUB_SHA}"
30+
else
31+
echo "[INFO] Last release tag: ${LAST_TAG}."
32+
COMMIT_SHA=`git show-ref -s ${LAST_TAG}`
33+
echo "[INFO] Last release commit: ${COMMIT_SHA}."
34+
VERSION_RANGE="${COMMIT_SHA}..${GITHUB_SHA}"
35+
echo "[INFO] Including all commits in the range ${VERSION_RANGE}."
36+
fi
37+
38+
echo ""
39+
40+
# Older versions of Bash (< 4.4) treat empty arrays as unbound variables, which triggers
41+
# errors when referencing them. Therefore we initialize each of these arrays with an empty
42+
# sentinel value, and later skip them.
43+
CHANGES=("")
44+
FIXES=("")
45+
FEATS=("")
46+
MISC=("")
47+
48+
while read -r line
49+
do
50+
COMMIT_MSG=`echo ${line} | cut -d ' ' -f 2-`
51+
if [[ $COMMIT_MSG =~ ^change(\(.*\))?: ]]; then
52+
CHANGES+=("$COMMIT_MSG")
53+
elif [[ $COMMIT_MSG =~ ^fix(\(.*\))?: ]]; then
54+
FIXES+=("$COMMIT_MSG")
55+
elif [[ $COMMIT_MSG =~ ^feat(\(.*\))?: ]]; then
56+
FEATS+=("$COMMIT_MSG")
57+
else
58+
MISC+=("${COMMIT_MSG}")
59+
fi
60+
done < <(git log ${VERSION_RANGE} --oneline)
61+
62+
printChangelog "Breaking Changes" "${CHANGES[@]}"
63+
printChangelog "New Features" "${FEATS[@]}"
64+
printChangelog "Bug Fixes" "${FIXES[@]}"
65+
printChangelog "Miscellaneous" "${MISC[@]}"
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/bin/bash
2+
3+
###################################### Outputs #####################################
4+
5+
# 1. version: The version of this release including the 'v' prefix (e.g. v1.2.3).
6+
# 2. changelog: Formatted changelog text for this release.
7+
8+
####################################################################################
9+
10+
set -e
11+
set -u
12+
13+
function echo_info() {
14+
local MESSAGE=$1
15+
echo "[INFO] ${MESSAGE}"
16+
}
17+
18+
function echo_warn() {
19+
local MESSAGE=$1
20+
echo "[WARN] ${MESSAGE}"
21+
}
22+
23+
function terminate() {
24+
echo ""
25+
echo_warn "--------------------------------------------"
26+
echo_warn "PREFLIGHT FAILED"
27+
echo_warn "--------------------------------------------"
28+
exit 1
29+
}
30+
31+
32+
echo_info "Starting publish preflight check..."
33+
echo_info "Git revision : ${GITHUB_SHA}"
34+
echo_info "Workflow triggered by : ${GITHUB_ACTOR}"
35+
echo_info "GitHub event : ${GITHUB_EVENT_NAME}"
36+
37+
38+
echo_info ""
39+
echo_info "--------------------------------------------"
40+
echo_info "Extracting release version"
41+
echo_info "--------------------------------------------"
42+
echo_info ""
43+
44+
readonly ABOUT_FILE="firebase_admin/__about__.py"
45+
echo_info "Loading version from: ${ABOUT_FILE}"
46+
47+
readonly VERSION_SCRIPT="exec(open('${ABOUT_FILE}').read()); print(__version__)"
48+
readonly RELEASE_VERSION=`python -c "${VERSION_SCRIPT}"` || true
49+
if [[ -z "${RELEASE_VERSION}" ]]; then
50+
echo_warn "Failed to extract release version from: ${ABOUT_FILE}"
51+
terminate
52+
fi
53+
54+
if [[ ! "${RELEASE_VERSION}" =~ ^([0-9]*)\.([0-9]*)\.([0-9]*)$ ]]; then
55+
echo_warn "Malformed release version string: ${RELEASE_VERSION}. Exiting."
56+
terminate
57+
fi
58+
59+
echo_info "Extracted release version: ${RELEASE_VERSION}"
60+
echo "::set-output name=version::v${RELEASE_VERSION}"
61+
62+
63+
echo_info ""
64+
echo_info "--------------------------------------------"
65+
echo_info "Checking previous releases"
66+
echo_info "--------------------------------------------"
67+
echo_info ""
68+
69+
readonly PYPI_URL="https://pypi.org/pypi/firebase-admin/${RELEASE_VERSION}/json"
70+
readonly PYPI_STATUS=`curl -s -o /dev/null -L -w "%{http_code}" ${PYPI_URL}`
71+
if [[ $PYPI_STATUS -eq 404 ]]; then
72+
echo_info "Release version ${RELEASE_VERSION} not found in Pypi."
73+
elif [[ $PYPI_STATUS -eq 200 ]]; then
74+
echo_warn "Release version ${RELEASE_VERSION} already present in Pypi."
75+
terminate
76+
else
77+
echo_warn "Unexpected ${PYPI_STATUS} response from Pypi. Exiting."
78+
terminate
79+
fi
80+
81+
82+
echo_info ""
83+
echo_info "--------------------------------------------"
84+
echo_info "Checking release tag"
85+
echo_info "--------------------------------------------"
86+
echo_info ""
87+
88+
echo_info "---< git fetch --depth=1 origin +refs/tags/*:refs/tags/* >---"
89+
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
90+
echo ""
91+
92+
readonly EXISTING_TAG=`git rev-parse -q --verify "refs/tags/v${RELEASE_VERSION}"` || true
93+
if [[ -n "${EXISTING_TAG}" ]]; then
94+
echo_warn "Tag v${RELEASE_VERSION} already exists. Exiting."
95+
echo_warn "If the tag was created in a previous unsuccessful attempt, delete it and try again."
96+
echo_warn " $ git tag -d v${RELEASE_VERSION}"
97+
echo_warn " $ git push --delete origin v${RELEASE_VERSION}"
98+
99+
readonly RELEASE_URL="https://github.com/firebase/firebase-admin-python/releases/tag/v${RELEASE_VERSION}"
100+
echo_warn "Delete any corresponding releases at ${RELEASE_URL}."
101+
terminate
102+
fi
103+
104+
echo_info "Tag v${RELEASE_VERSION} does not exist."
105+
106+
107+
echo_info ""
108+
echo_info "--------------------------------------------"
109+
echo_info "Generating changelog"
110+
echo_info "--------------------------------------------"
111+
echo_info ""
112+
113+
echo_info "---< git fetch origin master --prune --unshallow >---"
114+
git fetch origin master --prune --unshallow
115+
echo ""
116+
117+
echo_info "Generating changelog from history..."
118+
readonly CURRENT_DIR=$(dirname "$0")
119+
readonly CHANGELOG=`${CURRENT_DIR}/generate_changelog.sh`
120+
echo "$CHANGELOG"
121+
122+
# Parse and preformat the text to handle multi-line output.
123+
# See https://github.jpy.wangmunity/t5/GitHub-Actions/set-output-Truncates-Multiline-Strings/td-p/37870
124+
FILTERED_CHANGELOG=`echo "$CHANGELOG" | grep -v "\\[INFO\\]"`
125+
FILTERED_CHANGELOG="${FILTERED_CHANGELOG//'%'/'%25'}"
126+
FILTERED_CHANGELOG="${FILTERED_CHANGELOG//$'\n'/'%0A'}"
127+
FILTERED_CHANGELOG="${FILTERED_CHANGELOG//$'\r'/'%0D'}"
128+
echo "::set-output name=changelog::${FILTERED_CHANGELOG}"
129+
130+
131+
echo ""
132+
echo_info "--------------------------------------------"
133+
echo_info "PREFLIGHT SUCCESSFUL"
134+
echo_info "--------------------------------------------"

.github/workflows/release.yml

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Copyright 2020 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
name: Release
16+
17+
on:
18+
# Only run the workflow when a PR is closed, or when a developer explicitly requests
19+
# a build by sending a 'firebase_build' event.
20+
pull_request:
21+
types:
22+
- closed
23+
24+
repository_dispatch:
25+
types:
26+
- firebase_build
27+
28+
jobs:
29+
stage_release:
30+
# If triggered by a PR it must be merged and contain the label 'release:build'.
31+
if: github.event.action == 'firebase_build' ||
32+
(github.event.pull_request.merged &&
33+
contains(github.event.pull_request.labels.*.name, 'release:build'))
34+
35+
runs-on: ubuntu-latest
36+
37+
# When manually triggering the build, the requester can specify a target branch or a tag
38+
# via the 'ref' client parameter.
39+
steps:
40+
- name: Checkout source for staging
41+
uses: actions/checkout@v2
42+
with:
43+
ref: ${{ github.event.client_payload.ref || github.ref }}
44+
45+
- name: Set up Python
46+
uses: actions/setup-python@v1
47+
with:
48+
python-version: 3.6
49+
50+
- name: Install dependencies
51+
run: |
52+
python -m pip install --upgrade pip
53+
pip install -r requirements.txt
54+
55+
- name: Run tests
56+
run: |
57+
pytest
58+
echo "Running integration tests"
59+
60+
- name: Package release artifacts
61+
run: python setup.py bdist_wheel bdist_egg
62+
63+
# Attach the packaged artifacts to the workflow output. These can be manually
64+
# downloaded for later inspection if necessary.
65+
- name: Archive artifacts
66+
uses: actions/upload-artifact@v1
67+
with:
68+
name: dist
69+
path: dist
70+
71+
# Check whether the release should be published. We publish only when the trigger PR is
72+
# 1. merged
73+
# 2. to the master branch
74+
# 3. with the title prefix '[chore] Release '.
75+
- name: Publish preflight check
76+
if: success() && github.event.pull_request.merged &&
77+
github.ref == 'master' &&
78+
startsWith(github.event.pull_request.title, '[chore] Release ')
79+
id: preflight
80+
run: |
81+
./.github/scripts/publish_preflight_check.sh
82+
echo ::set-env name=FIREBASE_PUBLISH::true
83+
84+
# Tag the release if not executing in the dryrun mode. We pull this action froma
85+
# custom fork of a contributor until https://github.com/actions/create-release/pull/32
86+
# is merged. Also note that v1 of this action does not support the "body" parameter.
87+
- name: Create release tag
88+
if: success() && env.FIREBASE_PUBLISH
89+
uses: fleskesvor/create-release@1a72e235c178bf2ae6c51a8ae36febc24568c5fe
90+
env:
91+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
92+
with:
93+
tag_name: ${{ steps.preflight.outputs.version }}
94+
release_name: Firebase Admin Python SDK ${{ steps.preflight.outputs.version }}
95+
body: ${{ steps.preflight.outputs.changelog }}
96+
draft: false
97+
prerelease: false
98+
99+
- name: Publish to Pypi
100+
if: success() && env.FIREBASE_PUBLISH
101+
run: echo Publishing to Pypi
102+
103+
# Post to Twitter if explicitly opted-in by adding the label 'release:tweet'.
104+
- name: Post to Twitter
105+
if: success() && env.FIREBASE_PUBLISH &&
106+
contains(github.event.pull_request.labels.*.name, 'release:tweet')
107+
run: echo Posting Tweet
108+
continue-on-error: true

0 commit comments

Comments
 (0)