name: Build and Release run-name: ${{ github.ref_name == 'release' && '[Prod]' || '[Dev]' }} Build ${{ github.run_number }} of branch ${{ github.ref_name }} by @${{ github.actor }} on: push: branches: [release] workflow_dispatch: inputs: deploy: description: 'Deploy to K8S' default: 'Skip' required: true type: choice options: - Skip - Staging Only - Staging + Prod sandbox: description: 'Deploy to Sandbox' default: true required: true type: boolean sandboxNoDbRefresh: description: 'Sandbox Disable Daily DB Refresh' default: false required: true type: boolean skiptests: description: 'Skip Tests' default: false required: true type: boolean skiparm: description: 'Skip ARM64 Build' default: false required: true type: boolean ignoreLowerCoverage: description: 'Ignore Lower Coverage' default: false required: true type: boolean updateCoverage: description: 'Update Baseline Coverage' default: false required: true type: boolean concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: # ----------------------------------------------------------------- # PREPARE # ----------------------------------------------------------------- prepare: name: Prepare Release runs-on: ubuntu-latest outputs: should_deploy: ${{ steps.buildvars.outputs.should_deploy }} pkg_version: ${{ steps.buildvars.outputs.pkg_version }} from_tag: ${{ steps.semver.outputs.nextStrict }} to_tag: ${{ steps.semver.outputs.current }} steps: - uses: actions/checkout@v4 with: fetch-depth: 1 fetch-tags: false - name: Get Next Version (Prod) if: ${{ github.ref_name == 'release' }} id: semver uses: ietf-tools/semver-action@v1 with: token: ${{ github.token }} branch: release skipInvalidTags: true - name: Get Dev Version if: ${{ github.ref_name != 'release' }} id: semverdev uses: ietf-tools/semver-action@v1 with: token: ${{ github.token }} branch: release skipInvalidTags: true noVersionBumpBehavior: 'current' noNewCommitBehavior: 'current' - name: Set Release Flag if: ${{ github.ref_name == 'release' }} run: | echo "IS_RELEASE=true" >> $GITHUB_ENV - name: Create Draft Release uses: ncipollo/release-action@v1.14.0 if: ${{ github.ref_name == 'release' }} with: prerelease: true draft: false commit: ${{ github.sha }} tag: ${{ steps.semver.outputs.nextStrict }} name: ${{ steps.semver.outputs.nextStrict }} body: '*pending*' token: ${{ secrets.GITHUB_TOKEN }} - name: Set Build Variables id: buildvars run: | if [[ $IS_RELEASE ]]; then echo "Using AUTO SEMVER mode: ${{ steps.semver.outputs.nextStrict }}" echo "should_deploy=true" >> $GITHUB_OUTPUT echo "pkg_version=${{ steps.semver.outputs.nextStrict }}" >> $GITHUB_OUTPUT echo "::notice::Release ${{ steps.semver.outputs.nextStrict }} created using branch $GITHUB_REF_NAME" else echo "Using TEST mode: ${{ steps.semverdev.outputs.nextMajorStrict }}.0.0-dev.$GITHUB_RUN_NUMBER" echo "should_deploy=false" >> $GITHUB_OUTPUT echo "pkg_version=${{ steps.semverdev.outputs.nextMajorStrict }}.0.0-dev.$GITHUB_RUN_NUMBER" >> $GITHUB_OUTPUT echo "::notice::Non-production build ${{ steps.semverdev.outputs.nextMajorStrict }}.0.0-dev.$GITHUB_RUN_NUMBER created using branch $GITHUB_REF_NAME" fi # ----------------------------------------------------------------- # TESTS # ----------------------------------------------------------------- tests: name: Run Tests uses: ./.github/workflows/tests.yml if: ${{ github.event.inputs.skiptests == 'false' || github.ref_name == 'release' }} needs: [prepare] with: ignoreLowerCoverage: ${{ github.event.inputs.ignoreLowerCoverage == 'true' }} # ----------------------------------------------------------------- # RELEASE # ----------------------------------------------------------------- release: name: Make Release if: ${{ !failure() && !cancelled() }} needs: [tests, prepare] runs-on: ubuntu-latest permissions: contents: write packages: write env: SHOULD_DEPLOY: ${{needs.prepare.outputs.should_deploy}} PKG_VERSION: ${{needs.prepare.outputs.pkg_version}} FROM_TAG: ${{needs.prepare.outputs.from_tag}} TO_TAG: ${{needs.prepare.outputs.to_tag}} steps: - uses: actions/checkout@v4 with: fetch-depth: 1 fetch-tags: false - name: Launch build VM id: azlaunch run: | echo "Authenticating to Azure..." az login --service-principal -u ${{ secrets.AZ_BUILD_APP_ID }} -p ${{ secrets.AZ_BUILD_PWD }} --tenant ${{ secrets.AZ_BUILD_TENANT_ID }} echo "Creating VM..." vminfo=$(az vm create \ --resource-group ghaDatatracker \ --name tmpGhaBuildVM \ --image Ubuntu2204 \ --admin-username azureuser \ --generate-ssh-keys \ --priority Spot \ --size Standard_D8as_v5 \ --max-price -1 \ --os-disk-size-gb 30 \ --eviction-policy Delete \ --nic-delete-option Delete \ --output tsv \ --query "publicIpAddress") echo "ipaddr=$vminfo" >> "$GITHUB_OUTPUT" echo "VM Public IP: $vminfo" cat ~/.ssh/id_rsa > ${{ github.workspace }}/prvkey.key ssh-keyscan -t rsa $vminfo >> ~/.ssh/known_hosts # - name: Copy build files # uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # with: # host: ${{ steps.azlaunch.outputs.ipaddr }} # port: 22 # username: azureuser # key_path: ${{ github.workspace }}/prvkey.key # source: "${{ github.workspace }},!${{ github.workspace }}/.git,!${{ github.workspace }}/prvkey.key" # target: /workspace - name: Remote SSH into Build VM uses: appleboy/ssh-action@25ce8cbbcb08177468c7ff7ec5cbfa236f9341e1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_ACTOR: ${{ github.actor }} GITHUB_SHA: ${{ github.sha }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_RUN_ID: ${{ github.run_id }} AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_STATIC_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_STATIC_KEY_SECRET }} AWS_DEFAULT_REGION: auto AWS_ENDPOINT_URL: ${{ secrets.CF_R2_ENDPOINT }} PKG_VERSION: ${{ env.PKG_VERSION }} SHOULD_DEPLOY: ${{ env.SHOULD_DEPLOY }} SKIP_TESTS: ${{ github.event.inputs.skiptests }} DEBIAN_FRONTEND: noninteractive BROWSERSLIST_IGNORE_OLD_DATA: 1 with: host: ${{ steps.azlaunch.outputs.ipaddr }} port: 22 username: azureuser command_timeout: 60m key_path: ${{ github.workspace }}/prvkey.key envs: GITHUB_TOKEN,GITHUB_ACTOR,GITHUB_SHA,GITHUB_REF_NAME,GITHUB_RUN_ID,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_DEFAULT_REGION,AWS_ENDPOINT_URL,PKG_VERSION,SHOULD_DEPLOY,SKIP_TESTS,DEBIAN_FRONTEND,BROWSERSLIST_IGNORE_OLD_DATA script_stop: true script: | export DEBIAN_FRONTEND=noninteractive lsb_release -a sudo apt-get update sudo apt-get upgrade -y sudo apt-get install wget unzip curl -y echo "==========================================================================" echo "Installing Docker..." echo "==========================================================================" curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh echo "==========================================================================" echo "Login to ghcr.io..." echo "==========================================================================" echo $GITHUB_TOKEN | sudo docker login ghcr.io -u $GITHUB_ACTOR --password-stdin echo "==========================================================================" echo "Installing GH CLI..." echo "==========================================================================" sudo mkdir -p -m 755 /etc/apt/keyrings \ && wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \ && sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \ && sudo apt update \ && sudo apt install gh -y echo "==========================================================================" echo "Installing AWS CLI..." echo "==========================================================================" curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install echo "==========================================================================" echo "Install Node.js..." echo "==========================================================================" curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh sudo bash nodesource_setup.sh sudo apt-get install -y nodejs corepack enable echo "==========================================================================" echo "Install Python 3.x..." echo "==========================================================================" sudo apt-get install python3 python3-dev -y python3 --version echo "==========================================================================" echo "Clone project..." echo "==========================================================================" sudo mkdir -p /workspace sudo chown azureuser /workspace cd /workspace gh repo clone ietf-tools/datatracker -- --depth=1 --no-tags cd datatracker if [ "$SKIP_TESTS" = "false" ] || [ "$GITHUB_REF_NAME" = "release" ] ; then echo "==========================================================================" echo "Downloading coverage..." echo "==========================================================================" gh run download $GITHUB_RUN_ID -n coverage fi echo "==========================================================================" echo "Building project..." echo "==========================================================================" echo "PKG_VERSION: $PKG_VERSION" echo "GITHUB_SHA: $GITHUB_SHA" echo "GITHUB_REF_NAME: $GITHUB_REF_NAME" echo "Running frontend build script..." echo "Compiling native node packages..." yarn rebuild echo "Packaging static assets..." yarn build --base=https://static.ietf.org/dt/$PKG_VERSION/ yarn legacy:build echo "Setting version $PKG_VERSION..." sed -i -r -e "s|^__version__ += '.*'$|__version__ = '$PKG_VERSION'|" ietf/__init__.py sed -i -r -e "s|^__release_hash__ += '.*'$|__release_hash__ = '$GITHUB_SHA'|" ietf/__init__.py sed -i -r -e "s|^__release_branch__ += '.*'$|__release_branch__ = '$GITHUB_REF_NAME'|" ietf/__init__.py if [ "$SHOULD_DEPLOY" = "true" ] ; then echo "==========================================================================" echo "Setting production flags in settings.py..." echo "==========================================================================" sed -i -r -e 's/^DEBUG *= *.*$/DEBUG = False/' -e "s/^SERVER_MODE *= *.*\$/SERVER_MODE = 'production'/" ietf/settings.py fi echo "==========================================================================" echo "Build release tarball..." echo "==========================================================================" mkdir -p /workspace/release tar -czf /workspace/release.tar.gz -X dev/build/exclude-patterns.txt . echo "==========================================================================" echo "Collecting statics..." echo "==========================================================================" docker run --rm --name collectstatics -v $(pwd):/workspace ghcr.io/ietf-tools/datatracker-app-base:latest sh dev/build/collectstatics.sh echo "Pushing statics..." cd static aws s3 sync . s3://static/dt/$PKG_VERSION --only-show-errors cd .. echo "==========================================================================" echo "Augment dockerignore for docker image build..." echo "==========================================================================" cat >> .dockerignore <&2 exit 64 # - name: Destroy Build VM + resources # if: always() # shell: pwsh # run: | # echo "Destroying VM..." # az vm delete -g ghaDatatracker -n tmpGhaBuildVM --yes --force-deletion true # $resourceOrderRemovalOrder = [ordered]@{ # "Microsoft.Compute/virtualMachines" = 0 # "Microsoft.Compute/disks" = 1 # "Microsoft.Network/networkInterfaces" = 2 # "Microsoft.Network/publicIpAddresses" = 3 # "Microsoft.Network/networkSecurityGroups" = 4 # "Microsoft.Network/virtualNetworks" = 5 # } # echo "Fetching remaining resources..." # $resources = az resource list --resource-group ghaDatatracker | ConvertFrom-Json # $orderedResources = $resources # | Sort-Object @{ # Expression = {$resourceOrderRemovalOrder[$_.type]} # Descending = $False # } # echo "Deleting remaining resources..." # $orderedResources | ForEach-Object { # az resource delete --resource-group ghaDatatracker --ids $_.id --verbose # } # echo "Logout from Azure..." # az logout # - name: Build Release Docker Image # uses: docker/build-push-action@v6 # env: # DOCKER_BUILD_SUMMARY: false # with: # context: . # file: dev/build/Dockerfile # platforms: ${{ github.event.inputs.skiparm == 'true' && 'linux/amd64' || 'linux/amd64,linux/arm64' }} # push: true # tags: ghcr.io/ietf-tools/datatracker:${{ env.PKG_VERSION }} # cache-from: type=gha # cache-to: type=gha,mode=max - name: Update CHANGELOG id: changelog uses: Requarks/changelog-action@v1 if: ${{ env.SHOULD_DEPLOY == 'true' }} with: token: ${{ github.token }} fromTag: ${{ env.FROM_TAG }} toTag: ${{ env.TO_TAG }} writeToFile: false - name: Prepare Coverage Action if: ${{ github.event.inputs.skiptests == 'false' || github.ref_name == 'release' }} working-directory: ./dev/coverage-action run: npm install - name: Process Coverage Stats + Chart id: covprocess uses: ./dev/coverage-action/ if: ${{ github.event.inputs.skiptests == 'false' || github.ref_name == 'release' }} with: token: ${{ github.token }} tokenCommon: ${{ secrets.GH_COMMON_TOKEN }} repoCommon: common version: ${{needs.prepare.outputs.pkg_version}} changelog: ${{ steps.changelog.outputs.changes }} summary: '' coverageResultsPath: coverage.json histCoveragePath: historical-coverage.json - name: Create Release uses: ncipollo/release-action@v1.14.0 if: ${{ env.SHOULD_DEPLOY == 'true' }} with: allowUpdates: true makeLatest: true draft: false tag: ${{ env.PKG_VERSION }} name: ${{ env.PKG_VERSION }} body: ${{ steps.covprocess.outputs.changelog }} artifacts: "/home/runner/work/release/release.tar.gz,coverage.json,historical-coverage.json" token: ${{ secrets.GITHUB_TOKEN }} - name: Update Baseline Coverage uses: ncipollo/release-action@v1.14.0 if: ${{ github.event.inputs.updateCoverage == 'true' || github.ref_name == 'release' }} with: allowUpdates: true tag: baseline omitBodyDuringUpdate: true omitNameDuringUpdate: true omitPrereleaseDuringUpdate: true replacesArtifacts: true artifacts: "coverage.json" token: ${{ secrets.GITHUB_TOKEN }} - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: name: release-${{ env.PKG_VERSION }} path: /home/runner/work/release/release.tar.gz # ----------------------------------------------------------------- # NOTIFY # ----------------------------------------------------------------- notify: name: Notify if: ${{ always() }} needs: [prepare, tests, release] runs-on: ubuntu-latest env: PKG_VERSION: ${{needs.prepare.outputs.pkg_version}} steps: - name: Notify on Slack (Success) if: ${{ !contains(join(needs.*.result, ','), 'failure') }} uses: slackapi/slack-github-action@v1.27.0 with: channel-id: ${{ secrets.SLACK_GH_BUILDS_CHANNEL_ID }} payload: | { "text": "Datatracker Build by ${{ github.triggering_actor }} - <@${{ secrets.SLACK_UID_RJSPARKS }}>", "attachments": [ { "color": "28a745", "fields": [ { "title": "Status", "short": true, "value": "Completed" } ] } ] } env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_GH_BOT }} - name: Notify on Slack (Failure) if: ${{ contains(join(needs.*.result, ','), 'failure') }} uses: slackapi/slack-github-action@v1.27.0 with: channel-id: ${{ secrets.SLACK_GH_BUILDS_CHANNEL_ID }} payload: | { "text": "Datatracker Build by ${{ github.triggering_actor }} - <@${{ secrets.SLACK_UID_RJSPARKS }}>", "attachments": [ { "color": "a82929", "fields": [ { "title": "Status", "short": true, "value": "Failed" } ] } ] } env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_GH_BOT }} # ----------------------------------------------------------------- # SANDBOX # ----------------------------------------------------------------- sandbox: name: Deploy to Sandbox if: ${{ !failure() && !cancelled() && github.event.inputs.sandbox == 'true' }} needs: [prepare, release] runs-on: [self-hosted, dev-server] environment: name: sandbox env: PKG_VERSION: ${{needs.prepare.outputs.pkg_version}} steps: - uses: actions/checkout@v4 - name: Download a Release Artifact uses: actions/download-artifact@v4.1.8 with: name: release-${{ env.PKG_VERSION }} - name: Deploy to containers env: DEBIAN_FRONTEND: noninteractive run: | echo "Reset production flags in settings.py..." sed -i -r -e 's/^DEBUG *= *.*$/DEBUG = True/' -e "s/^SERVER_MODE *= *.*\$/SERVER_MODE = 'development'/" ietf/settings.py echo "Install Deploy to Container CLI dependencies..." cd dev/deploy-to-container npm ci cd ../.. echo "Start Deploy..." node ./dev/deploy-to-container/cli.js --branch ${{ github.ref_name }} --domain dev.ietf.org --appversion ${{ env.PKG_VERSION }} --commit ${{ github.sha }} --ghrunid ${{ github.run_id }} --nodbrefresh ${{ github.event.inputs.sandboxNoDbRefresh }} - name: Cleanup old docker resources env: DEBIAN_FRONTEND: noninteractive run: | docker image prune -a -f # ----------------------------------------------------------------- # STAGING # ----------------------------------------------------------------- staging: name: Deploy to Staging if: ${{ !failure() && !cancelled() && (github.event.inputs.deploy == 'Staging Only' || github.event.inputs.deploy == 'Staging + Prod' || github.ref_name == 'release') }} needs: [prepare, release] runs-on: ubuntu-latest environment: name: staging env: PKG_VERSION: ${{needs.prepare.outputs.pkg_version}} steps: - name: Deploy to staging uses: the-actions-org/workflow-dispatch@v4 with: workflow: deploy.yml repo: ietf-tools/infra-k8s ref: main token: ${{ secrets.GH_INFRA_K8S_TOKEN }} inputs: '{ "environment":"${{ secrets.GHA_K8S_CLUSTER }}", "app":"datatracker", "appVersion":"${{ env.PKG_VERSION }}", "remoteRef":"${{ github.sha }}" }' wait-for-completion: true wait-for-completion-timeout: 10m wait-for-completion-interval: 30s display-workflow-run-url: false # ----------------------------------------------------------------- # PROD # ----------------------------------------------------------------- prod: name: Deploy to Production if: ${{ !failure() && !cancelled() && (github.event.inputs.deploy == 'Staging + Prod' || github.ref_name == 'release') }} needs: [prepare, staging] runs-on: ubuntu-latest environment: name: production env: PKG_VERSION: ${{needs.prepare.outputs.pkg_version}} steps: - name: Deploy to production uses: the-actions-org/workflow-dispatch@v4 with: workflow: deploy.yml repo: ietf-tools/infra-k8s ref: main token: ${{ secrets.GH_INFRA_K8S_TOKEN }} inputs: '{ "environment":"${{ secrets.GHA_K8S_CLUSTER }}", "app":"datatracker", "appVersion":"${{ env.PKG_VERSION }}", "remoteRef":"${{ github.sha }}" }' wait-for-completion: true wait-for-completion-timeout: 10m wait-for-completion-interval: 30s display-workflow-run-url: false