name: Release on: workflow_dispatch: tags: - 'v*' push: tags: - 'v*' env: GO_VERSION: 1.16 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: | 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" ] 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_amd64" ] 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##*/}" - 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: | 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:* " }, { "type": "mrkdwn", "text": `*Branch:* ` }, { "type": "mrkdwn", "text": `*Event:* ${wf_run.data.event}` }, { "type": "mrkdwn", "text": `*Commit:* ` }, { "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 < long_message.json ${{ steps.status.outputs.result }} JSON cat < 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"