add the myos CLI: one model for both modes
A stack is a directory of compose files; the current directory is a stack when it holds one. The same command works for a project, a catalogue stack, a group and a host singleton, and stacks sharing a project are now a single compose call. Two traps of the make engine are closed on the way: an unknown command is an error instead of a silent success, and a group is only expanded when its name is lowercase, so an environment variable can no longer be mistaken for one. That guard spells out its character class because a-z matches uppercase too under a dictionary collation.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#shellcheck shell=sh
|
||||
# The commands that map straight onto docker compose.
|
||||
#
|
||||
# Stacks are grouped by compose project: every host stack shares the project of
|
||||
# the machine, so `myos up host` is a single compose call with every file, the
|
||||
# way the stack was meant to be described.
|
||||
|
||||
# myos_cmd_compose COMMAND
|
||||
myos_cmd_compose() {
|
||||
_cmd=$1
|
||||
_projects=
|
||||
_rc=0
|
||||
|
||||
for _ref in $MYOS_STACKS; do
|
||||
_files=$(myos_stack_compose_files "$_ref") || { _rc=$MYOS_E_NOSTACK; continue; }
|
||||
[ -n "$_files" ] || { myos_warning "no compose file for stack $_ref"; continue; }
|
||||
_scope=$(myos_scope "$_ref")
|
||||
_app=$(myos_stack_name "$_ref")
|
||||
case $_ref in .|./*|/*|../*) _app=$(basename "$(myos_stack_resolve "$_ref")") ;; esac
|
||||
_project=$(myos_project_name "$_scope" "$USER" "$ENV" "$_app")
|
||||
# accumulate the files of every stack sharing a project, keeping the order
|
||||
_projects=$(printf '%s\n%s\t%s' "$_projects" "$_project" "$(printf '%s' "$_files" | tr '\n' ' ')")
|
||||
done
|
||||
|
||||
[ "$_rc" = 0 ] || return "$_rc"
|
||||
|
||||
for _project in $(printf '%s' "$_projects" | sed '/^$/d' | cut -f1 | awk '!seen[$0]++'); do
|
||||
_files=$(printf '%s' "$_projects" | sed '/^$/d' | awk -F'\t' -v p="$_project" '$1==p {print $2}' | tr ' ' '\n' | sed '/^$/d' | awk '!seen[$0]++')
|
||||
# the framework networks always come last, as the make engine did
|
||||
[ -f "$MYOS_ROOT/share/compose/networks.yml" ] &&
|
||||
_files="$_files
|
||||
$MYOS_ROOT/share/compose/networks.yml"
|
||||
|
||||
COMPOSE_PROJECT_NAME=$_project
|
||||
COMPOSE_SERVICE_NAME=$(myos_service_name "$_project")
|
||||
DOCKER_NETWORK_DEFAULT=${DOCKER_NETWORK_DEFAULT:-$(myos_network_default "$_project")}
|
||||
DOCKER_NETWORK_PRIVATE=$(myos_network_private "$USER" "$ENV")
|
||||
# shellcheck disable=SC3028 # HOSTNAME is set by bin/myos, not by the shell
|
||||
DOCKER_NETWORK_PUBLIC=$(myos_network_public "$HOSTNAME")
|
||||
export COMPOSE_PROJECT_NAME COMPOSE_SERVICE_NAME
|
||||
export DOCKER_NETWORK_DEFAULT DOCKER_NETWORK_PRIVATE DOCKER_NETWORK_PUBLIC
|
||||
|
||||
case $_cmd in
|
||||
up) myos_network_ensure "$DOCKER_NETWORK_PRIVATE" "$DOCKER_NETWORK_PUBLIC" ;;
|
||||
esac
|
||||
|
||||
# shellcheck disable=SC2086,SC2046 # options and MYOS_ARGS are word lists
|
||||
myos_compose "$_project" "$_files" -- "$_cmd" $(myos_compose_options "$_cmd") ${MYOS_ARGS:-} || _rc=$?
|
||||
done
|
||||
return "$_rc"
|
||||
}
|
||||
|
||||
# myos_compose_options COMMAND the default options of each compose command
|
||||
myos_compose_options() {
|
||||
case $1 in
|
||||
up) printf '%s' "${DOCKER_COMPOSE_UP_OPTIONS:--d}" ;;
|
||||
logs) printf '%s' "${DOCKER_COMPOSE_LOGS_OPTIONS:---follow --tail=100}" ;;
|
||||
down) printf '%s' "${DOCKER_COMPOSE_DOWN_OPTIONS:-}" ;;
|
||||
*) printf '' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# myos_network_ensure NAME... create the external networks if they are missing
|
||||
myos_network_ensure() {
|
||||
for _n in "$@"; do
|
||||
[ -n "$_n" ] || continue
|
||||
if [ "${DRYRUN:-false}" = true ]; then
|
||||
printf 'docker network create %s\n' "$_n"
|
||||
else
|
||||
docker network inspect "$_n" >/dev/null 2>&1 || myos_run docker network create "$_n" >/dev/null
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#shellcheck shell=sh
|
||||
# myos doctor check that this installation can actually run a stack
|
||||
myos_cmd_doctor() {
|
||||
_rc=0
|
||||
_ok() { printf ' %-28s %s\n' "$1" "$2"; }
|
||||
_bad() { printf ' %-28s %s%s%s\n' "$1" "$MYOS_C_ERROR" "$2" "$MYOS_C_RESET"; _rc=$MYOS_E_NOREQ; }
|
||||
|
||||
printf 'myos %s at %s\n' "$MYOS_VERSION" "$MYOS_ROOT"
|
||||
printf 'requirements:\n'
|
||||
if myos_have docker; then _ok docker "$(docker version --format '{{.Client.Version}}' 2>/dev/null || echo present)"
|
||||
else _bad docker "not found"; fi
|
||||
if _c=$(myos_compose_bin 2>/dev/null); then _ok "compose" "$_c"; else _bad compose "docker compose >= $MYOS_COMPOSE_MIN_VERSION not found"; fi
|
||||
if [ "${DRYRUN:-false}" != true ]; then
|
||||
if docker info >/dev/null 2>&1; then _ok "docker daemon" "reachable${DOCKER_HOST:+ via $DOCKER_HOST}"
|
||||
else _bad "docker daemon" "unreachable${DOCKER_HOST:+ ($DOCKER_HOST)}"; fi
|
||||
fi
|
||||
|
||||
printf 'configuration:\n'
|
||||
for _f in $(myos_conf_files); do _ok "$_f" "read"; done
|
||||
[ -f "$HOME/.config/myos/config" ] && _ok "$HOME/.config/myos/config" "read"
|
||||
[ -f "$WORKDIR/.env" ] && _ok "$WORKDIR/.env" "read"
|
||||
_ok ENV "$ENV"
|
||||
_ok USER "$USER"
|
||||
# shellcheck disable=SC3028 # HOSTNAME is set by bin/myos
|
||||
_ok HOSTNAME "$HOSTNAME"
|
||||
_ok DOMAIN "$DOMAIN"
|
||||
_ok "project format" "${MYOS_PROJECT_FORMAT:-user-env-app}"
|
||||
|
||||
printf 'stacks:\n'
|
||||
_p=$(myos_path)
|
||||
if [ -n "$_p" ]; then printf '%s\n' "$_p" | tr ':' '\n' | sed 's/^/ /'
|
||||
else _bad "stack path" "empty"; fi
|
||||
|
||||
# a .env written for the make engine can hold values the shell reads differently
|
||||
if [ -f "$WORKDIR/.env" ] && grep -qE '\$\(|\$\{[a-z]' "$WORKDIR/.env"; then
|
||||
printf ' %-28s %s%s%s\n' "$WORKDIR/.env" "$MYOS_C_WARN" "contains make expansions" "$MYOS_C_RESET"
|
||||
fi
|
||||
return "$_rc"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#shellcheck shell=sh
|
||||
# myos env [VAR...] show resolved variables (replaces the make print-VAR target)
|
||||
myos_cmd_env() {
|
||||
_vars=${MYOS_VARS:-}
|
||||
[ -n "$_vars" ] || _vars=$MYOS_ARGS
|
||||
[ -n "$_vars" ] || _vars=$MYOS_REFS_RAW
|
||||
if [ -z "$_vars" ]; then
|
||||
_vars="ENV USER HOSTNAME DOMAIN WORKDIR MYOS_PATH STACK COMPOSE_PROJECT_NAME COMPOSE_FILE"
|
||||
fi
|
||||
for _v in $_vars; do
|
||||
case $_v in
|
||||
MYOS_PATH) printf '%s %s\n' "$_v" "$(myos_path)" ;;
|
||||
COMPOSE_FILE) printf '%s %s\n' "$_v" "$(myos_all_compose_files | tr '\n' ' ' | sed 's/ $//')" ;;
|
||||
COMPOSE_PROJECT_NAME) printf '%s %s\n' "$_v" "$(myos_first_project)" ;;
|
||||
STACK) printf '%s %s\n' "$_v" "$(printf '%s' "$MYOS_STACKS" | tr '\n' ' ' | sed 's/ $//')" ;;
|
||||
*) printf '%s %s\n' "$_v" "$(myos_var "$_v")" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# myos_all_compose_files every compose file of every requested stack, in order
|
||||
myos_all_compose_files() {
|
||||
for _ref in $MYOS_STACKS; do
|
||||
myos_stack_compose_files "$_ref" 2>/dev/null
|
||||
done
|
||||
[ -f "$MYOS_ROOT/share/compose/networks.yml" ] && printf '%s\n' "$MYOS_ROOT/share/compose/networks.yml"
|
||||
return 0
|
||||
}
|
||||
|
||||
# myos_first_project the compose project of the first requested stack
|
||||
myos_first_project() {
|
||||
for _ref in $MYOS_STACKS; do
|
||||
_app=$(myos_stack_name "$_ref")
|
||||
case $_ref in .|./*|/*|../*) _app=$(basename "$(myos_stack_resolve "$_ref" 2>/dev/null)") ;; esac
|
||||
myos_project_name "$(myos_scope "$_ref")" "$USER" "$ENV" "$_app"
|
||||
return 0
|
||||
done
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#shellcheck shell=sh
|
||||
# myos ls [--groups] list the stacks myos can see, and where they come from
|
||||
myos_cmd_ls() {
|
||||
case ${MYOS_REFS_RAW:-}${MYOS_ARGS:-} in
|
||||
*--groups*) myos_ls_groups; return 0 ;;
|
||||
esac
|
||||
_IFS=$IFS; IFS=:
|
||||
for _d in $(myos_path); do
|
||||
IFS=$_IFS
|
||||
printf '%s%s%s\n' "$MYOS_C_INFO" "$_d" "$MYOS_C_RESET"
|
||||
for _s in "$_d"/*; do
|
||||
[ -d "$_s" ] || continue
|
||||
_n=$(basename "$_s")
|
||||
_f=$(find "$_s" -maxdepth 1 \( -name '*.yml' -o -name '*.yaml' \) | wc -l | tr -d ' ')
|
||||
[ "$_f" = 0 ] && continue
|
||||
printf ' %-24s %s compose file(s)\n' "$_n" "$_f"
|
||||
done
|
||||
IFS=:
|
||||
done
|
||||
IFS=$_IFS
|
||||
}
|
||||
|
||||
myos_ls_groups() {
|
||||
_IFS=$IFS; IFS=:
|
||||
for _d in $(myos_path); do
|
||||
IFS=$_IFS
|
||||
for _f in "$_d"/*.mk "$_d"/*.env "$_d"/*/*.mk; do
|
||||
[ -f "$_f" ] || continue
|
||||
_n=$(basename "$_f"); _n=${_n%.mk}; _n=${_n%.env}
|
||||
_v=$(myos_group_value "$_n")
|
||||
[ -n "$_v" ] && printf '%-16s %s\n' "$_n" "$_v"
|
||||
done
|
||||
IFS=:
|
||||
done
|
||||
IFS=$_IFS
|
||||
}
|
||||
Reference in New Issue
Block a user