Merge pull request #1710 from chris-j-h/fix-release-workflow #51
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Release | ||
on: | ||
workflow_dispatch: | ||
tags: | ||
- 'v*' | ||
push: | ||
tags: | ||
- 'v*' | ||
env: | ||
GO_VERSION: 1.21.3 | ||
GOPATH: ${{ github.workspace }}/go | ||
WORKING_DIR: ${{ github.workspace }}/go/src/github.com/ethereum/go-ethereum | ||
jobs: | ||
publish-docker: | ||
name: Publish Docker Images | ||
runs-on: ubuntu-20.04 | ||
steps: | ||
- name: 'Check out project files' | ||
uses: actions/checkout@v3 | ||
# https://github.com/docker/setup-qemu-action | ||
- name: Set up QEMU | ||
uses: docker/setup-qemu-action@v2 | ||
# https://github.com/docker/setup-buildx-action | ||
- name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v2 | ||
- name: 'Extract Docker Image Tag' | ||
id: extract | ||
run: | | ||
REF=${{ github.ref }} | ||
echo ::set-output name=image_tag::$(echo $REF | sed 's/refs\/tags\/v//g') | ||
echo ::set-output name=image_tag_minor_latest::$(echo $REF | sed -e 's/refs\/tags\/v//g' -e 's/^\([[:digit:]]*\.[[:digit:]]*\).*/\1/') | ||
- name: 'Build ARM image to Docker Hub' | ||
id: build | ||
run: | | ||
echo "skipping..." | ||
# output_dir=${{ runner.temp }}/docker | ||
# mkdir -p $output_dir | ||
# docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_ACCESS_TOKEN }} | ||
# docker buildx build --push --platform linux/amd64,linux/arm64 -t ${{ secrets.DOCKER_REPO }}:latest -t ${{ secrets.DOCKER_REPO }}:${{ steps.extract.outputs.image_tag }} -t ${{ secrets.DOCKER_REPO }}:${{ steps.extract.outputs.image_tag_minor_latest }} . | ||
build-binary: | ||
name: 'Build binary for ${{ matrix.os }}' | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
# os: [ "ubuntu-20.04", "macos-latest" ] | ||
os: [ "macos-latest" ] | ||
runs-on: ${{ matrix.os }} | ||
steps: | ||
- name: 'Setup Go ${{ env.GO_VERSION }}' | ||
uses: actions/setup-go@v1 | ||
with: | ||
go-version: ${{ env.GO_VERSION }} | ||
- name: 'Prepare environment' | ||
id: env | ||
run: | | ||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH | ||
echo "::set-output name=key::$(go env GOOS)_$(go env GOARCH)" | ||
echo "::set-output name=version::${GITHUB_REF##*/}" | ||
- name: 'Check out project files' | ||
uses: actions/checkout@v2 | ||
with: | ||
path: ${{ env.WORKING_DIR }} | ||
- name: 'Build geth' | ||
# For MacOS, We use gtar and run purge command to workaround issue | ||
# https://github.com/actions/virtual-environments/issues/2619 | ||
id: build | ||
working-directory: ${{ env.WORKING_DIR }} | ||
run: |- | ||
make geth | ||
mkdir -p build/artifact | ||
tar_file=build/artifact/geth_${{ steps.env.outputs.version }}_${{ steps.env.outputs.key }}.tar.gz | ||
if [ "${{ matrix.os }}" == "macos-latest" ]; then | ||
sudo /usr/sbin/purge | ||
gtar cfvz ${tar_file} -C build/bin geth | ||
else | ||
tar cfvz ${tar_file} -C build/bin geth | ||
fi | ||
echo "::set-output name=tar_file::${tar_file}" | ||
echo "::set-output name=checksum::$(shasum -a 256 build/bin/geth | awk '{print $1}')" | ||
- name: 'Verify tarball' | ||
working-directory: ${{ env.WORKING_DIR }} | ||
run: |- | ||
cp ${{ steps.build.outputs.tar_file }} ${{ runner.temp }} | ||
pushd ${{ runner.temp}} | ||
tar xfvz *.tar.gz | ||
actual_checksum=$(shasum -a 256 geth | awk '{print $1}') | ||
echo "Checksum: ${actual_checksum}" | ||
popd | ||
if [ "${{ steps.build.outputs.checksum }}" != "${actual_checksum}" ]; then | ||
echo "::error::geth checksum validation fails" | ||
exit 1 | ||
fi | ||
- name: 'Upload artifact' | ||
uses: actions/upload-artifact@v2 | ||
with: | ||
path: ${{ env.WORKING_DIR }}/build/artifact | ||
name: ${{ steps.env.outputs.key }} | ||
if-no-files-found: error | ||
deploy-cloudsmith: | ||
name: 'Deploy binary to Cloudsmith for ${{ matrix.goarch }}' | ||
needs: | ||
- build-binary | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
# goarch: [ "linux_amd64", "darwin_arm64" ] | ||
goarch: [ "darwin_arm64" ] | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: 'Prepare environment' | ||
id: env | ||
run: | | ||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH | ||
# echo "::set-output name=version::${GITHUB_REF##*/}" | ||
echo "::set-output name=version::v24.4.0" | ||
- name: 'Download artifacts' | ||
uses: actions/download-artifact@v2 | ||
with: | ||
path: artifact | ||
- name: 'Upload artifacts to Cloudsmith' | ||
id: push | ||
uses: cloudsmith-io/action@master | ||
with: | ||
api-key: ${{ secrets.CLOUDSMITH_API_KEY }} | ||
command: "push" | ||
format: "raw" | ||
owner: "consensys" | ||
repo: "go-quorum" | ||
file: "artifact/${{ matrix.goarch }}/geth_${{ steps.env.outputs.version }}_${{ matrix.goarch }}.tar.gz" | ||
name: "geth_${{ steps.env.outputs.version }}_${{ matrix.goarch }}.tar.gz" | ||
summary: "GoQuorum ${{ steps.env.outputs.version }} - binary distribution for ${{ matrix.goarch }}" | ||
description: "See https://github.com/ConsenSys/quorum/" | ||
version: "${{ steps.env.outputs.version }}" | ||
draft-release: | ||
if: always() | ||
name: 'Draft Github release' | ||
needs: | ||
- deploy-cloudsmith | ||
- publish-docker | ||
runs-on: ubuntu-20.04 | ||
steps: | ||
- name: 'Check out project files' | ||
uses: actions/checkout@v2 | ||
- name: 'Generate release notes' | ||
id: release_notes | ||
run: | | ||
echo "skipping..." | ||
# git fetch --depth=1 origin +refs/tags/*:refs/tags/* | ||
# file="generated-release-notes.md" | ||
# current_version="${GITHUB_REF##*/}" | ||
# last_version=$(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`) | ||
# last_release_date=$(git log -1 --format=%cd --date=short $last_version) | ||
# echo "Last version: $last_version on $last_release_date" | ||
# # pulling from git logs | ||
# curl -q -s -H "Accept: application/vnd.github.v3+json" \ | ||
# "https://api.github.com/search/issues?q=repo:ConsenSys/quorum+is:pr+is:merged+merged%3A>=$last_release_date+sort%3Aupdated-desc" | jq -r '"* " + (.items[]|.title + " #" + (.number|tostring))' \ | ||
# >> $file | ||
# # pulling file hashes from Cloudsmith | ||
# echo "" >> $file | ||
# echo "| Filename | SHA256 Hash |" >> $file | ||
# echo "|:---------|:------------|" >> $file | ||
# curl --request GET \ | ||
# --url "https://api.cloudsmith.io/v1/packages/consensys/go-quorum/?query=version:$current_version" \ | ||
# --header 'Accept: application/json' \ | ||
# --header 'X-Api-Key: ${{ secrets.CLOUDSMITH_API_KEY }}' \ | ||
# | jq '.[] | select(.name | endswith(".asc") | not) | "|[\(.name)](\(.cdn_url))|`\(.checksum_sha256)`|"' -r \ | ||
# >> $file | ||
# content=$(cat $file) | ||
# # escape newline | ||
# content="${content//'%'/'%25'}" | ||
# content="${content//$'\n'/'%0A'}" | ||
# content="${content//$'\r'/'%0D'}" | ||
# echo "::set-output name=content::$content" | ||
# - name: 'Create Github draft release' | ||
# uses: actions/create-release@v1 | ||
# env: | ||
# # This token is provided by Actions, you do not need to create your own token | ||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
# with: | ||
# tag_name: ${{ github.ref }} | ||
# release_name: ${{ github.ref }} | ||
# body: | | ||
# ${{ steps.release_notes.outputs.content }} | ||
# draft: true | ||
# prerelease: false | ||
notify: | ||
if: always() | ||
name: 'Notify' | ||
needs: | ||
- build-binary | ||
- deploy-cloudsmith | ||
- publish-docker | ||
- draft-release | ||
runs-on: ubuntu-20.04 | ||
steps: | ||
- name: 'Setup metadata' | ||
id: setup | ||
run: | | ||
gitref_path="${{ github.ref }}" | ||
gitref_path=${gitref_path/refs\/heads/tree} # for refs/heads/my-branch | ||
gitref_path=${gitref_path/refs\/tags/tree} # for refs/tags/v1.0.0 | ||
gitref_path=${gitref_path#refs\/} # for refs/pull/123/merge | ||
gitref_path=${gitref_path%/merge} # for refs/pull/123/merge | ||
echo "::set-output name=gitref-path::$gitref_path" | ||
echo "::set-output name=version::${GITHUB_REF##*/}" | ||
- name: 'Prepare Slack message with full info' | ||
id: status | ||
uses: actions/github-script@0.8.0 | ||
with: | ||
script: | | ||
var gitref_path = "${{ steps.setup.outputs.gitref-path }}" | ||
//////////////////////////////////// | ||
// retrieve workflow run data | ||
//////////////////////////////////// | ||
console.log("get workflow run") | ||
const wf_run = await github.actions.getWorkflowRun({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
run_id: ${{ github.run_id }} | ||
}) | ||
console.log(wf_run.data) | ||
console.log("get jobs for workflow run:", wf_run.data.jobs_url) | ||
const jobs_response = await github.request(wf_run.data.jobs_url) | ||
//////////////////////////////////// | ||
// build slack notification message | ||
//////////////////////////////////// | ||
// some utility functions | ||
var date_diff_func = function(start, end) { | ||
var duration = end - start | ||
// format the duration | ||
var delta = duration / 1000 | ||
var days = Math.floor(delta / 86400) | ||
delta -= days * 86400 | ||
var hours = Math.floor(delta / 3600) % 24 | ||
delta -= hours * 3600 | ||
var minutes = Math.floor(delta / 60) % 60 | ||
delta -= minutes * 60 | ||
var seconds = Math.floor(delta % 60) | ||
var format_func = function(v, text, check) { | ||
if (v <= 0 && check) { | ||
return "" | ||
} else { | ||
return v + text | ||
} | ||
} | ||
return format_func(days, "d", true) + format_func(hours, "h", true) + format_func(minutes, "m", true) + format_func(seconds, "s", false) | ||
} | ||
var status_icon_func = function(s) { | ||
switch (s) { | ||
case "w_success": | ||
return ":white_check_mark:" | ||
case "w_failure": | ||
return ":no_entry:" | ||
case "w_cancelled": | ||
return ":warning:" | ||
case "success": | ||
return "\u2713" | ||
case "failure": | ||
return "\u2717" | ||
default: | ||
return "\u20e0" | ||
} | ||
} | ||
// build the message | ||
var job_blocks = [] | ||
var is_wf_success = true | ||
var is_wf_failure = false | ||
for (j of jobs_response.data.jobs) { | ||
console.log(j.name, ":", j.status, j.conclusion, j.started_at, j.completed_at) | ||
// ignore the current job running this script | ||
if (j.status != "completed") { | ||
continue | ||
} | ||
if (j.conclusion != "success") { | ||
is_wf_success = false | ||
} | ||
if (j.conclusion == "failure") { | ||
is_wf_failure = true | ||
} | ||
job_blocks.push({ | ||
type: "section", | ||
text: { | ||
type: "mrkdwn", | ||
text: `${status_icon_func(j.conclusion)} <${j.html_url}|${j.name}> took ${date_diff_func(new Date(j.started_at), new Date(j.completed_at))}` | ||
} | ||
}) | ||
} | ||
var workflow_status = "w_cancelled" | ||
if (is_wf_success) { | ||
workflow_status = "w_success" | ||
} else if (is_wf_failure) { | ||
workflow_status = "w_failure" | ||
} | ||
var context_elements = [ | ||
{ | ||
"type": "mrkdwn", | ||
"text": "*Repo:* <https://github.com/${{ github.repository }}|${{ github.repository }}>" | ||
}, | ||
{ | ||
"type": "mrkdwn", | ||
"text": `*Branch:* <https://github.com/${{ github.repository }}/${gitref_path}|${{ github.ref }}>` | ||
}, | ||
{ | ||
"type": "mrkdwn", | ||
"text": `*Event:* ${wf_run.data.event}` | ||
}, | ||
{ | ||
"type": "mrkdwn", | ||
"text": `*Commit:* <https://github.com/${{ github.repository }}/commit/${wf_run.data.head_commit.id}|${wf_run.data.head_commit.id.substr(0, 8)}>` | ||
}, | ||
{ | ||
"type": "mrkdwn", | ||
"text": `*Author:* ${wf_run.data.head_commit.author.name}` | ||
} | ||
] | ||
var header_blocks = [ | ||
{ | ||
type: "section", | ||
text: { | ||
type: "mrkdwn", | ||
text: `${status_icon_func(workflow_status)} *${{ github.workflow }} ${{ steps.setup.outputs.version }}* <${wf_run.data.html_url}|#${{ github.run_number }}> took ${date_diff_func(new Date(wf_run.data.created_at), new Date(wf_run.data.updated_at))}` | ||
} | ||
}, | ||
{ | ||
type: "context", | ||
elements: context_elements, | ||
}, | ||
{ | ||
type: "divider" | ||
} | ||
] | ||
var slack_msg = { | ||
blocks: [].concat(header_blocks, job_blocks) | ||
} | ||
return slack_msg | ||
- name: 'Prepare Slack message with partial info' | ||
id: short_status | ||
if: failure() | ||
uses: actions/github-script@0.8.0 | ||
with: | ||
script: | | ||
//////////////////////////////////// | ||
// retrieve workflow run data | ||
//////////////////////////////////// | ||
const wf_run = await github.actions.getWorkflowRun({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
run_id: ${{ github.run_id }} | ||
}) | ||
var date_diff_func = function(start, end) { | ||
var duration = end - start | ||
// format the duration | ||
var delta = duration / 1000 | ||
var days = Math.floor(delta / 86400) | ||
delta -= days * 86400 | ||
var hours = Math.floor(delta / 3600) % 24 | ||
delta -= hours * 3600 | ||
var minutes = Math.floor(delta / 60) % 60 | ||
delta -= minutes * 60 | ||
var seconds = Math.floor(delta % 60) | ||
var format_func = function(v, text, check) { | ||
if (v <= 0 && check) { | ||
return "" | ||
} else { | ||
return v + text | ||
} | ||
} | ||
return format_func(days, "d", true) + format_func(hours, "h", true) + format_func(minutes, "m", true) + format_func(seconds, "s", false) | ||
} | ||
var slack_msg = { | ||
blocks: [ | ||
{ | ||
type: "section", | ||
text: { | ||
type: "mrkdwn", | ||
text: `:skull_and_crossbones: *${{ github.workflow }}* <${wf_run.data.html_url}|#${{ github.run_number }}> (took ${date_diff_func(new Date(wf_run.data.created_at), new Date(wf_run.data.updated_at))})` | ||
} | ||
} | ||
] | ||
} | ||
return slack_msg | ||
- name: 'Send to Slack' | ||
if: always() | ||
run: | | ||
cat <<JSON > long_message.json | ||
${{ steps.status.outputs.result }} | ||
JSON | ||
cat <<JSON > short_message.json | ||
${{ steps.short_status.outputs.result }} | ||
JSON | ||
_post() { | ||
curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} -H "Content-type: application/json" --data "@${1}" | ||
} | ||
_post "long_message.json" || _post "short_message.json" | ||