mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 04:33:00 +02:00
On any change to a container network interface we should schedule a restart of the container to activate the changes. This code triggers also at boot, when applying the whole startup-config, which initctl handles by queuing any create/touch events for services. This patch depends on the two previous commtis backporing fixes to Finit's initctl tool and an upgrade of the k8s-logger. Fixes #375 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
638 lines
14 KiB
Bash
Executable File
638 lines
14 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 --replace --quiet --cgroup-parent=containers $caps"
|
|
args="$args --restart=$restart --systemd=false --tz=local $privileged"
|
|
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
|
|
args="$args --network=none"
|
|
fi
|
|
|
|
# shellcheck disable=SC2048
|
|
log "Calling podman create --name $name --conmon-pidfile=$pidfn $args $image $*"
|
|
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
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
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
|
|
-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 [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]
|
|
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"
|
|
;;
|
|
--cap-add)
|
|
shift
|
|
caps="$caps --cap-add=$1"
|
|
;;
|
|
--cap-drop)
|
|
shift
|
|
caps="$caps --cap-drop=$1"
|
|
;;
|
|
-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
|
|
cmd=$1
|
|
name=$2
|
|
if [ "$cmd" = "network" ] && [ -n "$name" ]; then
|
|
netrestart "$name"
|
|
else
|
|
name=$1
|
|
stop "$name"
|
|
timeout=20
|
|
while running "$name"; do
|
|
_=$((timeoute -= 1))
|
|
if [ $timeout -le 0 ]; then
|
|
log "Timeout waiting for container $1 to stop before restarting it."
|
|
exit 1
|
|
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)
|
|
img=$(podman inspect "$1" | jq -r .[].ImageName)
|
|
if [ -z "$img" ]; then
|
|
echo "No such container ($1), or invalid ImageName. Cannot upgrade."
|
|
exit 1;
|
|
fi
|
|
if echo "$img" | grep -Eq '^localhost/'; then
|
|
# Likely an OCI archive, or local directory, assume user has updated image.
|
|
file=$(awk '{s=$NF} END{print s}' "/var/lib/containers/active/${1}.sh")
|
|
echo "Upgrading container ${1} with local archive: $file ..."
|
|
else
|
|
podman stop "$1"
|
|
podman pull "$img" || (echo "Failed fetching $img, check your network (settings)."; exit 1)
|
|
fi
|
|
"/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
|