Compare commits
1 commit
master
...
docker-ser
Author | SHA1 | Date | |
---|---|---|---|
faa4b16358 |
|
@ -1348,6 +1348,16 @@ in
|
||||||
A new module is available: 'programs.gradle'.
|
A new module is available: 'programs.gradle'.
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
time = "2023-12-23T08:45:52+00:00";
|
||||||
|
message = ''
|
||||||
|
Three new modules are available:
|
||||||
|
'virtualisation.containers',
|
||||||
|
'virtualisation.oci-containers',
|
||||||
|
'virtualisation.podman'.
|
||||||
|
'';
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,6 +368,9 @@ let
|
||||||
./systemd.nix
|
./systemd.nix
|
||||||
./targets/darwin
|
./targets/darwin
|
||||||
./targets/generic-linux.nix
|
./targets/generic-linux.nix
|
||||||
|
./virtualisation/containers.nix
|
||||||
|
./virtualisation/oci-containers.nix
|
||||||
|
./virtualisation/podman/podman.nix
|
||||||
./xresources.nix
|
./xresources.nix
|
||||||
./xsession.nix
|
./xsession.nix
|
||||||
./misc/nix.nix
|
./misc/nix.nix
|
||||||
|
|
76
modules/virtualisation/containers.nix
Normal file
76
modules/virtualisation/containers.nix
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.virtualisation.containers;
|
||||||
|
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
|
||||||
|
toml = pkgs.formats.toml { };
|
||||||
|
in {
|
||||||
|
meta.maintainers = [ lib.maintainers.michaelCTS ];
|
||||||
|
|
||||||
|
options.virtualisation.containers = {
|
||||||
|
enable = lib.mkEnableOption "the common containers configuration module";
|
||||||
|
|
||||||
|
ociSeccompBpfHook.enable = lib.mkEnableOption "the OCI seccomp BPF hook";
|
||||||
|
|
||||||
|
registries = {
|
||||||
|
search = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ "docker.io" "quay.io" ];
|
||||||
|
description = ''
|
||||||
|
List of repositories to search.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
insecure = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
List of insecure repositories.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
block = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
List of blocked repositories.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
policy = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
default = [ { type = "insecureAcceptAnything"; } ];
|
||||||
|
transports = {
|
||||||
|
docker-daemon = {
|
||||||
|
"" = [ { type = "insecureAcceptAnything"; } ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Signature verification policy file.
|
||||||
|
If this option is empty the default policy file from
|
||||||
|
`skopeo` will be used.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
xdg.configFile."containers/registries.conf".source =
|
||||||
|
toml.generate "registries.conf" {
|
||||||
|
registries = lib.mapAttrs (n: v: { registries = v; }) cfg.registries;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."containers/policy.json".source = if cfg.policy != { } then
|
||||||
|
pkgs.writeText "policy.json" (builtins.toJSON cfg.policy)
|
||||||
|
else
|
||||||
|
"${pkgs.skopeo.src}/default-policy.json";
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
28
modules/virtualisation/oci-containers.nix
Normal file
28
modules/virtualisation/oci-containers.nix
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Equivalent of
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/virtualisation/oci-containers.nix
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.virtualisation.oci-containers;
|
||||||
|
|
||||||
|
inherit (lib) mkDefault mkIf mkMerge mkOption types;
|
||||||
|
|
||||||
|
defaultBackend = "podman";
|
||||||
|
in {
|
||||||
|
meta.maintainers = [ pkgs.lib.maintainers.michaelCTS ];
|
||||||
|
|
||||||
|
options.virtualisation.oci-containers = {
|
||||||
|
enable = lib.mkEnableOption
|
||||||
|
"a convenience option to enable containers in platform-agnostic manner";
|
||||||
|
|
||||||
|
backend = mkOption {
|
||||||
|
type = types.enum [ "podman" ];
|
||||||
|
default = defaultBackend;
|
||||||
|
description = "Which service to use as a backend for containers.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf (cfg.enable && cfg.backend == "podman") {
|
||||||
|
virtualisation.podman.enable = true;
|
||||||
|
};
|
||||||
|
}
|
30
modules/virtualisation/podman/podmactl/README.md
Normal file
30
modules/virtualisation/podman/podmactl/README.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# podmactl
|
||||||
|
|
||||||
|
`podmactl` is a script to manage the podman machines declared in Home
|
||||||
|
Manager.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
`main()` is a (hopefully) straight-forward method to read, but the gist of it is:
|
||||||
|
|
||||||
|
1. The declared machines and their configuration are passed in.
|
||||||
|
2. Existing machines and their configuration are listed.
|
||||||
|
3. A diff is made from the declared machines and existing machines.
|
||||||
|
4. New machines are added.
|
||||||
|
5. Existing machines are updated.
|
||||||
|
6. Old machines are removed.
|
||||||
|
7. The machine declared as `active` is started (if necessary).
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Enter a devshell with `nix-shell`.
|
||||||
|
|
||||||
|
Make your changes and then run
|
||||||
|
|
||||||
|
```
|
||||||
|
# Code autoformatting
|
||||||
|
black .
|
||||||
|
|
||||||
|
# Unittests
|
||||||
|
python -m unittest
|
||||||
|
```
|
28
modules/virtualisation/podman/podmactl/default.nix
Normal file
28
modules/virtualisation/podman/podmactl/default.nix
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{ pkgs ? (import <nixpkgs> { }), }:
|
||||||
|
|
||||||
|
pkgs.stdenv.mkDerivation {
|
||||||
|
name = "podmactl";
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
buildInputs = [ pkgs.python311 ];
|
||||||
|
doCheck = true;
|
||||||
|
checkPhase = ''
|
||||||
|
runHook preCheck
|
||||||
|
(
|
||||||
|
cd $src
|
||||||
|
black --check .
|
||||||
|
python -m unittest
|
||||||
|
)
|
||||||
|
runHook postCheck
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp podmactl.py $out/bin/podmactl
|
||||||
|
chmod +x $out/bin/podmactl
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
}
|
317
modules/virtualisation/podman/podmactl/podmactl.py
Normal file
317
modules/virtualisation/podman/podmactl/podmactl.py
Normal file
|
@ -0,0 +1,317 @@
|
||||||
|
#!/usr/bin/env python3.11
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
|
from functools import reduce
|
||||||
|
from operator import concat
|
||||||
|
from typing import Dict, Generic, Iterable, List, Optional, TypeVar
|
||||||
|
|
||||||
|
DEFAULT_MACHINE = "podman-machine-default"
|
||||||
|
T = TypeVar("T")
|
||||||
|
logger = logging.getLogger("podman-launchd")
|
||||||
|
logger_commander = logger.getChild("commander")
|
||||||
|
|
||||||
|
CAMEL_REGEX = re.compile(r"([A-Z]+)")
|
||||||
|
UNDERSCORE_REGEX = re.compile(r"^_")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Machine:
|
||||||
|
# Resource config for CLI
|
||||||
|
cpus: int
|
||||||
|
disk_size: int
|
||||||
|
memory: int
|
||||||
|
|
||||||
|
# Metadata about the machine
|
||||||
|
name: str
|
||||||
|
active: bool = field(compare=False, default=False)
|
||||||
|
qemu_binary: Optional[str] = None
|
||||||
|
"""A path to a custom QEMU command to be used when starting the machine with a specific arch"""
|
||||||
|
|
||||||
|
# Optional CLI parameters
|
||||||
|
image_path: Optional[str] = None
|
||||||
|
"""A local path to a custom QEMU image"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, a_dict: dict) -> "Machine":
|
||||||
|
return Machine(
|
||||||
|
**{
|
||||||
|
snake_key: value
|
||||||
|
for key, value in a_dict.items()
|
||||||
|
if (snake_key := camel2snake(key).lower()) in MACHINE_FIELDS
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
MACHINE_FIELDS = [field.name for field in fields(Machine)]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Diff(Generic[T]):
|
||||||
|
new: List[T] = field(default_factory=list)
|
||||||
|
modified: List[T] = field(default_factory=list)
|
||||||
|
same: List[T] = field(default_factory=list)
|
||||||
|
removed: List[T] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class PodmanMachineCommander:
|
||||||
|
MACHINE_CLI_ARGS = ("cpus", "disk_size", "memory")
|
||||||
|
|
||||||
|
def __init__(self, command: str = None):
|
||||||
|
self.command = command or "podman"
|
||||||
|
|
||||||
|
def _call(self, *args: str, **kwargs) -> str:
|
||||||
|
"""Call podman machine"""
|
||||||
|
|
||||||
|
args_ = [self.command, "machine"] + list(args)
|
||||||
|
logger_commander.debug("Executing %s", shlex.join(args_))
|
||||||
|
|
||||||
|
# no subprocess.run here as streaming is necessary
|
||||||
|
stdout_lines = []
|
||||||
|
with subprocess.Popen(
|
||||||
|
args_,
|
||||||
|
# Capture both streams in stdout
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
**kwargs,
|
||||||
|
) as process:
|
||||||
|
# Collect stdout+stderr and steam if requested
|
||||||
|
for line in process.stdout:
|
||||||
|
line_str = line.decode().rstrip()
|
||||||
|
stdout_lines.append(line_str)
|
||||||
|
logger_commander.debug(line_str)
|
||||||
|
|
||||||
|
stdout = "\n".join(stdout_lines)
|
||||||
|
# Check if the command failed
|
||||||
|
if (return_code := process.returncode) != 0:
|
||||||
|
print(stdout, file=sys.stderr)
|
||||||
|
raise subprocess.CalledProcessError(return_code, args_, stdout)
|
||||||
|
|
||||||
|
return stdout
|
||||||
|
|
||||||
|
def _call_json(self, *args: str, **kwargs) -> dict:
|
||||||
|
"""Call podman requesting JSON output and interpret it as such"""
|
||||||
|
return json.loads(self._call(*args, "--format", "json", **kwargs))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def make_cli_args(
|
||||||
|
cls, machine: Machine, selected_args: Iterable[str] = MACHINE_CLI_ARGS
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Converts dict from list of key-value pair
|
||||||
|
to list of ["--key1", value1, "--key2", value2, ... ]
|
||||||
|
"""
|
||||||
|
machine_dict = asdict(machine)
|
||||||
|
return reduce(
|
||||||
|
concat,
|
||||||
|
[
|
||||||
|
[("--" + key.replace("_", "-")), str(value)]
|
||||||
|
for key in selected_args
|
||||||
|
if (value := machine_dict.get(key))
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_active_machine_name(self) -> str:
|
||||||
|
"""Name of the machine that is currently running"""
|
||||||
|
output_json = self._call_json("info")
|
||||||
|
return output_json.get("Host", {}).get("CurrentMachine")
|
||||||
|
|
||||||
|
def list(self) -> Dict[str, Machine]:
|
||||||
|
"""Get all machines known to podman"""
|
||||||
|
machine_jsons = self._call_json("list")
|
||||||
|
if not isinstance(machine_jsons, list):
|
||||||
|
raise ValueError("Unexpected output from command", machine_jsons)
|
||||||
|
|
||||||
|
# `podman machine list` has different units for disk_size, memory, etc.
|
||||||
|
# `podman machine inspect` has the information we need
|
||||||
|
inspected_jsons = self.inspect(
|
||||||
|
*[listed_machine["Name"] for listed_machine in machine_jsons]
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
machine.name: machine
|
||||||
|
for inspected_json in inspected_jsons
|
||||||
|
if (
|
||||||
|
machine := Machine.from_dict(
|
||||||
|
{"name": inspected_json["Name"], **inspected_json["Resources"]}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def inspect(self, *machine_names: str):
|
||||||
|
"""Get information about a machine from podman"""
|
||||||
|
# The podman machine interface is really confusing
|
||||||
|
# inspect only returns JSON and other commands require --format json
|
||||||
|
return json.loads(self._call("inspect", *machine_names))
|
||||||
|
|
||||||
|
def add(self, machine: Machine):
|
||||||
|
"""
|
||||||
|
Let podman create a machine's config and initialize it
|
||||||
|
Also downloads the image of the machine
|
||||||
|
"""
|
||||||
|
self._call(
|
||||||
|
"init",
|
||||||
|
*self.make_cli_args(machine, self.MACHINE_CLI_ARGS + ("image_path",)),
|
||||||
|
machine.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(self, machine: Machine):
|
||||||
|
"""Update a machine's configuration and write it to disk"""
|
||||||
|
self._call("set", *self.make_cli_args(machine), machine.name)
|
||||||
|
|
||||||
|
# Set the custom QEMU path in the machine's config
|
||||||
|
# This is necessary for running machines with a specific architecture
|
||||||
|
if machine.qemu_binary:
|
||||||
|
inspection = self.inspect(machine.name)[0]
|
||||||
|
config_path = inspection.get("ConfigPath", {}).get("Path", {})
|
||||||
|
|
||||||
|
with open(config_path) as config_file:
|
||||||
|
config = json.load(config_file)
|
||||||
|
|
||||||
|
if not (cmd_line := config.get("CmdLine")):
|
||||||
|
logger.error(
|
||||||
|
"Cannot find CmdLine in config of %s at", machine.name, config_path
|
||||||
|
)
|
||||||
|
cmd_line[0] = machine.qemu_binary
|
||||||
|
|
||||||
|
with open(config_path, mode="w") as config_file:
|
||||||
|
json.dump(config, config_file)
|
||||||
|
|
||||||
|
def remove(self, machine: Machine):
|
||||||
|
"""Kills and removes the machine"""
|
||||||
|
self._call("rm", "--force", machine.name)
|
||||||
|
|
||||||
|
def start(self, machine_name: str):
|
||||||
|
"""Start up a machine"""
|
||||||
|
self._call("start", machine_name)
|
||||||
|
|
||||||
|
def stop(self, machine_name: str):
|
||||||
|
"""Stop a running machine"""
|
||||||
|
self._call("stop", machine_name)
|
||||||
|
|
||||||
|
|
||||||
|
def main(
|
||||||
|
requested_machines: Dict[str, Machine],
|
||||||
|
podman_command: str,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
:param requested_machines: Which machines should exist on the host
|
||||||
|
:param podman_command: The path to or the podman command itself
|
||||||
|
"""
|
||||||
|
podman_command = podman_command or "podman"
|
||||||
|
commander = PodmanMachineCommander(podman_command)
|
||||||
|
active_machines = [
|
||||||
|
name for name, machine in requested_machines.items() if machine.active
|
||||||
|
]
|
||||||
|
if len(active_machines) != 1:
|
||||||
|
raise ValueError("Exactly one machine in the configuration should be active")
|
||||||
|
requested_active = active_machines[0]
|
||||||
|
|
||||||
|
old_machines = commander.list()
|
||||||
|
# Find machines to add, update, delete
|
||||||
|
diffs = diff_machines(requested_machines, old_machines)
|
||||||
|
|
||||||
|
# Init new machines
|
||||||
|
for new_machine in diffs.new:
|
||||||
|
logger.info("Adding machine: %s. This may take some time...", new_machine.name)
|
||||||
|
commander.add(new_machine)
|
||||||
|
# Init the default machine if it's not
|
||||||
|
|
||||||
|
# Delete old ones
|
||||||
|
for removed_machine in diffs.removed:
|
||||||
|
logger.info("Removing machine: %s", removed_machine.name)
|
||||||
|
commander.remove(removed_machine)
|
||||||
|
|
||||||
|
# Update configuration of qemuBinary if necessary
|
||||||
|
for mod_machine in diffs.modified:
|
||||||
|
logger.info("Updating machine: %s", mod_machine.name)
|
||||||
|
commander.update(mod_machine)
|
||||||
|
|
||||||
|
# Start the requested machine if it isn't already running
|
||||||
|
active_machine = commander.get_active_machine_name()
|
||||||
|
if active_machine != requested_active:
|
||||||
|
if active_machine:
|
||||||
|
logger.info("Stopping machine: %s", active_machine)
|
||||||
|
commander.stop(active_machine)
|
||||||
|
logger.info("Starting: %s", requested_active)
|
||||||
|
commander.start(requested_active)
|
||||||
|
|
||||||
|
logger.info("%s is active and podman is ready to be used")
|
||||||
|
|
||||||
|
|
||||||
|
def camel2snake(camel: str) -> str:
|
||||||
|
"""
|
||||||
|
Converts camelCase to snake_case
|
||||||
|
"""
|
||||||
|
snake = CAMEL_REGEX.sub(r"_\1", camel).lower()
|
||||||
|
# if snake starts with _ remove it
|
||||||
|
return UNDERSCORE_REGEX.sub("", snake)
|
||||||
|
|
||||||
|
|
||||||
|
def diff_machines(
|
||||||
|
requested_machines: Dict[str, Machine], old_machines: Dict[str, Machine]
|
||||||
|
) -> Diff[Machine]:
|
||||||
|
diff: Diff[Machine] = Diff()
|
||||||
|
requested_names = requested_machines.keys()
|
||||||
|
old_names = old_machines.keys()
|
||||||
|
requested_items = requested_machines.items()
|
||||||
|
old_items = old_machines.items()
|
||||||
|
diff.new = [requested_machines[name] for name in (requested_names - old_names)]
|
||||||
|
diff.removed = [old_machines[name] for name in (old_names - requested_names)]
|
||||||
|
diff.same = list(dict(old_items & requested_items).values())
|
||||||
|
|
||||||
|
# Find modified machines = same key, different Machine
|
||||||
|
diff.modified = [
|
||||||
|
requested_machines[key]
|
||||||
|
for key in (requested_names & old_names)
|
||||||
|
if requested_machines[key] != old_machines[key]
|
||||||
|
]
|
||||||
|
|
||||||
|
return diff
|
||||||
|
|
||||||
|
|
||||||
|
def MachineDict(json_path: str) -> dict:
|
||||||
|
try:
|
||||||
|
with open(json_path) as json_file:
|
||||||
|
loaded_json = json.load(json_file)
|
||||||
|
return {
|
||||||
|
name: Machine.from_dict({"name": name, **machine})
|
||||||
|
for name, machine in loaded_json.items()
|
||||||
|
}
|
||||||
|
except json.JSONDecodeError as decode_error:
|
||||||
|
raise argparse.ArgumentTypeError() from decode_error
|
||||||
|
except Exception as exc:
|
||||||
|
raise argparse.ArgumentTypeError() from exc
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = ArgumentParser(
|
||||||
|
"podman-launchd", description="CRUDs pod machines and starts one"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"machines",
|
||||||
|
help="Path to JSON configuration of machines that should be on this host",
|
||||||
|
type=MachineDict,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p", "--podman", help="Name or path of the podman command to use"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--verbose", help="Activate verbose logging", action="store_true"
|
||||||
|
)
|
||||||
|
cmd_args = parser.parse_args()
|
||||||
|
logging.basicConfig(level=logging.DEBUG if cmd_args.verbose else logging.INFO)
|
||||||
|
|
||||||
|
try:
|
||||||
|
main(cmd_args.machines, cmd_args.podman)
|
||||||
|
except:
|
||||||
|
logger.exception("Couldn't complete command")
|
||||||
|
exit(1)
|
5
modules/virtualisation/podman/podmactl/shell.nix
Normal file
5
modules/virtualisation/podman/podmactl/shell.nix
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = [ (pkgs.python311.withPackages (ps: with ps; [ black ])) ];
|
||||||
|
}
|
232
modules/virtualisation/podman/podmactl/test_podmactl.py
Normal file
232
modules/virtualisation/podman/podmactl/test_podmactl.py
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from podmactl import (
|
||||||
|
Machine,
|
||||||
|
MachineDict,
|
||||||
|
diff_machines,
|
||||||
|
PodmanMachineCommander,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MachineTestCase(unittest.TestCase):
|
||||||
|
def test_from_list_dict(self):
|
||||||
|
"""Ensure that dicts from `podman machine list` can create a machine object"""
|
||||||
|
self.assertEqual(
|
||||||
|
Machine(
|
||||||
|
cpus=2, disk_size=100, memory=2048, name="indie-machine", active=True
|
||||||
|
),
|
||||||
|
Machine.from_dict(
|
||||||
|
dict(
|
||||||
|
CPUs=2,
|
||||||
|
DiskSize=100,
|
||||||
|
Memory=2048,
|
||||||
|
Name="indie-machine",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_from_extra_dict(self):
|
||||||
|
self.assertEqual(
|
||||||
|
Machine(
|
||||||
|
cpus=2, disk_size=100, memory=2048, name="indie-machine", active=True
|
||||||
|
),
|
||||||
|
Machine.from_dict(
|
||||||
|
dict(
|
||||||
|
cpus=2,
|
||||||
|
disk_size=100,
|
||||||
|
memory=2048,
|
||||||
|
name="indie-machine",
|
||||||
|
active=True,
|
||||||
|
new=True,
|
||||||
|
dont_exist="something",
|
||||||
|
something_else="ladidah",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_from_bad_dict(self):
|
||||||
|
"""Will pass the wrong number of args to the __init__"""
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError,
|
||||||
|
Machine.from_dict,
|
||||||
|
dict(
|
||||||
|
cpus=2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_make_cli_args(self):
|
||||||
|
args = PodmanMachineCommander.make_cli_args(
|
||||||
|
Machine(cpus=2, disk_size=50, memory=4096, name="manjaro", active=False)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
args,
|
||||||
|
[
|
||||||
|
"--cpus",
|
||||||
|
"2",
|
||||||
|
"--disk-size",
|
||||||
|
"50",
|
||||||
|
"--memory",
|
||||||
|
"4096",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_make_optional_cli_args(self):
|
||||||
|
machine = Machine(
|
||||||
|
cpus=2,
|
||||||
|
disk_size=50,
|
||||||
|
memory=4096,
|
||||||
|
name="manjaro",
|
||||||
|
active=False,
|
||||||
|
image_path="somewhere.qcow2.xz",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
PodmanMachineCommander.make_cli_args(machine),
|
||||||
|
[
|
||||||
|
"--cpus",
|
||||||
|
"2",
|
||||||
|
"--disk-size",
|
||||||
|
"50",
|
||||||
|
"--memory",
|
||||||
|
"4096",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
PodmanMachineCommander.make_cli_args(
|
||||||
|
machine, PodmanMachineCommander.MACHINE_CLI_ARGS + ("image_path",)
|
||||||
|
),
|
||||||
|
[
|
||||||
|
"--cpus",
|
||||||
|
"2",
|
||||||
|
"--disk-size",
|
||||||
|
"50",
|
||||||
|
"--memory",
|
||||||
|
"4096",
|
||||||
|
"--image-path",
|
||||||
|
"somewhere.qcow2.xz",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MachineDictTestCase(unittest.TestCase):
|
||||||
|
def test_load(self):
|
||||||
|
machine_json = {
|
||||||
|
"cpus": 2,
|
||||||
|
"disk_size": 100,
|
||||||
|
"memory": 2048,
|
||||||
|
"active": False,
|
||||||
|
}
|
||||||
|
machines_json = {"default": machine_json}
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
||||||
|
json.dump(machines_json, fp)
|
||||||
|
fp.seek(0)
|
||||||
|
machines = MachineDict(fp.name)
|
||||||
|
self.assertDictEqual(
|
||||||
|
machines,
|
||||||
|
{
|
||||||
|
"default": Machine(
|
||||||
|
cpus=2,
|
||||||
|
disk_size=100,
|
||||||
|
memory=2048,
|
||||||
|
name="default",
|
||||||
|
active=False,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_bad_machine_load(self):
|
||||||
|
with self.assertRaises(argparse.ArgumentTypeError):
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
||||||
|
json.dump({"default": {}}, fp)
|
||||||
|
fp.seek(0)
|
||||||
|
MachineDict(fp.name)
|
||||||
|
|
||||||
|
def test_bad_json_load(self):
|
||||||
|
with self.assertRaises(argparse.ArgumentTypeError):
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w") as fp:
|
||||||
|
fp.write("this is definitely not a json")
|
||||||
|
fp.seek(0)
|
||||||
|
MachineDict(fp.name)
|
||||||
|
|
||||||
|
|
||||||
|
class DiffTestCase(unittest.TestCase):
|
||||||
|
def test_new_machines(self):
|
||||||
|
diff = diff_machines(
|
||||||
|
{
|
||||||
|
"new": Machine(
|
||||||
|
cpus=1, disk_size=100, memory=1024, name="new", active=True
|
||||||
|
),
|
||||||
|
"old": Machine(
|
||||||
|
cpus=1, disk_size=100, memory=1024, name="old", active=False
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"old": Machine(
|
||||||
|
cpus=1, disk_size=100, memory=1024, name="old", active=False
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertListEqual(
|
||||||
|
diff.new,
|
||||||
|
[Machine(cpus=1, disk_size=100, memory=1024, name="new", active=False)],
|
||||||
|
)
|
||||||
|
self.assertListEqual(
|
||||||
|
diff.same,
|
||||||
|
[Machine(cpus=1, disk_size=100, memory=1024, name="old", active=False)],
|
||||||
|
)
|
||||||
|
self.assertListEqual(diff.removed, [])
|
||||||
|
self.assertListEqual(diff.modified, [])
|
||||||
|
|
||||||
|
def test_update_machine(self):
|
||||||
|
diff = diff_machines(
|
||||||
|
{
|
||||||
|
"changed": Machine(
|
||||||
|
cpus=1, disk_size=100, memory=1024, name="changed", active=True
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"changed": Machine(
|
||||||
|
cpus=2, disk_size=100, memory=2048, name="changed", active=False
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertListEqual(diff.new, [])
|
||||||
|
self.assertListEqual(diff.same, [])
|
||||||
|
self.assertListEqual(diff.removed, [])
|
||||||
|
self.assertListEqual(
|
||||||
|
diff.modified,
|
||||||
|
[Machine(cpus=1, disk_size=100, memory=1024, name="changed", active=False)],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_remove_machine(self):
|
||||||
|
diff = diff_machines(
|
||||||
|
{
|
||||||
|
"same": Machine(
|
||||||
|
cpus=1, disk_size=100, memory=1024, name="same", active=True
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"same": Machine(
|
||||||
|
cpus=1, disk_size=100, memory=1024, name="same", active=False
|
||||||
|
),
|
||||||
|
"removed": Machine(
|
||||||
|
cpus=1, disk_size=100, memory=1024, name="removed", active=True
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertListEqual(diff.new, [])
|
||||||
|
self.assertListEqual(
|
||||||
|
diff.same,
|
||||||
|
[Machine(cpus=1, disk_size=100, memory=1024, name="same", active=False)],
|
||||||
|
)
|
||||||
|
self.assertListEqual(
|
||||||
|
diff.removed,
|
||||||
|
[Machine(cpus=1, disk_size=100, memory=1024, name="removed", active=False)],
|
||||||
|
)
|
||||||
|
self.assertListEqual(diff.modified, [])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
274
modules/virtualisation/podman/podman.nix
Normal file
274
modules/virtualisation/podman/podman.nix
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.virtualisation.podman;
|
||||||
|
toml = pkgs.formats.toml { };
|
||||||
|
json = pkgs.formats.json { };
|
||||||
|
|
||||||
|
inherit (lib) mkDefault mkIf mkMerge mkOption types;
|
||||||
|
|
||||||
|
podmanPackage = (pkgs.podman.override { inherit (cfg) extraPackages; });
|
||||||
|
|
||||||
|
# Provides a fake "docker" binary mapping to podman
|
||||||
|
dockerAlias = pkgs.runCommandNoCC
|
||||||
|
"${podmanPackage.pname}-docker-alias-${podmanPackage.version}" {
|
||||||
|
outputs = [ "out" "man" ];
|
||||||
|
inherit (podmanPackage) meta;
|
||||||
|
} ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
ln -s ${podmanPackage}/bin/podman $out/bin/docker
|
||||||
|
|
||||||
|
mkdir -p $man/share/man/man1
|
||||||
|
for f in ${podmanPackage.man}/share/man/man1/*; do
|
||||||
|
basename=$(basename $f | sed s/podman/docker/g)
|
||||||
|
ln -s $f $man/share/man/man1/$basename
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
podmactl = pkgs.callPackage ./podmactl { };
|
||||||
|
|
||||||
|
machineOpts = {
|
||||||
|
# Options here are loaded into python. For simplicity, please use
|
||||||
|
# snake_case.
|
||||||
|
options = {
|
||||||
|
active = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
This machine should be started. Only one machine can be active at a time
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
qemu_binary = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
example = "''${pkgs.qemu}/bin/qemu-system-x86_64";
|
||||||
|
description = ''
|
||||||
|
Use this to start VM with the qemu appropriate for your architecture.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Options passed to Podman machine.
|
||||||
|
# See https://docs.podman.io/en/latest/markdown/podman-machine.1.html
|
||||||
|
cpus = mkOption {
|
||||||
|
type = types.ints.positive;
|
||||||
|
default = 1;
|
||||||
|
description = "The number of CPUs to assign to the VM.";
|
||||||
|
};
|
||||||
|
|
||||||
|
disk_size = mkOption {
|
||||||
|
type = types.ints.positive;
|
||||||
|
default = 100;
|
||||||
|
description = "Size of disk in gigabytes. Can only be increased";
|
||||||
|
};
|
||||||
|
|
||||||
|
image_path = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
builtins.fetchurl "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/38.20230819.3.0/x86_64/fedora-coreos-38.20230819.3.0-qemu.x86_64.qcow2.xz"'';
|
||||||
|
description = ''
|
||||||
|
Image to be used when starting the VM
|
||||||
|
Can be a local path or a URL to an image.
|
||||||
|
Alternatives can be found at <https://fedoraproject.org/en/coreos/download>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
memory = mkOption {
|
||||||
|
type = types.ints.positive;
|
||||||
|
default = 2048;
|
||||||
|
description = "RAM in MB to be assigned to the machine";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in {
|
||||||
|
meta.maintainers = [ pkgs.lib.maintainers.michaelCTS ];
|
||||||
|
|
||||||
|
options.virtualisation.podman = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
This option enables Podman, a daemonless container engine for
|
||||||
|
developing, managing, and running OCI Containers on your Linux System.
|
||||||
|
|
||||||
|
It is a drop-in replacement for the {command}`docker` command.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
enableDockerSocket = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Make the Podman socket available in place of the Docker socket, so
|
||||||
|
Docker tools can find the Podman socket.
|
||||||
|
|
||||||
|
Podman implements the Docker API.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
enableDockerAlias = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Create an alias mapping {command}`docker` to {command}`podman`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraPackages = mkOption {
|
||||||
|
type = with types; listOf package;
|
||||||
|
default = [ ];
|
||||||
|
example = lib.literalExpression "[ pkgs.gvisor ]";
|
||||||
|
description = ''
|
||||||
|
Extra packages to be installed in the Podman wrapper.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
finalPackage = lib.mkOption {
|
||||||
|
type = types.package;
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
default = podmanPackage;
|
||||||
|
description = ''
|
||||||
|
The final Podman package (including extra packages).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultNetwork.extraPlugins = lib.mkOption {
|
||||||
|
type = types.listOf json.type;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Extra CNI plugin configurations to add to Podman's default network.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
machines = lib.mkOption {
|
||||||
|
type = types.attrsOf (types.submodule machineOpts);
|
||||||
|
# One and only one machine may be active at any given time
|
||||||
|
apply = machines:
|
||||||
|
assert ((lib.lists.count (machine: machine.active)
|
||||||
|
(lib.attrsets.attrValues machines)) == 1);
|
||||||
|
machines;
|
||||||
|
default = {
|
||||||
|
podman-machine-default = {
|
||||||
|
active = true;
|
||||||
|
cpus = 2;
|
||||||
|
disk_size = 100;
|
||||||
|
memory = 2048;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
intel-x86 = {
|
||||||
|
cpus = 2;
|
||||||
|
disk_size = 200;
|
||||||
|
memory = 4096;
|
||||||
|
image_path = "fedora-coreos-38.20230806.3.0-qemu.x86_64.qcow2.xz";
|
||||||
|
qemu_binary = "${pkgs.qemu}/bin/qemu-system-x86_64";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Virtual machine descriptions when Podman is run in on non-Linux systems.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
{
|
||||||
|
home.packages = [ cfg.finalPackage ]
|
||||||
|
++ lib.optional cfg.enableDockerAlias dockerAlias;
|
||||||
|
|
||||||
|
virtualisation.containers = {
|
||||||
|
enable = true; # Enable common /etc/containers configuration
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(mkIf pkgs.stdenv.hostPlatform.isLinux (mkMerge [
|
||||||
|
{
|
||||||
|
systemd.user = {
|
||||||
|
services.podman = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Podman API Service";
|
||||||
|
Requires = "podman.socket";
|
||||||
|
After = "podman.socket";
|
||||||
|
Documentation = "man:podman-system-service(1)";
|
||||||
|
StartLimitIntervalSec = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
Type = "exec";
|
||||||
|
KillMode = "process";
|
||||||
|
Environment = ''LOGGING=" --log-level=info"'';
|
||||||
|
ExecStart = [
|
||||||
|
"${cfg.finalPackage}/bin/podman"
|
||||||
|
"$LOGGING"
|
||||||
|
"system"
|
||||||
|
"service"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
Install = { WantedBy = [ "default.target" ]; };
|
||||||
|
};
|
||||||
|
|
||||||
|
sockets.podman = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Podman API Socket";
|
||||||
|
Documentation = "man:podman-system-service(1)";
|
||||||
|
};
|
||||||
|
|
||||||
|
Socket = {
|
||||||
|
ListenStream = "%t/podman/podman.sock";
|
||||||
|
SocketMode = 660;
|
||||||
|
};
|
||||||
|
|
||||||
|
Install.WantedBy = [ "sockets.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(mkIf cfg.enableDockerSocket {
|
||||||
|
home.sessionVariables."DOCKER_HOST" =
|
||||||
|
"unix:///$XDG_RUNTIME_DIR/podman/podman.sock";
|
||||||
|
})
|
||||||
|
]))
|
||||||
|
|
||||||
|
(mkIf pkgs.stdenv.isDarwin (mkMerge [
|
||||||
|
{
|
||||||
|
home.packages = [
|
||||||
|
pkgs.qemu # To manage machines
|
||||||
|
pkgs.openssh # To ssh into the machines
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
home.extraActivationPath = [
|
||||||
|
pkgs.qemu # To manage machines.
|
||||||
|
pkgs.openssh # To ssh into the machines.
|
||||||
|
];
|
||||||
|
|
||||||
|
# CRUD the requested podman machines when activating the profile
|
||||||
|
home.activation.podman-machine =
|
||||||
|
lib.hm.dag.entryAfter [ "writeBoundary" ]
|
||||||
|
(lib.strings.concatStringsSep " " [
|
||||||
|
"$DRY_RUN_CMD"
|
||||||
|
"${podmactl}/bin/podmactl"
|
||||||
|
"--podman"
|
||||||
|
"${cfg.finalPackage}/bin/podman"
|
||||||
|
"$VERBOSE_ARG"
|
||||||
|
"${json.generate "podman-machines.json" cfg.machines}"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Socket is actually only available after the launchd agent has
|
||||||
|
# successfully completed and the machine has been started.
|
||||||
|
(mkIf cfg.enableDockerSocket {
|
||||||
|
home.sessionVariables."DOCKER_HOST" =
|
||||||
|
"unix:///Users/$USER/.local/share/containers/podman/machine/qemu/podman.sock";
|
||||||
|
})
|
||||||
|
]))
|
||||||
|
]);
|
||||||
|
}
|
|
@ -159,6 +159,7 @@ import nmt {
|
||||||
./modules/programs/zplug
|
./modules/programs/zplug
|
||||||
./modules/programs/zsh
|
./modules/programs/zsh
|
||||||
./modules/services/syncthing/common
|
./modules/services/syncthing/common
|
||||||
|
./modules/virtualisation/podman
|
||||||
./modules/xresources
|
./modules/xresources
|
||||||
] ++ lib.optionals isDarwin [
|
] ++ lib.optionals isDarwin [
|
||||||
./modules/launchd
|
./modules/launchd
|
||||||
|
@ -263,6 +264,7 @@ import nmt {
|
||||||
./modules/services/wlsunset
|
./modules/services/wlsunset
|
||||||
./modules/services/xsettingsd
|
./modules/services/xsettingsd
|
||||||
./modules/systemd
|
./modules/systemd
|
||||||
|
./modules/virtualisation/oci-containers
|
||||||
./modules/targets-linux
|
./modules/targets-linux
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
52
tests/modules/virtualisation/oci-containers/basic-config.nix
Normal file
52
tests/modules/virtualisation/oci-containers/basic-config.nix
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
lib.mkIf config.test.enableBig {
|
||||||
|
virtualisation.oci-containers.enable = true;
|
||||||
|
|
||||||
|
nmt.script = lib.mkIf pkgs.stdenv.isLinux ''
|
||||||
|
servicePath=home-files/.config/systemd/user
|
||||||
|
|
||||||
|
assertFileExists $servicePath/podman.service $servicePath/podman.socket
|
||||||
|
|
||||||
|
podmanServiceNormalized="$(normalizeStorePaths "$servicePath/podman.service")"
|
||||||
|
assertFileContent $podmanServiceNormalized \
|
||||||
|
${
|
||||||
|
builtins.toFile "podman.service-expected" ''
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=LOGGING=" --log-level=info"
|
||||||
|
ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman
|
||||||
|
ExecStart=$LOGGING
|
||||||
|
ExecStart=system
|
||||||
|
ExecStart=service
|
||||||
|
KillMode=process
|
||||||
|
Type=exec
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
After=podman.socket
|
||||||
|
Description=Podman API Service
|
||||||
|
Documentation=man:podman-system-service(1)
|
||||||
|
Requires=podman.socket
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
''
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFileContent $servicePath/podman.socket \
|
||||||
|
${
|
||||||
|
builtins.toFile "podman.socket-expected" ''
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target
|
||||||
|
|
||||||
|
[Socket]
|
||||||
|
ListenStream=%t/podman/podman.sock
|
||||||
|
SocketMode=660
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Podman API Socket
|
||||||
|
Documentation=man:podman-system-service(1)
|
||||||
|
''
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
}
|
1
tests/modules/virtualisation/oci-containers/default.nix
Normal file
1
tests/modules/virtualisation/oci-containers/default.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{ oci-containers-basic-config = ./basic-config.nix; }
|
52
tests/modules/virtualisation/podman/basic-config.nix
Normal file
52
tests/modules/virtualisation/podman/basic-config.nix
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
lib.mkIf config.test.enableBig {
|
||||||
|
virtualisation.podman.enable = true;
|
||||||
|
|
||||||
|
nmt.script = lib.mkIf pkgs.stdenv.isLinux ''
|
||||||
|
servicePath=home-files/.config/systemd/user
|
||||||
|
|
||||||
|
assertFileExists $servicePath/podman.service $servicePath/podman.socket
|
||||||
|
|
||||||
|
podmanServiceNormalized="$(normalizeStorePaths "$servicePath/podman.service")"
|
||||||
|
assertFileContent $podmanServiceNormalized \
|
||||||
|
${
|
||||||
|
builtins.toFile "podman.service-expected" ''
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=LOGGING=" --log-level=info"
|
||||||
|
ExecStart=/nix/store/00000000000000000000000000000000-podman/bin/podman
|
||||||
|
ExecStart=$LOGGING
|
||||||
|
ExecStart=system
|
||||||
|
ExecStart=service
|
||||||
|
KillMode=process
|
||||||
|
Type=exec
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
After=podman.socket
|
||||||
|
Description=Podman API Service
|
||||||
|
Documentation=man:podman-system-service(1)
|
||||||
|
Requires=podman.socket
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
''
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFileContent $servicePath/podman.socket \
|
||||||
|
${
|
||||||
|
builtins.toFile "podman.socket-expected" ''
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target
|
||||||
|
|
||||||
|
[Socket]
|
||||||
|
ListenStream=%t/podman/podman.sock
|
||||||
|
SocketMode=660
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Podman API Socket
|
||||||
|
Documentation=man:podman-system-service(1)
|
||||||
|
''
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
}
|
4
tests/modules/virtualisation/podman/default.nix
Normal file
4
tests/modules/virtualisation/podman/default.nix
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
podman-basic-config = ./basic-config.nix;
|
||||||
|
podman-docker-alias = ./docker-alias.nix;
|
||||||
|
}
|
14
tests/modules/virtualisation/podman/docker-alias.nix
Normal file
14
tests/modules/virtualisation/podman/docker-alias.nix
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
lib.mkIf config.test.enableBig {
|
||||||
|
virtualisation.podman = {
|
||||||
|
enable = true;
|
||||||
|
enableDockerAlias = true;
|
||||||
|
enableDockerSocket = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
nmt.script = ''
|
||||||
|
assertFileIsExecutable home-path/bin/docker
|
||||||
|
assertFileContains home-path/etc/profile.d/hm-session-vars.sh "DOCKER_HOST"
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Reference in a new issue