Build Release #11
This file contains hidden or 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
| # Build Release — Builds a numbered release as Docker images and pushes to ECR. | |
| # | |
| # Manually triggered from the master branch. Validates the version input, builds both | |
| # nginx and tomcat images in parallel, pushes them to ECR, tags the commit, and creates | |
| # a GitHub release with auto-generated notes. For snapshot builds, see Build Snapshot. | |
| name: Build Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Release Version (e.g. 9.0.0)' | |
| required: true | |
| type: string | |
| # Run all release builds one-by-one, but never cancel a release build due to another one. | |
| concurrency: | |
| group: build-release | |
| cancel-in-progress: false | |
| env: | |
| TOMCAT_IMAGE: xh/toolbox-tomcat | |
| NGINX_IMAGE: xh/toolbox-nginx | |
| jobs: | |
| validate: | |
| # Releases are always cut from master, which tracks the last released code. This guards | |
| # against an accidental release from develop (which carries unreleased SNAPSHOT work) or | |
| # any other branch. | |
| if: github.ref == 'refs/heads/master' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| # Checkout is required to be called with `fetch-depth: 0` and `fetch-tags: true`. | |
| - name: Validate release version | |
| uses: xh/hoist-dev-utils/.github/actions/validate-release-version@master | |
| with: | |
| version: ${{ inputs.version }} | |
| prepare: | |
| needs: validate | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| outputs: | |
| build-tag: ${{ steps.tag.outputs.build-tag }} | |
| steps: | |
| - name: Generate build tag | |
| id: tag | |
| run: echo "build-tag=${GITHUB_SHA::7}_${GITHUB_REF_NAME}_$(date -u +'%Y-%m-%dT%H:%MZ')" >> "$GITHUB_OUTPUT" | |
| build-tomcat: | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup JDK 21 | |
| uses: actions/setup-java@v5 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v6 | |
| with: | |
| build-scan-publish: false | |
| build-scan-terms-of-use-url: 'https://gradle.com/terms-of-service' | |
| build-scan-terms-of-use-agree: 'yes' | |
| - name: Build WAR | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| run: ./gradlew -PxhAppVersion="$VERSION" -PxhAppBuild="${{ needs.prepare.outputs.build-tag }}" war | |
| - name: Copy WAR into Docker context | |
| run: cp build/libs/*.war docker/tomcat/app.war | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v6 | |
| with: | |
| role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/xh-github-actions-deploy | |
| aws-region: ${{ secrets.AWS_REGION }} | |
| - name: Login to Amazon ECR | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| - name: Build and push tomcat image | |
| env: | |
| ECR_ROOT: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| docker build -t "$ECR_ROOT/${{ env.TOMCAT_IMAGE }}:$VERSION" \ | |
| -t "$ECR_ROOT/${{ env.TOMCAT_IMAGE }}:latest" \ | |
| docker/tomcat | |
| docker push "$ECR_ROOT/${{ env.TOMCAT_IMAGE }}:$VERSION" | |
| docker push "$ECR_ROOT/${{ env.TOMCAT_IMAGE }}:latest" | |
| build-nginx: | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: 'client-app/.nvmrc' | |
| # Cache the yarn download directory directly (rather than via setup-node's | |
| # `cache: yarn`, which keys strictly on the lockfile hash with no fallback). | |
| # The `restore-keys` prefix lets a lockfile-changed run restore the most | |
| # recent prior cache, so `yarn install` only downloads packages that | |
| # actually moved — important given the FontAwesome Pro bandwidth cap. | |
| - name: Resolve Yarn cache directory | |
| id: yarn-cache-dir | |
| run: echo "dir=$(cd client-app && yarn cache dir)" >> "$GITHUB_OUTPUT" | |
| - name: Restore Yarn cache | |
| uses: actions/cache@v5 | |
| with: | |
| path: ${{ steps.yarn-cache-dir.outputs.dir }} | |
| key: yarn-${{ runner.os }}-${{ hashFiles('client-app/yarn.lock') }} | |
| restore-keys: | | |
| yarn-${{ runner.os }}- | |
| - name: Configure Font Awesome registry auth | |
| env: | |
| FONTAWESOME_PACKAGE_TOKEN: ${{ secrets.FONTAWESOME_PACKAGE_TOKEN }} | |
| run: echo "//npm.fontawesome.com/:_authToken=$FONTAWESOME_PACKAGE_TOKEN" >> client-app/.npmrc | |
| - name: Install dependencies and build client app | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| run: cd client-app && yarn install --frozen-lockfile && yarn lint && yarn build --env appVersion="$VERSION" --env appBuild="${{ needs.prepare.outputs.build-tag }}" | |
| - name: Copy client build into Docker context | |
| run: cp -r client-app/build/ docker/nginx/build/ | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v6 | |
| with: | |
| role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/xh-github-actions-deploy | |
| aws-region: ${{ secrets.AWS_REGION }} | |
| - name: Login to Amazon ECR | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| - name: Build and push nginx image | |
| env: | |
| ECR_ROOT: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com | |
| VERSION: ${{ inputs.version }} | |
| run: | | |
| docker build -t "$ECR_ROOT/${{ env.NGINX_IMAGE }}:$VERSION" \ | |
| -t "$ECR_ROOT/${{ env.NGINX_IMAGE }}:latest" \ | |
| docker/nginx | |
| docker push "$ECR_ROOT/${{ env.NGINX_IMAGE }}:$VERSION" | |
| docker push "$ECR_ROOT/${{ env.NGINX_IMAGE }}:latest" | |
| release: | |
| needs: [ build-tomcat, build-nginx ] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| # Note: The tag intentionally points to a commit where gradle.properties still has | |
| # the SNAPSHOT version. We chose not to commit the version change back to the repo to | |
| # avoid paired set/reset commits on every release. The built WAR artifact has the | |
| # correct release version — the tag is just a pointer to the source commit. | |
| # Checkout is required to be called with `fetch-depth: 0` and `fetch-tags: true`. | |
| - name: Create tag and GitHub release | |
| uses: xh/hoist-dev-utils/.github/actions/create-tag-and-github-release@master | |
| with: | |
| version: ${{ inputs.version }} | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |