Merge upgrade-compat: seamless in-place upgrade from Boundary
ci / check (push) Successful in 21m11s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
shadowdaoandClaude Sonnet 5.5 committed 2026-10-12 11:14:31 -07:00
commit 764bb27d54
15 files changed
+1033 -32

No files matched your search

+1
View File
@@ -282,6 +282,7 @@ jobs:
rpm_depends: "openssl"
config_dir: ".release/linux/package/"
preinstall: ".release/linux/preinst"
postinstall: ".release/linux/postinst"
postremove: ".release/linux/postrm"
- name: Add Linux Package names to env
run: |
+10
View File
@@ -25,6 +25,16 @@ and is not suitable for actual deployment.
Please see the comments within the file for more information;
full configuration details can be found on Threshold's documentation site.
#### Upgrading from the HashiCorp Boundary image
The configuration directory moved from `/boundary` to `/threshold`. For
compatibility, `/boundary` is a symlink to `/threshold` inside the image, a
`-config /boundary/config.hcl` argument (or a config mounted at
`/boundary/config.hcl`) is still honored with a deprecation warning, `boundary`
is accepted as a command name, and `BOUNDARY_*` environment variables are
mirrored to `THRESHOLD_*`. Please migrate your mounts and arguments to the
`/threshold` and `THRESHOLD_*` names.
### Postgres
The usage instructions in this README assume you have an external postgres database (version 12 or greater) to run threshold server with.
+72
View File
@@ -22,6 +22,54 @@ for legacy_name in $(env | sed -n 's/^\(BOUNDARY_[A-Za-z0-9_]*\)=.*/\1/p'); do
fi
done
# Backwards compatibility: the legacy HashiCorp Boundary image kept its config
# in /boundary/config.hcl and ran the "boundary" binary. Accept both.
# (The *_TEST_* overrides exist only for .release/docker/test-entrypoint.sh.)
NEW_CONFIG_PATH=${THRESHOLD_ENTRYPOINT_TEST_NEW:-/threshold/config.hcl}
LEGACY_CONFIG_PATH=${THRESHOLD_ENTRYPOINT_TEST_LEGACY:-/boundary/config.hcl}
STOCK_CONFIG_COPY=${THRESHOLD_ENTRYPOINT_TEST_STOCK:-/usr/share/threshold/config.hcl.default}
legacy_warn() {
>&2 echo "WARNING: $1 (deprecated Boundary-era path); please move to /threshold/config.hcl"
}
# Prints the config path that should actually be used for a requested -config
# path. Prefers /threshold/config.hcl, falling back to /boundary/config.hcl.
resolve_config_path() {
case "$1" in
"$NEW_CONFIG_PATH")
# The image ships a stock demo config at /threshold/config.hcl. If it is
# still the stock one and the user supplied their own legacy config
# (for example by mounting it at /boundary/config.hcl on an image
# where /boundary is a real directory), use theirs.
if [ -f "$LEGACY_CONFIG_PATH" ] && ! [ "$LEGACY_CONFIG_PATH" -ef "$NEW_CONFIG_PATH" ] \
&& [ -f "$STOCK_CONFIG_COPY" ] && cmp -s "$NEW_CONFIG_PATH" "$STOCK_CONFIG_COPY"; then
legacy_warn "using $LEGACY_CONFIG_PATH"
echo "$LEGACY_CONFIG_PATH"
return
fi
;;
"$LEGACY_CONFIG_PATH")
if [ -f "$LEGACY_CONFIG_PATH" ]; then
legacy_warn "using $LEGACY_CONFIG_PATH"
elif [ -f "$NEW_CONFIG_PATH" ]; then
legacy_warn "$LEGACY_CONFIG_PATH not found, falling back to $NEW_CONFIG_PATH"
echo "$NEW_CONFIG_PATH"
return
fi
;;
esac
echo "$1"
}
# Accept the legacy command name: "docker run <image> boundary ..." (or an
# entrypoint/command override that invokes boundary) runs threshold.
if [ "$1" = 'boundary' ]; then
>&2 echo "WARNING: the 'boundary' command is deprecated; use 'threshold' (running threshold)"
shift
set -- threshold "$@"
fi
# If the user is trying to run Threshold directly with some arguments, then
# pass them to Threshold.
if [ "${1:0:1}" = '-' ]; then
@@ -38,6 +86,26 @@ elif threshold --help "$1" 2>&1 | grep -q "threshold $1"; then
set -- threshold "$@"
fi
# Rewrite -config arguments (see resolve_config_path).
if [ "$1" = 'threshold' ]; then
_n=$#
_prev=""
while [ "$_n" -gt 0 ]; do
_a=$1
shift
_n=$((_n - 1))
case "$_prev" in
-config | --config) _new=$(resolve_config_path "$_a") ;;
*) _new=$_a ;;
esac
case "$_a" in
-config=* | --config=*) _new="${_a%%=*}=$(resolve_config_path "${_a#*=}")" ;;
esac
_prev=$_a
set -- "$@" "$_new"
done
fi
# If we are running Threshold, make sure it executes as the proper user.
if [ "$1" = 'threshold' ]; then
is_root=0
@@ -51,6 +119,10 @@ if [ "$1" = 'threshold' ]; then
if [ "$(stat -c %u /threshold)" != "$(id -u threshold)" ]; then
chown -R threshold:threshold /threshold || echo "Could not chown /threshold (may not have appropriate permissions)"
fi
# Legacy config dir, when it is a real directory (not the symlink to /threshold)
if [ -d /boundary ] && [ ! -L /boundary ] && [ "$(stat -c %u /boundary)" != "$(id -u threshold)" ]; then
chown -R threshold:threshold /boundary || echo "Could not chown /boundary (may not have appropriate permissions)"
fi
else
>&2 echo "SKIP_CHOWN not set but running non-root; skipping chown /threshold"
fi
+107
View File
@@ -0,0 +1,107 @@
#!/bin/bash
# Self-contained test for the legacy-path handling in docker-entrypoint.sh.
#
# Runs the real entrypoint with a fake "threshold" binary and temporary
# config paths (via the THRESHOLD_ENTRYPOINT_TEST_* overrides). No Docker or
# root needed. The entrypoint is written for busybox/ash; bash is used here
# because it accepts the same ${var:off:len} syntax.
#
# Usage: .release/docker/test-entrypoint.sh
set -u
HERE=$(cd "$(dirname "$0")" && pwd)
ENTRY="$HERE/docker-entrypoint.sh"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
FAILS=0
PASSES=0
check() { # check DESC EXPECTED ACTUAL
if [ "$2" = "$3" ]; then
PASSES=$((PASSES + 1))
else
FAILS=$((FAILS + 1))
echo "FAIL: $1" >&2
echo " expected: $2" >&2
echo " actual: $3" >&2
fi
}
mkdir -p "$WORK/bin" "$WORK/new" "$WORK/legacy"
# Fake threshold: print the argument vector on one line, "|"-separated.
cat >"$WORK/bin/threshold" <<'EOF'
#!/bin/sh
case "$1" in --help) [ "$2" = threshold ] || echo "threshold $2"; exit 0 ;; esac
IFS='|'
echo "ARGS:$*"
EOF
cat >"$WORK/bin/su-exec" <<'EOF'
#!/bin/sh
shift
exec "$@"
EOF
chmod 755 "$WORK/bin/threshold" "$WORK/bin/su-exec"
NEWCFG="$WORK/new/config.hcl"
LEGCFG="$WORK/legacy/config.hcl"
STOCK="$WORK/stock.hcl"
run() { # run ARGS... ; prints "<stdout-args> || <stderr>"
PATH="$WORK/bin:$PATH" SKIP_CHOWN=1 SKIP_SETCAP=1 \
THRESHOLD_ENTRYPOINT_TEST_NEW="$NEWCFG" \
THRESHOLD_ENTRYPOINT_TEST_LEGACY="$LEGCFG" \
THRESHOLD_ENTRYPOINT_TEST_STOCK="$STOCK" \
bash "$ENTRY" "$@" 2>"$WORK/err" | grep '^ARGS:'
}
stderr() { cat "$WORK/err"; }
echo "stock demo config" >"$STOCK"
cp "$STOCK" "$NEWCFG"
# 1. default invocation, stock config, no legacy file -> unchanged, no warning
out=$(run server -config "$NEWCFG")
check "default path kept" "ARGS:server|-config|$NEWCFG" "$out"
check "no warning by default" "" "$(stderr | grep -c deprecated | grep -v '^0$')"
# 2. user's own /threshold config (differs from stock) wins over a legacy file
echo "user threshold config" >"$NEWCFG"
echo "user legacy config" >"$LEGCFG"
out=$(run server -config "$NEWCFG")
check "custom threshold config preferred" "ARGS:server|-config|$NEWCFG" "$out"
# 3. stock /threshold config + user legacy config -> legacy used with warning
cp "$STOCK" "$NEWCFG"
out=$(run server -config "$NEWCFG")
check "legacy config used when threshold is stock" "ARGS:server|-config|$LEGCFG" "$out"
check "deprecation warning printed" "1" "$(stderr | grep -c 'WARNING.*Boundary-era')"
# 4. explicit legacy path that exists -> used with warning
out=$(run server -config "$LEGCFG")
check "explicit legacy path kept" "ARGS:server|-config|$LEGCFG" "$out"
check "explicit legacy warns" "1" "$(stderr | grep -c 'WARNING')"
# 5. explicit legacy path that does not exist -> fall back to threshold path
rm -f "$LEGCFG"
out=$(run server -config "$LEGCFG")
check "missing legacy falls back to threshold" "ARGS:server|-config|$NEWCFG" "$out"
# 6. -config=PATH form
echo "user legacy config" >"$LEGCFG"
out=$(run server -config="$LEGCFG")
check "-config=path form" "ARGS:server|-config=$LEGCFG" "$out"
rm -f "$LEGCFG"
out=$(run server -config="$LEGCFG")
check "-config=path fallback" "ARGS:server|-config=$NEWCFG" "$out"
# 7. legacy command name
out=$(run boundary version)
check "boundary command accepted" "ARGS:version" "$out"
check "boundary command warns" "1" "$(stderr | grep -c "'boundary' command is deprecated")"
# 8. unrelated args untouched
out=$(run database init -config "$NEWCFG" -skip-auth-method-creation)
check "other args preserved" "ARGS:database|init|-config|$NEWCFG|-skip-auth-method-creation" "$out"
echo "passed: $PASSES, failed: $FAILS"
[ "$FAILS" -eq 0 ]
@@ -5,6 +5,9 @@ StartLimitIntervalSec=60
StartLimitBurst=3
[Service]
# Legacy HashiCorp Boundary environment file (BOUNDARY_* variables are still
# honored). Read first so that threshold.env takes precedence.
EnvironmentFile=-/etc/boundary.d/boundary.env
EnvironmentFile=-/etc/threshold.d/threshold.env
User=threshold
Group=threshold
+163
View File
@@ -0,0 +1,163 @@
#!/bin/sh
# Threshold package post-install script (deb "postinst" / rpm "%post").
#
# Completes an in-place upgrade from a HashiCorp Boundary install. Safe to run
# repeatedly; never deletes user configuration or data:
#
# 1. If the legacy "boundary" user exists and there is no "threshold" user,
# run the service as "boundary" through a systemd drop-in, so existing
# data/log directory ownership keeps working.
# 2. Provide a "boundary" command: /usr/bin/boundary -> threshold, only if
# /usr/bin/boundary does not exist.
# 3. If the legacy boundary.service is enabled/active: stop it, carry its
# enablement over to threshold.service, and start threshold.service if the
# old service was running (rolling back to boundary.service if that fails).
# 4. Make "systemctl <verb> boundary" address threshold.service by adding a
# boundary.service alias symlink, unless something already exists there.
#
# THRESHOLD_PKG_ROOT exists only so the logic can be exercised by
# .release/linux/test/test-package-scripts.sh without root; it is empty in
# normal use.
set -eu
ROOT="${THRESHOLD_PKG_ROOT:-}"
NEW_USER="threshold"
LEGACY_USER="boundary"
UNIT="threshold.service"
LEGACY_UNIT="boundary.service"
SYSTEMD_ETC="$ROOT/etc/systemd/system"
DROPIN_DIR="$SYSTEMD_ETC/threshold.service.d"
DROPIN="$DROPIN_DIR/10-legacy-boundary-user.conf"
DROPIN_MARK="# Managed by the Threshold package (legacy boundary user reuse)"
say() {
echo "threshold postinst: $*" >&2
}
user_exists() {
id -u "$1" >/dev/null 2>&1
}
have_systemd() {
command -v systemctl >/dev/null 2>&1 && [ -d "$ROOT/run/systemd/system" ]
}
# Print the Id= systemd resolves a unit name to (follows aliases).
unit_id() {
systemctl show -p Id "$1" 2>/dev/null | sed -n 's/^Id=//p'
}
unit_load_state() {
systemctl show -p LoadState "$1" 2>/dev/null | sed -n 's/^LoadState=//p'
}
# Where the packaged threshold.service lives (merged-usr or not).
packaged_unit_path() {
for _p in "$ROOT/usr/lib/systemd/system/$UNIT" "$ROOT/lib/systemd/system/$UNIT"; do
if [ -f "$_p" ]; then
echo "${_p#"$ROOT"}"
return 0
fi
done
return 1
}
# --- 1. legacy service user -------------------------------------------------
if user_exists "$LEGACY_USER" && ! user_exists "$NEW_USER"; then
_group=$(id -gn "$LEGACY_USER" 2>/dev/null || echo "$LEGACY_USER")
_content="$DROPIN_MARK
[Service]
User=$LEGACY_USER
Group=$_group"
if [ -e "$DROPIN" ] && ! grep -qF "$DROPIN_MARK" "$DROPIN"; then
say "leaving existing $DROPIN untouched (not managed by this package)"
else
mkdir -p "$DROPIN_DIR"
_tmp="$DROPIN.tmp.$$"
printf '%s\n' "$_content" >"$_tmp"
mv -f "$_tmp" "$DROPIN"
say "service will run as existing user '$LEGACY_USER' ($DROPIN)"
fi
fi
# --- 2. "boundary" command ----------------------------------------------------
_bin="$ROOT/usr/bin/boundary"
if [ ! -e "$_bin" ] && [ ! -L "$_bin" ]; then
if [ -e "$ROOT/usr/bin/threshold" ]; then
ln -s threshold "$_bin"
fi
elif [ ! -L "$_bin" ]; then
say "/usr/bin/boundary is still the HashiCorp Boundary binary (old package installed); leaving it"
say "after removing the old package run: ln -s threshold /usr/bin/boundary"
fi
# --- 3 + 4. legacy systemd service ----------------------------------------------
if have_systemd; then
_alias="$SYSTEMD_ETC/$LEGACY_UNIT"
if [ -L "$_alias" ]; then
# Already aliased (previous run): nothing to migrate.
:
elif [ -e "$_alias" ]; then
say "$_alias is a custom unit; not touching the legacy service."
say "move it away and re-run the package configuration to migrate."
else
_id=$(unit_id "$LEGACY_UNIT")
_load=$(unit_load_state "$LEGACY_UNIT")
_legacy_present=0
_was_enabled=0
_was_active=0
if [ "$_id" = "$LEGACY_UNIT" ] && [ "$_load" = "loaded" ]; then
_legacy_present=1
if systemctl is-enabled --quiet "$LEGACY_UNIT" 2>/dev/null; then
_was_enabled=1
fi
if systemctl is-active --quiet "$LEGACY_UNIT" 2>/dev/null; then
_was_active=1
fi
fi
# Only act on upgrades: a legacy unit or legacy config is present.
if [ "$_legacy_present" = 1 ] || [ -d "$ROOT/etc/boundary.d" ]; then
if [ "$_was_active" = 1 ]; then
say "stopping $LEGACY_UNIT"
systemctl stop "$LEGACY_UNIT" || say "could not stop $LEGACY_UNIT"
fi
if [ "$_was_enabled" = 1 ]; then
systemctl disable "$LEGACY_UNIT" >/dev/null 2>&1 || say "could not disable $LEGACY_UNIT"
fi
# Alias symlink so `systemctl restart boundary` reaches threshold.service.
if _unit_path=$(packaged_unit_path); then
mkdir -p "$SYSTEMD_ETC"
ln -s "$_unit_path" "$_alias"
fi
systemctl daemon-reload || true
if [ "$_was_enabled" = 1 ]; then
systemctl enable "$UNIT" >/dev/null 2>&1 || say "could not enable $UNIT"
fi
if [ "$_was_active" = 1 ]; then
if systemctl start "$UNIT"; then
say "$LEGACY_UNIT migrated to $UNIT (config: /etc/threshold.d)"
else
say "FAILED to start $UNIT; rolling back to $LEGACY_UNIT"
systemctl disable "$UNIT" >/dev/null 2>&1 || true
rm -f "$_alias"
systemctl daemon-reload || true
if [ "$_was_enabled" = 1 ]; then
systemctl enable "$LEGACY_UNIT" >/dev/null 2>&1 || true
fi
systemctl start "$LEGACY_UNIT" || say "could not restart $LEGACY_UNIT"
fi
elif [ "$_was_enabled" = 1 ]; then
say "$LEGACY_UNIT enablement carried over to $UNIT (not started: it was stopped)"
fi
fi
fi
# Pick up a new/changed user drop-in even when no migration was needed.
systemctl daemon-reload || true
fi
exit 0
Regular → Executable
+52 -5
View File
@@ -1,8 +1,55 @@
#!/bin/bash
#!/bin/sh
# Threshold package post-remove script (deb "postrm" / rpm "%postun").
#
# deb: $1 is remove|purge|upgrade|... rpm: $1 is 0 (erase) or 1+ (upgrade)
#
# On final removal this removes only what the Threshold package scripts
# themselves created: the managed systemd drop-in, the /usr/bin/boundary and
# /etc/systemd/system/boundary.service symlinks (only if they point at
# Threshold), and on purge the "threshold" user. User configuration, data and
# the legacy "boundary" user are never touched.
#
# THRESHOLD_PKG_ROOT exists only so the logic can be exercised by
# .release/linux/test/test-package-scripts.sh without root; it is empty in
# normal use.
if [ "$1" = "purge" ]
then
userdel threshold
set -eu
ROOT="${THRESHOLD_PKG_ROOT:-}"
SYSTEMD_ETC="$ROOT/etc/systemd/system"
DROPIN_DIR="$SYSTEMD_ETC/threshold.service.d"
DROPIN="$DROPIN_DIR/10-legacy-boundary-user.conf"
DROPIN_MARK="# Managed by the Threshold package (legacy boundary user reuse)"
# rm a symlink only if its target's basename matches $2.
rm_link_if() {
if [ -L "$1" ]; then
_t=$(readlink "$1" || true)
case "${_t##*/}" in
"$2") rm -f "$1" ;;
esac
fi
}
case "${1:-}" in
remove | purge | 0)
rm_link_if "$ROOT/usr/bin/boundary" threshold
rm_link_if "$SYSTEMD_ETC/boundary.service" threshold.service
rm_link_if "$SYSTEMD_ETC/multi-user.target.wants/threshold.service" threshold.service
if [ -f "$DROPIN" ] && grep -qF "$DROPIN_MARK" "$DROPIN"; then
rm -f "$DROPIN"
rmdir "$DROPIN_DIR" 2>/dev/null || true
fi
if command -v systemctl >/dev/null 2>&1 && [ -d "$ROOT/run/systemd/system" ]; then
systemctl daemon-reload >/dev/null 2>&1 || true
fi
;;
esac
if [ "${1:-}" = "purge" ]; then
if id -u threshold >/dev/null 2>&1; then
userdel threshold || true
fi
fi
exit 0
exit 0
Regular → Executable
+94 -9
View File
@@ -1,13 +1,98 @@
#!/bin/bash
#!/bin/sh
# Threshold package pre-install script (deb "preinst" / rpm "%pre").
#
# Runs before the package files are unpacked. It is safe to run repeatedly and
# never deletes or overwrites anything:
#
# * Service user: if a HashiCorp Boundary "boundary" user already exists and
# there is no "threshold" user yet, no new user is created. The postinst
# script makes the service run as the existing "boundary" user so that its
# data/log directory ownership keeps working.
# * Configuration: files from /etc/boundary.d are COPIED to /etc/threshold.d
# (boundary.hcl -> threshold.hcl, boundary.env -> threshold.env, everything
# else keeps its name). An existing file in /etc/threshold.d is never
# replaced, and the originals in /etc/boundary.d are left untouched.
#
# THRESHOLD_PKG_ROOT exists only so the logic can be exercised by
# .release/linux/test/test-package-scripts.sh without root; it is empty in
# normal use.
set -eu
USER="threshold"
ROOT="${THRESHOLD_PKG_ROOT:-}"
NEW_USER="threshold"
LEGACY_USER="boundary"
LEGACY_DIR="$ROOT/etc/boundary.d"
NEW_DIR="$ROOT/etc/threshold.d"
if ! id -u $USER > /dev/null 2>&1; then
useradd \
--system \
--user-group \
--shell /bin/false \
$USER
fi
say() {
echo "threshold preinst: $*" >&2
}
user_exists() {
id -u "$1" >/dev/null 2>&1
}
# Map a top-level legacy file name to its Threshold name.
map_name() {
case "$1" in
boundary.hcl) echo "threshold.hcl" ;;
boundary.env) echo "threshold.env" ;;
*) echo "$1" ;;
esac
}
# copy_tree SRC_DIR DST_DIR TOPLEVEL(0/1)
# Copies entries that do not exist yet in DST_DIR; never overwrites.
copy_tree() {
_src=$1
_dst=$2
_top=$3
for _f in "$_src"/* "$_src"/.[!.]*; do
if [ ! -e "$_f" ] && [ ! -L "$_f" ]; then
continue
fi
_name=${_f##*/}
if [ "$_top" = 1 ]; then
_name=$(map_name "$_name")
fi
_target="$_dst/$_name"
if [ -d "$_f" ] && [ ! -L "$_f" ]; then
if [ -d "$_target" ]; then
copy_tree "$_f" "$_target" 0
elif [ -e "$_target" ] || [ -L "$_target" ]; then
say "not replacing existing $_target"
else
cp -pR "$_f" "$_target"
say "copied $_f -> $_target"
fi
elif [ -e "$_target" ] || [ -L "$_target" ]; then
# Already present (user's own file, or a previous run): keep it.
:
else
cp -pP "$_f" "$_target"
say "copied $_f -> $_target"
fi
done
}
# --- service user -----------------------------------------------------------
if ! user_exists "$NEW_USER"; then
if user_exists "$LEGACY_USER"; then
say "existing '$LEGACY_USER' user found; reusing it instead of creating '$NEW_USER'"
else
useradd \
--system \
--user-group \
--shell /bin/false \
"$NEW_USER"
fi
fi
# --- configuration migration ------------------------------------------------
if [ -d "$LEGACY_DIR" ]; then
mkdir -p "$NEW_DIR"
copy_tree "$LEGACY_DIR" "$NEW_DIR" 1
fi
exit 0
+267
View File
@@ -0,0 +1,267 @@
#!/bin/sh
# Self-contained test for the Linux package scripts (preinst/postinst/postrm).
#
# Simulates an old HashiCorp Boundary layout in a temporary directory (fake
# /etc/boundary.d, a "boundary" user, a running boundary.service) using the
# THRESHOLD_PKG_ROOT prefix and fake id/useradd/userdel/systemctl commands.
# Needs neither root nor systemd.
#
# Usage: .release/linux/test/test-package-scripts.sh
set -eu
HERE=$(cd "$(dirname "$0")" && pwd)
PKG_DIR=$(cd "$HERE/.." && pwd)
PREINST="$PKG_DIR/preinst"
POSTINST="$PKG_DIR/postinst"
POSTRM="$PKG_DIR/postrm"
SH=${TEST_SH:-sh}
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT INT TERM
FAILS=0
PASSES=0
pass() { PASSES=$((PASSES + 1)); }
fail() {
FAILS=$((FAILS + 1))
echo "FAIL: $*" >&2
}
assert() { # assert DESC cmd...
_d=$1
shift
if "$@"; then pass; else fail "$_d"; fi
}
refute() { # refute DESC cmd...
_d=$1
shift
if "$@"; then fail "$_d"; else pass; fi
}
file_has() { grep -qF -- "$2" "$1"; }
file_eq() { [ "$(cat "$1")" = "$2" ]; }
# --- fake commands ------------------------------------------------------------
BIN="$WORK/bin"
mkdir -p "$BIN"
cat >"$BIN/id" <<'EOF'
#!/bin/sh
# fake id: id -u NAME | id -gn NAME ; users in $FAKE/users as name:group
mode=$1
name=$2
line=$(grep "^$name:" "$FAKE/users" 2>/dev/null) || exit 1
case "$mode" in
-u) echo 1000 ;;
-gn) echo "${line#*:}" ;;
esac
EOF
cat >"$BIN/useradd" <<'EOF'
#!/bin/sh
for a in "$@"; do name=$a; done
echo "$name:$name" >>"$FAKE/users"
echo "useradd $name" >>"$FAKE/calls"
EOF
cat >"$BIN/userdel" <<'EOF'
#!/bin/sh
grep -v "^$1:" "$FAKE/users" >"$FAKE/users.new" || true
mv "$FAKE/users.new" "$FAKE/users"
echo "userdel $1" >>"$FAKE/calls"
EOF
cat >"$BIN/systemctl" <<'EOF'
#!/bin/sh
# fake systemctl backed by files in $FAKE (state) and $THRESHOLD_PKG_ROOT
resolve() {
if [ -L "$THRESHOLD_PKG_ROOT/etc/systemd/system/$1" ]; then
_t=$(readlink "$THRESHOLD_PKG_ROOT/etc/systemd/system/$1")
echo "${_t##*/}"
else
echo "$1"
fi
}
echo "systemctl $*" >>"$FAKE/calls"
case "$1" in
show)
u=$(resolve "$4")
case "$3" in
Id) echo "Id=$u" ;;
LoadState)
if [ -f "$THRESHOLD_PKG_ROOT/usr/lib/systemd/system/$u" ]; then
echo "LoadState=loaded"
else
echo "LoadState=not-found"
fi
;;
esac
;;
is-enabled) u=$(resolve "$3"); [ -e "$FAKE/enabled/$u" ] ;;
is-active) u=$(resolve "$3"); [ -e "$FAKE/active/$u" ] ;;
enable) u=$(resolve "$2"); : >"$FAKE/enabled/$u" ;;
disable) u=$(resolve "$2"); rm -f "$FAKE/enabled/$u" ;;
start)
u=$(resolve "$2")
[ -e "$FAKE/fail_start/$u" ] && exit 1
: >"$FAKE/active/$u"
;;
stop) u=$(resolve "$2"); rm -f "$FAKE/active/$u" ;;
daemon-reload) : ;;
esac
exit $?
EOF
chmod 755 "$BIN"/*
# --- scenario helpers -----------------------------------------------------------
new_root() { # new_root NAME -> sets ROOT and FAKE, creates skeleton
ROOT="$WORK/$1/root"
FAKE="$WORK/$1/fake"
mkdir -p "$ROOT/etc" "$ROOT/usr/bin" "$ROOT/usr/lib/systemd/system" \
"$ROOT/etc/systemd/system" "$ROOT/run/systemd/system" \
"$FAKE/enabled" "$FAKE/active" "$FAKE/fail_start"
: >"$FAKE/users"
: >"$FAKE/calls"
# What the package itself would have unpacked.
: >"$ROOT/usr/bin/threshold"
echo "[Service]" >"$ROOT/usr/lib/systemd/system/threshold.service"
export THRESHOLD_PKG_ROOT="$ROOT" FAKE
}
run_script() { # run_script SCRIPT [ARGS...]
_s=$1
shift
PATH="$BIN:$PATH" "$SH" "$_s" "$@" >"$WORK/out.log" 2>&1
}
add_legacy_layout() {
mkdir -p "$ROOT/etc/boundary.d/tls"
echo 'controller { name = "mine" }' >"$ROOT/etc/boundary.d/boundary.hcl"
echo 'BOUNDARY_POSTGRES_URL=secret' >"$ROOT/etc/boundary.d/boundary.env"
echo 'worker { name = "w" }' >"$ROOT/etc/boundary.d/worker.hcl"
echo 'KEY' >"$ROOT/etc/boundary.d/tls/server.key"
chmod 600 "$ROOT/etc/boundary.d/tls/server.key"
echo "boundary:boundary" >>"$FAKE/users"
echo "[Service]" >"$ROOT/usr/lib/systemd/system/boundary.service"
}
# ==============================================================================
echo "== scenario 1: upgrade from Boundary (running + enabled), existing threshold.hcl"
new_root s1
add_legacy_layout
: >"$FAKE/enabled/boundary.service"
: >"$FAKE/active/boundary.service"
mkdir -p "$ROOT/etc/threshold.d"
echo 'USER-OWNED' >"$ROOT/etc/threshold.d/threshold.hcl"
assert "preinst succeeds" run_script "$PREINST" install
assert "threshold.hcl NOT clobbered" file_eq "$ROOT/etc/threshold.d/threshold.hcl" "USER-OWNED"
assert "boundary.env migrated to threshold.env" file_has "$ROOT/etc/threshold.d/threshold.env" "BOUNDARY_POSTGRES_URL=secret"
assert "worker.hcl copied under same name" file_has "$ROOT/etc/threshold.d/worker.hcl" 'name = "w"'
assert "subdirectory copied" file_has "$ROOT/etc/threshold.d/tls/server.key" "KEY"
assert "originals kept: boundary.hcl" test -f "$ROOT/etc/boundary.d/boundary.hcl"
assert "originals kept: boundary.env" test -f "$ROOT/etc/boundary.d/boundary.env"
refute "no duplicate threshold user created" grep -q '^useradd' "$FAKE/calls"
refute "no boundary.hcl name left in threshold.d" test -e "$ROOT/etc/threshold.d/boundary.hcl"
assert "postinst succeeds" run_script "$POSTINST" configure
assert "drop-in written" file_has "$ROOT/etc/systemd/system/threshold.service.d/10-legacy-boundary-user.conf" "User=boundary"
assert "drop-in group" file_has "$ROOT/etc/systemd/system/threshold.service.d/10-legacy-boundary-user.conf" "Group=boundary"
assert "boundary command symlink" test -L "$ROOT/usr/bin/boundary"
assert "boundary symlink target is threshold" test "$(readlink "$ROOT/usr/bin/boundary")" = "threshold"
assert "boundary.service alias symlink" test -L "$ROOT/etc/systemd/system/boundary.service"
assert "legacy service stopped" test ! -e "$FAKE/active/boundary.service"
assert "threshold enabled" test -e "$FAKE/enabled/threshold.service"
assert "threshold started" test -e "$FAKE/active/threshold.service"
refute "legacy unit not left enabled as itself" grep -q 'enable boundary.service' "$FAKE/calls"
: >"$FAKE/calls"
assert "postinst idempotent (2nd run)" run_script "$POSTINST" configure
refute "2nd run does not stop/start anything" grep -qE 'systemctl (stop|start|enable|disable)' "$FAKE/calls"
assert "preinst idempotent (2nd run)" run_script "$PREINST" upgrade
assert "threshold.hcl still not clobbered" file_eq "$ROOT/etc/threshold.d/threshold.hcl" "USER-OWNED"
echo "== scenario 1b: postrm remove/purge cleans only generated pieces"
assert "postrm remove" run_script "$POSTRM" remove
refute "boundary command symlink removed" test -L "$ROOT/usr/bin/boundary"
refute "alias symlink removed" test -L "$ROOT/etc/systemd/system/boundary.service"
refute "drop-in removed" test -e "$ROOT/etc/systemd/system/threshold.service.d/10-legacy-boundary-user.conf"
assert "user config untouched after remove" test -f "$ROOT/etc/boundary.d/boundary.hcl"
assert "migrated config untouched after remove" test -f "$ROOT/etc/threshold.d/threshold.env"
assert "postrm purge (no threshold user: no userdel)" run_script "$POSTRM" purge
refute "boundary user never deleted" grep -q 'userdel boundary' "$FAKE/calls"
# ==============================================================================
echo "== scenario 2: fresh install, no Boundary"
new_root s2
assert "preinst succeeds" run_script "$PREINST" install
assert "threshold user created" grep -q '^useradd threshold' "$FAKE/calls"
refute "no /etc/threshold.d created by migration" test -e "$ROOT/etc/threshold.d"
assert "postinst succeeds" run_script "$POSTINST" configure
refute "no drop-in" test -e "$ROOT/etc/systemd/system/threshold.service.d"
refute "no boundary.service alias on fresh install" test -L "$ROOT/etc/systemd/system/boundary.service"
assert "boundary command symlink still provided" test -L "$ROOT/usr/bin/boundary"
refute "threshold not auto-enabled on fresh install" test -e "$FAKE/enabled/threshold.service"
assert "postrm purge deletes threshold user" run_script "$POSTRM" purge
assert "userdel threshold called" grep -q '^userdel threshold' "$FAKE/calls"
# ==============================================================================
echo "== scenario 3: boundary service enabled but threshold start fails -> rollback"
new_root s3
add_legacy_layout
: >"$FAKE/enabled/boundary.service"
: >"$FAKE/active/boundary.service"
: >"$FAKE/fail_start/threshold.service"
assert "preinst succeeds" run_script "$PREINST" install
assert "postinst still exits 0" run_script "$POSTINST" configure
assert "legacy service running again" test -e "$FAKE/active/boundary.service"
assert "legacy service enabled again" test -e "$FAKE/enabled/boundary.service"
refute "threshold left disabled" test -e "$FAKE/enabled/threshold.service"
refute "alias removed on rollback" test -L "$ROOT/etc/systemd/system/boundary.service"
assert "rollback explained" file_has "$WORK/out.log" "rolling back"
# ==============================================================================
echo "== scenario 4: enabled but stopped boundary service is only carried over"
new_root s4
add_legacy_layout
: >"$FAKE/enabled/boundary.service"
assert "preinst" run_script "$PREINST" install
assert "postinst" run_script "$POSTINST" configure
assert "threshold enabled" test -e "$FAKE/enabled/threshold.service"
refute "threshold not started" test -e "$FAKE/active/threshold.service"
# ==============================================================================
echo "== scenario 5: custom regular-file /etc/systemd/system/boundary.service is left alone"
new_root s5
add_legacy_layout
: >"$FAKE/enabled/boundary.service"
: >"$FAKE/active/boundary.service"
echo "CUSTOM" >"$ROOT/etc/systemd/system/boundary.service"
assert "preinst" run_script "$PREINST" install
assert "postinst" run_script "$POSTINST" configure
assert "custom unit untouched" file_eq "$ROOT/etc/systemd/system/boundary.service" "CUSTOM"
assert "legacy service still running" test -e "$FAKE/active/boundary.service"
refute "threshold not started" test -e "$FAKE/active/threshold.service"
# ==============================================================================
echo "== scenario 6: old boundary package binary present is not replaced"
new_root s6
add_legacy_layout
echo "OLDBIN" >"$ROOT/usr/bin/boundary"
assert "preinst" run_script "$PREINST" install
assert "postinst" run_script "$POSTINST" configure
refute "boundary binary not turned into symlink" test -L "$ROOT/usr/bin/boundary"
assert "boundary binary content kept" file_eq "$ROOT/usr/bin/boundary" "OLDBIN"
assert "postrm remove does not delete the old binary" run_script "$POSTRM" remove
assert "old binary still there" test -f "$ROOT/usr/bin/boundary"
# ==============================================================================
echo "== scenario 7: threshold user already exists alongside boundary -> no drop-in"
new_root s7
add_legacy_layout
echo "threshold:threshold" >>"$FAKE/users"
assert "preinst" run_script "$PREINST" install
assert "postinst" run_script "$POSTINST" configure
refute "no drop-in when threshold user exists" test -e "$ROOT/etc/systemd/system/threshold.service.d"
# ==============================================================================
echo
echo "passed: $PASSES, failed: $FAILS"
[ "$FAILS" -eq 0 ]
+14
View File
@@ -29,6 +29,13 @@ ADD .release/docker/config.hcl /threshold/config.hcl
RUN chown -R threshold:threshold /threshold/
RUN chmod -R 640 /threshold/*
# Legacy HashiCorp Boundary compatibility: /boundary -> /threshold and a
# "boundary" command alias (prints a deprecation notice on stderr).
RUN mkdir -p /usr/share/threshold && \
cp /threshold/config.hcl /usr/share/threshold/config.hcl.default && \
ln -s /threshold /boundary && \
ln -s threshold /bin/boundary
EXPOSE 9200 9201 9202
VOLUME /threshold/
@@ -80,6 +87,13 @@ RUN setcap cap_ipc_lock=+ep /bin/$BIN_NAME
RUN chown -R ${NAME}:${NAME} /threshold
RUN chmod -R 640 /threshold/*
# Legacy HashiCorp Boundary compatibility: /boundary -> /threshold and a
# "boundary" command alias (prints a deprecation notice on stderr).
RUN mkdir -p /usr/share/threshold && \
cp /threshold/config.hcl /usr/share/threshold/config.hcl.default && \
ln -s /threshold /boundary && \
ln -s ${BIN_NAME} /bin/boundary
EXPOSE 9200 9201 9202
VOLUME /threshold/
+111
View File
@@ -68,6 +68,117 @@ Other fallbacks:
Tokens are always written using the new names.
## Upgrading from Boundary
Threshold is designed as an in-place replacement for HashiCorp Boundary CE:
the same database, the same configuration files, and a binary that can still be
invoked as `boundary`.
### Compatibility guarantees
These are unchanged from upstream Boundary CE, so a mixed fleet of Boundary and
Threshold controllers and workers interoperates, and you can upgrade (and roll
back) one node at a time:
- **Database schema**: the schema migrations are untouched and the
`boundary_schema_version` table keeps its name. Threshold runs against an
existing Boundary database as-is (`threshold database migrate` applies the
same migrations Boundary would).
- **Wire protocol**: protobuf packages, gRPC service names and the `/v1` HTTP
API paths are unchanged (only Go import paths were renamed). Versions are
still reported to peers with the `Boundary v` prefix.
- **Plugin protocol**: the plugin magic cookie
(`HASHICORP_BOUNDARY_PLUGIN` / `boundary-plugin`) is unchanged, so existing
`boundary-plugin-*` binaries keep working.
- **Configuration file format** (HCL) is unchanged.
Do not run `database migrate` from a newer Threshold than your other nodes
understand; as with Boundary, upgrade controllers before workers and follow the
usual rolling-upgrade guidance for schema migrations.
### Linux packages (deb/rpm)
1. Back up `/etc/boundary.d` and take a database backup.
2. Install the Threshold package. Do not `purge` the old `boundary` package
(removal is fine); purging runs its scripts, which may delete the `boundary`
user that Threshold will reuse. Installing Threshold while the old package is
still installed is supported.
3. The package scripts then, automatically:
- **copy** `/etc/boundary.d` to `/etc/threshold.d` (`boundary.hcl` becomes
`threshold.hcl`, `boundary.env` becomes `threshold.env`; subdirectories such
as TLS material are copied as-is). Existing files in `/etc/threshold.d` are
never overwritten and the originals are never modified or deleted;
- keep reading `/etc/boundary.d/boundary.env` as a fallback environment file
(`threshold.env` takes precedence), so `BOUNDARY_*` variables keep working;
- reuse the existing `boundary` service user and group instead of creating a
`threshold` user (through a managed systemd drop-in,
`/etc/systemd/system/threshold.service.d/10-legacy-boundary-user.conf`), so
ownership of your data, log and key directories keeps working;
- if `boundary.service` was enabled and/or running, stop and disable it,
enable `threshold.service`, and start it if the old service was running.
If `threshold.service` fails to start, the old service is restored;
- make `systemctl restart boundary` (and `status`, `stop`, ...) address
`threshold.service` via a `boundary.service` alias symlink in
`/etc/systemd/system`, and provide `/usr/bin/boundary` as a symlink to
`threshold` if no such file exists. The `boundary` command prints a
one-line deprecation notice to **stderr** only (silence it with
`THRESHOLD_NO_DEPRECATION_NOTICE=1`); stdout, flags and exit codes are
identical to `threshold`.
4. Verify: `systemctl status threshold`, `threshold version`.
What is **not** migrated automatically:
- Paths *inside* your configuration files are not rewritten. A config that
refers to `/etc/boundary.d/...` or `/var/lib/boundary` keeps working because
those locations are left in place; update them at your own pace.
- A custom `/etc/systemd/system/boundary.service` unit (a regular file) is left
alone and the legacy service is not touched; migrate it by hand.
- If `/usr/bin/boundary` is still the old binary from the Boundary package, it is
left alone. After removing the old package: `ln -s threshold /usr/bin/boundary`.
- Distribution-specific package-manager behavior such as `--force-confnew` (dpkg)
can replace `/etc/threshold.d` defaults; your originals remain in
`/etc/boundary.d`.
- Data directories, the database, KMS keys and the client cache are not moved
(the client cache directory is covered by the table above).
Rollback: stop `threshold.service`, remove the `boundary.service` alias
symlink (`/etc/systemd/system/boundary.service`) and the drop-in, reinstall the
old Boundary package, and `systemctl enable --now boundary`. Because the
database schema is unchanged and `/etc/boundary.d` was never modified, nothing
else needs to be restored. If you ran a Threshold release that applied schema
migrations newer than your Boundary version, restore the database backup
instead.
### Container image
The image's configuration directory moved from `/boundary` to `/threshold`
(default command: `threshold server -config /threshold/config.hcl`). For
compatibility the image:
- contains `/boundary` as a symlink to `/threshold`, so existing
`-v "$(pwd)":/boundary/` mounts land in the right place;
- falls back to `/boundary/config.hcl` (printing a deprecation warning) when
`-config /boundary/config.hcl` is requested, or when the stock demo config is
still in place and a config exists at `/boundary/config.hcl`;
- accepts `boundary ...` as the command and `BOUNDARY_*` environment variables
(mirrored to `THRESHOLD_*`).
Plan to switch your mounts, `-config` arguments and environment variables to
the `/threshold` and `THRESHOLD_*` names; the compatibility paths print
warnings and may be removed in a future major release.
### Other installs (binary / zip)
Replace the `boundary` binary with `threshold` (or keep the file name
`boundary`, which behaves identically). Existing `BOUNDARY_*` variables,
`~/.boundary` and stored credentials are picked up as described above.
### Development mode
`threshold dev` creates the `ssh.threshold.dev` and `postgres.threshold.dev`
aliases and, for compatibility, the legacy `ssh.boundary.dev` and
`postgres.boundary.dev` aliases pointing at the same targets.
## Building
```
+8
View File
@@ -10,5 +10,13 @@ import (
)
func main() {
// When invoked through the legacy "boundary" name (a symlink shipped in
// packages/images), print a one-line deprecation notice to stderr. Behavior
// is otherwise identical and stdout is never touched.
if len(os.Args) > 0 {
if notice := cmd.LegacyNameNotice(os.Args[0], os.Getenv); notice != "" {
_, _ = os.Stderr.WriteString(notice)
}
}
os.Exit(cmd.Run(os.Args[1:]))
}
+33 -18
View File
@@ -615,9 +615,11 @@ func (b *Server) CreateInitialTargetWithHostSources(ctx context.Context) (target
}
// Create targets that can be connected to using an alias. The three targets created are:
// - "postgres.boundary.dev": the Threshold dev postgres instance. Uses brokered credentials
// - "postgres.threshold.dev": the Threshold dev postgres instance. Uses brokered credentials
// (the legacy "postgres.boundary.dev" alias is also created)
// - "www.hashicorp.com": a web target
// - "ssh.boundary.dev": A localhost ssh target
// - "ssh.threshold.dev": A localhost ssh target (the legacy "ssh.boundary.dev"
// alias is also created)
func (b *Server) CreateInitialTargetsWithAlias(ctx context.Context) error {
rw := db.New(b.Database)
@@ -679,14 +681,9 @@ func (b *Server) createSshAliasTarget(ctx context.Context, targetRepo *target.Re
return err
}
sshAlias := "ssh.boundary.dev"
a, err := aliastar.NewAlias(ctx, "global", sshAlias, aliastar.WithDestinationId(t.GetPublicId()))
if err != nil {
return fmt.Errorf("failed to create alias object %w", err)
}
_, err = aliasRepo.CreateAlias(ctx, a)
if err != nil {
return fmt.Errorf("failed to save alias to the db %w", err)
sshAlias := "ssh.threshold.dev"
if err := createDevAliases(ctx, aliasRepo, t.GetPublicId(), sshAlias, legacySshAlias); err != nil {
return err
}
b.InfoKeys = append(b.InfoKeys, "generated ssh target with alias")
@@ -694,6 +691,29 @@ func (b *Server) createSshAliasTarget(ctx context.Context, targetRepo *target.Re
return nil
}
// Legacy dev-mode alias names used by upstream HashiCorp Boundary. They are
// still created, pointing at the same targets, so existing scripts and muscle
// memory keep working.
const (
legacySshAlias = "ssh.boundary.dev"
legacyPostgresAlias = "postgres.boundary.dev"
)
// createDevAliases creates one alias per value, all pointing at the given
// destination target.
func createDevAliases(ctx context.Context, aliasRepo *aliastar.Repository, destinationId string, values ...string) error {
for _, v := range values {
a, err := aliastar.NewAlias(ctx, "global", v, aliastar.WithDestinationId(destinationId))
if err != nil {
return fmt.Errorf("failed to create alias object %w", err)
}
if _, err = aliasRepo.CreateAlias(ctx, a); err != nil {
return fmt.Errorf("failed to save alias to the db %w", err)
}
}
return nil
}
func (b *Server) createPostgresAliasTarget(ctx context.Context,
targetRepo *target.Repository,
aliasRepo *aliastar.Repository,
@@ -757,14 +777,9 @@ func (b *Server) createPostgresAliasTarget(ctx context.Context,
return fmt.Errorf("failed to associate cred to target: %w", err)
}
postgresAlias := "postgres.boundary.dev"
a, err := aliastar.NewAlias(ctx, "global", postgresAlias, aliastar.WithDestinationId(t.GetPublicId()))
if err != nil {
return fmt.Errorf("failed to create alias object %w", err)
}
_, err = aliasRepo.CreateAlias(ctx, a)
if err != nil {
return fmt.Errorf("failed to save alias to the db %w", err)
postgresAlias := "postgres.threshold.dev"
if err := createDevAliases(ctx, aliasRepo, t.GetPublicId(), postgresAlias, legacyPostgresAlias); err != nil {
return err
}
b.InfoKeys = append(b.InfoKeys, "generated postgres target with alias")
+49
View File
@@ -0,0 +1,49 @@
// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: BUSL-1.1
package cmd
import (
"path/filepath"
"strings"
)
// legacyBinaryName is the name of the upstream HashiCorp Boundary binary. A
// "boundary" symlink to the threshold binary is shipped in packages and
// container images so existing scripts keep working.
const legacyBinaryName = "boundary"
// envNoLegacyNameNotice, when set to a non-empty value, silences the
// deprecation notice printed when the binary is invoked as "boundary".
const envNoLegacyNameNotice = "THRESHOLD_NO_DEPRECATION_NOTICE"
// invokedAsLegacyName reports whether argv0 refers to the legacy "boundary"
// binary name. Directory components and a Windows ".exe" suffix are ignored.
func invokedAsLegacyName(argv0 string) bool {
// Normalize Windows separators too, since filepath.Base on unix does not
// treat backslash as a separator.
argv0 = strings.ReplaceAll(argv0, `\`, "/")
base := strings.ToLower(filepath.Base(argv0))
base = strings.TrimSuffix(base, ".exe")
return base == legacyBinaryName
}
// LegacyNameNotice returns the one-line deprecation notice (including a
// trailing newline) to write to stderr when the binary was invoked as
// "boundary", or an empty string when no notice should be shown. No notice is
// shown for any other invocation name, when shell completion is running
// (COMP_LINE is set, so that a notice never corrupts the user's prompt), or
// when THRESHOLD_NO_DEPRECATION_NOTICE is set. The notice is only ever written
// to stderr so that machine-readable stdout output is unaffected.
//
// getenv is typically os.Getenv.
func LegacyNameNotice(argv0 string, getenv func(string) string) string {
if !invokedAsLegacyName(argv0) {
return ""
}
if getenv("COMP_LINE") != "" || getenv(envNoLegacyNameNotice) != "" {
return ""
}
return "Note: the 'boundary' command name is deprecated; use 'threshold' instead. " +
"(Set " + envNoLegacyNameNotice + "=1 to silence this notice.)\n"
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright IBM Corp. 2020, 2026
// SPDX-License-Identifier: BUSL-1.1
package cmd
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestInvokedAsLegacyName(t *testing.T) {
t.Parallel()
tests := []struct {
argv0 string
want bool
}{
{"boundary", true},
{"/usr/bin/boundary", true},
{"./boundary", true},
{`C:\bin\boundary.exe`, true},
{"Boundary.EXE", true},
{"threshold", false},
{"/usr/bin/threshold", false},
{"boundary-worker", false},
{"", false},
}
for _, tt := range tests {
t.Run(tt.argv0, func(t *testing.T) {
assert.Equal(t, tt.want, invokedAsLegacyName(tt.argv0))
})
}
}
func TestLegacyNameNotice(t *testing.T) {
t.Parallel()
env := func(m map[string]string) func(string) string {
return func(k string) string { return m[k] }
}
n := LegacyNameNotice("/usr/bin/boundary", env(nil))
assert.True(t, strings.HasSuffix(n, "\n"))
assert.Equal(t, 1, strings.Count(n, "\n"), "notice must be a single line")
assert.Contains(t, n, "threshold")
assert.Empty(t, LegacyNameNotice("/usr/bin/threshold", env(nil)))
assert.Empty(t, LegacyNameNotice("boundary", env(map[string]string{"COMP_LINE": "boundary auth"})))
assert.Empty(t, LegacyNameNotice("boundary", env(map[string]string{envNoLegacyNameNotice: "1"})))
}