Commit 8d9bdd72 authored by Ivan Mazhukin's avatar Ivan Mazhukin

Init commit

parents
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")"
DEFAULT_REMOTE_HOST="${EPM_DOCKER_TEST_REMOTE_HOST:-localhost}"
DEFAULT_LOG_ROOT="${XDG_STATE_HOME:-$HOME/.local/state}/epm-docker-test"
COMMAND="play"
APP_NAME=""
SYSTEM_INPUT=""
SYSTEM_IMAGE=""
RUN_MODE="auto"
REMOTE_HOST="$DEFAULT_REMOTE_HOST"
REMOTE_USER=""
SOURCE_KIND="local"
SOURCE_PATH=""
BUILDER_USER=""
BUILDER_PATH=""
INTERNAL_LOCAL_RUN=0
LOG_ROOT="$DEFAULT_LOG_ROOT"
LOG_FILE=""
usage() {
cat <<'EOF'
Usage:
epm-docker-test.sh [options] play <app> <system>
epm-docker-test.sh [options] <app> <system>
Options:
--mode <auto|local|remote> Runner mode. Default: auto
--remote Shortcut for --mode remote
--local Shortcut for --mode local
--remote-host <host> SSH host for fallback runner. Default: localhost
--remote-user <user> SSH user with Docker access
--eepm-source <local|builder64>
eepm source selector. Default: local
--eepm-dir <path> Explicit eepm tree path
--builder-user <user> Preferred owner for ~/Projects/... lookup
--builder-path <path> Explicit builder64 source path
--log-root <path> Directory for saved logs
-h, --help Show this help
Examples:
epm-docker-test.sh nginx fedora
epm-docker-test.sh play nginx Fedora/43
epm-docker-test.sh --remote-user dockerbot --eepm-source builder64 nginx fedora:43
EOF
}
info() {
printf '[info] %s\n' "$*" >&2
}
warn() {
printf '[warn] %s\n' "$*" >&2
}
fatal() {
printf '[error] %s\n' "$*" >&2
exit 1
}
require_command() {
local cmd="$1"
command -v "$cmd" >/dev/null 2>&1 || fatal "Required command not found: $cmd"
}
slugify() {
local value="${1,,}"
value="${value//[^a-z0-9]/-}"
value="${value##-}"
value="${value%%-}"
printf '%s\n' "${value:-run}"
}
normalize_system_name() {
local raw="$1"
local normalized
normalized="${raw// /}"
normalized="${normalized,,}"
normalized="${normalized//\//:}"
[[ -n "$normalized" ]] || fatal "Target system is empty"
if [[ "$normalized" != *:* ]]; then
normalized="${normalized}:latest"
fi
if [[ "$normalized" =~ ^[a-z0-9._/-]+:[a-z0-9._-]+$ ]]; then
printf '%s\n' "$normalized"
return 0
fi
fatal "Unsupported system format: $raw"
}
can_use_local_docker() {
command -v docker >/dev/null 2>&1 || return 1
docker info >/dev/null 2>&1
}
discover_remote_user() {
# TODO: implement remote user discovery for docker access.
return 1
}
user_home_dir() {
local user="$1"
local passwd_line
if command -v getent >/dev/null 2>&1; then
passwd_line="$(getent passwd "$user" || true)"
if [[ -n "$passwd_line" ]]; then
printf '%s\n' "${passwd_line##*:}"
return 0
fi
fi
printf '/home/%s\n' "$user"
}
project_suffix_from_pwd() {
local cwd marker
cwd="$(pwd -P)"
marker="/Projects/"
if [[ "$cwd" == *"$marker"* ]]; then
printf '%s\n' "${cwd#*"$marker"}"
return 0
fi
basename "$cwd"
}
verify_eepm_tree() {
local tree="$1"
[[ -d "$tree" ]] || fatal "eepm tree is not a directory: $tree"
[[ -f "$tree/epm" ]] || fatal "eepm tree does not contain ./epm: $tree"
}
resolve_local_source_path() {
local candidate
candidate="${SOURCE_PATH:-$(pwd -P)}"
candidate="$(realpath "$candidate")"
verify_eepm_tree "$candidate"
printf '%s\n' "$candidate"
}
resolve_builder_source_path() {
# TODO: implement builder64 source discovery.
fatal "TODO: resolve_builder_source_path is not implemented yet"
}
resolve_source_path() {
case "$SOURCE_KIND" in
local)
resolve_local_source_path
;;
builder64)
resolve_builder_source_path
;;
explicit)
[[ -n "$SOURCE_PATH" ]] || fatal "Explicit source path is empty"
SOURCE_PATH="$(realpath "$SOURCE_PATH")"
verify_eepm_tree "$SOURCE_PATH"
printf '%s\n' "$SOURCE_PATH"
;;
*)
fatal "Unsupported eepm source kind: $SOURCE_KIND"
;;
esac
}
create_log_file() {
local safe_app safe_system run_dir timestamp
if ! mkdir -p "$LOG_ROOT" 2>/dev/null; then
warn "Cannot write to log root $LOG_ROOT; falling back to ${TMPDIR:-/tmp}/epm-docker-test"
LOG_ROOT="${TMPDIR:-/tmp}/epm-docker-test"
mkdir -p "$LOG_ROOT" || fatal "Could not create log root: $LOG_ROOT"
fi
safe_app="$(slugify "$APP_NAME")"
safe_system="$(slugify "$SYSTEM_IMAGE")"
timestamp="$(date +%Y%m%d-%H%M%S)"
run_dir="$LOG_ROOT/$timestamp-$safe_app-$safe_system"
mkdir -p "$run_dir" || fatal "Could not create log directory: $run_dir"
printf '%s\n' "$run_dir/run.log"
}
print_failure_excerpt() {
local matches
[[ -f "$LOG_FILE" ]] || {
printf '\nNo log file was captured.\n' >&2
return 0
}
printf '\nCritical log lines:\n' >&2
matches="$(grep -nEi 'critical|fatal|error|failed|traceback|no such|permission denied' "$LOG_FILE" | tail -n 20 || true)"
if [[ -n "$matches" ]]; then
printf '%s\n' "$matches" >&2
return 0
fi
tail -n 40 "$LOG_FILE" >&2 || true
}
build_container_script() {
local script_path="$1"
# TODO: generate container bootstrap/run script.
fatal "TODO: build_container_script is not implemented yet"
}
run_container_locally() {
local resolved_source="$1"
local inner_script container_name
require_command docker
inner_script="$(mktemp "${TMPDIR:-/tmp}/epm-docker-test-inner.XXXXXX.sh")"
trap 'rm -f "$inner_script"' RETURN
build_container_script "$inner_script"
container_name="epm-test-$(slugify "$APP_NAME")-$(slugify "$SYSTEM_IMAGE")-$$"
info "Using eepm tree: $resolved_source"
info "Target image: $SYSTEM_IMAGE"
info "Runner: local docker"
docker run --rm \
--name "$container_name" \
--hostname epm-docker-test \
--workdir /work/eepm \
--volume "$resolved_source:/work/eepm:ro" \
--volume "$inner_script:/tmp/epm-docker-test-inner.sh:ro" \
"$SYSTEM_IMAGE" \
bash /tmp/epm-docker-test-inner.sh "$COMMAND" "$APP_NAME"
}
build_remote_args() {
local -a args
local explicit_local_source
args=(--internal-local-run --mode local)
if [[ "$SOURCE_KIND" == "local" || "$SOURCE_KIND" == "explicit" ]]; then
explicit_local_source="$(resolve_local_source_path)"
args+=(--eepm-source explicit --eepm-dir "$explicit_local_source")
else
args+=(--eepm-source "$SOURCE_KIND")
[[ -n "$BUILDER_USER" ]] && args+=(--builder-user "$BUILDER_USER")
[[ -n "$BUILDER_PATH" ]] && args+=(--builder-path "$BUILDER_PATH")
fi
args+=("$COMMAND" "$APP_NAME" "$SYSTEM_IMAGE")
printf '%s\0' "${args[@]}"
}
run_container_via_ssh() {
local target_user target
local -a ssh_args forwarded_args
require_command ssh
target_user="${REMOTE_USER:-$(discover_remote_user || true)}"
[[ -n "$target_user" ]] || fatal "Could not determine remote user with Docker access; use --remote-user"
target="${target_user}@${REMOTE_HOST}"
info "Runner: ssh -> $target"
while IFS= read -r -d '' arg; do
forwarded_args+=("$arg")
done < <(build_remote_args)
ssh_args=(
-o BatchMode=yes
-o StrictHostKeyChecking=accept-new
"$target"
bash -s --
)
ssh "${ssh_args[@]}" "${forwarded_args[@]}" <"$SCRIPT_PATH"
}
run_once() {
local resolved_source
case "$RUN_MODE" in
auto)
if can_use_local_docker; then
resolved_source="$(resolve_source_path)"
run_container_locally "$resolved_source"
else
warn "Local Docker is unavailable; falling back to ssh runner"
run_container_via_ssh
fi
;;
local)
resolved_source="$(resolve_source_path)"
run_container_locally "$resolved_source"
;;
remote)
run_container_via_ssh
;;
*)
fatal "Unsupported run mode: $RUN_MODE"
;;
esac
}
parse_args() {
local positional=()
while (($# > 0)); do
case "$1" in
play)
COMMAND="play"
shift
;;
--mode)
[[ $# -ge 2 ]] || fatal "--mode requires a value"
RUN_MODE="$2"
shift 2
;;
--remote)
RUN_MODE="remote"
shift
;;
--local)
RUN_MODE="local"
shift
;;
--remote-host)
[[ $# -ge 2 ]] || fatal "--remote-host requires a value"
REMOTE_HOST="$2"
shift 2
;;
--remote-user)
[[ $# -ge 2 ]] || fatal "--remote-user requires a value"
REMOTE_USER="$2"
shift 2
;;
--eepm-source)
[[ $# -ge 2 ]] || fatal "--eepm-source requires a value"
SOURCE_KIND="$2"
shift 2
;;
--eepm-dir)
[[ $# -ge 2 ]] || fatal "--eepm-dir requires a value"
SOURCE_PATH="$2"
shift 2
;;
--builder-user)
[[ $# -ge 2 ]] || fatal "--builder-user requires a value"
BUILDER_USER="$2"
shift 2
;;
--builder-path)
[[ $# -ge 2 ]] || fatal "--builder-path requires a value"
BUILDER_PATH="$2"
shift 2
;;
--log-root)
[[ $# -ge 2 ]] || fatal "--log-root requires a value"
LOG_ROOT="$2"
shift 2
;;
--internal-local-run)
INTERNAL_LOCAL_RUN=1
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
while (($# > 0)); do
positional+=("$1")
shift
done
;;
-*)
fatal "Unknown option: $1"
;;
*)
positional+=("$1")
shift
;;
esac
done
if ((${#positional[@]} != 2)); then
usage >&2
exit 1
fi
APP_NAME="${positional[0]}"
SYSTEM_INPUT="${positional[1]}"
}
main() {
local status
parse_args "$@"
SYSTEM_IMAGE="$(normalize_system_name "$SYSTEM_INPUT")"
if ((INTERNAL_LOCAL_RUN)); then
run_once
return 0
fi
LOG_FILE="$(create_log_file)"
info "Log file: $LOG_FILE"
info "Normalized system: $SYSTEM_IMAGE"
info "Test command: epm $COMMAND $APP_NAME"
if run_once 2>&1 | tee "$LOG_FILE"; then
status=0
else
status=$?
fi
if ((status == 0)); then
printf '\nTest passed: epm %s %s on %s\n' "$COMMAND" "$APP_NAME" "$SYSTEM_IMAGE"
printf 'Log: %s\n' "$LOG_FILE"
else
printf '\nTest failed: epm %s %s on %s\n' "$COMMAND" "$APP_NAME" "$SYSTEM_IMAGE" >&2
printf 'Log: %s\n' "$LOG_FILE" >&2
print_failure_excerpt
fi
return "$status"
}
main "$@"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment