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
44 changes: 36 additions & 8 deletions .github/workflows/release-helm-chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -225,29 +225,57 @@ jobs:
# BOTH the placeholder and the replacement (read from ENVIRON, never the
# command line) as plain strings — no regex, no backreference, no
# delimiter — so every byte of the tag lands verbatim.
#
# Stamp ONLY the `DEFAULT_REF=` assignment line. install.sh's fail-closed
# guard checks the SAME placeholder token to detect an un-stamped build;
# a global replace rewrote that guard to the tag too, so it fired on every
# correctly stamped install — "wasn't stamped" for everyone (the v1.8.1
# regression, backend#830). Anchoring to the assignment keeps the guard's
# sentinel intact.
TAG="$TAG" awk '
BEGIN { ph = "__TRACEBLOC_RELEASE_REF__"; rep = ENVIRON["TAG"] }
{
/^DEFAULT_REF=/ {
out = ""; line = $0
while ((p = index(line, ph)) > 0) {
out = out substr(line, 1, p - 1) rep
line = substr(line, p + length(ph))
}
print out line
print out line; next
}
{ print }
' scripts/install.stamped.sh > scripts/install.stamped.sh.new
mv scripts/install.stamped.sh.new scripts/install.sh
rm -f scripts/install.stamped.sh
# Confirm the stamp landed exactly and the placeholder is fully gone.
# Confirm the stamp landed on DEFAULT_REF (published installer pinned to
# this tag)...
grep -n 'DEFAULT_REF=' scripts/install.sh
if grep -q '__TRACEBLOC_RELEASE_REF__' scripts/install.sh; then
echo "::error::placeholder still present after stamping"; exit 1
fi
grep -qF "DEFAULT_REF=\"${TAG}\"" scripts/install.sh \
|| { echo "::error::DEFAULT_REF was not stamped to ${TAG}"; exit 1; }
# ...and that the surgical stamp left the fail-closed guard's sentinel
# untouched — a global replace would rewrite it to the tag and fire on
# every correct install (regression guard for the v1.8.1 clobber).
grep -qF '"$REF" == "__TRACEBLOC_RELEASE_REF__"' scripts/install.sh \
|| { echo "::error::unstamped-guard sentinel was clobbered by stamping"; exit 1; }

- name: Self-test the stamped installer parses and refuses bad refs
- name: Self-test the stamped installer parses, accepts its tag, and refuses bad refs
env:
TAG: ${{ github.event.release.tag_name }}
run: |
bash -n scripts/install.sh
# Un-pinned dev ref without the opt-in must fail closed (exit non-zero).
# POSITIVE PATH — the v1.8.1 regression. With no BRANCH/REF override the
# installer resolves to its stamped DEFAULT_REF (this tag) and MUST clear
# the fail-closed guard, i.e. NEVER print "wasn't stamped". It still exits
# non-zero further on (the signed manifest assets aren't attached to the
# release until the next step), so we assert only that it got past the
# guard. `timeout` bounds the network fetch so this can't hang the job.
out="$(timeout 120 bash scripts/install.sh </dev/null 2>&1 || true)"
if printf '%s\n' "$out" | grep -q "wasn't stamped"; then
echo "::error::stamped installer rejected its own pinned tag '${TAG}' — the unstamped-guard was clobbered"
printf '%s\n' "$out" | head -25
exit 1
fi
echo "stamped installer accepts its pinned tag '${TAG}' (cleared the guard)"
# NEGATIVE PATH — an un-pinned dev ref without the opt-in must fail closed.
if BRANCH=some-branch bash scripts/install.sh </dev/null >/dev/null 2>&1; then
echo "::error::stamped installer did not fail closed on a mutable BRANCH"; exit 1
fi
Expand Down
4 changes: 2 additions & 2 deletions client/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: client
description: A unified Helm chart for tracebloc on AKS, EKS, bare-metal, and OpenShift
type: application
version: 1.8.1
appVersion: "1.8.1"
version: 1.8.2
appVersion: "1.8.2"
keywords:
- tracebloc
- kubernetes
Expand Down
31 changes: 30 additions & 1 deletion scripts/lib/provision.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ _cli_supports_provisioning() {
return 0
}

# _prompt_tty: true when we can interactively prompt on the controlling terminal.
# `client create`'s own prompt can't fire (we redirect its output to the log), and
# under `curl | bash` stdin isn't the terminal — so read /dev/tty directly. Split
# out as a function so tests can force the non-interactive path deterministically.
_prompt_tty() { [[ -r /dev/tty && -w /dev/tty ]]; }

provision_client() {
step 3 5 "Sign in and provision this client"

Expand Down Expand Up @@ -81,9 +87,32 @@ 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.
local client_name="${TRACEBLOC_CLIENT_NAME:-}" client_location="${TRACEBLOC_CLIENT_LOCATION:-}"
if [[ -z "$client_name" ]] && _prompt_tty; then
printf '\n Name this machine (shown on your tracebloc dashboard): ' >/dev/tty
IFS= read -r client_name </dev/tty || true
if [[ -z "$client_location" ]]; then
printf ' Location zone for carbon reporting [e.g. DE, optional]: ' >/dev/tty
IFS= read -r client_location </dev/tty || true
fi
fi
# Trim surrounding whitespace from the (possibly typed) values.
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:]]}"}"
[[ -n "$client_name" ]] || error "A name for this machine is required to provision it. Re-run in a terminal to be prompted, or set TRACEBLOC_CLIENT_NAME (and optionally TRACEBLOC_CLIENT_LOCATION) for an unattended install."
# --name is required; pass --location only when we have one (the CLI defaults it
# otherwise). Build as an array so values with spaces survive intact.
local -a _create_args=(client create --yes --name "$client_name" --credential-file "$cred_file")
[[ -n "$client_location" ]] && _create_args+=(--location "$client_location")
# umask 077 so the credential file lands 0600 even if a future CLI build
# regresses on its explicit chmod — defense in depth (cli#104 already sets 0600).
if ! ( umask 077; tracebloc client create --yes --credential-file "$cred_file" ) >>"${LOG_FILE:-/dev/null}" 2>&1; then
if ! ( umask 077; tracebloc "${_create_args[@]}" ) >>"${LOG_FILE:-/dev/null}" 2>&1; then
rm -f "$cred_file" # remove any partial the failed create may have written
error "Provisioning the client failed — see ${LOG_FILE:-the install log}. Re-run to retry."
fi
Expand Down
2 changes: 1 addition & 1 deletion scripts/manifest.sha256
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ dd2e1f517b0f6bb665eacebfb7885c6a81443f69c2ebc772433a6baed53c7a8e scripts/lib/cl
5de1afac8b1062a7f8d0691b46ad39ccdc2287e0e422b53c48628ecfd47d852e scripts/lib/gpu-plugins.sh
a9ce18716756c113b750edcc45cca894bde81d234a4320cfb4655a266bdb070b scripts/lib/install-client-helm.sh
c5e0ed7aab268c91a131f09d753998cd5a2836a315b8d51428926f82387ad632 scripts/lib/install-cli.sh
d596c1f048cf9b40d5d4aaf57aca3846fa21af9a5ac083fb7e507fb6b78a8790 scripts/lib/provision.sh
fae91bd0f49d635cbe3034636625acd0fd589154de673f95300f82f6bc9bac53 scripts/lib/provision.sh
58bd83c06ca16e1d9346700a3f6bcafc67f20130f0bc1f160d2faedab19fd2cf scripts/lib/summary.sh
f22b3f57722feaf3a05a8527988c4404c27b772dd8ba8d6d4c6fe08077081a4a scripts/lib/diagnose.sh
34 changes: 33 additions & 1 deletion scripts/tests/provision.bats
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ setup() {
LOG_FILE="$(mktemp)"
HOST_DATA_DIR="$(mktemp -d)"
unset TRACEBLOC_VALUES_FILE TRACEBLOC_CLIENT_ID TRACEBLOC_CLIENT_PASSWORD \
TB_NAMESPACE TRACEBLOC_CLIENT_ADOPTED
TB_NAMESPACE TRACEBLOC_CLIENT_ADOPTED TRACEBLOC_CLIENT_LOCATION
# Tests are non-interactive — never touch a real /dev/tty — and carry a machine
# name so the mint tests clear provision.sh's required-name gate (the no-name
# test unsets it). CREATE_ARGS_FILE captures the `client create` argv to assert on.
_prompt_tty() { return 1; }
export TRACEBLOC_CLIENT_NAME="ci-machine"
CREATE_ARGS_FILE="$(mktemp)"
}

# A `tracebloc` stub: `login` succeeds; `client create` writes the given
Expand All @@ -34,6 +40,9 @@ _stub_tracebloc() {
[ "$1" = "login" ] && return 0
local f="" prev=""
for a in "$@"; do [ "$prev" = "--credential-file" ] && f="$a"; prev="$a"; done
# Record the real mint argv (the call that writes the credential file) so tests
# can assert --name / --location are passed through.
[ -n "$f" ] && printf '%s\n' "$*" >>"${CREATE_ARGS_FILE:-/dev/null}"
[ -n "$f" ] && printf '%b' "$CRED_LINES" > "$f"
return 0
}
Expand Down Expand Up @@ -133,3 +142,26 @@ _stub_tracebloc() {
[ "$status" -ne 0 ]
[ ! -f "${HOST_DATA_DIR}/client-credential.env" ]
}

@test "provision_client: mint passes --name (+ --location) through to client create" {
# `client create`'s output is redirected to the log, so it can't prompt and
# hard-requires --name; provision.sh must pass it. Multi-word name checks the
# array-based invocation keeps values with spaces intact.
export TRACEBLOC_CLIENT_NAME="lab box 3" TRACEBLOC_CLIENT_LOCATION="DE"
_stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n'
provision_client
run cat "$CREATE_ARGS_FILE"
[[ "$output" == *"--name lab box 3"* ]]
[[ "$output" == *"--location DE"* ]]
}

@test "provision_client: no name and no TTY to prompt is fatal (can't provision blind)" {
unset TRACEBLOC_CLIENT_NAME
_prompt_tty() { return 1; } # non-interactive: no terminal to prompt on
_stub_tracebloc 'TRACEBLOC_CLIENT_ID=1\nTRACEBLOC_CLIENT_PASSWORD=p\nTB_NAMESPACE=ns\n'
run provision_client
[ "$status" -ne 0 ]
[[ "$output" == *"name for this machine is required"* ]]
# and it must not have called client create (no argv recorded)
[ ! -s "$CREATE_ARGS_FILE" ]
}
Loading