mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-31 21:13:00 +02:00
When a container's image is on an inaccessible remote server, the container wrapper script waits in the background for any netowrk changes to retry download of the image. This change avoids the dangerous previous construct, and is also easier to read: timeuot after 60 seconds unless ip monitor reads at least one event before that. Fixes #1124 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
951 lines
22 KiB
Bash
Executable File
951 lines
22 KiB
Bash
Executable File
#!/bin/bash
|
|
# This script can be used to start, stop, create, and delete containers.
|
|
# It is what confd use, with the Finit container@.conf template, to set
|
|
# up, run, and delete containers.
|
|
#
|
|
# NOTE: when creating/deleting containers, remember 'initctl reload' to
|
|
# activate the changes! In confd this is already handled.
|
|
#
|
|
DOWNLOADS=/var/lib/containers/oci
|
|
BUILTIN=/lib/oci
|
|
BASEDIR=/var/tmp
|
|
container=$0
|
|
checksum=""
|
|
extracted=
|
|
timeout=30
|
|
dir=""
|
|
all=""
|
|
env=""
|
|
port=""
|
|
force=
|
|
|
|
log()
|
|
{
|
|
logger -I $PPID -t container -p local1.notice -- "$*"
|
|
}
|
|
|
|
err()
|
|
{
|
|
rc=$1; shift
|
|
logger -I $PPID -t container -p local1.err -- "Error: $*"
|
|
|
|
if [ -n "$extracted" ] && [ -n "$tmpdir" ]; then
|
|
if [ -d "$tmpdir" ]; then
|
|
log "Cleaning up temporary directory $tmpdir"
|
|
rm -rf "$tmpdir"
|
|
fi
|
|
fi
|
|
|
|
[ "$rc" -eq 0 ] || exit "$rc"
|
|
}
|
|
|
|
pidfn()
|
|
{
|
|
echo "/run/containers/${1}.pid"
|
|
}
|
|
|
|
check()
|
|
{
|
|
file=$1
|
|
|
|
if [ -z "$checksum" ]; then
|
|
log "no checksum to verify $file against, continuing."
|
|
return 0
|
|
fi
|
|
|
|
if echo "${checksum} ${file}" | "$cmdsum" -c -s; then
|
|
log "$file checksum verified OK."
|
|
return 0
|
|
fi
|
|
|
|
got=$("$cmdsum" "${file}" | awk '{print $1}')
|
|
log "$file checksum mismatch, got $got, expected $checksum, removing file."
|
|
rm -f "$file"
|
|
|
|
return 1
|
|
}
|
|
|
|
# Fetch an OCI image over ftp/http/https. Use wget for FTP, which curl
|
|
# empirically does not work well with. Log progress+ & error to syslog.
|
|
fetch()
|
|
{
|
|
url=$1
|
|
file=$(basename "$url")
|
|
dst="$DOWNLOADS/$file"
|
|
|
|
cd "$DOWNLOADS" || return
|
|
if [ -e "$file" ]; then
|
|
log "$file already available."
|
|
if check "$file"; then
|
|
echo "$dst"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
log "Fetching $url"
|
|
|
|
if echo "$url" | grep -qE "^ftp://"; then
|
|
cmd="wget -q $url"
|
|
elif echo "$url" | grep -qE "^https?://"; then
|
|
cmd="curl $creds -sSL --fail -o \"$file\" $url"
|
|
else
|
|
log "Unsupported URL scheme: $url"
|
|
return 1
|
|
fi
|
|
|
|
if out=$(eval "$cmd" 2>&1); then
|
|
log "$file downloaded successfully."
|
|
if check "$file"; then
|
|
echo "$dst"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# log error message from backend
|
|
while IFS= read -r line; do
|
|
log "$line"
|
|
done <<EOF
|
|
$out
|
|
EOF
|
|
return 1
|
|
}
|
|
|
|
# Extracts an oci-archive.tar[.gz] in a temporary directory. Finds and
|
|
# sanity checks that at least one index.json exist in the archive. This
|
|
# is the OCI directory fed to `podman load` and also used as repo name.
|
|
# NOTE: if there are >1 index.json, this function does not handle them.
|
|
load_archive()
|
|
{
|
|
uri=$1
|
|
tag=$2
|
|
img=$(basename "$uri")
|
|
|
|
# Supported transports for load and create
|
|
case "$uri" in
|
|
oci:*) # Unpacked OCI image
|
|
file=${uri#oci:}
|
|
;;
|
|
oci-archive:*) # Packed OCI image, .tar or .tar.gz format
|
|
file=${uri#oci-archive:}
|
|
;;
|
|
ftp://* | http://* | https://*)
|
|
if ! file=$(fetch "$uri"); then
|
|
return 1
|
|
fi
|
|
;;
|
|
*) # docker://*, docker-archive:*, or URL
|
|
if podman image exists "$img"; then
|
|
echo "$img"
|
|
return 0
|
|
fi
|
|
# XXX: use --retry=0 with Podman 5.0 or later.
|
|
if ! id=$(podman pull --quiet "$uri"); then
|
|
log "Failed pulling $uri"
|
|
return 1
|
|
fi
|
|
# Echo image tag to caller
|
|
podman images --filter id="$id" --format "{{.Repository}}:{{.Tag}}"
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
if [ ! -e "$file" ]; then
|
|
if [ -e "$DOWNLOADS/$file" ]; then
|
|
file="$DOWNLOADS/$file"
|
|
elif [ -e "$BUILTIN/$file" ]; then
|
|
file="$BUILTIN/$file"
|
|
else
|
|
err 1 "cannot find OCI archive $file in URI $uri"
|
|
fi
|
|
fi
|
|
|
|
file=$(realpath "$file")
|
|
if [ -d "$file" ]; then
|
|
index=$(find "$file" -name index.json)
|
|
if [ -z "$index" ]; then
|
|
err 1 "cannot find index.json in OCI image $file"
|
|
fi
|
|
else
|
|
# Extract files in a temporary directory, because most OCI
|
|
# archives are flat/bare, all files in the root w/o a dir/
|
|
tmpdir=$(mktemp -d -p "$BASEDIR") || err 1 "failed creating temporary directory"
|
|
cd "$tmpdir" || err 1 "failed cd to temporary directory $tmpdir"
|
|
|
|
index="$tmpdir/$(tar tf "$file" |grep index.json)"
|
|
if [ -z "$index" ]; then
|
|
err 1 "invalid OCI archive, cannot find index.json in $file"
|
|
fi
|
|
|
|
[ -n "$quiet" ] || log "Extracting OCI archive $file ..."
|
|
tar xf "$file" || err 1 "failed unpacking $file in $tmpdir"
|
|
extracted=true
|
|
cd - >/dev/null || err 0 "failed cd -"
|
|
fi
|
|
|
|
dir=$(dirname "$index")
|
|
|
|
# Handle flat tarballs without a sub-directory, because
|
|
# the $dir name is used as fallback when retagging below.
|
|
if [ -n "$extracted" ] && [ "$dir" = "$tmpdir" ]; then
|
|
parent=$(dirname "$dir")
|
|
dirnam=$(echo "$img" | sed 's/\(.*\)\.tar.*/\1/')
|
|
tmpdir="${parent}/${dirnam}"
|
|
mv "$dir" "$tmpdir"
|
|
dir="$tmpdir"
|
|
fi
|
|
|
|
if basename "$dir" | grep -q ":"; then
|
|
if [ -z "$tag" ]; then
|
|
tag=$(basename "$dir")
|
|
fi
|
|
|
|
sanitized_dir=$(echo "$dir" | cut -d':' -f1)
|
|
mv "$dir" "$sanitized_dir" || err 1 "failed renaming $dir to $sanitized_dir"
|
|
dir="$sanitized_dir"
|
|
fi
|
|
|
|
[ -n "$quiet" ] || log "Loading OCI image $dir ..."
|
|
output=$(podman load -qi "$dir")
|
|
|
|
# Extract image ID from podman load output:
|
|
# "Loaded image: sha256:cd9d0aaf81be..."
|
|
if echo "$output" | grep -q "sha256:"; then
|
|
img_id="${output##*sha256:}"
|
|
else
|
|
# Fallback to directory name if no SHA found
|
|
img_id="$dir"
|
|
fi
|
|
|
|
# On podman < 4.7.0 we had to retag images from default $dir:latest
|
|
# From >= 4.7.0 we always tag since loads come in as <none>:<none>
|
|
if [ -z "$tag" ]; then
|
|
tag=$(basename "$dir")
|
|
fi
|
|
|
|
# Repo names must be lowercase, and only '[a-z0-9._/-]+' and ':tag'
|
|
tag=$(printf "%s" "$tag" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9._/:-' '-')
|
|
|
|
[ -n "$quiet" ] || log "Tagging loaded image $img_id as $tag"
|
|
if ! podman tag "$img_id" "$tag"; then
|
|
err 1 "failed tagging image as $tag"
|
|
fi
|
|
|
|
# Clean up after ourselves
|
|
if [ -n "$extracted" ]; then
|
|
log "Cleaning up extracted $dir"
|
|
rm -rf "$tmpdir"
|
|
fi
|
|
|
|
echo "$tag"
|
|
}
|
|
|
|
running()
|
|
{
|
|
status=$(podman inspect -f '{{.State.Status}}' "$1" 2>/dev/null)
|
|
[ "$status" = "running" ] && return 0
|
|
return 1
|
|
}
|
|
|
|
# shellcheck disable=SC2086
|
|
create()
|
|
{
|
|
name=$1
|
|
image=$2
|
|
shift 2
|
|
|
|
if [ -z "$name" ] || [ -z "$image" ]; then
|
|
echo "Usage:"
|
|
echo " container create NAME IMAGE"
|
|
exit 1
|
|
fi
|
|
|
|
# Unpack and load docker-archive/oci/oci-archive, returning image
|
|
# name, or return docker:// URL for download.
|
|
if ! image=$(load_archive "$image"); then
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$logging" ]; then
|
|
logging="--log-driver syslog"
|
|
fi
|
|
|
|
# When we get here we've already fetched, or pulled, the image
|
|
args="$args --read-only --replace --quiet --cgroup-parent=containers $caps"
|
|
args="$args --restart=$restart --systemd=false --tz=local $privileged"
|
|
args="$args $vol $mount $hostname $entrypoint $env $port $logging"
|
|
pidfile=/run/container:${name}.pid
|
|
|
|
[ -n "$quiet" ] || log "---------------------------------------"
|
|
[ -n "$quiet" ] || log "Got name: $name image: $image"
|
|
[ -n "$quiet" ] || log "Got networks: $network"
|
|
|
|
if [ -n "$network" ]; then
|
|
for net in $network; do
|
|
args="$args --net=$net"
|
|
done
|
|
|
|
for srv in $dns; do
|
|
args="$args --dns=$srv"
|
|
done
|
|
|
|
for domain in $search; do
|
|
args="$args --dns-search=$domain"
|
|
done
|
|
else
|
|
args="$args --network=none"
|
|
fi
|
|
|
|
# shellcheck disable=SC2048
|
|
log "podman create --name $name --conmon-pidfile=$pidfile $args $image $*"
|
|
if podman create --name "$name" --conmon-pidfile="$pidfile" $args "$image" $*; then
|
|
[ -n "$quiet" ] || log "Successfully created container $name from $image"
|
|
[ -n "$manual" ] || start "$name"
|
|
|
|
# Should already be enabled by confd (this is for manual use)
|
|
initctl -bnq enable "container@${name}.conf"
|
|
exit 0
|
|
fi
|
|
|
|
err 1 "failed creating container $name, please check the configuration."
|
|
}
|
|
|
|
delete()
|
|
{
|
|
name=$1
|
|
|
|
if [ -z "$name" ]; then
|
|
echo "Usage:"
|
|
echo " container delete NAME"
|
|
exit 1
|
|
fi
|
|
|
|
# Should already be stopped, but if not ...
|
|
container stop "$name" >/dev/null
|
|
|
|
while running "$name"; do
|
|
log "$name: still running, waiting for it to stop ..."
|
|
_=$((timeout -= 1))
|
|
if [ $timeout -le 0 ]; then
|
|
err 1 "timed out waiting for container $1 to stop before deleting it."
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
podman rm -vif "$name" >/dev/null 2>&1
|
|
[ -n "$quiet" ] || log "Container $name has been removed."
|
|
|
|
cnt=$(podman image prune -af | wc -l)
|
|
log "Pruned $cnt image(s)"
|
|
}
|
|
|
|
waitfor()
|
|
{
|
|
while [ ! -f "$1" ]; do
|
|
_=$((timeout -= 1))
|
|
if [ $timeout -le 0 ]; then
|
|
err 1 "timed out waiting for $1, aborting!"
|
|
fi
|
|
sleep 1;
|
|
done
|
|
}
|
|
|
|
start()
|
|
{
|
|
name=$1
|
|
|
|
if running "$name"; then
|
|
[ -n "$quiet" ] || echo "$name: already running."
|
|
return
|
|
fi
|
|
|
|
initctl start container:$name
|
|
# Real work is done by wrap() courtesy of finit sysv emulation
|
|
}
|
|
|
|
stop()
|
|
{
|
|
name=$1
|
|
|
|
if ! running "$name"; then
|
|
[ -n "$quiet" ] || echo "$name: not running."
|
|
return
|
|
fi
|
|
|
|
initctl stop container:$name
|
|
# Real work is done by wrap() courtesy of finit sysv emulation
|
|
}
|
|
|
|
wrap()
|
|
{
|
|
name=$1
|
|
cmd=$2
|
|
pidfile=$(pidfn "$name")
|
|
|
|
# Containers have three phases: setup, running, and teardown.
|
|
|
|
# The setup phase may run forever in the background trying to fetch
|
|
# the image. It saves its PID in /run/containers/${name}.pid
|
|
if [ "$cmd" = "stop" ] && [ -f "$pidfile" ]; then
|
|
pid=$(cat "$pidfile")
|
|
|
|
# Check if setup is still running ...
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
kill "$pid"
|
|
wait "$pid" 2>/dev/null
|
|
fi
|
|
|
|
rm -f "$pidfile"
|
|
return 0
|
|
fi
|
|
|
|
# Skip "echo $name" from podman start in log
|
|
podman "$cmd" "$name" >/dev/null
|
|
}
|
|
|
|
# Removes network $1 from all containers
|
|
netwrm()
|
|
{
|
|
net=$1
|
|
|
|
for c in $(podman ps $all --format "{{.Names}}"); do
|
|
for n in $(podman inspect "$c" |jq -r '.[].NetworkSettings.Networks | keys[]'); do
|
|
if [ "$n" = "$net" ]; then
|
|
podman network disconnect $force "$n" "$c" >/dev/null
|
|
fi
|
|
done
|
|
done
|
|
}
|
|
|
|
# Schedule restart of (any) container using network $1 to activate network changes
|
|
netrestart()
|
|
{
|
|
net=$1
|
|
|
|
for c in $(podman ps $all --format "{{.Names}}"); do
|
|
for n in $(podman inspect "$c" |jq -r '.[].NetworkSettings.Networks | keys[]'); do
|
|
if [ "$n" = "$net" ]; then
|
|
initctl -nbq touch "container@$c"
|
|
fi
|
|
done
|
|
done
|
|
}
|
|
|
|
cleanup()
|
|
{
|
|
pidfile=$(pidfn "$name")
|
|
|
|
log "Received signal, exiting."
|
|
if [ -n "$name" ] && [ -f "$pidfile" ]; then
|
|
log "$name: in setup phase, removing $pidfile ..."
|
|
rm -f "$pidfile"
|
|
fi
|
|
|
|
exit 1
|
|
}
|
|
|
|
usage()
|
|
{
|
|
cat <<EOF
|
|
usage:
|
|
container [opt] cmd [arg]
|
|
|
|
options:
|
|
-a, --all Show all, of something
|
|
--dns NAMESERVER Set nameserver(s) when creating a container
|
|
--dns-search LIST Set host lookup search list when creating container
|
|
--cap-add CAP Add capability to unprivileged container
|
|
--cap-drop CAP Drop capability, for privileged containter
|
|
--checksum TYPE:SUM Use md5/sha256/sha512 to verify ftp/http/https archives
|
|
-c, --creds USR[:PWD] Credentials to pass to curl -u for remote ops
|
|
-d, --detach Detach a container started with 'run IMG [CMD]'
|
|
-e, --env FILE Environment variables when creating container
|
|
--entrypoint Disable container image's ENTRYPOINT, run cmd + arg
|
|
-f, --force Force operation, e.g. remove
|
|
-h, --help Show this help text
|
|
--hostname NAME Set hostname when creating container
|
|
--net NETWORK Network interface(s) when creating or finding container
|
|
-l, --log-driver DRV Log driver to use
|
|
--log-opt OPT Logging options to log driver
|
|
--log-path PATH Path for k8s-file log pipe
|
|
-m, --mount HOST:DEST Bind mount a read-only file inside a container
|
|
--manual Do not start container automatically after creation
|
|
-n, --name NAME Alternative way of supplying name to start/stop/restart
|
|
--privileged Give container extended privileges
|
|
-p, --publish PORT Publish ports when creating container
|
|
Syntax: [[ip:][hostPort]:]containerPort[/protocol]
|
|
-q, --quiet Quiet operation, called from confd
|
|
-r, --restart POLICY One of "no", "always", or "on-failure:NUM"
|
|
-s, --simple Show output in simplified format
|
|
-t, --timeout SEC Set timeout for delete/restart commands, default: 30
|
|
-v, --volume NAME:PATH Create named volume mounted inside container on PATH
|
|
|
|
commands:
|
|
create NAME IMAGE NET Create container NAME using IMAGE with networks NET
|
|
delete [network] NAME Remove container NAME or network NAME from all containers
|
|
exec NAME CMD Run a command inside a container
|
|
flush Clean up lingering containers and associated anonymous volumes
|
|
find [ifname PID] Find PID of container where '--net IFNAME' currently lives
|
|
or, find the name of our IFNAME inside the container @PID
|
|
help Show this help text
|
|
list [image | oci] List names (only) of containers, images, or OCI archives
|
|
load [NAME | URL] NM Load OCI tarball fileNAME or URL to image NM
|
|
locate Find container that currently owns '--net IFNAME'
|
|
remove IMAGE Remove an (unused) container image
|
|
restart [network] NAME Restart a (crashed) container or container(s) using network
|
|
run NAME [CMD] Run a container interactively, with an optional command
|
|
save IMAGE FILE Save a container image to an OCI tarball FILE[.tar.gz]
|
|
setup NAME Create and set up container as a Finit task
|
|
shell [CMD] Start a shell, or run CMD, inside a container
|
|
show [image | volume] Show containers, images, or volumes
|
|
stat Show continuous stats about containers (Ctrl-C aborts)
|
|
start [NAME] Start a container, see -n
|
|
stop [NAME] Stop a container, see -n
|
|
upgrade NAME Upgrade a running container (stop, pull, restart)
|
|
volume [prune] Prune unused volumes
|
|
EOF
|
|
}
|
|
|
|
while [ "$1" != "" ]; do
|
|
case $1 in
|
|
-a | --all)
|
|
all="-a"
|
|
;;
|
|
--cap-add)
|
|
shift
|
|
caps="$caps --cap-add=$1"
|
|
;;
|
|
--cap-drop)
|
|
shift
|
|
caps="$caps --cap-drop=$1"
|
|
;;
|
|
--checksum)
|
|
shift
|
|
type="${1%%:*}"
|
|
checksum="${1#*:}"
|
|
case "$type" in
|
|
md5)
|
|
cmdsum=md5sum
|
|
;;
|
|
sha256)
|
|
cmdsum=sha256sum
|
|
;;
|
|
sha512)
|
|
cmdsum=sha512sum
|
|
;;
|
|
*)
|
|
err 1 "Unsupported checksum type: $type"
|
|
;;
|
|
esac
|
|
;;
|
|
-c | --creds)
|
|
shift
|
|
creds="-u $1"
|
|
;;
|
|
-d | --detach)
|
|
detach="-d"
|
|
;;
|
|
--dns)
|
|
shift
|
|
dns="$dns $1"
|
|
;;
|
|
--dns-search)
|
|
shift
|
|
search="$search $1"
|
|
;;
|
|
-e | --env)
|
|
shift
|
|
env="$env --env-file=$1"
|
|
;;
|
|
--entrypoint)
|
|
entrypoint="--entrypoint=\"\""
|
|
;;
|
|
-f | --force)
|
|
force="-f"
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--hostname)
|
|
shift
|
|
hostname="--hostname $1"
|
|
;;
|
|
-l | --log-driver)
|
|
shift
|
|
logging=" --log-driver=$1"
|
|
;;
|
|
--log-opt)
|
|
shift
|
|
logging="$logging --log-opt $1"
|
|
;;
|
|
--log-path)
|
|
shift
|
|
logging="$logging --log-opt path=$1"
|
|
;;
|
|
-m | --mount)
|
|
shift
|
|
mount="$mount --mount=$1"
|
|
;;
|
|
--manual)
|
|
manual=true
|
|
;;
|
|
-n | --name)
|
|
shift
|
|
name="$1"
|
|
;;
|
|
--net)
|
|
shift
|
|
if [ -n "$network" ]; then
|
|
network="$network $1"
|
|
else
|
|
network=$1
|
|
fi
|
|
;;
|
|
--privileged)
|
|
privileged="--privileged=true"
|
|
;;
|
|
-p | --publish)
|
|
shift
|
|
port="$port -p $1"
|
|
;;
|
|
-q | --quiet)
|
|
quiet="-q"
|
|
;;
|
|
-r | --restart)
|
|
shift
|
|
restart=$1
|
|
;;
|
|
-s | --simple)
|
|
simple=true
|
|
;;
|
|
-t | --timeout)
|
|
shift
|
|
timeout=$1
|
|
;;
|
|
-v | --volume)
|
|
shift
|
|
vol="$vol -v $1"
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
cmd=$1
|
|
if [ -n "$cmd" ]; then
|
|
shift
|
|
fi
|
|
|
|
trap cleanup INT HUP TERM
|
|
|
|
case $cmd in
|
|
# Does not work atm., cannot attach to TTY because
|
|
# we monitor 'podman start -ai foo' with Finit.
|
|
# attach)
|
|
# podman attach "$1"
|
|
# ;;
|
|
create)
|
|
[ -n "$quiet" ] || log "Got create args: $*"
|
|
create "$@"
|
|
;;
|
|
delete)
|
|
cmd=$1
|
|
[ -n "$name" ] || name=$2
|
|
if [ "$cmd" = "network" ] && [ -n "$name" ]; then
|
|
netwrm "$name"
|
|
else
|
|
[ -n "$name" ] || name=$1
|
|
delete "$name"
|
|
fi
|
|
;;
|
|
exec)
|
|
if [ -z "$name" ]; then
|
|
name="$1"
|
|
shift
|
|
fi
|
|
podman exec -i "$name" "$@"
|
|
;;
|
|
flush)
|
|
echo "Cleaning up any lingering containers";
|
|
podman rm -av $force
|
|
;;
|
|
find)
|
|
cmd=$1
|
|
pid=$2
|
|
if [ "$cmd" = "ifname" ] && [ -n "$pid" ]; then
|
|
nsenter -t "$pid" -n ip -d -j link | \
|
|
jq --arg ifname "$network" -r '.[] | select(.ifalias==$ifname) | .ifname'
|
|
else
|
|
containers=$(podman ps $all --format "{{.Names}}")
|
|
for c in $containers; do
|
|
json=$(podman inspect "$c")
|
|
nets=$(echo "$json" |jq -r '.[].NetworkSettings.Networks | keys[]' 2>/dev/null)
|
|
for n in $nets; do
|
|
if [ "$network" = "$n" ]; then
|
|
pid=$(echo "$json" | jq .[].State.Pid)
|
|
echo "$pid"
|
|
exit 0
|
|
fi
|
|
done
|
|
done
|
|
fi
|
|
;;
|
|
help)
|
|
usage
|
|
;;
|
|
load)
|
|
# shellcheck disable=SC2086
|
|
name=$(load_archive "$1" $2)
|
|
[ -n "$name" ] || exit 1
|
|
|
|
# Show resulting image(s) matching $name
|
|
podman images -n "$name"
|
|
;;
|
|
locate) # Find where the host's ifname lives
|
|
if [ -z "$network" ]; then
|
|
echo "Missing --net IFNAME option."
|
|
exit 1
|
|
fi
|
|
containers=$(podman ps $all --format "{{.Names}}")
|
|
for c in $containers; do
|
|
json=$(podman inspect "$c")
|
|
nets=$(echo "$json" |jq -r '.[].NetworkSettings.Networks | keys[]' 2>/dev/null)
|
|
for n in $nets; do
|
|
if [ "$network" = "$n" ]; then
|
|
echo "$c"
|
|
exit 0;
|
|
fi
|
|
done
|
|
done
|
|
;;
|
|
ls | list)
|
|
cmd=$1
|
|
[ -n "$cmd" ] && shift
|
|
case $cmd in
|
|
image*)
|
|
podman images $all --format "{{.Repository}}:{{.Tag}}"
|
|
;;
|
|
oci)
|
|
find $BUILTIN $DOWNLOADS -type f 2>/dev/null
|
|
;;
|
|
*)
|
|
podman ps $all --format "{{.Names}}"
|
|
;;
|
|
esac
|
|
;;
|
|
pull)
|
|
podman pull "$@"
|
|
;;
|
|
remove)
|
|
podman rmi $all $force -i "$1"
|
|
;;
|
|
run)
|
|
img=$1
|
|
cmd=$2
|
|
[ -n "$port" ] || port="-P"
|
|
if [ -n "$cmd" ]; then
|
|
shift 2
|
|
[ -n "$detach" ] || echo "Starting $img ENTRYPOINT $cmd :: use Ctrl-p Ctrl-q to detach"
|
|
podman run -it --rm $detach $port --entrypoint="$cmd" "$img" "$@"
|
|
else
|
|
[ -n "$detach" ] || echo "Starting $img :: use Ctrl-p Ctrl-q to detach"
|
|
podman run -it --rm $detach $port "$img"
|
|
fi
|
|
;;
|
|
save)
|
|
name=$1
|
|
file=$2
|
|
if echo "$file" | grep -q ".gz"; then
|
|
file=${file%%.gz}
|
|
gzip=true
|
|
fi
|
|
if ! echo "$file" | grep -q ".tar"; then
|
|
file=${file}.tar
|
|
gzip=true
|
|
fi
|
|
podman save -o "$file" "$name"
|
|
if [ -s "$file" ] && [ -n "$gzip" ]; then
|
|
gzip "$file"
|
|
fi
|
|
;;
|
|
setup)
|
|
[ -n "$name" ] || err 1 "setup: missing container name."
|
|
script=/run/containers/${name}.sh
|
|
[ -x "$script" ] || err 1 "setup: $script does not exist or is not executable."
|
|
|
|
# Save our PID in case we get stuck here and someone wants to
|
|
# stop us, e.g., due to reconfiguration or reboot.
|
|
pidfile=$(pidfn "${name}")
|
|
echo $$ > "$pidfile"
|
|
|
|
while ! "$script"; do
|
|
log "${name}: setup failed, waiting for network changes ..."
|
|
|
|
# Timeout and retry after 60 seconds, on SIGTERM, or when
|
|
# any network event is caught.
|
|
timeout -s TERM -k 1 60 sh -c \
|
|
'ip monitor address route 2>/dev/null | head -n1 >/dev/null' || true
|
|
|
|
# On IP address/route changes, wait a few seconds more to ensure
|
|
# the system has ample time to react and set things up for us.
|
|
log "${name}: retrying ..."
|
|
sleep 2
|
|
done
|
|
|
|
rm -f "$pidfile"
|
|
cnt=$(podman image prune -f | wc -l)
|
|
log "setup: pruned $cnt image(s)"
|
|
;;
|
|
shell)
|
|
if [ -z "$name" ]; then
|
|
name="$1"
|
|
shift
|
|
fi
|
|
if [ $# -gt 0 ]; then
|
|
podman exec -i "$name" sh -c "$*"
|
|
else
|
|
podman exec -it "$name" sh -l
|
|
fi
|
|
;;
|
|
show)
|
|
cmd=$1
|
|
[ -n "$cmd" ] && shift
|
|
case $cmd in
|
|
image*)
|
|
if [ -n "$simple" ]; then
|
|
podman images $all --format "{{.Names}} {{.Size}}" \
|
|
| sed 's/\[\(.*\)\] /\1 /g' \
|
|
| awk '{ printf "%-60s %s %s\n", $1, $2, $3}'
|
|
else
|
|
podman images $all
|
|
fi
|
|
;;
|
|
volume*)
|
|
printf "%-20s CONTAINER\n" "VOLUME"
|
|
for v in $(podman volume ls --format "{{.Name}}"); do
|
|
printf "%-20s" "$v"
|
|
podman ps -a --filter volume="$v" --format '{{.Names}}' | sed 's/^/ /'
|
|
done
|
|
;;
|
|
*)
|
|
if [ -n "$simple" ]; then
|
|
podman ps $all --format "{{.ID}} {{.Names}} {{.Image}}" \
|
|
| awk '{ printf "%s %-30s %s\n", $1, $2, $3}'
|
|
else
|
|
podman ps $all
|
|
fi
|
|
;;
|
|
esac
|
|
;;
|
|
start)
|
|
if [ -n "$name" ]; then
|
|
wrap "$name" start
|
|
elif [ -n "$1" ]; then
|
|
start "$1"
|
|
else
|
|
usage
|
|
exit 1
|
|
fi
|
|
;;
|
|
restart)
|
|
if [ -n "$name" ]; then
|
|
wrap "$name" restart
|
|
elif [ -n "$1" ]; then
|
|
cmd=$1
|
|
name=$2
|
|
if [ "$cmd" = "network" ] && [ -n "$name" ]; then
|
|
netrestart "$name"
|
|
else
|
|
name=$1
|
|
stop "$name"
|
|
while running "$name"; do
|
|
_=$((timeout -= 1))
|
|
if [ $timeout -le 0 ]; then
|
|
err 1 "timed out waiting for container $1 to stop before restarting it."
|
|
fi
|
|
sleep 1
|
|
done
|
|
start "$name"
|
|
fi
|
|
else
|
|
usage
|
|
exit 1
|
|
fi
|
|
;;
|
|
stop)
|
|
if [ -n "$name" ]; then
|
|
wrap "$name" stop
|
|
elif [ -n "$1" ]; then
|
|
stop "$1"
|
|
else
|
|
usage
|
|
exit 1
|
|
fi
|
|
;;
|
|
stat*)
|
|
podman stats -i 2
|
|
;;
|
|
upgrade)
|
|
# Start script used to initially create container
|
|
script=/run/containers/${1}.sh
|
|
|
|
# Find container image
|
|
img=$(podman inspect "$1" | jq -r .[].ImageName)
|
|
if [ -z "$img" ]; then
|
|
echo "No such container ($1), or invalid ImageName. Cannot upgrade."
|
|
exit 1;
|
|
fi
|
|
|
|
# Likely an OCI archive, or local directory, assume user has updated image.
|
|
if echo "$img" | grep -Eq '^localhost/'; then
|
|
file=$(awk '/^# meta-image:/ {print $3}' "$script")
|
|
echo ">> Upgrading container $1 using $file ..."
|
|
else
|
|
printf ">> Stopping ... "
|
|
podman stop "$1"
|
|
printf ">> "
|
|
podman pull "$img" || (echo "Failed fetching $img, check your network (settings)."; exit 1)
|
|
echo ">> Starting $1 ..."
|
|
fi
|
|
if ! "$script"; then
|
|
echo ">> Failed recreating container $1"
|
|
exit 1
|
|
fi
|
|
echo ">> Done."
|
|
;;
|
|
volume)
|
|
cmd=$1
|
|
[ -n "$cmd" ] && shift
|
|
case $cmd in
|
|
prune)
|
|
podman volume prune $force
|
|
;;
|
|
*)
|
|
false
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
if [ -n "$SERVICE_SCRIPT_TYPE" ] && [ -n "$SERVICE_ID" ]; then
|
|
case "$SERVICE_SCRIPT_TYPE" in
|
|
pre)
|
|
# Called as pre-script from Finit service
|
|
exec $container -q -n "$SERVICE_ID" setup
|
|
;;
|
|
cleanup)
|
|
# Called as cleanup-script from Finit service
|
|
log "Calling $container -n $SERVICE_ID delete"
|
|
exec $container -q -n "$SERVICE_ID" delete
|
|
;;
|
|
*)
|
|
false
|
|
;;
|
|
esac
|
|
fi
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|