mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-01 05:13:01 +02:00
Sending SIGTERM to conmon is not a safe shutdown of a podman container. To handle gracefully handle shutdown, restarting and provide an orderly start of dependencies, we use the Finit sysv trick via container script wrapper to call 'podman stop foo'. However, since podman does not support syslog as output for containers we employ an old FIFO trick with another program, k8s-logger, to allow logs to reach syslog. Please note that k8s-logger must have properly started before we call `podman start` -- this makes us fully dependent on the 'container' wrapper script. Hence the documentation update. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
591 lines
13 KiB
Bash
Executable File
591 lines
13 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
all=""
|
|
env=""
|
|
port=""
|
|
force=
|
|
|
|
log()
|
|
{
|
|
logger -I $PPID -t container -p local1.notice -- "$*"
|
|
}
|
|
|
|
# Unpacks a given oci-archive.tar[.gz] in the current directory. Sanity
|
|
# checks, at least one index.json in the top-level dir of the archive.
|
|
# If there are more index files, this function does not handle them.
|
|
unpack_archive()
|
|
{
|
|
image=$1
|
|
name=$2
|
|
|
|
# Supported transports for load and create
|
|
case "$image" in
|
|
oci:*) # Unpacked OCI image
|
|
file=${image#oci:}
|
|
;;
|
|
oci-archive:*) # Packed OCI image, .tar or .tar.gz format
|
|
file=${image#oci-archive:}
|
|
;;
|
|
*) # docker://*, docker-archive:*, or URL
|
|
echo "$image"
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
if [ ! -e "$file" ]; then
|
|
if [ -e "/var/lib/conatainers/oci/$file" ]; then
|
|
file="/var/lib/conatainers/oci/$file"
|
|
elif [ -e "/lib/oci/$file" ]; then
|
|
file="/lib/oci/$file"
|
|
else
|
|
log "Error: cannot find OCI archive $file in search path."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ -d "$file" ]; then
|
|
index=$(find "$file" -name index.json)
|
|
if [ -z "$index" ]; then
|
|
log "Error: cannot find index.json in OCI image $file"
|
|
exit 1
|
|
fi
|
|
else
|
|
index=$(tar tf "$file" |grep index.json)
|
|
if [ -z "$index" ]; then
|
|
log "Error: invalid OCI archive, cannot find index.json in $file"
|
|
exit 1
|
|
fi
|
|
|
|
[ -n "$quiet" ] || log "Extracting OCI archive $file ..."
|
|
tar xf "$file" || (log "Error: failed unpacking $file in $(pwd)"; exit 1)
|
|
fi
|
|
|
|
dir=$(dirname "$index")
|
|
[ -n "$quiet" ] || log "Loading OCI image $dir ..."
|
|
podman load -qi "$dir" >/dev/null
|
|
|
|
# Rename image from podman default $dir:latest
|
|
if [ -n "$name" ]; then
|
|
podman tag "$dir" "$name" >/dev/null
|
|
podman rmi "$dir" >/dev/null
|
|
else
|
|
name=$dir
|
|
fi
|
|
|
|
echo "$name"
|
|
}
|
|
|
|
running()
|
|
{
|
|
run=$(podman inspect "$1" 2>/dev/null |jq .[].State.Running)
|
|
[ "$run" = "true" ] && 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.
|
|
image=$(unpack_archive "$image")
|
|
|
|
if [ -z "$logging" ]; then
|
|
logging="--log-driver k8s-file --log-opt path=/run/containers/$name.fifo"
|
|
fi
|
|
|
|
args="$args --cgroup-parent=containers"
|
|
args="$args --restart=$restart --systemd=false --tz=local $privileged --replace --quiet"
|
|
args="$args $ro $vol $mount $hostname $entrypoint $env $port $logging"
|
|
pidfn=/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
|
|
network="--net none"
|
|
fi
|
|
|
|
# shellcheck disable=SC2048
|
|
if podman create --name "$name" --conmon-pidfile="$pidfn" $args "$image" $*; then
|
|
[ -n "$quiet" ] || log "Successfully created container $name from $image"
|
|
rm -f "/run/containers/env/${name}.env"
|
|
[ -n "$manual" ] || start "$name"
|
|
exit 0
|
|
fi
|
|
|
|
log "Error: failed creating container $name, please check the configuration."
|
|
exit 1
|
|
}
|
|
|
|
delete()
|
|
{
|
|
name=$1
|
|
image=$2
|
|
|
|
if [ -z "$name" ]; then
|
|
echo "Usage:"
|
|
echo " container delete NAME"
|
|
exit 1
|
|
fi
|
|
|
|
podman rm -vif "$name" >/dev/null 2>&1
|
|
[ -n "$quiet" ] || log "Container $name has been removed."
|
|
}
|
|
|
|
waitfor()
|
|
{
|
|
timeout=$2
|
|
while [ ! -f "$1" ]; do
|
|
_=$((timeout -= 1))
|
|
if [ $timeout -le 0 ]; then
|
|
log "Timeout waiting for $1, aborting!"
|
|
exit 1
|
|
fi
|
|
sleep 1;
|
|
done
|
|
}
|
|
|
|
start()
|
|
{
|
|
name=$1
|
|
|
|
if running "$name"; then
|
|
[ -n "$quiet" ] || echo "$name: already running."
|
|
return
|
|
fi
|
|
|
|
initctl -bq cond set "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 -bq cond clr "container:$name"
|
|
# Real work is done by wrap() courtesy of finit sysv emulation
|
|
}
|
|
|
|
wrap()
|
|
{
|
|
name=$1
|
|
cmd=$2
|
|
|
|
podman "$cmd" "$name"
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
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
|
|
-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"
|
|
--read-only Do not create a writable layer
|
|
-s, --simple Show output in simplified format
|
|
-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
|
|
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
|
|
remove IMAGE Remove an (unused) container image
|
|
restart [NAME] Restart a crashed container
|
|
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]
|
|
shell Start a shell 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
|
|
volume [prune] Prune unused volumes
|
|
EOF
|
|
}
|
|
|
|
while [ "$1" != "" ]; do
|
|
case $1 in
|
|
-a | --all)
|
|
all="-a"
|
|
;;
|
|
-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"
|
|
log_path="$1"
|
|
;;
|
|
-m | --mount)
|
|
shift
|
|
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
|
|
;;
|
|
--read-only)
|
|
ro="--read-only=true"
|
|
;;
|
|
-s | --simple)
|
|
simple=true
|
|
;;
|
|
-v | --volume)
|
|
shift
|
|
vol="$vol -v $1"
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
cmd=$1
|
|
if [ -n "$cmd" ]; then
|
|
shift
|
|
fi
|
|
|
|
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
|
|
name=$2
|
|
if [ "$cmd" = "network" ] && [ -n "$name" ]; then
|
|
netwrm "$name"
|
|
else
|
|
delete "$@"
|
|
fi
|
|
;;
|
|
exec)
|
|
podman exec -it "$@"
|
|
;;
|
|
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)
|
|
url=$1
|
|
name=$2
|
|
# shellcheck disable=SC2086
|
|
if echo "$url" | grep -q "://"; then
|
|
file=$(basename "$url")
|
|
curl -k $creds -Lo "$file" "$url"
|
|
else
|
|
file="$url"
|
|
fi
|
|
|
|
# shellcheck disable=SC2086
|
|
name=$(unpack_archive "$file" $name)
|
|
|
|
# Show resulting image(s) matching $name
|
|
if [ -n "$name" ]; then
|
|
podman images -n "$name"
|
|
else
|
|
exit 1
|
|
fi
|
|
;;
|
|
ls | list)
|
|
cmd=$1
|
|
[ -n "$cmd" ] && shift
|
|
case $cmd in
|
|
image*)
|
|
podman images $all --format "{{.Repository}}:{{.Tag}}"
|
|
;;
|
|
oci)
|
|
find /lib/oci /var/lib/containers/oci -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
|
|
;;
|
|
shell)
|
|
podman exec -it "$1" sh -l
|
|
;;
|
|
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
|
|
stop "$1"
|
|
start "$1"
|
|
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)
|
|
img=$(podman inspect "$1" | jq -r .[].ImageName)
|
|
if [ -z "$img" ]; then
|
|
echo "No such container ($1), or invalid ImageName. Cannot upgrade."
|
|
exit 1;
|
|
fi
|
|
podman stop "$1"
|
|
podman pull "$img" || (echo "Failed fetching $img, check your network (settings)."; exit 1)
|
|
"/var/lib/containers/active/${1}.sh" || (echo "Failed recreating container $1"; exit 1)
|
|
;;
|
|
volume)
|
|
cmd=$1
|
|
[ -n "$cmd" ] && shift
|
|
case $cmd in
|
|
prune)
|
|
podman volume $force prune
|
|
;;
|
|
*)
|
|
false
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|