Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7163c844c9 | ||
|
|
084a25c627 | ||
|
|
6192d73cbe | ||
|
|
3e55cdcd14 | ||
|
|
55fae625d6 | ||
|
|
f541ca418b | ||
|
|
234739e531 | ||
|
|
ac41e6e5f9 | ||
|
|
c08c379565 | ||
|
|
32e2624245 |
@@ -0,0 +1,41 @@
|
||||
# myos, for agents working on this repository
|
||||
|
||||
myos runs docker compose stacks. `skills/myos/SKILL.md` explains how to *use*
|
||||
it; this file is about changing it.
|
||||
|
||||
## Check your work
|
||||
|
||||
```sh
|
||||
make test # unit + golden, both engines, docker is mocked
|
||||
make lint # shellcheck
|
||||
```
|
||||
|
||||
Golden tests compare the output of the CLI **and** of the legacy make engine
|
||||
against recordings made at the tag `legacy-1.0-beta`. If a change moves an
|
||||
output on purpose, record the CLI expectation in `spec/golden/expected.cli/`
|
||||
and write down why in `spec/golden/DELTAS.md`. Never edit
|
||||
`spec/golden/expected/` by hand: it is the behaviour of the old engine.
|
||||
|
||||
## Layout
|
||||
|
||||
See `skills/myos/references/authoring.md`. In short: `bin/myos` parses and
|
||||
dispatches, `lib/*.sh` holds one concern each, `lib/cmd/<name>.sh` holds one
|
||||
command each, `share/compose/` holds the two overlays the framework itself
|
||||
provides, and the stacks live in another repository, `myos-stacks`.
|
||||
|
||||
## Shell constraints
|
||||
|
||||
POSIX shell only: this runs on the bash 3.2 of macOS and on Alpine. No `local`,
|
||||
no arrays, no `[[`. Two traps already paid for:
|
||||
|
||||
- `[a-z]` in a `case` pattern also matches uppercase under a `fr_FR` collation;
|
||||
use `[:lower:]`.
|
||||
- a function called inside `$( )` cannot return anything through a global.
|
||||
|
||||
## Do not
|
||||
|
||||
- Reintroduce a `stack/` or `docker/` directory here: they belong to
|
||||
`myos-stacks`.
|
||||
- Make an unknown command or an unknown stack succeed silently.
|
||||
- Change a default project name without an entry in `DELTAS.md` and a note in
|
||||
the skill: it renames the containers and volumes of every deployment.
|
||||
@@ -1,5 +1,34 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v2.0.0-dev - 2026-09-03
|
||||
|
||||
- new bash CLI (`bin/myos`, `lib/`): one model for a project directory, a
|
||||
catalogue stack, a group and a host singleton
|
||||
- unknown command or unknown stack now fails, instead of succeeding silently
|
||||
- default compose project is `<user>-<env>-<app>`; set
|
||||
`MYOS_PROJECT_FORMAT=user-app-env` on deployments created before this
|
||||
- `myos ls`, `myos env`, `myos doctor` to inspect an installation
|
||||
- `install.sh`, and the catalogue is looked up beside the installation
|
||||
- agent skill in `skills/myos/`, contributor notes in `AGENTS.md`
|
||||
- stacks carry their settings in `<name>.env` and `<name>.sh` hooks, so the
|
||||
catalogue no longer needs make to be installed
|
||||
- lazy defaults (`myos_default_<VAR>` functions) give the recursive `?=` of
|
||||
make in pure shell: an explicit value wins, and the default is recomputed
|
||||
at each reference
|
||||
- `myos env-update` generates a `.env` from the `.env.dist` templates,
|
||||
expanding `${VAR}` and `$(command)`, including forward references
|
||||
- the project `.env` now wins over `/etc/conf.d/myos`, as documented;
|
||||
`MYOS_CONF_PRIORITY=system` restores the previous order
|
||||
- `share/make/shim.mk`: make as an optional front end over the same shell code
|
||||
- commands chain: `myos build up logs host/fabio`, as make targets did
|
||||
- the stack catalogue no longer needs make at all: its settings are hooks, and
|
||||
only six stacks keep a .mk, for targets
|
||||
- a stack found in several directories of the stack path is merged, project
|
||||
last, so a project refines a catalogue stack instead of replacing it
|
||||
- `--color always|never|auto`, and no colour when the output is piped
|
||||
- verified under the /bin/sh of Alpine (busybox) and Debian (dash)
|
||||
- the make engine still works and is still covered by the golden tests
|
||||
|
||||
## v1.1 - 2026-09-03
|
||||
|
||||
- move the stack catalogue and the docker build contexts to the myos-stacks project
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
DEV_TARGETS := test test-unit test-golden test-integration lint golden-record
|
||||
DEV_TARGETS := test test-unit test-golden test-integration test-portability lint golden-record
|
||||
ifneq ($(filter $(DEV_TARGETS),$(MAKECMDGOALS)),)
|
||||
|
||||
SHELLSPEC ?= shellspec
|
||||
@@ -16,6 +16,17 @@ test-integration: ## Run tests needing a real docker daemon
|
||||
MYOS_INTEGRATION=1 $(SHELLSPEC) spec/integration
|
||||
golden-record: ## Re-record golden expectations from the legacy engine
|
||||
spec/golden/record.sh $(CASES)
|
||||
test-portability: ## Run the CLI under the /bin/sh of Alpine and Debian
|
||||
@for img in alpine:3.20 debian:13-slim; do \
|
||||
printf '%s: ' "$$img"; \
|
||||
tar cf - --exclude .git . | docker run -i --rm "$$img" /bin/sh -c \
|
||||
'mkdir -p /myos && tar xf - -C /myos && cd /tmp && \
|
||||
export PATH=/myos/spec/support/bin:$$PATH && \
|
||||
/myos/bin/myos version >/dev/null && \
|
||||
/myos/bin/myos -C /myos/spec/fixtures/host-project -n up host >/dev/null && \
|
||||
echo ok'; \
|
||||
done
|
||||
|
||||
lint: ## shellcheck all shell sources
|
||||
$(SHELLCHECK) -s bash myos spec/golden/record.sh spec/support/run.sh spec/support/bin/* $(wildcard bin/* lib/*.sh lib/cmd/*.sh install.sh)
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ sudo git clone https://github.com/aya/myos-stacks /usr/local/share/myos
|
||||
## Usage
|
||||
|
||||
```sh
|
||||
myos doctor # check the installation first
|
||||
myos ls # what stacks are reachable
|
||||
myos -n up host # print what it would run
|
||||
myos up # the stack of the current directory
|
||||
myos up STACK=host # a group of stacks, see stack/host/host.mk
|
||||
myos up STACK=host/fabio # a single stack
|
||||
@@ -108,16 +111,38 @@ Networks: `default` = `_<project>` (private to the project), `private` =
|
||||
| `ENV=<env>` | environment: selects `.env.<env>` and the `<name>.<env>.yml` overlays |
|
||||
| `STACK=<refs>` | stacks to act on |
|
||||
| `SERVICE=<name>` | target one compose service (`exec`, `run`, `logs`, `scale`) |
|
||||
| `MYOS_PROJECT_FORMAT` | `user-env-app` (default) or `user-app-env` for deployments made before myos 2.0 |
|
||||
|
||||
```sh
|
||||
myos print-COMPOSE_FILE # show a variable
|
||||
myos print-COMPOSE_PROJECT_NAME
|
||||
myos debug # show debug variables
|
||||
myos doc # self documentation from the make comments
|
||||
myos env COMPOSE_FILE # show a variable
|
||||
myos env COMPOSE_PROJECT_NAME
|
||||
myos config <stack> # the rendered compose file
|
||||
```
|
||||
|
||||
The make targets keep working: `print-VAR`, `stack-<stack>-<command>`,
|
||||
`<command>@<env>`, and a project `Makefile` that includes `make/include.mk`.
|
||||
|
||||
`SETUP_UFW=true` enables the ufw/ufw-docker integration (`myos setup-ufw`).
|
||||
|
||||
## With make
|
||||
|
||||
A project that would rather drive make can include the shim, which turns every
|
||||
myos command into a make target while leaving its own targets alone:
|
||||
|
||||
```make
|
||||
MYOS ?= /usr/local/lib/myos
|
||||
include $(MYOS)/share/make/shim.mk
|
||||
```
|
||||
|
||||
`make up STACK=host` then runs exactly what `myos up host` runs: the shim only
|
||||
forwards. make is not needed otherwise, and the CLI never calls it.
|
||||
|
||||
## For agents
|
||||
|
||||
`skills/myos/SKILL.md` is a skill describing how to drive myos, with
|
||||
references on the conventions, the commands and the failure modes.
|
||||
`AGENTS.md` covers changing myos itself.
|
||||
|
||||
## Tests
|
||||
|
||||
```sh
|
||||
|
||||
@@ -19,21 +19,33 @@ done
|
||||
MYOS_ROOT=$(cd "$(dirname "$_self")/.." && pwd -P)
|
||||
export MYOS_ROOT
|
||||
|
||||
for _m in core str tags naming stack config compose; do
|
||||
for _m in core str var tags naming stack config compose hooks context; do
|
||||
# shellcheck source=/dev/null
|
||||
. "$MYOS_ROOT/lib/$_m.sh"
|
||||
done
|
||||
|
||||
# myos_is_command WORD true when WORD names a command rather than a stack
|
||||
myos_is_command() {
|
||||
case $1 in
|
||||
up|down|start|stop|restart|ps|logs|config|build|pull|create|kill|top|images) return 0 ;;
|
||||
version|help) return 0 ;;
|
||||
print-*|stack-*-*) return 0 ;;
|
||||
*@*) myos_is_command "${1%@*}"; return $? ;;
|
||||
esac
|
||||
[ -f "$MYOS_ROOT/lib/cmd/$1.sh" ]
|
||||
}
|
||||
|
||||
# --- command line ---------------------------------------------------------
|
||||
# These are read by the lib/cmd/* files sourced further down.
|
||||
# shellcheck disable=SC2034
|
||||
{
|
||||
MYOS_CMD=
|
||||
MYOS_CMDS=
|
||||
MYOS_REFS=
|
||||
MYOS_REFS_RAW=
|
||||
MYOS_VARS=
|
||||
MYOS_ARGS=
|
||||
MYOS_HOSTS=
|
||||
MYOS_COLOR=${MYOS_COLOR:-auto}
|
||||
VERBOSE=${VERBOSE:-}
|
||||
DEBUG=${DEBUG:-}
|
||||
}
|
||||
@@ -47,6 +59,7 @@ Options:
|
||||
-e ENV environment (default: local, or ENV from the config)
|
||||
-H HOSTS run on remote hosts instead (comma separated, or "all")
|
||||
-n, --dry-run print the commands instead of running them
|
||||
--color WHEN always, never or auto (default: colour when on a terminal)
|
||||
-v, --verbose show what myos does
|
||||
-d, --debug show every command
|
||||
-h, --help this help
|
||||
@@ -56,6 +69,7 @@ Commands:
|
||||
ps logs config exec run inspect and enter them
|
||||
ls [--groups] list the stacks myos can see
|
||||
env [VAR...] show resolved variables
|
||||
env-update fill .env from the .env.dist templates
|
||||
doctor check the installation
|
||||
version print the myos version
|
||||
|
||||
@@ -73,55 +87,100 @@ while [ $# -gt 0 ]; do
|
||||
-e) ENV=$2; shift 2 ;;
|
||||
-H) MYOS_HOSTS=$2; shift 2 ;;
|
||||
-n|--dry-run) DRYRUN=true; shift ;;
|
||||
--color) MYOS_COLOR=$2; shift 2 ;;
|
||||
--color=*) MYOS_COLOR=${1#--color=}; shift ;;
|
||||
-v|--verbose) VERBOSE=true; shift ;;
|
||||
-d|--debug) DEBUG=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--) shift; MYOS_ARGS="$*"; break ;;
|
||||
-*)
|
||||
if [ -n "$MYOS_CMD" ]; then
|
||||
if [ -n "$MYOS_CMDS" ]; then
|
||||
MYOS_ARGS="${MYOS_ARGS:+$MYOS_ARGS }$1"; shift
|
||||
else
|
||||
myos_error "unknown option: $1"; usage >&2; exit "$MYOS_E_USAGE"
|
||||
fi ;;
|
||||
*=*) eval "${1%%=*}=\${1#*=}"; export "${1%%=*}"; shift ;;
|
||||
*)
|
||||
if [ -z "$MYOS_CMD" ]; then MYOS_CMD=$1; else MYOS_REFS="${MYOS_REFS:+$MYOS_REFS }$1"; fi
|
||||
# Leading words that name commands are commands, the rest are stacks:
|
||||
# `myos build up logs host/fabio` runs three commands on one stack, the
|
||||
# way `make build up logs STACK=host/fabio` did.
|
||||
if [ -z "$MYOS_REFS" ] && myos_is_command "$1"; then
|
||||
MYOS_CMDS="${MYOS_CMDS:+$MYOS_CMDS }$1"
|
||||
else
|
||||
MYOS_REFS="${MYOS_REFS:+$MYOS_REFS }$1"
|
||||
fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$MYOS_CMD" ] || { usage; exit "$MYOS_E_USAGE"; }
|
||||
if [ -z "$MYOS_CMDS" ]; then
|
||||
if [ -n "$MYOS_REFS" ]; then
|
||||
# the first word was meant as a command; name it rather than dump the usage
|
||||
myos_error "unknown command: ${MYOS_REFS%% *}"
|
||||
myos_error "to act on a stack of that name, say what to do: myos up ${MYOS_REFS%% *}"
|
||||
exit "$MYOS_E_USAGE"
|
||||
fi
|
||||
usage
|
||||
exit "$MYOS_E_USAGE"
|
||||
fi
|
||||
MYOS_REFS_RAW=$MYOS_REFS
|
||||
case $MYOS_CMD in
|
||||
env|ls|doctor) MYOS_VARS=$MYOS_REFS; MYOS_REFS= ;;
|
||||
esac
|
||||
|
||||
# Targets of the make engine keep working: print-VAR, stack-<stack>-<command>,
|
||||
# <command>@<env> and a bare group name.
|
||||
case $MYOS_CMD in
|
||||
print-*)
|
||||
MYOS_VARS=${MYOS_CMD#print-}
|
||||
# shellcheck disable=SC2209 # the literal string "env", not the command
|
||||
MYOS_CMD=env ;;
|
||||
*@*) ENV=${MYOS_CMD#*@}; MYOS_CMD=${MYOS_CMD%@*} ;;
|
||||
# Targets of the make engine keep working: print-VAR, stack-<stack>-<command>
|
||||
# and <command>@<env> each translate to a command of the CLI.
|
||||
_cmds=
|
||||
for _c in $MYOS_CMDS; do
|
||||
case $_c in
|
||||
*@*) ENV=${_c#*@}; _c=${_c%@*} ;;
|
||||
esac
|
||||
case $MYOS_CMD in
|
||||
case $_c in
|
||||
print-*)
|
||||
MYOS_VARS="${MYOS_VARS:+$MYOS_VARS }${_c#print-}"
|
||||
# shellcheck disable=SC2209 # the literal string "env", not the command
|
||||
_c=env ;;
|
||||
stack-*-*)
|
||||
_rest=${MYOS_CMD#stack-}
|
||||
MYOS_CMD=${_rest##*-}
|
||||
MYOS_REFS="${_rest%-*} $MYOS_REFS"
|
||||
MYOS_REFS=${MYOS_REFS% } ;;
|
||||
_rest=${_c#stack-}
|
||||
_c=${_rest##*-}
|
||||
MYOS_REFS="${_rest%-*}${MYOS_REFS:+ $MYOS_REFS}" ;;
|
||||
esac
|
||||
_cmds="${_cmds:+$_cmds }$_c"
|
||||
done
|
||||
MYOS_CMDS=$_cmds
|
||||
|
||||
# env, ls and doctor take variable names where the others take stacks
|
||||
case $MYOS_CMDS in
|
||||
env|ls|doctor)
|
||||
[ -n "$MYOS_VARS" ] || MYOS_VARS=$MYOS_REFS
|
||||
MYOS_REFS= ;;
|
||||
esac
|
||||
|
||||
# --- configuration --------------------------------------------------------
|
||||
WORKDIR=${WORKDIR:-$PWD}
|
||||
WORKDIR=$(cd "$WORKDIR" 2>/dev/null && pwd -P) || myos_die "$MYOS_E_USAGE" "no such directory: $WORKDIR"
|
||||
|
||||
for _f in $(myos_conf_files); do myos_dotenv_load "$_f"; done
|
||||
myos_dotenv_load "${HOME:-}/.config/myos/config"
|
||||
# ENV decides which .env.<env> to read, so it is resolved first, from the most
|
||||
# specific source that names it.
|
||||
if [ -z "${ENV:-}" ]; then
|
||||
for _f in "$WORKDIR/.env" "${HOME:-}/.config/myos/config" $(myos_conf_files); do
|
||||
ENV=$(myos_dotenv_parse "$_f" | sed -n 's/^ENV=//p' | tail -1)
|
||||
[ -n "$ENV" ] && break
|
||||
done
|
||||
fi
|
||||
ENV=${ENV:-local}
|
||||
myos_dotenv_load "$WORKDIR/.env.$ENV"
|
||||
myos_dotenv_load "$WORKDIR/.env"
|
||||
|
||||
# The layers, most specific first: the loader never overwrites a value, so the
|
||||
# order below is the order of precedence. The environment and the VAR=value
|
||||
# arguments are already set, and therefore win over every file.
|
||||
# MYOS_CONF_PRIORITY=system puts the machine files first, as the make engine did.
|
||||
myos_config_layers() {
|
||||
if [ "${MYOS_CONF_PRIORITY:-}" = system ]; then
|
||||
myos_conf_files
|
||||
printf '%s\n' "${HOME:-}/.config/myos/config" "$WORKDIR/.env.$ENV" "$WORKDIR/.env"
|
||||
else
|
||||
printf '%s\n' "$WORKDIR/.env.$ENV" "$WORKDIR/.env" "${HOME:-}/.config/myos/config"
|
||||
myos_conf_files
|
||||
fi
|
||||
}
|
||||
for _f in $(myos_config_layers); do myos_dotenv_load "$_f"; done
|
||||
|
||||
# Overlay switches. Their names drive which <stack>.<suffix>.yml files load,
|
||||
# so these defaults decide that e.g. supabase.labels.yml is picked up.
|
||||
@@ -154,26 +213,53 @@ if [ -z "$MYOS_REFS" ]; then
|
||||
fi
|
||||
fi
|
||||
if [ -z "$MYOS_REFS" ]; then
|
||||
case $MYOS_CMD in
|
||||
env|ls|doctor|version|help) ;;
|
||||
# these commands describe the installation rather than act on a stack
|
||||
for _c in $MYOS_CMDS; do
|
||||
case $_c in
|
||||
env|env-update|ls|doctor|version|help) ;;
|
||||
*) myos_die "$MYOS_E_USAGE" "no stack given, and no compose file in $WORKDIR" ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086 # a list of references
|
||||
MYOS_STACKS=$(myos_group_expand $MYOS_REFS)
|
||||
|
||||
myos_context_defaults
|
||||
|
||||
# The uri a stack is served on: the tag helpers build on it, so it has to be
|
||||
# known before the hooks run.
|
||||
MYOS_SCOPE_FIRST=$(for _r in $MYOS_STACKS; do myos_scope "$_r"; break; done)
|
||||
MYOS_APP_FIRST=$(for _r in $MYOS_STACKS; do myos_stack_name "$_r"; break; done)
|
||||
APP_HOST=${APP_HOST:-$(myos_app_host "$MYOS_SCOPE_FIRST" "$USER" "$ENV" "$MYOS_APP_FIRST" "$DOMAINNAME" "$HOSTNAME")}
|
||||
APP_URI=${APP_URI:-$(myos_app_uri "$APP_HOST" "${APP_PATH:-}")}
|
||||
APP_SCHEME=${APP_SCHEME:-http}
|
||||
|
||||
# Per-stack hooks must run in this shell: everything downstream reads the
|
||||
# variables they set, and a command substitution would throw them away.
|
||||
for _ref in $MYOS_STACKS; do
|
||||
for _hdir in $(myos_stack_dirs "$_ref"); do
|
||||
myos_stack_hooks "$_hdir" "$(myos_stack_name "$_ref")"
|
||||
done
|
||||
done
|
||||
|
||||
# The two functions below are called from the lib/cmd/* files sourced later,
|
||||
# which shellcheck cannot see.
|
||||
# shellcheck disable=SC2329
|
||||
# myos_framework_compose_files the networks and volumes overlays myos itself
|
||||
# provides; they always come last so a stack can rely on them being there.
|
||||
myos_framework_compose_files() {
|
||||
myos_compose_files "$MYOS_ROOT/share/compose" "networks volumes" "$(myos_compose_suffixes)" "$ENV"
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2329
|
||||
# myos_stack_compose_files REF the ordered compose files of one reference
|
||||
myos_stack_compose_files() {
|
||||
_dir=$(myos_stack_resolve "$1") || return $?
|
||||
_dirs=$(myos_stack_dirs "$1")
|
||||
[ -n "$_dirs" ] || { myos_stack_resolve "$1" >/dev/null; return $?; }
|
||||
_name=$(myos_stack_name "$1")
|
||||
_suffixes="$(myos_compose_suffixes) $(myos_stack_version "$1")"
|
||||
for _dir in $_dirs; do
|
||||
case $1 in
|
||||
.|./*|/*|../*)
|
||||
myos_compose_files "$_dir" "docker-compose compose" "$_suffixes" "$ENV"
|
||||
@@ -181,26 +267,35 @@ myos_stack_compose_files() {
|
||||
*)
|
||||
myos_compose_files "$_dir" "docker-compose $_name" "$_suffixes" "$ENV" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# --- dispatch -------------------------------------------------------------
|
||||
case $MYOS_CMD in
|
||||
version) printf 'myos %s\n' "$MYOS_VERSION"; exit 0 ;;
|
||||
help) usage; exit 0 ;;
|
||||
# myos_dispatch COMMAND run one command
|
||||
myos_dispatch() {
|
||||
case $1 in
|
||||
version) printf 'myos %s\n' "$MYOS_VERSION"; return 0 ;;
|
||||
help) usage; return 0 ;;
|
||||
esac
|
||||
|
||||
if [ -f "$MYOS_ROOT/lib/cmd/$1.sh" ]; then
|
||||
# shellcheck source=/dev/null
|
||||
if [ -f "$MYOS_ROOT/lib/cmd/$MYOS_CMD.sh" ]; then
|
||||
. "$MYOS_ROOT/lib/cmd/$MYOS_CMD.sh"
|
||||
"myos_cmd_$MYOS_CMD"
|
||||
exit $?
|
||||
. "$MYOS_ROOT/lib/cmd/$1.sh"
|
||||
"myos_cmd_$(printf '%s' "$1" | tr '-' '_')"
|
||||
return $?
|
||||
fi
|
||||
|
||||
# compose passthrough commands
|
||||
case $MYOS_CMD in
|
||||
# commands that map straight onto docker compose
|
||||
case $1 in
|
||||
up|down|start|stop|restart|ps|logs|config|build|pull|create|kill|top|images) ;;
|
||||
*) myos_error "unknown command: $MYOS_CMD"; usage >&2; exit "$MYOS_E_USAGE" ;;
|
||||
*) myos_error "unknown command: $1"; usage >&2; return "$MYOS_E_USAGE" ;;
|
||||
esac
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
. "$MYOS_ROOT/lib/cmd/_compose.sh"
|
||||
myos_cmd_compose "$MYOS_CMD"
|
||||
myos_cmd_compose "$1"
|
||||
}
|
||||
|
||||
# Several commands run in order and stop at the first failure, as make did.
|
||||
_rc=0
|
||||
for MYOS_CMD in $MYOS_CMDS; do
|
||||
myos_dispatch "$MYOS_CMD" || { _rc=$?; break; }
|
||||
done
|
||||
exit "$_rc"
|
||||
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/bin/sh
|
||||
# myos installer.
|
||||
#
|
||||
# curl -fsSL https://raw.githubusercontent.com/aya/myos/lightning/install.sh | sh
|
||||
# ... | sh -s -- --prefix ~/.local --with-stacks
|
||||
#
|
||||
# Installs the framework into <prefix>/lib/myos, links <prefix>/bin/myos, and
|
||||
# optionally clones the stack catalogue into <prefix>/share/myos.
|
||||
set -eu
|
||||
|
||||
MYOS_REPOSITORY=${MYOS_REPOSITORY:-https://github.com/aya/myos}
|
||||
STACKS_REPOSITORY=${STACKS_REPOSITORY:-https://github.com/aya/myos-stacks}
|
||||
REF=${MYOS_REF:-lightning}
|
||||
PREFIX=
|
||||
WITH_STACKS=false
|
||||
WRITE_CONF=false
|
||||
|
||||
say() { printf '%s\n' "$*"; }
|
||||
warn() { printf 'warning: %s\n' "$*" >&2; }
|
||||
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
|
||||
have() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--prefix) PREFIX=$2; shift 2 ;;
|
||||
--ref) REF=$2; shift 2 ;;
|
||||
--repository) MYOS_REPOSITORY=$2; shift 2 ;;
|
||||
--with-stacks) WITH_STACKS=true; shift ;;
|
||||
--conf) WRITE_CONF=true; shift ;;
|
||||
-h|--help)
|
||||
sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) die "unknown option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Default prefix: system wide when we can write there, user local otherwise.
|
||||
if [ -z "$PREFIX" ]; then
|
||||
if [ "$(id -u)" = 0 ] || [ -w /usr/local/lib ]; then PREFIX=/usr/local; else PREFIX=$HOME/.local; fi
|
||||
fi
|
||||
|
||||
# --- requirements ---------------------------------------------------------
|
||||
have git || die "git is required"
|
||||
have docker || warn "docker not found: myos will not be able to run anything"
|
||||
if have docker && docker compose version >/dev/null 2>&1; then :
|
||||
elif have docker-compose; then :
|
||||
else warn "no docker compose found: install the compose plugin, or docker-compose >= 2.24.4"
|
||||
fi
|
||||
|
||||
# --- install --------------------------------------------------------------
|
||||
LIB=$PREFIX/lib/myos
|
||||
BIN=$PREFIX/bin
|
||||
|
||||
if [ -d "$LIB/.git" ]; then
|
||||
say "updating $LIB"
|
||||
git -C "$LIB" fetch --quiet origin "$REF"
|
||||
git -C "$LIB" checkout --quiet FETCH_HEAD
|
||||
else
|
||||
say "installing myos into $LIB"
|
||||
mkdir -p "$(dirname "$LIB")"
|
||||
git clone --quiet --branch "$REF" "$MYOS_REPOSITORY" "$LIB"
|
||||
fi
|
||||
|
||||
mkdir -p "$BIN"
|
||||
ln -sf "$LIB/bin/myos" "$BIN/myos"
|
||||
say "linked $BIN/myos"
|
||||
|
||||
if [ "$WITH_STACKS" = true ]; then
|
||||
SHARE=$PREFIX/share/myos
|
||||
if [ -d "$SHARE/.git" ]; then
|
||||
say "updating the stack catalogue in $SHARE"
|
||||
git -C "$SHARE" pull --quiet --ff-only
|
||||
else
|
||||
say "installing the stack catalogue into $SHARE"
|
||||
mkdir -p "$(dirname "$SHARE")"
|
||||
git clone --quiet "$STACKS_REPOSITORY" "$SHARE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- machine configuration ------------------------------------------------
|
||||
# /etc/conf.d on Alpine and other OpenRC systems, /etc/default elsewhere.
|
||||
if [ "$WRITE_CONF" = true ]; then
|
||||
if [ -d /etc/conf.d ]; then CONF=/etc/conf.d/myos; else CONF=/etc/default/myos; fi
|
||||
if [ "$(id -u)" != 0 ]; then CONF=$HOME/.config/myos/config; mkdir -p "$(dirname "$CONF")"; fi
|
||||
if [ -f "$CONF" ]; then
|
||||
say "keeping the existing $CONF"
|
||||
else
|
||||
cat > "$CONF" <<CONFEOF
|
||||
# myos machine settings, one KEY=value per line.
|
||||
# ENV=master
|
||||
# DOMAIN=$(hostname -d 2>/dev/null || echo example.org)
|
||||
# WORKDIR=/srv/myos
|
||||
# a deployment created before myos 2.0 must keep the old project names:
|
||||
# MYOS_PROJECT_FORMAT=user-app-env
|
||||
CONFEOF
|
||||
say "wrote $CONF"
|
||||
fi
|
||||
fi
|
||||
|
||||
case :$PATH: in
|
||||
*:$BIN:*) ;;
|
||||
*) warn "$BIN is not on your PATH" ;;
|
||||
esac
|
||||
|
||||
say ""
|
||||
say "myos $("$BIN/myos" version 2>/dev/null | awk '{print $2}') installed"
|
||||
say "next: myos doctor"
|
||||
@@ -0,0 +1,26 @@
|
||||
#shellcheck shell=sh
|
||||
# myos env-update fill the .env of the workdir from the .env.dist it finds
|
||||
#
|
||||
# Templates are read from the workdir and from every requested stack, so a
|
||||
# stack can ship the variables it expects and their defaults.
|
||||
myos_cmd_env_update() {
|
||||
_target=${ENV_FILE:-$WORKDIR/.env}
|
||||
_dists=$MYOS_ARGS
|
||||
if [ -z "$_dists" ]; then
|
||||
for _ref in $MYOS_STACKS; do
|
||||
_d=$(myos_stack_resolve "$_ref" 2>/dev/null) || continue
|
||||
for _c in "$_d/.env.dist" "$_d/$(myos_stack_name "$_ref").env.dist"; do
|
||||
[ -f "$_c" ] && _dists="${_dists:+$_dists }$_c"
|
||||
done
|
||||
done
|
||||
for _c in "$WORKDIR/.env.dist" "$WORKDIR/.env.example" "$WORKDIR/.env.sample"; do
|
||||
[ -f "$_c" ] && _dists="${_dists:+$_dists }$_c"
|
||||
done
|
||||
fi
|
||||
[ -n "$_dists" ] || { myos_warning "no .env.dist found for $MYOS_STACKS"; return 0; }
|
||||
for _dist in $_dists; do
|
||||
myos_info "env-update $_target from $_dist"
|
||||
myos_env_update "$_target" "$_dist" "$WORKDIR/.env.$ENV"
|
||||
done
|
||||
printf '%s\n' "$_target"
|
||||
}
|
||||
+32
-51
@@ -1,67 +1,48 @@
|
||||
#shellcheck shell=sh
|
||||
# shellcheck disable=SC3028 # HOSTNAME is a myos variable, set by bin/myos
|
||||
# myos env [VAR...] show resolved variables (replaces the make print-VAR target)
|
||||
|
||||
# myos_env_print NAME VALUE
|
||||
# Same shape as the make print-<VAR> target: the name padded to 37 columns in
|
||||
# the highlight colour, then the value in the value colour.
|
||||
myos_env_print() {
|
||||
printf '%s%-37s%s%s%s%s\n' \
|
||||
"$MYOS_C_HIGHLIGHT" "$1" "$MYOS_C_RESET" "$MYOS_C_VALUE" "$2" "$MYOS_C_RESET"
|
||||
}
|
||||
|
||||
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"
|
||||
_vars="ENV USER HOSTNAME DOMAIN WORKDIR MYOS_PATH SCOPE 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/ $//')" ;;
|
||||
DOCKER_NETWORK_DEFAULT) printf '%s %s\n' "$_v" "$(myos_network_default "$(myos_first_project)")" ;;
|
||||
DOCKER_NETWORK_PRIVATE) printf '%s %s\n' "$_v" "$(myos_network_private "$USER" "$ENV")" ;;
|
||||
DOCKER_NETWORK_PUBLIC) printf '%s %s\n' "$_v" "$(myos_network_public "${HOSTNAME:-}")" ;;
|
||||
COMPOSE_SERVICE_NAME) printf '%s %s\n' "$_v" "$(myos_service_name "$(myos_first_project)")" ;;
|
||||
COMPOSE_FILE_SUFFIX) printf '%s %s\n' "$_v" "$(myos_compose_suffixes)" ;;
|
||||
APP|APP_NAME) printf '%s %s\n' "$_v" "$(myos_first_app)" ;;
|
||||
SCOPE) printf '%s %s\n' "$_v" "$(myos_first_scope)" ;;
|
||||
# HOST_STACK/USER_STACK are the names the make engine used for the scope
|
||||
HOST_STACK) [ "$(myos_first_scope)" = host ] && printf '%s host\n' "$_v" || printf '%s\n' "$_v" ;;
|
||||
USER_STACK) [ "$(myos_first_scope)" = user ] && printf '%s User\n' "$_v" || printf '%s\n' "$_v" ;;
|
||||
DOCKER_REPOSITORY) printf '%s %s\n' "$_v" "$(printf '%s' "$(myos_first_project)" | tr '_-' '//')" ;;
|
||||
MYOS_PATH) myos_env_print "$_v" "$(myos_path)" ;;
|
||||
COMPOSE_FILE) myos_env_print "$_v" "$(myos_all_compose_files | tr '\n' ' ' | sed 's/ $//')" ;;
|
||||
COMPOSE_PROJECT_NAME) myos_env_print "$_v" "$(myos_first_project)" ;;
|
||||
COMPOSE_SERVICE_NAME) myos_env_print "$_v" "$(myos_service_name "$(myos_first_project)")" ;;
|
||||
COMPOSE_FILE_SUFFIX) myos_env_print "$_v" "$(myos_compose_suffixes)" ;;
|
||||
STACK) myos_env_print "$_v" "$(printf '%s' "$MYOS_STACKS" | tr '\n' ' ' | sed 's/ $//')" ;;
|
||||
SCOPE) myos_env_print "$_v" "$(myos_first_scope)" ;;
|
||||
APP|APP_NAME) myos_env_print "$_v" "$(myos_first_app)" ;;
|
||||
DOCKER_REPOSITORY) myos_env_print "$_v" "$(printf '%s' "$(myos_first_project)" | tr '_-' '//')" ;;
|
||||
DOCKER_NETWORK_DEFAULT) myos_env_print "$_v" "$(myos_network_default "$(myos_first_project)")" ;;
|
||||
DOCKER_NETWORK_PRIVATE) myos_env_print "$_v" "$(myos_network_private "$USER" "$ENV")" ;;
|
||||
DOCKER_NETWORK_PUBLIC) myos_env_print "$_v" "$(myos_network_public "${HOSTNAME:-}")" ;;
|
||||
DOCKER_NETWORK)
|
||||
if [ "$(myos_first_scope)" = user ]; then printf '%s %s\n' "$_v" "$USER"
|
||||
else printf '%s %s\n' "$_v" "$(myos_network_private "$USER" "$ENV")"; fi ;;
|
||||
*) printf '%s %s\n' "$_v" "$(myos_var "$_v")" ;;
|
||||
if [ "$(myos_first_scope)" = user ]; then myos_env_print "$_v" "$USER"
|
||||
else myos_env_print "$_v" "$(myos_network_private "$USER" "$ENV")"; fi ;;
|
||||
# HOST_STACK and USER_STACK are what the make engine called the scope
|
||||
HOST_STACK)
|
||||
if [ "$(myos_first_scope)" = host ]; then myos_env_print "$_v" host
|
||||
else myos_env_print "$_v" ""; fi ;;
|
||||
USER_STACK)
|
||||
if [ "$(myos_first_scope)" = user ]; then myos_env_print "$_v" User
|
||||
else myos_env_print "$_v" ""; fi ;;
|
||||
*) myos_env_print "$_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
|
||||
myos_framework_compose_files
|
||||
return 0
|
||||
}
|
||||
|
||||
# myos_first_app / myos_first_scope / myos_first_project
|
||||
# describe the first requested stack, which is what the introspection commands
|
||||
# report when several stacks are asked for at once.
|
||||
myos_first_app() {
|
||||
for _ref in $MYOS_STACKS; do
|
||||
case $_ref in
|
||||
.|./*|/*|../*) basename "$(myos_stack_resolve "$_ref" 2>/dev/null)" ;;
|
||||
*) myos_stack_name "$_ref" ;;
|
||||
esac
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
myos_first_scope() {
|
||||
for _ref in $MYOS_STACKS; do myos_scope "$_ref"; return 0; done
|
||||
}
|
||||
|
||||
myos_first_project() {
|
||||
for _ref in $MYOS_STACKS; do
|
||||
myos_project_name "$(myos_scope "$_ref")" "$USER" "$ENV" "$(myos_first_app)"
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#shellcheck shell=sh
|
||||
# myos exec <stack> [-- command...] run a command in a running service
|
||||
# myos run <stack> [-- command...] run it in a new container
|
||||
#
|
||||
# The service defaults to the stack name, which is what it is called in most
|
||||
# stacks; SERVICE=<name> picks another one.
|
||||
myos_cmd_exec() { myos_service_command exec; }
|
||||
myos_cmd_run() { myos_service_command run; }
|
||||
|
||||
myos_service_command() {
|
||||
_what=$1
|
||||
_ref=$(printf '%s' "$MYOS_STACKS" | head -1)
|
||||
[ -n "$_ref" ] || myos_die "$MYOS_E_USAGE" "usage: myos $_what <stack> [SERVICE=name] -- command..."
|
||||
_service=${SERVICE:-$(myos_stack_name "$_ref")}
|
||||
_files=$(myos_stack_compose_files "$_ref") || return $?
|
||||
_fw=$(myos_framework_compose_files)
|
||||
[ -n "$_fw" ] && _files="$_files
|
||||
$_fw"
|
||||
_app=$(myos_stack_name "$_ref")
|
||||
case $_ref in .|./*|/*|../*) _app=$(basename "$(myos_stack_resolve "$_ref")") ;; esac
|
||||
_project=$(myos_project_name "$(myos_scope "$_ref")" "$USER" "$ENV" "$_app")
|
||||
case $_what in
|
||||
exec) _opts="" ;;
|
||||
run) _opts=${DOCKER_COMPOSE_RUN_OPTIONS:---rm} ;;
|
||||
esac
|
||||
# shellcheck disable=SC2086 # options and arguments are deliberate word lists
|
||||
myos_compose "$_project" "$_files" -- "$_what" $_opts "$_service" ${MYOS_ARGS:-}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#shellcheck shell=sh
|
||||
# myos run: see lib/cmd/exec.sh
|
||||
# shellcheck source=lib/cmd/exec.sh
|
||||
. "$MYOS_ROOT/lib/cmd/exec.sh"
|
||||
@@ -0,0 +1,13 @@
|
||||
#shellcheck shell=sh
|
||||
# shellcheck source=lib/cmd/_compose.sh
|
||||
# myos scale <stack> SERVICE=<name> NUM=<n> run n containers of a service
|
||||
. "$MYOS_ROOT/lib/cmd/_compose.sh"
|
||||
|
||||
myos_cmd_scale() {
|
||||
_ref=$(printf '%s' "$MYOS_STACKS" | head -1)
|
||||
[ -n "$_ref" ] || myos_die "$MYOS_E_USAGE" "usage: myos scale <stack> SERVICE=name NUM=n"
|
||||
_service=${SERVICE:-$(myos_stack_name "$_ref")}
|
||||
[ -n "${NUM:-}" ] || myos_die "$MYOS_E_USAGE" "myos scale needs NUM=<n>"
|
||||
MYOS_ARGS="--scale $_service=$NUM ${MYOS_ARGS:-}"
|
||||
myos_cmd_compose up
|
||||
}
|
||||
+19
-10
@@ -39,22 +39,31 @@ myos_compose() {
|
||||
done
|
||||
_dir=$(dirname "$_first")
|
||||
|
||||
# variables the compose files reference, plus the network names they default
|
||||
# on (networks.yml is appended after the scan, so its variables are added here)
|
||||
# the variables the compose files reference, plus the network names: those
|
||||
# live in networks.yml, which is appended after the scan
|
||||
# shellcheck disable=SC2086 # both are deliberate word lists
|
||||
_vars=$(myos_env_vars $_files)
|
||||
# shellcheck disable=SC2086
|
||||
_envargs=$(myos_env_export $_vars DOCKER_NETWORK_DEFAULT DOCKER_NETWORK_PRIVATE DOCKER_NETWORK_PUBLIC COMPOSE_SERVICE_NAME)
|
||||
|
||||
if [ "${DRYRUN:-false}" = true ]; then
|
||||
# shellcheck disable=SC2086 # printed, not executed
|
||||
printf '%s%s -p %s --project-directory %s %s\n' "$_bin" "$_fargs" "$_project" "$_dir" "$*"
|
||||
else
|
||||
_IFS=$IFS; IFS='
|
||||
'
|
||||
# shellcheck disable=SC2046,SC2086 # deliberate word splitting on IFS=newline
|
||||
env $_envargs $_bin --ansi=auto $_fargs -p "$_project" --project-directory "$_dir" "$@"
|
||||
_rc=$?
|
||||
IFS=$_IFS
|
||||
return $_rc
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Export the variables in a subshell rather than through env(1): a value may
|
||||
# contain spaces, and the command line must still be split on spaces (the
|
||||
# compose binary can be the two words "docker compose").
|
||||
(
|
||||
while IFS= read -r _kv; do
|
||||
[ -n "$_kv" ] || continue
|
||||
# shellcheck disable=SC2163 # _kv is a NAME=value pair, not a name
|
||||
export "$_kv"
|
||||
done <<EOF
|
||||
$_envargs
|
||||
EOF
|
||||
# shellcheck disable=SC2086 # _bin and _fargs are deliberate word lists
|
||||
exec $_bin --ansi=auto $_fargs -p "$_project" --project-directory "$_dir" "$@"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -80,3 +80,94 @@ myos_env_export() {
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# myos_expand STRING substitute ${VAR} and $(command) in STRING.
|
||||
# shellcheck disable=SC2016 # the single quotes are deliberate: these patterns
|
||||
# match the literal characters ${ and $( in the input, they are not expansions
|
||||
# This is what the make engine did when it generated a .env out of a .env.dist:
|
||||
# ${VAR} takes the current value, $(cmd) runs the command. Nothing else is
|
||||
# interpreted, so the rest of the line can hold anything.
|
||||
myos_expand() {
|
||||
_in=$1
|
||||
_guard=0
|
||||
while [ "$_guard" -lt 16 ]; do
|
||||
_guard=$((_guard + 1))
|
||||
case $_in in
|
||||
*'${'*'}'*)
|
||||
_pre=${_in%%'${'*}
|
||||
_rest=${_in#*'${'}
|
||||
_name=${_rest%%\}*}
|
||||
_post=${_rest#*\}}
|
||||
case $_name in
|
||||
''|*[!A-Za-z0-9_]*) _in="$_pre\${$_name}$_post"; break ;;
|
||||
esac
|
||||
_in="$_pre$(myos_var "$_name")$_post" ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
_guard=0
|
||||
while [ "$_guard" -lt 16 ]; do
|
||||
_guard=$((_guard + 1))
|
||||
case $_in in
|
||||
*'$('*')'*)
|
||||
_pre=${_in%%'$('*}
|
||||
_rest=${_in#*'$('}
|
||||
_cmd=${_rest%%)*}
|
||||
_post=${_rest#*)}
|
||||
_in="$_pre$(eval "$_cmd" 2>/dev/null)$_post" ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
printf '%s' "$_in"
|
||||
}
|
||||
|
||||
# myos_env_update FILE DIST [OVER...]
|
||||
# Add to FILE every variable of DIST that is missing from it, expanded.
|
||||
# A variable that already has a value keeps it, whether it comes from the
|
||||
# environment, from FILE, or from one of the OVER files: the .env is a record
|
||||
# of the choices already made, never something that overwrites them.
|
||||
myos_env_update() {
|
||||
_file=$1; _dist=$2; shift 2
|
||||
[ -f "$_dist" ] || return 0
|
||||
[ -e "$_file" ] || : > "$_file"
|
||||
|
||||
# what the overrides pin, read before anything else
|
||||
for _over in "$@"; do
|
||||
[ -f "$_over" ] || continue
|
||||
myos_dotenv_load "$_over"
|
||||
done
|
||||
|
||||
# Which keys already hold a choice, made in the environment, in the .env or
|
||||
# in an override. Those are kept verbatim; everything else is a template.
|
||||
_preset=" "
|
||||
while IFS= read -r _kv; do
|
||||
[ -n "$_kv" ] || continue
|
||||
_k=${_kv%%=*}
|
||||
[ -n "$_k" ] || continue
|
||||
[ -n "$(myos_var "$_k")" ] && _preset="$_preset$_k "
|
||||
done <<EOF
|
||||
$(myos_dotenv_parse "$_dist")
|
||||
EOF
|
||||
|
||||
# Make the templates themselves visible, so a line may refer to a variable
|
||||
# defined further down the file, as the make engine allowed.
|
||||
myos_dotenv_load "$_dist"
|
||||
|
||||
_added=0
|
||||
while IFS= read -r _kv; do
|
||||
[ -n "$_kv" ] || continue
|
||||
_k=${_kv%%=*}
|
||||
[ -n "$_k" ] || continue
|
||||
myos_dotenv_has "$_file" "$_k" && continue
|
||||
case $_preset in
|
||||
*" $_k "*) _v=$(myos_var "$_k") ;;
|
||||
*) _v=$(myos_expand "${_kv#*=}") ;;
|
||||
esac
|
||||
printf '%s=%s\n' "$_k" "$_v" >> "$_file"
|
||||
_added=$((_added + 1))
|
||||
done <<EOF
|
||||
$(myos_dotenv_parse "$_dist")
|
||||
EOF
|
||||
[ "$_added" -gt 0 ] && myos_info "added $_added variable(s) to $_file"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#shellcheck shell=sh
|
||||
# shellcheck disable=SC3028 # HOSTNAME is a myos variable, set by bin/myos
|
||||
# context: what the requested stacks resolve to, and the framework variables a
|
||||
# stack hook may read.
|
||||
#
|
||||
# A hook is written the way a .mk was: it may mention COMPOSE_PROJECT_NAME,
|
||||
# USER or DOMAIN and expect the framework value. Those are registered as lazy
|
||||
# defaults, so each is computed when read and an explicit value still wins.
|
||||
|
||||
# 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
|
||||
myos_framework_compose_files
|
||||
return 0
|
||||
}
|
||||
|
||||
# myos_first_app / myos_first_scope / myos_first_project
|
||||
# describe the first requested stack, which is what the introspection commands
|
||||
# report when several stacks are asked for at once.
|
||||
myos_first_app() {
|
||||
for _ref in $MYOS_STACKS; do
|
||||
case $_ref in
|
||||
.|./*|/*|../*) basename "$(myos_stack_resolve "$_ref" 2>/dev/null)" ;;
|
||||
*) myos_stack_name "$_ref" ;;
|
||||
esac
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
myos_first_scope() {
|
||||
for _ref in $MYOS_STACKS; do myos_scope "$_ref"; return 0; done
|
||||
}
|
||||
|
||||
myos_first_project() {
|
||||
for _ref in $MYOS_STACKS; do
|
||||
myos_project_name "$(myos_scope "$_ref")" "$USER" "$ENV" "$(myos_first_app)"
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
# myos_context_defaults register the framework variables as lazy defaults
|
||||
# shellcheck disable=SC2329 # these are reached through myos_var
|
||||
myos_context_defaults() {
|
||||
myos_default_APP() { myos_first_app; }
|
||||
myos_default_APP_NAME() { myos_name "$(myos_first_app)"; }
|
||||
myos_default_SCOPE() { myos_first_scope; }
|
||||
myos_default_COMPOSE_PROJECT_NAME() { myos_first_project; }
|
||||
myos_default_COMPOSE_SERVICE_NAME() { myos_service_name "$(myos_first_project)"; }
|
||||
myos_default_DOCKER_REPOSITORY() { printf '%s' "$(myos_first_project)" | tr '_-' '//'; }
|
||||
myos_default_DOCKER_NETWORK_DEFAULT() { myos_network_default "$(myos_first_project)"; }
|
||||
myos_default_DOCKER_NETWORK_PRIVATE() { myos_network_private "$USER" "$ENV"; }
|
||||
myos_default_DOCKER_NETWORK_PUBLIC() { myos_network_public "${HOSTNAME:-}"; }
|
||||
myos_default_DOCKER_NETWORK() { myos_network_private "$USER" "$ENV"; }
|
||||
myos_default_DOCKER_IMAGE_TAG() { printf 'latest'; }
|
||||
myos_default_GIT_USER() { printf '%s' "$USER"; }
|
||||
myos_default_HOST() { myos_addprefix "${HOSTNAME:-}." "$(myos_var DOMAIN)"; }
|
||||
myos_default_HOSTNAME() { printf '%s' "${HOSTNAME:-}"; }
|
||||
myos_default_DOMAINNAME() { myos_firstword "$(myos_var DOMAIN)"; }
|
||||
myos_default_MACHINE() { uname -m 2>/dev/null; }
|
||||
myos_default_SYSTEM() { uname -s 2>/dev/null; }
|
||||
myos_default_HOST_COMPOSE_PROJECT_NAME() { printf '%s' "${HOSTNAME:-}"; }
|
||||
myos_default_HOST_DOCKER_VOLUME() { printf '%s' "${HOSTNAME:-}"; }
|
||||
myos_default_HOST_DOCKER_REPOSITORY() { printf '%s' "${HOSTNAME:-}" | tr '_-' '//'; }
|
||||
myos_default_USER_COMPOSE_PROJECT_NAME() { myos_resu "${MAIL:-}" | tr '.' '-'; }
|
||||
myos_default_RESU() { myos_resu "${MAIL:-}"; }
|
||||
}
|
||||
+13
-1
@@ -13,13 +13,25 @@ MYOS_E_USAGE=2 # bad invocation
|
||||
MYOS_E_NOSTACK=3 # stack not found
|
||||
MYOS_E_NOREQ=4 # missing requirement
|
||||
|
||||
# myos_colors decide whether to emit colour.
|
||||
# MYOS_COLOR=always|never|auto (default auto: only when stdout is a terminal).
|
||||
# The make engine always emitted the escape codes, even into a pipe.
|
||||
myos_colors() {
|
||||
if [ -t 2 ] && [ "${TERM:-dumb}" != dumb ] && [ -z "${NO_COLOR:-}" ]; then
|
||||
_want=${MYOS_COLOR:-auto}
|
||||
[ -n "${NO_COLOR:-}" ] && _want=never
|
||||
case $_want in
|
||||
never) _want=no ;;
|
||||
always) _want=yes ;;
|
||||
*) if [ -t 1 ] && [ "${TERM:-dumb}" != dumb ]; then _want=yes; else _want=no; fi ;;
|
||||
esac
|
||||
if [ "$_want" = yes ]; then
|
||||
MYOS_C_ERROR=$(printf '\033[31m'); MYOS_C_WARN=$(printf '\033[01;33m')
|
||||
MYOS_C_INFO=$(printf '\033[33m'); MYOS_C_DEBUG=$(printf '\033[01;34m')
|
||||
MYOS_C_VALUE=$(printf '\033[36m'); MYOS_C_RESET=$(printf '\033[0m')
|
||||
MYOS_C_HIGHLIGHT=$(printf '\033[32m')
|
||||
else
|
||||
MYOS_C_ERROR=; MYOS_C_WARN=; MYOS_C_INFO=; MYOS_C_DEBUG=; MYOS_C_VALUE=; MYOS_C_RESET=
|
||||
MYOS_C_HIGHLIGHT=
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
#shellcheck shell=sh
|
||||
# shellcheck disable=SC1090 # hooks are sourced by a path built at run time
|
||||
# hooks: the per-stack settings that used to live in a .mk file.
|
||||
#
|
||||
# A stack directory may ship, next to its compose files:
|
||||
# _stack.env settings shared by every stack of the directory
|
||||
# _stack.sh the same, computed
|
||||
# <name>.env settings of one stack
|
||||
# <name>.env.<env> the same, for one environment
|
||||
# <name>.sh computed settings of one stack
|
||||
# <name>.mk the legacy make snippet, still read for its groups
|
||||
#
|
||||
# A .sh hook declares lazy defaults (see lib/var.sh): functions named
|
||||
# myos_default_<VARIABLE>, called only when the variable has no value and
|
||||
# called again at each reference. That is a make `?=` on a recursive variable,
|
||||
# and it is what lets a stack of the catalogue work without make installed.
|
||||
#
|
||||
# The _stack hooks of every directory between the stack path root and the stack
|
||||
# itself are loaded, outermost first: make included both $(dir)/*.mk and
|
||||
# $(dir)/*/*.mk, so a stack in a subdirectory saw its parent's settings.
|
||||
|
||||
# myos_stack_hooks DIR NAME load the hooks that apply to one stack
|
||||
myos_stack_hooks() {
|
||||
_hdir=$1; _hname=$2
|
||||
|
||||
# the stack path entry this directory belongs to
|
||||
_root=
|
||||
_IFS=$IFS; IFS=:
|
||||
for _r in $(myos_path); do
|
||||
IFS=$_IFS
|
||||
case $_hdir in "$_r"|"$_r"/*) _root=$_r; break ;; esac
|
||||
IFS=:
|
||||
done
|
||||
IFS=$_IFS
|
||||
|
||||
# every directory from the root down to the stack, outermost first
|
||||
_chain=$_hdir
|
||||
if [ -n "$_root" ]; then
|
||||
_d=$_hdir
|
||||
while [ "$_d" != "$_root" ] && [ "$_d" != "/" ] && [ -n "$_d" ]; do
|
||||
_d=$(dirname "$_d")
|
||||
_chain="$_d
|
||||
$_chain"
|
||||
done
|
||||
fi
|
||||
|
||||
for _d in $_chain; do
|
||||
myos_hook_load "$_d/_stack.env" dotenv
|
||||
myos_hook_load "$_d/_stack.sh" shell
|
||||
done
|
||||
myos_hook_load "$_hdir/$_hname.env" dotenv
|
||||
myos_hook_load "$_hdir/$_hname.env.$ENV" dotenv
|
||||
myos_hook_load "$_hdir/$_hname.sh" shell
|
||||
myos_hook_load "$_hdir/$_hname.$ENV.sh" shell
|
||||
return 0
|
||||
}
|
||||
|
||||
# myos_hook_load FILE KIND
|
||||
myos_hook_load() {
|
||||
[ -f "$1" ] || return 0
|
||||
myos_debug "hook $1"
|
||||
# a hook may need to read a file it ships next to itself
|
||||
# shellcheck disable=SC2034 # read by the hooks sourced below
|
||||
MYOS_STACK_DIR=$(dirname "$1")
|
||||
case $2 in
|
||||
dotenv) myos_dotenv_load "$1" ;;
|
||||
shell) . "$1" ;;
|
||||
esac
|
||||
}
|
||||
@@ -79,3 +79,44 @@ myos_service_name() { printf '%s' "$1" | tr '_' '-'; }
|
||||
myos_network_default() { printf '_%s' "$1"; }
|
||||
myos_network_private() { printf '%s' "${DOCKER_NETWORK_PRIVATE:-${1}-${2}}"; }
|
||||
myos_network_public() { printf '%s' "${DOCKER_NETWORK_PUBLIC:-${1}}"; }
|
||||
|
||||
# myos_app_domain SCOPE USER DOMAIN
|
||||
# The domain a stack is served on. A host stack is never prefixed by the user:
|
||||
# it belongs to the machine.
|
||||
myos_app_domain() {
|
||||
_scope=$1; _u=$2; _dom=$3
|
||||
if [ "$_scope" != host ] && [ "${APP_HOST_MULTI_USER:-false}" = true ]; then
|
||||
printf '%s.%s' "$_u" "$_dom"
|
||||
else
|
||||
printf '%s' "$_dom"
|
||||
fi
|
||||
}
|
||||
|
||||
# myos_app_host SCOPE USER ENV APP DOMAIN HOSTNAME
|
||||
# A host stack is served on <hostname>.<domain>; anything else is prefixed by
|
||||
# the environment unless the environment is the main one.
|
||||
myos_app_host() {
|
||||
_scope=$1; _u=$2; _env=$3; _app=$4; _dom=$5; _host=$6
|
||||
_multi_env=${APP_HOST_MULTI_ENV:-}
|
||||
if [ -z "$_multi_env" ]; then
|
||||
case $_env in local|master|main) _multi_env=false ;; *) _multi_env=true ;; esac
|
||||
fi
|
||||
_prefix=
|
||||
if [ "$_scope" = host ]; then _prefix="$_host."
|
||||
elif [ "$_multi_env" = true ]; then _prefix="$_env."
|
||||
fi
|
||||
_name=
|
||||
[ "${APP_HOST_MULTI_APP:-false}" = true ] && _name="$(myos_name "$_app")."
|
||||
_out="$_prefix$_name$(myos_app_domain "$_scope" "$_u" "$_dom")"
|
||||
# a host stack behind the load balancer also answers on the bare domain
|
||||
[ "$_scope" = host ] && [ -n "${HOST_LB:-}" ] && _out="$_out $_dom"
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
# myos_app_uri HOST [PATH] the base uri the fabio tags are built on;
|
||||
# it always ends with a slash
|
||||
myos_app_uri() {
|
||||
_out=
|
||||
for _h in $1; do _out="${_out:+$_out }$_h/${2:-}"; done
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
+43
-4
@@ -15,7 +15,12 @@ myos_path() {
|
||||
_wd=${WORKDIR:-$PWD}
|
||||
_name=${STACK_DIR_NAME:-stack}
|
||||
_out=
|
||||
for _d in "$_wd" "$_wd/.." "${HOME:-/nonexistent}/.local/share" /usr/local/share /usr/share; do
|
||||
# <prefix>/share comes from MYOS_ROOT, so an installation under any prefix
|
||||
# finds the catalogue installed beside it
|
||||
_prefix=
|
||||
[ -n "${MYOS_ROOT:-}" ] && _prefix=$(dirname "$(dirname "$MYOS_ROOT")")/share
|
||||
for _d in "$_wd" "$_wd/.." "${HOME:-/nonexistent}/.local/share" \
|
||||
${_prefix:+"$_prefix"} /usr/local/share /usr/share; do
|
||||
for _c in "$_d/$_name" "$_d/myos/$_name"; do
|
||||
[ -d "$_c" ] || continue
|
||||
_c=$(cd "$_c" && pwd -P)
|
||||
@@ -39,6 +44,37 @@ myos_stack_version() {
|
||||
case $_r in *:*) printf '%s' "${_r##*:}" ;; *) printf 'latest' ;; esac
|
||||
}
|
||||
|
||||
# myos_stack_dirs REF every directory of MYOS_PATH holding this stack, least
|
||||
# specific first. A compose overlay wins over the ones before it, so a project
|
||||
# that ships stack/postgres/postgres.local.yml refines the postgres stack of
|
||||
# the catalogue instead of replacing it.
|
||||
myos_stack_dirs() {
|
||||
_ref=${1%/}
|
||||
case $_ref in *:*) _ref=${_ref%:*} ;; esac
|
||||
_name=$(myos_stack_name "$1")
|
||||
case $_ref in
|
||||
.|./*|/*|../*)
|
||||
[ -d "$_ref" ] && (cd "$_ref" && pwd -P)
|
||||
return 0 ;;
|
||||
esac
|
||||
_found=
|
||||
_IFS=$IFS; IFS=:
|
||||
for _d in $(myos_path); do
|
||||
IFS=$_IFS
|
||||
_hit=
|
||||
if [ -d "$_d/$_ref" ]; then _hit=$_d/$_ref
|
||||
elif [ -f "$_d/$_ref.yml" ] || [ -f "$_d/$_ref.yaml" ]; then _hit=$(dirname "$_d/$_ref")
|
||||
elif [ -d "$_d/$_name" ]; then _hit=$_d/$_name
|
||||
fi
|
||||
# least specific last here, then reversed below
|
||||
[ -n "$_hit" ] && _found="$_hit
|
||||
$_found"
|
||||
IFS=:
|
||||
done
|
||||
IFS=$_IFS
|
||||
printf '%s' "$_found" | sed '/^$/d'
|
||||
}
|
||||
|
||||
# myos_stack_resolve REF print the directory holding the stack,
|
||||
# or fail with MYOS_E_NOSTACK
|
||||
myos_stack_resolve() {
|
||||
@@ -135,9 +171,12 @@ myos_group_value() {
|
||||
_IFS=$IFS; IFS=:
|
||||
for _d in $(myos_path); do
|
||||
IFS=$_IFS
|
||||
[ -f "$_d/$1.env" ] && { _v=$(sed -n "s/^$1=//p" "$_d/$1.env" | tail -1 | tr -d '"'); }
|
||||
[ -z "$_v" ] && [ -f "$_d/$1.mk" ] && { _v=$(myos_mk_group "$_d/$1.mk" "$1"); }
|
||||
[ -z "$_v" ] && [ -f "$_d/$1/$1.mk" ] && { _v=$(myos_mk_group "$_d/$1/$1.mk" "$1"); }
|
||||
for _f in "$_d/$1.env" "$_d/$1/$1.env" "$_d/$1/_stack.env"; do
|
||||
[ -z "$_v" ] && [ -f "$_f" ] && _v=$(sed -n "s/^$1=//p" "$_f" | tail -1 | tr -d '"')
|
||||
done
|
||||
for _f in "$_d/$1.mk" "$_d/$1/$1.mk"; do
|
||||
[ -z "$_v" ] && [ -f "$_f" ] && _v=$(myos_mk_group "$_f" "$1")
|
||||
done
|
||||
[ -n "$_v" ] && { printf '%s' "$_v"; return 0; }
|
||||
IFS=:
|
||||
done
|
||||
|
||||
+102
@@ -14,8 +14,10 @@ myos_slugify() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9
|
||||
|
||||
# myos_reverse WORDS... reverse the order of space separated words
|
||||
myos_reverse() {
|
||||
set -f # a word may be a pattern such as *.example.org, do not glob it
|
||||
_out=
|
||||
for _w in $1; do _out="$_w${_out:+ }$_out"; done
|
||||
set +f
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
@@ -31,3 +33,103 @@ myos_verlt() {
|
||||
[ "$1" = "$2" ] && return 1
|
||||
myos_verle "$1" "$2"
|
||||
}
|
||||
|
||||
# The make list functions the catalogue uses, on space separated words.
|
||||
|
||||
# myos_firstword LIST / myos_lastword LIST
|
||||
myos_firstword() { set -f; for _w in $1; do set +f; printf '%s' "$_w"; return 0; done; set +f; }
|
||||
myos_lastword() { _l=; for _w in $1; do _l=$_w; done; printf '%s' "$_l"; }
|
||||
|
||||
# myos_or A B... the first argument that is not empty
|
||||
myos_or() { for _a in "$@"; do [ -n "$_a" ] && { printf '%s' "$_a"; return 0; }; done; }
|
||||
|
||||
# myos_patsubst PATTERN REPLACEMENT LIST
|
||||
# The pattern holds one %, standing for any text; the replacement puts it back.
|
||||
myos_patsubst() {
|
||||
set -f # a word may be a pattern such as *.example.org, do not glob it
|
||||
_pre=${1%%%*}; _suf=${1#*%}
|
||||
_rpre=${2%%%*}; _rsuf=${2#*%}
|
||||
_out=
|
||||
for _w in $3; do
|
||||
case $_w in
|
||||
"$_pre"*"$_suf")
|
||||
_stem=${_w#"$_pre"}; _stem=${_stem%"$_suf"}
|
||||
_out="${_out:+$_out }$_rpre$_stem$_rsuf" ;;
|
||||
*) _out="${_out:+$_out }$_w" ;;
|
||||
esac
|
||||
done
|
||||
set +f
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
# myos_pattern MAKE_PATTERN the shell pattern matching a make pattern.
|
||||
# In make only % is a wildcard, so a literal *, ? or [ has to be protected
|
||||
# before % becomes *: "*.%" means "starts with a star and a dot", not
|
||||
# "anything".
|
||||
myos_pattern() {
|
||||
printf '%s' "$1" | sed -e 's/[][*?]/\\&/g' -e 's/%/*/g'
|
||||
}
|
||||
|
||||
# myos_filter PATTERNS LIST / myos_filter_out PATTERNS LIST
|
||||
myos_filter() {
|
||||
set -f # a word may itself be a pattern, do not glob it
|
||||
_out=
|
||||
for _w in $2; do
|
||||
for _raw in $1; do
|
||||
_p=$(myos_pattern "$_raw")
|
||||
# shellcheck disable=SC2254 # the pattern is meant to match, not to glob
|
||||
case $_w in $_p) _out="${_out:+$_out }$_w"; break ;; esac
|
||||
done
|
||||
done
|
||||
set +f
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
myos_filter_out() {
|
||||
set -f
|
||||
_out=
|
||||
for _w in $2; do
|
||||
_keep=yes
|
||||
for _raw in $1; do
|
||||
_p=$(myos_pattern "$_raw")
|
||||
# shellcheck disable=SC2254 # the pattern is meant to match, not to glob
|
||||
case $_w in $_p) _keep=no; break ;; esac
|
||||
done
|
||||
[ "$_keep" = yes ] && _out="${_out:+$_out }$_w"
|
||||
done
|
||||
set +f
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
# myos_addprefix PREFIX LIST / myos_addsuffix SUFFIX LIST
|
||||
myos_addprefix() { set -f; _out=; for _w in $2; do _out="${_out:+$_out }$1$_w"; done; set +f; printf '%s' "$_out"; }
|
||||
myos_addsuffix() { set -f; _out=; for _w in $2; do _out="${_out:+$_out }$_w$1"; done; set +f; printf '%s' "$_out"; }
|
||||
|
||||
# myos_b64url read stdin, write url-safe base64 without padding
|
||||
myos_b64url() { openssl enc -A -base64 | tr '+/' '-_' | tr -d '='; }
|
||||
|
||||
# myos_jwt HEADER PAYLOAD SECRET a HS256 JSON web token
|
||||
# Ported from the JWT macro of make/def.mk, which supabase uses to derive its
|
||||
# anon and service keys from one secret. The make macro split on the commas of
|
||||
# the payload; this one does not.
|
||||
myos_jwt() {
|
||||
_h=${1:-'{"alg":"HS256","typ":"JWT"}'}
|
||||
_p=$2
|
||||
_s=$3
|
||||
_hb=$(printf '%s' "$_h" | myos_b64url)
|
||||
_pb=$(printf '%s' "$_p" | myos_b64url)
|
||||
_sig=$(printf '%s' "$_hb.$_pb" | openssl dgst -sha256 -binary -hmac "$_s" | myos_b64url)
|
||||
printf '%s.%s.%s' "$_hb" "$_pb" "$_sig"
|
||||
}
|
||||
|
||||
# myos_patsublist PATTERN REPLACEMENT LIST
|
||||
# patsubst over a list, joined by commas. The fabio tags are built this way:
|
||||
# one route per uri, in a single label.
|
||||
myos_patsublist() {
|
||||
set -f # a word may be a pattern such as *.example.org, do not glob it
|
||||
_out=
|
||||
for _w in $3; do
|
||||
_out="${_out:+$_out,}$(myos_patsubst "$1" "$2" "$_w")"
|
||||
done
|
||||
set +f
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
+33
-11
@@ -4,42 +4,49 @@
|
||||
# Ported from make/apps/def.mk (uri, url, urlprefix, urlprefixs, tagprefix,
|
||||
# envprefix, servicenvs). Registrator publishes the SERVICE_<port>_TAGS label
|
||||
# to consul, fabio routes on the urlprefix- tags it finds there.
|
||||
#
|
||||
# Every lookup goes through myos_var (lib/var.sh), so a stack setting may be a
|
||||
# plain value or a lazy default, and the two behave the same here.
|
||||
|
||||
# myos_var NAME value of the variable named NAME, empty when unset
|
||||
myos_var() {
|
||||
[ -n "${1:-}" ] || return 0
|
||||
eval "printf '%s' \"\${$1:-}\""
|
||||
}
|
||||
|
||||
# myos_uri SERVICE PORT [BASE_URI]
|
||||
# <service>.<base uri>, unless <SERVICE>_SERVICE[_<port>]_NAME overrides the prefix
|
||||
# myos_uri SERVICES PORT [BASE_URI]
|
||||
# <service>.<base uri> for each service and each base uri, unless
|
||||
# <SERVICE>_SERVICE[_<port>]_NAME overrides the prefix. The first argument is a
|
||||
# list: one stack may publish several services on one port.
|
||||
myos_uri() {
|
||||
_svc=$1; _port=${2:-}; _base=${3:-${APP_URI:-}}
|
||||
set -f # a uri may be a pattern such as *.ipns.example.org
|
||||
_svcs=$1; _port=${2:-}; _base=${3:-${APP_URI:-}}
|
||||
_out=
|
||||
for _svc in $_svcs; do
|
||||
_u=$(myos_upper "$_svc")
|
||||
_name=$(myos_var "${_u}_SERVICE_${_port}_NAME")
|
||||
[ -n "$_name" ] || _name=$(myos_var "${_u}_SERVICE_NAME")
|
||||
[ -n "$_name" ] || _name=$_svc
|
||||
_out=
|
||||
for _b in $_base; do _out="${_out:+$_out }${_name}.${_b}"; done
|
||||
done
|
||||
set +f
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
# myos_url SERVICE PORT [BASE_URI]
|
||||
myos_url() {
|
||||
set -f
|
||||
_out=
|
||||
for _u in $(myos_uri "$@"); do _out="${_out:+$_out }${APP_SCHEME:-http}://$_u"; done
|
||||
set +f
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
# myos_urlprefix [PATH] [OPTS] [URIS]
|
||||
# one comma separated "urlprefix-<uri><path>* [opts]" per uri
|
||||
myos_urlprefix() {
|
||||
set -f
|
||||
_path=${1:-}; _opts=${2:-}; _uris=${3:-${APP_URI:-}}
|
||||
_out=
|
||||
for _u in $_uris; do
|
||||
_tag="urlprefix-${_u}${_path}${MYOS_URL_SUFFIX:-*}${_opts:+ $_opts}"
|
||||
_out="${_out:+$_out,}$_tag"
|
||||
done
|
||||
set +f
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
@@ -65,9 +72,24 @@ myos_tagprefix() {
|
||||
[ -n "$_opts" ] || _opts=$(myos_envprefix "$_stack" "$_port" allow auth deny prepend proto register strip)
|
||||
_uris=
|
||||
for _k in "$@"; do
|
||||
_v=$(myos_var "${_u}_SERVICE_${_port}_${_k}"); [ -n "$_v" ] && _uris="${_uris:+$_uris }$_v"
|
||||
_v=$(myos_var "${_u}_SERVICE_${_port}_$(myos_upper "$_k")")
|
||||
[ -n "$_v" ] && _uris="${_uris:+$_uris }$_v"
|
||||
done
|
||||
[ -n "$_uris" ] || _uris=$(myos_var "${_u}_SERVICE_${_port}_URIS")
|
||||
[ -n "$_uris" ] || _uris=$(myos_uri "$_stack" "$_port")
|
||||
myos_urlprefix "$_path" "$_opts" "$_uris"
|
||||
}
|
||||
|
||||
# myos_servicenvs STACK GROUP KEY
|
||||
# Collect <STACK>_SERVICE_<env>_<KEY> for every env listed in
|
||||
# <STACK>_SERVICE_<GROUP>_ENVS. Used to build a list of listeners out of one
|
||||
# variable per protocol.
|
||||
myos_servicenvs() {
|
||||
_s=$(myos_upper "$1"); _g=$(myos_upper "$2"); _k=$(myos_upper "$3")
|
||||
_out=
|
||||
for _e in $(myos_var "${_s}_SERVICE_${_g}_ENVS"); do
|
||||
_v=$(myos_var "${_s}_SERVICE_$(myos_upper "$_e")_${_k}")
|
||||
[ -n "$_v" ] && _out="${_out:+$_out }$_v"
|
||||
done
|
||||
printf '%s' "$_out"
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
#shellcheck shell=sh
|
||||
# var: variable resolution, with the lazy defaults the make engine had.
|
||||
#
|
||||
# make gives every `VAR ?= $(call ...)` two properties at once: an explicit
|
||||
# value wins, and the default is re-evaluated at each reference, so it sees
|
||||
# whatever a .env loaded later has changed.
|
||||
#
|
||||
# The shell gets both by keeping defaults in functions: `myos_var NAME` reads
|
||||
# the variable when it has a value, and otherwise calls the function
|
||||
# `myos_default_NAME`. The prefix matters: a bare function named after the
|
||||
# variable would collide with commands on PATH, and a stack setting called
|
||||
# `host` or `test` would then run a program instead of returning a value.
|
||||
#
|
||||
# myos_default_HOST_FABIO_SERVICE_9998_TAGS() { myos_tagprefix HOST_FABIO 9998; }
|
||||
#
|
||||
# is the exact equivalent of
|
||||
#
|
||||
# HOST_FABIO_SERVICE_9998_TAGS ?= $(call tagprefix,HOST_FABIO,9998)
|
||||
|
||||
MYOS_VAR_MAX_DEPTH=${MYOS_VAR_MAX_DEPTH:-32}
|
||||
|
||||
# myos_var NAME the value of NAME: the variable if it has one, else the lazy
|
||||
# default, else empty.
|
||||
myos_var() {
|
||||
[ -n "${1:-}" ] || return 0
|
||||
eval "_myos_set=\${$1+yes}"
|
||||
if [ "${_myos_set:-}" = yes ]; then
|
||||
eval "printf '%s' \"\$$1\""
|
||||
return 0
|
||||
fi
|
||||
myos_var_is_lazy "$1" || return 0
|
||||
|
||||
# a default written in terms of itself would loop for ever
|
||||
_myos_depth=$((${MYOS_VAR_DEPTH:-0} + 1))
|
||||
if [ "$_myos_depth" -gt "$MYOS_VAR_MAX_DEPTH" ]; then
|
||||
myos_error "variable $1 is defined in terms of itself"
|
||||
return 1
|
||||
fi
|
||||
MYOS_VAR_DEPTH=$_myos_depth "myos_default_$1"
|
||||
}
|
||||
|
||||
# myos_default NAME BODY declare a lazy default from a string, for callers
|
||||
# that build the variable name at run time
|
||||
myos_default() {
|
||||
eval "myos_default_$1() { $2; }"
|
||||
}
|
||||
|
||||
# myos_var_is_lazy NAME true when NAME has no value but has a lazy default
|
||||
myos_var_is_lazy() {
|
||||
eval "_myos_set=\${$1+yes}"
|
||||
[ "${_myos_set:-}" = yes ] && return 1
|
||||
# a shell function, never a command on PATH: command -v prints the name back
|
||||
# for a function and a path for a program
|
||||
_myos_fn=$(command -v "myos_default_$1" 2>/dev/null) || return 1
|
||||
[ "$_myos_fn" = "myos_default_$1" ]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
##
|
||||
# myos, from make.
|
||||
#
|
||||
# This file gives a project the myos commands as make targets, without the make
|
||||
# engine: every target shells out to bin/myos, which is pure POSIX sh. What the
|
||||
# project keeps from make is what make is actually good at, and the CLI is not:
|
||||
# its own targets, its own dependencies, and the .mk files of its stacks.
|
||||
#
|
||||
# MYOS ?= /usr/local/lib/myos
|
||||
# include $(MYOS)/share/make/shim.mk
|
||||
#
|
||||
# It lives outside make/ on purpose: the legacy engine includes every .mk of
|
||||
# that directory, and would pull this one in too.
|
||||
#
|
||||
# Then `make up STACK=host` and `myos up host` do the same thing, through the
|
||||
# same code. Variables given on the command line are forwarded, so
|
||||
# `make up STACK=host DOMAIN=example.org` behaves as expected.
|
||||
|
||||
MYOS ?= $(patsubst %/share/make/shim.mk,%,$(lastword $(MAKEFILE_LIST)))
|
||||
MYOS_BIN ?= $(MYOS)/bin/myos
|
||||
STACK_DIR_NAME ?= stack
|
||||
## every directory myos looks for stacks in, so the .mk of a stack installed
|
||||
## system wide brings its targets along too
|
||||
STACK_DIR ?= $(subst :, ,$(shell $(MYOS_BIN) env MYOS_PATH --color=never 2>/dev/null | awk '{print $$2}'))
|
||||
|
||||
# variable MYOS_ARGS: variables set on the make command line, forwarded to myos
|
||||
MYOS_ARGS ?= $(foreach v,$(MAKEOVERRIDES),$(v))
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
## the stack files may add their own targets: that is what make is kept for
|
||||
include $(foreach dir,$(STACK_DIR),$(wildcard $(dir)/*.mk $(dir)/*/*.mk))
|
||||
|
||||
# function make: run a myos command, for the stack .mk files that call it
|
||||
define make
|
||||
$(MYOS_BIN) $(MYOS_ARGS) $(1)
|
||||
endef
|
||||
|
||||
# function myos-var: the value myos resolves for a variable.
|
||||
# A stack keeps its settings in hooks that only myos reads, so a .mk target
|
||||
# asks for them rather than defining them itself:
|
||||
# $(call myos-var,HOST_DOCKER_VOLUME)
|
||||
myos-var = $(shell $(MYOS_BIN) --color=never $(MYOS_ARGS) env $(1) | awk '{print $$2}')
|
||||
|
||||
# target help: List the myos commands
|
||||
.PHONY: help
|
||||
help:
|
||||
@$(MYOS_BIN) help
|
||||
|
||||
# target myos: Run an arbitrary myos command, as in `make myos ARGS="up host"`
|
||||
.PHONY: myos
|
||||
myos:
|
||||
@$(MYOS_BIN) $(MYOS_ARGS) $(ARGS)
|
||||
|
||||
# make tries to remake every makefile it read, and the catch-all below would
|
||||
# hand each of them to myos as a command. An empty rule stops that.
|
||||
$(MAKEFILE_LIST): ;
|
||||
|
||||
# target %: Hand anything else to myos
|
||||
## a target the project defines itself keeps precedence over this rule
|
||||
%: FORCE
|
||||
@$(MYOS_BIN) $(MYOS_ARGS) $@ $(ARGS)
|
||||
|
||||
.PHONY: FORCE
|
||||
FORCE: ;
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
name: myos
|
||||
description: Deploy, inspect and troubleshoot docker compose stacks on a server or a fleet with the myos CLI (Make Your Own Stack). Use when asked to install myos, to start/stop/debug a stack (a host singleton, an application, a user stack), to add a stack to the catalogue, or to work on the myos framework itself.
|
||||
---
|
||||
|
||||
# myos
|
||||
|
||||
myos runs docker compose stacks. It decides which compose files to load, under
|
||||
which project name, with which variables. Everything else is docker.
|
||||
|
||||
## Is this the right tool
|
||||
|
||||
Use it when the repository or the host has one of:
|
||||
- a `stack/` directory holding compose files,
|
||||
- a `Makefile` that includes `make/include.mk`,
|
||||
- `/etc/conf.d/myos` or `/etc/default/myos`,
|
||||
- the `myos` command on PATH.
|
||||
|
||||
Otherwise call `docker compose` directly.
|
||||
|
||||
## Look before you touch
|
||||
|
||||
```sh
|
||||
myos doctor # requirements, config layers, stack path
|
||||
myos ls # stacks reachable from here
|
||||
myos ls --groups # groups and what they expand to
|
||||
myos env # resolved variables
|
||||
myos -n up host # print what `up` would run, without running it
|
||||
```
|
||||
|
||||
**Always run `myos -n` before `up`, `down` or `restart` on a `host/` stack.**
|
||||
Host stacks bind privileged ports and are shared by everything on the machine:
|
||||
taking `host/fabio` down drops every site the machine serves.
|
||||
|
||||
## Working loop
|
||||
|
||||
```sh
|
||||
myos ls # find the stack
|
||||
myos -n config <stack> # check the file list and the project name
|
||||
myos up <stack> # create and start
|
||||
myos ps <stack> # what is running
|
||||
myos logs <stack> # follow the logs
|
||||
myos exec <stack> -- sh # a shell in the service named after the stack
|
||||
myos down <stack> # remove the containers
|
||||
```
|
||||
|
||||
## Naming a stack
|
||||
|
||||
A reference is `[<group>/]<name>[:<version>]`, or a path.
|
||||
|
||||
Commands chain: `myos build up logs host/fabio` runs the three in order and
|
||||
stops at the first failure.
|
||||
|
||||
```sh
|
||||
myos up # the current directory, when it holds a compose file
|
||||
myos up host # a group: expands to host/consul host/fabio ...
|
||||
myos up host/fabio # one stack
|
||||
myos up postgres:9.6 # the 9.6 overlay of the postgres stack
|
||||
myos -C /opt/app up # somewhere else
|
||||
myos -e master up # in another environment
|
||||
```
|
||||
|
||||
Stacks are looked up along the stack path: `./stack`, `../stack`,
|
||||
`~/.local/share/myos/stack`, `/usr/local/share/myos/stack`, `/usr/share/myos/stack`.
|
||||
`myos doctor` prints the resolved path.
|
||||
|
||||
A stack found in several of them is **merged**, the project last, so a project
|
||||
adds `stack/postgres/postgres.local.yml` next to the catalogue's `postgres.yml`
|
||||
and refines it instead of replacing it. The same goes for the settings hooks.
|
||||
|
||||
## Three kinds of stack
|
||||
|
||||
| kind | reference | compose project | meaning |
|
||||
|---|---|---|---|
|
||||
| host | `host/*` | the hostname | one per machine: binds 80/443, consul, certbot. Shared. Treat as production. |
|
||||
| user | `User/*` | the user identity | one per user: ssh-agent, personal services |
|
||||
| app | anything else | `<user>-<env>-<app>` | many per machine, one per user and environment |
|
||||
|
||||
Set `MYOS_PROJECT_FORMAT=user-app-env` on a deployment created before the
|
||||
rename, otherwise its containers and volumes look like a different project.
|
||||
See `references/conventions.md`.
|
||||
|
||||
## Rules
|
||||
|
||||
- Never run `myos clean` on a host stack: it removes images **and volumes**,
|
||||
including the certificates.
|
||||
- Secrets belong in a file outside the repository, never in a compose file.
|
||||
- A stack of the catalogue is shared: change it in `myos-stacks`, not in place
|
||||
on a server.
|
||||
- Before upgrading myos on a machine that already runs stacks, pin the naming
|
||||
(`references/conventions.md`), or every project gets a new name.
|
||||
|
||||
## More
|
||||
|
||||
- `references/commands.md` — every command, and its equivalent in the old make targets
|
||||
- `references/conventions.md` — file layout, naming, networks, variables, tags
|
||||
- `references/troubleshooting.md` — what each failure means and how to fix it
|
||||
- `references/authoring.md` — writing a stack, and working on myos itself
|
||||
@@ -0,0 +1,78 @@
|
||||
# Writing a stack, and working on myos
|
||||
|
||||
## A new stack in the catalogue
|
||||
|
||||
```
|
||||
stack/<name>/<name>.yml the services
|
||||
stack/<name>/<name>.local.yml what only makes sense on a workstation (published ports…)
|
||||
stack/<name>/<name>.labels.yml the registrator labels, so routing stays optional
|
||||
stack/<name>/<name>.env plain settings: versions, defaults
|
||||
stack/<name>/<name>.sh lazy defaults (fabio tags), no make needed
|
||||
stack/<name>/.env.dist the variables it expects, with defaults
|
||||
stack/<name>/README.md what it is and what it needs
|
||||
```
|
||||
|
||||
Rules that keep a stack reusable:
|
||||
- no `container_name`, except in a `host/` stack: it prevents scaling and
|
||||
collides between users,
|
||||
- no fixed host port outside `host/`; publish through the load balancer,
|
||||
- reference variables with a default: `${POSTGRES_VERSION:-16}`,
|
||||
- attach to `private` to be reachable by the other stacks of the user, to
|
||||
`public` to be routed,
|
||||
- name volumes, never bind-mount an absolute path.
|
||||
|
||||
Check it before committing:
|
||||
|
||||
```sh
|
||||
myos -n config <name> # the file list and the project
|
||||
myos config <name> # the rendered yaml
|
||||
```
|
||||
|
||||
## A group
|
||||
|
||||
```sh
|
||||
# stack/<group>.env
|
||||
mygroup=<name> other/<name>
|
||||
```
|
||||
|
||||
Lowercase, and it may name other groups.
|
||||
|
||||
## Working on myos itself
|
||||
|
||||
```sh
|
||||
make test # unit + golden, against both engines, with a mocked docker
|
||||
make test-golden # golden only
|
||||
make golden-record # re-record the golden expectations from the make engine
|
||||
make lint # shellcheck
|
||||
```
|
||||
|
||||
Layout:
|
||||
|
||||
```
|
||||
bin/myos argument parsing, configuration, dispatch
|
||||
lib/core.sh logging, exit codes, dry run
|
||||
lib/str.sh strings and version comparison
|
||||
lib/naming.sh project names, networks, user identity
|
||||
lib/stack.sh stack path, references, overlays, groups
|
||||
lib/var.sh variable resolution and lazy defaults
|
||||
lib/config.sh dotenv, templates, variables of the compose files
|
||||
lib/hooks.sh the per-stack .env and .sh
|
||||
lib/compose.sh finding and calling docker compose
|
||||
lib/tags.sh fabio tags
|
||||
lib/cmd/<x>.sh one file per command
|
||||
share/compose/ the networks and volumes overlays myos provides
|
||||
spec/ shellspec
|
||||
```
|
||||
|
||||
Adding a command: write `lib/cmd/<name>.sh` defining `myos_cmd_<name>`, add it
|
||||
to the usage text in `bin/myos`, and cover it in `spec/unit`.
|
||||
|
||||
Constraints:
|
||||
- POSIX shell, no bashisms: it has to run under the bash 3.2 of macOS and the
|
||||
ash of Alpine. `make test` runs on both.
|
||||
- No `a-z` ranges in a `case` pattern: under a dictionary collation such as
|
||||
`fr_FR` they also match uppercase. Use `[:lower:]`.
|
||||
- A function that has to return several values takes them out through printf,
|
||||
not through a global: a caller inside `$( )` would lose the global.
|
||||
- Changing what a command prints means updating `spec/golden/expected*/` and
|
||||
explaining the change in `spec/golden/DELTAS.md`.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Commands
|
||||
|
||||
```
|
||||
myos [options] <command> [stack...] [VAR=value...] [-- args...]
|
||||
```
|
||||
|
||||
| option | effect |
|
||||
|---|---|
|
||||
| `-C DIR` | work in DIR instead of the current directory |
|
||||
| `-e ENV` | environment: picks `.env.<env>` and the `<stack>.<env>.yml` overlays |
|
||||
| `-n`, `--dry-run` | print the commands instead of running them |
|
||||
| `-v`, `--verbose` | say what myos is doing |
|
||||
| `-d`, `--debug` | print every command |
|
||||
|
||||
| command | effect |
|
||||
|---|---|
|
||||
| `up` | create and start; creates the external networks first |
|
||||
| `down` | remove the containers |
|
||||
| `start` / `stop` / `restart` | on the existing containers |
|
||||
| `ps` | what is running |
|
||||
| `logs` | follow the logs |
|
||||
| `config` | the resolved compose file |
|
||||
| `exec` | run a command in a running service |
|
||||
| `run` | run it in a new container, removed afterwards |
|
||||
| `scale` | `myos scale <stack> SERVICE=<name> NUM=<n>` |
|
||||
| `build` / `pull` | images |
|
||||
| `ls [--groups]` | the stacks and groups myos can see |
|
||||
| `env [VAR...]` | resolved variables |
|
||||
| `env-update` | fill the `.env` from the `.env.dist` templates |
|
||||
| `doctor` | check the installation |
|
||||
| `version` | the myos version |
|
||||
|
||||
## Several commands at once
|
||||
|
||||
Commands chain, the way make targets did. Leading words that name commands are
|
||||
commands; the first word that is not one starts the list of stacks.
|
||||
|
||||
```sh
|
||||
myos build up logs host/fabio # like: make build up logs STACK=host/fabio
|
||||
myos up ps host
|
||||
```
|
||||
|
||||
They run in order and stop at the first failure. A stack whose name is also a
|
||||
command name has to be given as `STACK=<name>`, otherwise it is read as a
|
||||
command.
|
||||
|
||||
Anything after `--` goes to docker compose:
|
||||
|
||||
```sh
|
||||
myos logs host/fabio -- --tail 20
|
||||
myos up postgres -- --force-recreate
|
||||
```
|
||||
|
||||
`exec` and `run` take the service from the stack name, since most stacks name
|
||||
their main service after themselves. `SERVICE=` picks another one:
|
||||
|
||||
```sh
|
||||
myos exec host/consul -- consul members # service consul, command "consul members"
|
||||
myos exec host/fabio SERVICE=fabio -- sh
|
||||
myos run postgres -- psql -l
|
||||
```
|
||||
|
||||
## Exit codes
|
||||
|
||||
| code | meaning |
|
||||
|---|---|
|
||||
| 0 | fine |
|
||||
| 1 | the command failed |
|
||||
| 2 | bad invocation, or an unknown command |
|
||||
| 3 | stack not found (the message prints the search path) |
|
||||
| 4 | missing requirement, run `myos doctor` |
|
||||
|
||||
The make engine exited 0 on an unknown target, printing only a warning. The CLI
|
||||
does not: a typo is an error.
|
||||
|
||||
## Coming from the make targets
|
||||
|
||||
| make | myos |
|
||||
|---|---|
|
||||
| `make up STACK=host` | `myos up host` |
|
||||
| `make print-COMPOSE_FILE` | `myos env COMPOSE_FILE` |
|
||||
| `make host` | `myos up host` (a bare stack name is not a command) |
|
||||
| `make build up logs STACK=host/fabio` | `myos build up logs host/fabio` |
|
||||
| `make stack-host-config` | `myos config host` |
|
||||
| `make up@master` | `myos -e master up` |
|
||||
| `make exec SERVICE=php ARGS='ls'` | `myos exec <stack> -- php ls` |
|
||||
| `make DRYRUN=true up` | `myos -n up` |
|
||||
|
||||
`print-VAR`, `stack-<stack>-<command>` and `<command>@<env>` still work.
|
||||
A project `Makefile` that includes `make/include.mk` keeps working too.
|
||||
@@ -0,0 +1,182 @@
|
||||
# Conventions
|
||||
|
||||
## A stack is a directory of compose files
|
||||
|
||||
For stack `<name>` in environment `<env>`, myos loads whichever of these exist,
|
||||
in this order. A later file overrides the ones before it.
|
||||
|
||||
```
|
||||
<name>.yml the stack
|
||||
<name>.<env>.yml this environment only
|
||||
<env>/<name>.yml same, when the stack keeps a directory per environment
|
||||
<name>.<suffix>.yml an optional overlay, see below
|
||||
<name>.<suffix>.<env>.yml
|
||||
<name>.<version>.yml when the reference is <name>:<version>
|
||||
```
|
||||
|
||||
`docker-compose.yml` is read under the same rules, so a stack can keep an
|
||||
upstream `docker-compose.yml` untouched and add its own `<name>.yml` on top.
|
||||
|
||||
Then myos appends its own `share/compose/networks.yml`, and the
|
||||
`volumes.<suffix>.<env>.yml` of the enabled suffixes.
|
||||
|
||||
## Overlay suffixes
|
||||
|
||||
Every `COMPOSE_FILE_<X>` variable that is not `false` enables the suffix `<x>`.
|
||||
|
||||
| variable | default | loads |
|
||||
|---|---|---|
|
||||
| `COMPOSE_FILE_APP` | true | `<name>.app.yml` |
|
||||
| `COMPOSE_FILE_LABELS` | true | `<name>.labels.yml`, the registrator labels |
|
||||
| `COMPOSE_FILE_NETWORKS` | true | `<name>.networks.yml` |
|
||||
| `COMPOSE_FILE_SSH` | true | `<name>.ssh.yml` |
|
||||
| `COMPOSE_FILE_VOLUMES` | true | `<name>.volumes.yml` |
|
||||
| `COMPOSE_FILE_WWW`, `_DNS`, `_HOME`, `_LOG`, `_BACKUP` | false | the matching overlay, and the framework bind mount |
|
||||
|
||||
A value that is neither `true` nor `false` also enables `<suffix>.<value>`:
|
||||
`COMPOSE_FILE_WWW=nginx` loads `<name>.www.yml` **and** `<name>.www.nginx.yml`.
|
||||
|
||||
## Project names
|
||||
|
||||
| scope | project | why |
|
||||
|---|---|---|
|
||||
| `host/*` | `$HOSTNAME` | a singleton of the machine: one consul, one fabio on 80/443 |
|
||||
| `User/*` | the user identity derived from the mail address | one per user |
|
||||
| `cluster/*` | the stack name | one namespace per swarm |
|
||||
| anything else | `<user>-<env>-<app>` | one per user, environment and app |
|
||||
|
||||
`MYOS_PROJECT_FORMAT=user-app-env` restores the older order. **Any deployment
|
||||
created before the rename must set it**, in its `.env` or in
|
||||
`/etc/conf.d/myos`; otherwise the containers and volumes of the old project
|
||||
are orphaned and the stack comes back up empty.
|
||||
|
||||
## Networks
|
||||
|
||||
| network | name | scope |
|
||||
|---|---|---|
|
||||
| `default` | `_<project>` | the project. The leading underscore keeps it first alphabetically, so it is the first interface attached and service names never resolve across stacks. |
|
||||
| `private` | `<user>-<env>` | external, shared between the stacks of one user and environment |
|
||||
| `public` | `<hostname>` | external, where the load balancer reaches the services |
|
||||
|
||||
`myos up` creates the external ones when they are missing.
|
||||
|
||||
## Routing: registrator, consul, fabio
|
||||
|
||||
A service is published by labels, which registrator copies into consul, and on
|
||||
which fabio routes:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- SERVICE_8000_NAME=${COMPOSE_SERVICE_NAME}-kong-8000
|
||||
- SERVICE_8000_TAGS=${SUPABASE_KONG_SERVICE_8000_TAGS:-urlprefix-supabase.localhost/*}
|
||||
- SERVICE_8000_CHECK_TCP=true
|
||||
- SERVICE_8001_IGNORE=true
|
||||
```
|
||||
|
||||
Registrator only sees ports that are `expose`d, and consul only routes a
|
||||
service whose check passes.
|
||||
|
||||
Tag variables follow `<STACK>_SERVICE_<port>_<KEY>`, with `PATH`, `OPTS`,
|
||||
`URIS`, `NAME`, and the fabio options `allow`, `auth`, `deny`, `prepend`,
|
||||
`proto`, `register`, `strip`:
|
||||
|
||||
```sh
|
||||
HOST_NGINX_SERVICE_443_PROTO='https tlsskipverify=true'
|
||||
DUNITER_V2S_SERVICE_9944_STRIP=/ws
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Layers, the last one wins:
|
||||
|
||||
```
|
||||
defaults < /etc/conf.d/myos, /etc/default/myos < ~/.config/myos/config
|
||||
< <workdir>/.env < <workdir>/.env.<env> < environment < VAR=value on the command line
|
||||
```
|
||||
|
||||
All of them are dotenv: `KEY=value`, `#` comments, optional quotes. They are
|
||||
parsed, never sourced, so a value may contain a `#` or a `$(...)` without
|
||||
breaking anything or being executed. The make engine included `.env` as a
|
||||
makefile, where both broke.
|
||||
|
||||
## Where a stack lives
|
||||
|
||||
The same stack may exist in several directories of the stack path. They are all
|
||||
loaded, least specific first, so the project has the last word:
|
||||
|
||||
```
|
||||
/usr/local/share/myos/stack/postgres/postgres.yml the catalogue
|
||||
./stack/postgres/postgres.local.yml the project refines it
|
||||
./stack/postgres/postgres.sh and may redefine a default
|
||||
```
|
||||
|
||||
Nothing has to be copied to change one setting: a value in the project `.env`
|
||||
already wins over any default a stack ships.
|
||||
|
||||
## Per-stack settings
|
||||
|
||||
A stack keeps its own settings next to its compose files:
|
||||
|
||||
| file | for |
|
||||
|---|---|
|
||||
| `<name>.env` | plain values: versions, defaults |
|
||||
| `<name>.env.<env>` | the same, for one environment |
|
||||
| `<name>.sh` | values that have to be computed |
|
||||
| `<name>.mk` | the legacy make snippet; still read for its groups |
|
||||
|
||||
A `.sh` hook declares **lazy defaults**: a function named
|
||||
`myos_default_<VARIABLE>`, called only when the variable has no value, and
|
||||
called again at each reference. That is the make `?=` on a recursive variable,
|
||||
in shell:
|
||||
|
||||
```sh
|
||||
# stack/host/fabio.sh
|
||||
myos_default_HOST_FABIO_VERSION() { printf '1.6.3'; }
|
||||
myos_default_HOST_FABIO_SERVICE_9998_NAME() { printf 'fabio'; }
|
||||
myos_default_HOST_FABIO_SERVICE_9998_AUTH() { printf 'default'; }
|
||||
myos_default_HOST_FABIO_SERVICE_9998_TAGS() { myos_tagprefix HOST_FABIO 9998; }
|
||||
```
|
||||
|
||||
Two things follow, and they are the point:
|
||||
|
||||
- a value given anywhere (environment, `.env`, command line) wins over the
|
||||
default, without the hook having to say so;
|
||||
- the default is computed against the values current **at the moment it is
|
||||
read**, so a `DOMAIN` set in a `.env` loaded later is taken into account.
|
||||
|
||||
The prefix is not decoration: a bare function named `host` or `test` would be
|
||||
indistinguishable from the program of that name, and myos would run it.
|
||||
|
||||
Helpers available in a hook: `myos_tagprefix`, `myos_urlprefix`, `myos_uri`,
|
||||
`myos_url`, `myos_envprefix`, `myos_servicenvs`, `myos_var`, `myos_lower`,
|
||||
`myos_upper`, and `myos_default NAME 'body'` when the name is built at run time.
|
||||
|
||||
## Templates: .env.dist
|
||||
|
||||
A stack may ship a `.env.dist` listing the variables it expects, with their
|
||||
defaults. `myos env-update` writes the missing ones into the `.env`, expanding
|
||||
`${VAR}` against the current values and running `$(command)`:
|
||||
|
||||
```sh
|
||||
# stack/demo/.env.dist
|
||||
DEMO_IMAGE=alpine:${DEMO_VERSION}
|
||||
DEMO_VERSION=3.20
|
||||
DEMO_SECRET=$(openssl rand -hex 16)
|
||||
```
|
||||
|
||||
A line may refer to a variable defined further down. A variable that already
|
||||
has a value keeps it: the `.env` records choices, it never overwrites them, and
|
||||
running the command twice changes nothing.
|
||||
|
||||
## Groups
|
||||
|
||||
A group is a lowercase name whose value lists stacks. It can live in a `.env`,
|
||||
in the environment, in `<path>/<group>.env`, or in a legacy `<group>.mk`:
|
||||
|
||||
```sh
|
||||
host=host/consul host/fabio host/registrator
|
||||
testing=drone/drone drone/gc
|
||||
```
|
||||
|
||||
Groups expand recursively. Only lowercase names are considered, so an
|
||||
environment variable can never be mistaken for a group.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Troubleshooting
|
||||
|
||||
Start with `myos doctor`, then `myos -n <command>` to see what would run.
|
||||
|
||||
## `stack not found: <name>` (exit 3)
|
||||
|
||||
The reference is not on the stack path, which the message prints.
|
||||
- `myos ls` shows what is reachable.
|
||||
- The catalogue may not be installed: `git clone <myos-stacks> /usr/local/share/myos`.
|
||||
- A stack of the project is only found from the project: use `myos -C /path/to/project`.
|
||||
|
||||
## `docker compose >= 2.24.4 not found` (exit 4)
|
||||
|
||||
Install the compose plugin, or a `docker-compose` binary of that version.
|
||||
myos no longer falls back to a compose image.
|
||||
|
||||
## `unknown command: <x>` (exit 2)
|
||||
|
||||
Check the spelling against `myos help`. The make engine accepted any target
|
||||
and exited 0 after printing a warning, so typos used to look like successes.
|
||||
|
||||
## The containers came back under a different name
|
||||
|
||||
The default project name changed from `<user>-<app>-<env>` to
|
||||
`<user>-<env>-<app>`. The old containers and volumes are still there, under the
|
||||
old project. Put `MYOS_PROJECT_FORMAT=user-app-env` in the `.env` of the
|
||||
deployment (or in `/etc/conf.d/myos`) and bring it up again.
|
||||
|
||||
Check first: `myos env COMPOSE_PROJECT_NAME` against `docker ps --format '{{.Names}}'`.
|
||||
|
||||
## `network <name> declared as external, but could not be found`
|
||||
|
||||
The `private` or `public` network is missing. `myos up` creates them; a bare
|
||||
`docker compose up` does not. Or create it by hand:
|
||||
`docker network create <user>-<env>`.
|
||||
|
||||
## A service is up but not routed
|
||||
|
||||
In order: the port must be `expose`d (registrator ignores what it cannot see),
|
||||
the labels must be on the service, the consul check must pass, and only then
|
||||
does fabio route the `urlprefix-` tag.
|
||||
|
||||
```sh
|
||||
myos config <stack> | grep -A5 labels # what the labels resolve to
|
||||
myos exec host/consul -- consul catalog services
|
||||
myos logs host/registrator
|
||||
```
|
||||
|
||||
## A variable is empty in the container
|
||||
|
||||
myos only passes the variables the compose files actually mention. Check with
|
||||
`myos env` and `myos config <stack>`. A variable set in a `.env` of another
|
||||
directory is not read: only the workdir's `.env` is.
|
||||
|
||||
## On macOS with Colima
|
||||
|
||||
The daemon lives in a VM: a bind mount only works for a path the VM shares, and
|
||||
`host.docker.internal` is the way back to the host. `myos doctor` prints the
|
||||
`DOCKER_HOST` in use.
|
||||
|
||||
## Something changed after upgrading myos
|
||||
|
||||
`spec/golden/DELTAS.md` in the myos repository lists every intentional
|
||||
difference between the make engine and the CLI, with the reason.
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
MYOS ?= /usr/local/lib/myos
|
||||
include $(MYOS)/share/make/shim.mk
|
||||
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
consul:
|
||||
image: hashicorp/consul:1.15
|
||||
container_name: ${HOST_COMPOSE_PROJECT_NAME:-localhost}-consul
|
||||
network_mode: host
|
||||
restart: always
|
||||
environment:
|
||||
CONSUL_HTTP_TOKEN: ${HOST_CONSUL_HTTP_TOKEN}
|
||||
volumes:
|
||||
- consul:/consul/data
|
||||
volumes:
|
||||
consul:
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
fabio:
|
||||
image: fabiolb/fabio:1.6.3
|
||||
container_name: ${HOST_COMPOSE_PROJECT_NAME:-localhost}-fabio
|
||||
depends_on: [consul]
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
FABIO_REGISTRY_CONSUL_ADDR: ${DOCKER_HOST_INET4:-127.0.0.1}:8500
|
||||
FABIO_LOG_ACCESS_TARGET: ${HOST_FABIO_LOG_ACCESS:-}
|
||||
networks:
|
||||
- public
|
||||
@@ -0,0 +1,7 @@
|
||||
host ?= host/consul host/fabio
|
||||
|
||||
# a target of the project, on top of the myos commands: this is what make is
|
||||
# kept for, and what the shim leaves alone
|
||||
.PHONY: host-certs
|
||||
host-certs:
|
||||
@echo "would renew the certificates of $(host)"
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
registrator:
|
||||
image: gliderlabs/registrator:master
|
||||
container_name: ${HOST_COMPOSE_PROJECT_NAME:-localhost}-registrator
|
||||
network_mode: host
|
||||
depends_on: [consul]
|
||||
command: -internal=false -useIpFromLabel SERVICE_ADDRESS consul://127.0.0.1:8500
|
||||
volumes:
|
||||
- ${DOCKER_SOCKET_LOCATION:-/var/run/docker.sock}:/tmp/docker.sock
|
||||
@@ -70,3 +70,17 @@ inc-app-print-env-vars | app-git | @make print-ENV_VARS
|
||||
inc-app-print-network-default | app-git | @make print-DOCKER_NETWORK_DEFAULT
|
||||
inc-app-stack-postgres-up | app-git | @make up STACK=postgres
|
||||
inc-app-unknown-target | app-git | @make doesnotexist
|
||||
cmd-exec-host | host-project | exec host/consul -- consul members
|
||||
cmd-exec-service | host-project | exec host/fabio SERVICE=fabio -- sh
|
||||
cmd-run-catalogue | app-nogit | run postgres -- psql -l
|
||||
cmd-scale | app-nogit | scale postgres SERVICE=postgres NUM=3
|
||||
cmd-ls | host-project | ls
|
||||
cmd-ls-groups | host-project | ls --groups
|
||||
cmd-env-domain | host-project | env DOMAIN
|
||||
shim-up-group | shim-project | @make up STACK=host DRYRUN=true
|
||||
shim-config | shim-project | @make config STACK=host/consul DRYRUN=true
|
||||
shim-project-target | shim-project | @make host-certs
|
||||
shim-env | shim-project | @make env ARGS=COMPOSE_PROJECT_NAME STACK=host
|
||||
chain-build-up-logs | host-project | build up logs host/fabio
|
||||
chain-up-group | host-project | up ps host
|
||||
chain-print-two | host-project | print-COMPOSE_PROJECT_NAME print-APP STACK=host/consul
|
||||
|
||||
@@ -1,26 +1,2 @@
|
||||
ERROR: unknown command: run
|
||||
Usage: myos [options] <command> [stack...] [VAR=value...] [-- args...]
|
||||
|
||||
Options:
|
||||
-C DIR work in DIR instead of the current directory
|
||||
-e ENV environment (default: local, or ENV from the config)
|
||||
-H HOSTS run on remote hosts instead (comma separated, or "all")
|
||||
-n, --dry-run print the commands instead of running them
|
||||
-v, --verbose show what myos does
|
||||
-d, --debug show every command
|
||||
-h, --help this help
|
||||
|
||||
Commands:
|
||||
up down start stop restart recreate manage the containers of a stack
|
||||
ps logs config exec run inspect and enter them
|
||||
ls [--groups] list the stacks myos can see
|
||||
env [VAR...] show resolved variables
|
||||
doctor check the installation
|
||||
version print the myos version
|
||||
|
||||
Stacks:
|
||||
myos up the stack of the current directory
|
||||
myos up host a group, expanded from host=... in a .env or .mk
|
||||
myos up host/fabio a single stack
|
||||
myos up postgres:9.6 a versioned stack
|
||||
[exit 2]
|
||||
docker compose -f @WD@/docker-compose.yml -f @WD@/docker-compose.local.yml -f @WD@/docker/docker-compose.yml -f @MYOS@/share/compose/networks.yml -p tester-wd-local --project-directory @WD@ run --rm app
|
||||
[exit 0]
|
||||
|
||||
@@ -1,26 +1,4 @@
|
||||
ERROR: unknown command: scale
|
||||
Usage: myos [options] <command> [stack...] [VAR=value...] [-- args...]
|
||||
|
||||
Options:
|
||||
-C DIR work in DIR instead of the current directory
|
||||
-e ENV environment (default: local, or ENV from the config)
|
||||
-H HOSTS run on remote hosts instead (comma separated, or "all")
|
||||
-n, --dry-run print the commands instead of running them
|
||||
-v, --verbose show what myos does
|
||||
-d, --debug show every command
|
||||
-h, --help this help
|
||||
|
||||
Commands:
|
||||
up down start stop restart recreate manage the containers of a stack
|
||||
ps logs config exec run inspect and enter them
|
||||
ls [--groups] list the stacks myos can see
|
||||
env [VAR...] show resolved variables
|
||||
doctor check the installation
|
||||
version print the myos version
|
||||
|
||||
Stacks:
|
||||
myos up the stack of the current directory
|
||||
myos up host a group, expanded from host=... in a .env or .mk
|
||||
myos up host/fabio a single stack
|
||||
myos up postgres:9.6 a versioned stack
|
||||
[exit 2]
|
||||
docker network create tester-local
|
||||
docker network create testhost
|
||||
docker compose -f @WD@/docker-compose.yml -f @WD@/docker-compose.local.yml -f @WD@/docker/docker-compose.yml -f @MYOS@/share/compose/networks.yml -p tester-wd-local --project-directory @WD@ up -d --scale app=2
|
||||
[exit 0]
|
||||
|
||||
@@ -1,26 +1,3 @@
|
||||
ERROR: unknown command: doesnotexist
|
||||
Usage: myos [options] <command> [stack...] [VAR=value...] [-- args...]
|
||||
|
||||
Options:
|
||||
-C DIR work in DIR instead of the current directory
|
||||
-e ENV environment (default: local, or ENV from the config)
|
||||
-H HOSTS run on remote hosts instead (comma separated, or "all")
|
||||
-n, --dry-run print the commands instead of running them
|
||||
-v, --verbose show what myos does
|
||||
-d, --debug show every command
|
||||
-h, --help this help
|
||||
|
||||
Commands:
|
||||
up down start stop restart recreate manage the containers of a stack
|
||||
ps logs config exec run inspect and enter them
|
||||
ls [--groups] list the stacks myos can see
|
||||
env [VAR...] show resolved variables
|
||||
doctor check the installation
|
||||
version print the myos version
|
||||
|
||||
Stacks:
|
||||
myos up the stack of the current directory
|
||||
myos up host a group, expanded from host=... in a .env or .mk
|
||||
myos up host/fabio a single stack
|
||||
myos up postgres:9.6 a versioned stack
|
||||
ERROR: to act on a stack of that name, say what to do: myos up doesnotexist
|
||||
[exit 2]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
docker compose -f @WD@/stack/host/fabio.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host build
|
||||
docker network create tester-local
|
||||
docker network create testhost
|
||||
docker compose -f @WD@/stack/host/fabio.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host up -d
|
||||
docker compose -f @WD@/stack/host/fabio.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host logs --follow --tail=100
|
||||
[exit 0]
|
||||
@@ -0,0 +1,5 @@
|
||||
COMPOSE_PROJECT_NAME testhost
|
||||
APP consul
|
||||
COMPOSE_PROJECT_NAME testhost
|
||||
APP consul
|
||||
[exit 0]
|
||||
@@ -0,0 +1,5 @@
|
||||
docker network create tester-local
|
||||
docker network create testhost
|
||||
docker compose -f @WD@/stack/host/consul.yml -f @WD@/stack/host/fabio.yml -f @WD@/stack/host/registrator.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host up -d
|
||||
docker compose -f @WD@/stack/host/consul.yml -f @WD@/stack/host/fabio.yml -f @WD@/stack/host/registrator.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host ps
|
||||
[exit 0]
|
||||
@@ -0,0 +1,2 @@
|
||||
DOMAIN example.test
|
||||
[exit 0]
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose -f @WD@/stack/host/consul.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host exec consul consul members
|
||||
[exit 0]
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose -f @WD@/stack/host/fabio.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host exec fabio sh
|
||||
[exit 0]
|
||||
@@ -0,0 +1,4 @@
|
||||
host host/consul host/fabio host/registrator
|
||||
default postgres redis
|
||||
testing drone/drone redis
|
||||
[exit 0]
|
||||
@@ -0,0 +1,9 @@
|
||||
@WD@/stack
|
||||
host 3 compose file(s)
|
||||
@HOME@/.local/share/myos/stack
|
||||
User 1 compose file(s)
|
||||
drone 2 compose file(s)
|
||||
host 3 compose file(s)
|
||||
postgres 3 compose file(s)
|
||||
redis 1 compose file(s)
|
||||
[exit 0]
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose -f @HOME@/.local/share/myos/stack/postgres/postgres.yml -f @HOME@/.local/share/myos/stack/postgres/postgres.local.yml -f @MYOS@/share/compose/networks.yml -p tester-postgres-local --project-directory @HOME@/.local/share/myos/stack/postgres run --rm postgres psql -l
|
||||
[exit 0]
|
||||
@@ -0,0 +1,4 @@
|
||||
docker network create tester-local
|
||||
docker network create testhost
|
||||
docker compose -f @HOME@/.local/share/myos/stack/postgres/postgres.yml -f @HOME@/.local/share/myos/stack/postgres/postgres.local.yml -f @MYOS@/share/compose/networks.yml -p tester-postgres-local --project-directory @HOME@/.local/share/myos/stack/postgres up -d --scale postgres=3
|
||||
[exit 0]
|
||||
@@ -1,26 +1,2 @@
|
||||
ERROR: unknown command: exec
|
||||
Usage: myos [options] <command> [stack...] [VAR=value...] [-- args...]
|
||||
|
||||
Options:
|
||||
-C DIR work in DIR instead of the current directory
|
||||
-e ENV environment (default: local, or ENV from the config)
|
||||
-H HOSTS run on remote hosts instead (comma separated, or "all")
|
||||
-n, --dry-run print the commands instead of running them
|
||||
-v, --verbose show what myos does
|
||||
-d, --debug show every command
|
||||
-h, --help this help
|
||||
|
||||
Commands:
|
||||
up down start stop restart recreate manage the containers of a stack
|
||||
ps logs config exec run inspect and enter them
|
||||
ls [--groups] list the stacks myos can see
|
||||
env [VAR...] show resolved variables
|
||||
doctor check the installation
|
||||
version print the myos version
|
||||
|
||||
Stacks:
|
||||
myos up the stack of the current directory
|
||||
myos up host a group, expanded from host=... in a .env or .mk
|
||||
myos up host/fabio a single stack
|
||||
myos up postgres:9.6 a versioned stack
|
||||
[exit 2]
|
||||
docker compose -f @WD@/stack/host/consul.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host exec consul
|
||||
[exit 0]
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
ERROR: no stack given, and no compose file in @WD@
|
||||
ERROR: unknown command: host
|
||||
ERROR: to act on a stack of that name, say what to do: myos up host
|
||||
[exit 2]
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
make -o docker-stack-build MAKE_OLDFILE=docker-stack-build ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-build STACK=myos APP_NAME=myos
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local build --build-arg APP=myos --build-arg BRANCH=@BRANCH@ --build-arg COMPOSE_VERSION=2.24.4 --build-arg DOCKER_MACHINE=x86_64 --build-arg DOCKER_REPOSITORY=tester/myos/local --build-arg DOCKER_SYSTEM=Linux --build-arg GIT_AUTHOR_EMAIL=tester@example.test --build-arg GIT_AUTHOR_NAME=tester --build-arg SSH_REMOTE_HOSTS=github.com gitlab.com --build-arg USER=tester --build-arg VERSION=@VERSION@ --build-arg SSH_AUTHORIZED_KEYS=https://github.com/tester.keys --build-arg SSH_BASTION_USERNAME=tester --build-arg SSH_PORT=22 --build-arg SSH_PUBLIC_HOSTS= github.com gitlab.com --build-arg SSH_USER=tester --build-arg GID=20 --build-arg UID=502
|
||||
make -o docker-stack-up MAKE_OLDFILE=docker-stack-up ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-up STACK=myos APP_NAME=myos
|
||||
sh -c docker network create tester-local >/dev/null 2>&1
|
||||
sh -c docker network create testhost >/dev/null 2>&1
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local up -d
|
||||
make -o docker-stack-logs MAKE_OLDFILE=docker-stack-logs ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-logs STACK=myos APP_NAME=myos
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local logs --follow --tail=100
|
||||
WARNING: myos[0] host/fabio-rule-exists: target host/fabio unavailable in app myos
|
||||
[exit 0]
|
||||
@@ -0,0 +1,3 @@
|
||||
COMPOSE_PROJECT_NAME testhost
|
||||
APP myos
|
||||
[exit 0]
|
||||
@@ -0,0 +1,20 @@
|
||||
make -o docker-stack-up MAKE_OLDFILE=docker-stack-up ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-up STACK=myos APP_NAME=myos
|
||||
sh -c docker network create tester-local >/dev/null 2>&1
|
||||
sh -c docker network create testhost >/dev/null 2>&1
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local up -d
|
||||
make -o docker-stack-ps MAKE_OLDFILE=docker-stack-ps ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-ps STACK=myos APP_NAME=myos
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local ps
|
||||
make -o host MAKE_OLDFILE=host ENV=local DOCKER_COMPOSE=docker --log-level=error compose up STACK=host/consul host/fabio host/registrator
|
||||
make -o host MAKE_OLDFILE=host ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-up STACK=host/consul APP_NAME=host
|
||||
sh -c docker network create tester-local >/dev/null 2>&1
|
||||
sh -c docker network create testhost >/dev/null 2>&1
|
||||
docker --log-level=error compose --ansi=auto -f @WD@/stack/host/consul.yml -f @MYOS@/share/compose/networks.yml -p testhost up -d
|
||||
make -o host MAKE_OLDFILE=host ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-up STACK=host/fabio APP_NAME=host
|
||||
sh -c docker network create tester-local >/dev/null 2>&1
|
||||
sh -c docker network create testhost >/dev/null 2>&1
|
||||
docker --log-level=error compose --ansi=auto -f @WD@/stack/host/fabio.yml -f @MYOS@/share/compose/networks.yml -p testhost up -d
|
||||
make -o host MAKE_OLDFILE=host ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-up STACK=host/registrator APP_NAME=host
|
||||
sh -c docker network create tester-local >/dev/null 2>&1
|
||||
sh -c docker network create testhost >/dev/null 2>&1
|
||||
docker --log-level=error compose --ansi=auto -f @WD@/stack/host/registrator.yml -f @MYOS@/share/compose/networks.yml -p testhost up -d
|
||||
[exit 0]
|
||||
@@ -0,0 +1,3 @@
|
||||
WARNING: myos[0] env-rule-exists: target env unavailable in app myos
|
||||
WARNING: myos[0] DOMAIN-rule-exists: target DOMAIN unavailable in app myos
|
||||
[exit 0]
|
||||
@@ -0,0 +1,5 @@
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local exec -T sh -c host/consul consul members
|
||||
/bin/bash: -c: line 0: syntax error near unexpected token `||'
|
||||
/bin/bash: -c: line 0: `|| true'
|
||||
make: *** [exec] Error 2
|
||||
[exit 2]
|
||||
@@ -0,0 +1,5 @@
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local exec -T fabio sh -c host/fabio sh
|
||||
/bin/bash: -c: line 0: syntax error near unexpected token `||'
|
||||
/bin/bash: -c: line 0: `|| true'
|
||||
make: *** [exec] Error 2
|
||||
[exit 2]
|
||||
@@ -0,0 +1,46 @@
|
||||
make: unrecognized option `--groups'
|
||||
Usage: make [options] [target] ...
|
||||
Options:
|
||||
-b, -m Ignored for compatibility.
|
||||
-B, --always-make Unconditionally make all targets.
|
||||
-C DIRECTORY, --directory=DIRECTORY
|
||||
Change to DIRECTORY before doing anything.
|
||||
-d Print lots of debugging information.
|
||||
--debug[=FLAGS] Print various types of debugging information.
|
||||
-e, --environment-overrides
|
||||
Environment variables override makefiles.
|
||||
-f FILE, --file=FILE, --makefile=FILE
|
||||
Read FILE as a makefile.
|
||||
-h, --help Print this message and exit.
|
||||
-i, --ignore-errors Ignore errors from commands.
|
||||
-I DIRECTORY, --include-dir=DIRECTORY
|
||||
Search DIRECTORY for included makefiles.
|
||||
-j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg.
|
||||
-k, --keep-going Keep going when some targets can't be made.
|
||||
-l [N], --load-average[=N], --max-load[=N]
|
||||
Don't start multiple jobs unless load is below N.
|
||||
-L, --check-symlink-times Use the latest mtime between symlinks and target.
|
||||
-n, --just-print, --dry-run, --recon
|
||||
Don't actually run any commands; just print them.
|
||||
-o FILE, --old-file=FILE, --assume-old=FILE
|
||||
Consider FILE to be very old and don't remake it.
|
||||
-p, --print-data-base Print make's internal database.
|
||||
-q, --question Run no commands; exit status says if up to date.
|
||||
-r, --no-builtin-rules Disable the built-in implicit rules.
|
||||
-R, --no-builtin-variables Disable the built-in variable settings.
|
||||
-s, --silent, --quiet Don't echo commands.
|
||||
-S, --no-keep-going, --stop
|
||||
Turns off -k.
|
||||
-t, --touch Touch targets instead of remaking them.
|
||||
-v, --version Print the version number of make and exit.
|
||||
-w, --print-directory Print the current directory.
|
||||
--no-print-directory Turn off -w, even if it was turned on implicitly.
|
||||
-W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE
|
||||
Consider FILE to be infinitely new.
|
||||
--warn-undefined-variables Warn when an undefined variable is referenced.
|
||||
-N OPTION, --NeXT-option=OPTION
|
||||
Turn on value of NeXT OPTION.
|
||||
|
||||
This program built for i386-apple-darwin11.3.0
|
||||
Report bugs to <bug-make@gnu.org>
|
||||
[exit 2]
|
||||
@@ -0,0 +1,2 @@
|
||||
WARNING: myos[0] ls-rule-exists: target ls unavailable in app myos
|
||||
[exit 0]
|
||||
@@ -0,0 +1,2 @@
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local run --rm postgres psql -l
|
||||
[exit 0]
|
||||
@@ -0,0 +1,4 @@
|
||||
make -o docker-stack-scale MAKE_OLDFILE=docker-stack-scale ENV=local DOCKER_COMPOSE=docker --log-level=error compose docker-compose-scale STACK=myos APP_NAME=myos
|
||||
docker --log-level=error compose --ansi=auto -f @MYOS@/share/compose/networks.yml -p tester-myos-local up -d --scale postgres=3
|
||||
WARNING: myos[0] postgres-rule-exists: target postgres unavailable in app myos
|
||||
[exit 0]
|
||||
@@ -0,0 +1,2 @@
|
||||
docker compose -f @WD@/stack/host/consul.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host config
|
||||
[exit 0]
|
||||
@@ -0,0 +1,2 @@
|
||||
COMPOSE_PROJECT_NAME testhost
|
||||
[exit 0]
|
||||
@@ -0,0 +1,2 @@
|
||||
would renew the certificates of host/consul host/fabio
|
||||
[exit 0]
|
||||
@@ -0,0 +1,4 @@
|
||||
docker network create tester-local
|
||||
docker network create testhost
|
||||
docker compose -f @WD@/stack/host/consul.yml -f @WD@/stack/host/fabio.yml -f @MYOS@/share/compose/networks.yml -p testhost --project-directory @WD@/stack/host up -d
|
||||
[exit 0]
|
||||
@@ -1,6 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Mock docker for myos tests: logs every call, answers a few read-only queries deterministically.
|
||||
[ -n "${MYOS_DOCKER_LOG:-}" ] && printf 'docker %s\n' "$*" >> "$MYOS_DOCKER_LOG"
|
||||
if [ -n "${MYOS_DOCKER_LOG:-}" ]; then
|
||||
printf 'docker %s\n' "$*" >> "$MYOS_DOCKER_LOG"
|
||||
# the environment is recorded too, so a test can assert what was exported
|
||||
env > "$(dirname "$MYOS_DOCKER_LOG")/env.log"
|
||||
fi
|
||||
case "$1 $2" in
|
||||
"compose version") echo "${MOCK_COMPOSE_VERSION:-2.29.0}" ;;
|
||||
"compose config") echo "# mock compose config" ;;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/sh
|
||||
[ -n "${MYOS_DOCKER_LOG:-}" ] && printf 'docker-compose %s\n' "$*" >> "$MYOS_DOCKER_LOG"
|
||||
if [ -n "${MYOS_DOCKER_LOG:-}" ]; then
|
||||
printf 'docker-compose %s\n' "$*" >> "$MYOS_DOCKER_LOG"
|
||||
# the environment is recorded too, so a test can assert what was exported
|
||||
env > "$(dirname "$MYOS_DOCKER_LOG")/env.log"
|
||||
fi
|
||||
case "$1" in version) echo "${MOCK_COMPOSE_VERSION:-2.29.0}" ;; config) echo "# mock compose config" ;; *) : ;; esac
|
||||
exit 0
|
||||
|
||||
+5
-2
@@ -39,6 +39,8 @@ myos_normalize() {
|
||||
-e 's/^APPS .*/APPS @APPS@/' \
|
||||
-e 's/^BRANCH .*/BRANCH @BRANCH@/' \
|
||||
-e 's/^VERSION .*/VERSION @VERSION@/' \
|
||||
-e 's/--build-arg VERSION=[^ ]*/--build-arg VERSION=@VERSION@/' \
|
||||
-e 's/--build-arg BRANCH=[^ ]*/--build-arg BRANCH=@BRANCH@/' \
|
||||
-e 's/[[:space:]][[:space:]]*/ /g' \
|
||||
-e 's/[[:space:]]*$//'
|
||||
}
|
||||
@@ -47,8 +49,9 @@ myos_normalize() {
|
||||
# shellcheck disable=SC2046 # myos_hermetic_env output is meant to be word-split
|
||||
myos_run_engine() {
|
||||
_engine=$1; _sb=$2; shift 2
|
||||
# "@make" as first arg = include mode: the project Makefile includes make/include.mk
|
||||
# and make runs from the project dir (CURDIR = project). Same for both engines.
|
||||
# "@make" as first arg = the project drives make itself: its Makefile includes
|
||||
# either the legacy engine (make/include.mk) or the shim (make/shim.mk), and
|
||||
# make runs from the project directory.
|
||||
if [ "${1:-}" = "@make" ]; then
|
||||
shift
|
||||
_out=$(cd "$_sb/wd" && env -i $(myos_hermetic_env "$_sb") MYOS_CONF=/dev/null \
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#shellcheck shell=sh
|
||||
Include lib/str.sh
|
||||
Include lib/core.sh
|
||||
Include lib/var.sh
|
||||
Include lib/tags.sh
|
||||
Include lib/config.sh
|
||||
Include lib/compose.sh
|
||||
|
||||
# These assertions look at what is actually executed, not at what --dry-run
|
||||
# prints: the two used to disagree, because the execution path set IFS to a
|
||||
# newline and so never split "-f a -f b", nor the two words "docker compose".
|
||||
Describe 'lib/compose.sh execution'
|
||||
setup() {
|
||||
MYOS_TMP=$(mktemp -d "${TMPDIR:-/tmp}/myos-compose.XXXXXX")
|
||||
MYOS_TMP=$(cd "$MYOS_TMP" && pwd -P)
|
||||
printf 'services:\n a:\n image: ${IMAGE}\n' > "$MYOS_TMP/a.yml"
|
||||
printf 'services:\n b:\n image: alpine\n' > "$MYOS_TMP/b.yml"
|
||||
# exported, otherwise the mock (a child process) never sees it
|
||||
export MYOS_DOCKER_LOG=$MYOS_TMP/docker.log
|
||||
export PATH=$SPEC_DIR/support/bin:$PATH
|
||||
DRYRUN=false
|
||||
IMAGE=alpine:3.20
|
||||
unset MYOS_COMPOSE_BIN 2>/dev/null || true
|
||||
}
|
||||
cleanup() { rm -rf "$MYOS_TMP"; }
|
||||
BeforeEach setup
|
||||
AfterEach cleanup
|
||||
|
||||
logged() { cat "$MYOS_DOCKER_LOG"; }
|
||||
|
||||
two_files() { myos_compose demo "$(printf '%s\n%s' "$MYOS_TMP/a.yml" "$MYOS_TMP/b.yml")" -- config; }
|
||||
|
||||
It 'passes every -f as its own argument'
|
||||
When call two_files
|
||||
The status should be success
|
||||
The result of function logged should include "-f $MYOS_TMP/a.yml -f $MYOS_TMP/b.yml"
|
||||
The result of function logged should include "-p demo"
|
||||
The result of function logged should include "--project-directory $MYOS_TMP"
|
||||
The result of function logged should end with "config"
|
||||
End
|
||||
|
||||
It 'splits the two words of the docker compose plugin'
|
||||
MYOS_COMPOSE_BIN="docker compose"
|
||||
When call myos_compose demo "$MYOS_TMP/a.yml" -- config
|
||||
The status should be success
|
||||
The result of function logged should start with "docker compose --ansi=auto"
|
||||
End
|
||||
|
||||
It 'runs the docker-compose binary as one word'
|
||||
MYOS_COMPOSE_BIN="docker-compose"
|
||||
When call myos_compose demo "$MYOS_TMP/a.yml" -- config
|
||||
The status should be success
|
||||
The result of function logged should start with "docker-compose --ansi=auto"
|
||||
End
|
||||
|
||||
It 'passes the variables the compose files reference'
|
||||
MYOS_COMPOSE_BIN="docker-compose"
|
||||
When call myos_compose demo "$MYOS_TMP/a.yml" -- config
|
||||
The status should be success
|
||||
The contents of file "$MYOS_TMP/env.log" should include "IMAGE=alpine:3.20"
|
||||
End
|
||||
|
||||
It 'keeps a value that contains spaces in one piece'
|
||||
MYOS_COMPOSE_BIN="docker-compose"
|
||||
IMAGE="alpine with spaces"
|
||||
When call myos_compose demo "$MYOS_TMP/a.yml" -- config
|
||||
The status should be success
|
||||
The contents of file "$MYOS_TMP/env.log" should include "IMAGE=alpine with spaces"
|
||||
End
|
||||
|
||||
It 'refuses to run without a compose file'
|
||||
When call myos_compose demo "" -- config
|
||||
The status should equal 3
|
||||
The stderr should include "no compose file"
|
||||
End
|
||||
End
|
||||
@@ -1,6 +1,7 @@
|
||||
#shellcheck shell=sh
|
||||
Include lib/str.sh
|
||||
Include lib/core.sh
|
||||
Include lib/var.sh
|
||||
Include lib/tags.sh
|
||||
Include lib/config.sh
|
||||
|
||||
@@ -104,3 +105,95 @@ Describe 'lib/config.sh robustness'
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
# The make engine generated a .env out of a .env.dist, expanding ${VAR} against
|
||||
# the current values and running $(cmd). These check the shell equivalent.
|
||||
Describe 'lib/config.sh templates'
|
||||
setup() { MYOS_TMP=$(mktemp -d "${TMPDIR:-/tmp}/myos-tpl.XXXXXX"); }
|
||||
cleanup() { rm -rf "$MYOS_TMP"; }
|
||||
BeforeEach setup
|
||||
AfterEach cleanup
|
||||
|
||||
Describe 'myos_expand'
|
||||
It 'substitutes a variable'
|
||||
DOMAIN=example.org
|
||||
When call myos_expand 'https://app.${DOMAIN}/'
|
||||
The output should equal "https://app.example.org/"
|
||||
End
|
||||
It 'substitutes several, including twice the same'
|
||||
DOMAIN=example.org
|
||||
When call myos_expand '${DOMAIN}:${DOMAIN}'
|
||||
The output should equal "example.org:example.org"
|
||||
End
|
||||
It 'substitutes a lazy default like any other value'
|
||||
# shellcheck disable=SC2317
|
||||
myos_default_LAZY_DOMAIN() { printf 'lazy.example.org'; }
|
||||
When call myos_expand 'https://${LAZY_DOMAIN}/'
|
||||
The output should equal "https://lazy.example.org/"
|
||||
End
|
||||
It 'empties an unknown variable, as make does'
|
||||
When call myos_expand 'a${NOT_SET_ANYWHERE}b'
|
||||
The output should equal "ab"
|
||||
End
|
||||
It 'runs a command substitution'
|
||||
When call myos_expand 'pre-$(echo mid)-post'
|
||||
The output should equal "pre-mid-post"
|
||||
End
|
||||
It 'leaves a malformed reference alone'
|
||||
When call myos_expand 'a${not-a-name}b'
|
||||
The output should equal 'a${not-a-name}b'
|
||||
End
|
||||
End
|
||||
|
||||
Describe 'myos_env_update'
|
||||
It 'adds the missing variables, expanded'
|
||||
printf 'DOMAIN=example.org\nAPP_URL=https://app.${DOMAIN}/\n' > "$MYOS_TMP/.env.dist"
|
||||
DOMAIN=chosen.org
|
||||
When call myos_env_update "$MYOS_TMP/.env" "$MYOS_TMP/.env.dist"
|
||||
The status should be success
|
||||
The contents of file "$MYOS_TMP/.env" should include "APP_URL=https://app.chosen.org/"
|
||||
The contents of file "$MYOS_TMP/.env" should include "DOMAIN=chosen.org"
|
||||
End
|
||||
It 'never touches a value already recorded'
|
||||
printf 'KEEP=default\n' > "$MYOS_TMP/.env.dist"
|
||||
printf 'KEEP=already-chosen\n' > "$MYOS_TMP/.env"
|
||||
When call myos_env_update "$MYOS_TMP/.env" "$MYOS_TMP/.env.dist"
|
||||
The contents of file "$MYOS_TMP/.env" should equal "KEEP=already-chosen"
|
||||
End
|
||||
It 'is idempotent'
|
||||
printf 'A=1\nB=${A}2\n' > "$MYOS_TMP/.env.dist"
|
||||
When run source spec/unit/config_update_helper.sh "$MYOS_TMP"
|
||||
The output should equal "2"
|
||||
End
|
||||
It 'does nothing without a template'
|
||||
When call myos_env_update "$MYOS_TMP/.env" "$MYOS_TMP/nope.dist"
|
||||
The status should be success
|
||||
The path "$MYOS_TMP/.env" should not be exist
|
||||
End
|
||||
End
|
||||
End
|
||||
|
||||
Describe 'lib/config.sh forward references'
|
||||
setup() { MYOS_TMP=$(mktemp -d "${TMPDIR:-/tmp}/myos-fwd.XXXXXX"); }
|
||||
cleanup() { rm -rf "$MYOS_TMP"; }
|
||||
BeforeEach setup
|
||||
AfterEach cleanup
|
||||
|
||||
It 'resolves a reference to a variable defined further down the template'
|
||||
printf 'IMAGE=alpine:${VERSION}\nVERSION=3.20\n' > "$MYOS_TMP/.env.dist"
|
||||
When call myos_env_update "$MYOS_TMP/.env" "$MYOS_TMP/.env.dist"
|
||||
The contents of file "$MYOS_TMP/.env" should include "IMAGE=alpine:3.20"
|
||||
End
|
||||
It 'resolves a chain of references'
|
||||
printf 'A=${B}\nB=${C}\nC=deep\n' > "$MYOS_TMP/.env.dist"
|
||||
When call myos_env_update "$MYOS_TMP/.env" "$MYOS_TMP/.env.dist"
|
||||
The contents of file "$MYOS_TMP/.env" should include "A=deep"
|
||||
End
|
||||
It 'still lets an explicit choice win over the template'
|
||||
printf 'IMAGE=alpine:${VERSION}\nVERSION=3.20\n' > "$MYOS_TMP/.env.dist"
|
||||
VERSION=3.19
|
||||
When call myos_env_update "$MYOS_TMP/.env" "$MYOS_TMP/.env.dist"
|
||||
The contents of file "$MYOS_TMP/.env" should include "IMAGE=alpine:3.19"
|
||||
The contents of file "$MYOS_TMP/.env" should include "VERSION=3.19"
|
||||
End
|
||||
End
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#shellcheck shell=sh
|
||||
# Running the update twice must leave the same file: print how many lines it has.
|
||||
myos_env_update "$1/.env" "$1/.env.dist" >/dev/null 2>&1
|
||||
myos_env_update "$1/.env" "$1/.env.dist" >/dev/null 2>&1
|
||||
wc -l < "$1/.env" | tr -d ' '
|
||||
@@ -0,0 +1,5 @@
|
||||
#shellcheck shell=sh
|
||||
# Helper: hooks set variables in the caller's scope, which a subshell loses.
|
||||
[ -n "${4:-}" ] && eval "$3=\$4"
|
||||
myos_stack_hooks "$1" "$2"
|
||||
eval "printf '%s\n' \"\${$3:-}\""
|
||||
@@ -0,0 +1,85 @@
|
||||
#shellcheck shell=sh
|
||||
Include lib/str.sh
|
||||
Include lib/core.sh
|
||||
Include lib/var.sh
|
||||
Include lib/tags.sh
|
||||
Include lib/naming.sh
|
||||
Include lib/config.sh
|
||||
Include lib/stack.sh
|
||||
Include lib/hooks.sh
|
||||
|
||||
# A stack ships its computed settings as a shell hook, so the catalogue works
|
||||
# on a machine without make. These check the hook and the uri it builds on.
|
||||
Describe 'lib/hooks.sh'
|
||||
setup() {
|
||||
MYOS_TMP=$(mktemp -d "${TMPDIR:-/tmp}/myos-hook.XXXXXX")
|
||||
MYOS_TMP=$(cd "$MYOS_TMP" && pwd -P)
|
||||
ENV=local; DOMAIN=example.org; USER=tester; HOSTNAME=testhost
|
||||
MYOS_PATH=$MYOS_TMP
|
||||
APP_HOST=demo.example.org; APP_URI=demo.example.org/
|
||||
}
|
||||
cleanup() { rm -rf "$MYOS_TMP"; }
|
||||
BeforeEach setup
|
||||
AfterEach cleanup
|
||||
|
||||
It 'loads plain values from a .env hook'
|
||||
printf 'DEMO_VERSION=1.2.3\n' > "$MYOS_TMP/demo.env"
|
||||
When run source spec/unit/hooks_helper.sh "$MYOS_TMP" demo DEMO_VERSION
|
||||
The output should equal "1.2.3"
|
||||
End
|
||||
|
||||
It 'loads a computed value from a .sh hook'
|
||||
printf 'DEMO_TAGS=$(myos_tagprefix demo 8000)\n' > "$MYOS_TMP/demo.sh"
|
||||
When run source spec/unit/hooks_helper.sh "$MYOS_TMP" demo DEMO_TAGS
|
||||
The output should equal "urlprefix-demo.demo.example.org/*"
|
||||
End
|
||||
|
||||
It 'lets a hook name the service it publishes'
|
||||
printf 'DEMO_SERVICE_8000_NAME=www\nDEMO_TAGS=$(myos_tagprefix demo 8000)\n' > "$MYOS_TMP/demo.sh"
|
||||
When run source spec/unit/hooks_helper.sh "$MYOS_TMP" demo DEMO_TAGS
|
||||
The output should equal "urlprefix-www.demo.example.org/*"
|
||||
End
|
||||
|
||||
It 'lets the environment win over a hook default'
|
||||
printf 'DEMO_VERSION=${DEMO_VERSION:-1.2.3}\n' > "$MYOS_TMP/demo.sh"
|
||||
When run source spec/unit/hooks_helper.sh "$MYOS_TMP" demo DEMO_VERSION 9.9.9
|
||||
The output should equal "9.9.9"
|
||||
End
|
||||
|
||||
It 'is quiet when a stack has no hook'
|
||||
When call myos_stack_hooks "$MYOS_TMP" nothing
|
||||
The status should be success
|
||||
The output should equal ""
|
||||
End
|
||||
End
|
||||
|
||||
Describe 'lib/naming.sh service uris'
|
||||
BeforeEach 'ENV=local; unset APP_HOST_MULTI_APP APP_HOST_MULTI_USER APP_HOST_MULTI_ENV HOST_LB 2>/dev/null || true'
|
||||
|
||||
It 'serves a host stack on <hostname>.<domain>'
|
||||
When call myos_app_host host aya local fabio example.org sonic
|
||||
The output should equal "sonic.example.org"
|
||||
End
|
||||
It 'adds the bare domain when the host is the load balancer'
|
||||
HOST_LB=true
|
||||
When call myos_app_host host aya local fabio example.org sonic
|
||||
The output should equal "sonic.example.org example.org"
|
||||
End
|
||||
It 'does not prefix a main environment'
|
||||
When call myos_app_host app aya master duniter example.org sonic
|
||||
The output should equal "example.org"
|
||||
End
|
||||
It 'prefixes any other environment'
|
||||
When call myos_app_host app aya staging duniter example.org sonic
|
||||
The output should equal "staging.example.org"
|
||||
End
|
||||
It 'prefixes the user when asked to'
|
||||
APP_HOST_MULTI_USER=true
|
||||
When call myos_app_host app aya master duniter example.org sonic
|
||||
The output should equal "aya.example.org"
|
||||
End
|
||||
It 'builds a uri that ends with a slash'
|
||||
When call myos_app_uri "a.example.org b.example.org"
|
||||
The output should equal "a.example.org/ b.example.org/"
|
||||
End
|
||||
End
|
||||
@@ -1,6 +1,7 @@
|
||||
#shellcheck shell=sh
|
||||
Include lib/str.sh
|
||||
Include lib/core.sh
|
||||
Include lib/var.sh
|
||||
Include lib/tags.sh
|
||||
Include lib/stack.sh
|
||||
|
||||
@@ -168,3 +169,25 @@ Describe 'lib/stack.sh group safety'
|
||||
The lines of output should equal 2
|
||||
End
|
||||
End
|
||||
|
||||
Describe 'lib/stack.sh installation prefix'
|
||||
setup() {
|
||||
MYOS_TMP=$(mktemp -d "${TMPDIR:-/tmp}/myos-prefix.XXXXXX")
|
||||
# myos resolves stack paths physically, so compare against the physical path
|
||||
MYOS_TMP=$(cd "$MYOS_TMP" && pwd -P)
|
||||
mkdir -p "$MYOS_TMP/lib/myos" "$MYOS_TMP/share/myos/stack/demo"
|
||||
: > "$MYOS_TMP/share/myos/stack/demo/demo.yml"
|
||||
MYOS_ROOT=$MYOS_TMP/lib/myos
|
||||
WORKDIR=$MYOS_TMP
|
||||
HOME=$MYOS_TMP/nohome
|
||||
MYOS_PATH=
|
||||
}
|
||||
cleanup() { rm -rf "$MYOS_TMP"; }
|
||||
BeforeEach setup
|
||||
AfterEach cleanup
|
||||
|
||||
It 'finds the catalogue installed beside the framework'
|
||||
When call myos_stack_resolve demo
|
||||
The output should equal "$MYOS_TMP/share/myos/stack/demo"
|
||||
End
|
||||
End
|
||||
|
||||
@@ -68,3 +68,55 @@ Describe 'lib/str.sh'
|
||||
End
|
||||
End
|
||||
End
|
||||
|
||||
# The make list functions the catalogue relies on, ported so that a stack hook
|
||||
# reads like the .mk it replaces.
|
||||
Describe 'lib/str.sh list functions'
|
||||
It 'takes the first and last word'
|
||||
When call myos_firstword "a b c"
|
||||
The output should equal "a"
|
||||
End
|
||||
It 'takes the last word'
|
||||
When call myos_lastword "a b c"
|
||||
The output should equal "c"
|
||||
End
|
||||
It 'returns the first argument that is not empty'
|
||||
When call myos_or "" "" "third"
|
||||
The output should equal "third"
|
||||
End
|
||||
Describe 'myos_patsubst'
|
||||
Parameters
|
||||
"%" "pre%" "a b" "prea preb"
|
||||
"%.yml" "%.yaml" "a.yml b.md" "a.yaml b.md"
|
||||
"%" "%/" "x y" "x/ y/"
|
||||
End
|
||||
It "substitutes $1 -> $2 in $3"
|
||||
When call myos_patsubst "$1" "$2" "$3"
|
||||
The output should equal "$4"
|
||||
End
|
||||
End
|
||||
It 'filters a list'
|
||||
When call myos_filter "a%" "abc bcd axe"
|
||||
The output should equal "abc axe"
|
||||
End
|
||||
It 'filters a list out'
|
||||
When call myos_filter_out "local master main" "staging"
|
||||
The output should equal "staging"
|
||||
End
|
||||
It 'drops the words it is told to'
|
||||
When call myos_filter_out "local master main" "master"
|
||||
The output should equal ""
|
||||
End
|
||||
It 'adds a prefix and a suffix'
|
||||
When call myos_addprefix "x." "a b"
|
||||
The output should equal "x.a x.b"
|
||||
End
|
||||
End
|
||||
|
||||
Describe 'lib/str.sh myos_jwt'
|
||||
It 'signs a token whose payload decodes back'
|
||||
When call myos_jwt "" '{"role":"anon"}' secret
|
||||
The output should start with "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
|
||||
The output should include "eyJyb2xlIjoiYW5vbiJ9"
|
||||
End
|
||||
End
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#shellcheck shell=sh
|
||||
Include lib/str.sh
|
||||
Include lib/core.sh
|
||||
Include lib/var.sh
|
||||
Include lib/tags.sh
|
||||
|
||||
# The expectations below are the very examples left as comments in
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#shellcheck shell=sh
|
||||
myos_default BUILT 'printf "built-%s" here'
|
||||
myos_var BUILT
|
||||
@@ -0,0 +1,7 @@
|
||||
#shellcheck shell=sh
|
||||
# A lazy default is re-evaluated at each reference, so it follows a value that
|
||||
# changes later; an explicit value still wins over it.
|
||||
myos_default_LAZY_ONE() { printf 'from-%s' "$(myos_var LAZY_BASE)"; }
|
||||
LAZY_BASE=first; myos_var LAZY_ONE; echo
|
||||
LAZY_BASE=second; myos_var LAZY_ONE; echo
|
||||
LAZY_ONE=pinned; myos_var LAZY_ONE; echo
|
||||
@@ -0,0 +1,3 @@
|
||||
#shellcheck shell=sh
|
||||
myos_default_LOOPY() { myos_var LOOPY; }
|
||||
myos_var LOOPY
|
||||
@@ -0,0 +1,73 @@
|
||||
#shellcheck shell=sh
|
||||
Include lib/core.sh
|
||||
Include lib/var.sh
|
||||
|
||||
Describe 'lib/var.sh'
|
||||
Describe 'myos_var'
|
||||
It 'returns the value of a plain variable'
|
||||
SOME_VAR=plain
|
||||
When call myos_var SOME_VAR
|
||||
The output should equal "plain"
|
||||
End
|
||||
It 'returns empty for an unset variable'
|
||||
When call myos_var NEVER_SET
|
||||
The output should equal ""
|
||||
End
|
||||
It 'returns empty for an empty name'
|
||||
When call myos_var ""
|
||||
The output should equal ""
|
||||
End
|
||||
It 'never runs a command that happens to share the name'
|
||||
# `host` is a real program: a lazy default must never be confused with it
|
||||
When call myos_var host
|
||||
The output should equal ""
|
||||
The status should be success
|
||||
The stderr should equal ""
|
||||
End
|
||||
End
|
||||
|
||||
Describe 'lazy defaults'
|
||||
lazy() {
|
||||
myos_default_LAZY_ONE() { printf 'from-%s' "$(myos_var LAZY_BASE)"; }
|
||||
myos_var LAZY_ONE
|
||||
}
|
||||
It 'computes the default when the variable has no value'
|
||||
LAZY_BASE=a
|
||||
When call lazy
|
||||
The output should equal "from-a"
|
||||
End
|
||||
It 'recomputes it against the current values'
|
||||
When run source spec/unit/var_lazy_helper.sh
|
||||
The line 1 should equal "from-first"
|
||||
The line 2 should equal "from-second"
|
||||
The line 3 should equal "pinned"
|
||||
End
|
||||
It 'declares a default from a string too'
|
||||
When run source spec/unit/var_default_helper.sh
|
||||
The output should equal "built-here"
|
||||
End
|
||||
It 'refuses a default written in terms of itself'
|
||||
When run source spec/unit/var_loop_helper.sh
|
||||
The stderr should include "defined in terms of itself"
|
||||
The status should equal 1
|
||||
End
|
||||
End
|
||||
|
||||
Describe 'myos_var_is_lazy'
|
||||
It 'is true for a variable that only has a default'
|
||||
myos_default_ONLY_LAZY() { echo x; }
|
||||
When call myos_var_is_lazy ONLY_LAZY
|
||||
The status should be success
|
||||
End
|
||||
It 'is false once the variable has a value'
|
||||
myos_default_BOTH() { echo x; }
|
||||
BOTH=explicit
|
||||
When call myos_var_is_lazy BOTH
|
||||
The status should be failure
|
||||
End
|
||||
It 'is false for a program on PATH'
|
||||
When call myos_var_is_lazy host
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
Reference in New Issue
Block a user