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
60 changes: 57 additions & 3 deletions internal/enterprise/assets/install-advisor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
# advisor_register only pushes the chart + prereqs to Harbor; this installs
# the chart and confirms the advisor serves. Idempotent: safe to re-run.
#
# args: <framework> <advisor_lb_ip> <chart_version> [base_url] [console_pool] [console_account]
# args: <framework> <advisor_lb_ip> <chart_version> [base_url] [console_pool]
# [console_account] [provider_key_file] [provider_url]
set -uo pipefail
FRAMEWORK="${1:?framework name required}"
ADVISOR_LB_IP="${2:?advisor lb ip required}"
Expand All @@ -27,6 +28,10 @@ CONSOLE_POOL="${5:-}"
# provisions "advisor" for exactly this, so that is the default; empty leaves
# the console disabled, which is what every install did before.
CONSOLE_ACCOUNT="${6:-advisor}"
# The inference endpoint the chat surface calls, and a file holding its key.
# Both optional: given, they set it; omitted, whatever the deployment already
# uses is carried through.
PROVIDER_URL="${8:-}"
NS=cube-advisor

fail() { echo "ERROR: $*" >&2; exit 1; }
Expand Down Expand Up @@ -242,23 +247,71 @@ if [ "${#POOL_ADDRS[@]}" -ge 2 ]; then
WC_ARGS+=(--set webConsole.enabled=true --set webConsole.mode=address)
n=0
if [ -n "$INGRESS" ]; then
# /portal, not /: the framework ingress serves the portal there and
# Keycloak under /auth, and claims nothing at the root.
WC_ARGS+=(--set "webConsole.origins[$n].address=${POOL_ADDRS[$n]}" \
--set "webConsole.origins[$n].upstream=https://$INGRESS" \
--set "webConsole.origins[$n].path=/portal" \
--set "webConsole.origins[$n].targets[0]=cube-cmp" \
--set "webConsole.origins[$n].targets[1]=app-fw-idp")
n=$((n+1))
else
echo "warning: no ingress-lb on framework $FRAMEWORK; the CMP console origin is not configured" >&2
fi
# The node's own dashboard, which every enrolled node serves locally.
# The node's own dashboard, at the control VIP rather than loopback:
# 127.0.0.1:8080 is httpd, which answers 403 to everything.
#
# The three endpoints it links out to are companions rather than origins of
# their own: the dashboard sends the browser straight at them, so they must
# be reachable from one session, and they share its address and its LB.
WC_ARGS+=(--set "webConsole.origins[$n].address=${POOL_ADDRS[$n]}" \
--set "webConsole.origins[$n].upstream=http://127.0.0.1:8080" \
--set "webConsole.origins[$n].upstream=https://$CTRL" \
--set "webConsole.origins[$n].targets[0]=cube-cos")
c=0
for pair in "10443:cube-cos-idp" "9999:cube-cos-skyline" "7443:cube-cos-ceph"; do
port="${pair%%:*}"; name="${pair#*:}"
WC_ARGS+=(--set "webConsole.origins[$n].companions[$c].address=${POOL_ADDRS[$n]}:$port" \
--set "webConsole.origins[$n].companions[$c].upstream=https://$CTRL:$port" \
--set "webConsole.origins[$n].companions[$c].targets[0]=$name")
c=$((c+1))
done
echo "web console enabled on ${#POOL_ADDRS[@]} origin address(es)."
elif [ -n "$CONSOLE_POOL" ]; then
echo "warning: the console pool has fewer than 2 addresses; leaving the web console off" >&2
fi

# --- the inference provider ---
# Carried through a re-run, like the TLS and enrollment material above and for
# the same reason: this script renders the whole release, so a value it does
# not pass back is one helm removes.
PROVIDER_ARGS=()
PROVIDER_KEY_FILE="${7:-}"
if [ -n "$PROVIDER_KEY_FILE" ]; then
[ -r "$PROVIDER_KEY_FILE" ] || fail "cannot read the provider key file: $PROVIDER_KEY_FILE"
PROVIDER_ARGS+=(--set-file provider.key="$PROVIDER_KEY_FILE")
[ -n "$PROVIDER_URL" ] && PROVIDER_ARGS+=(--set provider.url="$PROVIDER_URL")
echo "provider set to ${PROVIDER_URL:-the chart default}."
else
# Read back what the deployment is already using. The key lives in the
# Secret; the URL is an argument on the container.
PREV_KEY="$($K -n "$NS" get secret cube-advisor-secrets -o jsonpath='{.data.providerKey}' 2>/dev/null | base64 -d)"
# By position in the argument list: a jsonpath range piped through grep
# silently produced nothing, carrying the key while the URL fell back.
PREV_URL="$($K -n "$NS" get deploy cube-advisor -o json 2>/dev/null | jq -r '
.spec.template.spec.containers[0].args as $a
| ($a | index("-provider-url")) as $i
| if $i == null then empty else $a[$i + 1] end')"
if [ -n "$PREV_KEY" ]; then
PROVIDER_KEEP="$(mktemp)"; TMPDIRS+=("$PROVIDER_KEEP")
printf '%s' "$PREV_KEY" > "$PROVIDER_KEEP"
PROVIDER_ARGS+=(--set-file provider.key="$PROVIDER_KEEP")
echo "carrying the existing provider key through the upgrade."
fi
if [ -n "$PREV_URL" ]; then
PROVIDER_ARGS+=(--set provider.url="$PREV_URL")
fi
fi

# --- the console's SSH user CA ---
# advisor-api generates one at startup when the chart passes no key, which
# suits a dev run and breaks a deployment: a node pins this CA in sshd and
Expand Down Expand Up @@ -293,6 +346,7 @@ helm upgrade --install cube-advisor "oci://$RURL/$RPROJ/cube-advisor" --version
"${ENROLL_ARGS[@]}" \
"${WC_ARGS[@]}" \
"${CONSOLE_ARGS[@]}" \
"${PROVIDER_ARGS[@]}" \
--kube-insecure-skip-tls-verify --insecure-skip-tls-verify --timeout 20m --wait=false \
|| fail "helm upgrade failed — the release is unchanged; do not uninstall to retry, that deletes the database"

Expand Down
62 changes: 62 additions & 0 deletions internal/enterprise/assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,65 @@ func TestAdvisorInstallPrintsTheConsoleCAForTheNodes(t *testing.T) {
"the console CA, so the console cannot work end to end")
}
}

// The script renders the whole release, so a value it does not pass back is a
// value helm removes. The provider was not carried, so every re-run reset the
// chat surface to the chart's placeholder endpoint and dropped the key —
// leaving a deployment that reports "chat enabled" and fails on the first
// question. Seen twice on the 1cc r630, the second time caused by deploying an
// unrelated fix.
func TestAdvisorInstallCarriesTheProviderThroughAnUpgrade(t *testing.T) {
for _, want := range []string{
"get secret cube-advisor-secrets -o jsonpath='{.data.providerKey}'",
"--set-file provider.key=",
"carrying the existing provider key through the upgrade",
} {
if !contains(installAdvisorScript, want) {
t.Errorf("the advisor installer does not %q; a re-run would reset the "+
"provider and the chat surface would fail on its first question", want)
}
}
}

// 127.0.0.1:8080 is httpd, which answers 403 to everything. The dashboard is
// nginx on the management address, so a cube-cos target pointed at loopback
// looks configured and refuses every request — which is exactly how it
// presented: a proxied tab showing "403 Forbidden" with nothing in any log.
func TestAdvisorConsolePointsCubeCosAtTheDashboard(t *testing.T) {
if contains(installAdvisorScript, "upstream=http://127.0.0.1:8080") {
t.Error("cube-cos still points at 127.0.0.1:8080, which is httpd and 403s; " +
"the dashboard is served on the control address")
}
if !contains(installAdvisorScript, `webConsole.origins[$n].upstream=https://$CTRL`) {
t.Error("cube-cos does not point at the control address the rest of this " +
"script already resolves")
}
}

// The dashboard sends the browser at Keycloak on :10443 of the same address,
// so that endpoint has to be part of the same openable group — a companion,
// The dashboard links out to Keycloak, Skyline and the Ceph dashboard, each
// on another port of the same address. Every one has to be part of the same
// openable group -- a companion, not a separate origin the session does not
// carry, which would answer "no such origin" the moment the browser followed
// the link.
func TestAdvisorInstallGivesTheDashboardItsCompanions(t *testing.T) {
for _, want := range []string{
`"10443:cube-cos-idp" "9999:cube-cos-skyline" "7443:cube-cos-ceph"`,
"webConsole.origins[$n].companions[$c].address=${POOL_ADDRS[$n]}:$port",
"webConsole.origins[$n].companions[$c].upstream=https://$CTRL:$port",
"webConsole.origins[$n].companions[$c].targets[0]=$name",
} {
if !contains(installAdvisorScript, want) {
t.Errorf("install-advisor.sh does not set %s", want)
}
}
}

// The framework ingress serves the portal under /portal and claims nothing at
// the root, so an origin opened at / lands on the ingress's own 404.
func TestAdvisorInstallOpensTheCmpPortalAtItsOwnPath(t *testing.T) {
if !contains(installAdvisorScript, "webConsole.origins[$n].path=/portal") {
t.Error("install-advisor.sh does not give the CMP origin its landing path")
}
}
Loading