Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions scripts/install-k8s.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,47 @@ function Invoke-DiagnoseBundle {
# never abort THIS installer.
$TRACEBLOC_CLI_INSTALL_URL = "https://github.com/tracebloc/cli/releases/latest/download/install.ps1"

# Where the CLI's own Windows installer drops the binary + adds to the *user*
# PATH (see cli's install.ps1) — the dir we point at if a fresh shell can't
# find it yet. Guard the Join-Path: $env:LOCALAPPDATA is null when the Pester
# suite dot-sources this script on Linux CI, and Join-Path throws on a null
# -Path (aborting the whole test container). The value is only ever USED on
# Windows (in Test-TraceblocCli), so "" is a fine non-Windows load-time placeholder.
$TRACEBLOC_CLI_INSTALL_DIR = if ($env:LOCALAPPDATA) {
Join-Path $env:LOCALAPPDATA "Programs\tracebloc"
} else { "" }

# Post-install self-verification (#738). Proves the CLI is usable from a FRESH
# terminal and prints a VERIFIED next command — or, if a new shell wouldn't
# find it yet, the exact Windows-correct fix (the install dir + open a new
# window) rather than a vague "open a new terminal". The CLI installer edits the
# user-scope PATH in the registry, so RefreshPath (re-reading Machine+User PATH)
# is the faithful "fresh terminal" probe here — there is no `source ~/.rc`
# analogue on Windows. ALWAYS non-fatal: the client is connected by Step 5.
function Test-TraceblocCli {
# Pull the persisted (registry) PATH into THIS process — same env a brand-new
# PowerShell window would start with.
try { RefreshPath } catch { Log "RefreshPath failed during CLI verify: $_" }

if (Has "tracebloc") {
# `tracebloc version` is the real proof; cosmetic, never fatal. The canonical
# "tracebloc dataset push ./data" next step lives in Print-Summary's "What to
# do next" — don't duplicate it; just confirm the verdict.
$ver = ""
try { $ver = (& tracebloc version 2>$null | Select-Object -First 1) } catch { $ver = "" }
if ($ver) { Ok "tracebloc CLI installed ($ver) -- verified on your PATH." }
else { Ok "tracebloc CLI installed -- verified on your PATH." }
return
}

# Installed, but not resolvable from a fresh shell yet. The installer added it
# to the user PATH, so a NEW window will have it; tell the user exactly where
# it is and how to use it now (so the summary's command works from a new window).
Ok "tracebloc CLI installed -- open a new PowerShell window to use it."
Hint " Installed to: $TRACEBLOC_CLI_INSTALL_DIR"
Hint " Or use it now via: & `"$TRACEBLOC_CLI_INSTALL_DIR\tracebloc.exe`" dataset push .\data"
}

function Install-TraceblocCli {
Step 5 5 "Install the tracebloc CLI"

Expand Down Expand Up @@ -1611,8 +1652,9 @@ function Install-TraceblocCli {
# as success — a failed re-install on a machine that already had the CLI
# would then be misreported as a success.
if ($p.ExitCode -eq 0) {
RefreshPath
Ok "tracebloc CLI installed -- open a new terminal so it's on your PATH."
# Self-verify usability from a fresh terminal and print a verified next
# command (or the Windows-correct fix). Non-fatal.
Test-TraceblocCli
} else {
Warn "Couldn't install the tracebloc CLI automatically -- your client is set up fine."
Hint "Install it later: irm $TRACEBLOC_CLI_INSTALL_URL | iex"
Expand Down
105 changes: 99 additions & 6 deletions scripts/lib/install-cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,102 @@

TRACEBLOC_CLI_INSTALL_URL="https://github.com/tracebloc/cli/releases/latest/download/install.sh"

# Where the CLI's own installer drops the binary when /usr/local/bin isn't
# writable (see cli's install.sh) — the dir we tell the user to put on PATH.
TRACEBLOC_CLI_FALLBACK_BIN="${HOME}/.local/bin"

# Which rc file a *fresh* interactive shell of the user's $SHELL actually reads,
# so the PATH fix we print sources the right file. Mirrors how the cli's
# install.sh routes guidance, but resolved per-shell here:
# zsh → ~/.zshrc
# bash + Linux → ~/.bashrc (a fresh non-login bash reads ~/.bashrc,
# NOT ~/.profile — this is the failure mode)
# bash + macOS → ~/.bash_profile
# fish → ~/.config/fish/config.fish
# anything else → ~/.profile (POSIX sh fallback)
_cli_rc_for_shell() {
local sh_name; sh_name="$(basename "${SHELL:-/bin/sh}")"
case "$sh_name" in
zsh) echo "${HOME}/.zshrc" ;;
bash)
if [[ "${OS:-$(uname -s)}" == "Darwin" ]]; then
echo "${HOME}/.bash_profile"
else
echo "${HOME}/.bashrc"
fi
;;
fish) echo "${HOME}/.config/fish/config.fish" ;;
*) echo "${HOME}/.profile" ;;
esac
}

# The shell-correct line a fish user must add differs (no POSIX `export`).
_cli_path_export_line() {
local sh_name; sh_name="$(basename "${SHELL:-/bin/sh}")"
if [[ "$sh_name" == "fish" ]]; then
echo "fish_add_path \"${TRACEBLOC_CLI_FALLBACK_BIN}\""
else
echo "export PATH=\"${TRACEBLOC_CLI_FALLBACK_BIN}:\$PATH\""
fi
}

# Does a *fresh* shell resolve `tracebloc` on its PATH? This is the real test
# the success message has, until now, only asserted by hope: a brand-new
# terminal must find the binary. We probe two ways because they read different
# startup files:
# 1. login shell ("$SHELL" -lic) → ~/.profile / ~/.zprofile / ~/.bash_profile
# 2. non-login shell ("$SHELL" -ic) → ~/.bashrc / ~/.zshrc
# A pass requires BOTH (cli#61 was "works in my login shell, missing in a plain
# `bash` subshell"). Indirected into its own function so the bats suite can stub
# it without spawning real shells. Never fatal: returns non-zero on "not found".
_cli_on_fresh_path() {
local shell_bin="${SHELL:-/bin/sh}"
"$shell_bin" -lic 'command -v tracebloc' >/dev/null 2>&1 || return 1
"$shell_bin" -ic 'command -v tracebloc' >/dev/null 2>&1 || return 1
Comment thread
saadqbal marked this conversation as resolved.
return 0
}

# Post-install self-verification (#738). Proves the CLI is actually usable from
# a fresh terminal and prints a VERIFIED next command — or, if a new shell would
# NOT find it, the EXACT shell-correct PATH fix instead of a vague "open a new
# terminal". ALWAYS returns 0: the client is connected by Step 5, so a CLI
# verification hiccup must never abort an otherwise-successful install.
_verify_tracebloc_cli() {
if _cli_on_fresh_path; then
# Usable from a new terminal. `tracebloc version` is the real proof; keep it
# cosmetic (never let a failing version call or a SIGPIPE flip the outcome).
# The canonical "tracebloc dataset push ./data" next step lives in the
# summary's "What to do next" — don't duplicate it here; just confirm the
# verdict so the summary's command is known-good.
local ver; ver="$(tracebloc version 2>/dev/null | head -1 || true)"
success "tracebloc CLI installed${ver:+ ($ver)} — verified on your PATH."
return 0
fi

# Installed, but a fresh terminal won't find it (e.g. it landed in
# ~/.local/bin, which isn't on PATH). Tell the user precisely how to fix it
# for THEIR shell — not a generic "open a new terminal" that won't help.
# `|| true` so a hiccup in rc-resolution can't trip the orchestrator's set -e.
local rc; rc="$(_cli_rc_for_shell || true)"
local export_line; export_line="$(_cli_path_export_line || true)"
local sh_name; sh_name="$(basename "${SHELL:-/bin/sh}")"
success "tracebloc CLI installed — put it on your PATH:"
if [[ "$sh_name" == "fish" ]]; then
# fish_add_path persists (a universal var) AND applies to this shell — no
# `source` needed, unlike a POSIX rc edit.
hint " ${export_line}"
else
# Append the line to the rc, then load it: fixes THIS terminal and every
# new one in a single copy-pasteable step (the old code printed a bare
# `export` that fixed only this shell, then `source`d an rc that didn't
# yet contain the line — so nothing persisted).
hint " echo '${export_line}' >> ${rc}"
hint " source ${rc}"
fi
info "Then the 'tracebloc dataset push ./data' step below will work."
return 0
}

install_tracebloc_cli() {
step 5 5 "Install the tracebloc CLI"

Expand Down Expand Up @@ -51,12 +147,9 @@ install_tracebloc_cli() {
# verifies SHA256 + cosign and falls back to ~/.local/bin (printing PATH
# guidance) when /usr/local/bin isn't writable.
if sh "$installer" >> "${LOG_FILE:-/dev/null}" 2>&1; then
if has tracebloc; then
local ver="$(tracebloc version 2>/dev/null | head -1 || true)"
success "tracebloc CLI installed${ver:+ ($ver)}."
else
success "tracebloc CLI installed — open a new terminal so it's on your PATH."
fi
# Self-verify usability from a FRESH terminal and print a verified next
# command (or a shell-correct PATH fix). Non-fatal — always returns 0.
_verify_tracebloc_cli
else
warn "Couldn't install the tracebloc CLI automatically — your client is set up fine."
hint "Install it later: curl -fsSL ${TRACEBLOC_CLI_INSTALL_URL} | sh"
Expand Down
87 changes: 82 additions & 5 deletions scripts/tests/install-cli.bats
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ setup() {
info() { :; }
success() { echo "SUCCESS: $*"; }
warn() { echo "WARN: $*"; }
hint() { :; }
# hint() carries the actionable PATH-fix lines (#738), so echo it (like
# success/warn) instead of silencing — the verification tests assert on it.
hint() { echo "HINT: $*"; }
has() { return 1; } # default: tracebloc not present
# CURL_SECURE is set readonly by common.sh (loaded via load_lib); don't
# reassign it. curl is mocked in every test below, so its value is moot.
Expand All @@ -38,11 +40,86 @@ setup() {
}

@test "install_tracebloc_cli: success path reports installed" {
curl() { : > "${@: -1}"; return 0; }
sh() { return 0; }
has() { return 0; } # tracebloc now resolvable
tracebloc() { echo "tracebloc 0.2.0"; }
curl() { : > "${@: -1}"; return 0; }
sh() { return 0; }
has() { return 0; } # tracebloc now resolvable
_cli_on_fresh_path() { return 0; } # a fresh terminal finds it (don't spawn real shells)
tracebloc() { echo "tracebloc 0.2.0"; }
run install_tracebloc_cli
[ "$status" -eq 0 ]
[[ "$output" == *"SUCCESS: tracebloc CLI installed"* ]]
}

# ── Self-verification (#738) ────────────────────────────────────────────────
# After install, prove the CLI is usable from a FRESH terminal and print a
# verified next command; if a new shell wouldn't find it, print the EXACT
# shell-correct PATH fix instead of a generic "open a new terminal". Always
# non-fatal (return 0) — the client is already connected by Step 5.

@test "install_tracebloc_cli: fresh-shell success reports a VERIFIED verdict (not 'open a new terminal')" {
curl() { : > "${@: -1}"; return 0; }
sh() { return 0; }
_cli_on_fresh_path() { return 0; } # a brand-new terminal resolves tracebloc
tracebloc() { echo "tracebloc 0.2.0"; }
run install_tracebloc_cli
[ "$status" -eq 0 ]
[[ "$output" == *"verified on your PATH"* ]] # explicit proof, not hope
[[ "$output" == *"0.2.0"* ]] # real proof via `tracebloc version`
[[ "$output" != *"open a new terminal"* ]] # the old, useless message is gone
# The canonical dataset-push next step lives in summary.sh — don't duplicate it
# here on the verified path (#738: "don't duplicate; keep consistent").
[[ "$output" != *"tracebloc dataset push"* ]]
}

@test "install_tracebloc_cli: CLI-missing-from-fresh-shell prints an actionable, shell-correct PATH hint" {
curl() { : > "${@: -1}"; return 0; }
sh() { return 0; }
_cli_on_fresh_path() { return 1; } # installed, but a fresh terminal does NOT find it
SHELL="/bin/zsh"; OS="Linux" # zsh → ~/.zshrc (rc routing under test)
run install_tracebloc_cli
[ "$status" -eq 0 ]
# Append the exact PATH line to the rc, THEN source it — fixes this terminal
# and every new one (the old code printed a bare `export` + a `source` of an
# rc that didn't contain the line, so nothing persisted).
[[ "$output" == *"echo 'export PATH=\"$HOME/.local/bin:\$PATH\"' >> $HOME/.zshrc"* ]]
[[ "$output" == *"source $HOME/.zshrc"* ]] # the right rc for zsh
[[ "$output" != *"open a new terminal"* ]] # never the generic line
}

@test "install_tracebloc_cli: fish gets a fish-correct fix (fish_add_path, no source needed)" {
curl() { : > "${@: -1}"; return 0; }
sh() { return 0; }
_cli_on_fresh_path() { return 1; }
SHELL="/usr/bin/fish"; OS="Linux"
run install_tracebloc_cli
[ "$status" -eq 0 ]
[[ "$output" == *"fish_add_path \"$HOME/.local/bin\""* ]] # fish's idiom, not POSIX export
[[ "$output" != *"export PATH"* ]] # never a POSIX export for fish
# fish_add_path persists (universal var) AND applies to the running shell, so
# fish users must NOT be told to `source` anything (the old guidance did).
[[ "$output" != *"source "* ]]
}

@test "install_tracebloc_cli: verification failure is still NON-FATAL (status 0)" {
curl() { : > "${@: -1}"; return 0; }
sh() { return 0; }
# The whole verification step explodes — must NOT abort the install.
_cli_on_fresh_path() { return 2; }
_cli_rc_for_shell() { return 7; } # even if rc resolution itself errors
run install_tracebloc_cli
[ "$status" -eq 0 ]
}

@test "install_tracebloc_cli: NON-FATAL even under the orchestrator's set -e" {
# The real installer sources this under `set -e`; a verification hiccup must
# never abort an otherwise-good install. Reproduce that exact condition.
curl() { : > "${@: -1}"; return 0; }
sh() { return 0; }
_cli_on_fresh_path() { return 1; } # CLI not on a fresh PATH (failure branch)
_cli_rc_for_shell() { return 7; } # and rc resolution itself errors
set -e
install_tracebloc_cli
local rc=$?
set +e
[ "$rc" -eq 0 ]
}
31 changes: 31 additions & 0 deletions scripts/tests/install-k8s.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ BeforeAll {
function docker { }
function helm { }
function k3d { }
function tracebloc { } # Test-TraceblocCli (#738) calls `& tracebloc version`;
# Pester can only Mock a command that already exists.
}

Describe "Get-BackendUrl" {
Expand Down Expand Up @@ -160,6 +162,35 @@ Describe "Install-TraceblocCli" {
}
}

Describe "Test-TraceblocCli" {
# Post-install self-verification (#738). Proves the CLI is usable from a fresh
# terminal and prints a VERIFIED next command, or the Windows-correct fix if a
# new shell wouldn't find it. Load-bearing property: NON-FATAL (never throws).
BeforeEach { Mock RefreshPath {} }

It "fresh-shell success: reports a VERIFIED verdict, not 'open a new terminal so'" {
Mock Has { $true } # a fresh shell resolves tracebloc
Mock tracebloc { "tracebloc 0.2.0" }
$out = Test-TraceblocCli 6>&1 | Out-String
$out | Should -Match "verified on your PATH"
$out | Should -Match "0.2.0" # real proof via `tracebloc version`
$out | Should -Not -Match "open a new terminal so" # the old, useless line is gone
}

It "CLI-missing-from-fresh-shell: prints an actionable hint (install dir)" {
Mock Has { $false } # installed, but not yet resolvable
$out = Test-TraceblocCli 6>&1 | Out-String
$out | Should -Match "open a new PowerShell window"
$out | Should -Match "Installed to:" # the exact location, not a vague hint
}

It "non-fatal: does not throw even if RefreshPath blows up" {
Mock RefreshPath { throw "registry unavailable" }
Mock Has { $false }
{ Test-TraceblocCli 6>&1 | Out-Null } | Should -Not -Throw
}
}

Describe "Get-WindowsArch" {
AfterEach { $env:PROCESSOR_ARCHITECTURE = "AMD64" }
It "AMD64 -> amd64" { $env:PROCESSOR_ARCHITECTURE = "AMD64"; Get-WindowsArch | Should -Be "amd64" }
Expand Down
Loading