diff --git a/base/ubi9/Dockerfile b/base/ubi9/Dockerfile index 5a119aa6..37c4d172 100644 --- a/base/ubi9/Dockerfile +++ b/base/ubi9/Dockerfile @@ -145,10 +145,15 @@ RUN KUBEDOCK_ARCH="linux_amd64" && \ COPY --chown=0:0 kubedock_setup.sh /usr/local/bin/kubedock_setup # Configure Podman wrapper -ENV PODMAN_WRAPPER_PATH=/usr/bin/podman.wrapper -ENV ORIGINAL_PODMAN_PATH=/usr/bin/podman.orig +COPY --chown=0:0 kubedock_setup.sh /usr/local/bin/kubedock_setup +ENV PODMAN_WRAPPER_PATH=/usr/bin/podman.wrapper \ + ORIGINAL_PODMAN_PATH=/usr/bin/podman.orig COPY --chown=0:0 podman-wrapper.sh "${PODMAN_WRAPPER_PATH}" +COPY --chown=0:0 podman-compose-down-wrapper.sh "/usr/bin/podman-compose-down-wrapper.sh" +COPY --chown=0:0 podman-interactive-wrapper.sh "/usr/bin/podman-interactive-wrapper.sh" +COPY --chown=0:0 podman-cp-wrapper.sh "/usr/bin/podman-cp-wrapper.sh" RUN mv /usr/bin/podman "${ORIGINAL_PODMAN_PATH}" +COPY --chown=0:0 docker.sh "/usr/bin/docker" COPY --chown=0:0 entrypoint.sh / COPY --chown=0:0 .stow-local-ignore /home/tooling/ diff --git a/base/ubi9/docker.sh b/base/ubi9/docker.sh new file mode 100755 index 00000000..a94f4e69 --- /dev/null +++ b/base/ubi9/docker.sh @@ -0,0 +1,9 @@ +#!/usr/bin/sh + +[ -e /etc/containers/nodocker ] || \ +echo "Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg." >&2 +if [ "${KUBEDOCK_ENABLED:-false}" = "true" ]; then + exec /usr/bin/podman.wrapper "$@" +else + exec /usr/bin/podman "$@" +fi diff --git a/base/ubi9/entrypoint.sh b/base/ubi9/entrypoint.sh old mode 100644 new mode 100755 diff --git a/base/ubi9/kubedock_setup.sh b/base/ubi9/kubedock_setup.sh index 2c8400f8..79548442 100755 --- a/base/ubi9/kubedock_setup.sh +++ b/base/ubi9/kubedock_setup.sh @@ -25,7 +25,18 @@ if [ "${KUBEDOCK_ENABLED:-false}" = "true" ]; then if [ -f $KUBECONFIG ]; then echo "Kubeconfig found." - KUBEDOCK_PARAMS=${KUBEDOCK_PARAMS:-"--reverse-proxy --kubeconfig $KUBECONFIG"} + echo "Fix Kubeconfig permission." + chmod 600 $KUBECONFIG + + if [ -z "$KUBEDOCK_PARAMS" ]; then + KUBEDOCK_PARAMS="--reverse-proxy --kubeconfig $KUBECONFIG" + if [ -n "$REQUEST_CPU" ] && [ -n "$REQUEST_MEMORY" ]; then + KUBEDOCK_PARAMS="$KUBEDOCK_PARAMS --request-cpu=$REQUEST_CPU --request-memory=$REQUEST_MEMORY" + fi + if [ -n "$REAPER_KEEPMAX" ]; then + KUBEDOCK_PARAMS="$KUBEDOCK_PARAMS --reapmax=$REAPER_KEEPMAX" + fi + fi echo "Starting kubedock with params \"${KUBEDOCK_PARAMS}\"..." diff --git a/base/ubi9/podman-compose-down-wrapper.sh b/base/ubi9/podman-compose-down-wrapper.sh new file mode 100755 index 00000000..f6213564 --- /dev/null +++ b/base/ubi9/podman-compose-down-wrapper.sh @@ -0,0 +1,105 @@ +#!/bin/bash +set -euo pipefail + +# This script is a wrapper for 'podman compose down' or 'docker compose down' +# It removes the containers/services defined in the docker-compose file using 'podman rm' +# This is needed when running with kubedock, as 'compose down' cannot shut down pods directly + +usage() { + echo "Usage: $0 [OPTIONS] [SERVICES]" + echo " OPTIONS: Options for docker-compose down (e.g., -f docker-compose.yml)" + echo " SERVICES: Optional list of services to remove" + exit 1 +} + +# Parse options and service names +COMPOSE_FILE="docker-compose.yml" +OPTIONS=() +SERVICES=() +while [[ $# -gt 0 ]]; do + case "$1" in + -f|--file) + if [[ -n "${2:-}" ]]; then + COMPOSE_FILE="$2" + OPTIONS+=("$1" "$2") + shift 2 + else + echo "Error: Missing argument for $1" + usage + fi + ;; + --file=*) + COMPOSE_FILE="${1#*=}" + OPTIONS+=("$1") + shift + ;; + -h|--help) + usage + ;; + -*) + OPTIONS+=("$1") + shift + ;; + *) + SERVICES+=("$1") + shift + ;; + esac +done + +# If no -f/--file was provided, support both docker-compose.yml and docker-compose.yaml +if [[ "${COMPOSE_FILE}" == "docker-compose.yml" ]]; then + if [[ -f "docker-compose.yml" ]]; then + COMPOSE_FILE="docker-compose.yml" + elif [[ -f "docker-compose.yaml" ]]; then + COMPOSE_FILE="docker-compose.yaml" + else + echo "Error: No docker-compose.yml or docker-compose.yaml found in the current directory." + exit 1 + fi +fi + +if [[ ! -f "$COMPOSE_FILE" ]]; then + echo "Error: Compose file '$COMPOSE_FILE' not found." + exit 1 +fi + +# Get service names from compose file if none specified +if [[ ${#SERVICES[@]} -eq 0 ]]; then + # Try to use yq if available, else fallback to grep/sed/awk + if command -v yq >/dev/null 2>&1; then + mapfile -t SERVICES < <(yq '.services | keys | .[]' "$COMPOSE_FILE") + else + # Fallback: extract service names by finding top-level keys under 'services:' + mapfile -t SERVICES < <(awk '/services:/ {flag=1; next} /^[^[:space:]]/ {flag=0} flag && /^[[:space:]]+[a-zA-Z0-9_-]+:/ {gsub(":",""); print $1}' "$COMPOSE_FILE" | sed 's/^[[:space:]]*//') + fi +fi + +if [[ ${#SERVICES[@]} -eq 0 ]]; then + echo "No services found in compose file '$COMPOSE_FILE'." + exit 0 +fi + +# Compose container name: --1 +PROJECT_NAME="$(basename "$PWD")" + +echo "Removing services: ${SERVICES[*]}" +for svc in "${SERVICES[@]}"; do + # Try to get container_name from compose file + CONTAINER_NAME="" + if command -v yq >/dev/null 2>&1; then + CONTAINER_NAME=$(yq ".services.${svc}.container_name // \"\"" "$COMPOSE_FILE" | tr -d '"') + fi + if [[ -z "$CONTAINER_NAME" ]]; then + CONTAINER_NAME="${PROJECT_NAME}-${svc}-1" + fi + # Check if the container exists + if ! podman ps -a --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then + echo "No container found for service '$svc' (expected name: $CONTAINER_NAME)" + continue + fi + echo "Removing container: $CONTAINER_NAME" + podman rm -f "$CONTAINER_NAME" +done + +echo "All specified services have been removed." \ No newline at end of file diff --git a/base/ubi9/podman-cp-wrapper.sh b/base/ubi9/podman-cp-wrapper.sh new file mode 100755 index 00000000..18cfe916 --- /dev/null +++ b/base/ubi9/podman-cp-wrapper.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -euo pipefail + +# This script replaces 'podman cp' with 'oc cp', mapping the container to the correct pod via the kubedock label. + +# Parse options (stop at first non-option argument) +OPTIONS=() +while [[ $# -gt 0 ]]; do + case "$1" in + -*) OPTIONS+=("$1"); shift ;; + *) break ;; + esac +done + +# Now $1 and $2 are [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH +if [[ $# -lt 2 || -z "${1:-}" || -z "${2:-}" ]]; then + echo "Error: Both [CONTAINER:]SRC_PATH and [CONTAINER:]DEST_PATH must be specified." >&2 + exit 1 +fi + +SRC="$1" +DEST="$2" + +get_pod_for_container() { + local container_ref="$1" + if [[ "$container_ref" == *:* ]]; then + local container_id="${container_ref%%:*}" + # Find pod with label kubedock.containerid= + local pod_name + pod_name=$(oc get pods -l "kubedock.containerid=${container_id}" -o jsonpath='{.items[0].metadata.name}') + if [[ -z "$pod_name" ]]; then + echo "Error: No pod found for container id: $container_id" >&2 + exit 1 + fi + # Return pod:rest_of_path + echo "${pod_name}:${container_ref#*:}" + else + # No container ref, just return as is + echo "$container_ref" + fi +} + +OC_SRC=$(get_pod_for_container "$SRC") +OC_DEST=$(get_pod_for_container "$DEST") + +exec oc cp "${OPTIONS[@]}" "$OC_SRC" "$OC_DEST" \ No newline at end of file diff --git a/base/ubi9/podman-interactive-wrapper.sh b/base/ubi9/podman-interactive-wrapper.sh new file mode 100755 index 00000000..c53d2913 --- /dev/null +++ b/base/ubi9/podman-interactive-wrapper.sh @@ -0,0 +1,136 @@ +#!/bin/bash +set -euo pipefail + +# This script transparently replaces problematic 'podman run -it ...' invocations +# with a workaround for kubedock compatibility: +# podman exec -it $(podman run -d --rm [OPTIONS] IMAGE tail -f /dev/null) [COMMAND] + +usage() { + echo "Usage: $0 run [OPTIONS] IMAGE [COMMAND] [ARG...]" + exit 1 +} + +ARGS=( "$@" ) +if [[ "${ARGS[0]:-}" != "run" ]]; then + usage +fi + +RUN_OPTS=() +IMAGE="" +CMD_ARGS=() + +skip_next=0 +found_image=0 +for ((i=1; i<${#ARGS[@]}; i++)); do + arg="${ARGS[$i]}" + if [[ $skip_next -eq 1 ]]; then + # Only skip for --tty/--interactive with space value + RUN_OPTS+=("$arg") + skip_next=0 + continue + fi + if [[ $found_image -eq 0 ]]; then + # Remove all forms of interactive/tty options for internal run + case "$arg" in + -i|-t|-it|-ti) + continue + ;; + --tty|--interactive) + # skip this and the next arg if it does not start with '-' + if [[ $((i+1)) -lt ${#ARGS[@]} && "${ARGS[$((i+1))]}" != "-"* ]]; then + skip_next=1 + fi + continue + ;; + --tty=*|--interactive=*) + continue + ;; + # Options that take a value + -u=*|--user=*) + # Handle -u=root and --user=root + RUN_OPTS+=("--user=$(id -u)") + continue + ;; + -u|--user) + # Handle -u root and --user root + RUN_OPTS+=("--user=$(id -u)") + ((i++)) # Skip the next argument (the value) + continue + ;; + -v|--volume|-e|--env|-w|--workdir|--name|--hostname|--entrypoint|--add-host|--device|--label|--network|--cap-add|--cap-drop|--security-opt|--tmpfs|--ulimit|--mount|--publish|--expose|--dns|--dns-search|--dns-option|--mac-address|--memory|--memory-swap|--cpu-shares|--cpus|--cpu-period|--cpu-quota|--cpu-rt-runtime|--cpu-rt-period|--cpuset-cpus|--cpuset-mems|--blkio-weight|--blkio-weight-device|--device-read-bps|--device-write-bps|--device-read-iops|--device-write-iops|--shm-size|--sysctl|--log-driver|--log-opt|--restart|--stop-signal|--stop-timeout|--health-cmd|--health-interval|--health-retries|--health-timeout|--health-start-period|--userns|--cgroup-parent|--pid|--ipc|--uts|--runtime|--storage-opt|--volume-driver|--volumes-from|--env-file|--group-add|--init|--isolation|--kernel-memory|--memory-reservation|--memory-swappiness|--oom-kill-disable|--oom-score-adj|--pids-limit|--privileged|--publish-all|--read-only|--sig-proxy + RUN_OPTS+=("$arg") + skip_next=1 + continue + ;; + --*=*) + # long option with value, e.g. --workdir=/foo + # filter out --tty= and --interactive= + if [[ "$arg" == --tty=* || "$arg" == --interactive=* ]]; then + continue + fi + if [[ "$arg" == --user=* ]]; then + RUN_OPTS+=("--user=$(id -u)") + continue + fi + RUN_OPTS+=("$arg") + continue + ;; + --) + found_image=1 + continue + ;; + -*) + # Handle combined short options, e.g. -itv, -tiv, etc. + # Remove i and t, keep the rest + if [[ "$arg" =~ ^-([it]+)$ ]]; then + # skip if only i/t/it/ti + continue + elif [[ "$arg" =~ ^-([it]+)(.+)$ ]]; then + rest="${BASH_REMATCH[2]}" + if [[ -n "$rest" ]]; then + RUN_OPTS+=("-$rest") + fi + continue + else + RUN_OPTS+=("$arg") + continue + fi + ;; + *) + IMAGE="$arg" + found_image=1 + continue + ;; + esac + else + CMD_ARGS+=("$arg") + fi +done +# Remove duplicate --rm if present +has_rm=0 +for opt in "${RUN_OPTS[@]}"; do + if [[ "$opt" == "--rm" ]]; then + has_rm=1 + break + fi +done + + +FINAL_RUN_OPTS=("${RUN_OPTS[@]}") +if [[ $has_rm -eq 0 ]]; then + FINAL_RUN_OPTS+=("--rm") +fi + +if [[ -z "$IMAGE" ]]; then + echo "Error: Could not determine image name in podman run command." + usage +fi + + +CONTAINER_ID=$(podman run -d "${FINAL_RUN_OPTS[@]}" "$IMAGE" tail -f /dev/null) + +if [[ ${#CMD_ARGS[@]} -eq 0 ]]; then + CMD_ARGS=(bash) +fi + +exec podman exec -it "$CONTAINER_ID" "${CMD_ARGS[@]}" diff --git a/base/ubi9/podman-wrapper.sh b/base/ubi9/podman-wrapper.sh index b7f7fbc9..51cb870b 100755 --- a/base/ubi9/podman-wrapper.sh +++ b/base/ubi9/podman-wrapper.sh @@ -6,6 +6,33 @@ KUBEDOCK_SUPPORTED_COMMANDS=${KUBEDOCK_SUPPORTED_COMMANDS:-"run ps exec cp logs PODMAN_ARGS=( "$@" ) +# Check for 'compose down' command and delegate to the special wrapper +if [[ "${PODMAN_ARGS[0]:-}" == "compose" && "${PODMAN_ARGS[1]:-}" == "down" ]]; then + # Forward all args after 'compose down' to the wrapper + exec "/usr/bin/podman-compose-down-wrapper.sh" "${PODMAN_ARGS[@]:2}" +fi + +# Intercept 'podman run' with interactive/tty flags and delegate to the interactive wrapper +if [[ "${PODMAN_ARGS[0]:-}" == "run" ]]; then + has_interactive=0 + has_tty=0 + for arg in "${PODMAN_ARGS[@]}"; do + case "$arg" in + -i|--interactive) has_interactive=1 ;; + -t|--tty) has_tty=1 ;; + -it|-ti) has_interactive=1; has_tty=1 ;; + esac + done + if [[ $has_interactive -eq 1 && $has_tty -eq 1 ]]; then + exec "/usr/bin/podman-interactive-wrapper.sh" "${PODMAN_ARGS[@]}" + fi +fi + +# Intercept 'podman cp' and delegate to the cp wrapper +if [[ "${PODMAN_ARGS[0]:-}" == "cp" ]]; then + exec "/usr/bin/podman-cp-wrapper.sh" "${PODMAN_ARGS[@]:1}" +fi + TRUE=0 FALSE=1