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
65 changes: 18 additions & 47 deletions scripts/lib/provision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ _report_create_failure() {
echo ""
if grep -qiE 'location.*not a valid choice' "$out" 2>/dev/null; then
warn "\"${loc:-that location}\" isn't a recognized carbon zone — the client wasn't created."
hint "Re-run and press Enter to accept the detected zone, or use a zone code"
hint "like DE, FR, US, GB (all codes: https://api.electricitymap.org/v3/zones)."
hint "The zone is auto-derived from this machine's timezone; to pin one, set"
hint "TRACEBLOC_CLIENT_LOCATION=<code> (e.g. DE, FR, US, GB — all codes:"
hint "https://api.electricitymap.org/v3/zones) and re-run."
return 0
fi
errline="$(grep -aE 'Error:|HTTP [0-9][0-9][0-9]|refused|timed? ?out|unauthorized|forbidden|denied' "$out" 2>/dev/null | head -4)"
Expand Down Expand Up @@ -231,18 +232,17 @@ provision_client() {
# between mint and the explicit removal below — the secret must never linger.
_PROVISION_CRED_FILE="$cred_file"
rm -f "$cred_file"
# Name + location for this machine (RFC-0001 §6.4: "name this machine + confirm
# its location"). `client create` would prompt for these, but we redirect its
# output to the log below (the credential must never reach the terminal), so it
# can't — and it hard-requires --name when it can't prompt. Collect them here and
# pass explicitly. Precedence: env override (unattended) > interactive prompt on
# /dev/tty (works under `curl | bash`, whose stdin isn't the terminal) > fail closed.
# Name this machine, then provision. `client create` would prompt for the name
# itself, but we redirect its output to the log below (the credential must never
# reach the terminal), so it can't — collect the name here and pass it explicitly.
# Precedence: TRACEBLOC_CLIENT_NAME (unattended) > interactive prompt on /dev/tty
# (works under `curl | bash`, whose stdin isn't the terminal) > fail closed.
#
# INTERIM — KEEP THIS PROMPT. The locked run-through drops the name/location
# question (machine auto-named <firstname>-NN, location auto-derived), but the
# DEPLOYED CLI still hard-requires --name without a TTY (backend#992), so
# dropping it now would break `curl | bash`. Slated for removal when cli#137
# ships the auto-name + optional-location `client create`; until then it stays.
# Location is NEVER prompted (RFC-0001 §6.4 target spec; cli#137). The CLI's
# `client create` now treats --location as optional, so we auto-derive the carbon
# zone from the system timezone below (silent, no network) and, when detection
# yields nothing, provision with NO location rather than asking. A pinned
# TRACEBLOC_CLIENT_LOCATION still overrides for unattended installs.
local client_name="${TRACEBLOC_CLIENT_NAME:-}" client_location="${TRACEBLOC_CLIENT_LOCATION:-}"
if [[ -z "$client_name" ]] && _prompt_tty; then
# Read the name from the terminal, RETRYING on an empty line instead of
Expand All @@ -261,47 +261,18 @@ provision_client() {
[[ -n "$client_name" ]] && break # captured a name (incl. a no-newline partial)
[[ "$_name_read_ok" == 0 ]] && break # EOF / no interactive input — retrying won't help
done
if [[ -z "$client_location" ]]; then
local _loc_detect _loc_zone _loc_tz _loc_try
_loc_detect="$(_detect_location_zone)"
_loc_zone="${_loc_detect%% *}"; _loc_tz="${_loc_detect#* }"
if [[ -n "$_loc_zone" ]]; then
printf ' Location for carbon reporting — detected %s (from your timezone %s).\n' "$_loc_zone" "$_loc_tz" >/dev/tty 2>/dev/null || true
printf ' Press Enter to use it, or type another zone code: ' >/dev/tty 2>/dev/null || true
IFS= read -r client_location <"$TB_TTY" || true
# Trim BEFORE the non-empty check so a whitespace-only answer counts as
# "just pressed Enter" and falls back to the detected zone.
client_location="${client_location#"${client_location%%[![:space:]]*}"}"; client_location="${client_location%"${client_location##*[![:space:]]}"}"
[[ -n "$client_location" ]] || client_location="$_loc_zone"
else
# No detection on this machine. The released CLI still REQUIRES a zone
# (client create fails without --location when it can't prompt), so a
# blank answer here would doom the create — ask until non-empty.
# Optional-location ships with the next CLI release; then this prompt
# goes away entirely (spec: silent, no location).
for _loc_try in 1 2 3; do
printf ' Location zone for carbon reporting (e.g. DE): ' >/dev/tty 2>/dev/null || true
IFS= read -r client_location <"$TB_TTY" || true
# Trim BEFORE the non-empty check: a whitespace-only answer must NOT
# satisfy it, break the loop, and skip the "required" error below.
client_location="${client_location#"${client_location%%[![:space:]]*}"}"; client_location="${client_location%"${client_location##*[![:space:]]}"}"
[[ -n "$client_location" ]] && break
done
[[ -n "$client_location" ]] || error "A location zone is required to provision this client. Re-run and enter a zone code (e.g. DE), or set TRACEBLOC_CLIENT_LOCATION for unattended installs."
fi
fi
fi
# Trim surrounding whitespace from the (possibly typed) values FIRST, so a
# whitespace-only answer (spaces then Enter) counts as "unset" for the silent
# fallback below instead of slipping through as a non-empty, doomed location.
client_name="${client_name#"${client_name%%[![:space:]]*}"}"; client_name="${client_name%"${client_name##*[![:space:]]}"}"
client_location="${client_location#"${client_location%%[![:space:]]*}"}"; client_location="${client_location%"${client_location##*[![:space:]]}"}"

# No location yet (unattended env-name path, a TTY-less run, or a whitespace-only
# answer): fall back to the timezone-detected zone silently — the released CLI
# requires a zone, and this matches the target spec (silent auto-derive, never
# prompt). The detected code is already whitespace-free. If detection also comes
# up empty, the create's own error is surfaced by _report_create_failure below
# No location from TRACEBLOC_CLIENT_LOCATION: derive the carbon zone from the
# system timezone silently — never prompted, matching the target spec. The
# detected code is already whitespace-free. If detection also comes up empty we
# leave client_location unset and provision with NO --location (cli#137 makes it
# optional); a rejected zone still surfaces via _report_create_failure below
# rather than failing blind here.
if [[ -z "$client_location" ]]; then
client_location="$(_detect_location_zone)"; client_location="${client_location%% *}"
Expand Down
2 changes: 1 addition & 1 deletion scripts/manifest.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ b9a1f3be69c0ea5fe78555e7da101b45e485960795a9881126361e36d6c740af scripts/lib/se
1790633a102040537707ba868ca350bee6a6d78dbbd8858bd51d2121d79f5a35 scripts/lib/gpu-plugins.sh
182f5d76f45590596ccf4becfd489a2f4124af634c81e02d650bcc4a52ab769a scripts/lib/install-client-helm.sh
7d180a17a6308a6039c6067bd7429be7af39c2af9cd504ee40d0092deb530151 scripts/lib/install-cli.sh
ea0d8a0e8e460ea06b90bfce4f436c635bc0a8e215d6c46e4299d4ae75511928 scripts/lib/provision.sh
bf283e1bb750348ea0b81d484f336c909017b2eb71ddeb848221dad91538728b scripts/lib/provision.sh
e2ea63d844e6649f1d3aaae9fd4733845a1a39df37d68abbaeda00330f9e1c7e scripts/lib/assess.sh
dfdaedb3cc8cfd3b5778efe388202dc14874b5a7d8cc081296c20db7c62a4542 scripts/lib/summary.sh
153ebc0d46157cfb5b9d6eeed2c534337f6c780a5dce6d1410daa47fb7d8f220 scripts/lib/diagnose.sh
Expand Down
40 changes: 38 additions & 2 deletions scripts/tests/provision.bats
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ _stub_tracebloc() {
# RETRY past empty lines and still capture the real name. TB_TTY=/dev/stdin
# lets us feed the queued blanks + the name on stdin.
unset TRACEBLOC_CLIENT_NAME
export TRACEBLOC_CLIENT_LOCATION="DE" # skip the location sub-prompt; isolate the name read
export TRACEBLOC_CLIENT_LOCATION="DE" # pin the zone (skip the timezone auto-derive); isolate the name read
_prompt_tty() { return 0; } # a terminal IS available
TB_TTY=/dev/stdin
_stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n'
Expand All @@ -290,7 +290,7 @@ _stub_tracebloc() {
# no interactive input (EOF) — e.g. a non-PTY ssh / IDE terminal. Must NOT loop
# or hang, and must surface the set-TRACEBLOC_CLIENT_NAME guidance.
unset TRACEBLOC_CLIENT_NAME
export TRACEBLOC_CLIENT_LOCATION="DE"
export TRACEBLOC_CLIENT_LOCATION="DE" # pin the zone; the read under test is the name
_prompt_tty() { return 0; }
TB_TTY=/dev/stdin
_stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n'
Expand All @@ -300,6 +300,42 @@ _stub_tracebloc() {
[ ! -s "$CREATE_ARGS_FILE" ]
}

@test "provision_client: interactive install auto-derives location from the timezone — never prompts (#354)" {
# Only the name is prompted. With no TRACEBLOC_CLIENT_LOCATION the zone is derived
# silently from the system timezone and passed as --location; the user is never
# asked. Feed ONLY a name on stdin — a lingering location prompt would block on the
# absent second line and the test would hang/fail.
unset TRACEBLOC_CLIENT_NAME TRACEBLOC_CLIENT_LOCATION
_prompt_tty() { return 0; }
TB_TTY=/dev/stdin
_detect_location_zone() { printf 'FR Europe/Paris\n'; } # detection succeeds
_stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n'
run provision_client <<< $'MyBox\n'
[ "$status" -eq 0 ]
[[ "$output" != *"carbon reporting"* ]] # the location prompt is gone
run cat "$CREATE_ARGS_FILE"
[[ "$output" == *"--name MyBox"* ]]
[[ "$output" == *"--location FR"* ]] # zone came from the timezone, silently
}

@test "provision_client: interactive install with no detectable zone provisions with NO location — never prompts (#354)" {
# Timezone detection yields nothing. The installer must NOT ask for a zone (the old
# required-entry loop is gone) — it provisions with no --location, which the CLI
# now accepts (cli#137), and the backend records the client with no location.
unset TRACEBLOC_CLIENT_NAME TRACEBLOC_CLIENT_LOCATION
_prompt_tty() { return 0; }
TB_TTY=/dev/stdin
_detect_location_zone() { return 0; } # nothing detected
_stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n'
run provision_client <<< $'MyBox\n'
[ "$status" -eq 0 ] # no location is not fatal anymore
[[ "$output" != *"carbon reporting"* ]] # never prompted
[[ "$output" != *"location zone is required"* ]]
run cat "$CREATE_ARGS_FILE"
[[ "$output" == *"--name MyBox"* ]]
[[ "$output" != *"--location"* ]] # provisioned with no location
}

# ── cli#141: the #303 pre-flight's grep contract with `tracebloc client list` ──
# _account_owns_namespace shells out to `tracebloc client list --plain` and greps
# the output for `namespace=<ns>([[:space:]]|$)` to decide ownership. `client list`
Expand Down
Loading