#!/usr/bin/env bash set -euo pipefail # Draws the Flux Kustomization dependency graph from the dependsOn fields in # kubernetes/clusters/homelab. Edges point from a dependency to its dependent. # # flux-graph.sh DOT on stdout, every edge # flux-graph.sh out.dot the same, written to out.dot # flux-graph.sh out.svg rendered; out.png likewise # # Rendered output is the transitive reduction (tred): an edge implied by a # longer path is left out, the order is the same. # # Green: infrastructure. Blue: the Velero restore chain. Dashed: nothing is # health-checked. Grey fill: suspended. # # Needs jq, either yq (mikefarah's or the Python wrapper) and, to render, # graphviz. REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CLUSTER_DIR="${REPO_ROOT}/kubernetes/clusters/homelab" OUT="${1:-}" command -v jq >/dev/null || { echo "jq not found" >&2; exit 1; } command -v yq >/dev/null || { echo "yq not found" >&2; exit 1; } # One JSON document per YAML document. yaml_to_json() { if [[ "$(yq --version)" == *mikefarah* ]]; then yq eval-all -o=json -I=0 '.' "$@" else yq -c '.' "$@" fi } to_dot() { yaml_to_json "${CLUSTER_DIR}"/*.yaml | jq -r -s ' [ .[] | select(.kind == "Kustomization" and .apiVersion == "kustomize.toolkit.fluxcd.io/v1") ] as $ks | "digraph flux {", " rankdir=LR;", " node [shape=box, style=\"rounded\", fontname=\"Helvetica\", fontsize=10];", " edge [color=\"#888888\"];", ( $ks[] | .metadata.name as $n # healthChecks are ignored under wait: true; without either, nothing # is checked, healthCheckExprs included. | (if .spec.wait == true then "wait" elif ((.spec.healthChecks // []) | length) > 0 then "health checks" else "wait: false" end) as $wait | (if .spec.suspend == true then ", style=\"rounded,filled\", fillcolor=\"#dddddd\"" elif $wait == "wait: false" then ", style=\"rounded,dashed\"" else "" end) as $dash | (if .spec.suspend == true then "\\nsuspended" else "" end) as $suspended | (if ($n | startswith("restore-")) or ($n | IN("velero", "velero-backups-synced")) then ", color=\"#1f77b4\", fontcolor=\"#1f77b4\"" elif ($n | startswith("infrastructure")) or ($n | IN("crowdsec", "external-dns-unifi", "netbird-ingress", "velero-schedules")) then ", color=\"#2ca02c\", fontcolor=\"#2ca02c\"" else "" end) as $colour | " \"\($n)\" [label=\"\($n)\\n\($wait) ยท \(.spec.timeout // "-")\($suspended)\"\($dash)\($colour)];" ), ( $ks[] | .metadata.name as $n | (.spec.dependsOn // [])[] | " \"\(.name)\" -> \"\($n)\";" ), "}"' } case "${OUT}" in "") to_dot ;; *.dot) to_dot > "${OUT}" ;; *.svg|*.png) command -v dot >/dev/null || { echo "graphviz (dot) not found" >&2; exit 1; } to_dot | tred | dot -T"${OUT##*.}" -Gdpi=110 -o "${OUT}" ;; *) echo "usage: $0 [out.dot|out.svg|out.png]" >&2 exit 1 ;; esac