From cc9881c1fbea3690fac5d2564e7e431e653d37f4 Mon Sep 17 00:00:00 2001 From: Dylan Wilson Date: Sat, 23 Nov 2024 12:46:13 -0500 Subject: [PATCH] podman: added volume, image, and build quadlets --- modules/services/podman-linux/activation.nix | 14 +- modules/services/podman-linux/builds.nix | 168 +++++++++++++++ modules/services/podman-linux/containers.nix | 75 +++++-- modules/services/podman-linux/default.nix | 11 +- modules/services/podman-linux/images.nix | 168 +++++++++++++++ modules/services/podman-linux/podman-lib.nix | 22 +- modules/services/podman-linux/services.nix | 4 +- modules/services/podman-linux/volumes.nix | 192 ++++++++++++++++++ .../podman-linux/build-expected.service | 30 +++ tests/modules/services/podman-linux/build.nix | 40 ++++ .../modules/services/podman-linux/default.nix | 3 + .../podman-linux/image-expected.service | 28 +++ tests/modules/services/podman-linux/image.nix | 18 ++ .../integration-build-expected.service | 30 +++ ...integration-container-bld-expected.service | 39 ++++ .../integration-container-expected.service | 7 +- .../integration-image-expected.service | 28 +++ .../integration-volume-expected.service | 33 +++ .../services/podman-linux/integration.nix | 47 ++++- .../podman-linux/volume-expected.service | 36 ++++ .../modules/services/podman-linux/volume.nix | 35 ++++ 21 files changed, 996 insertions(+), 32 deletions(-) create mode 100644 modules/services/podman-linux/builds.nix create mode 100644 modules/services/podman-linux/images.nix create mode 100644 modules/services/podman-linux/volumes.nix create mode 100644 tests/modules/services/podman-linux/build-expected.service create mode 100644 tests/modules/services/podman-linux/build.nix create mode 100644 tests/modules/services/podman-linux/image-expected.service create mode 100644 tests/modules/services/podman-linux/image.nix create mode 100644 tests/modules/services/podman-linux/integration-build-expected.service create mode 100644 tests/modules/services/podman-linux/integration-container-bld-expected.service create mode 100644 tests/modules/services/podman-linux/integration-image-expected.service create mode 100644 tests/modules/services/podman-linux/integration-volume-expected.service create mode 100644 tests/modules/services/podman-linux/volume-expected.service create mode 100644 tests/modules/services/podman-linux/volume.nix diff --git a/modules/services/podman-linux/activation.nix b/modules/services/podman-linux/activation.nix index 5791f19b0..27966c7f1 100644 --- a/modules/services/podman-linux/activation.nix +++ b/modules/services/podman-linux/activation.nix @@ -18,6 +18,7 @@ local manifestFile="${config.xdg.configHome}/podman/$2" local extraListCommands="''${3:-}" [[ $resourceType = "container" ]] && extraListCommands+=" -a" + [[ $resourceType = "volume" ]] && extraListCommands+=" --filter label=nix.home-manager.preserve=false" [ ! -f "$manifestFile" ] && VERBOSE_ENABLED && echo "Manifest does not exist: $manifestFile" && return 0 @@ -69,19 +70,20 @@ commands=() case "$resourceType" in "container") - commands+="${config.services.podman.package}/bin/podman $resourceType stop $resource" - commands+="${config.services.podman.package}/bin/podman $resourceType rm -f $resource" + commands+=("${config.services.podman.package}/bin/podman $resourceType stop $resource") + commands+=("${config.services.podman.package}/bin/podman $resourceType rm -f $resource") ;; - "network") - commands+="${config.services.podman.package}/bin/podman $resourceType rm $resource" + "image" | "network" | "volume") + commands+=("${config.services.podman.package}/bin/podman $resourceType rm $resource") ;; esac for command in "''${commands[@]}"; do command=$(echo $command | tr -d ';&|`') DRYRUN_ENABLED && echo "Would run: $command" && continue || true VERBOSE_ENABLED && echo "Running: $command" || true - if [[ "$(eval "$command")" != "$resource" ]]; then + if [[ "$(eval "$command")" != *"$resource" ]]; then echo -e "\tCommand failed: ''${command}" + [ "$resourceType" == "image" ] && resourceType="ancestor" usedByContainers=$(${config.services.podman.package}/bin/podman container ls -a --filter "$resourceType=$resource" --format "{{.Names}}") echo -e "\t$resource in use by containers: $usedByContainers" fi @@ -92,7 +94,7 @@ [[ "$@" == *"--verbose"* ]] && VERBOSE="true" [[ "$@" == *"--dry-run"* ]] && DRY_RUN="true" - for type in "container" "network"; do + for type in "container" "image" "network" "volume"; do cleanup "$type" "''${type}s.manifest" done ''; diff --git a/modules/services/podman-linux/builds.nix b/modules/services/podman-linux/builds.nix new file mode 100644 index 000000000..5abf86c3c --- /dev/null +++ b/modules/services/podman-linux/builds.nix @@ -0,0 +1,168 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.podman; + + podman-lib = import ./podman-lib.nix { inherit lib config; }; + + createQuadletSource = name: buildDef: + let + buildConfig = podman-lib.deepMerge { + Build = { + AuthFile = buildDef.authFile; + Environment = buildDef.environment; + File = buildDef.file; + ImageTag = [ "homemanager/${name}" ] ++ buildDef.tags; + Label = buildDef.labels // { "nix.home-manager.managed" = true; }; + PodmanArgs = buildDef.extraPodmanArgs; + SetWorkingDirectory = buildDef.workingDirectory; + TLSVerify = buildDef.tlsVerify; + }; + Install = { + WantedBy = optionals buildDef.autoStart [ + "default.target" + "multi-user.target" + ]; + }; + Service = { + TimeoutStartSec = 300; + RemainAfterExit = "yes"; + }; + Unit = { Description = buildDef.description; }; + } buildDef.extraConfig; + in '' + # Automatically generated by home-manager for podman build configuration + # DO NOT EDIT THIS FILE DIRECTLY + # + # ${name}.build + ${podman-lib.toQuadletIni buildConfig} + ''; + + toQuadletInternal = name: buildDef: { + assertions = podman-lib.buildConfigAsserts name buildDef.extraConfig; + serviceName = + "podman-${name}"; # quadlet service name: 'podman--build.service + source = podman-lib.removeBlankLines (createQuadletSource name buildDef); + resourceType = "build"; + }; +in let + buildDefinitionType = types.submodule ({ name, ... }: { + options = { + + autoStart = mkOption { + type = types.bool; + default = true; + description = + "Whether to start the build on boot. Requires user lingering."; + }; + + authFile = mkOption { + type = with types; nullOr path; + default = null; + description = "Path of the authentication file."; + }; + + description = mkOption { + type = with types; nullOr str; + default = "Service for build ${name}"; + defaultText = "Service for build \${name}"; + example = "My Build"; + description = "The description of the build."; + }; + + environment = mkOption { + type = podman-lib.primitiveAttrs; + default = { }; + example = literalExpression '' + { + VAR1 = "0:100"; + VAR2 = true; + VAR3 = 5; + } + ''; + description = "Environment variables to set in the build."; + }; + + extraConfig = mkOption { + type = podman-lib.extraConfigType; + default = { }; + example = literalExpression '' + { + Build = { + Arch = "aarch64"; + }; + Service = { + TimeoutStartSec = 15; + }; + } + ''; + description = "INI sections and values to populate the Build Quadlet."; + }; + + extraPodmanArgs = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--retries 5" ]; + description = "Extra arguments to pass to the podman build command."; + }; + + file = mkOption { + type = types.str; + example = literalExpression '' + `"xdg.configFile."containerfiles/my-img/Containerfile"` + or + `"https://github.com/.../my-img/Containerfile"` + ''; + description = + "Path to a Containerfile which contains instructions to build the image."; + }; + + tags = mkOption { + type = with types; listOf str; + default = [ ]; + description = '' + Name associated with the build. + First tag will always be "homemanager/". + ''; + }; + + labels = mkOption { + type = with types; attrsOf str; + default = { }; + example = { + app = "myapp"; + some-label = "somelabel"; + }; + description = "The labels to apply to the build."; + }; + + tlsVerify = mkOption { + type = types.bool; + default = true; + description = + "Require HTTPS and verification of certificates when contacting registries."; + }; + + workingDirectory = mkOption { + type = with types; nullOr path; + default = null; + description = "WorkingDirectory of the systemd unit file."; + }; + }; + }); +in { + options.services.podman.builds = mkOption { + type = types.attrsOf buildDefinitionType; + default = { }; + description = "Defines Podman build quadlet configurations."; + }; + + config = let buildQuadlets = mapAttrsToList toQuadletInternal cfg.builds; + in mkIf cfg.enable { + services.podman.internal.quadletDefinitions = buildQuadlets; + assertions = flatten (map (build: build.assertions) buildQuadlets); + + xdg.configFile."/podman/images.manifest".text = + podman-lib.generateManifestText buildQuadlets; + }; +} diff --git a/modules/services/podman-linux/containers.nix b/modules/services/podman-linux/containers.nix index 41ab29130..839c2f3f8 100644 --- a/modules/services/podman-linux/containers.nix +++ b/modules/services/podman-linux/containers.nix @@ -9,19 +9,66 @@ let createQuadletSource = name: containerDef: let - mapHmNetworks = network: - if builtins.hasAttr network cfg.networks then - "podman-${network}-network.service" - else - null; + makeServiceName = name: type: + let + typeName = (if lib.strings.hasSuffix "s" type then + (lib.strings.substring 0 (builtins.stringLength type - 1) type) + else + type); + in "podman-${name}-${typeName}.service"; + + extractVolumeName = resource: builtins.head (builtins.split ":" resource); + + standardizeResource = res: + let + lst = if lib.isList res then + res + else if res != null then + [ res ] + else + [ ]; + in map extractVolumeName lst; + + filterManagedResources = resource: definedResources: + builtins.filter (x: builtins.elem x definedResources) + (standardizeResource resource); + + getDefinedResourceNames = type: + let + typeName = + if lib.strings.hasSuffix "s" type then type else "${type}s"; + in builtins.attrNames cfg."${typeName}"; + + findDefinedImageType = if (builtins.elem containerDef.image + (getDefinedResourceNames "builds")) then + "builds" + else + "images"; + + getManagedResourceNames = type: + let + resourceType = if type == "image" then findDefinedImageType else type; + in (filterManagedResources (builtins.getAttr type containerDef) + (getDefinedResourceNames resourceType)); + + getServiceNames = type: + let + resourceType = if type == "image" then findDefinedImageType else type; + in map (name: makeServiceName name resourceType) + (getManagedResourceNames type); finalConfig = let - managedNetworks = if lib.isList containerDef.network then - map mapHmNetworks containerDef.network - else if containerDef.network != null then - map mapHmNetworks [ containerDef.network ] - else - [ ]; + managedServices = builtins.concatLists + (map (type: getServiceNames type) [ "image" "network" "volumes" ]); + + getActualImage = + if (builtins.hasAttr containerDef.image cfg.images) then + cfg.images."${containerDef.image}".image + else if (builtins.hasAttr containerDef.image cfg.builds) then + "localhost/homemanager/${containerDef.image}" + else + containerDef.image; + in (podman-lib.deepMerge { Container = { AddCapability = containerDef.addCapabilities; @@ -34,7 +81,7 @@ let EnvironmentFile = containerDef.environmentFile; Exec = containerDef.exec; Group = containerDef.group; - Image = containerDef.image; + Image = getActualImage; IP = containerDef.ip4; IP6 = containerDef.ip6; Label = @@ -66,8 +113,8 @@ let TimeoutStopSec = 30; }; Unit = { - After = [ "network.target" ] ++ managedNetworks; - Requires = managedNetworks; + After = [ "network.target" ] ++ managedServices; + Requires = managedServices; Description = (if (builtins.isString containerDef.description) then containerDef.description else diff --git a/modules/services/podman-linux/default.nix b/modules/services/podman-linux/default.nix index 459762d46..a08541a6d 100644 --- a/modules/services/podman-linux/default.nix +++ b/modules/services/podman-linux/default.nix @@ -5,8 +5,15 @@ let in { meta.maintainers = with lib.hm.maintainers; [ bamhm182 n-hass ]; - imports = - [ ./containers.nix ./install-quadlet.nix ./networks.nix ./services.nix ]; + imports = [ + ./builds.nix + ./containers.nix + ./images.nix + ./install-quadlet.nix + ./networks.nix + ./services.nix + ./volumes.nix + ]; options.services.podman = { enable = lib.mkEnableOption "Podman, a daemonless container engine"; diff --git a/modules/services/podman-linux/images.nix b/modules/services/podman-linux/images.nix new file mode 100644 index 000000000..354293c63 --- /dev/null +++ b/modules/services/podman-linux/images.nix @@ -0,0 +1,168 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.podman; + + podman-lib = import ./podman-lib.nix { inherit lib config; }; + + awaitPodmanUnshare = pkgs.writeShellScript "await-podman-unshare" '' + until ${cfg.package}/bin/podman unshare ${pkgs.coreutils}/bin/true; do + ${pkgs.coreutils}/bin/sleep 1 + done + ''; + + createQuadletSource = name: imageDef: + let + credsString = + (if imageDef.username != null then imageDef.username else "") + + (if imageDef.password != null then ":${imageDef.password}" else ""); + + imageConfig = podman-lib.deepMerge { + Image = { + AuthFile = imageDef.authFile; + CertDir = imageDef.certDir; + Creds = (if credsString != "" then credsString else null); + DecryptionKey = imageDef.decryptionKeyFile; + Image = imageDef.image; + ImageTag = imageDef.tag; + PodmanArgs = imageDef.extraPodmanArgs; + TLSVerify = imageDef.tlsVerify; + }; + Install = { + WantedBy = optionals imageDef.autoStart [ + "default.target" + "multi-user.target" + ]; + }; + Service = { + ExecStartPre = [ "${awaitPodmanUnshare}" ]; + TimeoutStartSec = 300; + RemainAfterExit = "yes"; + }; + Unit = { Description = imageDef.description; }; + } imageDef.extraConfig; + in '' + # Automatically generated by home-manager for podman image configuration + # DO NOT EDIT THIS FILE DIRECTLY + # + # ${name}.image + ${podman-lib.toQuadletIni imageConfig} + ''; + + toQuadletInternal = name: imageDef: { + assertions = podman-lib.buildConfigAsserts name imageDef.extraConfig; + serviceName = + "podman-${name}"; # quadlet service name: 'podman--image.service + source = podman-lib.removeBlankLines (createQuadletSource name imageDef); + resourceType = "image"; + }; +in let + imageDefinitionType = types.submodule ({ name, ... }: { + options = { + autoStart = mkOption { + type = types.bool; + default = true; + description = + "Whether to pull the image on boot. Requires user lingering."; + }; + + authFile = mkOption { + type = with types; nullOr path; + default = null; + description = + "Path of the authentication file used to connect to registry."; + }; + + certDir = mkOption { + type = with types; nullOr path; + default = null; + description = + "Path of certificates (*.{crt,cert,key}) used to connect to registry."; + }; + + decryptionKeyFile = mkOption { + type = with types; nullOr path; + default = null; + description = "Path to key used for decrpytion of images."; + }; + + description = mkOption { + type = with types; nullOr str; + default = "Service for image ${name}"; + defaultText = "Service for image \${name}"; + example = "My Image"; + description = "The description of the image."; + }; + + extraConfig = mkOption { + type = podman-lib.extraConfigType; + default = { }; + example = literalExpression '' + { + Image = { + ContainersConfModule = "/etc/nvd.conf"; + }; + } + ''; + description = "INI sections and values to populate the Image Quadlet."; + }; + + extraPodmanArgs = mkOption { + type = with types; listOf str; + default = [ ]; + example = [ "--os=linux" ]; + description = + "Extra arguments to pass to the podman image pull command."; + }; + + image = mkOption { + type = types.str; + example = "quay.io/centos/centos:latest"; + description = "Image to pull."; + }; + + password = mkOption { + type = with types; nullOr str; + default = null; + example = "P@ssw0rd"; + description = + "Password used to connect to registry. (Will be visible in nix store)"; + }; + + tag = mkOption { + type = with types; nullOr str; + default = null; + example = "quay.io/centos/centos:latest"; + description = + "FQIN of referenced Image when source is a file or directory archive."; + }; + + tlsVerify = mkOption { + type = types.bool; + default = true; + description = + "Require HTTPS and verification of certificates when contacting registries."; + }; + + username = mkOption { + type = with types; nullOr str; + default = null; + example = "bob"; + description = "Username used to connect to registry."; + }; + + }; + }); +in { + options.services.podman.images = mkOption { + type = types.attrsOf imageDefinitionType; + default = { }; + description = "Defines Podman image quadlet configurations."; + }; + + config = let imageQuadlets = mapAttrsToList toQuadletInternal cfg.images; + in mkIf cfg.enable { + services.podman.internal.quadletDefinitions = imageQuadlets; + assertions = flatten (map (image: image.assertions) imageQuadlets); + }; +} diff --git a/modules/services/podman-linux/podman-lib.nix b/modules/services/podman-linux/podman-lib.nix index 1acc930fc..0b44c4ea0 100644 --- a/modules/services/podman-linux/podman-lib.nix +++ b/modules/services/podman-linux/podman-lib.nix @@ -63,8 +63,10 @@ in { buildConfigAsserts = quadletName: extraConfig: let configRules = { + Build = { ImageTag = (with types; listOf str); }; Container = { ContainerName = types.enum [ quadletName ]; }; Network = { NetworkName = types.enum [ quadletName ]; }; + Volume = { VolumeName = types.enum [ quadletName ]; }; }; # Function to build assertions for a specific section and its attributes. @@ -83,8 +85,23 @@ in { else [ ]; + checkImageTag = extraConfig: + let + imageTags = (extraConfig.Build or { }).ImageTag or [ ]; + containsRequiredTag = + builtins.elem "homemanager/${quadletName}" imageTags; + imageTagsStr = concatMapStringsSep ''" "'' toString imageTags; + in [{ + assertion = imageTags == [ ] || containsRequiredTag; + message = '' + In '${quadletName}' config. Build.ImageTag: '[ "${imageTagsStr}" ]' does not contain 'homemanager/${quadletName}'.''; + }]; + # Flatten assertions from all sections in `extraConfig`. - in flatten (mapAttrsToList buildSectionAsserts extraConfig); + in flatten (concatLists [ + (mapAttrsToList buildSectionAsserts extraConfig) + (checkImageTag extraConfig) + ]); extraConfigType = with types; attrsOf (attrsOf (oneOf [ primitiveAttrs primitiveList primitive ])); @@ -107,8 +124,11 @@ in { # specific logic for writing the unit name goes here. It should be # identical to what `podman ls` shows in { + "build" = strippedName; "container" = strippedName; + "image" = strippedName; "network" = strippedName; + "volume" = strippedName; }."${quadlet.resourceType}"; in if allQuadletsSameType then '' ${concatStringsSep "\n" diff --git a/modules/services/podman-linux/services.nix b/modules/services/podman-linux/services.nix index 87ff83ad8..c4c69da66 100644 --- a/modules/services/podman-linux/services.nix +++ b/modules/services/podman-linux/services.nix @@ -42,8 +42,8 @@ in { "${config.home.homeDirectory}/.nix-profile/bin" ] }"; - ExecStart = "${pkgs.podman}/bin/podman auto-update"; - ExecStartPost = "${pkgs.podman}/bin/podman image prune -f"; + ExecStart = "${cfg.package}/bin/podman auto-update"; + ExecStartPost = "${cfg.package}/bin/podman image prune -f"; TimeoutStartSec = "300s"; TimeoutStopSec = "10s"; }; diff --git a/modules/services/podman-linux/volumes.nix b/modules/services/podman-linux/volumes.nix new file mode 100644 index 000000000..312ba6390 --- /dev/null +++ b/modules/services/podman-linux/volumes.nix @@ -0,0 +1,192 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.podman; + + podman-lib = import ./podman-lib.nix { inherit lib config; }; + + awaitPodmanUnshare = pkgs.writeShellScript "await-podman-unshare" '' + until ${cfg.package}/bin/podman unshare ${pkgs.coreutils}/bin/true; do + ${pkgs.coreutils}/bin/sleep 1 + done + ''; + + createQuadletSource = name: volumeDef: + let + volumeConfig = podman-lib.deepMerge { + Install = { + WantedBy = optionals volumeDef.autoStart [ + "default.target" + "multi-user.target" + ]; + }; + Service = { + Environment = { + PATH = (builtins.concatStringsSep ":" [ + "${podman-lib.newuidmapPaths}" + "${makeBinPath [ pkgs.su pkgs.coreutils ]}" + ]); + }; + ExecStartPre = [ "${awaitPodmanUnshare}" ]; + TimeoutStartSec = 15; + RemainAfterExit = "yes"; + }; + Unit = { Description = volumeDef.description; }; + Volume = { + Copy = volumeDef.copy; + Device = volumeDef.device; + Driver = volumeDef.driver; + Group = volumeDef.group; + Image = volumeDef.image; + Label = volumeDef.labels // { + "nix.home-manager.managed" = true; + "nix.home-manager.preserve" = volumeDef.preserve; + }; + PodmanArgs = volumeDef.extraPodmanArgs; + Type = volumeDef.type; + User = volumeDef.user; + VolumeName = name; + }; + } volumeDef.extraConfig; + in '' + # Automatically generated by home-manager for podman volume configuration + # DO NOT EDIT THIS FILE DIRECTLY + # + # ${name}.volume + ${podman-lib.toQuadletIni volumeConfig} + ''; + + toQuadletInternal = name: volumeDef: { + assertions = podman-lib.buildConfigAsserts name volumeDef.extraConfig; + serviceName = + "podman-${name}"; # quadlet service name: 'podman--volume.service' + source = podman-lib.removeBlankLines (createQuadletSource name volumeDef); + resourceType = "volume"; + }; + +in let + volumeDefinitionType = types.submodule ({ name, ... }: { + options = { + + autoStart = mkOption { + type = types.bool; + default = true; + description = "Whether to create the volume on boot."; + }; + + copy = mkOption { + type = types.bool; + default = true; + description = + "Copy content of the image located at the mountpoint of the volume on first run."; + }; + + description = mkOption { + type = with types; nullOr str; + default = "Service for volume ${name}"; + defaultText = "Service for volume \${name}"; + example = "My Volume"; + description = "The description of the volume."; + }; + + device = mkOption { + type = with types; nullOr str; + default = null; + example = "tmpfs"; + description = "The path of a device which is mounted for the volume."; + }; + + driver = mkOption { + type = with types; nullOr str; + default = null; + example = "image"; + description = "The volume driver to use."; + }; + + extraConfig = mkOption { + type = podman-lib.extraConfigType; + default = { }; + example = literalExpression '' + { + Volume = { + ContainerConfModule = "/etc/nvd.conf"; + }; + } + ''; + description = "INI sections and values to populate the Volume Quadlet."; + }; + + extraPodmanArgs = mkOption { + type = with types; listOf str; + default = [ ]; + example = [ "--opt copy" ]; + description = + "Extra arguments to pass to the podman volume create command."; + }; + + group = mkOption { + type = with types; nullOr (either int str); + default = null; + description = "The group ID owning the volume inside the container."; + }; + + image = mkOption { + type = with types; nullOr str; + default = null; + example = "quay.io/centos/centos:latest"; + description = + "Specifies the image the volume is based on when Driver is set to the image."; + }; + + labels = mkOption { + type = with types; attrsOf str; + default = { }; + example = { + app = "myapp"; + some-label = "somelabel"; + }; + description = "The labels to apply to the volume."; + }; + + preserve = mkOption { + type = types.bool; + default = true; + description = '' + Whether the volume should be preserved if it is removed from the configuration. + Setting this to false will cause the volume to be deleted if the volume is removed from the configuration + ''; + }; + + type = mkOption { + type = with types; nullOr str; + default = null; + example = "tmpfs"; + description = + "Filesystem type of Device. (used as -t in mount commands)"; + }; + + user = mkOption { + type = with types; nullOr (either int str); + default = null; + description = "The user ID owning the volume inside the container."; + }; + }; + }); +in { + options.services.podman.volumes = mkOption { + type = types.attrsOf volumeDefinitionType; + default = { }; + description = "Defines Podman volume quadlet configurations."; + }; + + config = let volumeQuadlets = mapAttrsToList toQuadletInternal cfg.volumes; + in mkIf cfg.enable { + services.podman.internal.quadletDefinitions = volumeQuadlets; + assertions = flatten (map (volume: volume.assertions) volumeQuadlets); + + xdg.configFile."/podman/volumes.manifest".text = + podman-lib.generateManifestText volumeQuadlets; + }; +} diff --git a/tests/modules/services/podman-linux/build-expected.service b/tests/modules/services/podman-linux/build-expected.service new file mode 100644 index 000000000..ab4064c22 --- /dev/null +++ b/tests/modules/services/podman-linux/build-expected.service @@ -0,0 +1,30 @@ +# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator +# +# Automatically generated by home-manager for podman build configuration +# DO NOT EDIT THIS FILE DIRECTLY +# +# my-bld.build +[X-Build] +Environment= +File=/nix/store/00000000000000000000000000000000-Containerfile +ImageTag=homemanager/my-bld +Label=nix.home-manager.managed=true +TLSVerify=true + +[Install] +WantedBy=default.target +WantedBy=multi-user.target + +[Service] +RemainAfterExit=yes +TimeoutStartSec=300 +ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman build --tls-verify --tag homemanager/my-bld --label nix.home-manager.managed=true --file /nix/store/00000000000000000000000000000000-Containerfile +SyslogIdentifier=%N +Type=oneshot + +[Unit] +Wants=podman-user-wait-network-online.service +After=podman-user-wait-network-online.service +Description=Service for build my-bld +RequiresMountsFor=%t/containers +SourcePath=/nix/store/00000000000000000000000000000000-home-build-podman-my-bld/quadlets/podman-my-bld.build diff --git a/tests/modules/services/podman-linux/build.nix b/tests/modules/services/podman-linux/build.nix new file mode 100644 index 000000000..78457edbf --- /dev/null +++ b/tests/modules/services/podman-linux/build.nix @@ -0,0 +1,40 @@ +{ pkgs, ... }: +let + containerFile = pkgs.writeTextFile { + name = "Containerfile"; + text = '' + FROM docker.io/alpine:latest + ''; + }; + +in { + services.podman = { + enable = true; + builds = { + "my-bld" = { file = "${containerFile}"; }; + + "my-bld-2" = { + file = "https://www.github.com/././Containerfile"; + extraConfig = { + Build.ImageTag = [ "locahost/somethingelse" "localhost/anothertag" ]; + }; + }; + }; + }; + + test.asserts.assertions.expected = [ + '' + In 'my-bld-2' config. Build.ImageTag: '[ "locahost/somethingelse" "localhost/anothertag" ]' does not contain 'homemanager/my-bld-2'.'' + ]; + + nmt.script = '' + configPath=home-files/.config/systemd/user + buildFile=$configPath/podman-my-bld-build.service + + assertFileExists $buildFile + + buildFile=$(normalizeStorePaths $buildFile) + + assertFileContent $buildFile ${./build-expected.service} + ''; +} diff --git a/tests/modules/services/podman-linux/default.nix b/tests/modules/services/podman-linux/default.nix index a5ba9467e..c7f04bdaf 100644 --- a/tests/modules/services/podman-linux/default.nix +++ b/tests/modules/services/podman-linux/default.nix @@ -1,7 +1,10 @@ { podman-configuration = ./configuration.nix; podman-container = ./container.nix; + podman-build = ./build.nix; + podman-image = ./image.nix; podman-integration = ./integration.nix; podman-manifest = ./manifest.nix; podman-network = ./network.nix; + podman-volume = ./volume.nix; } diff --git a/tests/modules/services/podman-linux/image-expected.service b/tests/modules/services/podman-linux/image-expected.service new file mode 100644 index 000000000..2a2e688e3 --- /dev/null +++ b/tests/modules/services/podman-linux/image-expected.service @@ -0,0 +1,28 @@ +# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator +# +# Automatically generated by home-manager for podman image configuration +# DO NOT EDIT THIS FILE DIRECTLY +# +# my-img.image +[X-Image] +Image=docker.io/alpine:latest +TLSVerify=true + +[Install] +WantedBy=default.target +WantedBy=multi-user.target + +[Service] +ExecStartPre=/nix/store/00000000000000000000000000000000-await-podman-unshare +RemainAfterExit=yes +TimeoutStartSec=300 +ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman image pull --tls-verify docker.io/alpine:latest +SyslogIdentifier=%N +Type=oneshot + +[Unit] +Wants=podman-user-wait-network-online.service +After=podman-user-wait-network-online.service +Description=Service for image my-img +SourcePath=/nix/store/00000000000000000000000000000000-home-image-podman-my-img/quadlets/podman-my-img.image +RequiresMountsFor=%t/containers diff --git a/tests/modules/services/podman-linux/image.nix b/tests/modules/services/podman-linux/image.nix new file mode 100644 index 000000000..105c7d552 --- /dev/null +++ b/tests/modules/services/podman-linux/image.nix @@ -0,0 +1,18 @@ +{ ... }: + +{ + services.podman = { + enable = true; + images = { "my-img" = { image = "docker.io/alpine:latest"; }; }; + }; + + nmt.script = '' + configPath=home-files/.config/systemd/user + imageFile=$configPath/podman-my-img-image.service + assertFileExists $imageFile + + imageFile=$(normalizeStorePaths $imageFile) + + assertFileContent $imageFile ${./image-expected.service} + ''; +} diff --git a/tests/modules/services/podman-linux/integration-build-expected.service b/tests/modules/services/podman-linux/integration-build-expected.service new file mode 100644 index 000000000..ab4064c22 --- /dev/null +++ b/tests/modules/services/podman-linux/integration-build-expected.service @@ -0,0 +1,30 @@ +# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator +# +# Automatically generated by home-manager for podman build configuration +# DO NOT EDIT THIS FILE DIRECTLY +# +# my-bld.build +[X-Build] +Environment= +File=/nix/store/00000000000000000000000000000000-Containerfile +ImageTag=homemanager/my-bld +Label=nix.home-manager.managed=true +TLSVerify=true + +[Install] +WantedBy=default.target +WantedBy=multi-user.target + +[Service] +RemainAfterExit=yes +TimeoutStartSec=300 +ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman build --tls-verify --tag homemanager/my-bld --label nix.home-manager.managed=true --file /nix/store/00000000000000000000000000000000-Containerfile +SyslogIdentifier=%N +Type=oneshot + +[Unit] +Wants=podman-user-wait-network-online.service +After=podman-user-wait-network-online.service +Description=Service for build my-bld +RequiresMountsFor=%t/containers +SourcePath=/nix/store/00000000000000000000000000000000-home-build-podman-my-bld/quadlets/podman-my-bld.build diff --git a/tests/modules/services/podman-linux/integration-container-bld-expected.service b/tests/modules/services/podman-linux/integration-container-bld-expected.service new file mode 100644 index 000000000..a6bfd90d8 --- /dev/null +++ b/tests/modules/services/podman-linux/integration-container-bld-expected.service @@ -0,0 +1,39 @@ +# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator +# +# Automatically generated by home-manager podman container configuration +# DO NOT EDIT THIS FILE DIRECTLY +# +# my-container-bld.container +[X-Container] +ContainerName=my-container-bld +Environment= +Image=localhost/homemanager/my-bld +Label=nix.home-manager.managed=true + +[Install] +WantedBy=default.target +WantedBy=multi-user.target + +[Service] +Environment=PATH=/run/wrappers/bin:/run/current-system/sw/bin:/home/hm-user/.nix-profile/bin +Restart=always +TimeoutStopSec=30 +Environment=PODMAN_SYSTEMD_UNIT=%n +KillMode=mixed +ExecStop=/nix/store/00000000000000000000000000000000-podman/bin/podman rm -v -f -i --cidfile=%t/%N.cid +ExecStopPost=-/nix/store/00000000000000000000000000000000-podman/bin/podman rm -v -f -i --cidfile=%t/%N.cid +Delegate=yes +Type=notify +NotifyAccess=all +SyslogIdentifier=%N +ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --name my-container-bld --cidfile=%t/%N.cid --replace --rm --cgroups=split --sdnotify=conmon -d --label nix.home-manager.managed=true localhost/homemanager/my-bld + +[Unit] +Wants=podman-user-wait-network-online.service +After=podman-user-wait-network-online.service +After=network.target +After=podman-my-bld-build.service +Description=Service for container my-container-bld +Requires=podman-my-bld-build.service +SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container-bld/quadlets/podman-my-container-bld.container +RequiresMountsFor=%t/containers diff --git a/tests/modules/services/podman-linux/integration-container-expected.service b/tests/modules/services/podman-linux/integration-container-expected.service index 528d9c18e..9f4e224f3 100644 --- a/tests/modules/services/podman-linux/integration-container-expected.service +++ b/tests/modules/services/podman-linux/integration-container-expected.service @@ -11,6 +11,7 @@ Image=docker.io/alpine:latest Label=nix.home-manager.managed=true Network=my-net Network=externalnet +Volume=my-vol:/data [Install] WantedBy=default.target @@ -28,14 +29,18 @@ Delegate=yes Type=notify NotifyAccess=all SyslogIdentifier=%N -ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --name my-container --cidfile=%t/%N.cid --replace --rm --cgroups=split --network my-net --network externalnet --sdnotify=conmon -d --label nix.home-manager.managed=true docker.io/alpine:latest +ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman run --name my-container --cidfile=%t/%N.cid --replace --rm --cgroups=split --network my-net --network externalnet --sdnotify=conmon -d -v my-vol:/data --label nix.home-manager.managed=true docker.io/alpine:latest [Unit] Wants=podman-user-wait-network-online.service After=podman-user-wait-network-online.service After=network.target +After=podman-my-img-image.service After=podman-my-net-network.service +After=podman-my-vol-volume.service Description=Service for container my-container +Requires=podman-my-img-image.service Requires=podman-my-net-network.service +Requires=podman-my-vol-volume.service SourcePath=/nix/store/00000000000000000000000000000000-home-container-podman-my-container/quadlets/podman-my-container.container RequiresMountsFor=%t/containers diff --git a/tests/modules/services/podman-linux/integration-image-expected.service b/tests/modules/services/podman-linux/integration-image-expected.service new file mode 100644 index 000000000..2a2e688e3 --- /dev/null +++ b/tests/modules/services/podman-linux/integration-image-expected.service @@ -0,0 +1,28 @@ +# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator +# +# Automatically generated by home-manager for podman image configuration +# DO NOT EDIT THIS FILE DIRECTLY +# +# my-img.image +[X-Image] +Image=docker.io/alpine:latest +TLSVerify=true + +[Install] +WantedBy=default.target +WantedBy=multi-user.target + +[Service] +ExecStartPre=/nix/store/00000000000000000000000000000000-await-podman-unshare +RemainAfterExit=yes +TimeoutStartSec=300 +ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman image pull --tls-verify docker.io/alpine:latest +SyslogIdentifier=%N +Type=oneshot + +[Unit] +Wants=podman-user-wait-network-online.service +After=podman-user-wait-network-online.service +Description=Service for image my-img +SourcePath=/nix/store/00000000000000000000000000000000-home-image-podman-my-img/quadlets/podman-my-img.image +RequiresMountsFor=%t/containers diff --git a/tests/modules/services/podman-linux/integration-volume-expected.service b/tests/modules/services/podman-linux/integration-volume-expected.service new file mode 100644 index 000000000..b5e6a6010 --- /dev/null +++ b/tests/modules/services/podman-linux/integration-volume-expected.service @@ -0,0 +1,33 @@ +# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator +# +# Automatically generated by home-manager for podman volume configuration +# DO NOT EDIT THIS FILE DIRECTLY +# +# my-vol.volume +[Install] +WantedBy=default.target +WantedBy=multi-user.target + +[Service] +Environment=PATH=/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin:/nix/store/00000000000000000000000000000000-shadow/bin:/nix/store/00000000000000000000000000000000-coreutils/bin +ExecStartPre=/nix/store/00000000000000000000000000000000-await-podman-unshare +RemainAfterExit=yes +TimeoutStartSec=15 +ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman volume create --ignore --opt copy --opt device=tmpfs --opt type=tmpfs --label nix.home-manager.managed=true --label nix.home-manager.preserve=false my-vol +SyslogIdentifier=%N +Type=oneshot + +[Unit] +Wants=podman-user-wait-network-online.service +After=podman-user-wait-network-online.service +Description=Service for volume my-vol +SourcePath=/nix/store/00000000000000000000000000000000-home-volume-podman-my-vol/quadlets/podman-my-vol.volume +RequiresMountsFor=%t/containers + +[X-Volume] +Copy=true +Device=tmpfs +Label=nix.home-manager.managed=true +Label=nix.home-manager.preserve=false +Type=tmpfs +VolumeName=my-vol diff --git a/tests/modules/services/podman-linux/integration.nix b/tests/modules/services/podman-linux/integration.nix index de213092d..e9b4abb50 100644 --- a/tests/modules/services/podman-linux/integration.nix +++ b/tests/modules/services/podman-linux/integration.nix @@ -1,29 +1,64 @@ -{ ... }: - -{ +{ pkgs, ... }: +let + containerFile = pkgs.writeTextFile { + name = "Containerfile"; + text = '' + FROM docker.io/alpine:latest + ''; + }; +in { services.podman = { enable = true; - containers."my-container" = { - image = "docker.io/alpine:latest"; - network = [ "my-net" "externalnet" ]; + builds."my-bld" = { file = "${containerFile}"; }; + containers = { + "my-container" = { + image = "my-img"; + network = [ "my-net" "externalnet" ]; + volumes = [ "my-vol:/data" ]; + }; + "my-container-bld" = { image = "my-bld"; }; }; + images."my-img" = { image = "docker.io/alpine:latest"; }; networks."my-net" = { gateway = "192.168.123.1"; subnet = "192.168.123.0/24"; }; + volumes."my-vol" = { + device = "tmpfs"; + preserve = false; + type = "tmpfs"; + }; }; nmt.script = '' configPath=home-files/.config/systemd/user + buildFile=$configPath/podman-my-bld-build.service containerFile=$configPath/podman-my-container.service + containerBldFile=$configPath/podman-my-container-bld.service + imageFile=$configPath/podman-my-img-image.service networkFile=$configPath/podman-my-net-network.service + volumeFile=$configPath/podman-my-vol-volume.service + assertFileExists $buildFile assertFileExists $containerFile + assertFileExists $containerBldFile + assertFileExists $imageFile assertFileExists $networkFile + assertFileExists $volumeFile + buildFile=$(normalizeStorePaths $buildFile) containerFile=$(normalizeStorePaths $containerFile) + containerBldFile=$(normalizeStorePaths $containerBldFile) + imageFile=$(normalizeStorePaths $imageFile) networkFile=$(normalizeStorePaths $networkFile) + volumeFile=$(normalizeStorePaths $volumeFile) + assertFileContent $buildFile ${./integration-build-expected.service} assertFileContent $containerFile ${./integration-container-expected.service} + assertFileContent $containerBldFile ${ + ./integration-container-bld-expected.service + } + assertFileContent $imageFile ${./integration-image-expected.service} assertFileContent $networkFile ${./integration-network-expected.service} + assertFileContent $volumeFile ${./integration-volume-expected.service} ''; } diff --git a/tests/modules/services/podman-linux/volume-expected.service b/tests/modules/services/podman-linux/volume-expected.service new file mode 100644 index 000000000..69d396e71 --- /dev/null +++ b/tests/modules/services/podman-linux/volume-expected.service @@ -0,0 +1,36 @@ +# Automatically generated by /nix/store/00000000000000000000000000000000-podman/lib/systemd/user-generators/podman-user-generator +# +# Automatically generated by home-manager for podman volume configuration +# DO NOT EDIT THIS FILE DIRECTLY +# +# my-vol.volume +[Install] +WantedBy=default.target +WantedBy=multi-user.target + +[Service] +Environment=PATH=/run/wrappers/bin:/usr/bin:/bin:/usr/sbin:/sbin:/nix/store/00000000000000000000000000000000-shadow/bin:/nix/store/00000000000000000000000000000000-coreutils/bin +ExecStartPre=/nix/store/00000000000000000000000000000000-await-podman-unshare +RemainAfterExit=yes +TimeoutStartSec=15 +ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman volume create --ignore --opt copy --opt device=tmpfs --opt type=tmpfs --opt o=uid=1000,gid=1000 --label nix.home-manager.managed=true --label nix.home-manager.preserve=true --module=/etc/nvd.conf my-vol +SyslogIdentifier=%N +Type=oneshot + +[Unit] +Wants=podman-user-wait-network-online.service +After=podman-user-wait-network-online.service +Description=Service for volume my-vol +SourcePath=/nix/store/00000000000000000000000000000000-home-volume-podman-my-vol/quadlets/podman-my-vol.volume +RequiresMountsFor=%t/containers + +[X-Volume] +Copy=true +Device=tmpfs +Group=1000 +Label=nix.home-manager.managed=true +Label=nix.home-manager.preserve=true +PodmanArgs=--module=/etc/nvd.conf +Type=tmpfs +User=1000 +VolumeName=my-vol diff --git a/tests/modules/services/podman-linux/volume.nix b/tests/modules/services/podman-linux/volume.nix new file mode 100644 index 000000000..a83a806db --- /dev/null +++ b/tests/modules/services/podman-linux/volume.nix @@ -0,0 +1,35 @@ +{ ... }: + +{ + services.podman = { + enable = true; + volumes = { + "my-vol" = { + device = "tmpfs"; + extraConfig = { Volume = { User = 1000; }; }; + extraPodmanArgs = [ "--module=/etc/nvd.conf" ]; + group = 1000; + type = "tmpfs"; + }; + + "my-vol-2" = { + extraConfig = { Volume = { VolumeName = "some-other-volume-name"; }; }; + }; + }; + }; + + test.asserts.assertions.expected = [ + '' + In 'my-vol-2' config. Volume.VolumeName: 'some-other-volume-name' does not match expected type: value "my-vol-2" (singular enum)'' + ]; + + nmt.script = '' + configPath=home-files/.config/systemd/user + volumeFile=$configPath/podman-my-vol-volume.service + assertFileExists $volumeFile + + volumeFile=$(normalizeStorePaths $volumeFile) + + assertFileContent $volumeFile ${./volume-expected.service} + ''; +}