Merge pull request 'Recreate containers when a deploy rebuilds their image' (#2) from redeploy-fix into main
Reviewed-on: #2
This commit is contained in:
commit
ba0a0155c8
1 changed files with 81 additions and 7 deletions
88
panelctl.sh
88
panelctl.sh
|
|
@ -17,6 +17,7 @@ PANEL_GROUP="${PANEL_GROUP:-panelroutes}"
|
||||||
# as KEY=VALUE words (passed to compose via env(1), never sourced).
|
# as KEY=VALUE words (passed to compose via env(1), never sourced).
|
||||||
CURRENT_APP=""
|
CURRENT_APP=""
|
||||||
COMPOSE_ARGS=()
|
COMPOSE_ARGS=()
|
||||||
|
ORPHAN_ARGS=()
|
||||||
APP_ENV_ARGS=()
|
APP_ENV_ARGS=()
|
||||||
APP_ENV_KEYS=()
|
APP_ENV_KEYS=()
|
||||||
|
|
||||||
|
|
@ -198,6 +199,7 @@ load_app() {
|
||||||
local manifest
|
local manifest
|
||||||
manifest="$(app_manifest "${name}")"
|
manifest="$(app_manifest "${name}")"
|
||||||
[[ -f "${manifest}" ]] || fail "app '${name}' does not exist"
|
[[ -f "${manifest}" ]] || fail "app '${name}' does not exist"
|
||||||
|
unset APP_COMPOSE_PROJECT
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
source "${manifest}"
|
source "${manifest}"
|
||||||
|
|
||||||
|
|
@ -235,10 +237,20 @@ app_ports_override() {
|
||||||
echo "${STACKS_DIR}/$1/.panel-ports.yaml"
|
echo "${STACKS_DIR}/$1/.panel-ports.yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# The compose project the app's containers belong to: the app's name, unless
|
||||||
|
# the manifest keeps an older one (see adopt_legacy_project).
|
||||||
|
compose_project() {
|
||||||
|
echo "${APP_COMPOSE_PROJECT:-${CURRENT_APP}}"
|
||||||
|
}
|
||||||
|
|
||||||
# The app's compose file followed by whichever generated overrides exist.
|
# The app's compose file followed by whichever generated overrides exist.
|
||||||
refresh_compose_args() {
|
refresh_compose_args() {
|
||||||
local override
|
local override
|
||||||
COMPOSE_ARGS=(-f "${APP_COMPOSE_FILE}")
|
COMPOSE_ARGS=(-p "$(compose_project)" -f "${APP_COMPOSE_FILE}")
|
||||||
|
# Other git apps may share a kept legacy project; their containers would
|
||||||
|
# look like orphans.
|
||||||
|
ORPHAN_ARGS=(--remove-orphans)
|
||||||
|
[[ "$(compose_project)" == "${CURRENT_APP}" ]] || ORPHAN_ARGS=()
|
||||||
for override in "$(app_ports_override "${CURRENT_APP}")" "$(app_env_override "${CURRENT_APP}")"; do
|
for override in "$(app_ports_override "${CURRENT_APP}")" "$(app_env_override "${CURRENT_APP}")"; do
|
||||||
if [[ -f "${override}" ]]; then
|
if [[ -f "${override}" ]]; then
|
||||||
COMPOSE_ARGS+=(-f "${override}")
|
COMPOSE_ARGS+=(-f "${override}")
|
||||||
|
|
@ -597,6 +609,56 @@ cmd_render_route() {
|
||||||
log info "rendered route ${route_file}"
|
log info "rendered route ${route_file}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Before panelctl passed -p, compose named the project after the compose
|
||||||
|
# file's directory, so every git app (repo/compose.yaml) shared the project
|
||||||
|
# "repo". Move the app's old containers out of the way so they are recreated
|
||||||
|
# under its own name. Named volumes are prefixed with the project name, so an
|
||||||
|
# app whose containers use them keeps its old project instead.
|
||||||
|
adopt_legacy_project() {
|
||||||
|
[[ -z "${APP_COMPOSE_PROJECT:-}" ]] || return 0
|
||||||
|
local dir id project legacy="" volumes
|
||||||
|
local ids=()
|
||||||
|
dir="$(cd "$(dirname "${APP_COMPOSE_FILE}")" 2>/dev/null && pwd -P)" || return 0
|
||||||
|
|
||||||
|
while IFS='|' read -r id project; do
|
||||||
|
[[ -n "${id}" && -n "${project}" && "${project}" != "${CURRENT_APP}" ]] || continue
|
||||||
|
ids+=("${id}")
|
||||||
|
legacy="${project}"
|
||||||
|
done < <(run_podman ps --all --filter "label=com.docker.compose.project.working_dir=${dir}" \
|
||||||
|
--format '{{.ID}}|{{index .Labels "com.docker.compose.project"}}' 2>/dev/null || true)
|
||||||
|
[[ ${#ids[@]} -gt 0 ]] || return 0
|
||||||
|
|
||||||
|
volumes="$(run_podman inspect --format '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}} {{end}}{{end}}' "${ids[@]}" 2>/dev/null | xargs || true)"
|
||||||
|
if [[ -n "${volumes}" ]]; then
|
||||||
|
echo "APP_COMPOSE_PROJECT=\"${legacy}\"" >>"$(app_manifest "${CURRENT_APP}")"
|
||||||
|
APP_COMPOSE_PROJECT="${legacy}"
|
||||||
|
refresh_compose_args
|
||||||
|
echo "Keeping compose project '${legacy}', because the app's containers use named volumes (${volumes})"
|
||||||
|
log warning "app '${CURRENT_APP}' keeps compose project '${legacy}' because its containers use named volumes"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Moving the app's containers from compose project '${legacy}' to '${CURRENT_APP}'"
|
||||||
|
run_podman rm --force "${ids[@]}" >/dev/null || fail "could not remove the old containers"
|
||||||
|
# The shared pod goes away once no other app's containers are left in it.
|
||||||
|
run_podman pod rm "pod_${legacy}" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# True when a container of the app runs an older image than its tag now
|
||||||
|
# points at, e.g. after a rebuild. podman-compose only recreates containers
|
||||||
|
# when the compose file itself changes, so without this a push that only
|
||||||
|
# changes code would build a new image and keep running the old one.
|
||||||
|
containers_outdated() {
|
||||||
|
local id image current
|
||||||
|
while IFS='|' read -r id image; do
|
||||||
|
[[ -n "${id}" && -n "${image}" ]] || continue
|
||||||
|
current="$(run_podman image inspect --format '{{.Id}}' "${image}" 2>/dev/null)" || continue
|
||||||
|
[[ "${current}" == "${id}"* || "${id}" == "${current}"* ]] || return 0
|
||||||
|
done < <(run_podman ps --all --filter "label=com.docker.compose.project=$(compose_project)" \
|
||||||
|
--format '{{.ImageID}}|{{.Image}}' 2>/dev/null || true)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
cmd_deploy() {
|
cmd_deploy() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
validate_name "${name}"
|
validate_name "${name}"
|
||||||
|
|
@ -607,10 +669,21 @@ cmd_deploy() {
|
||||||
cmd_render_route "${name}"
|
cmd_render_route "${name}"
|
||||||
prepare_ports_override "${name}"
|
prepare_ports_override "${name}"
|
||||||
prepare_env_override "${name}"
|
prepare_env_override "${name}"
|
||||||
|
adopt_legacy_project
|
||||||
|
|
||||||
# Stream compose output as it happens (the panel shows it live in the
|
# Stream compose output as it happens (the panel shows it live in the
|
||||||
# deployment log) and keep a copy in the journal.
|
# deployment log) and keep a copy in the journal.
|
||||||
if ! run_compose "${COMPOSE_ARGS[@]}" up -d --build --remove-orphans 2>&1 | tee >(journal_copy); then
|
if ! run_compose "${COMPOSE_ARGS[@]}" build 2>&1 | tee >(journal_copy); then
|
||||||
|
log err "Deployment failed for app '${name}'"
|
||||||
|
fail "image build failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local up_args=(up -d --no-build "${ORPHAN_ARGS[@]}")
|
||||||
|
if containers_outdated; then
|
||||||
|
echo "Images changed, recreating the app's containers"
|
||||||
|
up_args+=(--force-recreate)
|
||||||
|
fi
|
||||||
|
if ! run_compose "${COMPOSE_ARGS[@]}" "${up_args[@]}" 2>&1 | tee >(journal_copy); then
|
||||||
log err "Deployment failed for app '${name}'"
|
log err "Deployment failed for app '${name}'"
|
||||||
fail "compose up failed"
|
fail "compose up failed"
|
||||||
fi
|
fi
|
||||||
|
|
@ -625,11 +698,12 @@ cmd_restart() {
|
||||||
|
|
||||||
log info "Restarting app '${name}'"
|
log info "Restarting app '${name}'"
|
||||||
|
|
||||||
run_compose "${COMPOSE_ARGS[@]}" down --remove-orphans || fail "compose down failed"
|
adopt_legacy_project
|
||||||
|
run_compose "${COMPOSE_ARGS[@]}" down "${ORPHAN_ARGS[@]}" || fail "compose down failed"
|
||||||
prepare_ports_override "${name}"
|
prepare_ports_override "${name}"
|
||||||
prepare_env_override "${name}"
|
prepare_env_override "${name}"
|
||||||
|
|
||||||
if ! run_compose "${COMPOSE_ARGS[@]}" up -d --build --remove-orphans 2>&1; then
|
if ! run_compose "${COMPOSE_ARGS[@]}" up -d --build "${ORPHAN_ARGS[@]}" 2>&1; then
|
||||||
fail "compose up failed during restart"
|
fail "compose up failed during restart"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -641,7 +715,7 @@ cmd_stop() {
|
||||||
validate_name "${name}"
|
validate_name "${name}"
|
||||||
load_app "${name}"
|
load_app "${name}"
|
||||||
|
|
||||||
run_compose "${COMPOSE_ARGS[@]}" down --remove-orphans || fail "compose down failed"
|
run_compose "${COMPOSE_ARGS[@]}" down "${ORPHAN_ARGS[@]}" || fail "compose down failed"
|
||||||
log info "stopped app '${name}'"
|
log info "stopped app '${name}'"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -742,7 +816,7 @@ cmd_remove() {
|
||||||
validate_name "${name}"
|
validate_name "${name}"
|
||||||
load_app "${name}"
|
load_app "${name}"
|
||||||
|
|
||||||
run_compose "${COMPOSE_ARGS[@]}" down --remove-orphans 2>/dev/null || true
|
run_compose "${COMPOSE_ARGS[@]}" down "${ORPHAN_ARGS[@]}" 2>/dev/null || true
|
||||||
|
|
||||||
# Remove this app's block from the aggregate routes file.
|
# Remove this app's block from the aggregate routes file.
|
||||||
local route_file
|
local route_file
|
||||||
|
|
@ -878,7 +952,7 @@ cmd_volume_clear() {
|
||||||
load_app "${name}"
|
load_app "${name}"
|
||||||
|
|
||||||
log info "clearing volume data for app '${name}'"
|
log info "clearing volume data for app '${name}'"
|
||||||
run_compose "${COMPOSE_ARGS[@]}" down --remove-orphans 2>/dev/null || true
|
run_compose "${COMPOSE_ARGS[@]}" down "${ORPHAN_ARGS[@]}" 2>/dev/null || true
|
||||||
|
|
||||||
local data_dir="${APP_VOLUME_DIR}/data"
|
local data_dir="${APP_VOLUME_DIR}/data"
|
||||||
if [[ -d "${data_dir}" ]]; then
|
if [[ -d "${data_dir}" ]]; then
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue