Repository navigation
Expand file tree
/
Copy pathTaskfile.yaml
More file actions
439 lines (399 loc) · 20.4 KB
/
Copy pathTaskfile.yaml
File metadata and controls
439 lines (399 loc) · 20.4 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
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# SPDX-License-Identifier: Apache-2.0
version: 3
silent: false
output: prefixed
# Optional local secrets (not committed; see .env.example). Provides
# GITHUB_USER / GITHUB_ACCESS_TOKEN to tasks like uat:secrets.
dotenv: [".env"]
vars:
OS: [linux]
ARCH: [amd64, arm64]
GO_VERSION: 1.27.2
# Build metadata stamped into github.com/automa-saga/version at link time via -X.
# Override VERSION on the command line or in CI (e.g. `task build:cli VERSION=0.20.0`);
# COMMIT and DATE are derived automatically.
# Unstamped default is 0.0.0 (a valid semver), not the library's "dev" sentinel:
# the startup CLI-version migration parses this string via pkg/semver, and a
# non-semver value ("dev") makes `solo-provisioner install` fail. 0.0.0 also
# matches the pre-automa behavior (the old generate script defaulted VERSION to 0.0.0).
VERSION: '{{.VERSION | default "0.0.0"}}'
# -c safe.directory='*' keeps the commit stamp working when building inside the
# UTM VM, where the mounted /mnt/solo-weaver repo is owned by a different uid and
# plain git would abort with "detected dubious ownership" (stamping commit=none).
# The .vm-commit fallback covers in-VM builds: vm:sync excludes .git, so git has
# no history there — vm:sync writes the host HEAD into .vm-commit which is synced.
COMMIT:
sh: "git -c safe.directory='*' rev-parse HEAD 2>/dev/null || cat .vm-commit 2>/dev/null || echo none"
DATE:
sh: "date -u +%Y-%m-%dT%H:%M:%SZ"
LDFLAGS: "-w -s -X github.com/automa-saga/version.Version={{.VERSION}} -X github.com/automa-saga/version.Commit={{.COMMIT}} -X github.com/automa-saga/version.Date={{.DATE}}"
# VM configuration
VM_OS_TYPE: '{{.VM_OS_TYPE | default "debian"}}' # Options: debian, ubuntu
VM_NAME: "solo-weaver-{{.VM_OS_TYPE}}"
VM_GOLDEN_NAME: "solo-weaver-{{.VM_OS_TYPE}}-golden"
# Set this to the actual URL or local path of your golden image (e.g., "https://storage.example.com/golden-image.utm" or "/path/to/golden-image.utm")
# For Debian: Set your Debian golden image URL
# For Ubuntu: Set your Ubuntu 22.04 golden image URL
VM_GOLDEN_IMAGE_URL_DEBIAN: '{{.VM_GOLDEN_IMAGE_URL_DEBIAN | default "https://storage.googleapis.com/solo-weaver/solo-weaver-debian-golden.utm.tar.gz" }}'
VM_GOLDEN_IMAGE_URL_UBUNTU: '{{.VM_GOLDEN_IMAGE_URL_UBUNTU | default "https://storage.googleapis.com/solo-weaver/solo-weaver-ubuntu-golden.utm.tar.gz" }}'
VM_USER: "provisioner"
SSH_PRIVATE_KEY: "{{.PWD}}/.ssh/id_rsa_vm"
SSH_PUBLIC_KEY: "{{.SSH_PRIVATE_KEY}}.pub"
# Common SSH options for all VM connections. IdentityAgent=none prevents
# external SSH agents (e.g. 1Password) from intercepting key auth.
# ProxyCommand routes through nc with --apple-no-connectx to avoid macOS
# connectx(2) failures on bridged UTM networks behind captive WiFi or VPNs.
#SSH_OPTS: '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentityAgent=none -o ProxyCommand="nc --apple-no-connectx %h %p"'
SSH_OPTS: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentityAgent=none"
UTM_VMS_DIR: "{{.HOME}}/Library/Containers/com.utmapp.UTM/Data/Documents"
# Resolve the VM's bridged LAN IP via peers-ip.sh — the single resolver, which
# picks the guest address on one of the host's own subnets (see its header).
GET_VM_IP: |
get_vm_ip() {
bash "{{.ROOT_DIR}}/scripts/network/peers-ip.sh" "{{.VM_NAME}}"
}
includes:
tests:
taskfile: ./taskfiles/tests.yaml
flatten: true
uat:
taskfile: ./taskfiles/uat.yaml
flatten: true
proxy:
taskfile: ./taskfiles/proxy.yaml
flatten: true
alloy:
taskfile: ./taskfiles/alloy.yaml
flatten: true
teleport:
taskfile: ./taskfiles/teleport.yaml
flatten: true
vm:
taskfile: ./taskfiles/vm.yaml
flatten: true
cli:
taskfile: ./taskfiles/cli.yaml
flatten: true
daemon:
taskfile: ./taskfiles/daemon.yaml
flatten: true
network:
taskfile: ./taskfiles/network.yaml
flatten: true
tasks:
clean:
cmds:
- "rm -rf bin"
- "rm -f unit-test-report.md"
- "mkdir -p bin"
- "go clean -cache -testcache"
vendor:
desc: "Populate vendor/ from go.mod (skipped when vendor/ is already in sync)"
# Skip re-vendoring when vendor/ is present AND already records the go.mod
# version of the private solo-operator module. This keeps CI and the VM build
# offline: the host (which has GitHub creds for the private module) populates
# vendor/, it is committed / rsynced by `task vm:sync`, and consumers reuse it
# instead of re-running `go mod vendor` — which would fail with "terminal prompts
# disabled" where there are no credentials.
#
# Two earlier guards were both wrong here:
# 1. mtime (`vendor/modules.txt -nt go.mod`): a fresh `git checkout` stamps
# every file with the same mtime, so `-nt` is false, the guard fails, and
# CI re-vendored — reaching for the private module it cannot fetch.
# 2. `GOPROXY=off go list -mod=vendor`: correct in spirit but it makes `go`
# resolve the `go 1.26.x` toolchain, and with GOPROXY=off a fresh runner
# cannot download it ("toolchain not available"), so the guard fails and CI
# re-vendored anyway.
# So the guard must not invoke `go` at all. solo-operator is the only private
# module whose re-vendor CI cannot perform; comparing its go.mod version against
# the one recorded in vendor/modules.txt is a pure filesystem check that is
# correct on any fresh checkout. Full vendor consistency is still enforced right
# after, by `go build -mod=vendor` (the default when vendor/ exists).
#
# TODO(solo-operator-public): once github.com/hashgraph/solo-operator is
# public, module fetches no longer need auth — narrow GOPRIVATE (drop the
# github.com/hashgraph/* scope from CI: zxc-*.yaml) so the module is verified
# via proxy.golang.org / sum.golang.org again. This guard stays useful as a
# build-speed optimization regardless.
status:
- test -f vendor/modules.txt
- |
want=$(awk '$1=="github.com/hashgraph/solo-operator"{print $2}' go.mod)
test -n "$want" && grep -qxF "# github.com/hashgraph/solo-operator $want" vendor/modules.txt
cmds:
# Reached only when vendor/ is stale (status above failed). The status guard
# keeps the offline VM/local build skipping this entirely when vendor/ is in
# sync; CI (fresh checkout, no vendor/) falls through and re-vendors here,
# fetching the private github.com/hashgraph/solo-operator module over the
# network using the credentials configured by the workflow (GOPRIVATE + a
# git insteadOf token). Any auth/transport failure surfaces as go's own error.
- go mod vendor
registry:login:ghcr:
desc: "Log Helm into ghcr.io using GITHUB_USER/GITHUB_ACCESS_TOKEN from the root .env, so private charts can be pulled (e.g. before 'task chart-checksums'). Tries `helm registry login` first (honours credential helpers); falls back to writing the auth entry directly when Helm's login probe returns a false 403 against ghcr."
cmds:
- |
set -e
: "${GITHUB_USER:?Set GITHUB_USER in a root .env file (see .env.example)}"
: "${GITHUB_ACCESS_TOKEN:?Set GITHUB_ACCESS_TOKEN (token with read:packages) in a root .env file (see .env.example)}"
# Preferred path: the standard helm login (respects any configured
# credential helper). It fails against ghcr because helm probes a
# placeholder repo and treats ghcr's 403 as fatal, so fall back below.
if printf '%s' "$GITHUB_ACCESS_TOKEN" | helm registry login ghcr.io -u "$GITHUB_USER" --password-stdin 2>/dev/null; then
echo "✓ helm registry login ghcr.io succeeded for $GITHUB_USER"
exit 0
fi
echo "ℹ helm registry login failed (ghcr 403 probe quirk); writing the auth entry directly ..."
# Fallback: write the same base64 auth entry helm/docker would store, into
# Helm's registry config, merging with existing auths (0600, owner-only).
REG_CONF=$(helm env | sed -n 's/^HELM_REGISTRY_CONFIG="\(.*\)"$/\1/p')
[ -n "$REG_CONF" ] || { echo "FAIL: could not resolve HELM_REGISTRY_CONFIG"; exit 1; }
AUTH=$(printf '%s:%s' "$GITHUB_USER" "$GITHUB_ACCESS_TOKEN" | base64 | tr -d '\n')
mkdir -p "$(dirname "$REG_CONF")"
python3 - "$REG_CONF" "$AUTH" <<'PY'
import json, os, sys
path, auth = sys.argv[1], sys.argv[2]
cfg = {}
if os.path.exists(path):
try:
with open(path) as f:
cfg = json.load(f)
except Exception:
cfg = {}
cfg.setdefault("auths", {})["ghcr.io"] = {"auth": auth}
# Create with 0600 (owner-only) so the token is not group/world-readable,
# matching what `helm`/`docker login` do. The value is base64, not encrypted.
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w") as f:
json.dump(cfg, f, indent=2)
os.chmod(path, 0o600)
PY
echo "✓ Wrote ghcr.io auth for $GITHUB_USER to $REG_CONF"
chart-checksums:
desc: "Recompute SHA256 / OCI digests for every chart and version listed under cluster: in infrastructure-catalog.yaml. Prints by default; pass '-write' (go run ./scripts/chart-checksums -write) to rewrite the checksums in place. Requires 'helm' on PATH. For private charts, run 'task registry:login:ghcr' first."
cmds:
- "go run ./scripts/chart-checksums"
bump:solo-operator:
desc: "Bump solo-operator to VERSION in ONE command: the Go API module (go.mod + vendor, the CRD types), the SOLO_OPERATOR_VERSION constant (single source of truth for the chart + operator image + UC sidecar), and the catalog checksum — then it re-compiles and verifies the invariants. Host-only; needs 'helm' on PATH and GITHUB_USER/GITHUB_ACCESS_TOKEN in the root .env (used for BOTH the private Go module and the private ghcr chart). A breaking CRD change in the new version surfaces here as a compile error — fix the code and re-run. After it succeeds, run 'task vm:sync' to push vendor/ into the VM. Accepts 0.6.1 or v0.6.1."
cmds:
- |
set -e
VERSION="{{.VERSION}}"
# The Taskfile has a global VERSION (default 0.0.0, for binary ldflags), so an
# omitted arg resolves to 0.0.0 rather than empty — reject both so a forgotten
# VERSION can't silently bump the operator to 0.0.0.
case "$VERSION" in ""|"0.0.0") echo "✗ Pass VERSION, e.g. 'task bump:solo-operator VERSION=0.6.1'" >&2; exit 1 ;; esac
: "${GITHUB_USER:?Set GITHUB_USER in a root .env file (see .env.example)}"
: "${GITHUB_ACCESS_TOKEN:?Set GITHUB_ACCESS_TOKEN (repo read + read:packages) in a root .env file (see .env.example)}"
BARE="${VERSION#v}" # artifacts (constant, catalog key) use the bare version
MODVER="v$BARE" # Go module versions are v-prefixed
DEPS=pkg/deps/deps.go
CATALOG=pkg/software/infrastructure-catalog.yaml
# 1. Go API module (CRD types) + re-vendor. First, so a bad version / auth fails
# before anything else is touched. The PAT is injected into git via env-config
# for these commands only (never written to ~/.gitconfig), and GONOSUMCHECK/
# GONOSUMDB skip the public sum DB (which 404s for a private repo).
export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0="url.https://${GITHUB_USER}:${GITHUB_ACCESS_TOKEN}@github.com/.insteadOf"
export GIT_CONFIG_VALUE_0="https://github.com/"
export GOPRIVATE="github.com/hashgraph/*"
export GONOSUMCHECK=1 GONOSUMDB="github.com/hashgraph/*" GOFLAGS=-mod=mod
echo "→ go get github.com/hashgraph/solo-operator@$MODVER"
go get "github.com/hashgraph/solo-operator@$MODVER"
go mod tidy
go mod vendor
# Restore normal module settings for the remaining (vendored) build/test steps.
unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0 GOPRIVATE GONOSUMCHECK GONOSUMDB GOFLAGS
# 2. The single source of truth: the Go constant (chart + image + UC tag).
perl -i -pe 's/(SOLO_OPERATOR_VERSION\s*=\s*)"[^"]*"/${1}"'"$BARE"'"/' "$DEPS"
grep -q "SOLO_OPERATOR_VERSION = \"$BARE\"" "$DEPS" || { echo "✗ failed to update $DEPS" >&2; exit 1; }
# 3. Point the catalog's solo-operator version key at the new version, scoped to
# the solo-operator block so no other chart is touched. Its checksum is filled
# in step 5. (The catalog default is injected from the constant at load, so
# only the version key + checksum live in the YAML.)
awk -v ver="$BARE" '
/^- name:/ { inblk = ($0 ~ /solo-operator/) }
inblk && !done && /^ [0-9][^:]*:[[:space:]]*$/ { sub(/^ [0-9][^:]*:/, " " ver ":"); done=1 }
{ print }
' "$CATALOG" > "$CATALOG.tmp" && mv "$CATALOG.tmp" "$CATALOG"
# 4. Auth for the private chart, then 5. recompute + write the checksum.
task registry:login:ghcr
go run ./scripts/chart-checksums -write
# 6. Compatibility gate: compile the packages that use the CRD types against the
# newly vendored module. A breaking schema change fails loudly here.
echo "→ verifying the new CRD types compile ..."
GOOS=linux GOARCH=amd64 go build ./cmd/cli/... ./internal/... ./pkg/...
# 7. Verify the single-source-of-truth invariants (default injected, checksum
# present for the version, UC tag tracks the operator version).
go test -tags integration -run Test_Config_SoloOperatorVersionSourceOfTruth ./pkg/software/
echo ""
echo "✓ solo-operator bumped to $BARE (module + chart + image + UC sidecar)."
echo " Next: review the diff (go.mod, vendor/, deps.go, catalog) and 'task vm:sync' before building in the VM."
lint:
cmds:
- "go fmt -x ./..."
- task: lint:errorx
lint:check:
cmds:
- 'R=$(go fmt ./...) && if [[ -n "$R" ]]; then echo "The following files require formatting:"; echo "$R"; exit 1; fi'
- task: lint:errorx
lint:errorx:
internal: true
desc: "Enforce errorx over errors.New / fmt.Errorf in production code (only lines new vs origin/main)"
deps:
- task: setup:golangci-lint
# `mocks` produces *_generated.go test mocks, required by forbidigo's
# type-aware analysis (analyze-types: true) to typecheck the tree cleanly.
- task: mocks
env:
# Run as Linux so platform-tagged files (e.g. internal/mount/*_unix.go, pkg/kernel
# via pault.ag/go/modprobe) typecheck on developer macOS the same way they do in CI.
GOOS: linux
cmds:
- cmd: git fetch --no-tags origin main >/dev/null 2>&1 || true
silent: true
- golangci-lint run --config .golangci.yml ./...
setup:golangci-lint:
desc: "Install golangci-lint v2.15.0 (used by lint:errorx to enforce errorx)"
internal: true
status:
# Require the pinned version exactly, so developers running `task lint:errorx`
# use the same binary version as CI.
- 'golangci-lint --version 2>/dev/null | grep -q "version v2.15.0"'
cmds:
- cmd: echo "Installing golangci-lint v2.15.0..."
silent: true
- cmd: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.15.0
silent: true
license:check:
desc: "Check if all source files have SPDX license headers"
dir: scripts
cmds:
- "./license-header.sh check"
license:add:
desc: "Add SPDX license headers to source files that are missing them"
dir: scripts
cmds:
- "./license-header.sh add"
license:
desc: "Alias for license:add - Add SPDX license headers to source files"
cmds:
- task: license:add
build:
deps:
- "vendor"
cmds:
- task: "clean"
# Produces the daemon binaries too: build:cli depends on the matching
# build:daemon, because the CLI is stamped with that daemon's digest.
# Calling build:daemon:all here as well would just rebuild them.
- task: "build:cli:all"
sources:
- "**/*.go"
generates:
- "bin/solo-provisioner-linux-*"
- "bin/solo-provisioner-darwin-*"
- "bin/solo-provisioner-daemon-linux-*"
- "bin/solo-provisioner-daemon-darwin-*"
build:image:
deps:
- task: "build"
cmds:
- "docker build -t solo-weaver ."
hash:
desc: "Hash every released binary (CLI + daemon) for all OS/arch combinations"
deps:
- "build"
cmds:
- task: "hash:cli:all"
- task: "hash:daemon:all"
sign:
desc: "Sign every released binary (CLI + daemon) for all OS/arch combinations"
deps:
- "build"
cmds:
- task: "sign:cli:all"
- task: "sign:daemon:all"
# Last word on the release: every CLI about to be published must carry the
# digest of the daemon published with it, or its auto-download cannot work.
- task: "verify:cli:all"
generate:
# Declared `run: once` so multiple callers in the same invocation (e.g.
# build:cli:all and build:daemon:all from top-level `task build`) share a
# single execution.
run: once
cmds:
- "go generate ./..."
run:
deps:
- "run:cli"
install:go:
deps:
- "install:go:linux"
install:go:linux:
internal: true
status:
- "command -v go"
- "command -v dlv"
platforms:
- "linux/amd64"
- "linux/arm64"
cmds:
- "curl -sSLO https://go.dev/dl/go{{.GO_VERSION}}.{{OS}}-{{ARCH}}.tar.gz"
- "sudo rm -rf /usr/local/go"
- "sudo tar -C /usr/local -xzf go{{.GO_VERSION}}.{{OS}}-{{ARCH}}.tar.gz"
- "rm -f go{{.GO_VERSION}}.{{OS}}-{{ARCH}}.tar.gz"
- 'echo ''PATH="${PATH}:/usr/local/go/bin:$HOME/go/bin"'' | sudo tee /etc/profile.d/go.sh >/dev/null 2>&1'
- "source /etc/profile.d/go.sh"
- "source /etc/profile.d/go.sh && go install github.com/go-delve/delve/cmd/dlv@latest"
# Set the Go toolchain to auto-upgrade to the latest version due to https://github.com/golang/go/issues/75031
- "/usr/local/go/bin/go env -w GOTOOLCHAIN=go1.27.2+auto"
- "sudo /usr/local/go/bin/go env -w GOTOOLCHAIN=go1.27.2+auto"
generates:
- "/usr/local/go/**"
mocks:
cmds:
- go install github.com/golang/mock/mockgen@v1.6.0
# Generate mocks in the package it belongs. Note these generated mock files are not checked in.
- mockgen -source=pkg/security/principal/interface.go -self_package github.com/hashgraph/solo-weaver/pkg/security/principal -package principal > pkg/security/principal/mocks_generated.go
- mockgen -source=pkg/fsx/interface.go -self_package github.com/hashgraph/solo-weaver/pkg/fsx -package fsx > pkg/fsx/mocks_generated.go
- mockgen -source=pkg/software/interface.go -self_package github.com/hashgraph/solo-weaver/pkg/software -package software> pkg/software/mocks_generated.go
- mockgen -source=pkg/kernel/interface.go -self_package github.com/hashgraph/solo-weaver/pkg/kernel -package kernel > pkg/kernel/mocks_generated.go
- mockgen -source=pkg/helm/interface.go -self_package github.com/hashgraph/solo-weaver/pkg/helm -package helm > pkg/helm/mocks_generated.go
# Remote debug tasks for Docker container debugging
debug:container:
desc: "Setup debug environment and show available debug commands"
deps:
- "debug:container:setup"
cmds:
- |
echo "🐛 Debug environment ready!"
echo ""
echo "Available debug commands:"
echo " task debug:container:test -- [package] # Debug tests (package required)"
echo " task debug:container:app -- [args...] # Debug weaver application"
echo " task debug:container:stop # Stop debug container"
echo ""
debug:container:setup:
desc: "Setup Docker container for remote debugging"
dir: build
cmds:
- "./debug-setup.sh"
debug:container:test:
desc: "Start remote test debug server in Docker container (specify package with -- ./pkg/name)"
deps:
- "debug:container:setup"
dir: build
cmds:
- "./debug-run.sh test {{.CLI_ARGS}}"
debug:container:app:
desc: "Start remote application debug server in Docker container (pass args with -- after command)"
deps:
- "debug:container:setup"
dir: build
cmds:
- "./debug-run.sh app {{.CLI_ARGS}}"
debug:container:stop:
desc: "Stop remote debug container"
cmds:
- "docker stop solo-weaver-debug 2>/dev/null || true"
# VM Debugging Tasks