Label Sync / Label Sync (push) Failing after 1m7s
E2E / reject-invalid (bad-bgp-asn) (push) Skipped
E2E / reject-invalid (bad-mac-address) (push) Skipped
E2E / reject-invalid (bad-repo-url) (push) Skipped
E2E / reject-invalid (bad-vlan-tag) (push) Skipped
E2E / reject-invalid (duplicate-gateway-addrs) (push) Skipped
E2E / reject-invalid (duplicate-node-names) (push) Skipped
E2E / reject-invalid (gateway-node-collision) (push) Skipped
E2E / reject-invalid (missing-dns-token) (push) Skipped
E2E / reject-invalid (nested-cidr-overlap) (push) Skipped
E2E / reject-invalid (node-addr-outside-cidr) (push) Skipped
E2E / reject-invalid (tunnel-without-dns) (push) Skipped
E2E / accept-valid (selfhosted) (push) Skipped
E2E / reject-invalid (missing-known-hosts) (push) Skipped
E2E / reject-invalid (missing-schematic) (push) Skipped
E2E / reject-invalid (partial-bgp) (push) Skipped
E2E / accept-valid (internal) (push) Skipped
E2E / reject-invalid (missing-external-gateway) (push) Skipped
E2E / reject-invalid (node-uses-gateway-addr) (push) Skipped
E2E / reject-invalid (non-canonical-cidr) (push) Skipped
E2E / accept-valid (private) (push) Skipped
E2E / accept-valid (single-node) (push) Skipped
E2E / reject-invalid (overlapping-cidrs) (push) Skipped
E2E / reject-invalid (reserved-node-name) (push) Skipped
E2E / reject-invalid (tiny-svc-cidr) (push) Skipped
E2E / validator-tests (push) Skipped
E2E / accept-valid (direct) (push) Skipped
E2E / accept-valid (multi-controller) (push) Skipped
E2E / accept-valid (no-webhook) (push) Skipped
E2E / accept-valid (public) (push) Skipped
183 lines
7.6 KiB
Plaintext
183 lines
7.6 KiB
Plaintext
set quiet
|
|
set default-list
|
|
set default-script
|
|
set shell := ['bash', '-euo', 'pipefail', '-c']
|
|
set script-interpreter := ['bash', '-euo', 'pipefail']
|
|
set working-directory := '..'
|
|
|
|
bootstrap_dir := justfile_dir() + '/bootstrap'
|
|
kubernetes_dir := justfile_dir() + '/kubernetes'
|
|
talos_dir := justfile_dir() + '/talos'
|
|
private_dir := justfile_dir() + '/.private'
|
|
|
|
template_dir := justfile_dir() + '/template'
|
|
template_resources := template_dir + '/resources'
|
|
makejinja_config := justfile_dir() + '/makejinja.toml'
|
|
mise_fragment := justfile_dir() + '/.mise/conf.d/template.toml'
|
|
|
|
config_file := justfile_dir() + '/cluster.toml'
|
|
schema_file := justfile_dir() + '/cluster.schema.json'
|
|
sample_config_file := justfile_dir() + '/cluster.sample.toml'
|
|
validate_script := template_dir + '/scripts/validate.py'
|
|
deploy_key := justfile_dir() + '/deploy.key'
|
|
webhook_token_file := justfile_dir() + '/flux-webhook-token.txt'
|
|
cloudflare_tunnel := justfile_dir() + '/cloudflare-tunnel.json'
|
|
|
|
[doc('Render and validate configuration files')]
|
|
[group('template')]
|
|
configure: render encrypt-secrets validate-kubernetes validate-talos
|
|
|
|
[doc('Check that all prerequisite files exist and cluster.toml validates against the schema')]
|
|
[group('template')]
|
|
[no-exit-message]
|
|
doctor:
|
|
rc=0
|
|
just log info "just version" version "{{ just_version() }}"
|
|
just template doctor-check "cluster.toml" "{{ config_file }}" || rc=1
|
|
just template doctor-check "cluster.sample.toml" "{{ sample_config_file }}" || rc=1
|
|
config_json="$(just template config-json 2>/dev/null || true)"
|
|
if [ "$(jq -r '.ingress.mode' <<< "$config_json" 2>/dev/null)" = "cloudflare-tunnel" ]; then
|
|
just template doctor-check "cloudflare-tunnel.json" "{{ cloudflare_tunnel }}" || rc=1
|
|
fi
|
|
just template doctor-check "age.key" "$SOPS_AGE_KEY_FILE" || rc=1
|
|
just template doctor-check "deploy.key" "{{ deploy_key }}" || rc=1
|
|
just template doctor-check "flux-webhook-token.txt" "{{ webhook_token_file }}" || rc=1
|
|
just template doctor-check "template/" "{{ template_dir }}" || rc=1
|
|
just template doctor-check "makejinja.toml" "{{ makejinja_config }}" || rc=1
|
|
just template doctor-check "validate.py" "{{ validate_script }}" || rc=1
|
|
if [ -f "{{ config_file }}" ] && [ -f "{{ validate_script }}" ]; then
|
|
if [ -n "$config_json" ]; then
|
|
just log info "ok" check schema
|
|
else
|
|
just log error "fail" check schema
|
|
just log info "diagnose with: uv run --locked --no-dev {{ validate_script }} {{ config_file }}"
|
|
rc=1
|
|
fi
|
|
fi
|
|
[ "$rc" = 0 ] && just log info "all good"
|
|
exit "$rc"
|
|
|
|
[doc('Initialize configuration files (cluster.toml, age key, deploy key, webhook token)')]
|
|
[group('template')]
|
|
init:
|
|
[ -f "{{ config_file }}" ] || cp "{{ sample_config_file }}" "{{ config_file }}"
|
|
[ -f "$SOPS_AGE_KEY_FILE" ] || age-keygen -pq --output "$SOPS_AGE_KEY_FILE"
|
|
[ -f "{{ deploy_key }}" ] || ssh-keygen -t ed25519 -C "deploy-key" -f "{{ deploy_key }}" -q -P ""
|
|
[ -f "{{ webhook_token_file }}" ] || openssl rand -hex 16 > "{{ webhook_token_file }}"
|
|
|
|
# Editors resolve the #:schema directive in cluster.toml against this file;
|
|
# the validator tests fail when it drifts from the pydantic model.
|
|
[doc('Regenerate the cluster.toml JSON Schema from the validator')]
|
|
[group('template')]
|
|
schema:
|
|
uv run --quiet --locked --no-dev "{{ validate_script }}" --schema > "{{ schema_file }}"
|
|
oxfmt "{{ schema_file }}" > /dev/null
|
|
|
|
[confirm("Remove all templated files and directories — continue? [y/N]")]
|
|
[doc('Remove rendered files (bootstrap/, kubernetes/, talos/, .sops.yaml)')]
|
|
[group('template')]
|
|
reset:
|
|
rm -rf "{{ bootstrap_dir }}" "{{ kubernetes_dir }}" "{{ talos_dir }}" "{{ justfile_dir() }}/.sops.yaml"
|
|
|
|
# tidy-archive moves template/ (this module's source) away, so it runs as a
|
|
# subsequent dependency: the tidy script itself must spawn while the module
|
|
# directory still exists.
|
|
[confirm("All template related config will be archived — continue? [y/N]")]
|
|
[doc('Archive all template tooling under .private/<ts>/ (one-way, run after configure works)')]
|
|
[group('template')]
|
|
tidy: tidy-preconditions tidy-strip && tidy-archive
|
|
|
|
# Validated, defaulted cluster config as JSON on stdout; fails when the
|
|
# config is invalid.
|
|
[private]
|
|
config-json:
|
|
uv run --quiet --locked --no-dev "{{ validate_script }}" "{{ config_file }}"
|
|
|
|
[no-exit-message]
|
|
[private]
|
|
doctor-check label path:
|
|
if [ -e "{{ path }}" ]; then
|
|
just log info "ok" file "{{ label }}"
|
|
else
|
|
just log error "missing" file "{{ label }}"
|
|
exit 1
|
|
fi
|
|
|
|
[private]
|
|
encrypt-secrets:
|
|
find "{{ bootstrap_dir }}" "{{ kubernetes_dir }}" "{{ talos_dir }}" -type f -name '*.sops.*' -print0 \
|
|
| while IFS= read -r -d '' f; do
|
|
if ! status="$(sops filestatus "$f" | jq -r '.encrypted')"; then
|
|
just log fatal "could not read encryption status" file "$f"
|
|
fi
|
|
case "$status" in
|
|
false) sops encrypt --in-place "$f" ;;
|
|
true) : ;;
|
|
*) just log fatal "could not determine encryption status" file "$f" ;;
|
|
esac
|
|
done
|
|
|
|
[private]
|
|
render:
|
|
PYTHONDONTWRITEBYTECODE=1 uv run --locked --no-dev makejinja
|
|
|
|
[private]
|
|
tidy-archive:
|
|
TIDY_FOLDER="{{ private_dir }}/$(date +%s)"
|
|
mkdir -p "$TIDY_FOLDER"
|
|
rm -rf "{{ justfile_dir() }}/.github/template-tests" "{{ justfile_dir() }}/.github/workflows"/template-*.yaml
|
|
mv \
|
|
"{{ template_dir }}" \
|
|
"{{ makejinja_config }}" \
|
|
"{{ mise_fragment }}" \
|
|
"{{ schema_file }}" \
|
|
"{{ config_file }}" \
|
|
"{{ sample_config_file }}" \
|
|
"{{ justfile_dir() }}/pyproject.toml" \
|
|
"{{ justfile_dir() }}/uv.lock" \
|
|
"$TIDY_FOLDER/"
|
|
rmdir "$(dirname "{{ mise_fragment }}")" 2>/dev/null || true
|
|
rm -rf "{{ justfile_dir() }}/.venv"
|
|
|
|
[private]
|
|
tidy-preconditions:
|
|
test -d "{{ template_dir }}"
|
|
test -d "{{ justfile_dir() }}/.github/template-tests"
|
|
test -f "{{ makejinja_config }}"
|
|
test -f "{{ config_file }}"
|
|
test -f "{{ sample_config_file }}"
|
|
test -f "{{ justfile_dir() }}/pyproject.toml"
|
|
test -f "{{ justfile_dir() }}/uv.lock"
|
|
test -f "{{ justfile_dir() }}/justfile"
|
|
test -f "{{ mise_fragment }}"
|
|
test -f "{{ schema_file }}"
|
|
test -f "{{ justfile_dir() }}/.renovaterc.json5"
|
|
compgen -G "{{ justfile_dir() }}/.github/workflows/template-*.yaml" > /dev/null
|
|
|
|
[private]
|
|
tidy-strip:
|
|
sd '\..\.j2' '' "{{ justfile_dir() }}/.renovaterc.json5"
|
|
sd -A '(?ms)^# === template ===$.*' '' "{{ justfile_dir() }}/justfile"
|
|
|
|
# Renders every bootstrap chart with the rendered values, catching chart or
|
|
# values drift that dry runs cannot; needs network access to pull charts.
|
|
[doc('Render the bootstrap helmfile charts against the rendered values')]
|
|
[group('template')]
|
|
test-helmfile:
|
|
helmfile --file "{{ bootstrap_dir }}/helmfile/apps.yaml" template --quiet > /dev/null
|
|
just log info "bootstrap charts rendered cleanly"
|
|
|
|
[private]
|
|
validate-kubernetes:
|
|
bash "{{ template_resources }}/kubeconform.sh" "{{ kubernetes_dir }}"
|
|
|
|
# Rendering needs the secrets bundle; topf generates and sops-encrypts one
|
|
# on first run (--confirm=false so it never prompts mid-pipeline).
|
|
[private]
|
|
[working-directory('talos')]
|
|
validate-talos:
|
|
topf render --confirm=false --output "$(mktemp -d)" >/dev/null
|
|
if ! sops filestatus secrets.sops.yaml | jq --exit-status '.encrypted == true' >/dev/null; then
|
|
just log fatal "Talos secrets bundle is not encrypted"
|
|
fi
|