Make in-place upgrade from HashiCorp Boundary seamless
Linux packages: migrate /etc/boundary.d to /etc/threshold.d without clobbering, reuse the boundary service user, carry over service enablement with a boundary.service alias, provide a boundary command. Docker: accept /boundary/config.hcl, boundary command, /boundary symlink. CLI: deprecation notice when invoked as boundary (stderr only). Dev mode: also create *.threshold.dev aliases. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
80ece9c4fc
commit
d07030a10b
15 files changed
+1033
-32
No files matched your search
@@ -282,6 +282,7 @@ jobs:
|
|||||||
rpm_depends: "openssl"
|
rpm_depends: "openssl"
|
||||||
config_dir: ".release/linux/package/"
|
config_dir: ".release/linux/package/"
|
||||||
preinstall: ".release/linux/preinst"
|
preinstall: ".release/linux/preinst"
|
||||||
|
postinstall: ".release/linux/postinst"
|
||||||
postremove: ".release/linux/postrm"
|
postremove: ".release/linux/postrm"
|
||||||
- name: Add Linux Package names to env
|
- name: Add Linux Package names to env
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -25,6 +25,16 @@ and is not suitable for actual deployment.
|
|||||||
Please see the comments within the file for more information;
|
Please see the comments within the file for more information;
|
||||||
full configuration details can be found on Threshold's documentation site.
|
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
|
### Postgres
|
||||||
|
|
||||||
The usage instructions in this README assume you have an external postgres database (version 12 or greater) to run threshold server with.
|
The usage instructions in this README assume you have an external postgres database (version 12 or greater) to run threshold server with.
|
||||||
|
|||||||
@@ -22,6 +22,54 @@ for legacy_name in $(env | sed -n 's/^\(BOUNDARY_[A-Za-z0-9_]*\)=.*/\1/p'); do
|
|||||||
fi
|
fi
|
||||||
done
|
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
|
# If the user is trying to run Threshold directly with some arguments, then
|
||||||
# pass them to Threshold.
|
# pass them to Threshold.
|
||||||
if [ "${1:0:1}" = '-' ]; then
|
if [ "${1:0:1}" = '-' ]; then
|
||||||
@@ -38,6 +86,26 @@ elif threshold --help "$1" 2>&1 | grep -q "threshold $1"; then
|
|||||||
set -- threshold "$@"
|
set -- threshold "$@"
|
||||||
fi
|
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 we are running Threshold, make sure it executes as the proper user.
|
||||||
if [ "$1" = 'threshold' ]; then
|
if [ "$1" = 'threshold' ]; then
|
||||||
is_root=0
|
is_root=0
|
||||||
@@ -51,6 +119,10 @@ if [ "$1" = 'threshold' ]; then
|
|||||||
if [ "$(stat -c %u /threshold)" != "$(id -u 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)"
|
chown -R threshold:threshold /threshold || echo "Could not chown /threshold (may not have appropriate permissions)"
|
||||||
fi
|
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
|
else
|
||||||
>&2 echo "SKIP_CHOWN not set but running non-root; skipping chown /threshold"
|
>&2 echo "SKIP_CHOWN not set but running non-root; skipping chown /threshold"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Executable
+107
@@ -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
|
StartLimitBurst=3
|
||||||
|
|
||||||
[Service]
|
[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
|
EnvironmentFile=-/etc/threshold.d/threshold.env
|
||||||
User=threshold
|
User=threshold
|
||||||
Group=threshold
|
Group=threshold
|
||||||
|
|||||||
Executable
+163
@@ -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
@@ -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" ]
|
set -eu
|
||||||
then
|
|
||||||
userdel threshold
|
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
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
Regular → Executable
+94
-9
@@ -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
|
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
|
say() {
|
||||||
useradd \
|
echo "threshold preinst: $*" >&2
|
||||||
--system \
|
}
|
||||||
--user-group \
|
|
||||||
--shell /bin/false \
|
user_exists() {
|
||||||
$USER
|
id -u "$1" >/dev/null 2>&1
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
# 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
|
||||||
Executable
+267
@@ -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
@@ -29,6 +29,13 @@ ADD .release/docker/config.hcl /threshold/config.hcl
|
|||||||
RUN chown -R threshold:threshold /threshold/
|
RUN chown -R threshold:threshold /threshold/
|
||||||
RUN chmod -R 640 /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
|
EXPOSE 9200 9201 9202
|
||||||
VOLUME /threshold/
|
VOLUME /threshold/
|
||||||
|
|
||||||
@@ -80,6 +87,13 @@ RUN setcap cap_ipc_lock=+ep /bin/$BIN_NAME
|
|||||||
RUN chown -R ${NAME}:${NAME} /threshold
|
RUN chown -R ${NAME}:${NAME} /threshold
|
||||||
RUN chmod -R 640 /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
|
EXPOSE 9200 9201 9202
|
||||||
VOLUME /threshold/
|
VOLUME /threshold/
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,117 @@ Other fallbacks:
|
|||||||
|
|
||||||
Tokens are always written using the new names.
|
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
|
## Building
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -10,5 +10,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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:]))
|
os.Exit(cmd.Run(os.Args[1:]))
|
||||||
}
|
}
|
||||||
@@ -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:
|
// 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
|
// - "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 {
|
func (b *Server) CreateInitialTargetsWithAlias(ctx context.Context) error {
|
||||||
rw := db.New(b.Database)
|
rw := db.New(b.Database)
|
||||||
|
|
||||||
@@ -679,14 +681,9 @@ func (b *Server) createSshAliasTarget(ctx context.Context, targetRepo *target.Re
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sshAlias := "ssh.boundary.dev"
|
sshAlias := "ssh.threshold.dev"
|
||||||
a, err := aliastar.NewAlias(ctx, "global", sshAlias, aliastar.WithDestinationId(t.GetPublicId()))
|
if err := createDevAliases(ctx, aliasRepo, t.GetPublicId(), sshAlias, legacySshAlias); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.InfoKeys = append(b.InfoKeys, "generated ssh target with alias")
|
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
|
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,
|
func (b *Server) createPostgresAliasTarget(ctx context.Context,
|
||||||
targetRepo *target.Repository,
|
targetRepo *target.Repository,
|
||||||
aliasRepo *aliastar.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)
|
return fmt.Errorf("failed to associate cred to target: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
postgresAlias := "postgres.boundary.dev"
|
postgresAlias := "postgres.threshold.dev"
|
||||||
a, err := aliastar.NewAlias(ctx, "global", postgresAlias, aliastar.WithDestinationId(t.GetPublicId()))
|
if err := createDevAliases(ctx, aliasRepo, t.GetPublicId(), postgresAlias, legacyPostgresAlias); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.InfoKeys = append(b.InfoKeys, "generated postgres target with alias")
|
b.InfoKeys = append(b.InfoKeys, "generated postgres target with alias")
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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"})))
|
||||||
|
}
|
||||||
Reference in new issue
Block a user