| #!/bin/bash |
| set -eo pipefail |
| |
| trap cleanup EXIT |
| |
| # Defaults |
| REGISTRY=registry.redhat.io |
| IMAGE=rhel9/support-tools |
| AUTHFILE=/var/lib/kubelet/config.json |
| TOOLBOX_NAME=toolbox-"$(whoami)" |
| TOOLBOXRC="${HOME}"/.toolboxrc |
| |
| errecho() { |
| >&2 echo "${@}" |
| } |
| |
| setup() { |
| # Allow user overrides |
| if [ -f "${TOOLBOXRC}" ]; then |
| errecho ".toolboxrc file detected, overriding defaults..." |
| # This file only potentially exists on a running system |
| # shellcheck disable=SC1090 |
| source "${TOOLBOXRC}" |
| fi |
| TOOLBOX_IMAGE="${REGISTRY}"/"${IMAGE}" |
| } |
| |
| run() { |
| # Pull the container image if it does not exists yet |
| if ! image_exists; then |
| image_pull |
| if container_exists; then |
| sudo podman rm "${TOOLBOX_NAME}" |
| fi |
| # If it already exists, make sure it is up-to-date |
| elif ! image_fresh; then |
| >&2 read -r -p "There is a newer version of ${TOOLBOX_IMAGE} available. Would you like to pull it? [y/N] " |
| if [[ ${REPLY} =~ ^([Yy][Ee][Ss]|[Yy])+$ ]]; then |
| image_pull |
| if container_exists; then |
| sudo podman rm "${TOOLBOX_NAME}" |
| fi |
| else |
| errecho "Skipping retrieval of new image.." |
| fi |
| fi |
| |
| # If the container does not already exists, create it, while making sure to |
| # use the option from the RUN label if provided |
| local runlabel |
| runlabel=$(image_runlabel) |
| if ! container_exists; then |
| errecho "Spawning a container '${TOOLBOX_NAME}' with image '${TOOLBOX_IMAGE}'" |
| if [[ -z "${runlabel}" ]] || [[ "${runlabel}" == "<no value>" ]]; then |
| container_create |
| else |
| errecho "Detected RUN label in the container image. Using that as the default..." |
| container_create_runlabel |
| fi |
| else |
| errecho "Container '${TOOLBOX_NAME}' already exists. Trying to start..." |
| errecho "(To remove the container and start with a fresh toolbox, run: sudo podman rm '${TOOLBOX_NAME}')" |
| fi |
| |
| # Start our freshly created container |
| local state |
| state=$(container_state) |
| if [[ "${state}" == configured ]] || [[ "${state}" == created ]] || [[ "${state}" == exited ]] || [[ "${state}" == stopped ]]; then |
| container_start |
| elif [[ "${state}" != running ]]; then |
| errecho "Container '${TOOLBOX_NAME}' in unknown state: '$state'" |
| return 1 |
| fi |
| |
| if [[ "$#" -eq "0" ]]; then |
| errecho "Container started successfully. To exit, type 'exit'." |
| fi |
| # Attach to the interactive shell in the container or directly execute the |
| # command passed as argument |
| container_exec "$@" |
| } |
| |
| cleanup() { |
| sudo podman stop "${TOOLBOX_NAME}" &>/dev/null |
| } |
| |
| container_exists() { |
| sudo podman container inspect "${TOOLBOX_NAME}" &>/dev/null |
| } |
| |
| container_state() { |
| sudo podman container inspect "${TOOLBOX_NAME}" --format '{{.State.Status}}' |
| } |
| |
| image_exists() { |
| sudo podman image inspect "${TOOLBOX_IMAGE}" &>/dev/null |
| } |
| |
| # returns 0 if the image on disk is "fresh", i.e. no newer image on remote |
| # registry. (or if we couldn't inspect the registry successfully) |
| image_fresh() { |
| errecho "Checking if there is a newer version of ${TOOLBOX_IMAGE} available..." |
| local_date=$(sudo podman image inspect "${TOOLBOX_IMAGE}" --format '{{.Created}}') |
| |
| if ! remote_date=$(sudo --preserve-env skopeo inspect --authfile "${AUTHFILE}" docker://"${TOOLBOX_IMAGE}" --format '{{.Created}}'); then |
| errecho "Error inspecting ${TOOLBOX_IMAGE} via skopeo" |
| return |
| fi |
| # if the date on the registry is *NOT* newer than the local image date |
| # then we return 1. (doing a less-than comparison of the strings always |
| # fails if they are the same, hence the weird conditional here) |
| ! [[ "${remote_date}" > "${local_date}" ]] |
| } |
| |
| image_runlabel() { |
| sudo podman image inspect "${TOOLBOX_IMAGE}" --format '{{- if index .Labels "run" -}}{{.Labels.run}}{{- end -}}' |
| } |
| |
| |
| image_pull() { |
| if ! sudo --preserve-env podman pull --authfile "${AUTHFILE}" "${TOOLBOX_IMAGE}"; then |
| >&2 read -r -p "Would you like to manually authenticate to registry: '${REGISTRY}' and try again? [y/N] " |
| |
| if [[ ${REPLY} =~ ^([Yy][Ee][Ss]|[Yy])+$ ]]; then |
| sudo --preserve-env podman login --authfile "${AUTHFILE}" "${REGISTRY}" |
| sudo --preserve-env podman pull --authfile "${AUTHFILE}" "${TOOLBOX_IMAGE}" |
| else |
| errecho "Exiting..." |
| exit 1 |
| fi |
| fi |
| } |
| |
| container_create() { |
| local -r cmd=$(sudo podman image inspect "${TOOLBOX_IMAGE}" | jq -re ".[].Config.Cmd[0]") || cmd="/bin/sh" |
| if ! sudo podman create \ |
| --hostname toolbox \ |
| --name "${TOOLBOX_NAME}" \ |
| --privileged \ |
| --net=host \ |
| --pid=host \ |
| --ipc=host \ |
| --tty \ |
| --interactive \ |
| --env HOST=/host \ |
| --env NAME="${TOOLBOX_NAME}" \ |
| --env IMAGE="${IMAGE}" \ |
| --security-opt label=disable \ |
| --volume /run:/run \ |
| --volume /var/log:/var/log \ |
| --volume /etc/machine-id:/etc/machine-id \ |
| --volume /etc/localtime:/etc/localtime \ |
| --volume /:/host \ |
| "${TOOLBOX_IMAGE}" "${cmd}"; then |
| errecho "$0: failed to create container '${TOOLBOX_NAME}'" |
| exit 1 |
| fi |
| } |
| |
| container_start() { |
| if ! sudo podman start "${TOOLBOX_NAME}"; then |
| errecho "$0: failed to start container '${TOOLBOX_NAME}'" |
| exit 1 |
| fi |
| } |
| |
| container_create_runlabel() { |
| # Variable replacement logic reproduced from: |
| # https://github.com/containers/podman/blob/29d7ab3f82e38c442e449739e218349b9a4a16ea/pkg/domain/infra/abi/containers_runlabel.go#L226 |
| local pod |
| pod="$(echo "${runlabel}" \ |
| | sed 's/podman run/sudo podman create/' \ |
| | sed 's/--name NAME/--name ${TOOLBOX_NAME}/' \ |
| | sed 's/NAME=NAME/NAME=${TOOLBOX_NAME}/' \ |
| | sed 's/IMAGE=IMAGE/IMAGE=${TOOLBOX_IMAGE}/' \ |
| | sed 's/host IMAGE/host ${TOOLBOX_IMAGE}/')" |
| if ! eval "${pod}" ; then |
| errecho "$0: failed to create container from runlabel '${TOOLBOX_NAME}'" |
| exit 1 |
| fi |
| } |
| |
| container_exec() { |
| if [[ "$#" -eq 0 ]]; then |
| sudo podman attach "${TOOLBOX_NAME}" |
| else |
| echo "${*}; exit" | sudo podman attach "${TOOLBOX_NAME}" |
| fi |
| } |
| |
| show_help() { |
| errecho "USAGE: toolbox [-h/--help] [command] |
| toolbox is a small script that launches a container to let you bring in your favorite debugging or admin tools. |
| The toolbox container is a pet container and will be restarted on following runs. |
| To remove the container and start fresh, do sudo podman rm ${TOOLBOX_NAME}. |
| |
| Options: |
| -h/--help: Shows this help message |
| |
| You may override the following variables by setting them in ${TOOLBOXRC}: |
| - REGISTRY: The registry to pull from. Default: ${REGISTRY} |
| - IMAGE: The image and tag from the registry to pull. Default: ${IMAGE} |
| - TOOLBOX_NAME: The name to use for the local container. Default: ${TOOLBOX_NAME} |
| - AUTHFILE: The location where your registry credentials are stored. Default: ${AUTHFILE} |
| |
| Example toolboxrc: |
| REGISTRY=my.special.registry.example.com |
| IMAGE=debug:latest |
| TOOLBOX_NAME=special-debug-container |
| AUTHFILE=/home/core/.docker/config.json" |
| } |
| |
| main() { |
| # Execute setup first so we get proper variables |
| setup |
| # If we are passed a help switch, show help and exit |
| if [[ "$1" =~ ^(--help|-h)$ ]]; then |
| show_help |
| exit 0 |
| fi |
| run "$@" |
| cleanup |
| } |
| |
| main "$@" |