#!/usr/bin/env bash
# http://redsymbol.net/articles/unofficial-bash-strict-mode/

#########################################################################################
#  SCRIPT: clear-docker-cache.sh
#  Description: Used to cleanup unused docker/podman containers, images, and volumes
######################################################################################
IFS=$'\n\t'
set -euo pipefail

# Prefer docker; fall back to podman when it isn't on PATH. The INFO
# lines surface the fallback in cron mail.
CONTAINER_ENGINE=docker
if ! [ -x "$(command -v docker)" ]; then
  echo -e "INFO: docker not found, checking for podman..."
  if [ -x "$(command -v podman)" ]; then
    echo -e "INFO: podman found, using podman"
    CONTAINER_ENGINE=podman
  else
    # Exit 0 so cron doesn't mail on every run for hosts with no engine.
    echo -e "WARNING: neither docker nor podman found, skipping clear-docker-cache"
    exit 0
  fi
fi

ENGINE_API_VERSION=$("$CONTAINER_ENGINE" version --format '{{.Client.APIVersion}}')
ENGINE_CLIENT_VERSION=$("$CONTAINER_ENGINE" version --format '{{.Client.Version}}')
ENGINE_CLIENT_VERSION="${ENGINE_CLIENT_VERSION%%-*}"
REQUIRED_DOCKER_API_VERSION=1.25

# Per-type filter flags for granular control (comma-separated values).
# Default: only filter managed containers and volumes; prune all unused images.
# Backward compatibility: FILTER_FLAG sets CONTAINER_FILTER_FLAGS as a fallback.
CONTAINER_FILTER_FLAGS="${CONTAINER_FILTER_FLAGS:-${FILTER_FLAG:-label=com.gitlab.gitlab-runner.managed=true}}"
IMAGE_FILTER_FLAGS="${IMAGE_FILTER_FLAGS:-}"
VOLUME_FILTER_FLAGS="${VOLUME_FILTER_FLAGS:-label=com.gitlab.gitlab-runner.managed=true}"

# Build filter flag arrays from comma-separated values.
# Using mapfile avoids IFS word-splitting issues at call sites.
build_filters() {
  local flags="$1"
  [ -z "$flags" ] && return 0
  local IFS=,
  # Intentional word splitting on IFS=, to emit one --filter per flag
  # shellcheck disable=SC2086
  printf -- '--filter=%s\n' $flags
}

mapfile -t CONTAINER_FILTERS < <(build_filters "$CONTAINER_FILTER_FLAGS")
mapfile -t IMAGE_FILTERS < <(build_filters "$IMAGE_FILTER_FLAGS")
mapfile -t VOLUME_FILTERS < <(build_filters "$VOLUME_FILTER_FLAGS")

usage() {
  echo -e "\nUsage: $0 prune-volumes|prune|space|help\n"
  echo -e "\tprune-volumes    Remove all unused containers, images, and volumes"
  echo -e "\tprune            Remove all unused containers and images"
  echo -e "\tspace            Show docker/podman disk usage"
  echo -e "\thelp             Show usage"
  exit 1
}

# Skip the docker API gate under podman: different version scheme,
# and podman v3+ supports the prune commands below natively.
if [ "$CONTAINER_ENGINE" = "docker" ] && awk "BEGIN {exit !(\"$ENGINE_API_VERSION\" < \"$REQUIRED_DOCKER_API_VERSION\")}"; then
  echo -e "ERROR: Your current API version is lower than ${REQUIRED_DOCKER_API_VERSION}. The client and daemon API must both be at least ${REQUIRED_DOCKER_API_VERSION}+ to run these commands. Kindly upgrade your docker version."
  exit 1
fi

COMMAND="${1:-prune-volumes}"

case "$COMMAND" in

  prune)

    echo -e "\nCheck and remove all unused containers (both dangling and unreferenced)"
    echo -e "-----------------------------------------------------------------------"

    if [ "$CONTAINER_ENGINE" = "docker" ]; then
      if awk "BEGIN {exit !(\"$ENGINE_CLIENT_VERSION\" < \"17.06.1\")}"; then
        # docker system prune does not exist before 17.06.1, fall back to docker rm
        CONTAINERS=$("$CONTAINER_ENGINE" ps -a -q \
                    --filter=status=exited \
                    --filter=status=dead \
                    "${CONTAINER_FILTERS[@]}")

        if [ -n "${CONTAINERS}" ]; then
          "$CONTAINER_ENGINE" rm "${CONTAINERS}"
        fi
      else
        "$CONTAINER_ENGINE" container prune -f "${CONTAINER_FILTERS[@]}"
        "$CONTAINER_ENGINE" image prune -af "${IMAGE_FILTERS[@]}"
        "$CONTAINER_ENGINE" network prune -f
      fi
    elif [ "$CONTAINER_ENGINE" = "podman" ]; then
      "$CONTAINER_ENGINE" container prune -f "${CONTAINER_FILTERS[@]}"
      "$CONTAINER_ENGINE" image prune -af "${IMAGE_FILTERS[@]}"
      "$CONTAINER_ENGINE" network prune -f
    else
      echo -e "ERROR: unsupported container engine: ${CONTAINER_ENGINE}"
      exit 1
    fi

    exit 0
    ;;

  space)

    echo -e "\nShow docker/podman disk usage"
    echo -e "-----------------------------"
    "$CONTAINER_ENGINE" system df

    exit 0
    ;;

  help)

    usage
    ;;

  prune-volumes)

    echo -e "\nCheck and remove all unused containers (both dangling and unreferenced) including volumes."
    echo -e "------------------------------------------------------------------------------------------"

    if [ "$CONTAINER_ENGINE" = "docker" ]; then
      if awk "BEGIN {exit !(\"$ENGINE_CLIENT_VERSION\" < \"17.04.0\")}"; then
        # Prior to 17.04, use docker rm -v as fallback
        CONTAINERS=$("$CONTAINER_ENGINE" ps -a -q \
                    --filter=status=exited \
                    --filter=status=dead \
                    "${CONTAINER_FILTERS[@]}")

        if [ -n "${CONTAINERS}" ]; then
          "$CONTAINER_ENGINE" rm -v "${CONTAINERS}"
        fi

        exit 0
      elif awk "BEGIN {exit !(\"$ENGINE_CLIENT_VERSION\" < \"23.0.0\")}"; then
        # Prior to 23.0, docker volume prune didn't support --all
        "$CONTAINER_ENGINE" container prune -f "${CONTAINER_FILTERS[@]}"
        "$CONTAINER_ENGINE" image prune -af "${IMAGE_FILTERS[@]}"
        "$CONTAINER_ENGINE" volume prune -f "${VOLUME_FILTERS[@]}"
        "$CONTAINER_ENGINE" network prune -f
      else
        "$CONTAINER_ENGINE" container prune -f "${CONTAINER_FILTERS[@]}"
        "$CONTAINER_ENGINE" image prune -af "${IMAGE_FILTERS[@]}"
        "$CONTAINER_ENGINE" volume prune -af "${VOLUME_FILTERS[@]}"
        "$CONTAINER_ENGINE" network prune -f
      fi
    elif [ "$CONTAINER_ENGINE" = "podman" ]; then
      "$CONTAINER_ENGINE" container prune -f "${CONTAINER_FILTERS[@]}"
      "$CONTAINER_ENGINE" image prune -af "${IMAGE_FILTERS[@]}"
      # Released podman (through 5.8) has no -a/--all on `volume prune`; it
      # already removes all unused volumes, so -f is the complete equivalent of
      # docker's --all (docker only grew `volume prune --all` in 23.0).
      # TODO: podman's unreleased docker-matching change adds --all and makes
      # the default anonymous-only. Once that ships, add --all here so named
      # volumes are also pruned. https://gitlab.com/gitlab-org/gitlab-runner/-/issues/36934
      "$CONTAINER_ENGINE" volume prune -f "${VOLUME_FILTERS[@]}"
      "$CONTAINER_ENGINE" network prune -f
    else
      echo -e "ERROR: unsupported container engine: ${CONTAINER_ENGINE}"
      exit 1
    fi

    exit 0
    ;;

esac
