From b49a2a43bf343596d8b7a02315bb04723910e770 Mon Sep 17 00:00:00 2001 From: Matt Oswalt Date: Sat, 20 Mar 2021 15:03:15 -0400 Subject: [PATCH] WIP converting image build logic to pure GH actions Signed-off-by: Matt Oswalt --- .github/workflows/generate-preview.yml | 107 ------ .github/workflows/main.yml | 305 +++++++++++++++--- .../check-changelog.sh | 0 .../check-spelling.sh | 0 .../create-preview.sh | 2 +- .scripts/retag-images.sh | 35 ++ start-preview.sh => .scripts/start-preview.sh | 0 .scripts/wait-for-status.sh | 41 +++ CHANGELOG.md | 1 + wait-for-status.sh | 36 --- 10 files changed, 340 insertions(+), 187 deletions(-) delete mode 100644 .github/workflows/generate-preview.yml rename check-changelog.sh => .scripts/check-changelog.sh (100%) rename check-spelling.sh => .scripts/check-spelling.sh (100%) rename create-preview.sh => .scripts/create-preview.sh (92%) create mode 100755 .scripts/retag-images.sh rename start-preview.sh => .scripts/start-preview.sh (100%) create mode 100755 .scripts/wait-for-status.sh delete mode 100755 wait-for-status.sh diff --git a/.github/workflows/generate-preview.yml b/.github/workflows/generate-preview.yml deleted file mode 100644 index d63111c1..00000000 --- a/.github/workflows/generate-preview.yml +++ /dev/null @@ -1,107 +0,0 @@ -name: Generate Preview -on: - workflow_run: - workflows: ["CI"] - branches: [master] - types: - - completed - -jobs: - prebuild: - runs-on: ubuntu-latest - steps: - - # TODO - for security reasons (that we will absolutely want) this will run on the default branch. However we still want to check out the PR branch - # so we can build the images. - - - uses: actions/checkout@v2 - - # https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/ - - - # - name: Create Preview - # id: create_preview - # run: | - # echo ::set-output name=preview_id::$(./create-preview.sh | jq -r '.ID') - - - name: Get Buildables - id: get_buildables - run: | - echo ::set-output name=buildables::$(cd images && find . -maxdepth 2 -type f -name 'Makefile' -printf '%h;' | tr -d './' | rev | cut -c 2- | rev | jq -Rc 'split(";")') - - - name: Get Changed - id: get_buildables - run: | - echo ::set-output name=changed::$(git diff --name-only master..HEAD images/ | sed -rn 's/images\/([^/]*)\/.*/\\1/p' | tr '\n' ';' | rev | cut -c 2- | rev) - - - name: Get PR number - id: get_pr_number - run: | - echo ::set-output name=pr_number::$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") - if: ${{ success() }} - - # - name: Set up QEMU - # uses: docker/setup-qemu-action@v1 - # - name: Set up Docker Buildx - # uses: docker/setup-buildx-action@v1 - - # - name: Login to DockerHub - # uses: docker/login-action@v1 - # with: - # username: ${{ secrets.DOCKERHUB_USERNAME }} - # password: ${{ secrets.DOCKERHUB_TOKEN }} - - # https://stackoverflow.com/questions/59175332/using-output-from-a-previous-job-in-a-new-one-in-a-github-action/61236803#61236803 - outputs: - # preview_id: ${{ steps.create_preview.outputs.preview_id }} - pr_number: ${{ steps.get_pr_number.outputs.pr_number }} - buildables: ${{ steps.get_buildables.outputs.buildables }} - changed: ${{ steps.get_changed.outputs.changed }} - - # build: - # needs: prebuild - # runs-on: ubuntu-latest - - # strategy: - # matrix: - # images_to_build: ${{ fromJson(needs.prebuild.outputs.buildables) }} - - # steps: - # - uses: actions/checkout@v2 - # - name: Build and push - # # VERY IMPORTANT that we statically have "preview-" in the tag name so we don't conflict with existing prod images - # # run: "cd images/${{ matrix.images_to_build }} && TARGET_VERSION=preview-$preview_id make docker" - - # # TODO - remove this and comment back in the "make docker equivalent" above once you're confident that the directory iteration is done properly - # # with parallelism - # run: "cd images/${{ matrix.images_to_build }} && TARGET_VERSION=preview-$preview_id echo $(pwd)" - - # env: - # preview_id: ${{needs.prebuild.outputs.preview_id}} - - # # TODO(mierdin) need two retag jobs. One that you populate dynamically and one that you do statically. OR, figure out how to append to an array in github actions - # retag_static: - # # Some images can't be built automatically, and therefore should just be retagged from a known-good "source" tag, like a curriculum release. - - # needs: prebuild - # runs-on: ubuntu-latest - - # strategy: - # matrix: - # images_to_retag: [ - # "vqfx-snap1", - # "vqfx-snap2", - # "vqfx-snap3", - # ] - - # steps: - # - uses: actions/checkout@v2 - # - name: Build and push - # run: "cd images/${{ matrix.images_to_retag }} && docker tag antidotelabs/${{ matrix.images_to_retag }}:$retag_source antidotelabs/${{ matrix.images_to_retag }}:preview-$preview_id && docker push antidotelabs/${{ matrix.images_to_retag }}:preview-$preview_id" - - # env: - - # # TODO - this is a weak point, as this may need to get updated every release, if these images change. They often don't, so maybe - # # this is okay for now, and the right thing to do is just fix these images so they can be built properly, at which point - # # this whole job can be deleted. - # retag_source: v1.3.0 \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c72ee19a..8b77632c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,19 +1,35 @@ name: CI -on: - pull_request: - branches: [ master ] +on: # https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/ + pull_request_target: + types: [assigned, opened, synchronize, reopened] +env: + emptyArray: "[]" + emptyLiteral: "empty" + retagSource: "v1.3.0" jobs: - build: + prebuild: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - name: Get PR number + id: get_pr_number + run: | + echo ::set-output name=pr_number::$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") + if: ${{ success() }} + + - name: Check out PR + run: | + hub pr checkout ${{ steps.get_pr_number.outputs.pr_number }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Install xmllint run: sudo apt-get install aspell aspell-en dictionaries-common - name: Run spellchecker - run: ./check-spelling.sh + run: ./.scripts/check-spelling.sh if: ${{ success() }} - name: Install antidote binaries @@ -25,43 +41,246 @@ jobs: if: ${{ success() }} - name: Check changelog - run: ./check-changelog.sh + run: ./.scripts/check-changelog.sh + if: ${{ success() }} + + - name: Create Preview + id: create_preview + run: | + echo ::set-output name=preview_id::$(./.scripts/create-preview.sh | jq -r '.ID') + + - name: Create preview check + id: create_preview_check + uses: actions/github-script@v3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + result-encoding: string + script: | + + var check_result = await github.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: "nrelabs-preview", + head_sha: "${{ github.event.pull_request.head.sha }}", + status: "in_progress", + output: { + "title": "Preview is being provisioned...", + "summary": "Your content preview is currently being provisioned, please wait. Once provisioned, detailed information about how to view it will appear here.", + }, + }); + + console.log(check_result) + return check_result.data.id + + - name: Create preview logs check + id: create_preview_logs_check + uses: actions/github-script@v3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + result-encoding: string + script: | + + var check_result = await github.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + name: "nrelabs-preview-logs", + head_sha: "${{ github.event.pull_request.head.sha }}", + status: "in_progress", + output: { + "title": "Preview is being provisioned...", + "summary": "Your content preview is currently being provisioned, please wait. Once provisioned, detailed logs about the infrastructure provisioned to serve this preview will be provided here.", + }, + }); + console.log(check_result) + return check_result.data.id + + - name: Get Buildables + id: get_buildables + run: | + echo ::set-output name=buildables::$(cd images && find . -maxdepth 2 -type f -name 'Makefile' -printf '%h;' | tr -d './' | rev | cut -c 2- | rev | jq -Rc 'split(";")') + + - name: Get Changed + id: get_changed + run: | + echo ::set-output name=changed::$(git diff --name-only master..HEAD images/ | sed -rn 's/images\/([^/]*)\/.*/\\1/p' | tr '\n' ';' | rev | cut -c 2- | rev) + + - name: Get Image Directives + uses: actions/github-script@v3 + id: get_image_directives + with: + script: | + var to_retag = [ + "vqfx-snap1", + "vqfx-snap2", + "vqfx-snap3", + ] + var to_build = [] + + var buildables = ${{ steps.get_buildables.outputs.buildables || env.emptyArray }}; + var changed = ${{ steps.get_changed.outputs.changed || env.emptyArray }}; + + buildables.forEach(function(buildable) { + if (!changed.includes(buildable)) { + console.log(buildable + " not found in changed, adding to to_retag"); + to_retag.push(buildable); + } else { + console.log(buildable + " was found in changed, adding to to_build"); + to_build.push(buildable); + } + }); + + // The strategy matrix can't handle empty arrays so we'll add this here and check for it during the retag/build jobs + if (to_build.length == 0) { + to_build.push("empty") + } + if (to_retag.length == 0) { + to_retag.push("empty") + } + + return { + "to_build": to_build, + "to_retag": to_retag + } + + outputs: + preview_id: ${{ steps.create_preview.outputs.preview_id }} + pr_number: ${{ steps.get_pr_number.outputs.pr_number }} + image_directives: ${{ steps.get_image_directives.outputs.result }} + preview_check_id: ${{ steps.create_preview_check.outputs.result }} + preview_logs_check_id: ${{ steps.create_preview_logs_check.outputs.result }} + + logins: + needs: prebuild + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@master + with: + project_id: ${{ secrets.GCP_PROJECT_ID }} + service_account_key: ${{ secrets.GCP_SA_KEY }} + export_default_credentials: true + + build_image: + needs: [prebuild, logins] + runs-on: ubuntu-latest + strategy: + matrix: + image_to_build: ${{ fromJson(needs.prebuild.outputs.image_directives).to_build }} + steps: + - uses: actions/checkout@v2 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # - name: Set up QEMU + # uses: docker/setup-qemu-action@v1 + # - name: Set up Docker Buildx + # uses: docker/setup-buildx-action@v1 + + - name: Build image + run: | + cd images/${{ matrix.image_to_build }} && make dockerfast TARGET_VERSION=${{ env.retagSource }} + if: ${{ matrix.image_to_build != env.emptyLiteral }} # Ugly hack to prevent this from running when the matrix array is "empty" + + retag_images: + needs: [prebuild, logins] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Re-tag images + run: | + ./.scripts/retag-images.sh '${{ toJson(fromJson(needs.prebuild.outputs.image_directives).to_retag) }}' ${{ env.retagSource }} ${{ needs.prebuild.outputs.preview_id }} + env: + DOCKER_USER: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASS: ${{ secrets.DOCKERHUB_TOKEN }} + + deploy_preview: + needs: [prebuild, build_image, retag_images] + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v2 + + - name: Request preview + run: ./.scripts/start-preview.sh ${{ needs.prebuild.outputs.preview_id }} if: ${{ success() }} - # --------------------------- - - - # - name: Create Preview - # id: create_preview - # run: | - # echo ::set-output name=preview_id::$(./create-preview.sh | jq -r '.ID') - # if: ${{ success() }} - - # - name: Get PR number - # id: get_pr_number - # run: | - # echo ::set-output name=pr_number::$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") - # if: ${{ success() }} - - # - name: Build Preview Images - # id: build_preview_images - # run: | - # curl -k -X POST https://abathur.nrelabs.io/api/v1/webhooks/preview_images_build -H "St2-Api-Key: $PREVIEWER_APIK" -H "Content-Type: application/json" \ - # --data "{\"preview_id\": \"$PREVIEW_ID\", \"pr_number\": \"$PREVIEW_PR_NUMBER\", \"status_commit_id\": \"$PREVIEW_STATUS_COMMIT\", \"github_token\": \"$GH_TOKEN\"}" - # env: - # PREVIEW_ID: ${{ steps.create_preview.outputs.preview_id }} - # PREVIEW_STATUS_COMMIT: ${{ github.event.pull_request.head.sha }} - # PREVIEW_PR_NUMBER: ${{ steps.get_pr_number.outputs.pr_number }} - # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # # Locked down account which can only invoke preview webhook - # PREVIEWER_APIK: MjE0ZTlkYWZjMDg1OTNkOWJkMjQxZDA0Mzk0NzIzNDI1MTc2Nzk0NDVkMjk0MGE5NTNhODkxOTNiMzVmNWM5Mg - # if: ${{ success() }} - - # - name: Wait for status - # run: ./wait-for-status.sh ${{ github.event.pull_request.head.sha }} - # if: ${{ success() }} - - # - name: Request preview - # run: ./start-preview.sh ${{ steps.create_preview.outputs.preview_id }} - # if: ${{ success() }} + - name: Wait for status + run: ./.scripts/wait-for-status.sh ${{ needs.prebuild.outputs.preview_id }} + if: ${{ success() }} + + - name: Retrieve Logs + id: retrieve_preview_logs + uses: actions/github-script@v3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + result-encoding: string + script: | + const result = await github.request('https://preview.nrelabs.io/status?id=${{ needs.prebuild.outputs.preview_id }}') + console.log(result.data.PreviewLogs) + return result.data.PreviewLogs + + - name: Update logs check + uses: actions/github-script@v3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + + var summary = `Aggregated logs for the provisioning of preview infrastructure are provided below. + + Note that these are only logs for the provisioning of the infrastructure necessary for your preview. For troubleshooting problems with an active lesson, your best bet is the Jaeger deployment available [here](https://inspect-preview.nrelabs.io/search?lookback=12h&operation=api_livelesson_request&service=preview-${{ needs.prebuild.outputs.preview_id }}) + `; + + var logsText = `${{ steps.retrieve_preview_logs.outputs.result }}`; + + // https://octokit.github.io/rest.js/v18#checks-update + github.checks.update({ + owner: context.repo.owner, + repo: context.repo.repo, + check_run_id: "${{ needs.prebuild.outputs.preview_logs_check_id }}", + output: { + "title": "Preview Provisioning Logs", + "summary": summary, + "text": logsText, + }, + conclusion: "neutral", + }); + + - name: Update status check + uses: actions/github-script@v3 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + + var summary = `Your content is ready to be previewed, and is available at the link below: + + [Open Preview](https://preview-${{ needs.prebuild.outputs.preview_id }}.nrelabs.io) + + Note that this is a fully-deployed version of the main NRE Labs site, but includes the changes you've made in + this branch. Once you navigate to the site above, use the lesson catalog or other site navigation to find the content + you've added/changed. + + For more information, see the [NRE Labs documentation](https://docs.nrelabs.io/creating-contributing/preview-your-changes). + `; + var detail_text = `Some additional tools for you to use can be found below: + + - [Jaeger Traces (for troubleshooting lesson startup, etc)](https://inspect-preview.nrelabs.io/search?lookback=12h&operation=api_livelesson_request&service=preview-${{ needs.prebuild.outputs.preview_id }}) + `; + + // https://octokit.github.io/rest.js/v18#checks-update + github.checks.update({ + owner: context.repo.owner, + repo: context.repo.repo, + check_run_id: "${{ needs.prebuild.outputs.preview_check_id }}", + output: { + "title": "Preview is ready! Click 'Details' to continue.", + "summary": summary, + "text": detail_text, + }, + conclusion: "success", + }); + + # TODO - consider posting a comment here that just instructs the contributor to scroll down? YOu'll have to remember to delete the comment like you did before. diff --git a/check-changelog.sh b/.scripts/check-changelog.sh similarity index 100% rename from check-changelog.sh rename to .scripts/check-changelog.sh diff --git a/check-spelling.sh b/.scripts/check-spelling.sh similarity index 100% rename from check-spelling.sh rename to .scripts/check-spelling.sh diff --git a/create-preview.sh b/.scripts/create-preview.sh similarity index 92% rename from create-preview.sh rename to .scripts/create-preview.sh index 3f2db2d1..1260d56a 100755 --- a/create-preview.sh +++ b/.scripts/create-preview.sh @@ -14,7 +14,7 @@ fi echo $(curl -s $url --header "Content-Type: application/json" \ --data "{ - \"branch\":\"$GITHUB_HEAD_REF\", + \"branch\":\"$GITHUB_REF\", \"pullRequest\":\"$PR_ID\", \"repoSlug\":\"$GITHUB_REPOSITORY\", \"prSha\":\"$GITHUB_SHA\" diff --git a/.scripts/retag-images.sh b/.scripts/retag-images.sh new file mode 100755 index 00000000..47b450af --- /dev/null +++ b/.scripts/retag-images.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -e +o pipefail + +if [ -z "$1" ] +then + echo "Must pass JSON array of image names as first parameter to this script" +fi + +if [ -z "$2" ] +then + echo "Must provide source tag as second parameter to this script" +fi + +if [ -z "$3" ] +then + echo "Must provide preview ID as third parameter to this script" +fi + +wget -q "https://raw.githubusercontent.com/nre-learning/docker-retag/d702a5a109af5e8f04baea2c80782dc107142f19/docker-retag" && chmod +x docker-retag + +for row in $(echo "$1" | jq -r '.[] | @base64'); do + _getimage() { + echo ${row} | base64 --decode + } + + image=$(_getimage) + + if [ "$image" != "empty" ]; + then + $(pwd)/docker-retag antidotelabs/$image:$2 preview-$3 + fi +done + +rm -f $(pwd)/docker-retag diff --git a/start-preview.sh b/.scripts/start-preview.sh similarity index 100% rename from start-preview.sh rename to .scripts/start-preview.sh diff --git a/.scripts/wait-for-status.sh b/.scripts/wait-for-status.sh new file mode 100755 index 00000000..7349cd1c --- /dev/null +++ b/.scripts/wait-for-status.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash + +# set -e +o pipefail + +if [ -z "$1" ] +then + echo "Must provide preview ID as parameter to this script" + exit 1 +fi + +for i in {1..120} +do + + build_status=$(curl https://preview.nrelabs.io/status\?id\=$1 | jq -r .Status) + if [[ -z "$build_status" ]] + then + echo "Error retrieving build status" + exit 1 + fi + + if [[ "$build_status" == "FAILED" ]] + then + echo "Preview deployment failed" + exit 1 + elif [[ "$build_status" == "CREATED" ]] + then + echo "Preview deployment not started - call this script after a created preview has also been started." + exit 1 + elif [[ "$build_status" == "READY" ]] + then + echo "Preview deployment succeeded" + exit 0 + fi + + echo "Status for preview $1 is $build_status; sleeping for 10 seconds..." + sleep 10 + +done + +echo "Timed out" +exit 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index b192c77b..d929fc70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## In development - Adding image build to preview pipeline [#352](https://github.com/nre-learning/nrelabs-curriculum/pull/352) +- More image build changes (moving to GH actions) [#354](https://github.com/nre-learning/nrelabs-curriculum/pull/354) ## v1.3.0 - December 13, 2020 diff --git a/wait-for-status.sh b/wait-for-status.sh deleted file mode 100755 index 22c68b2d..00000000 --- a/wait-for-status.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash - -# set -e +o pipefail - -if [ -z "$1" ] -then - echo "Must provide status commit ID as parameter to this script" - exit 1 -fi - -for i in {1..120} -do - - # TODO - May want to consider checking for the status of the commit in general, which is always there - it's a key "state" - # at the top level of the returned object here, outside of the "statuses" array. - build_status=$(curl \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/nre-learning/nrelabs-curriculum/commits/$1/status | jq -r '.statuses[] | select(.context=="Building Endpoint Images").state') - - if [[ "$build_status" == "failure" ]] - then - echo "Status failed" - exit 1 - elif [[ "$build_status" == "success" ]] - then - echo "Status succeeded" - exit 0 - fi - - echo "Sleeping for 10 seconds..." - sleep 10 - -done - -echo "Timed out" -exit 1