1
0
Fork 0

Compare commits

..

No commits in common. "5bcf2f846cdf1ebda669429e42fc6913f3d9678f" and "a601e76a937482fa4244f509857a5364124a6626" have entirely different histories.

4 changed files with 160 additions and 460 deletions

View file

@ -39,9 +39,6 @@
"restart": { "restart": {
"type": "string" "type": "string"
}, },
"timezone": {
"type": "string"
},
"network": { "network": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -137,66 +134,6 @@
"add", "add",
"drop" "drop"
] ]
},
"accounting": {
"type": "object",
"propeties": {
"cgroup": {
"type": "object",
"properties": {
"config": {
"type": "string"
},
"parent": {
"type": "string"
},
"namespace": {
"type": "string"
},
"how": {
"type": "string"
}
}
},
"cpu": {
"type": "object",
"properties": {
"period": {
"type": "string"
},
"quota": {
"type": "string"
},
"shares": {
"type": "string"
},
"number": {
"type": "string"
},
"cpuset": {
"cpus": {
"type": "string"
},
"mems": {
"type": "string"
}
}
}
},
"memory": {
"type": "object",
"properties": {
"limit": {
"type": "string"
},
"reservation": {
"type": "string"
},
"swap": {
"type": "string"
}
}
}
} }
}, },
"required": [ "required": [

View file

@ -10,7 +10,6 @@
"replace": false, "replace": false,
"pull_policy": "always", "pull_policy": "always",
"restart": "always", "restart": "always",
"timezone": "Etc/UTC",
"network": { "network": {
"mode": "podman1", "mode": "podman1",
"options": [] "options": []
@ -43,34 +42,11 @@
} }
}, },
"volumes": { "volumes": {
"/etc": "/etc:ro,noexec", "etc": "/etc:ro,noexec",
"var": "/var" "var": "/var"
}, },
"capabilities": { "capabilities": {
"add": [ "NET_RAW" ], "add": [ "NET_RAW" ],
"drop": [ "CAP_SYS_ADMIN" ] "drop": [ "CAP_SYS_ADMIN" ]
},
"accounting": {
"cgroup": {
"config": ["memory.high=1073741824"],
"parent": "/example-parent",
"namespace": "host",
"how": "enabled"
},
"cpu": {
"period": "100000",
"quota": "100000",
"shares": "1024",
"number": "4",
"cpuset": {
"cpus": "0-3,11-15",
"mems": "0,1"
}
},
"memory": {
"limit": "16g",
"reservation": "512m",
"swap": "20g"
}
} }
} }

View file

@ -29,7 +29,7 @@ class ConfigError(Exception):
return f"Configuration error: {self.message}" return f"Configuration error: {self.message}"
def maybe(json: dict, key: str) -> ConfigValue: def maybe(json: dict, key: str) -> str | dict | list | bool | None:
"""Maybe get a value.""" """Maybe get a value."""
try: try:
return json[key] return json[key]
@ -37,14 +37,6 @@ def maybe(json: dict, key: str) -> ConfigValue:
return None return None
def maybe_or(json: dict, key: str, _or: ConfigValue) -> ConfigValue:
"""Maybe get a value, but return _or if it is None."""
val = maybe(json, key)
if val is None or not isinstance(val, type(_or)):
return _or
return val
def trim(s: str) -> str: def trim(s: str) -> str:
"""Remove sequential whitespace.""" """Remove sequential whitespace."""
s = s.replace("\t ", "\t") s = s.replace("\t ", "\t")
@ -54,207 +46,6 @@ def trim(s: str) -> str:
return s return s
@dataclass
class Cgroup:
"""cgroup Config."""
config: list
parent: str
namespace: str
how: str
@classmethod
def from_json(cls, val: ConfigValue, logger: Log) -> Self:
"""Create from JSON."""
if val is None:
return cls([], "", "", "")
if not isinstance(val, dict):
logger.log_warning("cgroup Config is invalid!")
return cls([], "", "", "")
config = maybe_or(val, "config", [])
parent = maybe_or(val, "parent", "")
namespace = maybe_or(val, "namespace", "")
how = maybe_or(val, "how", "")
if not isinstance(config, list):
logger.log_warning("Config key in cgroup Config is invalid!")
config = []
if not isinstance(parent, str):
logger.log_warning("Parent key in cgroup Config is invalid!")
parent = ""
if not isinstance(namespace, str):
logger.log_warning("Namespace key in cgroup Config is invalid!")
namespace = ""
if not isinstance(how, str):
logger.log_warning("How key in cgroup Config is invalid!")
how = ""
if how == "split" and parent != "":
logger.log_warning(
"Split cgroups can not be combined with a cgroup parent!"
)
parent = ""
return cls(config, parent, namespace, how)
def command(self) -> str:
"""Option for podman container create."""
cmd = ""
seperator = " \\\n"
cmd += seperator.join(
[f"\t--cgroup-conf={option}{seperator}" for option in self.config]
)
if self.parent != "":
cmd += f"\t--cgroup-parent={self.parent}{seperator}"
if self.namespace != "":
cmd += f"\t--cgroupns={self.namespace}{seperator}"
if self.how != "":
cmd += f"\t--cgroups={self.how}{seperator}"
return cmd
@dataclass
class Cpu:
"""CPU Accounting."""
period: str
quota: str
shares: str
number: str
cpuset_cpus: str
cpuset_mems: str
@classmethod
def from_json(cls, val: ConfigValue, logger: Log) -> Self:
"""Create from JSON."""
if val is None:
return cls("", "", "", "", "", "")
if not isinstance(val, dict):
logger.log_warning("cpu Config is invalid!")
return cls("", "", "", "", "", "")
period = maybe_or(val, "period", "")
quota = maybe_or(val, "quota", "")
shares = maybe_or(val, "shares", "")
number = maybe_or(val, "number", "")
cpuset = maybe_or(val, "cpuset", {})
cpuset_cpus = ""
cpuset_mems = ""
if len(cpuset) != 0:
cpuset_cpus += maybe_or(cpuset, "cpus", "")
cpuset_mems += maybe_or(cpuset, "mems", "")
return cls(
period,
quota,
shares,
number,
cpuset_cpus,
cpuset_mems,
)
def command(self) -> str:
"""Option for podman container create."""
cmd = ""
seperator = " \\\n"
if self.period != "":
cmd += f"\t--cpu-period={self.period}{seperator}"
if self.quota != "":
cmd += f"\t--cpu-quota={self.quota}{seperator}"
if self.shares != "":
cmd += f"\t--cpu-shares={self.shares}{seperator}"
if self.number != "":
cmd += f"\t--cpus={self.number}{seperator}"
if self.cpuset_cpus != "":
cmd += f"\t--cpuset-cpus={self.cpuset_cpus}{seperator}"
if self.cpuset_mems != "":
cmd += f"\t--cpuset-mems={self.cpuset_mems}{seperator}"
return cmd
@dataclass
class Memory:
"""Memory accounting."""
limit: str
reservation: str
swap: str
@classmethod
def from_json(cls, val: ConfigValue, logger: Log) -> Self:
"""Create from JSON."""
if val is None:
return cls("", "", "")
if not isinstance(val, dict):
logger.log_warning("memory Config is invalid!")
return cls("", "", "")
limit = maybe_or(val, "limit", "")
reservation = maybe_or(val, "reservation", "")
swap = maybe_or(val, "swap", "")
if limit == "":
return cls("", "", "")
return cls(limit, reservation, swap)
def command(self) -> str:
"""Option for podman container create."""
if self.limit == "":
return ""
cmd = ""
seperator = " \\\n"
cmd += f"\t--memory={self.limit}{seperator}"
if self.reservation != "":
cmd += f"\t--memory-reservation={self.reservation}{seperator}"
if self.swap != "":
cmd += f"\t--memory-swap={self.swap}{seperator}"
return cmd
@dataclass
class Accounting:
"""Resource Accounting."""
cgroup: Cgroup
cpu: Cpu
memory: Memory
@classmethod
def from_json(cls, data: ConfigValue, logger: Log) -> Self:
"""Create from JSON."""
if data is None:
return cls(
Cgroup.from_json(None, logger),
Cpu.from_json(None, logger),
Memory.from_json(None, logger),
)
cgroup_data = maybe(data, "cgroup")
cpu_data = maybe(data, "cpu")
memory_data = maybe(data, "memory")
cgroup = Cgroup.from_json(cgroup_data, logger)
cpu = Cpu.from_json(cpu_data, logger)
memory = Memory.from_json(memory_data, logger)
return cls(cgroup, cpu, memory)
def command(self) -> str:
"""Options for podman container create."""
cgroup = self.cgroup.command()
cpu = self.cpu.command()
memory = self.memory.command()
return cgroup + cpu + memory
@dataclass @dataclass
class Volume: class Volume:
"""Container Volume.""" """Container Volume."""
@ -331,9 +122,9 @@ class Secret:
logger.log_warning(f"Secret {key} is malformed!") logger.log_warning(f"Secret {key} is malformed!")
continue continue
name = key name = key
secret_type = maybe_or(val[key], "type", "") secret_type = maybe(val[key], "type")
target = maybe_or(val[key], "target", "") target = maybe(val[key], "target")
options = maybe_or(val[key], "options", "") options = maybe(val[key], "options")
if options is None: if options is None:
options = "" options = ""
secrets.append( secrets.append(
@ -503,9 +294,9 @@ class Image:
if val is None or not isinstance(val, dict): if val is None or not isinstance(val, dict):
logger.log_error("Image key either not present or malformed!") logger.log_error("Image key either not present or malformed!")
return None return None
registry = maybe_or(val, "registry", "") registry = maybe(val, "registry")
image = maybe_or(val, "image", "") image = maybe(val, "image")
tag = maybe_or(val, "tag", "") tag = maybe(val, "tag")
return cls(str(registry), str(image), str(tag)) return cls(str(registry), str(image), str(tag))
def command(self) -> str: def command(self) -> str:
@ -550,7 +341,7 @@ class Dns:
if val is None or not isinstance(val, dict): if val is None or not isinstance(val, dict):
logger.log_error("DNS Key is either missing or malformed!") logger.log_error("DNS Key is either missing or malformed!")
return cls([], "") return cls([], "")
search = maybe_or(val, "search", "") search = maybe(val, "search")
servers = maybe(val, "servers") servers = maybe(val, "servers")
if not isinstance(servers, list): if not isinstance(servers, list):
logger.log_error("Servers key is not an array!") logger.log_error("Servers key is not an array!")
@ -582,7 +373,6 @@ class Container:
replace: bool replace: bool
restart: str restart: str
pull_policy: str pull_policy: str
timezone: str
network: Network network: Network
dns: Dns dns: Dns
ports: Ports ports: Ports
@ -590,7 +380,6 @@ class Container:
secrets: list | None secrets: list | None
volumes: list | None volumes: list | None
capabilities: list | None capabilities: list | None
accounting: Accounting
def __init__(self, json: dict, logger: Log | None = None) -> None: def __init__(self, json: dict, logger: Log | None = None) -> None:
"""Create from JSON.""" """Create from JSON."""
@ -607,9 +396,12 @@ class Container:
privileged = maybe(json, "privileged") privileged = maybe(json, "privileged")
read_only = maybe(json, "read_only") read_only = maybe(json, "read_only")
replace = maybe(json, "replace") replace = maybe(json, "replace")
pull_policy = maybe_or(json, "pull_policy", "always") pull_policy = maybe(json, "pull_policy")
restart = maybe_or(json, "restart", "no") if pull_policy is None:
timezone = maybe_or(json, "timezone", "local") pull_policy = "always"
restart = maybe(json, "restart")
if restart is None:
restart = "no"
network = maybe(json, "network") network = maybe(json, "network")
dns = maybe(json, "dns") dns = maybe(json, "dns")
ports = maybe(json, "ports") ports = maybe(json, "ports")
@ -617,7 +409,6 @@ class Container:
secrets = maybe(json, "secrets") secrets = maybe(json, "secrets")
volumes = maybe(json, "volumes") volumes = maybe(json, "volumes")
capabilities = maybe(json, "capabilities") capabilities = maybe(json, "capabilities")
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.privileged = privileged is not None and bool(privileged)
@ -625,7 +416,6 @@ class Container:
self.replace = replace is not None and bool(replace) self.replace = replace is not None and bool(replace)
self.pull_policy = str(pull_policy) self.pull_policy = str(pull_policy)
self.restart = str(restart) 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)
@ -635,7 +425,6 @@ class Container:
self.secrets = Secret.from_json(secrets, logger) self.secrets = Secret.from_json(secrets, logger)
self.volumes = Volume.from_json(volumes, logger) self.volumes = Volume.from_json(volumes, logger)
self.capabilities = Capability.from_json(capabilities, logger) self.capabilities = Capability.from_json(capabilities, logger)
self.accounting = Accounting.from_json(accounting, logger)
def create_volumes(self) -> str: def create_volumes(self) -> str:
"""Generate podman volume create commands.""" """Generate podman volume create commands."""
@ -674,7 +463,6 @@ class Container:
cmd += "\t--read-only \\\n" cmd += "\t--read-only \\\n"
cmd += f"\t--restart={self.restart} \\\n" cmd += f"\t--restart={self.restart} \\\n"
cmd += f"\t--pull={self.pull_policy} \\\n" cmd += f"\t--pull={self.pull_policy} \\\n"
cmd += f"\t--tz={self.timezone} \\\n"
cmd += f"\t{self.network.command()} \\\n" cmd += f"\t{self.network.command()} \\\n"
cmd += f"\t{self.dns.command()} \\\n" cmd += f"\t{self.dns.command()} \\\n"
cmd += f"{self.ports.command()}" cmd += f"{self.ports.command()}"
@ -686,7 +474,6 @@ class Container:
cmd += f"\t{volume.command()} \\\n" cmd += f"\t{volume.command()} \\\n"
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"\t{self.image.command()}\n" cmd += f"\t{self.image.command()}\n"
return cmd return cmd

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.4" GENERATE_VERSION = "0.0.2"
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>