#!/bin/bash # Shared primitives for HTTPS Gateway listener and cert-manager certificate lifecycle. # # Source from component *-lib.sh scripts. Requires _kubectl to be defined # in the calling context (standard in all catalog component libs). # # Functions: # _lib_gateway_patch_https # _lib_gateway_remove_https # _lib_ensure_certificate [extra_domain...] # _lib_create_cf_secret # Upsert an HTTPS listener on a Gateway. # If the listener already exists, ensures certificateRefs namespace matches cert_ns. # Idempotent: safe to call on every update. _lib_gateway_patch_https() { local gw_name="$1" gw_ns="$2" listener_name="$3" domain="$4" tls_secret="$5" cert_ns="$6" # Opt-out for gateways that own their listeners declaratively (adr-007). When a # consumer sets MANAGE_GATEWAY_LISTENER=false, the listener is a property of the # gateway's own manifest (e.g. private_gateway.https_listeners) and must NOT be # imperatively patched here. Default true preserves behaviour for every workspace # whose gateway does not declare the listener. if [ "${MANAGE_GATEWAY_LISTENER:-true}" != "true" ]; then echo " [skip] listener ${listener_name} owned declaratively by the gateway (MANAGE_GATEWAY_LISTENER=false)" return 0 fi [ -z "$domain" ] && { echo " [skip] domain not set — skipping gateway HTTPS patch"; return 0; } # kubectl resolves the lookup server-side (jsonpath filter) — no JSON parser on the node. local exists="" [ -n "$(_kubectl get gateway "$gw_name" -n "$gw_ns" \ -o "jsonpath={.spec.listeners[?(@.name=='${listener_name}')].name}" 2>/dev/null)" ] \ && exists="yes" if [ "${exists:-no}" = "yes" ]; then local current_cert_ns current_cert_ns=$(_kubectl get gateway "$gw_name" -n "$gw_ns" \ -o "jsonpath={.spec.listeners[?(@.name=='${listener_name}')].tls.certificateRefs[0].namespace}" 2>/dev/null) if [ "${current_cert_ns}" = "${cert_ns}" ]; then echo " [skip] gateway listener ${listener_name} exists (cert ns: ${cert_ns})" return 0 fi # Listener array index for the JSON-patch path: kubectl streams names in order, # bash counts to the match — no JSON parser. local idx="" _i=0 _n while IFS= read -r _n; do [ "$_n" = "$listener_name" ] && { idx="$_i"; break; } _i=$((_i + 1)) done < <(_kubectl get gateway "$gw_name" -n "$gw_ns" \ -o 'jsonpath={range .spec.listeners[*]}{.name}{"\n"}{end}' 2>/dev/null) local cert_patch cert_patch=$(printf '[{"op":"replace","path":"/spec/listeners/%s/tls/certificateRefs","value":[{"group":"","kind":"Secret","name":"%s","namespace":"%s"}]}]' \ "$idx" "$tls_secret" "$cert_ns") _kubectl patch gateway "$gw_name" -n "$gw_ns" --type=json -p "$cert_patch" echo " [ok] gateway ${gw_name}: cert ns updated ${current_cert_ns} → ${cert_ns} on ${listener_name}" return 0 fi local patch patch=$(printf '[{"op":"add","path":"/spec/listeners/-","value":{"name":"%s","port":443,"protocol":"HTTPS","hostname":"%s","tls":{"mode":"Terminate","certificateRefs":[{"group":"","kind":"Secret","name":"%s","namespace":"%s"}]},"allowedRoutes":{"namespaces":{"from":"All"}}}}]' \ "$listener_name" "$domain" "$tls_secret" "$cert_ns") _kubectl patch gateway "$gw_name" -n "$gw_ns" --type=json -p "$patch" echo " [ok] gateway ${gw_name}: HTTPS listener ${listener_name} added for ${domain}" } # Remove an HTTPS listener from a Gateway by name. _lib_gateway_remove_https() { local gw_name="$1" gw_ns="$2" listener_name="$3" # Symmetric with _lib_gateway_patch_https: when the gateway owns the listener # declaratively, deleting the consumer must not strip it from the shared gateway. if [ "${MANAGE_GATEWAY_LISTENER:-true}" != "true" ]; then echo " [skip] listener ${listener_name} owned declaratively by the gateway (MANAGE_GATEWAY_LISTENER=false)" return 0 fi local idx="" _i=0 _n while IFS= read -r _n; do [ "$_n" = "$listener_name" ] && { idx="$_i"; break; } _i=$((_i + 1)) done < <(_kubectl get gateway "$gw_name" -n "$gw_ns" \ -o 'jsonpath={range .spec.listeners[*]}{.name}{"\n"}{end}' 2>/dev/null) [ -z "$idx" ] && { echo " [skip] gateway listener ${listener_name} not found"; return 0; } _kubectl patch gateway "$gw_name" -n "$gw_ns" --type=json \ -p "[{\"op\":\"remove\",\"path\":\"/spec/listeners/${idx}\"}]" echo " [ok] gateway ${gw_name}: HTTPS listener ${listener_name} removed" } # Ensure a cert-manager Certificate resource exists in the given namespace. # Idempotent: creates only if absent. Does not overwrite existing certificates. # Extra domains beyond the first are added as additional dnsNames. _lib_ensure_certificate() { local cert_name="$1" cert_ns="$2" domain="$3" cluster_issuer="$4" shift 4 local extra_domains=("$@") local existing existing=$(_kubectl get certificate "$cert_name" -n "$cert_ns" --ignore-not-found -o name 2>/dev/null) if [ -n "$existing" ]; then echo " [skip] certificate ${cert_name} already exists in ${cert_ns}" return 0 fi # Build the dnsNames YAML list (primary domain + any extras) in pure bash — # one indented ' - "domain"' line per non-empty entry, no trailing newline. local dns_names="" _d for _d in "$domain" "${extra_domains[@]}"; do [ -z "$_d" ] && continue dns_names="${dns_names} - \"${_d}\""$'\n' done dns_names="${dns_names%$'\n'}" _kubectl apply -f - </dev/null 2>&1; then echo " [skip] CF token secret ${secret_name} already exists in ${cert_manager_ns}" return 0 fi _kubectl create secret generic "$secret_name" \ --namespace "$cert_manager_ns" \ --from-literal=api-token="$cf_token" echo " [ok] CF token secret ${secret_name} created in ${cert_manager_ns}" }