diff --git a/nix/pkgs/default.nix b/nix/pkgs/default.nix index 9e9e70a..1573b90 100644 --- a/nix/pkgs/default.nix +++ b/nix/pkgs/default.nix @@ -25,6 +25,8 @@ let ) (builtins.attrNames config.site.hosts) ); + network-graphs = pkgs.callPackage ./network-graphs.nix { inherit config; }; + mkRootfs = hostName: self.nixosConfigurations.${hostName}.config.system.build.toplevel; @@ -63,6 +65,6 @@ let inherit pkgs; }; in -salt-pillars // rootfs-packages // vm-packages // device-templates // starlink // { +salt-pillars // rootfs-packages // vm-packages // device-templates // network-graphs // starlink // { inherit export-openwrt-models export-config dns-slaves; } diff --git a/nix/pkgs/network-graphs.nix b/nix/pkgs/network-graphs.nix new file mode 100644 index 000000000..c3c33c0 --- /dev/null +++ b/nix/pkgs/network-graphs.nix @@ -0,0 +1,162 @@ +{ lib, config, runCommand, graphviz, ... }: +let + netColor = net: + if net == "core" + then "grey" + else if net == "mgmt" + then "brown" + else if net == "c3d2" + then "orange" + else if net == "serv" + then "orange2" + else if net == "pub" + then "green" + else if builtins.match "up.+" net != null + then "red" + else if builtins.match "priv.+" net != null + then "blue" + else "black"; + + dedupLinks = links: + builtins.attrValues ( + builtins.foldl' (result: { pair, attrs ? {}, startLabel ? "" }: + let + peer1 = builtins.elemAt pair 0; + peer2 = builtins.elemAt pair 1; + sorted = + if peer1 < peer2 + then { from = peer1; to = peer2; } + else { from = peer2; to = peer1; }; + key = with sorted; "${from} ${to}"; + prevAttrs = + if result ? ${key} + then result.${key}.attrs + else {}; + newAttrs = attrs // ( + if peer1 < peer2 + then { taillabel = startLabel; } + else { headlabel = startLabel; } + ); + in result // { + "${key}" = { + inherit (sorted) from to; + attrs = prevAttrs // newAttrs; + }; + } + ) {} links + ); + + toDot = { nodes, links, ... }: '' + digraph { + graph [splines=ortho, nodesep=64] + node [] + edge [arrowhead=none, labelfontsize=8] + + ${lib.concatMapStrings (name: + let + nodeAttrs = nodes.${name}; + in '' + "${name}" [${lib.concatMapStringsSep ", " (attr: + let + value = nodeAttrs.${attr}; + in + if builtins.isString value + then "${attr}=\"${value}\"" + else "${attr}=${toString value}" + ) (builtins.attrNames nodeAttrs)}] + '') (builtins.attrNames nodes)} + + ${lib.concatMapStrings ({ from, to, attrs }: '' + "${from}" -> "${to}" [${lib.concatMapStringsSep ", " (attr: + let + value = attrs.${attr}; + in + if builtins.isString value + then "${attr}=\"${value}\"" + else "${attr}=${toString value}" + ) (builtins.attrNames attrs)}] + '') (dedupLinks links)} + } + ''; + renderGraph = args@{ name, engine, ... }: + runCommand "${name}.png" { + src = builtins.toFile "${name}.dot" ( + toDot args + ); + } '' + echo $src + ${graphviz}/bin/${engine} -Tpng $src > $out + ''; + +in rec { + # Layer 2 + physical-graph = renderGraph { + name = "physical"; + engine = "fdp"; + nodes = + builtins.mapAttrs (_: { role, ... }: { + shape = { + switch = "box"; + server = "house"; + container = "oval"; + ap = "doubleoctagon"; + client = "doublecircle"; + }.${role}; + }) ( + lib.filterAttrs (_: { links, ... }: + links != {} + ) config.site.hosts + ); + links = + builtins.concatMap (hostName: + map (link: { + pair = [ hostName link ]; + startLabel = lib.concatStringsSep "," + config.site.hosts.${hostName}.links.${link}.ports; + }) ( + builtins.filter (link: + config.site.hosts ? ${link} + ) (builtins.attrNames config.site.hosts.${hostName}.links) + ) + ) (builtins.attrNames config.site.hosts); + }; + + # Layer 3 + logical-graph = + let + containers = + lib.filterAttrs (_: { isRouter, role, ... }: + role == "container" + ) config.site.hosts; + in + renderGraph { + name = "logical"; + engine = "sfdp"; + nodes = + builtins.foldl' (result: hostName: + result // { + "${hostName}".shape = "box"; + } // builtins.mapAttrs (_: _: { + shape = "circle"; + }) containers.${hostName}.interfaces + ) {} (builtins.attrNames containers); + links = + builtins.concatMap (hostName: + map (net: { + pair = [ hostName net ]; + attrs.color = netColor net; + }) (builtins.attrNames containers.${hostName}.interfaces) + ) (builtins.attrNames containers); + }; + + network-graphs = runCommand "network-graphs" {} '' + DIR=$out/share/doc/zentralwerk + mkdir -p $DIR + ln -s ${physical-graph} $DIR/physical.png + ln -s ${logical-graph} $DIR/logical.png + + mkdir -p $out/nix-support + echo doc image $DIR/physical.png >> $out/nix-support/hydra-build-products + echo doc image $DIR/logical.png >> $out/nix-support/hydra-build-products + ''; +}