Repository navigation
Expand file tree
/
Copy pathTaskfile.yaml
More file actions
424 lines (403 loc) · 17.1 KB
/
Copy pathTaskfile.yaml
File metadata and controls
424 lines (403 loc) · 17.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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# Copyright The Platform Mesh Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
version: "3"
# Root Taskfile for the platform-mesh monorepo.
#
# Structure:
# * Global targets (`lint`, `test`, `test-e2e`, `generate`, `build`) run the
# matching target for EVERY component.
# * Per-component targets (`lint-<name>`, `test-<name>`, ...) run it for ONE
# component, e.g. `task lint-account-operator`.
#
# Every component (module/service) must expose the standard targets in its own
# Taskfile: lint, test, test-e2e, generate, build. The root simply fans out to
# them, so component-specific setup (kcp/envtest, codegen tools, ...) lives with
# the component.
#
# The components below are linked locally through the repository-root go.work
# file. Add a component by listing its name here; its directory is discovered
# under ./, ./operators/ or ./services/.
includes:
# Shared go-installable tooling (controller-gen, golangci-lint, mockery,
# apigen), installed once into the root bin/ and reused by every component.
tools:
taskfile: tools/Taskfile.yaml
dir: .
vars:
COMPONENTS:
# shared
- apis
- subroutines
- golang-commons
# operators
- account-operator
- backup-operator
- extension-manager-operator
- kcp-migration-operator
- kro-composition-operator
- platform-mesh-deployer
- platform-mesh-operator
- resource-broker
- search-operator
- security-operator
- terminal-controller-manager
# services
- iam-service
- kubernetes-graphql-gateway
- rebac-authz-webhook
- search-service
- virtual-workspaces
LIBRARIES:
- apis
- subroutines
- golang-commons
tasks:
default:
cmds:
- task --list
# ---------------------------------------------------------------------------
# Global fan-out targets: run the standard target across all components.
# ---------------------------------------------------------------------------
verify:
desc: Runs all check targets in sequence.
cmds:
- task: dco
- task: lint
- task: boilerplate
- task: tidy-check
dco:
desc: 'Every commit on this branch needs a Signed-off-by matching its author (BASE=<ref> to override the comparison point).'
vars:
BASE: '{{.BASE | default "origin/main"}}'
cmds:
- |
base='{{.BASE}}'
if ! git rev-parse --verify --quiet "$base^{commit}" >/dev/null; then
echo "dco: cannot resolve base ref '$base', skipping (set BASE=<ref> to pick another)" >&2
exit 0
fi
# Mirrors the DCO app that gates pull requests: merge commits and bot
# authors are exempt, and the sign-off email must match the author's.
# The whole message is scanned, not just the trailer block -- a commit
# may end with another paragraph (e.g. On-behalf-of:), which would hide
# the sign-off from git's trailer parser but still satisfies the app.
bad=$(git rev-list --no-merges "$base..HEAD" | while read -r sha; do
author=$(git show -s --format='%ae' "$sha" | tr 'A-Z' 'a-z')
case "$author" in *"[bot]"*) continue ;; esac
git show -s --format='%B' "$sha" | grep -i '^signed-off-by:' \
| tr 'A-Z' 'a-z' | grep -qF "<$author>" \
|| git show -s --format=' %h %s' "$sha"
done)
if [ -n "$bad" ]; then
echo "Commits without a Signed-off-by matching the commit author:" >&2
echo "$bad" >&2
echo "Fix with: git rebase --signoff $base" >&2
exit 1
fi
lint:
desc: Lint every component.
cmds:
- for: {var: COMPONENTS}
cmd: task lint-{{.ITEM}}
tidy-check:
desc: Each go.mod must be self-contained (tidy + deps resolve without go.work).
cmds:
- for: {var: COMPONENTS}
cmd: task tidy-check-{{.ITEM}}
boilerplate:
desc: Verifies the boilerplate comment in each source file.
cmds:
- |
# --skip args from hack/boilerplate/skip.txt, ignoring blank lines and '#' comment lines.
skip_args=$(grep -vE '^[[:space:]]*(#|$)' hack/boilerplate/skip.txt | sed 's/^/--skip /' | tr '\n' ' ')
./hack/verify_boilerplate.py --boilerplate-dir hack/boilerplate/ $skip_args
boilerplate-fix:
desc: Prepends the boilerplate comment to source files that are missing it.
cmds:
- |
# --skip args from hack/boilerplate/skip.txt, ignoring blank lines and '#' comment lines.
skip_args=$(grep -vE '^[[:space:]]*(#|$)' hack/boilerplate/skip.txt | sed 's/^/--skip /' | tr '\n' ' ')
./hack/fix_boilerplate.py --boilerplate-dir hack/boilerplate/ $skip_args
test:
desc: Run unit tests for every component.
cmds:
- for: {var: COMPONENTS}
cmd: task test-{{.ITEM}}
test-e2e:
desc: Run e2e/integration tests for every component.
cmds:
- for: {var: COMPONENTS}
cmd: task test-e2e-{{.ITEM}}
generate:
desc: Run code generation for every component.
cmds:
- for: {var: COMPONENTS}
cmd: task generate-{{.ITEM}}
build:
desc: Build every component.
cmds:
- for: {var: COMPONENTS}
cmd: task build-{{.ITEM}}
images:
desc: 'Build container images for every component that ships a Dockerfile (VERSION=, PUSH=, PLATFORMS= to override).'
cmds:
- for: {var: COMPONENTS}
task: images-{{.ITEM}}
vars:
REGISTRY: '{{.REGISTRY}}'
REPO: '{{.REPO}}'
VERSION: '{{.VERSION}}'
PUSH: '{{.PUSH}}'
PLATFORMS: '{{.PLATFORMS}}'
# ---------------------------------------------------------------------------
# Per-component targets. Each delegates to the component's own Taskfile.
# `test-e2e-*` is declared before `test-*` so the more specific pattern wins.
# ---------------------------------------------------------------------------
"test-e2e-*":
desc: "Run e2e tests for one component (e.g. task test-e2e-account-operator)."
cmds:
- task: _run
vars: {COMPONENT: "{{index .MATCH 0}}", TARGET: test-e2e}
"test-*":
desc: "Run unit tests for one component (e.g. task test-account-operator)."
cmds:
- task: _run
vars: {COMPONENT: "{{index .MATCH 0}}", TARGET: test}
"lint-*":
desc: "Lint one component (e.g. task lint-account-operator)."
cmds:
- task: _run
vars: {COMPONENT: "{{index .MATCH 0}}", TARGET: lint}
"generate-*":
desc: "Run code generation for one component (e.g. task generate-account-operator)."
deps: [tools:controller-gen, tools:apigen, tools:mockery]
cmds:
- task: _run
vars: {COMPONENT: "{{index .MATCH 0}}", TARGET: generate}
"build-*":
desc: "Build one component (e.g. task build-account-operator)."
cmds:
- task: _run
vars: {COMPONENT: "{{index .MATCH 0}}", TARGET: build}
"tidy-check-*":
desc: "Verify one module is self-contained (e.g. task tidy-check-apis)."
env:
GOWORK: "off"
cmds:
- |
comp='{{index .MATCH 0}}'
dir=$(ls -d "$comp" "operators/$comp" "services/$comp" 2>/dev/null | head -1)
if [ -z "$dir" ]; then
echo "unknown component '$comp' (not under ./, ./operators/ or ./services/)" >&2
exit 1
fi
cd "$dir" && go mod tidy -diff && go list -m all >/dev/null
"boilerplate-*":
desc: "Verify boilerplate for one component (e.g. task boilerplate-apis)."
cmds:
- |
comp='{{index .MATCH 0}}'
dir=$(ls -d "$comp" "operators/$comp" "services/$comp" 2>/dev/null | head -1)
if [ -z "$dir" ]; then
echo "unknown component '$comp' (not under ./, ./operators/ or ./services/)" >&2
exit 1
fi
root=$PWD
# --skip args from hack/boilerplate/skip.txt, ignoring blank lines and '#' comment lines.
skip_args=$(grep -vE '^[[:space:]]*(#|$)' "$root/hack/boilerplate/skip.txt" | sed 's/^/--skip /' | tr '\n' ' ')
cd "$dir" && "$root/hack/verify_boilerplate.py" --boilerplate-dir "$root/hack/boilerplate/" $skip_args
"verify-*":
desc: "Run all checks for one component (e.g. task verify-account-operator)."
cmds:
- task: lint-{{index .MATCH 0}}
- task: tidy-check-{{index .MATCH 0}}
- task: boilerplate-{{index .MATCH 0}}
"images-*":
desc: "Build (and optionally push) one component's image (e.g. task images-account-operator VERSION=v0.1.0 PUSH=true)."
vars:
# Defaults match the release pipeline's image naming; override on the CLI.
REGISTRY: '{{.REGISTRY | default "ghcr.io"}}'
REPO: '{{.REPO | default "platform-mesh/platform-mesh"}}' # ghcr namespace: <registry>/<repo>/<name>
VERSION: '{{.VERSION | default "dev"}}' # image tag; also pushed without leading "v"
PUSH: '{{.PUSH | default "false"}}' # true -> buildx --push, false -> --load
PLATFORMS: '{{.PLATFORMS | default "linux/amd64"}}' # multi-arch (e.g. linux/amd64,linux/arm64) requires PUSH=true
CONTEXT: '{{.CONTEXT | default "."}}' # build context; repo root by default (shared go.work)
METADATA_FILE: '{{.METADATA_FILE | default ""}}' # write buildx metadata (incl. containerimage.digest) here for signing
cmds:
- task: _image
vars:
COMPONENT: "{{index .MATCH 0}}"
REGISTRY: "{{.REGISTRY}}"
REPO: "{{.REPO}}"
VERSION: "{{.VERSION}}"
PUSH: "{{.PUSH}}"
PLATFORMS: "{{.PLATFORMS}}"
CONTEXT: "{{.CONTEXT}}"
METADATA_FILE: "{{.METADATA_FILE}}"
# Resolve a component name to its directory and delegate to its Taskfile.
_run:
internal: true
silent: true
cmds:
- |
comp='{{.COMPONENT}}'; target='{{.TARGET}}'
dir=''
for p in "$comp" "operators/$comp" "services/$comp"; do
if [ -f "$p/Taskfile.yaml" ] || [ -f "$p/Taskfile.yml" ]; then dir="$p"; break; fi
done
if [ -z "$dir" ]; then
echo "unknown component '$comp' (no Taskfile under ./, ./operators/ or ./services/)" >&2
exit 1
fi
task --dir "$dir" "$target"
# Build (and optionally push) a single component image. Mirrors how the release
# workflows build the operator images (root context + shared go.work), so a
# local `task images-<name>` reproduces the CI image.
_image:
internal: true
silent: true
cmds:
- |
set -eu
comp='{{.COMPONENT}}'
dir=''
for p in "$comp" "operators/$comp" "services/$comp"; do
if [ -d "$p" ]; then dir="$p"; break; fi
done
if [ -z "$dir" ]; then
echo "unknown component '$comp' (not under ./, ./operators/ or ./services/)" >&2
exit 1
fi
# apis and other code-only modules ship no Dockerfile — skip, don't fail,
# so the `images` fan-out can iterate every component.
if [ ! -f "$dir/Dockerfile" ]; then
echo ">> $comp: no Dockerfile in $dir — skipping image build"
exit 0
fi
version='{{.VERSION}}'
image='{{.REGISTRY}}/{{.REPO}}'/"$comp"
# Build context is the repo ROOT: the operator Dockerfiles use the shared
# go.work + apis module, so they COPY root-relative paths. A self-contained
# component can override with CONTEXT=<dir>.
context='{{.CONTEXT | default "."}}'
push_flag='--load'
[ '{{.PUSH}}' = 'true' ] && push_flag='--push'
# Single tag: image:VERSION (e.g. v0.1.0, latest, pr-N, dev).
set -- -t "$image:$version"
# Optional: emit buildx metadata (incl. containerimage.digest) so callers
# like the release workflow can cosign-sign the pushed image by digest.
meta='{{.METADATA_FILE}}'
[ -n "$meta" ] && set -- "$@" --metadata-file "$meta"
echo ">> building $image:$version (context=$context platforms={{.PLATFORMS}} push={{.PUSH}})"
docker buildx build \
--file "$dir/Dockerfile" \
--platform '{{.PLATFORMS}}' \
"$push_flag" \
"$@" \
"$context"
# ---------------------------------------------------------------------------
# Release: cut a per-component semver tag (cmd/release). The tag (<name>/v*)
# triggers that component's release workflow (e.g. account-operator.yml), which
# builds + signs the image, cuts a GitHub release, bumps the chart and publishes
# SBOM + OCM. `apis` tags are go-gettable module tags only. Pass component +
# flags after `--`, e.g.:
# task release -- account-operator --minor
# task release -- all --dry-run
# ---------------------------------------------------------------------------
release:
desc: 'Cut a release tag for a component (e.g. task release -- account-operator --minor).'
dir: cmd/release
env:
GOWORK: "off" # cmd/release is a standalone tool module, not in the workspace.
cmds:
- go run . {{.CLI_ARGS}}
# Point every consumer at a released version of an internal module. Generic
# over the module: the component's own go.mod supplies the module path, so the
# same target works for apis, golang-commons, subroutines, ... — nothing is
# hardcoded. `cmd/release` prints the exact invocation after tagging such a
# module; run it, then commit the resulting go.mod/go.sum changes.
# task bump-deps -- apis v0.0.1
bump-deps:
desc: 'Point every consumer at a released internal-module version (e.g. task bump-deps -- apis v0.0.1).'
env:
GOWORK: "off" # resolve the published version from the proxy, not the local workspace copy.
GOSUMDB: "" # skip resolving on gosumbdb
GOPROXY: "direct" # skip module proxy, get from source
cmds:
- |
set -eu
comp=$(printf '%s\n' {{.CLI_ARGS}} | sed -n 1p)
version=$(printf '%s\n' {{.CLI_ARGS}} | sed -n 2p)
if [ -z "$comp" ] || [ -z "$version" ]; then
echo "usage: task bump-deps -- <component> <version> e.g. task bump-deps -- apis v0.0.1" >&2
exit 1
fi
dir=$(ls -d "$comp" "operators/$comp" "services/$comp" 2>/dev/null | head -1)
if [ -z "$dir" ]; then
echo "unknown component '$comp' (not under ./, ./operators/ or ./services/)" >&2
exit 1
fi
module=$(go mod edit -json "$dir/go.mod" | jq -r '.Module.Path')
echo ">> bumping consumers of $module to $version"
# Trailing space pins the require line for exactly this module, not a
# longer path that shares its prefix (e.g. .../apis vs .../apis/foo).
grep -rls "$module " --include=go.mod . | while read -r f; do
d=$(dirname "$f")
# Skip the module's own go.mod.
[ "$(go mod edit -json "$f" | jq -r '.Module.Path')" = "$module" ] && continue
echo " - $d"
(cd "$d" && go get "$module@$version" && go mod tidy)
done
# Re-sync the workspace with GOWORK reset (the task disables it above so
# the per-module bumps resolve the published version, not the local copy).
GOWORK= go work sync
# ---------------------------------------------------------------------------
# Workspace helpers
# ---------------------------------------------------------------------------
tidy:
desc: Tidy every module's go.mod (standalone) and re-sync the workspace.
cmds:
- go work sync
- for: {var: COMPONENTS}
cmd: task tidy-{{.ITEM}}
# ref: https://github.com/golang/go/issues/63901
# TLDR: go work sync doesn't actually everything.
# We force fill the go.work.sum by downloading all deps.
# Without this the go.work.sum is randomly updated when
# a developer pulls a dependency they didn't have in the module
# cache yet.
- go mod download all
"tidy-*":
desc: "Tidy one module's go.mod (e.g. task tidy-apis)."
env:
GOWORK: "off"
cmds:
- |
comp='{{index .MATCH 0}}'
dir=$(ls -d "$comp" "operators/$comp" "services/$comp" 2>/dev/null | head -1)
if [ -z "$dir" ]; then
echo "unknown component '$comp' (not under ./, ./operators/ or ./services/)" >&2
exit 1
fi
# Forward version-pin replaces from go.work into the module's go.mod so
# it resolves standalone, e.g. in docker builds.
replaces=$(GOWORK= go work edit -json | jq -r '.Replace[] | "\(.Old.Path)=\(.New.Path)@\(.New.Version)"')
cd "$dir"
go mod edit $(printf ' -replace=%s' $replaces)
go mod tidy
# Drop old replaces and those whose Old path isn't referenced by this module.
deps=$(go list -m -f '{{`{{.Path}}`}}' all)
for r in $(go mod edit -json | jq -r '.Replace[]?.Old.Path'); do
grep -qxF "$r" <<<"$deps" || go mod edit -dropreplace="$r"
done