1
0
Fork 0

Compare commits

..

7 commits

Author SHA1 Message Date
aa13b77758
generate: generate: Bump version to 0.0.11
Signed-off-by: Enno Tensing <tenno@suij.in>
2025-08-08 16:20:55 +02:00
147e5630aa
generate: container: Create new ContainerOptions class for read_only and Co
Signed-off-by: Enno Tensing <tenno@suij.in>
2025-08-08 16:20:12 +02:00
e4ec47401e
generate: generate: Bump version to 0.0.10
Signed-off-by: Enno Tensing <tenno@suij.in>
2025-08-08 16:01:37 +02:00
45d2e3a3d1
containerrc: Update JSON schema
Signed-off-by: Enno Tensing <tenno@suij.in>
2025-08-08 16:00:52 +02:00
7a794197f4
generate: container: Allow one volume to be mounted multiple times
Signed-off-by: Enno Tensing <tenno@suij.in>
2025-08-08 15:58:53 +02:00
8b7221363c
generate: container: Allow one secret to be used multiple times
Secret a can now be used more than once, but currently only with the same
secret type.

Signed-off-by: Enno Tensing <tenno@suij.in>
2025-08-08 15:51:53 +02:00
486a38440f
generate: container: Add command section to image configuration
Signed-off-by: Enno Tensing <tenno@suij.in>
2025-08-08 15:43:39 +02:00
3 changed files with 113 additions and 67 deletions

View file

@ -16,6 +16,9 @@
}, },
"tag": { "tag": {
"type": "string" "type": "string"
},
"command": {
"type": "string"
} }
}, },
"required": [ "required": [
@ -52,11 +55,7 @@
"type": "array", "type": "array",
"items": {} "items": {}
} }
}, }
"required": [
"mode",
"options"
]
}, },
"dns": { "dns": {
"type": "object", "type": "object",
@ -72,11 +71,7 @@
} }
] ]
} }
}, }
"required": [
"search",
"servers"
]
}, },
"ports": { "ports": {
"type": "object", "type": "object",
@ -97,11 +92,7 @@
} }
] ]
} }
}, }
"required": [
"tcp",
"udp"
]
}, },
"env": { "env": {
"type": "object" "type": "object"
@ -132,11 +123,7 @@
} }
] ]
} }
}, }
"required": [
"add",
"drop"
]
}, },
"accounting": { "accounting": {
"type": "object", "type": "object",

View file

@ -244,7 +244,7 @@ class Volume:
"""Container Volume.""" """Container Volume."""
name: str name: str
path: str path: list
@classmethod @classmethod
def from_json(cls, val: ConfigValue, logger: Log) -> list: def from_json(cls, val: ConfigValue, logger: Log) -> list:
@ -254,7 +254,16 @@ class Volume:
if not isinstance(val, dict): if not isinstance(val, dict):
logger.log_warning("Volume key is present, but malformed.") logger.log_warning("Volume key is present, but malformed.")
return [] return []
return [cls(key, value) for key, value in val.items()] return [
Volume.from_json_entry(key, value) for key, value in val.items()
]
@classmethod
def from_json_entry(cls, key: str, value: str | list) -> Self:
"""Create from JSON entry."""
if isinstance(value, str):
return cls(key, [value])
return cls(key, value)
def is_host_volume(self) -> bool: def is_host_volume(self) -> bool:
"""Check if this Volume is a named or a host volume.""" """Check if this Volume is a named or a host volume."""
@ -262,7 +271,10 @@ class Volume:
def command(self) -> str: def command(self) -> str:
"""Option for podman container create.""" """Option for podman container create."""
return f"--volume {self.name}:{self.path}" cmd = ""
for path in self.path:
cmd += f"\t--volume {self.name}:{path} \\\n"
return cmd
def create(self) -> str: def create(self) -> str:
"""Create volume, if it does not exist.""" """Create volume, if it does not exist."""
@ -298,7 +310,7 @@ class Secret:
name: str name: str
secret_type: str secret_type: str
target: str target: list
options: str options: str
@classmethod @classmethod
@ -316,26 +328,33 @@ class Secret:
continue continue
name = key name = key
secret_type = maybe_or(val[key], "type", "") secret_type = maybe_or(val[key], "type", "")
target = maybe_or(val[key], "target", "") target = maybe(val[key], "target")
if isinstance(target, str):
target = [target]
if not isinstance(target, list):
target = []
options = maybe_or(val[key], "options", "") options = maybe_or(val[key], "options", "")
if options is None: if options is None:
options = "" options = ""
secrets.append( secrets.append(cls(name, str(secret_type), target, str(options)))
cls(name, str(secret_type), str(target), str(options))
)
return secrets return secrets
def command(self) -> str: def command(self) -> str:
"""Option for podman container create.""" """Option for podman container create."""
cmd = ( cmd = ""
f"--secret {self.name},:" for target in self.target:
cmd += (
f"\t--secret {self.name},:"
f"type={self.secret_type}," f"type={self.secret_type},"
f"target={self.target}" f"target={target}"
) )
# Not a password, ruff... # Not a password, ruff...
if self.secret_type == "mount" and self.options != "": # noqa: S105 has_option = self.secret_type == "mount" # noqa: S105
has_option = has_option and self.options != ""
if has_option:
cmd = f"{cmd},{self.options}" cmd = f"{cmd},{self.options}"
cmd += " \\\n"
return cmd return cmd
@ -506,6 +525,7 @@ class Image:
registry: str registry: str
image: str image: str
tag: str tag: str
cmd: str
@classmethod @classmethod
def from_json(cls, val: ConfigValue, logger: Log) -> Self | None: def from_json(cls, val: ConfigValue, logger: Log) -> Self | None:
@ -516,10 +536,13 @@ class Image:
registry = maybe_or(val, "registry", "") registry = maybe_or(val, "registry", "")
image = maybe_or(val, "image", "") image = maybe_or(val, "image", "")
tag = maybe_or(val, "tag", "") tag = maybe_or(val, "tag", "")
return cls(str(registry), str(image), str(tag)) cmd = maybe_or(val, "command", "")
return cls(str(registry), str(image), str(tag), cmd)
def command(self) -> str: def command(self) -> str:
"""Option for podman container create.""" """Option for podman container create."""
if self.cmd != "":
return f"{self.registry}/{self.image}:{self.tag} {self.cmd}"
return f"{self.registry}/{self.image}:{self.tag}" return f"{self.registry}/{self.image}:{self.tag}"
@ -585,17 +608,69 @@ class Dns:
return cmd return cmd
@dataclass
class ContainerOptions:
"""Container-Meta settings."""
privileged: bool = False
read_only: bool = False
replace: bool = False
restart: str = "no"
pull_policy: str = "always"
timezone: str = "local"
is_valid: bool = False
@classmethod
def from_json(cls, val: ConfigValue, logger: Log) -> Self:
"""Create from JSON."""
if val is None:
# Should not happen!
return cls()
if not isinstance(val, dict):
logger.log_error("Container config is invalid!")
return cls()
privileged = maybe_or(val, "privileged", _or=False)
read_only = maybe_or(val, "read_only", _or=False)
replace = maybe_or(val, "replace", _or=False)
restart = maybe_or(val, "restart", "no")
pull_policy = maybe_or(val, "pull_policy", "always")
timezone = maybe_or(val, "timezone", "local")
return cls(
privileged,
read_only,
replace,
restart,
pull_policy,
timezone,
is_valid=True,
)
def command(self) -> str:
"""Option for podman conainter create."""
cmd = ""
if self.privileged:
cmd += "\t--privileged \\\n"
if self.read_only:
cmd += "\t--read-only \\\n"
if self.replace:
cmd += "\t--replace \\\n"
if self.restart != "":
cmd += f"\t--restart={self.restart} \\\n"
if self.pull_policy != "":
cmd += f"\t--pull-policy={self.pull_policy} \\\n"
if self.timezone != "":
cmd += f"\t--tz={self.timezone} \\\n"
return ""
class Container: class Container:
"""Container.""" """Container."""
name: str name: str
image: Image image: Image
privileged: bool ct_opts: ContainerOptions
read_only: bool
replace: bool
restart: str
pull_policy: str
timezone: str
network: Network network: Network
dns: Dns dns: Dns
ports: Ports ports: Ports
@ -617,12 +692,9 @@ class Container:
if image is None: if image is None:
logger.log_error("No image set, aborting!") logger.log_error("No image set, aborting!")
return return
privileged = maybe(json, "privileged") ct_opts = ContainerOptions.from_json(json, logger)
read_only = maybe(json, "read_only") if not ct_opts.is_valid:
replace = maybe(json, "replace") return
pull_policy = maybe_or(json, "pull_policy", "always")
restart = maybe_or(json, "restart", "no")
timezone = maybe_or(json, "timezone", "local")
network = maybe(json, "network") network = maybe(json, "network")
dns = maybe(json, "dns") dns = maybe(json, "dns")
ports = maybe(json, "ports") ports = maybe(json, "ports")
@ -633,12 +705,7 @@ class Container:
accounting = maybe(json, "accounting") accounting = maybe(json, "accounting")
self.name = str(name) self.name = str(name)
self.image = Image.from_json(image, logger) self.image = Image.from_json(image, logger)
self.privileged = privileged is not None and bool(privileged) self.ct_opts = ct_opts
self.read_only = read_only is not None and bool(read_only)
self.replace = replace is not None and bool(replace)
self.pull_policy = str(pull_policy)
self.restart = str(restart)
self.timezone = str(timezone)
self.network = Network.from_json(network, logger) self.network = Network.from_json(network, logger)
self.dns = Dns.from_json(dns, logger) self.dns = Dns.from_json(dns, logger)
self.ports = Ports.from_json(ports, logger) self.ports = Ports.from_json(ports, logger)
@ -679,23 +746,15 @@ class Container:
cmd = f"# Create container {self.name}\n" cmd = f"# Create container {self.name}\n"
cmd += "podman container create \\\n" cmd += "podman container create \\\n"
cmd += f"\t--name={self.name} \\\n" cmd += f"\t--name={self.name} \\\n"
if self.privileged: cmd += f"{self.ct_opts.command()}"
cmd += "\t--privileged \\\n"
if self.replace:
cmd += "\t--replace \\\n"
if self.read_only:
cmd += "\t--read-only \\\n"
cmd += f"\t--restart={self.restart} \\\n"
cmd += f"\t--pull={self.pull_policy} \\\n"
cmd += f"\t--tz={self.timezone} \\\n"
cmd += f"{self.network.command()}" cmd += f"{self.network.command()}"
cmd += f"{self.dns.command()}" cmd += f"{self.dns.command()}"
cmd += f"{self.ports.command()}" cmd += f"{self.ports.command()}"
cmd += f"{self.env.command()}" cmd += f"{self.env.command()}"
for secret in self.secrets: for secret in self.secrets:
cmd += f"\t{secret.command()} \\\n" cmd += f"{secret.command()}"
for volume in self.volumes: for volume in self.volumes:
cmd += f"\t{volume.command()} \\\n" cmd += f"{volume.command()}"
for capability in self.capabilities: for capability in self.capabilities:
cmd += f"\t{capability.command()} \\\n" cmd += f"\t{capability.command()} \\\n"
cmd += f"{self.accounting.command()}" cmd += f"{self.accounting.command()}"

View file

@ -14,7 +14,7 @@ from pathlib import Path
from container import ConfigError, Container from container import ConfigError, Container
from log import Log from log import Log
GENERATE_VERSION = "0.0.9" GENERATE_VERSION = "0.0.11"
HEADER = f"""#!/bin/sh HEADER = f"""#!/bin/sh
# This script was generated by containerctl v{GENERATE_VERSION} # This script was generated by containerctl v{GENERATE_VERSION}
# Report bugs with _this script_ to <tenno+containerctl@suij.in> # Report bugs with _this script_ to <tenno+containerctl@suij.in>