Repository navigation
222 lines (207 loc) · 10.1 KB
/
Copy path_docker-pipeline.yml
File metadata and controls
222 lines (207 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
name: _docker-pipeline (reusable)
# Reusable workflow — the per-arch lego brick for all Docker CI steps.
#
# Called by smoke-test.yml, dependency-review.yml (push: false) and
# publish-docker.yml (push: true, once per arch via matrix).
#
# Two modes:
# push: false → build + smoke test + integration test (main image only)
# push: true → above + push the built image by digest to GHCR + Docker Hub,
# and upload the resulting digest as an artifact. The caller's
# merge-manifests job assembles per-arch digests into a
# multi-arch manifest list at the user-facing tags.
#
# Permissions required from the calling workflow:
# push: false → contents: read
# push: true → contents: read, packages: write
on:
workflow_call:
inputs:
name:
description: "Image name, e.g. socket-basics"
type: string
required: true
dockerfile:
description: "Path to Dockerfile relative to repo root"
type: string
required: true
context:
description: "Docker build context"
type: string
required: false
default: "."
check_set:
description: "Smoke-test tool set: main or app-tests"
type: string
required: true
runs_on:
description: "Runner label (e.g. ubuntu-latest, ubuntu-24.04-arm). The build/test steps run natively on this arch."
type: string
required: false
default: "ubuntu-latest"
arch_label:
description: "Short arch identifier used for digest artifact name and cache scope (e.g. amd64, arm64). Required when push=true."
type: string
required: false
default: ""
push_name:
description: >
Registry repository to push to (defaults to name). Image variants share
the base repository and are distinguished by tag suffix (e.g. -heavy),
so the heavy build passes name=socket-basics-heavy (local tag, cache
scope, digest artifact) with push_name=socket-basics.
type: string
required: false
default: ""
push:
description: "Push to GHCR + Docker Hub by digest after testing. The publish workflow merges per-arch digests into a multi-arch manifest list."
type: boolean
required: false
default: false
version:
description: "Semver without v prefix (e.g. 2.0.0) — passed as the SOCKET_BASICS_VERSION build-arg, baked into OCI labels"
type: string
required: false
default: "dev"
ref:
description: "Git ref to check out. Publish mode passes the resolved release tag so builds use the release source."
type: string
required: false
default: ""
secrets:
DOCKERHUB_USERNAME:
required: false
DOCKERHUB_TOKEN:
required: false
jobs:
pipeline:
runs-on: ${{ inputs.runs_on }}
# Push mode binds the `publish` environment so the environment-scoped
# Docker Hub secrets resolve (callers use `secrets: inherit`). Test mode
# (push: false) binds no environment — PR branches would violate the
# environment's deployment policy.
environment: ${{ inputs.push && 'publish' || '' }}
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.ref }}
persist-credentials: false
- name: Resolve source revision
id: source
run: echo "revision=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: 🔨 Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
# GHCR login runs before the build in EVERY mode (not just push): the
# trivy build stage pulls the private ghcr.io/socketdev/trivy image, so
# unauthenticated PR builds would fail at FROM. Callers must grant
# `packages: read`.
- name: Login to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
# Docker Hub login is deferred until after the build and tests.
# A repo-scoped Docker Hub token (socketdev/socket-basics only) would cause
# 401s if active during the build, since BuildKit uses it for ALL Docker Hub
# requests including pulling public base images (python, trivy, trufflehog).
# Those public images pull fine without auth; only the push needs credentials.
# ── Step 1: Build ──────────────────────────────────────────────────────
# Loads image into the local Docker daemon without pushing.
# Per-arch cache scope ensures amd64 and arm64 builds don't pollute each
# other's layer cache. arch_label defaults to "smoke" when push=false.
- name: 🔨 Build (load for testing)
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
# zizmor: ignore[template-injection] — safe: always hardcoded "." from same-repo callers; passed as array element to exec, not shell-interpolated
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
load: true
push: false
tags: ${{ inputs.name }}:pipeline-test
build-args: |
SOCKET_BASICS_VERSION=${{ inputs.version }}
VCS_REF=${{ steps.source.outputs.revision }}
BUILD_DATE=${{ github.event.repository.updated_at }}
cache-from: type=gha,scope=${{ inputs.name }}-${{ inputs.arch_label || 'smoke' }}
cache-to: type=gha,mode=max,scope=${{ inputs.name }}-${{ inputs.arch_label || 'smoke' }}
# Disable attestations for the test build — provenance/SBOM cause BuildKit
# to pull docker/buildkit-syft-scanner from Docker Hub, which fails with a
# repo-scoped token. Attestations are enabled on the push step only.
provenance: false
sbom: false
# ── Step 2: Smoke test ─────────────────────────────────────────────────
- name: 🧪 Smoke test
env:
IMAGE_NAME: ${{ inputs.name }}
CHECK_SET: ${{ inputs.check_set }}
run: |
bash ./scripts/smoke-test-docker.sh \
--skip-build \
--image-tag "$IMAGE_NAME:pipeline-test" \
--check-set "$CHECK_SET"
# ── Step 3: Integration test (socket-basics variants only) ─────────────
- name: 🔬 Integration test
if: inputs.name == 'socket-basics' || inputs.name == 'socket-basics-heavy'
env:
IMAGE_NAME: ${{ inputs.name }}
run: |
bash ./scripts/integration-test-docker.sh \
--image-tag "$IMAGE_NAME:pipeline-test"
# ── Step 4: Push by digest (publish mode only) ─────────────────────────
# Docker Hub login happens here — after build and tests, immediately before
# push. Keeping it here prevents the repo-scoped token from interfering
# with public image pulls during the build step.
- name: Login to Docker Hub
if: inputs.push
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Per-arch by-digest push to BOTH registries. No tags are written here;
# the publish workflow's merge-manifests job creates the multi-arch
# manifest list at user-facing tags via `docker buildx imagetools create`.
# Layer cache from step 1 means this is mostly a metadata write + push.
- name: 🚀 Build & push by digest
if: inputs.push
id: build-digest
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
# zizmor: ignore[template-injection] — safe: always hardcoded "." from same-repo callers; passed as array element to exec, not shell-interpolated
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
build-args: |
SOCKET_BASICS_VERSION=${{ inputs.version }}
VCS_REF=${{ steps.source.outputs.revision }}
BUILD_DATE=${{ github.event.repository.updated_at }}
# One `--output` per registry → blobs land in both, by digest.
# build-push-action splits this scalar on newlines into separate outputs.
outputs: |
type=image,name=ghcr.io/socketdev/${{ inputs.push_name || inputs.name }},push-by-digest=true,name-canonical=true,push=true
type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/${{ inputs.push_name || inputs.name }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ inputs.name }}-${{ inputs.arch_label }}
cache-to: type=gha,mode=max,scope=${{ inputs.name }}-${{ inputs.arch_label }}
# SBOM and provenance generation pull docker/buildkit-syft-scanner from
# Docker Hub, which fails with a repo-scoped token. Disabled until a
# token with broader Docker Hub read access is available.
provenance: false
sbom: false
# Persist the per-arch digest as an artifact so the merge-manifests job
# can reference it via `<image>@sha256:<digest>` when creating the list.
- name: 📤 Export digest
if: inputs.push
env:
DIGEST: ${{ steps.build-digest.outputs.digest }}
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"
- name: ⬆️ Upload digest artifact
if: inputs.push
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: digests-${{ inputs.name }}__${{ inputs.arch_label }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1