release.yml 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. name: Release
  2. on:
  3. workflow_dispatch:
  4. tags:
  5. - 'v*'
  6. push:
  7. tags:
  8. - 'v*'
  9. env:
  10. GO_VERSION: 1.16
  11. GOPATH: ${{ github.workspace }}/go
  12. WORKING_DIR: ${{ github.workspace }}/go/src/github.com/ethereum/go-ethereum
  13. jobs:
  14. publish-docker:
  15. name: Publish Docker Images
  16. runs-on: ubuntu-20.04
  17. steps:
  18. - name: 'Check out project files'
  19. uses: actions/checkout@v3
  20. # https://github.com/docker/setup-qemu-action
  21. - name: Set up QEMU
  22. uses: docker/setup-qemu-action@v2
  23. # https://github.com/docker/setup-buildx-action
  24. - name: Set up Docker Buildx
  25. uses: docker/setup-buildx-action@v2
  26. - name: 'Extract Docker Image Tag'
  27. id: extract
  28. run: |
  29. REF=${{ github.ref }}
  30. echo ::set-output name=image_tag::$(echo $REF | sed 's/refs\/tags\/v//g')
  31. echo ::set-output name=image_tag_minor_latest::$(echo $REF | sed -e 's/refs\/tags\/v//g' -e 's/^\([[:digit:]]*\.[[:digit:]]*\).*/\1/')
  32. - name: 'Build ARM image to Docker Hub'
  33. id: build
  34. run: |
  35. output_dir=${{ runner.temp }}/docker
  36. mkdir -p $output_dir
  37. docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_ACCESS_TOKEN }}
  38. 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 }} .
  39. build-binary:
  40. name: 'Build binary for ${{ matrix.os }}'
  41. strategy:
  42. fail-fast: false
  43. matrix:
  44. os: [ "ubuntu-20.04", "macos-latest" ]
  45. runs-on: ${{ matrix.os }}
  46. steps:
  47. - name: 'Setup Go ${{ env.GO_VERSION }}'
  48. uses: actions/setup-go@v1
  49. with:
  50. go-version: ${{ env.GO_VERSION }}
  51. - name: 'Prepare environment'
  52. id: env
  53. run: |
  54. echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
  55. echo "::set-output name=key::$(go env GOOS)_$(go env GOARCH)"
  56. echo "::set-output name=version::${GITHUB_REF##*/}"
  57. - name: 'Check out project files'
  58. uses: actions/checkout@v2
  59. with:
  60. path: ${{ env.WORKING_DIR }}
  61. - name: 'Build geth'
  62. # For MacOS, We use gtar and run purge command to workaround issue
  63. # https://github.com/actions/virtual-environments/issues/2619
  64. id: build
  65. working-directory: ${{ env.WORKING_DIR }}
  66. run: |-
  67. make geth
  68. mkdir -p build/artifact
  69. tar_file=build/artifact/geth_${{ steps.env.outputs.version }}_${{ steps.env.outputs.key }}.tar.gz
  70. if [ "${{ matrix.os }}" == "macos-latest" ]; then
  71. sudo /usr/sbin/purge
  72. gtar cfvz ${tar_file} -C build/bin geth
  73. else
  74. tar cfvz ${tar_file} -C build/bin geth
  75. fi
  76. echo "::set-output name=tar_file::${tar_file}"
  77. echo "::set-output name=checksum::$(shasum -a 256 build/bin/geth | awk '{print $1}')"
  78. - name: 'Verify tarball'
  79. working-directory: ${{ env.WORKING_DIR }}
  80. run: |-
  81. cp ${{ steps.build.outputs.tar_file }} ${{ runner.temp }}
  82. pushd ${{ runner.temp}}
  83. tar xfvz *.tar.gz
  84. actual_checksum=$(shasum -a 256 geth | awk '{print $1}')
  85. echo "Checksum: ${actual_checksum}"
  86. popd
  87. if [ "${{ steps.build.outputs.checksum }}" != "${actual_checksum}" ]; then
  88. echo "::error::geth checksum validation fails"
  89. exit 1
  90. fi
  91. - name: 'Upload artifact'
  92. uses: actions/upload-artifact@v2
  93. with:
  94. path: ${{ env.WORKING_DIR }}/build/artifact
  95. name: ${{ steps.env.outputs.key }}
  96. if-no-files-found: error
  97. deploy-cloudsmith:
  98. name: 'Deploy binary to Cloudsmith for ${{ matrix.goarch }}'
  99. needs:
  100. - build-binary
  101. strategy:
  102. fail-fast: false
  103. matrix:
  104. goarch: [ "linux_amd64", "darwin_amd64" ]
  105. runs-on: ubuntu-latest
  106. steps:
  107. - name: 'Prepare environment'
  108. id: env
  109. run: |
  110. echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
  111. echo "::set-output name=version::${GITHUB_REF##*/}"
  112. - name: 'Download artifacts'
  113. uses: actions/download-artifact@v2
  114. with:
  115. path: artifact
  116. - name: 'Upload artifacts to Cloudsmith'
  117. id: push
  118. uses: cloudsmith-io/action@master
  119. with:
  120. api-key: ${{ secrets.CLOUDSMITH_API_KEY }}
  121. command: "push"
  122. format: "raw"
  123. owner: "consensys"
  124. repo: "go-quorum"
  125. file: "artifact/${{ matrix.goarch }}/geth_${{ steps.env.outputs.version }}_${{ matrix.goarch }}.tar.gz"
  126. name: "geth_${{ steps.env.outputs.version }}_${{ matrix.goarch }}.tar.gz"
  127. summary: "GoQuorum ${{ steps.env.outputs.version }} - binary distribution for ${{ matrix.goarch }}"
  128. description: "See https://github.com/ConsenSys/quorum/"
  129. version: "${{ steps.env.outputs.version }}"
  130. draft-release:
  131. if: always()
  132. name: 'Draft Github release'
  133. needs:
  134. - deploy-cloudsmith
  135. - publish-docker
  136. runs-on: ubuntu-20.04
  137. steps:
  138. - name: 'Check out project files'
  139. uses: actions/checkout@v2
  140. - name: 'Generate release notes'
  141. id: release_notes
  142. run: |
  143. git fetch --depth=1 origin +refs/tags/*:refs/tags/*
  144. file="generated-release-notes.md"
  145. current_version="${GITHUB_REF##*/}"
  146. last_version=$(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`)
  147. last_release_date=$(git log -1 --format=%cd --date=short $last_version)
  148. echo "Last version: $last_version on $last_release_date"
  149. # pulling from git logs
  150. curl -q -s -H "Accept: application/vnd.github.v3+json" \
  151. "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))' \
  152. >> $file
  153. # pulling file hashes from Cloudsmith
  154. echo "" >> $file
  155. echo "| Filename | SHA256 Hash |" >> $file
  156. echo "|:---------|:------------|" >> $file
  157. curl --request GET \
  158. --url "https://api.cloudsmith.io/v1/packages/consensys/go-quorum/?query=version:$current_version" \
  159. --header 'Accept: application/json' \
  160. --header 'X-Api-Key: ${{ secrets.CLOUDSMITH_API_KEY }}' \
  161. | jq '.[] | select(.name | endswith(".asc") | not) | "|[\(.name)](\(.cdn_url))|`\(.checksum_sha256)`|"' -r \
  162. >> $file
  163. content=$(cat $file)
  164. # escape newline
  165. content="${content//'%'/'%25'}"
  166. content="${content//$'\n'/'%0A'}"
  167. content="${content//$'\r'/'%0D'}"
  168. echo "::set-output name=content::$content"
  169. - name: 'Create Github draft release'
  170. uses: actions/create-release@v1
  171. env:
  172. # This token is provided by Actions, you do not need to create your own token
  173. GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  174. with:
  175. tag_name: ${{ github.ref }}
  176. release_name: ${{ github.ref }}
  177. body: |
  178. ${{ steps.release_notes.outputs.content }}
  179. draft: true
  180. prerelease: false
  181. notify:
  182. if: always()
  183. name: 'Notify'
  184. needs:
  185. - build-binary
  186. - deploy-cloudsmith
  187. - publish-docker
  188. - draft-release
  189. runs-on: ubuntu-20.04
  190. steps:
  191. - name: 'Setup metadata'
  192. id: setup
  193. run: |
  194. gitref_path="${{ github.ref }}"
  195. gitref_path=${gitref_path/refs\/heads/tree} # for refs/heads/my-branch
  196. gitref_path=${gitref_path/refs\/tags/tree} # for refs/tags/v1.0.0
  197. gitref_path=${gitref_path#refs\/} # for refs/pull/123/merge
  198. gitref_path=${gitref_path%/merge} # for refs/pull/123/merge
  199. echo "::set-output name=gitref-path::$gitref_path"
  200. echo "::set-output name=version::${GITHUB_REF##*/}"
  201. - name: 'Prepare Slack message with full info'
  202. id: status
  203. uses: actions/github-script@0.8.0
  204. with:
  205. script: |
  206. var gitref_path = "${{ steps.setup.outputs.gitref-path }}"
  207. ////////////////////////////////////
  208. // retrieve workflow run data
  209. ////////////////////////////////////
  210. console.log("get workflow run")
  211. const wf_run = await github.actions.getWorkflowRun({
  212. owner: context.repo.owner,
  213. repo: context.repo.repo,
  214. run_id: ${{ github.run_id }}
  215. })
  216. console.log(wf_run.data)
  217. console.log("get jobs for workflow run:", wf_run.data.jobs_url)
  218. const jobs_response = await github.request(wf_run.data.jobs_url)
  219. ////////////////////////////////////
  220. // build slack notification message
  221. ////////////////////////////////////
  222. // some utility functions
  223. var date_diff_func = function(start, end) {
  224. var duration = end - start
  225. // format the duration
  226. var delta = duration / 1000
  227. var days = Math.floor(delta / 86400)
  228. delta -= days * 86400
  229. var hours = Math.floor(delta / 3600) % 24
  230. delta -= hours * 3600
  231. var minutes = Math.floor(delta / 60) % 60
  232. delta -= minutes * 60
  233. var seconds = Math.floor(delta % 60)
  234. var format_func = function(v, text, check) {
  235. if (v <= 0 && check) {
  236. return ""
  237. } else {
  238. return v + text
  239. }
  240. }
  241. return format_func(days, "d", true) + format_func(hours, "h", true) + format_func(minutes, "m", true) + format_func(seconds, "s", false)
  242. }
  243. var status_icon_func = function(s) {
  244. switch (s) {
  245. case "w_success":
  246. return ":white_check_mark:"
  247. case "w_failure":
  248. return ":no_entry:"
  249. case "w_cancelled":
  250. return ":warning:"
  251. case "success":
  252. return "\u2713"
  253. case "failure":
  254. return "\u2717"
  255. default:
  256. return "\u20e0"
  257. }
  258. }
  259. // build the message
  260. var job_blocks = []
  261. var is_wf_success = true
  262. var is_wf_failure = false
  263. for (j of jobs_response.data.jobs) {
  264. console.log(j.name, ":", j.status, j.conclusion, j.started_at, j.completed_at)
  265. // ignore the current job running this script
  266. if (j.status != "completed") {
  267. continue
  268. }
  269. if (j.conclusion != "success") {
  270. is_wf_success = false
  271. }
  272. if (j.conclusion == "failure") {
  273. is_wf_failure = true
  274. }
  275. job_blocks.push({
  276. type: "section",
  277. text: {
  278. type: "mrkdwn",
  279. 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))}`
  280. }
  281. })
  282. }
  283. var workflow_status = "w_cancelled"
  284. if (is_wf_success) {
  285. workflow_status = "w_success"
  286. } else if (is_wf_failure) {
  287. workflow_status = "w_failure"
  288. }
  289. var context_elements = [
  290. {
  291. "type": "mrkdwn",
  292. "text": "*Repo:* <https://github.com/${{ github.repository }}|${{ github.repository }}>"
  293. },
  294. {
  295. "type": "mrkdwn",
  296. "text": `*Branch:* <https://github.com/${{ github.repository }}/${gitref_path}|${{ github.ref }}>`
  297. },
  298. {
  299. "type": "mrkdwn",
  300. "text": `*Event:* ${wf_run.data.event}`
  301. },
  302. {
  303. "type": "mrkdwn",
  304. "text": `*Commit:* <https://github.com/${{ github.repository }}/commit/${wf_run.data.head_commit.id}|${wf_run.data.head_commit.id.substr(0, 8)}>`
  305. },
  306. {
  307. "type": "mrkdwn",
  308. "text": `*Author:* ${wf_run.data.head_commit.author.name}`
  309. }
  310. ]
  311. var header_blocks = [
  312. {
  313. type: "section",
  314. text: {
  315. type: "mrkdwn",
  316. 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))}`
  317. }
  318. },
  319. {
  320. type: "context",
  321. elements: context_elements,
  322. },
  323. {
  324. type: "divider"
  325. }
  326. ]
  327. var slack_msg = {
  328. blocks: [].concat(header_blocks, job_blocks)
  329. }
  330. return slack_msg
  331. - name: 'Prepare Slack message with partial info'
  332. id: short_status
  333. if: failure()
  334. uses: actions/github-script@0.8.0
  335. with:
  336. script: |
  337. ////////////////////////////////////
  338. // retrieve workflow run data
  339. ////////////////////////////////////
  340. const wf_run = await github.actions.getWorkflowRun({
  341. owner: context.repo.owner,
  342. repo: context.repo.repo,
  343. run_id: ${{ github.run_id }}
  344. })
  345. var date_diff_func = function(start, end) {
  346. var duration = end - start
  347. // format the duration
  348. var delta = duration / 1000
  349. var days = Math.floor(delta / 86400)
  350. delta -= days * 86400
  351. var hours = Math.floor(delta / 3600) % 24
  352. delta -= hours * 3600
  353. var minutes = Math.floor(delta / 60) % 60
  354. delta -= minutes * 60
  355. var seconds = Math.floor(delta % 60)
  356. var format_func = function(v, text, check) {
  357. if (v <= 0 && check) {
  358. return ""
  359. } else {
  360. return v + text
  361. }
  362. }
  363. return format_func(days, "d", true) + format_func(hours, "h", true) + format_func(minutes, "m", true) + format_func(seconds, "s", false)
  364. }
  365. var slack_msg = {
  366. blocks: [
  367. {
  368. type: "section",
  369. text: {
  370. type: "mrkdwn",
  371. 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))})`
  372. }
  373. }
  374. ]
  375. }
  376. return slack_msg
  377. - name: 'Send to Slack'
  378. if: always()
  379. run: |
  380. cat <<JSON > long_message.json
  381. ${{ steps.status.outputs.result }}
  382. JSON
  383. cat <<JSON > short_message.json
  384. ${{ steps.short_status.outputs.result }}
  385. JSON
  386. _post() {
  387. curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} -H "Content-type: application/json" --data "@${1}"
  388. }
  389. _post "long_message.json" || _post "short_message.json"