Skip to content

Commit 4558fe3

Browse files
authored
Merge pull request #328 from smocker-dev/refactor/build-pipeline
build: embed the client via a build tag and streamline the release pipeline
2 parents 2a8bb19 + 8c91a12 commit 4558fe3

9 files changed

Lines changed: 173 additions & 179 deletions

File tree

.github/workflows/main.yml

Lines changed: 57 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ on:
1414
- main
1515
workflow_dispatch: # Allow manual trigger
1616

17-
# Least privilege by default; jobs that need more elevate it explicitly (see deploy).
17+
# Least privilege by default; the deploy job elevates it explicitly.
1818
permissions:
1919
contents: read
2020

21-
# Cancel superseded runs on the same ref, but only for pull requests — never abort an
22-
# in-flight push to main or a tag (which may be mid-release).
21+
# Cancel superseded runs on the same ref, but only for pull requests — never abort an in-flight
22+
# push to main or a tag (which may be mid-release).
2323
concurrency:
2424
group: ${{ github.workflow }}-${{ github.ref }}
2525
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
@@ -32,14 +32,14 @@ jobs:
3232
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
3333
with:
3434
persist-credentials: false
35-
3635
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
3736
with:
3837
node-version-file: .nvmrc
3938
cache: npm
40-
39+
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
40+
with:
41+
go-version-file: go.mod
4142
- run: npm ci
42-
4343
- name: Lint sources
4444
run: |
4545
make lint
@@ -53,124 +53,104 @@ jobs:
5353
with:
5454
fetch-depth: 0 # SonarCloud needs the full history for blame/coverage
5555
persist-credentials: false
56-
5756
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
5857
with:
5958
node-version-file: .nvmrc
6059
cache: npm
61-
62-
- name: Setup Go environment
63-
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
60+
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
6461
with:
6562
go-version-file: go.mod
66-
6763
- run: npm ci
68-
6964
- name: Execute tests
7065
run: |
7166
make test
7267
make test-integration
7368
npm test
7469
make coverage
75-
7670
- name: SonarCloud Scan
7771
uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1
7872
continue-on-error: true
7973
env:
8074
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8175
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
8276

83-
build:
77+
e2e:
8478
runs-on: ubuntu-latest
85-
timeout-minutes: 5
79+
timeout-minutes: 10
8680
steps:
8781
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
8882
with:
8983
persist-credentials: false
90-
91-
- id: extract_ref
92-
run: echo "GIT_REF=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
93-
9484
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
9585
with:
9686
node-version-file: .nvmrc
9787
cache: npm
98-
99-
- name: Setup Go environment
100-
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
88+
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
10189
with:
10290
go-version-file: go.mod
103-
10491
- run: npm ci
92+
- name: Install Playwright browser
93+
run: npx playwright install --with-deps chromium
94+
- name: Run Playwright non-regression suite
95+
run: make test-e2e
10596

106-
- name: Build
107-
run: |
108-
make VERSION=${{ steps.extract_ref.outputs.GIT_REF }} RELEASE=1 release
109-
make VERSION=${{ steps.extract_ref.outputs.GIT_REF }} build-docker
110-
make VERSION=${{ steps.extract_ref.outputs.GIT_REF }} start-docker
111-
112-
- name: Smoke-test the docker image
113-
run: make VERSION=${{ steps.extract_ref.outputs.GIT_REF }} smoke-docker
114-
115-
- if: startsWith(github.ref, 'refs/tags/')
116-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
117-
with:
118-
name: smocker-bin
119-
path: ./build/smocker.tar.gz
120-
121-
e2e:
97+
security:
12298
runs-on: ubuntu-latest
12399
timeout-minutes: 10
124100
steps:
125101
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
126102
with:
127103
persist-credentials: false
128-
129104
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
130105
with:
131106
node-version-file: .nvmrc
132107
cache: npm
133-
134-
- name: Setup Go environment
135-
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
108+
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
136109
with:
137110
go-version-file: go.mod
111+
# Audit only the dependencies that ship in the client bundle (dev tooling isn't served), and
112+
# fail only on high/critical advisories.
113+
- name: Audit frontend dependencies (npm)
114+
run: npm audit --omit=dev --audit-level=high
115+
# govulncheck reports only vulnerabilities actually reachable from the code, so it's low-noise
116+
# and safe to gate on.
117+
- name: Scan backend dependencies (govulncheck)
118+
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
138119

139-
- run: npm ci
140-
141-
- name: Install Playwright browser
142-
run: npx playwright install --with-deps chromium
143-
144-
- name: Run Playwright non-regression suite
145-
run: make test-e2e
146-
147-
security:
120+
build:
148121
runs-on: ubuntu-latest
149122
timeout-minutes: 10
150123
steps:
151124
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
152125
with:
153126
persist-credentials: false
154-
155127
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
156128
with:
157129
node-version-file: .nvmrc
158130
cache: npm
159-
160-
- name: Setup Go environment
161-
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
131+
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
162132
with:
163133
go-version-file: go.mod
164134

165-
# Audit only the dependencies that ship in the client bundle (dev tooling isn't
166-
# served), and fail only on high/critical advisories.
167-
- name: Audit frontend dependencies (npm)
168-
run: npm audit --omit=dev --audit-level=high
135+
# Every run assembles the native image and smoke-tests it (proves the Dockerfile + embedded
136+
# UI still work). Only tag builds also cross-compile the release binaries + tarball, which the
137+
# deploy job packages into the multi-arch image and the GitHub release.
138+
- name: Build image (+ release binaries on tags) and smoke-test
139+
env:
140+
VERSION: ${{ github.ref_name }}
141+
run: |
142+
targets="build-docker start-docker smoke-docker"
143+
[[ "$GITHUB_REF" == refs/tags/* ]] && targets="release $targets"
144+
make VERSION="$VERSION" RELEASE=1 $targets
169145
170-
# govulncheck reports only vulnerabilities actually reachable from the code, so it's
171-
# low-noise and safe to gate on.
172-
- name: Scan backend dependencies (govulncheck)
173-
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
146+
- name: Upload release artifacts
147+
if: startsWith(github.ref, 'refs/tags/')
148+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
149+
with:
150+
name: smocker-dist
151+
path: |
152+
build/smocker.tar.gz
153+
build/smocker-*
174154
175155
deploy:
176156
needs: [lint, test, build, e2e, security]
@@ -186,20 +166,18 @@ jobs:
186166
with:
187167
persist-credentials: false
188168

189-
- id: extract_ref
190-
run: echo "GIT_REF=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
191-
192169
- id: release_flags
193170
name: Derive the release flavor from the tag suffix
171+
env:
172+
REF: ${{ github.ref_name }}
194173
run: |
195-
ref="${GITHUB_REF##*/}"
196174
draft=false
197175
prerelease=false
198176
make_latest=true
199-
if [[ "$ref" == *-draft ]]; then
177+
if [[ "$REF" == *-draft ]]; then
200178
draft=true # unpublished, editable on GitHub before release
201179
make_latest=false
202-
elif [[ "$ref" == *-* ]]; then
180+
elif [[ "$REF" == *-* ]]; then
203181
prerelease=true # e.g. -preview / -rc: published but never "Latest"
204182
make_latest=false
205183
fi
@@ -209,33 +187,34 @@ jobs:
209187
echo "make_latest=$make_latest"
210188
} >> "$GITHUB_OUTPUT"
211189
190+
# The pre-built binaries (+ tarball) from the build job; the image is assembled from them.
212191
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
213192
with:
214-
name: smocker-bin
193+
name: smocker-dist
215194
path: ./build
216195

217-
- run: cd build && tar -xvf smocker.tar.gz
218-
219196
- name: Docker login
220197
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
221198
with:
222199
registry: ghcr.io
223200
username: ${{ github.actor }}
224201
password: ${{ secrets.GITHUB_TOKEN }}
225202

226-
- name: Set up QEMU
227-
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
228-
203+
# No QEMU: the image only packages the pre-built binaries (COPY-only target stages).
229204
- name: Set up Docker Buildx
230205
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
231206

232207
- name: Deploy on Docker registry
233-
run: make VERSION=${{ steps.extract_ref.outputs.GIT_REF }} deploy-docker
208+
env:
209+
VERSION: ${{ github.ref_name }}
210+
run: make VERSION="$VERSION" deploy-docker
234211

235212
- name: Deploy on GitHub releases
236213
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
237214
with:
238-
files: build/smocker.tar.gz
215+
files: |
216+
build/smocker.tar.gz
217+
build/smocker-*
239218
token: ${{ secrets.GITHUB_TOKEN }}
240219
draft: ${{ steps.release_flags.outputs.draft }}
241220
prerelease: ${{ steps.release_flags.outputs.prerelease }}

.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@
33
node_modules/
44
# All build, test and runtime output goes here (see Makefile / vite.config.ts / playwright.config.ts).
55
build/
6-
# The embedded client is copied here by the release build; keep only the placeholder in git.
7-
server/frontend/dist/*
8-
!server/frontend/dist/.gitkeep
6+
# Vite builds the client straight here for embedding (release builds only, -tags embedclient).
7+
server/frontend/dist/

Dockerfile

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
1-
ARG GO_VERSION=1.26
2-
FROM golang:${GO_VERSION}-alpine AS build-backend
3-
RUN apk add --no-cache make ca-certificates
4-
ARG VERSION=snapshot
5-
ARG COMMIT
6-
WORKDIR /go/src/smocker
7-
COPY go.mod go.sum ./
8-
RUN go mod download
9-
COPY Makefile main.go ./
10-
COPY server/ ./server/
11-
# The client is built on the host (make release); bake it into the binary via go:embed.
12-
COPY build/client ./server/frontend/dist/
13-
# CGO_ENABLED=0 produces a fully static binary (pure-Go net/tls), required for a scratch image.
14-
RUN CGO_ENABLED=0 make VERSION=$VERSION COMMIT=$COMMIT RELEASE=1 build
1+
# The images are assembled from pre-built, static, client-embedded binaries (see
2+
# `make build-binaries`) — no compilation happens here, just packaging. buildx sets
3+
# TARGETOS/TARGETARCH/TARGETVARIANT per target platform, which selects the matching binary.
4+
5+
# CA roots for the proxy mock type's HTTPS backends (scratch ships none). Pin to the build
6+
# platform: the cert bundle is arch-independent, so it's produced once natively and no target
7+
# platform ever runs a RUN step — the per-arch stage is COPY-only, so no QEMU emulation is needed.
8+
FROM --platform=$BUILDPLATFORM alpine:3 AS certs
9+
RUN apk add --no-cache ca-certificates
1510

1611
FROM scratch
1712
LABEL org.opencontainers.image.source="https://github.com/smocker-dev/smocker"
1813
EXPOSE 8080 8081
19-
# CA roots so the proxy mock type can reach HTTPS backends (scratch ships none).
20-
COPY --from=build-backend /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
21-
# The UI is embedded in the binary, so nothing else is needed — just the static binary.
22-
COPY --from=build-backend /go/src/smocker/build/smocker /smocker
23-
# Run unprivileged (nobody). The listen ports are >1024 so no privilege is needed; a mounted
24-
# persistence directory must be writable by this uid.
14+
ARG TARGETOS
15+
ARG TARGETARCH
16+
ARG TARGETVARIANT
17+
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
18+
COPY build/smocker-${TARGETOS}-${TARGETARCH}${TARGETVARIANT} /smocker
19+
# Run unprivileged (nobody); ports are >1024 and a mounted persistence dir must be writable by it.
2520
USER 65534:65534
2621
ENTRYPOINT ["/smocker"]

0 commit comments

Comments
 (0)