{ config, pkgs, ... }: let inherit (pkgs) lib runCommand curl; netColor = net: if net == "core" then "grey" else if net == "mgmt" then "brown" else if builtins.elem net [ "c3d2" "serv" "cluster" ] then "green" else if builtins.match "up.+" net != null || builtins.match "anon.+" 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 ); toCypher = { nodes, links, ... }: '' ${lib.concatMapStringsSep " " (name: let nodeAttrs = nodes.${name}; id = builtins.replaceStrings ["-"] ["_"] name; nodeType = nodeAttrs.type; in '' CREATE (${id}:`${nodeType}` {label:"${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.concatMapStringsSep " " ({ from, to, attrs }: let idFrom = builtins.replaceStrings ["-"] ["_"] from; idTo = builtins.replaceStrings ["-"] ["_"] to; in '' CREATE (${idFrom})-[:CONNECTED_TO {${lib.concatMapStringsSep ", " (attr: let value = attrs.${attr}; in if builtins.isString value then "${attr}:\"${value}\"" else "${attr}:${toString value}" ) (builtins.attrNames attrs)} }]->(${idTo}) '') (dedupLinks links)} ''; cypherGraph = args@{ name, ... }: runCommand "${name}.cypher" { src = builtins.toFile "${name}.cypher" ( toCypher args ); } '' sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' $src | tr -d '\n' > $out ''; in rec { # Layer 2 physical-cypher-graph = cypherGraph { name = "physical"; nodes = builtins.mapAttrs (_: { role, ... }: { type = { switch = "Switch"; server = "Server"; container = "Container"; ap = "AccessPoint"; client = "Client"; }.${role}; }) ( lib.filterAttrs (_: { links, ... }: links != {} ) config.site.hosts ) // builtins.mapAttrs (_: _: { type = "Other"; }) ( lib.filterAttrs (net: _: builtins.match "up.*" net != null ) config.site.net ); links = builtins.concatMap (hostName: map (link: { pair = [ hostName link ]; startLabel = ( #lib.optionalString (config.site.hosts.${hostName}.links ? ${link}) ( lib.concatStringsSep "," config.site.hosts.${hostName}.links.${link}.ports ); }) ( builtins.filter (link: config.site.hosts ? ${link} || builtins.match "up.*" link != null ) (builtins.attrNames config.site.hosts.${hostName}.links) ) ) (builtins.attrNames config.site.hosts); }; # Layer 3 logical-cypher-graph = let containers = lib.filterAttrs (_: { isRouter, role, ... }: role == "container" ) config.site.hosts; in cypherGraph { name = "logical"; nodes = builtins.foldl' (result: hostName: result // { "${hostName}".type = "Container"; } // builtins.mapAttrs (_: _: { type = "Container"; }) 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); }; import-network-graphs = runCommand "import-network-graphs" {} '' ${curl}/bin/curl -X POST -H 'Content-type: application/json' http://localhost:7474/db/data/transaction/commit -d "{\"statements\": [{\"statement\": \"$(cat ${physical-cypher-graph})\"}]}" ${curl}/bin/curl -X POST -H 'Content-type: application/json' http://localhost:7474/db/data/transaction/commit -d "{\"statements\": [{\"statement\": \"$(cat ${logical-cypher-graph})\"}]}" ''; }