mirror of
https://github.com/enix/x509-certificate-exporter.git
synced 2026-08-18 03:26:16 +00:00
513 lines
23 KiB
YAML
513 lines
23 KiB
YAML
version: "3"
|
|
|
|
# Thin façade over the build tools:
|
|
# - Dagger module (dagger.json at repo root, source in dagger/) for
|
|
# sandboxed QA/CI pipelines (lint, test, security, helm utilities).
|
|
# Invoked via `dagger call <function>` — find-up locates the module.
|
|
# - GoReleaser for ALL image builds — release pipeline AND the dev
|
|
# image consumed by Tilt (one Dockerfile path, dev image == release
|
|
# image). Driven by .goreleaser.yaml; in CI via release.yaml;
|
|
# locally via `task build:image:host` (host-arch, fast) or
|
|
# `task build:image:all` (every cross-arch variant). Tilt invokes
|
|
# goreleaser directly with GORELEASER_TILT=1.
|
|
# - Direct CLI for things that don't need sandboxing: k3d/tilt for
|
|
# the dev cluster, Renovate dry-run via Docker, and `go mod tidy` /
|
|
# `go get -u`. GitHub Action SHA-pinning is owned by Renovate
|
|
# (`pinDigests: true` in renovate.json5).
|
|
|
|
env:
|
|
# Silence Dagger's Cloud upload and analytics paths globally.
|
|
DAGGER_NO_NAG: "1"
|
|
DAGGER_CLOUD_TOKEN: ""
|
|
DO_NOT_TRACK: "1"
|
|
|
|
vars:
|
|
KUBECONFIG_DEV: "{{.TASKFILE_DIR}}/kubeconfig.yaml"
|
|
DEV_CLUSTER: x509ce-dev
|
|
DEV_REGISTRY: x509ce-dev-registry
|
|
DEV_REGISTRY_PORT: 5000
|
|
# Shared by `dev:cluster:up` and `test:e2e` so the two clusters
|
|
# always run the same k3s — drift would mean e2e tests don't
|
|
# exercise what dev does. Renovate-tracked (custom regex manager
|
|
# in renovate.json5).
|
|
K3S_IMAGE: rancher/k3s:v1.36.3-k3s1
|
|
# Shared by `renovate:plan` and `renovate:patch`. Renovate-tracked
|
|
# (custom regex manager in renovate.json5) and kept in lockstep with
|
|
# `renovateImage` in dagger/base.go.
|
|
RENOVATE_IMAGE: renovate/renovate:44.11.4
|
|
|
|
tasks:
|
|
default:
|
|
desc: List available tasks
|
|
cmds:
|
|
- task --list
|
|
silent: true
|
|
|
|
check:
|
|
desc: Run every gate that CI runs — lint + test + security
|
|
cmds:
|
|
- task: lint
|
|
- task: test
|
|
- task: security
|
|
|
|
# ─── Build ───────────────────────────────────────────────────────────────
|
|
build:binary:host:
|
|
desc: "Host-arch binary via GoReleaser snapshot — symlinked at dist/x509-certificate-exporter"
|
|
# Goes through GoReleaser (same flags, ldflags, version stamping
|
|
# as the release pipeline) instead of a direct `go build`, so the
|
|
# local binary matches what the tag-driven release would produce.
|
|
# `--single-target` builds only the host platform, picked from the
|
|
# cross-OS matrix in .goreleaser.yaml's `x509ce` build entry.
|
|
# `--clean` wipes dist/ before building — running `task build:binary:host`
|
|
# and `task build:image:host` back-to-back will clobber each other's
|
|
# artifacts; both are local iteration shortcuts, that's acceptable.
|
|
#
|
|
# The post-build symlink gives a stable, predictable path for the
|
|
# binary regardless of host arch / GOAMD64 variant. We `cd dist`
|
|
# so the symlink target is relative to the dist/ dir itself (not
|
|
# to the project root), which keeps it valid even if the repo
|
|
# gets moved. The glob matches exactly one dir thanks to
|
|
# --single-target + --clean — multiple matches would make `ln`
|
|
# fail loudly, which is fine.
|
|
cmds:
|
|
- goreleaser build --single-target --snapshot --clean
|
|
- cd dist && ln -sf x509ce_*/x509-certificate-exporter x509-certificate-exporter
|
|
|
|
build:image:host:
|
|
desc: "Build the host-arch image variants locally via GoReleaser snapshot — fast iteration, no QEMU cross-build"
|
|
# GORELEASER_LOCAL_PLATFORM=1 flips two switches at once:
|
|
# 1. builds[]: swaps the full 23-target cross-OS matrix for a
|
|
# Linux-only x509ce-local build (amd64).
|
|
# 2. dockers_v2: enables only the scratch-local / busybox-local
|
|
# entries, which build for `linux/{{ .Runtime.Goarch }}` —
|
|
# the host arch, no QEMU emulation.
|
|
# Disabled cross-arch entries are reported as "configuration is
|
|
# disabled" — cosmetic, exit code is 0.
|
|
env:
|
|
GORELEASER_LOCAL_PLATFORM: "1"
|
|
IMAGE_NAME: '{{.IMAGE_NAME | default "x509-certificate-exporter"}}'
|
|
cmds:
|
|
- goreleaser release --snapshot --skip=publish,sign --clean
|
|
|
|
build:image:all:
|
|
desc: "Like `task build:image:host` but every cross-arch variant (no host-only short-circuit) — validates the full release matrix without pushing"
|
|
# Snapshot mode skips git tag validation, push, and signing, so
|
|
# this is safe to run on any branch / dirty tree. Useful to verify
|
|
# the .goreleaser.yaml config produces what's expected.
|
|
# IMAGE_NAME defaults to the project name for local snapshots; CI
|
|
# overrides it via the `release` Environment's `vars.IMAGE_NAME`.
|
|
env:
|
|
IMAGE_NAME: '{{.IMAGE_NAME | default "x509-certificate-exporter"}}'
|
|
cmds:
|
|
- goreleaser release --snapshot --skip=publish,sign --clean
|
|
|
|
# ─── Dependencies ────────────────────────────────────────────────────────
|
|
go:tidy:
|
|
desc: go mod tidy on the main module and ./dagger
|
|
cmds:
|
|
- go mod tidy
|
|
- go -C dagger mod tidy
|
|
|
|
go:upgrade:
|
|
desc: Upgrade the main module's Go deps to latest then tidy
|
|
# Main module only. The dagger/ module is deliberately excluded: its
|
|
# `dagger.io/dagger` SDK must equal dagger.json's engineVersion (the
|
|
# single source of truth), and `go get -u` there would desync it and
|
|
# break against the generated bindings — a newer SDK relocates
|
|
# packages like `querybuilder`. The Dagger version moves via the
|
|
# Renovate "dagger SDK + engine" PR; `task dagger:develop` realigns
|
|
# the module afterwards (CI does it automatically on that PR).
|
|
cmds:
|
|
- go get -u ./...
|
|
- go mod tidy
|
|
|
|
dagger:develop:
|
|
desc: "Regenerate the Dagger module bindings and realign dagger/go.mod (after a dagger.json engineVersion change)"
|
|
# Local counterpart of the `Dagger module` workflow. Only needed when
|
|
# you edit dagger.json yourself — for Renovate's bumps, CI already
|
|
# commits the result onto the PR.
|
|
cmds:
|
|
- dagger develop
|
|
- go -C dagger mod tidy
|
|
|
|
nix:update:
|
|
desc: Update flake.lock to latest inputs
|
|
cmds:
|
|
- nix --extra-experimental-features 'nix-command flakes' flake update
|
|
|
|
renovate:plan:
|
|
desc: "Dry-run Renovate against the working tree — lists planned bumps; does NOT modify files"
|
|
# `platform=local` defaults to `dryRun=lookup`: extract + lookup
|
|
# phases only, nothing touches the working tree. The actual update
|
|
# plan is buried in DEBUG-level structured logs (info-level only
|
|
# gives extraction stats). We pull them at LOG_FORMAT=json and
|
|
# filter the `packageFiles with updates` event with jq for a
|
|
# human-readable plan: one line per planned bump (file, dep,
|
|
# current → new). Useful for debugging renovate.json5 without
|
|
# polluting GitHub. Actual bumps come from .github/workflows/renovate.yaml.
|
|
cmds:
|
|
- |
|
|
set -o pipefail
|
|
export GITHUB_COM_TOKEN="$(gh auth token 2>/dev/null || echo '')"
|
|
# Authenticate to Docker Hub if credentials are in the env —
|
|
# avoids the 100/6h anonymous rate limit that otherwise aborts
|
|
# the run with `external-host-error`. Set DOCKERHUB_USERNAME +
|
|
# DOCKERHUB_TOKEN locally (e.g. via direnv) to opt in.
|
|
if [ -n "${DOCKERHUB_TOKEN:-}" ]; then
|
|
export RENOVATE_HOST_RULES='[{"matchHost":"index.docker.io","username":"'"$DOCKERHUB_USERNAME"'","password":"'"$DOCKERHUB_TOKEN"'"}]'
|
|
fi
|
|
docker run --rm \
|
|
-v "$(pwd):/usr/src/app" \
|
|
-w /usr/src/app \
|
|
--user "$(id -u):$(id -g)" \
|
|
-e RENOVATE_PLATFORM=local \
|
|
-e LOG_LEVEL=debug \
|
|
-e LOG_FORMAT=json \
|
|
-e GITHUB_COM_TOKEN \
|
|
${RENOVATE_HOST_RULES:+-e RENOVATE_HOST_RULES} \
|
|
{{.RENOVATE_IMAGE}} 2>&1 \
|
|
| jq -rR 'fromjson?
|
|
| select(.msg == "packageFiles with updates")
|
|
| .config | to_entries[] | .value[]
|
|
| .packageFile as $f
|
|
| .deps[]? as $d | $d.updates[]?
|
|
| . as $u
|
|
| ($d.currentValue // "?") as $cv
|
|
| ($d.currentDigest // "") as $cd
|
|
| ($u.newValue // $cv) as $nv
|
|
| ($u.newDigest // "") as $nd
|
|
| "\($f)\t\($d.depName)\t\($cv)\(if $cd != "" then "@\($cd)" else "" end) → \($nv)\(if $nd != "" then "@\($nd)" else "" end)"' \
|
|
| column -t -s "$(printf '\t')"
|
|
|
|
renovate:patch:
|
|
desc: "Apply Renovate's planned bumps to the working tree (best-effort, format-preserving)"
|
|
# Same Renovate dry-run as renovate:plan, but pipe the JSON debug log
|
|
# to scripts/renovate-patch.py — which finds the `packageFiles with
|
|
# updates` event and rewrites each `replaceString` in place. Intent:
|
|
# leave the working tree in a state Renovate's own delta logic can
|
|
# pick up cleanly on its next run, so we never get a diff Renovate
|
|
# would refuse. Any ambiguity is a SKIP with diagnostic on stderr —
|
|
# better to do nothing than to corrupt formatting Renovate would
|
|
# then have to fight.
|
|
cmds:
|
|
- |
|
|
set -o pipefail
|
|
export GITHUB_COM_TOKEN="$(gh auth token 2>/dev/null || echo '')"
|
|
# See `renovate:plan` for the rationale on Docker Hub auth.
|
|
if [ -n "${DOCKERHUB_TOKEN:-}" ]; then
|
|
export RENOVATE_HOST_RULES='[{"matchHost":"index.docker.io","username":"'"$DOCKERHUB_USERNAME"'","password":"'"$DOCKERHUB_TOKEN"'"}]'
|
|
fi
|
|
docker run --rm \
|
|
-v "$(pwd):/usr/src/app" \
|
|
-w /usr/src/app \
|
|
--user "$(id -u):$(id -g)" \
|
|
-e RENOVATE_PLATFORM=local \
|
|
-e LOG_LEVEL=debug \
|
|
-e LOG_FORMAT=json \
|
|
-e GITHUB_COM_TOKEN \
|
|
${RENOVATE_HOST_RULES:+-e RENOVATE_HOST_RULES} \
|
|
{{.RENOVATE_IMAGE}} 2>&1 \
|
|
| python3 scripts/renovate-patch.py
|
|
|
|
# ─── Tests ───────────────────────────────────────────────────────────────
|
|
test:
|
|
desc: "Run all tests (unit + fuzz + helm-examples + helm-fixtures + helm-render + e2e)"
|
|
cmds:
|
|
- task: test:unit
|
|
- task: test:fuzz
|
|
- task: test:helm-examples
|
|
- task: test:helm-fixtures
|
|
- task: test:helm-render
|
|
- task: test:e2e
|
|
|
|
test:unit:
|
|
desc: "Unit tests with race detector + coverage (gotestsum)"
|
|
cmds:
|
|
- dagger call --progress tty test
|
|
|
|
test:fuzz:
|
|
desc: "Smoke-run every fuzzer (5s each) — fails on any new crash"
|
|
# Go fuzz only targets one Fuzz* function at a time, so we list each
|
|
# explicitly. Long-form fuzzing is a manual local activity:
|
|
# go test -fuzz=<name> -fuzztime=10m ./<pkg>
|
|
#
|
|
# -parallel=4 caps the fuzz workers (default GOMAXPROCS): when the
|
|
# -fuzztime deadline hits, the coordinator cancels the workers'
|
|
# context and a loaded machine (e.g. `task check` right after the
|
|
# Dagger unit tests) can miss the grace window, failing the run
|
|
# with a spurious `context deadline exceeded`. The smoke only
|
|
# guards against seed-corpus crashes — random-exploration
|
|
# throughput is not what it's for — so fewer, snappier workers is
|
|
# the right trade.
|
|
cmds:
|
|
- go test -run=^$ -fuzz=FuzzParse -fuzztime=5s -parallel=4 ./pkg/cert/pem/
|
|
- go test -run=^$ -fuzz=FuzzParse -fuzztime=5s -parallel=4 ./pkg/cert/pkcs12/
|
|
- go test -run=^$ -fuzz=FuzzParse -fuzztime=5s -parallel=4 ./pkg/cert/der/
|
|
- go test -run=^$ -fuzz=FuzzParse -fuzztime=5s -parallel=4 ./pkg/cert/jks/
|
|
- go test -run=^$ -fuzz=FuzzCompile -fuzztime=5s -parallel=4 ./pkg/fileglob/
|
|
|
|
test:helm-examples:
|
|
desc: "Assert every docs/examples/**/*.values.yaml is accepted by the chart"
|
|
cmds:
|
|
- dagger call test-helm-examples
|
|
|
|
test:helm-fixtures:
|
|
desc: "Schema regression net — every test/schema/{valid,invalid}/*.yaml against the chart, with paired .expect.txt"
|
|
cmds:
|
|
- dagger call test-helm-fixtures
|
|
|
|
test:helm-render:
|
|
desc: "Cross-template alignment — `helm template` the chart with every host-paths watch mode and assert each ConfigMap scan path is reachable from a DaemonSet volumeMount"
|
|
cmds:
|
|
- dagger call test-helm-render
|
|
|
|
test:e2e:
|
|
desc: "End-to-end against a fully isolated throwaway cluster — random names + free port, race-free across parallel runs"
|
|
# Each invocation gets a unique RUN_ID, embedded in the cluster
|
|
# name, registry name, and (via socket bind(0)) registry port.
|
|
# Multiple `task test:e2e` runs can therefore execute concurrently
|
|
# without colliding on docker container names or host ports. No
|
|
# pre-cleanup step is needed since stale state from a prior run
|
|
# cannot share names with this one. The defer chain still
|
|
# guarantees teardown on success, failure, or Ctrl+C.
|
|
vars:
|
|
RUN_ID:
|
|
sh: python3 -c "import secrets; print(secrets.token_hex(4))"
|
|
CLUSTER: "x509ce-e2e-{{.RUN_ID}}"
|
|
REGISTRY: "x509ce-e2e-registry-{{.RUN_ID}}"
|
|
# Ask the kernel for a free port via bind(0) → getsockname →
|
|
# close. Tiny TOCTOU window between close and `k3d registry
|
|
# create`, in practice negligible for local dev.
|
|
REGISTRY_PORT:
|
|
sh: python3 -c "import socket; s=socket.socket(); s.bind(('',0)); port=s.getsockname()[1]; s.close(); print(port)"
|
|
KUBECONFIG_E2E:
|
|
sh: mktemp -t e2e-XXXXXX.kubeconfig.yaml
|
|
env:
|
|
_CLUSTER: "{{.CLUSTER}}"
|
|
_REGISTRY: "{{.REGISTRY}}"
|
|
_REGISTRY_PORT: "{{.REGISTRY_PORT}}"
|
|
_KUBECONFIG_E2E: "{{.KUBECONFIG_E2E}}"
|
|
_K3S_IMAGE: "{{.K3S_IMAGE}}"
|
|
cmds:
|
|
# Defers are LIFO. Order of registration → execution:
|
|
# 1. k3d registry delete — disconnects the registry container from the
|
|
# cluster network (`--registry-use` attached it at create time). If
|
|
# we left it for after the cluster delete, Docker would refuse to
|
|
# remove the network because the registry is still on it, and the
|
|
# network would leak. After enough leaks, Docker's predefined
|
|
# address pool exhausts: "all predefined address pools have been
|
|
# fully subnetted".
|
|
# 2. k3d cluster delete — removes the server/agent containers and
|
|
# the network (now empty).
|
|
# 3. docker network rm — paranoia step: if k3d ever still leaks the
|
|
# network for whatever reason, this catches it. Always exits 0.
|
|
# 4. rm kubeconfig.
|
|
- defer: rm -f -- "$_KUBECONFIG_E2E"
|
|
- defer: docker network rm "k3d-$_CLUSTER" 2>/dev/null || true
|
|
- defer: k3d cluster delete "$_CLUSTER"
|
|
- defer: k3d registry delete "$_REGISTRY"
|
|
- k3d registry create "$_REGISTRY" --port "$_REGISTRY_PORT" --no-help
|
|
- cmd: |
|
|
k3d cluster create "$_CLUSTER" \
|
|
--image "$_K3S_IMAGE" \
|
|
--registry-use "k3d-${_REGISTRY}:${_REGISTRY_PORT}" \
|
|
--kubeconfig-update-default=false \
|
|
--kubeconfig-switch-context=false \
|
|
--k3s-arg "--disable=traefik@server:*" \
|
|
--k3s-arg "--disable=servicelb@server:*" \
|
|
--no-lb \
|
|
--wait
|
|
- k3d kubeconfig get "$_CLUSTER" > "$_KUBECONFIG_E2E"
|
|
# k3d's `--wait` only signals that the server container is
|
|
# reachable, not that cluster add-ons have converged. Without an
|
|
# explicit barrier, Tilt's `helm install` races the flannel CNI
|
|
# bringing up `/run/flannel/subnet.env`, which fails the first
|
|
# pod sandbox with `failed to load flannel 'subnet.env' file`.
|
|
# K3s bootstrap is asynchronous: the API answers before the
|
|
# controllers have populated kube-system, so `kubectl wait` would
|
|
# exit "no matching resources" if called too early. We poll until
|
|
# at least one kube-system pod exists, then wait for full Ready.
|
|
- cmd: |
|
|
export KUBECONFIG="$_KUBECONFIG_E2E"
|
|
for _ in $(seq 1 60); do
|
|
[ "$(kubectl --namespace=kube-system get pods --no-headers 2>/dev/null | wc -l)" -gt 0 ] && break
|
|
sleep 1
|
|
done
|
|
kubectl --namespace=kube-system wait \
|
|
--for=condition=Ready pods --all --timeout=120s
|
|
- cmd: |
|
|
export KUBECONFIG="$_KUBECONFIG_E2E"
|
|
export E2E_CLUSTER_NAME="$_CLUSTER"
|
|
export E2E_REGISTRY_NAME="$_REGISTRY"
|
|
export E2E_REGISTRY_PORT="$_REGISTRY_PORT"
|
|
tilt -f test/e2e/Tiltfile ci
|
|
|
|
# ─── Quality ─────────────────────────────────────────────────────────────
|
|
lint:
|
|
desc: Run all linters
|
|
cmds:
|
|
- task: lint:go
|
|
- task: lint:helm
|
|
- task: lint:renovate
|
|
- task: lint:markdown
|
|
|
|
lint:go:
|
|
desc: golangci-lint on Go code (full configured set)
|
|
cmds:
|
|
- dagger call lint-go
|
|
|
|
lint:helm:
|
|
desc: helm lint on the chart
|
|
cmds:
|
|
- dagger call lint-helm
|
|
|
|
lint:renovate:
|
|
desc: renovate-config-validator on renovate.json5
|
|
cmds:
|
|
- dagger call lint-renovate
|
|
|
|
lint:markdown:
|
|
desc: markdownlint-cli2 on hand-written Markdown
|
|
cmds:
|
|
- dagger call lint-markdown
|
|
|
|
# ─── Security ────────────────────────────────────────────────────────────
|
|
security:
|
|
desc: Run all security checks
|
|
cmds:
|
|
- task: security:gitleaks
|
|
- task: security:govulncheck
|
|
- task: security:vuln-deps
|
|
- task: security:chart-misconfig
|
|
|
|
security:gitleaks:
|
|
desc: "Gitleaks — scan the working tree for committed secrets"
|
|
cmds:
|
|
- dagger call gitleaks
|
|
|
|
security:govulncheck:
|
|
desc: govulncheck — reachability-based CVE scan on Go code
|
|
cmds:
|
|
- dagger call govulncheck
|
|
|
|
security:vuln-deps:
|
|
desc: "Trivy filesystem scan — Go deps, lockfiles, OS packages (HIGH/CRITICAL)"
|
|
cmds:
|
|
- dagger call trivy --scan-type=fs
|
|
|
|
security:chart-misconfig:
|
|
desc: "Trivy IaC misconfig scan on chart/"
|
|
cmds:
|
|
- dagger call trivy --scan-type=config --scan-ref=chart
|
|
|
|
# ─── Documentation ───────────────────────────────────────────────────────
|
|
doc:
|
|
desc: Regenerate all documentation
|
|
cmds:
|
|
- task: doc:helm
|
|
|
|
doc:helm:
|
|
desc: Regenerate chart/README.md and chart/values.schema.json from values.yaml (+ .gotmpl, + @schema annotations)
|
|
cmds:
|
|
- dagger call helm-docs export --path=chart/README.md
|
|
- dagger call chart-schema export --path=chart/values.schema.json
|
|
|
|
# ─── Analysis ────────────────────────────────────────────────────────────
|
|
analysis:graph:
|
|
desc: "Render & open the full package dependency graph (goda → graphviz → xdg-open)"
|
|
# Output sits under dist/ so it inherits the repo's "build outputs
|
|
# live in dist/" convention. Caveat: `task build:binary:host` and
|
|
# `task build:image:*` use `goreleaser --clean`, which wipes dist/ —
|
|
# re-run this task if the SVG disappears.
|
|
cmds:
|
|
- mkdir -p dist
|
|
- goda graph ./... | dot -Tsvg > dist/graph.svg
|
|
- xdg-open dist/graph.svg 2>/dev/null || open dist/graph.svg 2>/dev/null || echo "Graph saved to dist/graph.svg — open it manually."
|
|
|
|
analysis:size:
|
|
desc: "Inspect host-arch binary size (gsa TUI, compact view) — builds first"
|
|
deps: [build:binary:host]
|
|
cmds:
|
|
- gsa ./dist/x509-certificate-exporter --tui
|
|
|
|
# ─── Dev environment ─────────────────────────────────────────────────────
|
|
dev:up:
|
|
desc: tilt up — full dev loop (cluster, build, deploy, seed)
|
|
deps: [dev:cluster:up]
|
|
env:
|
|
KUBECONFIG: "{{.KUBECONFIG_DEV}}"
|
|
cmds:
|
|
- tilt up
|
|
|
|
dev:down:
|
|
desc: tilt down + destroy cluster, registry, kubeconfig (full teardown)
|
|
env:
|
|
KUBECONFIG: "{{.KUBECONFIG_DEV}}"
|
|
cmds:
|
|
- tilt down
|
|
- task: dev:cluster:down
|
|
|
|
dev:cluster:up:
|
|
desc: Bring up k3d cluster + local registry (idempotent)
|
|
env:
|
|
KUBECONFIG: "{{.KUBECONFIG_DEV}}"
|
|
_DEV_CLUSTER: "{{.DEV_CLUSTER}}"
|
|
_DEV_REGISTRY: "{{.DEV_REGISTRY}}"
|
|
_DEV_REGISTRY_PORT: "{{.DEV_REGISTRY_PORT}}"
|
|
_K3S_IMAGE: "{{.K3S_IMAGE}}"
|
|
_KUBECONFIG_DEV: "{{.KUBECONFIG_DEV}}"
|
|
cmds:
|
|
- cmd: |
|
|
if ! k3d registry get "$_DEV_REGISTRY" >/dev/null 2>&1; then
|
|
printf '[cluster] creating local registry at localhost:%s\n' "$_DEV_REGISTRY_PORT"
|
|
k3d registry create "$_DEV_REGISTRY" --port "$_DEV_REGISTRY_PORT" >/dev/null
|
|
fi
|
|
- cmd: |
|
|
if ! k3d cluster get "$_DEV_CLUSTER" >/dev/null 2>&1; then
|
|
printf '[cluster] creating k3d cluster %s\n' "$_DEV_CLUSTER"
|
|
k3d cluster create "$_DEV_CLUSTER" \
|
|
--image "$_K3S_IMAGE" \
|
|
--registry-use "k3d-${_DEV_REGISTRY}:${_DEV_REGISTRY_PORT}" \
|
|
--kubeconfig-update-default=false \
|
|
--kubeconfig-switch-context=false \
|
|
--k3s-arg "--disable=traefik@server:*" \
|
|
--k3s-arg "--disable=servicelb@server:*" \
|
|
--no-lb \
|
|
--wait
|
|
fi
|
|
- k3d kubeconfig get "$_DEV_CLUSTER" > "$_KUBECONFIG_DEV"
|
|
- chmod 600 "$_KUBECONFIG_DEV"
|
|
# See `test:e2e` for the rationale: k3d's `--wait` doesn't cover
|
|
# CNI / DNS readiness, and `kubectl wait` would race the empty
|
|
# kube-system namespace right after a fresh cluster create.
|
|
- cmd: |
|
|
export KUBECONFIG="$_KUBECONFIG_DEV"
|
|
for _ in $(seq 1 60); do
|
|
[ "$(kubectl --namespace=kube-system get pods --no-headers 2>/dev/null | wc -l)" -gt 0 ] && break
|
|
sleep 1
|
|
done
|
|
kubectl --namespace=kube-system wait \
|
|
--for=condition=Ready pods --all --timeout=120s
|
|
- printf '[cluster] ready — KUBECONFIG=%s\n' "$_KUBECONFIG_DEV"
|
|
- echo "[cluster] reminder — when you're done, free the resources with 'task dev:cluster:down'"
|
|
run: once
|
|
|
|
dev:cluster:down:
|
|
desc: Destroy cluster + registry
|
|
env:
|
|
KUBECONFIG: "{{.KUBECONFIG_DEV}}"
|
|
_DEV_CLUSTER: "{{.DEV_CLUSTER}}"
|
|
_DEV_REGISTRY: "{{.DEV_REGISTRY}}"
|
|
_KUBECONFIG_DEV: "{{.KUBECONFIG_DEV}}"
|
|
cmds:
|
|
- cmd: |
|
|
if k3d cluster get "$_DEV_CLUSTER" >/dev/null 2>&1; then
|
|
k3d cluster delete "$_DEV_CLUSTER"
|
|
fi
|
|
- cmd: |
|
|
if k3d registry get "$_DEV_REGISTRY" >/dev/null 2>&1; then
|
|
k3d registry delete "$_DEV_REGISTRY"
|
|
fi
|
|
- rm -f -- "$_KUBECONFIG_DEV"
|
|
- echo "[cluster] torn down"
|