From 8f6d17d875e990645f3b5842815f6277a301683d Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Thu, 3 Sep 2026 14:32:25 -0300 Subject: [PATCH] Tag the build server image on its Node version (#38281) * Tag the build server image on its Node version mattermost-build-server was published as :${GO_VERSION}, but the image bakes NODE_VERSION into PATH at build time. Since setup-buildenv skips the build whenever `docker manifest inspect` finds that tag, bumping the Dockerfile's Node changed nothing: CI kept pulling the existing image, and the new Node only appeared once a master merge overwrote the tag in place -- which also silently changes what release branches pinned to that tag resolve to. Tag on both instead, e.g. go1.26.7-node24.11.1. A Node bump now names a tag nobody has published, so it gets built and published exactly once, the same way a Go bump does. The "go" prefix keeps these distinct from the bare Go versions published under the old scheme. server/build/buildenv-tag.sh is the one place that composes the tag, and it verifies both Dockerfiles agree on Node since one tag covers both images. CI resolves it where it already resolved the Go version and propagates BUILD_SERVER_TAG, so the buildenv actions just take a string. That leaves nothing needing a threaded Go version except the actions/setup-go in server-test-template, which reads server/.go-version via go-version-file. Only two of that template's seven callers shard at all, so the rest were passing a value nothing read. Node also moves to 24.11.1 to match .nvmrc, which is what the webapp's engines constraint has expected for a while. The buildenv job in server-ci.yml is renamed from "go" to match server-ci-weekly and server-ci-nightly-race, so its check name changes from "Compute Go Version" to "Build Environment". * Fail the step when buildenv-tag.sh fails `echo BUILD_SERVER_TAG=$(./buildenv-tag.sh)` reports echo's exit status, not the script's, so under `bash -e` a failing script left the step green and wrote an empty value: $ bash -e -c 'echo "TAG=$(false)"; echo continued' TAG= continued That defeated the consistency check the script exists for: on drift it exits 1, the step passed anyway, and setup-buildenv went on to inspect `mattermost-build-server:` with no tag -- failing well away from the cause. Capture into a variable first, which `bash -e` does catch. Applies to all nine call sites, including the ones already quoted; quoting doesn't help here. * Rebuild the buildenv image when a version pin changes buildenv-tag.sh reads server/.go-version and .nvmrc, so name them in the paths that trigger a republish. Redundant in practice -- the script requires the pins and the Dockerfiles to agree, so a valid pin change always carries a Dockerfile change, which already triggers this -- but it makes the dependency explicit and fails here rather than in Server CI. * Pass buildenv inputs via env, not expression interpolation GitHub expands `${{ }}` into the script before bash runs it, so `echo "BUILDENV_TAG=${{ inputs.build-server-tag }}"` executed whatever the input contained. That was reachable: buildenv-tag.sh reads ARG NODE_VERSION straight out of a Dockerfile, and a fork PR controls that file, so `ARG NODE_VERSION=$(...)` would have landed in the tag and then in this script. Pass all three inputs through env instead, and reject a tag that isn't usable as a docker tag anyway. Also fixes the aggregate's error message, which still named the 'go' job after it was renamed to 'buildenv'. * Don't write a literal expression in a shell comment GitHub parses ${{ ... }} anywhere in the template, including inside a run: block's comment text, so an empty one failed the action manifest: "An expression was expected". * Drop the redundant tag validation The env mapping is the fix: bash gets the input as a value, never as script text, so there is nothing left to inject with. The regex only restated what docker would reject anyway. * Validate the tag before emitting it * Pass buildenv values as step outputs, not GITHUB_ENV --- .github/actions/load-buildenv/action.yml | 18 ++-- .github/actions/run-in-buildenv/action.yml | 18 ++-- .github/actions/setup-buildenv/action.yml | 65 ++++++++------ .github/workflows/build-server-image.yml | 26 +++--- .github/workflows/mmctl-test-template.yml | 6 +- .github/workflows/server-ci-nightly-race.yml | 10 ++- .github/workflows/server-ci-weekly.yml | 14 +-- .github/workflows/server-ci.yml | 91 +++++++++++--------- .github/workflows/server-test-template.yml | 8 +- .nvmrc | 2 +- server/build/Dockerfile.buildenv | 2 +- server/build/Dockerfile.buildenv-fips | 2 +- server/build/buildenv-tag.sh | 57 ++++++++++++ 13 files changed, 202 insertions(+), 117 deletions(-) create mode 100755 server/build/buildenv-tag.sh diff --git a/.github/actions/load-buildenv/action.yml b/.github/actions/load-buildenv/action.yml index 697dd1759ed..d6c3c3e77eb 100644 --- a/.github/actions/load-buildenv/action.yml +++ b/.github/actions/load-buildenv/action.yml @@ -1,13 +1,13 @@ name: Load build environment description: > Loads build-server images uploaded by setup-buildenv into the local Docker - daemon, and resolves the image name from the Go version. Use in jobs that - run the container directly (test jobs). For jobs that only need a shell - inside the container, use run-in-buildenv instead. + daemon, and resolves the image name from the tag. Use in jobs that run the + container directly (test jobs). For jobs that only need a shell inside the + container, use run-in-buildenv instead. inputs: - go-version: - description: "Go version used to construct the image tag (e.g. 1.26.3)." + build-server-tag: + description: "Build image tag, from server/build/buildenv-tag.sh (e.g. go1.26.3-node24.11.1)" required: true fips-enabled: description: "Set to 'true' to resolve the FIPS build image" @@ -16,7 +16,7 @@ inputs: outputs: image: - description: "Fully-qualified build image name (e.g. mattermost/mattermost-build-server:1.26.3)." + description: "Fully-qualified build image name (e.g. mattermost/mattermost-build-server:go1.26.3-node24.11.1)." value: ${{ steps.resolve.outputs.image }} runs: @@ -57,10 +57,10 @@ runs: shell: bash env: FIPS: ${{ inputs.fips-enabled }} - GO_VERSION: ${{ inputs.go-version }} + TAG: ${{ inputs.build-server-tag }} run: | if [[ "${FIPS}" == 'true' ]]; then - echo "image=mattermost/mattermost-build-server-fips:${GO_VERSION}" >> "${GITHUB_OUTPUT}" + echo "image=mattermost/mattermost-build-server-fips:${TAG}" >> "${GITHUB_OUTPUT}" else - echo "image=mattermost/mattermost-build-server:${GO_VERSION}" >> "${GITHUB_OUTPUT}" + echo "image=mattermost/mattermost-build-server:${TAG}" >> "${GITHUB_OUTPUT}" fi diff --git a/.github/actions/run-in-buildenv/action.yml b/.github/actions/run-in-buildenv/action.yml index 5be49822119..9491684e4a3 100644 --- a/.github/actions/run-in-buildenv/action.yml +++ b/.github/actions/run-in-buildenv/action.yml @@ -14,8 +14,8 @@ inputs: run: description: "Bash script to execute inside the container" required: true - go-version: - description: "Go version to use (e.g. 1.24.3). Defaults to the version in server/.go-version." + build-server-tag: + description: "Build image tag. Defaults to server/build/buildenv-tag.sh." required: false default: "" fips-enabled: @@ -30,20 +30,22 @@ inputs: runs: using: composite steps: - - name: Read Go version + - name: Read build image tag + id: tag shell: bash env: - GO_VERSION_INPUT: ${{ inputs.go-version }} + TAG_INPUT: ${{ inputs.build-server-tag }} run: | - if [[ -n "${GO_VERSION_INPUT}" ]]; then - echo "GO_VERSION=${GO_VERSION_INPUT}" >> "${GITHUB_ENV}" + if [[ -n "${TAG_INPUT}" ]]; then + tag="${TAG_INPUT}" else - echo "GO_VERSION=$(cat server/.go-version)" >> "${GITHUB_ENV}" + tag=$(./server/build/buildenv-tag.sh) fi + echo "tag=${tag}" >> "${GITHUB_OUTPUT}" - uses: ./.github/actions/load-buildenv id: buildenv with: - go-version: ${{ env.GO_VERSION }} + build-server-tag: ${{ steps.tag.outputs.tag }} fips-enabled: ${{ inputs.fips-enabled }} - name: Run in buildenv shell: bash diff --git a/.github/actions/setup-buildenv/action.yml b/.github/actions/setup-buildenv/action.yml index 972618702ba..c4f6ca8b01a 100644 --- a/.github/actions/setup-buildenv/action.yml +++ b/.github/actions/setup-buildenv/action.yml @@ -1,12 +1,12 @@ name: Setup build environment description: > Builds the mattermost-build-server images if they don't yet exist on Docker - Hub (e.g. during a Go version bump), and uploads them as artifacts for - downstream jobs. Run once per workflow before any build or test jobs. + Hub (e.g. during a Go or Node version bump), and uploads them as artifacts + for downstream jobs. Run once per workflow before any build or test jobs. inputs: - go-version: - description: "Go version (e.g. 1.26.3)" + build-server-tag: + description: "Build image tag, from server/build/buildenv-tag.sh (e.g. go1.26.3-node24.11.1)" required: true dockerfile: description: "Path to Dockerfile.buildenv (relative to workspace root)" @@ -21,44 +21,50 @@ runs: using: composite steps: - name: Set constants + id: constants shell: bash run: | - echo "BUILDENV_IMAGE=mattermost/mattermost-build-server" >> "${GITHUB_ENV}" - echo "BUILDENV_ARTIFACT=buildenv-image" >> "${GITHUB_ENV}" - echo "BUILDENV_DOCKERFILE=${{ inputs.dockerfile }}" >> "${GITHUB_ENV}" - echo "BUILDENV_FIPS_IMAGE=mattermost/mattermost-build-server-fips" >> "${GITHUB_ENV}" - echo "BUILDENV_FIPS_ARTIFACT=buildenv-fips-image" >> "${GITHUB_ENV}" - echo "BUILDENV_FIPS_DOCKERFILE=${{ inputs.dockerfile-fips }}" >> "${GITHUB_ENV}" - echo "BUILDENV_TMPDIR=/tmp/buildenv-save" >> "${GITHUB_ENV}" + { + echo "image=mattermost/mattermost-build-server" + echo "artifact=buildenv-image" + echo "fips-image=mattermost/mattermost-build-server-fips" + echo "fips-artifact=buildenv-fips-image" + echo "tmpdir=/tmp/buildenv-save" + } >> "${GITHUB_OUTPUT}" # ── Regular ──────────────────────────────────────────────────────── - name: Resolve regular image id: resolve shell: bash env: - GO_VERSION: ${{ inputs.go-version }} + BUILDENV_IMAGE: ${{ steps.constants.outputs.image }} + BUILDENV_TAG: ${{ inputs.build-server-tag }} run: | - docker manifest inspect "${BUILDENV_IMAGE}:${GO_VERSION}" > /dev/null 2>&1 || \ + docker manifest inspect "${BUILDENV_IMAGE}:${BUILDENV_TAG}" > /dev/null 2>&1 || \ echo "needs-build=true" >> "${GITHUB_OUTPUT}" - name: Build regular image if: ${{ steps.resolve.outputs.needs-build == 'true' }} shell: bash env: - GO_VERSION: ${{ inputs.go-version }} - run: docker build -t "${BUILDENV_IMAGE}:${GO_VERSION}" - < "${BUILDENV_DOCKERFILE}" + BUILDENV_IMAGE: ${{ steps.constants.outputs.image }} + BUILDENV_TAG: ${{ inputs.build-server-tag }} + BUILDENV_DOCKERFILE: ${{ inputs.dockerfile }} + run: docker build -t "${BUILDENV_IMAGE}:${BUILDENV_TAG}" - < "${BUILDENV_DOCKERFILE}" - name: Save and upload regular image if: ${{ steps.resolve.outputs.needs-build == 'true' }} shell: bash env: - GO_VERSION: ${{ inputs.go-version }} + BUILDENV_IMAGE: ${{ steps.constants.outputs.image }} + BUILDENV_TAG: ${{ inputs.build-server-tag }} + BUILDENV_TMPDIR: ${{ steps.constants.outputs.tmpdir }} run: | mkdir -p "${BUILDENV_TMPDIR}" - docker save "${BUILDENV_IMAGE}:${GO_VERSION}" | gzip > "${BUILDENV_TMPDIR}/image.tar.gz" + docker save "${BUILDENV_IMAGE}:${BUILDENV_TAG}" | gzip > "${BUILDENV_TMPDIR}/image.tar.gz" - name: Upload regular image artifact if: ${{ steps.resolve.outputs.needs-build == 'true' }} uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: - name: ${{ env.BUILDENV_ARTIFACT }} - path: ${{ env.BUILDENV_TMPDIR }}/image.tar.gz + name: ${{ steps.constants.outputs.artifact }} + path: ${{ steps.constants.outputs.tmpdir }}/image.tar.gz retention-days: 1 # ── FIPS (skipped on fork PRs) ───────────────────────────────────── - name: Docker Hub login @@ -77,28 +83,33 @@ runs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} shell: bash env: - GO_VERSION: ${{ inputs.go-version }} + BUILDENV_FIPS_IMAGE: ${{ steps.constants.outputs.fips-image }} + BUILDENV_TAG: ${{ inputs.build-server-tag }} run: | - docker manifest inspect "${BUILDENV_FIPS_IMAGE}:${GO_VERSION}" > /dev/null 2>&1 || \ + docker manifest inspect "${BUILDENV_FIPS_IMAGE}:${BUILDENV_TAG}" > /dev/null 2>&1 || \ echo "needs-build=true" >> "${GITHUB_OUTPUT}" - name: Build FIPS image if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }} shell: bash env: - GO_VERSION: ${{ inputs.go-version }} - run: docker build -t "${BUILDENV_FIPS_IMAGE}:${GO_VERSION}" - < "${BUILDENV_FIPS_DOCKERFILE}" + BUILDENV_FIPS_IMAGE: ${{ steps.constants.outputs.fips-image }} + BUILDENV_TAG: ${{ inputs.build-server-tag }} + BUILDENV_FIPS_DOCKERFILE: ${{ inputs.dockerfile-fips }} + run: docker build -t "${BUILDENV_FIPS_IMAGE}:${BUILDENV_TAG}" - < "${BUILDENV_FIPS_DOCKERFILE}" - name: Save and upload FIPS image if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }} shell: bash env: - GO_VERSION: ${{ inputs.go-version }} + BUILDENV_FIPS_IMAGE: ${{ steps.constants.outputs.fips-image }} + BUILDENV_TAG: ${{ inputs.build-server-tag }} + BUILDENV_TMPDIR: ${{ steps.constants.outputs.tmpdir }} run: | mkdir -p "${BUILDENV_TMPDIR}" - docker save "${BUILDENV_FIPS_IMAGE}:${GO_VERSION}" | gzip > "${BUILDENV_TMPDIR}/image-fips.tar.gz" + docker save "${BUILDENV_FIPS_IMAGE}:${BUILDENV_TAG}" | gzip > "${BUILDENV_TMPDIR}/image-fips.tar.gz" - name: Upload FIPS image artifact if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }} uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: - name: ${{ env.BUILDENV_FIPS_ARTIFACT }} - path: ${{ env.BUILDENV_TMPDIR }}/image-fips.tar.gz + name: ${{ steps.constants.outputs.fips-artifact }} + path: ${{ steps.constants.outputs.tmpdir }}/image-fips.tar.gz retention-days: 1 diff --git a/.github/workflows/build-server-image.yml b/.github/workflows/build-server-image.yml index be2dd2392d0..ed430a19d1e 100644 --- a/.github/workflows/build-server-image.yml +++ b/.github/workflows/build-server-image.yml @@ -8,11 +8,17 @@ on: paths: - server/build/Dockerfile.buildenv - server/build/Dockerfile.buildenv-fips + - server/build/buildenv-tag.sh + - server/.go-version + - .nvmrc - .github/workflows/build-server-image.yml pull_request: paths: - server/build/Dockerfile.buildenv - server/build/Dockerfile.buildenv-fips + - server/build/buildenv-tag.sh + - server/.go-version + - .nvmrc - .github/workflows/build-server-image.yml workflow_dispatch: @@ -52,11 +58,11 @@ jobs: run: | docker run --rm mattermost/mattermost-build-server:test /bin/sh -c "go version && node --version" - - name: buildenv/calculate-golang-version - id: go + - name: buildenv/calculate-tag + id: tag run: | - GO_VERSION=$(docker run --rm mattermost/mattermost-build-server:test go version | awk '{print $3}' | sed 's/go//') - echo "GO_VERSION=${GO_VERSION}" >> "${GITHUB_OUTPUT}" + tag=$(./server/build/buildenv-tag.sh) + echo "TAG=${tag}" >> "${GITHUB_OUTPUT}" - name: buildenv/push if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') @@ -67,7 +73,7 @@ jobs: load: false push: true pull: true - tags: mattermost/mattermost-build-server:${{ steps.go.outputs.GO_VERSION }} + tags: mattermost/mattermost-build-server:${{ steps.tag.outputs.TAG }} build-image-fips: permissions: @@ -103,11 +109,11 @@ jobs: run: | docker run --rm --entrypoint bash mattermost/mattermost-build-server-fips:test -c "go version && node --version" - - name: buildenv/calculate-golang-version - id: go + - name: buildenv/calculate-tag + id: tag run: | - GO_VERSION=$(docker run --rm --entrypoint bash mattermost/mattermost-build-server-fips:test -c "go version" | awk '{print $3}' | sed 's/go//') - echo "GO_VERSION=${GO_VERSION}" >> "${GITHUB_OUTPUT}" + tag=$(./server/build/buildenv-tag.sh) + echo "TAG=${tag}" >> "${GITHUB_OUTPUT}" - name: buildenv/push if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') @@ -118,4 +124,4 @@ jobs: load: false push: true pull: true - tags: mattermost/mattermost-build-server-fips:${{ steps.go.outputs.GO_VERSION }} + tags: mattermost/mattermost-build-server-fips:${{ steps.tag.outputs.TAG }} diff --git a/.github/workflows/mmctl-test-template.yml b/.github/workflows/mmctl-test-template.yml index 3805a4cd2ae..eee23eae534 100644 --- a/.github/workflows/mmctl-test-template.yml +++ b/.github/workflows/mmctl-test-template.yml @@ -14,7 +14,8 @@ on: logsartifact: required: true type: string - go-version: + build-server-tag: + description: "Build image tag (e.g. go1.26.3-node24.11.1)" required: true type: string fips-enabled: @@ -43,7 +44,6 @@ jobs: COMPOSE_PROJECT_NAME: ghactions INPUT_DATASOURCE: ${{ inputs.datasource }} INPUT_FIPS_ENABLED: ${{ inputs.fips-enabled }} - INPUT_GO_VERSION: ${{ inputs.go-version }} INPUT_LOGSARTIFACT: ${{ inputs.logsartifact }} INPUT_NAME: ${{ inputs.name }} INPUT_PR_NUMBER: ${{ github.event.pull_request.number }} @@ -64,7 +64,7 @@ jobs: id: buildenv uses: ./.github/actions/load-buildenv with: - go-version: ${{ inputs.go-version }} + build-server-tag: ${{ inputs.build-server-tag }} fips-enabled: ${{ inputs.fips-enabled }} - name: Setup log artifact name id: log-artifact diff --git a/.github/workflows/server-ci-nightly-race.yml b/.github/workflows/server-ci-nightly-race.yml index b0b78dab5a7..537a4278f82 100644 --- a/.github/workflows/server-ci-nightly-race.yml +++ b/.github/workflows/server-ci-nightly-race.yml @@ -25,16 +25,18 @@ jobs: name: Build Environment runs-on: ubuntu-22.04 outputs: - version: ${{ steps.calculate.outputs.GO_VERSION }} + build-server-tag: ${{ steps.calculate.outputs.BUILD_SERVER_TAG }} steps: - name: Checkout mattermost project uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Calculate version + - name: Calculate build image tag id: calculate working-directory: server/ - run: echo GO_VERSION=$(cat .go-version) >> "${GITHUB_OUTPUT}" + run: | + tag=$(./build/buildenv-tag.sh) + echo "BUILD_SERVER_TAG=${tag}" >> "${GITHUB_OUTPUT}" test-race: name: Race Detector @@ -48,7 +50,7 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 drivername: postgres logsartifact: race-detector-server-test-logs - go-version: ${{ needs.buildenv.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: false fullyparallel: false race-enabled: true diff --git a/.github/workflows/server-ci-weekly.yml b/.github/workflows/server-ci-weekly.yml index c20d70f650b..e435cfeae6f 100644 --- a/.github/workflows/server-ci-weekly.yml +++ b/.github/workflows/server-ci-weekly.yml @@ -26,16 +26,18 @@ jobs: name: Build Environment runs-on: ubuntu-22.04 outputs: - version: ${{ steps.calculate.outputs.GO_VERSION }} + build-server-tag: ${{ steps.calculate.outputs.BUILD_SERVER_TAG }} steps: - name: Checkout mattermost project uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Calculate version + - name: Calculate build image tag id: calculate working-directory: server/ - run: echo GO_VERSION=$(cat .go-version) >> "${GITHUB_OUTPUT}" + run: | + tag=$(./build/buildenv-tag.sh) + echo "BUILD_SERVER_TAG=${tag}" >> "${GITHUB_OUTPUT}" test-postgres-binary: name: Postgres with binary parameters @@ -49,7 +51,7 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10&binary_parameters=yes drivername: postgres logsartifact: postgres-binary-server-test-logs - go-version: ${{ needs.buildenv.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: false # Unsharded run on a single 8-core runner: fullyparallel=true causes # resource exhaustion (too many server instances, WebSocket hubs, and @@ -71,7 +73,7 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 drivername: postgres logsartifact: postgres-server-fips-test-logs - go-version: ${{ needs.buildenv.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: true # Unsharded run on a single 8-core runner: see note on test-postgres-binary. fullyparallel: false @@ -92,5 +94,5 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 drivername: postgres logsartifact: mmctl-fips-test-logs - go-version: ${{ needs.buildenv.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: true diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index a320129a80b..e6d2e282e74 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -20,8 +20,8 @@ permissions: contents: read jobs: - go: - name: Compute Go Version + buildenv: + name: Build Environment runs-on: ubuntu-22.04 permissions: contents: read @@ -29,7 +29,7 @@ jobs: actions: write id-token: write # for chainguard (FIPS base image pull) outputs: - version: ${{ steps.calculate.outputs.GO_VERSION }} + build-server-tag: ${{ steps.calculate.outputs.BUILD_SERVER_TAG }} gomod-changed: ${{ steps.changed-files.outputs.any_changed }} # Individual jobs below use this to skip via `if:` rather # than the workflow being skipped entirely, @@ -40,10 +40,12 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Calculate version + - name: Calculate build image tag id: calculate working-directory: server/ - run: echo GO_VERSION=$(cat .go-version) >> "${GITHUB_OUTPUT}" + run: | + tag=$(./build/buildenv-tag.sh) + echo "BUILD_SERVER_TAG=${tag}" >> "${GITHUB_OUTPUT}" - name: Check for go.mod changes id: changed-files uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 @@ -61,6 +63,9 @@ jobs: .github/workflows/server-test-template.yml .github/workflows/server-test-merge-template.yml .github/workflows/mmctl-test-template.yml + .github/actions/setup-buildenv/** + .github/actions/load-buildenv/** + .github/actions/run-in-buildenv/** tools/mattermost-govet/** files_ignore: | server/**/*.md @@ -73,11 +78,11 @@ jobs: DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} uses: ./.github/actions/setup-buildenv with: - go-version: ${{ steps.calculate.outputs.GO_VERSION }} + build-server-tag: ${{ steps.calculate.outputs.BUILD_SERVER_TAG }} check-generated: name: Check generated files - needs: go - if: needs.go.outputs.relevant-changed == 'true' + needs: buildenv + if: needs.buildenv.outputs.relevant-changed == 'true' runs-on: ubuntu-22.04 services: # make default-roles-permissions snapshots a live database. @@ -115,9 +120,9 @@ jobs: fi check-backport-migrations: name: Check backport migrations - needs: go + needs: buildenv # Only backports (release-* base) must keep a migration's version+name; new master migrations are expected. - if: needs.go.outputs.relevant-changed == 'true' && startsWith(github.base_ref, 'release-') + if: needs.buildenv.outputs.relevant-changed == 'true' && startsWith(github.base_ref, 'release-') runs-on: ubuntu-22.04 steps: - name: Checkout mattermost project @@ -134,8 +139,8 @@ jobs: make check-backport-migrations check-style: name: check-style - needs: go - if: needs.go.outputs.relevant-changed == 'true' + needs: buildenv + if: needs.buildenv.outputs.relevant-changed == 'true' runs-on: ubuntu-22.04 steps: - name: Checkout mattermost project @@ -147,8 +152,8 @@ jobs: run: make check-style check-mattermost-vet-api: name: Vet API - needs: go - if: needs.go.outputs.relevant-changed == 'true' + needs: buildenv + if: needs.buildenv.outputs.relevant-changed == 'true' runs-on: ubuntu-22.04 steps: - name: Checkout mattermost project @@ -165,8 +170,8 @@ jobs: # -- Sharded into 4 parallel runners for ~88% wall-time improvement -- test-postgres-normal: name: Postgres (shard ${{ matrix.shard }}) - needs: go - if: needs.go.outputs.relevant-changed == 'true' + needs: buildenv + if: needs.buildenv.outputs.relevant-changed == 'true' strategy: fail-fast: false # Let all shards complete so we get full test results matrix: @@ -184,7 +189,7 @@ jobs: # Each shard gets a unique artifact name so they don't collide logsartifact: "postgres-server-test-logs-shard-${{ matrix.shard }}" enablecoverage: ${{ github.event_name != 'pull_request' || !startsWith(github.event.pull_request.base.ref, 'release-') }} - go-version: ${{ needs.go.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: false shard-index: ${{ matrix.shard }} shard-total: 4 @@ -205,8 +210,8 @@ jobs: test-elasticsearch-v8: name: Elasticsearch v8 Compatibility - needs: go - if: needs.go.outputs.relevant-changed == 'true' + needs: buildenv + if: needs.buildenv.outputs.relevant-changed == 'true' permissions: contents: read actions: write @@ -216,15 +221,15 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 drivername: postgres logsartifact: elasticsearch-v8-server-test-logs - go-version: ${{ needs.go.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: false elasticsearch-version: "8.9.0" test-target: "test-server-elasticsearch" test-opensearch-v2: name: OpenSearch v2 Compatibility - needs: go - if: needs.go.outputs.relevant-changed == 'true' + needs: buildenv + if: needs.buildenv.outputs.relevant-changed == 'true' permissions: contents: read actions: write @@ -234,7 +239,7 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 drivername: postgres logsartifact: opensearch-v2-server-test-logs - go-version: ${{ needs.go.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: false opensearch-version: "2.19.0" test-target: "test-server-opensearch" @@ -243,10 +248,10 @@ jobs: # Sharded for fast iteration. Weekly workflow provides regular full coverage. test-postgres-normal-fips: if: >- - needs.go.outputs.relevant-changed == 'true' && - (contains(github.head_ref, 'fips') || needs.go.outputs.gomod-changed == 'true') + needs.buildenv.outputs.relevant-changed == 'true' && + (contains(github.head_ref, 'fips') || needs.buildenv.outputs.gomod-changed == 'true') name: "Postgres FIPS (shard ${{ matrix.shard }})" - needs: go + needs: buildenv strategy: fail-fast: false matrix: @@ -263,7 +268,7 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 drivername: postgres logsartifact: "postgres-server-fips-test-logs-shard-${{ matrix.shard }}" - go-version: ${{ needs.go.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: true shard-index: ${{ matrix.shard }} shard-total: 4 @@ -281,8 +286,8 @@ jobs: test-mmctl: name: Run mmctl tests - needs: go - if: needs.go.outputs.relevant-changed == 'true' + needs: buildenv + if: needs.buildenv.outputs.relevant-changed == 'true' permissions: contents: read actions: write @@ -294,14 +299,14 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 drivername: postgres logsartifact: mmctl-test-logs - go-version: ${{ needs.go.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: false test-mmctl-fips: if: >- - needs.go.outputs.relevant-changed == 'true' && - (contains(github.head_ref, 'fips') || needs.go.outputs.gomod-changed == 'true') + needs.buildenv.outputs.relevant-changed == 'true' && + (contains(github.head_ref, 'fips') || needs.buildenv.outputs.gomod-changed == 'true') name: Run mmctl tests (FIPS) - needs: go + needs: buildenv permissions: contents: read actions: write @@ -315,13 +320,13 @@ jobs: datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 drivername: postgres logsartifact: mmctl-fips-test-logs - go-version: ${{ needs.go.outputs.version }} + build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }} fips-enabled: true build-mattermost-server: name: Build mattermost server app - needs: go - if: needs.go.outputs.relevant-changed == 'true' + needs: buildenv + if: needs.buildenv.outputs.relevant-changed == 'true' permissions: contents: read actions: write @@ -370,7 +375,7 @@ jobs: permissions: {} runs-on: ubuntu-22.04 needs: - - go + - buildenv - merge-postgres-test-results - test-elasticsearch-v8 - test-opensearch-v2 @@ -383,8 +388,8 @@ jobs: run: | # Explicitly fail here rather than relying on `if:`, since a job # skipped via `if:` is reported as "Success" by GitHub. - if [ "${{ needs.go.result }}" != "success" ]; then - echo "::error::'go' job did not succeed (result: ${{ needs.go.result }}); relevant-changed could not be computed reliably. Failing Server CI Complete." + if [ "${{ needs.buildenv.result }}" != "success" ]; then + echo "::error::'buildenv' job did not succeed (result: ${{ needs.buildenv.result }}); relevant-changed could not be computed reliably. Failing Server CI Complete." exit 1 fi echo "Server CI Complete" @@ -393,7 +398,7 @@ jobs: name: Server CI Report # Skip cleanly (rather than fail) when no relevant Server CI paths # changed, since none of the test jobs it reports on will have run. - if: always() && needs.go.outputs.relevant-changed == 'true' + if: always() && needs.buildenv.outputs.relevant-changed == 'true' permissions: contents: read actions: read @@ -401,7 +406,7 @@ jobs: pull-requests: write issues: write needs: - - go + - buildenv - ci-complete uses: ./.github/workflows/server-ci-report.yml secrets: @@ -421,7 +426,7 @@ jobs: # (and thus server-dist-artifact) will not have run. if: >- success('ci-complete') && - needs.go.outputs.relevant-changed == 'true' && + needs.buildenv.outputs.relevant-changed == 'true' && github.repository_owner == 'mattermost' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository @@ -430,7 +435,7 @@ jobs: actions: read statuses: write needs: - - go + - buildenv - ci-complete uses: ./.github/workflows/server-ci-artifacts.yml secrets: diff --git a/.github/workflows/server-test-template.yml b/.github/workflows/server-test-template.yml index 7e6e7787fcc..5dc0e82dba2 100644 --- a/.github/workflows/server-test-template.yml +++ b/.github/workflows/server-test-template.yml @@ -26,7 +26,8 @@ on: required: false type: boolean default: false - go-version: + build-server-tag: + description: "Build image tag (e.g. go1.26.3-node24.11.1)" required: true type: string fips-enabled: @@ -90,7 +91,6 @@ jobs: INPUT_ENABLECOVERAGE: ${{ inputs.enablecoverage }} INPUT_FIPS_ENABLED: ${{ inputs.fips-enabled }} INPUT_FULLYPARALLEL: ${{ inputs.fullyparallel }} - INPUT_GO_VERSION: ${{ inputs.go-version }} INPUT_LOGSARTIFACT: ${{ inputs.logsartifact }} INPUT_NAME: ${{ inputs.name }} INPUT_PR_NUMBER: ${{ github.event.pull_request.number }} @@ -114,7 +114,7 @@ jobs: id: buildenv uses: ./.github/actions/load-buildenv with: - go-version: ${{ inputs.go-version }} + build-server-tag: ${{ inputs.build-server-tag }} fips-enabled: ${{ inputs.fips-enabled }} - name: Restore test timing data @@ -180,7 +180,7 @@ jobs: if: inputs.shard-total > 1 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: ${{ inputs.go-version }} + go-version-file: server/.go-version - name: Split tests across shards if: inputs.shard-total > 1 diff --git a/.nvmrc b/.nvmrc index f88da62e246..9e2934aa343 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.11 +24.11.1 diff --git a/server/build/Dockerfile.buildenv b/server/build/Dockerfile.buildenv index 4eb6cbf931c..9ad32e1ef7d 100644 --- a/server/build/Dockerfile.buildenv +++ b/server/build/Dockerfile.buildenv @@ -1,5 +1,5 @@ FROM golang:1.26.7-bookworm@sha256:6ef6e30f0ea5c384f6d111cf856e024e3086bbdcb1779da3f3b3fbba0aea53d2 -ARG NODE_VERSION=20.11.1 +ARG NODE_VERSION=24.11.1 RUN apt-get update && apt-get install -y make git apt-transport-https ca-certificates curl software-properties-common build-essential zip xmlsec1 jq pgloader gnupg diff --git a/server/build/Dockerfile.buildenv-fips b/server/build/Dockerfile.buildenv-fips index 883a85e65ca..56633c5d66e 100644 --- a/server/build/Dockerfile.buildenv-fips +++ b/server/build/Dockerfile.buildenv-fips @@ -1,5 +1,5 @@ FROM cgr.dev/mattermost.com/go-msft-fips:1.26.7.1-dev@sha256:ae345a37a612265894a7e7f318be45680663346cb7992613916e196daf38e839 -ARG NODE_VERSION=20.11.1 +ARG NODE_VERSION=24.11.1 RUN apk add curl ca-certificates mailcap unrtf wv poppler-utils tzdata gpg xmlsec diff --git a/server/build/buildenv-tag.sh b/server/build/buildenv-tag.sh new file mode 100755 index 00000000000..d56857a11a2 --- /dev/null +++ b/server/build/buildenv-tag.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Prints the tag identifying the mattermost-build-server image, e.g. +# "go1.26.7-node24.11.1". The image bakes in both toolchains, so the tag names +# both. CI resolves this once and propagates it as BUILD_SERVER_TAG. +# +# The tag comes from the Dockerfiles, since those are what actually determine +# the image, and they are checked against .go-version and .nvmrc so the pins +# cannot drift apart unnoticed. +set -euo pipefail + +cd "$(dirname "${BASH_SOURCE[0]}")/../.." + +DOCKERFILES=(server/build/Dockerfile.buildenv server/build/Dockerfile.buildenv-fips) + +fail() { + echo "$*" >&2 + exit 1 +} + +# A pin may be more specific than its source of truth: the FIPS base image +# carries a fourth component, 1.26.7.1 for Go 1.26.7. +satisfies() { + [[ "$1" == "$2" || "$1" == "$2."* ]] +} + +go_version=$(cat server/.go-version) +node_spec=$(cat .nvmrc) + +node_version="" +for dockerfile in "${DOCKERFILES[@]}"; do + from=$(awk '$1 == "FROM" { print $2; exit }' "${dockerfile}") + [[ -n "${from}" ]] || fail "${dockerfile}: no FROM" + base_version="${from#*:}" + base_version="${base_version%%@*}" + base_version="${base_version%-*}" + satisfies "${base_version}" "${go_version}" || + fail "${dockerfile}: base image is Go ${base_version}, but server/.go-version says ${go_version}" + + version=$(awk -F= '$1 == "ARG NODE_VERSION" { print $2; exit }' "${dockerfile}") + [[ -n "${version}" ]] || fail "${dockerfile}: no ARG NODE_VERSION" + satisfies "${version}" "${node_spec}" || + fail "${dockerfile}: ARG NODE_VERSION is ${version}, but .nvmrc says ${node_spec}" + + # A single tag covers both images, so they have to agree. + if [[ -n "${node_version}" && "${version}" != "${node_version}" ]]; then + fail "${DOCKERFILES[0]} has Node ${node_version} but ${dockerfile} has ${version}" + fi + node_version="${version}" +done + +tag="go${go_version}-node${node_version}" + +# CI interpolates this tag into shell commands and workflow files, so refuse +# anything that isn't a well-formed Docker tag. +[[ "${tag}" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]] || fail "invalid tag: ${tag}" + +echo "${tag}"