#!/usr/bin/env bash set -euo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=lib/env.sh source "$HERE/lib/env.sh" ENV_ALIAS="${NDO_ENV:-}" # -e/--env may appear anywhere; strip it before dispatch. ARGS=() while [ $# -gt 0 ]; do case "$1" in -e|--env) ENV_ALIAS="$2"; shift 2 ;; *) ARGS+=("$1"); shift ;; esac done set -- "${ARGS[@]:-}" NDO_REALM="${NDO_REALM:-default}" NDO_CLIENT="${NDO_CLIENT:-frontend}" NDO_USER="${NDO_USER:?Set NDO_USER through an approved configuration source before using authenticated API commands}" NDO_PASS="${NDO_PASS:?Set NDO_PASS through an approved secret source before using authenticated API commands}" usage() { cat <<'USAGE' ndo-api.sh — drive the NDO BOM API for live repro, on any registered env. Every command needs a target env: -e (or NDO_ENV=). Auth is automatic: a keycloak password-grant token is minted per env and refreshed on expiry (~15 min). Token cache: ~/.cache/ndo-repro/token-. Env: env ls list registered envs env discover rescan kube contexts, rebuild the registry env show resolved context / namespace / gateway for -e API: login mint a fresh token now token save an externally-supplied bearer token whoami check auth (200 = ok) opdef GET operation-definition for an op key initiate [bodyfile] POST initiate, prints operation-request-id perform POST /{rid}/perform with a wrapped {method,url,body} prepare POST /{rid}/prepare?key= with body file get [innerBodyJson] perform a GET against /consolidated-inventory-manager report replacement report (resolved/unresolved) target replacement target tree avail [type] available-target-values (type default l2_link) Cluster: logs [since] [grep] tail+denoise logs (default since=10m) image deployed image of -v1 pods pod phase/restarts for -v1 Examples: ndo-api.sh env ls ndo-api.sh -e shared-244 whoami ndo-api.sh -e oss-01/dev-2 report 21dec51b-f9cb-41fe-af94-512c0921036b ndo-api.sh -e oss-01/dev-2 logs consolidated-inventory-manager 15m '\[UNM-231239\]' USAGE } case "${1:-}" in ""|-h|--help|help) usage; exit 0 ;; env) case "${2:-ls}" in ls|list) env_list; exit 0 ;; discover) env_discover; exit 0 ;; show) env_resolve "$ENV_ALIAS"; printf 'alias : %s\ncontext : %s\nns : %s\ngateway : %s\n' \ "$ENV_ALIAS" "$NDO_CTX" "$NDO_NS" "$NDO_GW"; exit 0 ;; *) echo "env: ls | discover | show" >&2; exit 2 ;; esac ;; esac env_resolve "$ENV_ALIAS" GW="${NDO_GW_OVERRIDE:-$NDO_GW}" BOM="$GW/business-operation-manager/v1" mkdir -p "$NDO_CACHE" TOKFILE="${NDO_TOKEN_FILE:-$NDO_CACHE/token-$(tr '/' '_' <<<"$ENV_ALIAS").txt}" mint() { local out out=$(curl -sk -X POST "$GW/auth/realms/$NDO_REALM/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=password" --data-urlencode "client_id=$NDO_CLIENT" \ --data-urlencode "username=$NDO_USER" --data-urlencode "password=$NDO_PASS") printf '%s' "$out" | python3 -c "import sys,json;d=json.load(sys.stdin);open('$TOKFILE','w').write(d['access_token']) if 'access_token' in d else sys.exit('mint failed: '+json.dumps(d)[:200])" || return 1 chmod 600 "$TOKFILE" } token_valid() { [ -s "$TOKFILE" ] || return 1 python3 - "$TOKFILE" <<'PY' 2>/dev/null import sys,base64,json,time t=open(sys.argv[1]).read().strip() p=t.split('.')[1]; p+='='*(-len(p)%4) exp=json.loads(base64.urlsafe_b64decode(p)).get('exp',0) sys.exit(0 if exp-time.time()>30 else 1) PY } ensure_token() { token_valid || mint; } tok() { cat "$TOKFILE"; } auth() { ensure_token >&2 || { echo "auth failed on $ENV_ALIAS" >&2; exit 1; }; echo "Authorization: Bearer $(tok)"; } K() { kubectl --context="$NDO_CTX" -n "$NDO_NS" "$@"; } # Services use either app=-v1 or name=-v1 depending on the chart. selector_for() { local svc="$1" l for l in "app=$svc-v1" "name=$svc-v1" "app=$svc" "name=$svc"; do [ -n "$(K get pod -l "$l" -o name 2>/dev/null)" ] && { echo "$l"; return 0; } done echo "no pods for $svc (tried app=/name= selectors) in $NDO_NS" >&2 return 1 } case "${1:-}" in token) printf '%s' "$2" > "$TOKFILE"; chmod 600 "$TOKFILE"; echo "saved to $TOKFILE"; ;; login) mint && echo "minted ($NDO_USER, realm=$NDO_REALM, env=$ENV_ALIAS) → $TOKFILE" ;; whoami) curl -sk -o /dev/null -w "HTTP %{http_code}\n" -H "$(auth)" "$BOM/operation-definition?key=nc_op_ci_as-is_hw-component.replacement" ;; opdef) curl -sk -H "$(auth)" "$BOM/operation-definition?key=$2" ;; initiate) body="${3:-{} }"; [ -f "${3:-}" ] && body="@$3" curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/initiate?key=$2" -d "$body" ;; perform) inner="$3"; [ -f "$3" ] && inner="@$3" curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/perform" -d "$inner" ;; prepare) curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/prepare?key=$3" -d "@$4" ;; get) rid="$2"; path="$3"; innerbody="${4:-}" if [ -n "$innerbody" ]; then req="{\"method\":\"GET\",\"url\":\"/consolidated-inventory-manager$path\",\"body\":$innerbody}"; else req="{\"method\":\"GET\",\"url\":\"/consolidated-inventory-manager$path\"}"; fi curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$rid/perform" -d "$req" ;; report) curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/perform" \ -d '{"method":"GET","url":"/consolidated-inventory-manager/v3/replacement/report"}' \ | python3 -c "import sys,json;i=json.load(sys.stdin).get('action-report',{}).get('results',{}).get('impact',[]);print(json.dumps(i,indent=1))" ;; target) curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/perform" \ -d '{"method":"GET","url":"/consolidated-inventory-manager/v3/replacement/target"}' ;; avail) typ="${5:-l2_link}" curl -sk -X POST -H "$(auth)" -H 'Content-Type: application/json' "$BOM/operation-request/$2/perform" \ -d "{\"method\":\"GET\",\"url\":\"/consolidated-inventory-manager/v3/replacement/mapping/available-target-values\",\"body\":{\"impact-type\":\"$typ\",\"impacted-entity-mkey\":\"$3\",\"ref-endpoint-mkey\":\"$4\"}}" \ | python3 -c "import sys,json;r=json.load(sys.stdin).get('action-report',{}).get('results',{});print('total',r.get('total'),'values',len(r.get('available-values',[])))" ;; logs) svc="$2"; since="${3:-10m}"; pat="${4:-}" SEL=$(selector_for "$svc") || exit 1 P=$(K get pod -l "$SEL" -o jsonpath='{.items[0].metadata.name}') K logs "$P" --since="$since" 2>/dev/null \ | sed -E 's/\[(tenant_id|thread|originating_bi_id|traceId|spanId|request_id)=[^]]*\] ?//g' \ | { [ -n "$pat" ] && grep -aE "$pat" || cat; } ;; image) K get deploy "$2-v1" -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}' ;; pods) SEL=$(selector_for "$2") || exit 1 K get pod -l "$SEL" -o custom-columns='POD:.metadata.name,PHASE:.status.phase,READY:.status.containerStatuses[0].ready,RESTARTS:.status.containerStatuses[0].restartCount,IMAGE:.status.containerStatuses[0].image' ;; *) echo "unknown cmd: $1"; usage; exit 1 ;; esac