178 lines
5.9 KiB
Python
178 lines
5.9 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
SOURCE_REPOSITORY = "jens/unraid-appops-gateway"
|
|
HELPER_NAME = "itworx-controller-artifact-promoter-modern"
|
|
CONTROLLER_VERSION = "2.1.21"
|
|
HELPER_LABEL = "itworx.controller.promoter"
|
|
_REVISION = re.compile(r"^[0-9a-f]{40}$")
|
|
_IMAGE_ID = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
_CONTAINER_ID = re.compile(r"^[0-9a-f]{64}$")
|
|
_VERSION = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$")
|
|
|
|
|
|
def helper_container_spec(
|
|
*,
|
|
revision: str,
|
|
image_id: str,
|
|
current_controller_id: str | None = None,
|
|
current_controller_revision: str | None = None,
|
|
current_controller_tree: str | None = None,
|
|
current_controller_version: str | None = None,
|
|
) -> dict[str, Any]:
|
|
if not _REVISION.fullmatch(revision):
|
|
raise ValueError("controller_promoter_revision_invalid")
|
|
if not _IMAGE_ID.fullmatch(image_id):
|
|
raise ValueError("controller_promoter_image_invalid")
|
|
|
|
bootstrap_values = (
|
|
current_controller_id,
|
|
current_controller_revision,
|
|
current_controller_tree,
|
|
current_controller_version,
|
|
)
|
|
if any(value is not None for value in bootstrap_values):
|
|
if (
|
|
not _CONTAINER_ID.fullmatch(current_controller_id or "")
|
|
or not _REVISION.fullmatch(current_controller_revision or "")
|
|
or not _REVISION.fullmatch(current_controller_tree or "")
|
|
or not _VERSION.fullmatch(current_controller_version or "")
|
|
):
|
|
raise ValueError("controller_promoter_bootstrap_binding_invalid")
|
|
|
|
command = f"""set -Eeuo pipefail
|
|
phase=shell_setup
|
|
work=''
|
|
diagnostic_emitted=0
|
|
emit_failure() {{
|
|
local exit_code="$1"
|
|
if (( diagnostic_emitted == 0 )); then
|
|
printf 'APPOPS_CONTROLLER_PROMOTION_DIAGNOSTIC code=helper_failed phase=%s exitCode=%s\\n' "$phase" "$exit_code" >&2
|
|
diagnostic_emitted=1
|
|
fi
|
|
}}
|
|
on_error() {{
|
|
local exit_code=$?
|
|
trap - ERR
|
|
emit_failure "$exit_code"
|
|
exit "$exit_code"
|
|
}}
|
|
cleanup() {{
|
|
local exit_code=$?
|
|
local cleanup_exit_code=0
|
|
trap - EXIT
|
|
phase=cleanup
|
|
if [[ -n "$work" ]]; then
|
|
if rm -rf -- "$work"; then
|
|
:
|
|
else
|
|
cleanup_exit_code=$?
|
|
emit_failure "$cleanup_exit_code"
|
|
if (( exit_code == 0 )); then exit_code=$cleanup_exit_code; fi
|
|
fi
|
|
fi
|
|
exit "$exit_code"
|
|
}}
|
|
trap on_error ERR
|
|
umask 077
|
|
revision='{revision}'
|
|
version='{CONTROLLER_VERSION}'
|
|
image_id='{image_id}'
|
|
repo='/gitea-repositories/jens/unraid-appops-gateway.git'
|
|
phase=tmp_root
|
|
[[ \"$(trap - ERR; stat -c '%a:%u:%g' /tmp)\" == '1777:0:0' ]]
|
|
phase=workspace_create
|
|
work=\"$(trap - ERR; mktemp -d -p /tmp itworx-controller-artifact-promoter-{revision[:12]}.XXXXXX)\"
|
|
phase=workspace_validate
|
|
[[ -d \"$work\" && ! -L \"$work\" && \"$(stat -c '%u:%a' \"$work\")\" == '0:700' ]]
|
|
trap cleanup EXIT
|
|
phase=tool_git
|
|
command -v git >/dev/null
|
|
phase=tool_docker
|
|
command -v docker >/dev/null
|
|
phase=tool_compose
|
|
docker compose version >/dev/null
|
|
phase=tool_python
|
|
command -v python3 >/dev/null
|
|
phase=tool_tar
|
|
command -v tar >/dev/null
|
|
phase=tool_curl
|
|
command -v curl >/dev/null
|
|
phase=source_commit
|
|
git --git-dir=\"$repo\" cat-file -e \"$revision^{{commit}}\"
|
|
phase=source_tree
|
|
tree=\"$(trap - ERR; git --git-dir=\"$repo\" rev-parse \"$revision^{{tree}}\")\"
|
|
phase=source_archive
|
|
git --git-dir=\"$repo\" archive --format=tar \"$revision\" \\
|
|
ops/gitea-deploy-broker \\
|
|
scripts/prepare_controller_artifact.py \\
|
|
scripts/reconcile_controller_state.py \\
|
|
scripts/prepare_controller_hub_release_volume.py \\
|
|
scripts/vendor \\
|
|
scripts/run_repopulse_adoption.py \
|
|
scripts/upgrade_deployment_controller.sh > \"$work/controller.tar\"
|
|
phase=archive_permissions
|
|
chmod 0600 \"$work/controller.tar\"
|
|
phase=archive_digest
|
|
archive_sha=\"$(trap - ERR; sha256sum \"$work/controller.tar\" | cut -d ' ' -f 1)\"
|
|
phase=upgrade_script
|
|
git --git-dir=\"$repo\" show \"$revision:scripts/upgrade_deployment_controller.sh\" > \"$work/upgrade.sh\"
|
|
phase=upgrade_permissions
|
|
chmod 0700 \"$work/upgrade.sh\"
|
|
phase=upgrade
|
|
bash \"$work/upgrade.sh\" \\
|
|
--archive \"$work/controller.tar\" \\
|
|
--archive-sha256 \"$archive_sha\" \\
|
|
--source-repository '{SOURCE_REPOSITORY}' \\
|
|
--source-revision \"$revision\" \\
|
|
--source-tree \"$tree\" \\
|
|
--controller-version \"$version\" \\
|
|
--image-id \"$image_id\" \\
|
|
--confirm UPGRADE_DEPLOYMENT_CONTROLLER_V2
|
|
"""
|
|
|
|
labels = {
|
|
HELPER_LABEL: "true",
|
|
"itworx.source.repository": SOURCE_REPOSITORY,
|
|
"itworx.source.revision": revision,
|
|
"itworx.controller.version": CONTROLLER_VERSION,
|
|
}
|
|
if current_controller_id is not None:
|
|
labels.update(
|
|
{
|
|
"itworx.controller.bootstrap.target.id": current_controller_id,
|
|
"itworx.controller.bootstrap.target.image": image_id,
|
|
"itworx.controller.bootstrap.target.revision": current_controller_revision or "",
|
|
"itworx.controller.bootstrap.target.tree": current_controller_tree or "",
|
|
"itworx.controller.bootstrap.target.version": current_controller_version or "",
|
|
}
|
|
)
|
|
|
|
return {
|
|
"Image": image_id,
|
|
"User": "0:0",
|
|
"Cmd": ["/bin/bash", "-lc", command],
|
|
"Labels": labels,
|
|
"HostConfig": {
|
|
"Binds": [
|
|
"/mnt/cache:/mnt/cache",
|
|
"/mnt/user:/mnt/user",
|
|
"/var/lib/docker:/var/lib/docker:ro",
|
|
"/srv/example/appdata/gitea/git/repositories:/gitea-repositories:ro",
|
|
"/var/run/docker.sock:/var/run/docker.sock",
|
|
],
|
|
"NetworkMode": "host",
|
|
"Privileged": False,
|
|
"ReadonlyRootfs": True,
|
|
"CpuPeriod": 100000,
|
|
"CpuQuota": 100000,
|
|
"Memory": 1024 * 1024 * 1024,
|
|
"PidsLimit": 128,
|
|
"Tmpfs": {"/tmp": "rw,nosuid,nodev,size=268435456,mode=1777"},
|
|
"SecurityOpt": ["no-new-privileges:true"],
|
|
"RestartPolicy": {"Name": "no"},
|
|
},
|
|
}
|