Compare commits
No commits in common. "5bcf2f846cdf1ebda669429e42fc6913f3d9678f" and "a601e76a937482fa4244f509857a5364124a6626" have entirely different histories.
5bcf2f846c
...
a601e76a93
4 changed files with 160 additions and 460 deletions
|
@ -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": [
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Reference in a new issue