#!/bin/sh # Copyright (C) 2014-2024 Daniel Baumann # # SPDX-License-Identifier: GPL-3.0+ # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . set -e PROJECT="open-infrastructure" SOFTWARE="compute-tools" PROGRAM="container" COMMAND="$(basename ${0})" CONFIG="/etc/${SOFTWARE}/config" HOOKS="/etc/${SOFTWARE}/hooks" MACHINES="/var/lib/machines" VERSION="$(${PROGRAM} version)" Parameters () { GETOPT_LONGOPTIONS="all,csv-separator:,format:,host:,nwdiag-color:,nwdiag-label:,other,started,stopped," GETOPT_OPTIONS="a,f:,h:,o,s,t," PARAMETERS="$(getopt --longoptions ${GETOPT_LONGOPTIONS} --name=${COMMAND} --options ${GETOPT_OPTIONS} --shell sh -- ${@})" if [ "${?}" != "0" ] then echo "'${COMMAND}': getopt exit" >&2 exit 1 fi eval set -- "${PARAMETERS}" while true do case "${1}" in -a|--all) LIST="${LIST} all" shift 1 ;; --csv-separator) CSV_SEPARATOR="${2}" shift 2 ;; -f|--format) FORMAT="${2}" shift 2 ;; -h|--host) HOST="${2}" shift 2 ;; --nwdiag-color) NWDIAG_COLOR="${2}" shift 2 ;; --nwdiag-label) NWDIAG_LABEL="${2}" shift 2 ;; -o|--other) LIST="${LIST} other" shift 1 ;; -s|--started) LIST="${LIST} started" shift 1 ;; -t|--stopped) LIST="${LIST} stopped" shift 1 ;; --) shift 1 break ;; *) echo "'${COMMAND}': getopt error" >&2 exit 1 ;; esac done } Usage () { echo "Usage: ${PROGRAM} ${COMMAND} [-a|--all] [--csv-separator SEPARATOR] [--format FORMAT] [-h|--host HOSTNAME] [--nwdiag-color COLOR] [--nwdiag-label LABEL] [-o|--other] [-s|--started] [-t|--stopped]" >&2 echo echo "See ${COMMAND}(1), ${PROGRAM}(1) and ${PROJECT}(7) for more information." exit 1 } Parameters "${@}" LIST="${LIST:-started stopped}" FORMAT="${FORMAT:-cli}" HOST="${HOST:-$(hostname -f 2> /dev/null || hostname)}" CSV_SEPARATOR="${CSV_SEPARATOR:-,}" # Pre hooks for FILE in "${HOOKS}/pre-${COMMAND}".* "${HOOKS}/${NAME}.pre-${COMMAND}" do if [ -x "${FILE}" ] then "${FILE}" fi done # Run List () { case "${FORMAT}" in cli) printf "%1s %-74s %-19s\n" "${STATUS}" "${BLUE}${CONTAINER}${NORMAL}" "${YELLOW}${ADDRESS}${NORMAL}" ;; csv) echo "${HOST}${CSV_SEPARATOR}${CONTAINER}${CSV_SEPARATOR}${STATE}${CSV_SEPARATOR}${ADDRESS}" ;; json) cat << EOF { "name": "${CONTAINER}", "status": "${STATE}", "address": "${ADDRESS}", }, EOF ;; nwdiag) Nwdiag_width "${CONTAINER}" case "${STATE}" in started) COLOR="#73d216" ;; stopped) COLOR="#cc0000" ;; other) COLOR="#d3d7cf" ;; esac echo " ${CONTAINER} [address = \"${ADDRESS}\", color = \"${COLOR}\", shape = \"flowchart.terminator\", width = \"${WIDTH}\"];" ;; shell|sh) echo "${CONTAINER}" ;; yaml) cat << EOF - name: ${CONTAINER} address: ${ADDRESS} EOF ;; xml) cat << EOF ${CONTAINER}
${ADDRESS}
EOF ;; esac } Nwdiag_width () { NAME="${1}" CHARACTERS="$(echo "${NAME}" | wc -c)" if [ "${CHARACTERS}" -gt 13 ] then # default width is 128 fitting 13 characters WIDTH="$(( ${CHARACTERS} - 13 ))" WIDTH="$(( ${WIDTH} * 5 ))" WIDTH="$(( ${WIDTH} + 128 ))" else WIDTH="128" fi } case "${FORMAT}" in cli) RED="$(tput setaf 1)$(tput bold)" GREEN="$(tput setaf 2)$(tput bold)" YELLOW="$(tput setaf 3)$(tput bold)" BLUE="$(tput setaf 4)$(tput bold)" WHITE="$(tput setaf 7)$(tput bold)" NORMAL="$(tput sgr0)" cat << EOF ${WHITE} ${NORMAL} Container IP Address -------------------------------------------------------------------------------- EOF ;; csv) cat << EOF # compute-tools ${VERSION} Host${CSV_SEPARATOR}Container${CSV_SEPARATOR}Status${CSV_SEPARATOR}IPv4-Address EOF ;; json) cat << EOF { "compute-tools": { "version": "${VERSION}", }, "host": { "name": "${HOST}", }, "container": [ EOF ;; nwdiag) NETWORK="$(echo ${HOST} | sed -e 's|\.|_|g')" cat << EOF # compute-tools ${VERSION} nwdiag { external_connector = none; network ${NETWORK} { EOF if [ -n "${NWDIAG_LABEL}" ] then echo " label = \"${NWDIAG_LABEL}\"" else echo " label = \"\"" fi NUMBER="$(${0} --format=short | wc -l)" if [ -n "${NWDIAG_COLOR}" ] then COLOR="${NWDIAG_COLOR}" else COLOR="#3465a4" fi Nwdiag_width "${HOST}" cat << EOF # host ${HOST} [color = "${COLOR}", shape = "box", numbered = "${NUMBER}", width = "${WIDTH}"]; # container EOF ;; yaml) cat << EOF --- compute_tools: version: ${VERSION} host: name: ${HOST} container: EOF ;; xml) cat << EOF ${VERSION} ${HOST} EOF ;; esac if ls "${MACHINES}"/* > /dev/null 2>&1 then CONTAINERS="$(cd "${MACHINES}" 2>/dev/null && find -maxdepth 1 \( -type d -or -type l \) -and -not -name 'lost+found' -printf '%P\n' | sort -V)" fi for CONTAINER in ${CONTAINERS} do STATE="$(machinectl show ${CONTAINER} 2>&1 | awk -FState= '/^State=/ { print $2 }')" if [ -e "${CONFIG}/${CONTAINER}.conf" ] then CONTAINER_SERVER="$(awk -Fcnt.container-server= '/^cnt.container-server=/ { print $2 }' ${CONFIG}/${CONTAINER}.conf)" CONTAINER_SERVER="${CONTAINER_SERVER:-false}" case "${CONTAINER_SERVER}" in ${HOST}|true) ;; *) STATE="other" ;; esac else STATE="other" fi case "${STATE}" in running) STATE="started" STATUS="${GREEN}●${NORMAL}" ;; other) STATUS="${WHITE}○${NORMAL}" ;; *) STATE="stopped" STATUS="${RED}●${NORMAL}" ;; esac ADDRESS="" case "${STATE}" in started) case "${FORMAT}" in shell|sh) ;; *) LEADER="$(machinectl status ${CONTAINER} | awk '/Leader: / { print $2 }')" ADDRESS="$(nsenter --all --target "${LEADER}" /bin/hostname -I)" ;; esac ADDRESS="${ADDRESS:-none}" ;; *) if ls "${MACHINES}/${CONTAINER}/etc/systemd/network"/*.network > /dev/null 2>&1 then ADDRESS="$(for IP in $(awk -FAddress= '/^Address/ { print $2 }' ${MACHINES}/${CONTAINER}/etc/systemd/network/*.network); do echo -n "${IP} "; done)" elif [ -e "${MACHINES}/${CONTAINER}/etc/network/interfaces" ] then ADDRESS="$(for IP in $(awk '/address/ { print $2 }' ${MACHINES}/${CONTAINER}/etc/network/interfaces); do echo -n "${IP} "; done)" fi ADDRESS="${ADDRESS:-n/a}" ;; esac if echo ${LIST} | grep -qs all then List fi for ITEM in other started stopped do if echo ${LIST} | grep -qs ${ITEM} then case "${STATE}" in ${ITEM}) List ;; esac fi done done case "${FORMAT}" in cli) cat << EOF -------------------------------------------------------------------------------- EOF ;; json) cat << EOF ], } EOF ;; nwdiag) cat << EOF } } EOF ;; xml) cat << EOF EOF ;; esac # Post hooks for FILE in "${HOOKS}/post-${COMMAND}".* "${HOOKS}/${NAME}.post-${COMMAND}" do if [ -x "${FILE}" ] then "${FILE}" fi done