Compare commits
10 commits
015f2e5a16
...
d1e4452a5d
Author | SHA1 | Date | |
---|---|---|---|
d1e4452a5d | |||
131ff71f34 | |||
2216bf68da | |||
ca9d914449 | |||
6963fdea2a | |||
72062edbf5 | |||
e2f2b3e35f | |||
53622827a0 | |||
7200d09621 | |||
b0df681fea |
4 changed files with 196 additions and 41 deletions
134
containerctl
Executable file
134
containerctl
Executable file
|
@ -0,0 +1,134 @@
|
|||
#!/bin/sh
|
||||
|
||||
BASEDIR="/var/lib/containerctl"
|
||||
CONTAINERDIR="${BASEDIR}/containers"
|
||||
CONFIGDIR="${BASEDIR}/configs"
|
||||
LOGDIR="/var/log/containerctl"
|
||||
TODAY="$(date '+%F')"
|
||||
LOG="${LOGDIR}/${TODAY}"
|
||||
|
||||
log_error()
|
||||
{
|
||||
printf '[%b] (EE) %b\n' "${TODAY}" "${@}" | tee -a "${LOG}"
|
||||
}
|
||||
|
||||
list_dir()
|
||||
{
|
||||
find "${1}" -mindepth 1 -type d
|
||||
}
|
||||
|
||||
list_containers()
|
||||
{
|
||||
list_dir "${CONTAINERDIR}"
|
||||
}
|
||||
|
||||
list_configs()
|
||||
{
|
||||
list_dir "${CONFIGDIR}"
|
||||
}
|
||||
|
||||
exec_script()
|
||||
{
|
||||
container="${1}"
|
||||
script="${2}"
|
||||
|
||||
if [ -z "${container}" ]
|
||||
then
|
||||
log_error "No container passed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${script}" ]
|
||||
then
|
||||
log_error "No script passed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -e "${CONTAINERDIR}/${container}" ]
|
||||
then
|
||||
log_error "${container} does not exist, did you forget to generate it?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -d "${CONTAINERDIR}/${container}" ]
|
||||
then
|
||||
log_error "${container} does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -e "${CONTAINERDIR}/${container}/${script}" ]
|
||||
then
|
||||
log_error "${script} is invalid, maybe try regenerating the container?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -f "${CONTAINERDIR}/${container}/${script}" ]
|
||||
then
|
||||
log_error "${script} is invalid, please recreate the container"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/bin/sh "${CONTAINERDIR}/${container}/${script}" || log_error "${script} failed!"
|
||||
}
|
||||
|
||||
|
||||
generate_container()
|
||||
{
|
||||
config="${1}"
|
||||
|
||||
if [ -z "${config}" ]
|
||||
then
|
||||
log_error "No config was passed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -e "${CONFIGDIR}" ]
|
||||
then
|
||||
log_error "${CONFIGDIR} does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -d "${CONFIGDIR}" ]
|
||||
then
|
||||
log_error "${CONFIGDIR} is not a directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -e "${CONFIGDIR}/${config}" ]
|
||||
then
|
||||
log_error "${config} does not exist!" \
|
||||
"Please check the name and or location of the desired config."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -f "${CONFIGDIR}/${config}" ]
|
||||
then
|
||||
log_error "${config} is not a file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
/usr/bin/env python3 "${BASEDIR}/generate/generate.py" \
|
||||
"${CONFIGDIR}/${config}" "${LOG}" "${CONTAINERDIR}"
|
||||
|
||||
if [ "${?}" = 1 ]
|
||||
then
|
||||
log_error "Container generation failed."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
generate_all()
|
||||
{
|
||||
list_configs | while read -r config
|
||||
do
|
||||
generate_config "$(printf '%b' "${config}" | sed -e "s|${CONFIGDIR}||g")"
|
||||
done
|
||||
}
|
||||
|
||||
case "${1}" in
|
||||
"list-containers") list_containers ;;
|
||||
"list-configs") list_configs ;;
|
||||
"generate-all") generate_all ;;
|
||||
"generate") shift; generate_config "${@}" ;;
|
||||
*) exec_script "${@}" ;;
|
||||
esac
|
|
@ -17,25 +17,13 @@ class ConfigError(Exception):
|
|||
def __init__(self, message: str = "") -> None:
|
||||
"""Create Exception object."""
|
||||
self.message = message
|
||||
self.super.__init__(message)
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Convert Exception object to a string."""
|
||||
return f"Configuration error: {self.message}"
|
||||
|
||||
|
||||
def join(data: list, separator: str = ",") -> str:
|
||||
"""Join a list together."""
|
||||
ret: str = ""
|
||||
x = data.copy()
|
||||
if len(x) == 0:
|
||||
return ret
|
||||
while len(x) > 1:
|
||||
ret += x.pop() + separator
|
||||
ret += x.pop()
|
||||
return ret
|
||||
|
||||
|
||||
def maybe(json: dict, key: str) -> str | dict | list | bool | None:
|
||||
"""Maybe get a value."""
|
||||
try:
|
||||
|
@ -44,20 +32,11 @@ def maybe(json: dict, key: str) -> str | dict | list | bool | None:
|
|||
return None
|
||||
|
||||
|
||||
def command_join(items: list | None) -> str:
|
||||
"""Like join(), but call command()."""
|
||||
joined = ""
|
||||
if items is not None:
|
||||
for item in items:
|
||||
joined += " " + item.command()
|
||||
return joined
|
||||
|
||||
|
||||
def trim(s: str) -> str:
|
||||
"""Remove sequential whitespace."""
|
||||
s = s.replace("\t ", "\t")
|
||||
s = s.replace("\t\\\n", " ")
|
||||
while s.__contains__(" "):
|
||||
while " " in s:
|
||||
s = s.replace(" ", " ")
|
||||
return s
|
||||
|
||||
|
@ -141,7 +120,8 @@ class Secret:
|
|||
cmd = (
|
||||
f"--secret {self.name},type={self.secret_type},target={self.target}"
|
||||
)
|
||||
if self.target == "mount" and self.options != "":
|
||||
# Not a password, ruff...
|
||||
if self.secret_type == "mount" and self.options != "": # noqa: S105
|
||||
cmd = f"{cmd},{self.options}"
|
||||
|
||||
return cmd
|
||||
|
@ -236,10 +216,15 @@ class Ports:
|
|||
def command(self) -> str:
|
||||
"""Option for podman container create."""
|
||||
ports = ""
|
||||
for port in self.tcp_ports:
|
||||
ports += f"\t--port {port}/tcp \\\n"
|
||||
for port in self.udp_ports:
|
||||
ports += f"\t--port {port}/udp \\\n"
|
||||
seperator = " \\\n"
|
||||
ports += seperator.join(
|
||||
[f"\t--port {port}/tcp" for port in self.tcp_ports]
|
||||
)
|
||||
ports += seperator
|
||||
ports += seperator.join(
|
||||
[f"\t--port {port}/udp" for port in self.udp_ports]
|
||||
)
|
||||
ports += seperator
|
||||
return ports
|
||||
|
||||
|
||||
|
@ -269,7 +254,7 @@ class Network:
|
|||
if self.mode == "":
|
||||
return ""
|
||||
cmd = f"--network={self.mode}"
|
||||
opts = join(self.options)
|
||||
opts = ",".join(self.options)
|
||||
if opts != "":
|
||||
cmd += f":{opts}"
|
||||
return cmd
|
||||
|
@ -345,15 +330,15 @@ class Dns:
|
|||
|
||||
def command(self) -> str:
|
||||
"""Option for podman container create."""
|
||||
if len(self.servers) == 0 and self.serach == "":
|
||||
if len(self.servers) == 0 and self.search == "":
|
||||
return ""
|
||||
if len(self.servers) == 0:
|
||||
return f"--dns-search={self.search}"
|
||||
if self.search == "":
|
||||
return f"--dns={join(self.servers)}"
|
||||
return f"--dns={','.join(self.servers)}"
|
||||
|
||||
cmd = f"--dns-search={self.search} \\\n\t--dns="
|
||||
cmd += join(self.servers)
|
||||
cmd += ",".join(self.servers)
|
||||
|
||||
return cmd
|
||||
|
||||
|
@ -447,7 +432,7 @@ class Container:
|
|||
|
||||
def create_container(self) -> str:
|
||||
"""Generate podman container create command."""
|
||||
cmd = f"# Create container {self.name}"
|
||||
cmd = f"# Create container {self.name}\n"
|
||||
cmd += "podman container crate \\\n"
|
||||
cmd += f"\t--name={self.name} \\\n"
|
||||
if self.privileged:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"""Generate the control files.."""
|
||||
|
||||
import json
|
||||
import stat
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
@ -48,17 +49,21 @@ def create_container_from_config(data: dict, log: Log) -> Container | None:
|
|||
|
||||
def main() -> None:
|
||||
"""Run the program."""
|
||||
argc_threshold = 2
|
||||
if len(sys.argv) < argc_threshold:
|
||||
config_threshold = 2
|
||||
log_threshold = 3
|
||||
if len(sys.argv) < config_threshold:
|
||||
logger = Log("/dev/stdout")
|
||||
logger.log_error("No arguments passed!")
|
||||
sys.exit(1)
|
||||
config_file = ""
|
||||
log_file = ""
|
||||
if len(sys.argv) >= argc_threshold:
|
||||
base = "/var/lib/containerctl/containers"
|
||||
if len(sys.argv) >= config_threshold:
|
||||
config_file = sys.argv[1]
|
||||
if len(sys.argv) > argc_threshold:
|
||||
if len(sys.argv) >= log_threshold:
|
||||
log_file = sys.argv[2]
|
||||
if len(sys.argv) > log_threshold:
|
||||
base = sys.argv[3]
|
||||
logger = Log(log_file)
|
||||
data = load_container_config(Path(config_file), logger)
|
||||
if data is None:
|
||||
|
@ -66,11 +71,42 @@ def main() -> None:
|
|||
sys.exit(1)
|
||||
try:
|
||||
ct = create_container_from_config(data, logger)
|
||||
except Exception as e: # noqa: BLE001
|
||||
except ConfigError as e:
|
||||
logger.log_error(e)
|
||||
sys.exit(1)
|
||||
print(ct)
|
||||
print(vars(ct))
|
||||
scripts = {
|
||||
"create-volumes": ct.create_volumes,
|
||||
"create-secrets": ct.create_secrets,
|
||||
"create-environment": ct.create_environment,
|
||||
"remove-volumes": ct.remove_volumes,
|
||||
"remove-secrets": ct.remove_secrets,
|
||||
"create": ct.create_container,
|
||||
"remove": ct.remove_container,
|
||||
"purge": ct.purge_container,
|
||||
"start": ct.start_container,
|
||||
"stop": ct.stop_container,
|
||||
"restart": ct.restart_container,
|
||||
"upgrade": ct.upgrade_container,
|
||||
}
|
||||
base = f"{base}/{ct.name}"
|
||||
if not Path(base).exists():
|
||||
Path(base).mkdir()
|
||||
for script, method in scripts.items():
|
||||
s = Path(f"{base}/{script}")
|
||||
with s.open("w+", encoding="utf-8") as f:
|
||||
f.write("#!/bin/sh\n")
|
||||
if script == "create":
|
||||
f.write(ct.create_volumes())
|
||||
f.write(ct.create_secrets())
|
||||
f.write(ct.create_environment())
|
||||
f.write(method())
|
||||
s.chmod(
|
||||
stat.S_IRWXU
|
||||
| stat.S_IRGRP
|
||||
| stat.S_IXGRP
|
||||
| stat.S_IROTH
|
||||
| stat.S_IXOTH,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -64,7 +64,7 @@ class Log:
|
|||
print(msg)
|
||||
if self.logfile == Path("/dev/stdout"):
|
||||
return
|
||||
with self.logfile.open("a+") as f:
|
||||
with self.logfile.open("a+", encoding="utf-8") as f:
|
||||
f.write(msg)
|
||||
|
||||
def timestamp(self) -> str:
|
||||
|
|
Loading…
Add table
Reference in a new issue