Ctrl K
ComunidadGuías…

lib/inspection.nix

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
{
  lib,
  config,
}: let
  containers = config.services.podman.containers;

  stackContainers = lib.filterAttrs (_: container: container.stack != null) containers;
  stackNames = lib.unique (lib.mapAttrsToList (_: container: container.stack) stackContainers);

  toStringValue = value:
    if builtins.isPath value
    then toString value
    else if builtins.isBool value || builtins.isInt value || builtins.isFloat value
    then toString value
    else if value == null
    then "null"
    else value;

  normalizeValue = value:
    if builtins.isPath value
    then toString value
    else if builtins.isAttrs value
    then lib.mapAttrs (_: normalizeValue) value
    else if builtins.isList value
    then map normalizeValue value
    else value;

  normalizeContainerDependency = dependency:
    if lib.hasPrefix "podman-" dependency && lib.hasSuffix ".service" dependency
    then lib.removeSuffix ".service" (lib.removePrefix "podman-" dependency)
    else dependency;

  normalizeExtraEnv = values: let
    normalized = normalizeValue values;
  in
    lib.filterAttrs (_: value: value != null && !(builtins.isAttrs value && value == {})) (
      lib.mapAttrs (
        _: value:
          if builtins.isAttrs value
          then lib.filterAttrs (_: field: field != null) value
          else value
      )
      normalized
    );

  mkContainer = name: container: let
    network = container.network or null;
    networkNames =
      if network == null
      then []
      else if builtins.isList network
      then network
      else [network];
  in
    normalizeValue {
      inherit name;
      stack = container.stack;
      image = container.image;
      networks = networkNames;
      port = container.port or null;
      volumes = container.volumes or [];
      volumeMap = container.volumeMap or {};
      environment = container.environment or {};
      extraEnv = normalizeExtraEnv (container.extraEnv or {});
      environmentFile = container.environmentFile or [];
      dependencies = {
        requires = lib.unique (map normalizeContainerDependency (container.dependsOnContainer or []) ++ (container.dependsOn or []));
        wants = lib.unique (map normalizeContainerDependency (container.wantsContainer or []) ++ (container.wants or []));
      };
      integrations = {
        traefik = {
          name = container.traefik.name or null;
          serviceHost = container.traefik.serviceHost or null;
          serviceUrl = container.traefik.serviceUrl or null;
          expose = container.expose or null;
        };
        dashboard = container.dashboard or {};
      };
    };

  stackData = lib.mapAttrsToList (name: container: mkContainer name container) stackContainers;

  networks = lib.unique (lib.concatMap (container: container.networks) stackData);

  edges =
    lib.concatMap (
      container:
        map (dependency: {
          from = container.name;
          to = dependency;
          kind = "requires";
        })
        container.dependencies.requires
        ++ map (dependency: {
          from = container.name;
          to = dependency;
          kind = "wants";
        })
        container.dependencies.wants
        ++ map (network: {
          from = container.name;
          to = network;
          kind = "network";
        })
        container.networks
        ++ lib.optional (container.integrations.traefik.name != null) {
          from = container.name;
          to = "route:${container.integrations.traefik.name}";
          kind = "route";
        }
    )
    stackData;

  data = {
    inherit networks edges;
    containers = stackData;
  };

  renderContainer = container: let
    field = label: value: let
      labelWidth = 12;
      padding = lib.concatStrings (lib.replicate (labelWidth - builtins.stringLength label) " ");
    in "  ${label}:${padding} ${value}";
    formatList = values:
      if values == []
      then "none"
      else lib.concatStringsSep ", " (map toStringValue values);
    volumeLines = map (volume: "    ${volume}") container.volumes;
    environmentLines =
      lib.mapAttrsToList (name: value: "    ${name}=${toStringValue value}") container.environment;
    extraEnvironmentLines =
      lib.mapAttrsToList (
        name: value:
          "    ${name}="
          + (
            if builtins.isAttrs value
            then builtins.toJSON value
            else toStringValue value
          )
      )
      container.extraEnv;
    dashboardParts = lib.filter (value: value != null) [
      (container.integrations.dashboard.name or null)
      (container.integrations.dashboard.category or null)
      (container.integrations.dashboard.icon or null)
    ];
    traefikValue =
      if container.integrations.traefik.name == null
      then "not configured"
      else "${toStringValue container.integrations.traefik.serviceUrl} (${
        if container.integrations.traefik.expose == true
        then "public"
        else "private"
      })";
    dashboardValue =
      if dashboardParts == []
      then "not configured"
      else lib.concatStringsSep " / " (map toStringValue dashboardParts);
  in ''
    ${container.name} (${container.stack})
    ${field "Image" (toStringValue container.image)}
    ${field "Networks" (formatList container.networks)}
    ${field "Port" (
      if container.port == null
      then "not configured"
      else toStringValue container.port
    )}
    ${field "Requires" (formatList container.dependencies.requires)}
    ${field "Wants" (formatList container.dependencies.wants)}
    ${field "Traefik" traefikValue}
    ${field "Dashboard" dashboardValue}
      Volumes:
    ${
      if volumeLines == []
      then "    none"
      else lib.concatStringsSep "\n" volumeLines
    }
      Environment (resolved):
    ${
      if environmentLines == []
      then "    none"
      else lib.concatStringsSep "\n" environmentLines
    }
      ExtraEnv sources:
    ${
      if extraEnvironmentLines == []
      then "    none"
      else lib.concatStringsSep "\n" extraEnvironmentLines
    }
      Environment files:
    ${
      if container.environmentFile == []
      then "    none"
      else lib.concatStringsSep "\n" (map (file: "    ${file}") container.environmentFile)
    }
  '';

  escapeDot = value:
    lib.replaceStrings ["\\" "\"" "\n" "\r"] ["\\\\" "\\\"" "\\n" ""] (toString value);

  renderDot = selectedContainers: let
    selectedNetworks = lib.unique (lib.concatMap (container: container.networks) selectedContainers);
    selectedNames = map (container: container.name) selectedContainers;
    routedContainers = lib.filter (container: container.integrations.traefik.name != null) selectedContainers;
    dependencies = lib.unique (lib.concatMap (container: container.dependencies.requires ++ container.dependencies.wants) selectedContainers);
    externalDependencies = lib.filter (dependency: !(builtins.elem dependency selectedNames)) dependencies;
    dependencyTarget = dependency:
      if builtins.elem dependency selectedNames
      then "container:${escapeDot dependency}"
      else "dependency:${escapeDot dependency}";
    nodes =
      map (container: ''"container:${escapeDot container.name}" [label="${escapeDot container.name}\n${escapeDot container.image}"];'') selectedContainers
      ++ map (network: ''"network:${escapeDot network}" [shape=ellipse,label="${escapeDot network}"];'') selectedNetworks
      ++ map (container: ''"route:${escapeDot container.integrations.traefik.name}" [shape=component,label="${escapeDot container.integrations.traefik.serviceHost}\n${escapeDot container.integrations.traefik.serviceUrl}"];'') routedContainers
      ++ map (dependency: ''"dependency:${escapeDot dependency}" [shape=box,label="${escapeDot dependency} (outside selection)"];'') externalDependencies;
    containerEdges =
      lib.concatMap (
        container:
          map (dependency: ''"container:${escapeDot container.name}" -> "${dependencyTarget dependency}" [label="requires"];'') container.dependencies.requires
          ++ map (dependency: ''"container:${escapeDot container.name}" -> "${dependencyTarget dependency}" [style=dashed,label="wants"];'') container.dependencies.wants
          ++ map (network: ''"container:${escapeDot container.name}" -> "network:${escapeDot network}" [style=dotted,label="network"];'') container.networks
          ++ lib.optional (container.integrations.traefik.name != null) ''"container:${escapeDot container.name}" -> "route:${escapeDot container.integrations.traefik.name}" [label="route"];''
      )
      selectedContainers;
  in ''
    digraph nps {
      rankdir=LR;
    ${lib.concatStringsSep "\n" (nodes ++ containerEdges)}
    }
  '';

  mkRender = selectedContainers: {
    summary = lib.concatMapStringsSep "\n" renderContainer selectedContainers;
    json = builtins.toJSON {
      networks = lib.unique (lib.concatMap (container: container.networks) selectedContainers);
      containers = selectedContainers;
      edges =
        lib.filter (
          edge:
            builtins.elem edge.from (map (container: container.name) selectedContainers)
        )
        edges;
    };
    dot = renderDot selectedContainers;
  };

  rendersByStack = lib.genAttrs stackNames (
    stack:
      mkRender (lib.filter (container: container.stack == stack) stackData)
  );
in {
  inherit data;
  render = (mkRender stackData) // {stacks = rendersByStack;};
}