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
This commit is contained in:
Jesse Hallam authored and GitHub committed 2026-09-03 14:32:25 -03:00
1 parent 24e436e7ae
commit 8f6d17d875
13 files changed
+202 -117

No files matched your search

+9 -9
View File
@@ -1,13 +1,13 @@
name: Load build environment name: Load build environment
description: > description: >
Loads build-server images uploaded by setup-buildenv into the local Docker 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 daemon, and resolves the image name from the tag. Use in jobs that run the
run the container directly (test jobs). For jobs that only need a shell container directly (test jobs). For jobs that only need a shell inside the
inside the container, use run-in-buildenv instead. container, use run-in-buildenv instead.
inputs: inputs:
go-version: build-server-tag:
description: "Go version used to construct the image tag (e.g. 1.26.3)." description: "Build image tag, from server/build/buildenv-tag.sh (e.g. go1.26.3-node24.11.1)"
required: true required: true
fips-enabled: fips-enabled:
description: "Set to 'true' to resolve the FIPS build image" description: "Set to 'true' to resolve the FIPS build image"
@@ -16,7 +16,7 @@ inputs:
outputs: outputs:
image: 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 }} value: ${{ steps.resolve.outputs.image }}
runs: runs:
@@ -57,10 +57,10 @@ runs:
shell: bash shell: bash
env: env:
FIPS: ${{ inputs.fips-enabled }} FIPS: ${{ inputs.fips-enabled }}
GO_VERSION: ${{ inputs.go-version }} TAG: ${{ inputs.build-server-tag }}
run: | run: |
if [[ "${FIPS}" == 'true' ]]; then 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 else
echo "image=mattermost/mattermost-build-server:${GO_VERSION}" >> "${GITHUB_OUTPUT}" echo "image=mattermost/mattermost-build-server:${TAG}" >> "${GITHUB_OUTPUT}"
fi fi
+10 -8
View File
@@ -14,8 +14,8 @@ inputs:
run: run:
description: "Bash script to execute inside the container" description: "Bash script to execute inside the container"
required: true required: true
go-version: build-server-tag:
description: "Go version to use (e.g. 1.24.3). Defaults to the version in server/.go-version." description: "Build image tag. Defaults to server/build/buildenv-tag.sh."
required: false required: false
default: "" default: ""
fips-enabled: fips-enabled:
@@ -30,20 +30,22 @@ inputs:
runs: runs:
using: composite using: composite
steps: steps:
- name: Read Go version - name: Read build image tag
id: tag
shell: bash shell: bash
env: env:
GO_VERSION_INPUT: ${{ inputs.go-version }} TAG_INPUT: ${{ inputs.build-server-tag }}
run: | run: |
if [[ -n "${GO_VERSION_INPUT}" ]]; then if [[ -n "${TAG_INPUT}" ]]; then
echo "GO_VERSION=${GO_VERSION_INPUT}" >> "${GITHUB_ENV}" tag="${TAG_INPUT}"
else else
echo "GO_VERSION=$(cat server/.go-version)" >> "${GITHUB_ENV}" tag=$(./server/build/buildenv-tag.sh)
fi fi
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
- uses: ./.github/actions/load-buildenv - uses: ./.github/actions/load-buildenv
id: buildenv id: buildenv
with: with:
go-version: ${{ env.GO_VERSION }} build-server-tag: ${{ steps.tag.outputs.tag }}
fips-enabled: ${{ inputs.fips-enabled }} fips-enabled: ${{ inputs.fips-enabled }}
- name: Run in buildenv - name: Run in buildenv
shell: bash shell: bash
+38 -27
View File
@@ -1,12 +1,12 @@
name: Setup build environment name: Setup build environment
description: > description: >
Builds the mattermost-build-server images if they don't yet exist on Docker 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 Hub (e.g. during a Go or Node version bump), and uploads them as artifacts
downstream jobs. Run once per workflow before any build or test jobs. for downstream jobs. Run once per workflow before any build or test jobs.
inputs: inputs:
go-version: build-server-tag:
description: "Go version (e.g. 1.26.3)" description: "Build image tag, from server/build/buildenv-tag.sh (e.g. go1.26.3-node24.11.1)"
required: true required: true
dockerfile: dockerfile:
description: "Path to Dockerfile.buildenv (relative to workspace root)" description: "Path to Dockerfile.buildenv (relative to workspace root)"
@@ -21,44 +21,50 @@ runs:
using: composite using: composite
steps: steps:
- name: Set constants - name: Set constants
id: constants
shell: bash shell: bash
run: | run: |
echo "BUILDENV_IMAGE=mattermost/mattermost-build-server" >> "${GITHUB_ENV}" {
echo "BUILDENV_ARTIFACT=buildenv-image" >> "${GITHUB_ENV}" echo "image=mattermost/mattermost-build-server"
echo "BUILDENV_DOCKERFILE=${{ inputs.dockerfile }}" >> "${GITHUB_ENV}" echo "artifact=buildenv-image"
echo "BUILDENV_FIPS_IMAGE=mattermost/mattermost-build-server-fips" >> "${GITHUB_ENV}" echo "fips-image=mattermost/mattermost-build-server-fips"
echo "BUILDENV_FIPS_ARTIFACT=buildenv-fips-image" >> "${GITHUB_ENV}" echo "fips-artifact=buildenv-fips-image"
echo "BUILDENV_FIPS_DOCKERFILE=${{ inputs.dockerfile-fips }}" >> "${GITHUB_ENV}" echo "tmpdir=/tmp/buildenv-save"
echo "BUILDENV_TMPDIR=/tmp/buildenv-save" >> "${GITHUB_ENV}" } >> "${GITHUB_OUTPUT}"
# ── Regular ──────────────────────────────────────────────────────── # ── Regular ────────────────────────────────────────────────────────
- name: Resolve regular image - name: Resolve regular image
id: resolve id: resolve
shell: bash shell: bash
env: env:
GO_VERSION: ${{ inputs.go-version }} BUILDENV_IMAGE: ${{ steps.constants.outputs.image }}
BUILDENV_TAG: ${{ inputs.build-server-tag }}
run: | 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}" echo "needs-build=true" >> "${GITHUB_OUTPUT}"
- name: Build regular image - name: Build regular image
if: ${{ steps.resolve.outputs.needs-build == 'true' }} if: ${{ steps.resolve.outputs.needs-build == 'true' }}
shell: bash shell: bash
env: env:
GO_VERSION: ${{ inputs.go-version }} BUILDENV_IMAGE: ${{ steps.constants.outputs.image }}
run: docker build -t "${BUILDENV_IMAGE}:${GO_VERSION}" - < "${BUILDENV_DOCKERFILE}" 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 - name: Save and upload regular image
if: ${{ steps.resolve.outputs.needs-build == 'true' }} if: ${{ steps.resolve.outputs.needs-build == 'true' }}
shell: bash shell: bash
env: 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: | run: |
mkdir -p "${BUILDENV_TMPDIR}" 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 - name: Upload regular image artifact
if: ${{ steps.resolve.outputs.needs-build == 'true' }} if: ${{ steps.resolve.outputs.needs-build == 'true' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with: with:
name: ${{ env.BUILDENV_ARTIFACT }} name: ${{ steps.constants.outputs.artifact }}
path: ${{ env.BUILDENV_TMPDIR }}/image.tar.gz path: ${{ steps.constants.outputs.tmpdir }}/image.tar.gz
retention-days: 1 retention-days: 1
# ── FIPS (skipped on fork PRs) ───────────────────────────────────── # ── FIPS (skipped on fork PRs) ─────────────────────────────────────
- name: Docker Hub login - 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 }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
shell: bash shell: bash
env: env:
GO_VERSION: ${{ inputs.go-version }} BUILDENV_FIPS_IMAGE: ${{ steps.constants.outputs.fips-image }}
BUILDENV_TAG: ${{ inputs.build-server-tag }}
run: | 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}" echo "needs-build=true" >> "${GITHUB_OUTPUT}"
- name: Build FIPS image - name: Build FIPS image
if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }} if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }}
shell: bash shell: bash
env: env:
GO_VERSION: ${{ inputs.go-version }} BUILDENV_FIPS_IMAGE: ${{ steps.constants.outputs.fips-image }}
run: docker build -t "${BUILDENV_FIPS_IMAGE}:${GO_VERSION}" - < "${BUILDENV_FIPS_DOCKERFILE}" 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 - name: Save and upload FIPS image
if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }} if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }}
shell: bash shell: bash
env: 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: | run: |
mkdir -p "${BUILDENV_TMPDIR}" 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 - name: Upload FIPS image artifact
if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }} if: ${{ steps.resolve-fips.outputs.needs-build == 'true' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with: with:
name: ${{ env.BUILDENV_FIPS_ARTIFACT }} name: ${{ steps.constants.outputs.fips-artifact }}
path: ${{ env.BUILDENV_TMPDIR }}/image-fips.tar.gz path: ${{ steps.constants.outputs.tmpdir }}/image-fips.tar.gz
retention-days: 1 retention-days: 1
+16 -10
View File
@@ -8,11 +8,17 @@ on:
paths: paths:
- server/build/Dockerfile.buildenv - server/build/Dockerfile.buildenv
- server/build/Dockerfile.buildenv-fips - server/build/Dockerfile.buildenv-fips
- server/build/buildenv-tag.sh
- server/.go-version
- .nvmrc
- .github/workflows/build-server-image.yml - .github/workflows/build-server-image.yml
pull_request: pull_request:
paths: paths:
- server/build/Dockerfile.buildenv - server/build/Dockerfile.buildenv
- server/build/Dockerfile.buildenv-fips - server/build/Dockerfile.buildenv-fips
- server/build/buildenv-tag.sh
- server/.go-version
- .nvmrc
- .github/workflows/build-server-image.yml - .github/workflows/build-server-image.yml
workflow_dispatch: workflow_dispatch:
@@ -52,11 +58,11 @@ jobs:
run: | run: |
docker run --rm mattermost/mattermost-build-server:test /bin/sh -c "go version && node --version" docker run --rm mattermost/mattermost-build-server:test /bin/sh -c "go version && node --version"
- name: buildenv/calculate-golang-version - name: buildenv/calculate-tag
id: go id: tag
run: | run: |
GO_VERSION=$(docker run --rm mattermost/mattermost-build-server:test go version | awk '{print $3}' | sed 's/go//') tag=$(./server/build/buildenv-tag.sh)
echo "GO_VERSION=${GO_VERSION}" >> "${GITHUB_OUTPUT}" echo "TAG=${tag}" >> "${GITHUB_OUTPUT}"
- name: buildenv/push - name: buildenv/push
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-')
@@ -67,7 +73,7 @@ jobs:
load: false load: false
push: true push: true
pull: 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: build-image-fips:
permissions: permissions:
@@ -103,11 +109,11 @@ jobs:
run: | run: |
docker run --rm --entrypoint bash mattermost/mattermost-build-server-fips:test -c "go version && node --version" docker run --rm --entrypoint bash mattermost/mattermost-build-server-fips:test -c "go version && node --version"
- name: buildenv/calculate-golang-version - name: buildenv/calculate-tag
id: go id: tag
run: | run: |
GO_VERSION=$(docker run --rm --entrypoint bash mattermost/mattermost-build-server-fips:test -c "go version" | awk '{print $3}' | sed 's/go//') tag=$(./server/build/buildenv-tag.sh)
echo "GO_VERSION=${GO_VERSION}" >> "${GITHUB_OUTPUT}" echo "TAG=${tag}" >> "${GITHUB_OUTPUT}"
- name: buildenv/push - name: buildenv/push
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-')
@@ -118,4 +124,4 @@ jobs:
load: false load: false
push: true push: true
pull: true pull: true
tags: mattermost/mattermost-build-server-fips:${{ steps.go.outputs.GO_VERSION }} tags: mattermost/mattermost-build-server-fips:${{ steps.tag.outputs.TAG }}
+3 -3
View File
@@ -14,7 +14,8 @@ on:
logsartifact: logsartifact:
required: true required: true
type: string type: string
go-version: build-server-tag:
description: "Build image tag (e.g. go1.26.3-node24.11.1)"
required: true required: true
type: string type: string
fips-enabled: fips-enabled:
@@ -43,7 +44,6 @@ jobs:
COMPOSE_PROJECT_NAME: ghactions COMPOSE_PROJECT_NAME: ghactions
INPUT_DATASOURCE: ${{ inputs.datasource }} INPUT_DATASOURCE: ${{ inputs.datasource }}
INPUT_FIPS_ENABLED: ${{ inputs.fips-enabled }} INPUT_FIPS_ENABLED: ${{ inputs.fips-enabled }}
INPUT_GO_VERSION: ${{ inputs.go-version }}
INPUT_LOGSARTIFACT: ${{ inputs.logsartifact }} INPUT_LOGSARTIFACT: ${{ inputs.logsartifact }}
INPUT_NAME: ${{ inputs.name }} INPUT_NAME: ${{ inputs.name }}
INPUT_PR_NUMBER: ${{ github.event.pull_request.number }} INPUT_PR_NUMBER: ${{ github.event.pull_request.number }}
@@ -64,7 +64,7 @@ jobs:
id: buildenv id: buildenv
uses: ./.github/actions/load-buildenv uses: ./.github/actions/load-buildenv
with: with:
go-version: ${{ inputs.go-version }} build-server-tag: ${{ inputs.build-server-tag }}
fips-enabled: ${{ inputs.fips-enabled }} fips-enabled: ${{ inputs.fips-enabled }}
- name: Setup log artifact name - name: Setup log artifact name
id: log-artifact id: log-artifact
+6 -4
View File
@@ -25,16 +25,18 @@ jobs:
name: Build Environment name: Build Environment
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
outputs: outputs:
version: ${{ steps.calculate.outputs.GO_VERSION }} build-server-tag: ${{ steps.calculate.outputs.BUILD_SERVER_TAG }}
steps: steps:
- name: Checkout mattermost project - name: Checkout mattermost project
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
persist-credentials: false persist-credentials: false
- name: Calculate version - name: Calculate build image tag
id: calculate id: calculate
working-directory: server/ 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: test-race:
name: Race Detector name: Race Detector
@@ -48,7 +50,7 @@ jobs:
datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres drivername: postgres
logsartifact: race-detector-server-test-logs logsartifact: race-detector-server-test-logs
go-version: ${{ needs.buildenv.outputs.version }} build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }}
fips-enabled: false fips-enabled: false
fullyparallel: false fullyparallel: false
race-enabled: true race-enabled: true
+8 -6
View File
@@ -26,16 +26,18 @@ jobs:
name: Build Environment name: Build Environment
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
outputs: outputs:
version: ${{ steps.calculate.outputs.GO_VERSION }} build-server-tag: ${{ steps.calculate.outputs.BUILD_SERVER_TAG }}
steps: steps:
- name: Checkout mattermost project - name: Checkout mattermost project
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
persist-credentials: false persist-credentials: false
- name: Calculate version - name: Calculate build image tag
id: calculate id: calculate
working-directory: server/ 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: test-postgres-binary:
name: Postgres with binary parameters 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 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10&binary_parameters=yes
drivername: postgres drivername: postgres
logsartifact: postgres-binary-server-test-logs logsartifact: postgres-binary-server-test-logs
go-version: ${{ needs.buildenv.outputs.version }} build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }}
fips-enabled: false fips-enabled: false
# Unsharded run on a single 8-core runner: fullyparallel=true causes # Unsharded run on a single 8-core runner: fullyparallel=true causes
# resource exhaustion (too many server instances, WebSocket hubs, and # 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 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres drivername: postgres
logsartifact: postgres-server-fips-test-logs logsartifact: postgres-server-fips-test-logs
go-version: ${{ needs.buildenv.outputs.version }} build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }}
fips-enabled: true fips-enabled: true
# Unsharded run on a single 8-core runner: see note on test-postgres-binary. # Unsharded run on a single 8-core runner: see note on test-postgres-binary.
fullyparallel: false fullyparallel: false
@@ -92,5 +94,5 @@ jobs:
datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres drivername: postgres
logsartifact: mmctl-fips-test-logs logsartifact: mmctl-fips-test-logs
go-version: ${{ needs.buildenv.outputs.version }} build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }}
fips-enabled: true fips-enabled: true
+48 -43
View File
@@ -20,8 +20,8 @@ permissions:
contents: read contents: read
jobs: jobs:
go: buildenv:
name: Compute Go Version name: Build Environment
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions: permissions:
contents: read contents: read
@@ -29,7 +29,7 @@ jobs:
actions: write actions: write
id-token: write # for chainguard (FIPS base image pull) id-token: write # for chainguard (FIPS base image pull)
outputs: outputs:
version: ${{ steps.calculate.outputs.GO_VERSION }} build-server-tag: ${{ steps.calculate.outputs.BUILD_SERVER_TAG }}
gomod-changed: ${{ steps.changed-files.outputs.any_changed }} gomod-changed: ${{ steps.changed-files.outputs.any_changed }}
# Individual jobs below use this to skip via `if:` rather # Individual jobs below use this to skip via `if:` rather
# than the workflow being skipped entirely, # than the workflow being skipped entirely,
@@ -40,10 +40,12 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with: with:
persist-credentials: false persist-credentials: false
- name: Calculate version - name: Calculate build image tag
id: calculate id: calculate
working-directory: server/ 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 - name: Check for go.mod changes
id: changed-files id: changed-files
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
@@ -61,6 +63,9 @@ jobs:
.github/workflows/server-test-template.yml .github/workflows/server-test-template.yml
.github/workflows/server-test-merge-template.yml .github/workflows/server-test-merge-template.yml
.github/workflows/mmctl-test-template.yml .github/workflows/mmctl-test-template.yml
.github/actions/setup-buildenv/**
.github/actions/load-buildenv/**
.github/actions/run-in-buildenv/**
tools/mattermost-govet/** tools/mattermost-govet/**
files_ignore: | files_ignore: |
server/**/*.md server/**/*.md
@@ -73,11 +78,11 @@ jobs:
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
uses: ./.github/actions/setup-buildenv uses: ./.github/actions/setup-buildenv
with: with:
go-version: ${{ steps.calculate.outputs.GO_VERSION }} build-server-tag: ${{ steps.calculate.outputs.BUILD_SERVER_TAG }}
check-generated: check-generated:
name: Check generated files name: Check generated files
needs: go needs: buildenv
if: needs.go.outputs.relevant-changed == 'true' if: needs.buildenv.outputs.relevant-changed == 'true'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
services: services:
# make default-roles-permissions snapshots a live database. # make default-roles-permissions snapshots a live database.
@@ -115,9 +120,9 @@ jobs:
fi fi
check-backport-migrations: check-backport-migrations:
name: 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. # 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 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout mattermost project - name: Checkout mattermost project
@@ -134,8 +139,8 @@ jobs:
make check-backport-migrations make check-backport-migrations
check-style: check-style:
name: check-style name: check-style
needs: go needs: buildenv
if: needs.go.outputs.relevant-changed == 'true' if: needs.buildenv.outputs.relevant-changed == 'true'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout mattermost project - name: Checkout mattermost project
@@ -147,8 +152,8 @@ jobs:
run: make check-style run: make check-style
check-mattermost-vet-api: check-mattermost-vet-api:
name: Vet API name: Vet API
needs: go needs: buildenv
if: needs.go.outputs.relevant-changed == 'true' if: needs.buildenv.outputs.relevant-changed == 'true'
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout mattermost project - name: Checkout mattermost project
@@ -165,8 +170,8 @@ jobs:
# -- Sharded into 4 parallel runners for ~88% wall-time improvement -- # -- Sharded into 4 parallel runners for ~88% wall-time improvement --
test-postgres-normal: test-postgres-normal:
name: Postgres (shard ${{ matrix.shard }}) name: Postgres (shard ${{ matrix.shard }})
needs: go needs: buildenv
if: needs.go.outputs.relevant-changed == 'true' if: needs.buildenv.outputs.relevant-changed == 'true'
strategy: strategy:
fail-fast: false # Let all shards complete so we get full test results fail-fast: false # Let all shards complete so we get full test results
matrix: matrix:
@@ -184,7 +189,7 @@ jobs:
# Each shard gets a unique artifact name so they don't collide # Each shard gets a unique artifact name so they don't collide
logsartifact: "postgres-server-test-logs-shard-${{ matrix.shard }}" logsartifact: "postgres-server-test-logs-shard-${{ matrix.shard }}"
enablecoverage: ${{ github.event_name != 'pull_request' || !startsWith(github.event.pull_request.base.ref, 'release-') }} 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 fips-enabled: false
shard-index: ${{ matrix.shard }} shard-index: ${{ matrix.shard }}
shard-total: 4 shard-total: 4
@@ -205,8 +210,8 @@ jobs:
test-elasticsearch-v8: test-elasticsearch-v8:
name: Elasticsearch v8 Compatibility name: Elasticsearch v8 Compatibility
needs: go needs: buildenv
if: needs.go.outputs.relevant-changed == 'true' if: needs.buildenv.outputs.relevant-changed == 'true'
permissions: permissions:
contents: read contents: read
actions: write actions: write
@@ -216,15 +221,15 @@ jobs:
datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres drivername: postgres
logsartifact: elasticsearch-v8-server-test-logs logsartifact: elasticsearch-v8-server-test-logs
go-version: ${{ needs.go.outputs.version }} build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }}
fips-enabled: false fips-enabled: false
elasticsearch-version: "8.9.0" elasticsearch-version: "8.9.0"
test-target: "test-server-elasticsearch" test-target: "test-server-elasticsearch"
test-opensearch-v2: test-opensearch-v2:
name: OpenSearch v2 Compatibility name: OpenSearch v2 Compatibility
needs: go needs: buildenv
if: needs.go.outputs.relevant-changed == 'true' if: needs.buildenv.outputs.relevant-changed == 'true'
permissions: permissions:
contents: read contents: read
actions: write actions: write
@@ -234,7 +239,7 @@ jobs:
datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres drivername: postgres
logsartifact: opensearch-v2-server-test-logs logsartifact: opensearch-v2-server-test-logs
go-version: ${{ needs.go.outputs.version }} build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }}
fips-enabled: false fips-enabled: false
opensearch-version: "2.19.0" opensearch-version: "2.19.0"
test-target: "test-server-opensearch" test-target: "test-server-opensearch"
@@ -243,10 +248,10 @@ jobs:
# Sharded for fast iteration. Weekly workflow provides regular full coverage. # Sharded for fast iteration. Weekly workflow provides regular full coverage.
test-postgres-normal-fips: test-postgres-normal-fips:
if: >- if: >-
needs.go.outputs.relevant-changed == 'true' && needs.buildenv.outputs.relevant-changed == 'true' &&
(contains(github.head_ref, 'fips') || needs.go.outputs.gomod-changed == 'true') (contains(github.head_ref, 'fips') || needs.buildenv.outputs.gomod-changed == 'true')
name: "Postgres FIPS (shard ${{ matrix.shard }})" name: "Postgres FIPS (shard ${{ matrix.shard }})"
needs: go needs: buildenv
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -263,7 +268,7 @@ jobs:
datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres drivername: postgres
logsartifact: "postgres-server-fips-test-logs-shard-${{ matrix.shard }}" 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 fips-enabled: true
shard-index: ${{ matrix.shard }} shard-index: ${{ matrix.shard }}
shard-total: 4 shard-total: 4
@@ -281,8 +286,8 @@ jobs:
test-mmctl: test-mmctl:
name: Run mmctl tests name: Run mmctl tests
needs: go needs: buildenv
if: needs.go.outputs.relevant-changed == 'true' if: needs.buildenv.outputs.relevant-changed == 'true'
permissions: permissions:
contents: read contents: read
actions: write actions: write
@@ -294,14 +299,14 @@ jobs:
datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres drivername: postgres
logsartifact: mmctl-test-logs logsartifact: mmctl-test-logs
go-version: ${{ needs.go.outputs.version }} build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }}
fips-enabled: false fips-enabled: false
test-mmctl-fips: test-mmctl-fips:
if: >- if: >-
needs.go.outputs.relevant-changed == 'true' && needs.buildenv.outputs.relevant-changed == 'true' &&
(contains(github.head_ref, 'fips') || needs.go.outputs.gomod-changed == 'true') (contains(github.head_ref, 'fips') || needs.buildenv.outputs.gomod-changed == 'true')
name: Run mmctl tests (FIPS) name: Run mmctl tests (FIPS)
needs: go needs: buildenv
permissions: permissions:
contents: read contents: read
actions: write actions: write
@@ -315,13 +320,13 @@ jobs:
datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10 datasource: postgres://mmuser:mostest_password@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10
drivername: postgres drivername: postgres
logsartifact: mmctl-fips-test-logs logsartifact: mmctl-fips-test-logs
go-version: ${{ needs.go.outputs.version }} build-server-tag: ${{ needs.buildenv.outputs.build-server-tag }}
fips-enabled: true fips-enabled: true
build-mattermost-server: build-mattermost-server:
name: Build mattermost server app name: Build mattermost server app
needs: go needs: buildenv
if: needs.go.outputs.relevant-changed == 'true' if: needs.buildenv.outputs.relevant-changed == 'true'
permissions: permissions:
contents: read contents: read
actions: write actions: write
@@ -370,7 +375,7 @@ jobs:
permissions: {} permissions: {}
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: needs:
- go - buildenv
- merge-postgres-test-results - merge-postgres-test-results
- test-elasticsearch-v8 - test-elasticsearch-v8
- test-opensearch-v2 - test-opensearch-v2
@@ -383,8 +388,8 @@ jobs:
run: | run: |
# Explicitly fail here rather than relying on `if:`, since a job # Explicitly fail here rather than relying on `if:`, since a job
# skipped via `if:` is reported as "Success" by GitHub. # skipped via `if:` is reported as "Success" by GitHub.
if [ "${{ needs.go.result }}" != "success" ]; then if [ "${{ needs.buildenv.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." echo "::error::'buildenv' job did not succeed (result: ${{ needs.buildenv.result }}); relevant-changed could not be computed reliably. Failing Server CI Complete."
exit 1 exit 1
fi fi
echo "Server CI Complete" echo "Server CI Complete"
@@ -393,7 +398,7 @@ jobs:
name: Server CI Report name: Server CI Report
# Skip cleanly (rather than fail) when no relevant Server CI paths # Skip cleanly (rather than fail) when no relevant Server CI paths
# changed, since none of the test jobs it reports on will have run. # 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: permissions:
contents: read contents: read
actions: read actions: read
@@ -401,7 +406,7 @@ jobs:
pull-requests: write pull-requests: write
issues: write issues: write
needs: needs:
- go - buildenv
- ci-complete - ci-complete
uses: ./.github/workflows/server-ci-report.yml uses: ./.github/workflows/server-ci-report.yml
secrets: secrets:
@@ -421,7 +426,7 @@ jobs:
# (and thus server-dist-artifact) will not have run. # (and thus server-dist-artifact) will not have run.
if: >- if: >-
success('ci-complete') && success('ci-complete') &&
needs.go.outputs.relevant-changed == 'true' && needs.buildenv.outputs.relevant-changed == 'true' &&
github.repository_owner == 'mattermost' && github.repository_owner == 'mattermost' &&
github.event_name == 'pull_request' && github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository github.event.pull_request.head.repo.full_name == github.repository
@@ -430,7 +435,7 @@ jobs:
actions: read actions: read
statuses: write statuses: write
needs: needs:
- go - buildenv
- ci-complete - ci-complete
uses: ./.github/workflows/server-ci-artifacts.yml uses: ./.github/workflows/server-ci-artifacts.yml
secrets: secrets:
+4 -4
View File
@@ -26,7 +26,8 @@ on:
required: false required: false
type: boolean type: boolean
default: false default: false
go-version: build-server-tag:
description: "Build image tag (e.g. go1.26.3-node24.11.1)"
required: true required: true
type: string type: string
fips-enabled: fips-enabled:
@@ -90,7 +91,6 @@ jobs:
INPUT_ENABLECOVERAGE: ${{ inputs.enablecoverage }} INPUT_ENABLECOVERAGE: ${{ inputs.enablecoverage }}
INPUT_FIPS_ENABLED: ${{ inputs.fips-enabled }} INPUT_FIPS_ENABLED: ${{ inputs.fips-enabled }}
INPUT_FULLYPARALLEL: ${{ inputs.fullyparallel }} INPUT_FULLYPARALLEL: ${{ inputs.fullyparallel }}
INPUT_GO_VERSION: ${{ inputs.go-version }}
INPUT_LOGSARTIFACT: ${{ inputs.logsartifact }} INPUT_LOGSARTIFACT: ${{ inputs.logsartifact }}
INPUT_NAME: ${{ inputs.name }} INPUT_NAME: ${{ inputs.name }}
INPUT_PR_NUMBER: ${{ github.event.pull_request.number }} INPUT_PR_NUMBER: ${{ github.event.pull_request.number }}
@@ -114,7 +114,7 @@ jobs:
id: buildenv id: buildenv
uses: ./.github/actions/load-buildenv uses: ./.github/actions/load-buildenv
with: with:
go-version: ${{ inputs.go-version }} build-server-tag: ${{ inputs.build-server-tag }}
fips-enabled: ${{ inputs.fips-enabled }} fips-enabled: ${{ inputs.fips-enabled }}
- name: Restore test timing data - name: Restore test timing data
@@ -180,7 +180,7 @@ jobs:
if: inputs.shard-total > 1 if: inputs.shard-total > 1
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with: with:
go-version: ${{ inputs.go-version }} go-version-file: server/.go-version
- name: Split tests across shards - name: Split tests across shards
if: inputs.shard-total > 1 if: inputs.shard-total > 1
+1 -1
View File
@@ -1 +1 @@
24.11 24.11.1
+1 -1
View File
@@ -1,5 +1,5 @@
FROM golang:1.26.7-bookworm@sha256:6ef6e30f0ea5c384f6d111cf856e024e3086bbdcb1779da3f3b3fbba0aea53d2 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 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
+1 -1
View File
@@ -1,5 +1,5 @@
FROM cgr.dev/mattermost.com/go-msft-fips:1.26.7.1-dev@sha256:ae345a37a612265894a7e7f318be45680663346cb7992613916e196daf38e839 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 RUN apk add curl ca-certificates mailcap unrtf wv poppler-utils tzdata gpg xmlsec
+57
View File
@@ -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}"