From f541ca418b9e83a3128c8a49cec442f04ed70790 Mon Sep 17 00:00:00 2001 From: Yann Autissier Date: Thu, 3 Sep 2026 20:22:52 +0200 Subject: [PATCH] let a stack compute its settings without make A stack can now ship .env and .sh next to its compose files. The hook is sourced with the tag helpers available, which is what the computing .mk files of the catalogue were using make for: 29 of its 44 .mk files only exist to build variables like the fabio tags. Converting stack/host/fabio.mk by hand gives byte-identical output for the route tag, and drops a trailing comma the make version left in the listener list. Also: APP_HOST and APP_URI are computed (the tag helpers build on them), --color controls the escape codes rather than always emitting them, and make test-portability runs the CLI under busybox ash and dash. --- CHANGELOG.md | 4 + Makefile | 13 ++- bin/myos | 21 ++++- lib/cmd/env.sh | 51 +++++++----- lib/core.sh | 14 +++- lib/hooks.sh | 28 +++++++ lib/naming.sh | 41 ++++++++++ lib/tags.sh | 14 ++++ skills/myos/references/authoring.md | 2 + skills/myos/references/conventions.md | 27 ++++++ .../expected.cli/cat-unknown-target.txt | 1 + spec/unit/hooks_helper.sh | 5 ++ spec/unit/hooks_spec.sh | 82 +++++++++++++++++++ 13 files changed, 281 insertions(+), 22 deletions(-) create mode 100644 lib/hooks.sh create mode 100644 spec/unit/hooks_helper.sh create mode 100644 spec/unit/hooks_spec.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index b00711b..2b2f6c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ - `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 `.env` and `.sh` hooks, so the + catalogue no longer needs make to be installed +- `--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 diff --git a/Makefile b/Makefile index 7f095a3..b00e326 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/bin/myos b/bin/myos index 431285d..c2b8c35 100755 --- a/bin/myos +++ b/bin/myos @@ -19,7 +19,7 @@ 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 tags naming stack config compose hooks; do # shellcheck source=/dev/null . "$MYOS_ROOT/lib/$_m.sh" done @@ -34,6 +34,7 @@ done MYOS_VARS= MYOS_ARGS= MYOS_HOSTS= + MYOS_COLOR=${MYOS_COLOR:-auto} VERBOSE=${VERBOSE:-} DEBUG=${DEBUG:-} } @@ -47,6 +48,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 @@ -73,6 +75,8 @@ 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 ;; @@ -163,6 +167,21 @@ fi # shellcheck disable=SC2086 # a list of references MYOS_STACKS=$(myos_group_expand $MYOS_REFS) +# 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 + _hdir=$(myos_stack_resolve "$_ref" 2>/dev/null) || continue + myos_stack_hooks "$_hdir" "$(myos_stack_name "$_ref")" +done + # 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() { diff --git a/lib/cmd/env.sh b/lib/cmd/env.sh index 9a2a4db..c9315e1 100644 --- a/lib/cmd/env.sh +++ b/lib/cmd/env.sh @@ -1,34 +1,47 @@ #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- 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 } diff --git a/lib/core.sh b/lib/core.sh index 1c29223..4e38a34 100644 --- a/lib/core.sh +++ b/lib/core.sh @@ -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 } diff --git a/lib/hooks.sh b/lib/hooks.sh new file mode 100644 index 0000000..16d0603 --- /dev/null +++ b/lib/hooks.sh @@ -0,0 +1,28 @@ +#shellcheck shell=sh +# hooks: the per-stack settings that used to live in a .mk file. +# +# A stack may ship, next to its compose files: +# .env dotenv, for plain values +# .env. the same, for one environment +# .sh shell, for values that have to be computed (fabio tags, JWTs) +# .mk the legacy make snippet, still read for its groups +# +# A .sh hook runs with the myos helpers available (myos_tagprefix, myos_uri, +# myos_var) and sets variables directly. This is what lets a stack of the +# catalogue work on a machine that has no make. + +# myos_stack_hooks DIR NAME load the hooks of one stack, most specific last +myos_stack_hooks() { + _hdir=$1; _hname=$2 + for _h in "$_hdir/$_hname.env" "$_hdir/$_hname.env.$ENV"; do + [ -f "$_h" ] && myos_dotenv_load "$_h" + done + for _h in "$_hdir/$_hname.sh" "$_hdir/$_hname.$ENV.sh"; do + if [ -f "$_h" ]; then + myos_debug "hook $_h" + # shellcheck source=/dev/null + . "$_h" + fi + done + return 0 +} diff --git a/lib/naming.sh b/lib/naming.sh index 0155520..e8bb73d 100644 --- a/lib/naming.sh +++ b/lib/naming.sh @@ -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 .; 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" +} diff --git a/lib/tags.sh b/lib/tags.sh index ae7310c..714d146 100644 --- a/lib/tags.sh +++ b/lib/tags.sh @@ -71,3 +71,17 @@ myos_tagprefix() { [ -n "$_uris" ] || _uris=$(myos_uri "$_stack" "$_port") myos_urlprefix "$_path" "$_opts" "$_uris" } + +# myos_servicenvs STACK GROUP KEY +# Collect _SERVICE__ for every env listed in +# _SERVICE__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" +} diff --git a/skills/myos/references/authoring.md b/skills/myos/references/authoring.md index ffe04a2..b9bdfc8 100644 --- a/skills/myos/references/authoring.md +++ b/skills/myos/references/authoring.md @@ -6,6 +6,8 @@ stack//.yml the services stack//.local.yml what only makes sense on a workstation (published ports…) stack//.labels.yml the registrator labels, so routing stays optional +stack//.env plain settings: versions, defaults +stack//.sh computed settings (fabio tags), no make needed stack//.env.dist the variables it expects, with defaults stack//README.md what it is and what it needs ``` diff --git a/skills/myos/references/conventions.md b/skills/myos/references/conventions.md index 8a89e57..bcca7e4 100644 --- a/skills/myos/references/conventions.md +++ b/skills/myos/references/conventions.md @@ -99,6 +99,33 @@ 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. +## Per-stack settings + +A stack keeps its own settings next to its compose files: + +| file | for | +|---|---| +| `.env` | plain values: versions, defaults | +| `.env.` | the same, for one environment | +| `.sh` | values that have to be computed | +| `.mk` | the legacy make snippet; still read for its groups | + +A `.sh` hook is sourced with the myos helpers available, and sets variables +directly. This is what lets a stack work on a machine that has no make: + +```sh +# stack/host/fabio.sh +HOST_FABIO_VERSION=${HOST_FABIO_VERSION:-1.6.3} +HOST_FABIO_SERVICE_9998_NAME=${HOST_FABIO_SERVICE_9998_NAME:-fabio} +HOST_FABIO_SERVICE_9998_AUTH=${HOST_FABIO_SERVICE_9998_AUTH:-default} +HOST_FABIO_SERVICE_9998_TAGS=${HOST_FABIO_SERVICE_9998_TAGS:-$(myos_tagprefix HOST_FABIO 9998)} +``` + +Always write `${VAR:-default}` so the environment and the `.env` still win. +Helpers available in a hook: `myos_tagprefix`, `myos_urlprefix`, `myos_uri`, +`myos_url`, `myos_envprefix`, `myos_servicenvs`, `myos_var`, `myos_lower`, +`myos_upper`. + ## Groups A group is a lowercase name whose value lists stacks. It can live in a `.env`, diff --git a/spec/golden/expected.cli/cat-unknown-target.txt b/spec/golden/expected.cli/cat-unknown-target.txt index 97c37a3..08636f8 100644 --- a/spec/golden/expected.cli/cat-unknown-target.txt +++ b/spec/golden/expected.cli/cat-unknown-target.txt @@ -6,6 +6,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 diff --git a/spec/unit/hooks_helper.sh b/spec/unit/hooks_helper.sh new file mode 100644 index 0000000..712281f --- /dev/null +++ b/spec/unit/hooks_helper.sh @@ -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:-}\"" diff --git a/spec/unit/hooks_spec.sh b/spec/unit/hooks_spec.sh new file mode 100644 index 0000000..078ff65 --- /dev/null +++ b/spec/unit/hooks_spec.sh @@ -0,0 +1,82 @@ +#shellcheck shell=sh +Include lib/str.sh +Include lib/core.sh +Include lib/tags.sh +Include lib/naming.sh +Include lib/config.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 + 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 .' + 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