Refactor shell scripts for manifest rendering via prole_render_manifest function, introduce dynamic port forwarding configuration, and improve handling for k3d compatibility.

This commit is contained in:
chrisfu 2026-02-06 21:22:26 -08:00
parent 63e6f18b5b
commit 42ec9bb7ab
10 changed files with 145 additions and 18 deletions

1
.gitignore vendored
View File

@ -41,6 +41,7 @@
/prole/backup/ /prole/backup/
# Secrets and local config # Secrets and local config
*.kubeconfig
etc/secrets/ etc/secrets/
prole/conf/ prole/conf/
*-password.txt *-password.txt

View File

@ -3,6 +3,6 @@
<mapping id="dashboard" namespace="kubernetes-dashboard" target="svc/kubernetes-dashboard-kong-proxy" address="127.0.0.1" hostPort="8443" servicePort="443" protocol="TCP" description="Kubernetes Dashboard (https://127.0.0.1:8443)"/> <mapping id="dashboard" namespace="kubernetes-dashboard" target="svc/kubernetes-dashboard-kong-proxy" address="127.0.0.1" hostPort="8443" servicePort="443" protocol="TCP" description="Kubernetes Dashboard (https://127.0.0.1:8443)"/>
<mapping id="prometheus" namespace="default" target="svc/prometheus-community-kube-prometheus" address="127.0.0.1" hostPort="9090" servicePort="9090" protocol="TCP" description="Prometheus UI (http://127.0.0.1:9090)"/> <mapping id="prometheus" namespace="default" target="svc/prometheus-community-kube-prometheus" address="127.0.0.1" hostPort="9090" servicePort="9090" protocol="TCP" description="Prometheus UI (http://127.0.0.1:9090)"/>
<mapping id="grafana" namespace="${NAMESPACE}" target="svc/grafana-prole" address="0.0.0.0" hostPort="3000" servicePort="80" protocol="TCP" description="Grafana UI (http://127.0.0.1:3000)"/> <mapping id="grafana" namespace="${NAMESPACE}" target="svc/grafana-prole" address="0.0.0.0" hostPort="3000" servicePort="80" protocol="TCP" description="Grafana UI (http://127.0.0.1:3000)"/>
<mapping id="postgres" namespace="${NAMESPACE}" target="svc/prole-db-rw" address="0.0.0.0" hostPort="5432" servicePort="5432" protocol="TCP" description="PostgreSQL (primary) (127.0.0.1:5432)"/> <mapping id="postgres" namespace="${NAMESPACE}" target="svc/prole-db-rw" address="0.0.0.0" hostPort="5432" servicePort="5432" protocol="TCP" description="PostgreSQL (primary) (0.0.0.0:5432)"/>
<mapping id="openbao" namespace="${NAMESPACE}" target="svc/openbao" address="127.0.0.1" hostPort="8200" servicePort="8200" protocol="TCP" description="OpenBao UI (http://127.0.0.1:8200)"/> <mapping id="openbao" namespace="${NAMESPACE}" target="svc/openbao" address="127.0.0.1" hostPort="8200" servicePort="8200" protocol="TCP" description="OpenBao UI (http://127.0.0.1:8200)"/>
</portMappings> </portMappings>

View File

@ -324,12 +324,28 @@ apply_barman_objectstore_if_present() {
return 0 return 0
fi fi
if kubectl get crd objectstores.barmancloud.cnpg.io >/dev/null 2>&1; then if kubectl get crd objectstores.barmancloud.cnpg.io >/dev/null 2>&1; then
kubectl apply -n "$NAMESPACE" -f "$BARMAN_OBJECTSTORE_MANIFEST" prole_render_manifest "$BARMAN_OBJECTSTORE_MANIFEST" | kubectl apply -n "$NAMESPACE" -f -
else else
echo "WARN: Barman Cloud ObjectStore CRD not found; skipping $BARMAN_OBJECTSTORE_MANIFEST." echo "WARN: Barman Cloud ObjectStore CRD not found; skipping $BARMAN_OBJECTSTORE_MANIFEST."
fi fi
} }
apply_prole_manifest_file() {
local file="$1"
local output=""
if output=$(prole_render_manifest "$file" | kubectl apply -n "$NAMESPACE" -f - 2>&1); then
printf '%s\n' "$output"
return 0
fi
if [[ "${PROLE_MODE:-}" == "k3d" && "$(basename "$file")" == "garage-statefulset.yaml" ]] \
&& echo "$output" | grep -q "updates to statefulset spec"; then
echo "WARN: Garage StatefulSet immutable in k3d; skipping apply."
return 0
fi
echo "$output" >&2
return 1
}
ensure_prole_stack_resources() { ensure_prole_stack_resources() {
echo "Applying CloudNative-PG cluster and related resources ..." echo "Applying CloudNative-PG cluster and related resources ..."
ensure_grafana_admin_secret ensure_grafana_admin_secret
@ -346,7 +362,7 @@ ensure_prole_stack_resources() {
continue continue
;; ;;
esac esac
kubectl apply -n "$NAMESPACE" -f "$file" apply_prole_manifest_file "$file"
done done
apply_barman_objectstore_if_present apply_barman_objectstore_if_present
apply_cnpg_cluster_manifest "$CNPG_MANIFEST_OVERRIDE" apply_cnpg_cluster_manifest "$CNPG_MANIFEST_OVERRIDE"
@ -359,7 +375,7 @@ ensure_prole_stack_resources() {
continue continue
;; ;;
esac esac
kubectl apply -n "$NAMESPACE" -f "$file" apply_prole_manifest_file "$file"
done done
apply_barman_objectstore_if_present apply_barman_objectstore_if_present
apply_cnpg_cluster_manifest "$CNPG_MANIFEST" apply_cnpg_cluster_manifest "$CNPG_MANIFEST"
@ -390,7 +406,7 @@ apply_cnpg_cluster_manifest() {
local i out local i out
for ((i=1; i<=attempts; i++)); do for ((i=1; i<=attempts; i++)); do
if out=$(kubectl apply -n "$NAMESPACE" -f "$manifest" 2>&1); then if out=$(prole_render_manifest "$manifest" | kubectl apply -n "$NAMESPACE" -f - 2>&1); then
printf '%s\n' "$out" printf '%s\n' "$out"
return 0 return 0
fi fi
@ -864,7 +880,7 @@ case "$ACTION" in
exit 1 exit 1
fi fi
echo "Starting CloudNative-PG cluster from $CNPG_MANIFEST in namespace $NAMESPACE..." echo "Starting CloudNative-PG cluster from $CNPG_MANIFEST in namespace $NAMESPACE..."
kubectl apply -n "$NAMESPACE" -f "$CNPG_MANIFEST" prole_render_manifest "$CNPG_MANIFEST" | kubectl apply -n "$NAMESPACE" -f -
;; ;;
stop) stop)
ensure_tools ensure_tools

View File

@ -97,7 +97,18 @@ ensure_secrets() {
apply_manifests() { apply_manifests() {
for f in "${GARAGE_FILES[@]}"; do for f in "${GARAGE_FILES[@]}"; do
if [[ -f "$f" ]]; then if [[ -f "$f" ]]; then
kubectl apply -n "$NAMESPACE" -f "$f" local output=""
if output=$(prole_render_manifest "$f" | kubectl apply -n "$NAMESPACE" -f - 2>&1); then
printf '%s\n' "$output"
else
if [[ "${PROLE_MODE:-}" == "k3d" && "$(basename "$f")" == "garage-statefulset.yaml" ]] \
&& echo "$output" | grep -q "updates to statefulset spec"; then
echo "WARN: Garage StatefulSet immutable in k3d; skipping apply."
continue
fi
echo "$output" >&2
exit 1
fi
else else
echo "ERROR: Missing manifest: $f" >&2 echo "ERROR: Missing manifest: $f" >&2
exit 1 exit 1
@ -134,6 +145,26 @@ ensure_container_command() {
fi fi
} }
pin_garage_node_k3d() {
if [[ "${PROLE_MODE:-}" != "k3d" ]]; then
return 0
fi
if ! kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
return 0
fi
local target=""
target=$(kubectl get nodes -l node-role.kubernetes.io/master -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
if [[ -z "$target" ]]; then
target=$(kubectl get nodes -l node-role.kubernetes.io/control-plane -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
fi
if [[ -z "$target" ]]; then
return 0
fi
echo "Pinning Garage StatefulSet to node '$target' for k3d exec access ..."
kubectl patch statefulset "$GARAGE_NAME" -n "$NAMESPACE" --type merge \
-p "{\"spec\":{\"template\":{\"spec\":{\"nodeSelector\":{\"kubernetes.io/hostname\":\"$target\"}}}}}" >/dev/null || true
}
restart_statefulset() { restart_statefulset() {
if kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then if kubectl get statefulset "$GARAGE_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "Restarting Garage StatefulSet to pick up config changes ..." echo "Restarting Garage StatefulSet to pick up config changes ..."
@ -271,6 +302,7 @@ case "$ACTION" in
echo "Applying Garage manifests in namespace '$NAMESPACE'..." echo "Applying Garage manifests in namespace '$NAMESPACE'..."
apply_manifests apply_manifests
ensure_container_command ensure_container_command
pin_garage_node_k3d
restart_statefulset restart_statefulset
wait_ready wait_ready
init_layout init_layout
@ -287,6 +319,7 @@ case "$ACTION" in
echo "Re-applying Garage manifests in namespace '$NAMESPACE'..." echo "Re-applying Garage manifests in namespace '$NAMESPACE'..."
apply_manifests apply_manifests
ensure_container_command ensure_container_command
pin_garage_node_k3d
restart_statefulset restart_statefulset
wait_ready wait_ready
init_layout init_layout

View File

@ -296,10 +296,10 @@ EOF
apply_k8s() { apply_k8s() {
echo "Applying OpenBao manifest to namespace '$NAMESPACE' ..." echo "Applying OpenBao manifest to namespace '$NAMESPACE' ..."
if [[ -f "$SCRIPT_DIR/../k8s/prole/openbao-statefulset.yaml" ]]; then if [[ -f "$SCRIPT_DIR/../k8s/prole/openbao-statefulset.yaml" ]]; then
kubectl apply -n "$NAMESPACE" -f "$SCRIPT_DIR/../k8s/prole/openbao-statefulset.yaml" prole_render_manifest "$SCRIPT_DIR/../k8s/prole/openbao-statefulset.yaml" | kubectl apply -n "$NAMESPACE" -f -
kubectl apply -n "$NAMESPACE" -f "$SCRIPT_DIR/../k8s/prole/openbao-service.yaml" prole_render_manifest "$SCRIPT_DIR/../k8s/prole/openbao-service.yaml" | kubectl apply -n "$NAMESPACE" -f -
else else
kubectl apply -n "$NAMESPACE" -f "$OPENBAO_MANIFEST_DIR/deployment.yaml" prole_render_manifest "$OPENBAO_MANIFEST_DIR/deployment.yaml" | kubectl apply -n "$NAMESPACE" -f -
fi fi
echo "Applying Kerberos ConfigMap (external realm) to namespace '$NAMESPACE' ..." echo "Applying Kerberos ConfigMap (external realm) to namespace '$NAMESPACE' ..."
kubectl apply -n "$NAMESPACE" -f "$OPENBAO_MANIFEST_DIR/kerberos-configmap.yaml" kubectl apply -n "$NAMESPACE" -f "$OPENBAO_MANIFEST_DIR/kerberos-configmap.yaml"
@ -685,7 +685,7 @@ case "$ACTION" in
# Read password from stdin if provided # Read password from stdin if provided
db_pass="" db_pass=""
if [[ ! -t 0 ]]; then if [[ ! -t 0 ]]; then
read -r db_pass read -r db_pass || true
fi fi
ensure_admin_keypair ensure_admin_keypair

View File

@ -169,7 +169,7 @@ ensure_opentofu_secret() {
apply_k8s() { apply_k8s() {
echo "Applying OpenTofu manifest to namespace '$NAMESPACE' ..." echo "Applying OpenTofu manifest to namespace '$NAMESPACE' ..."
kubectl apply -n "$NAMESPACE" -f "$OPENTOFU_MANIFEST_DIR/deployment.yaml" prole_render_manifest "$OPENTOFU_MANIFEST_DIR/deployment.yaml" | kubectl apply -n "$NAMESPACE" -f -
kubectl rollout status deploy/$OPENTOFU_NAME -n "$NAMESPACE" --timeout=120s || true kubectl rollout status deploy/$OPENTOFU_NAME -n "$NAMESPACE" --timeout=120s || true
} }

View File

@ -13,7 +13,15 @@ source "$SCRIPT_DIR/prole_cfg.sh"
PROLE_HOME="${PROLE_HOME:-/Users/chrisfu/dev/prole}" PROLE_HOME="${PROLE_HOME:-/Users/chrisfu/dev/prole}"
VERBOSE=0 VERBOSE=0
CONFIG_FILE="$PROLE_HOME/conf/port-mappings.properties" CONFIG_FILE=""
CONFIG_FILE_SOURCE="auto"
PORT_FORWARD_ENABLED="${PORT_FORWARD_ENABLED:-}"
PORT_FORWARD_K3D_ENABLED="${PORT_FORWARD_K3D_ENABLED:-}"
PORT_FORWARD_K3S_ENABLED="${PORT_FORWARD_K3S_ENABLED:-}"
PORT_FORWARD_CONFIG_FILE="${PORT_FORWARD_CONFIG_FILE:-}"
PORT_FORWARD_CONFIG_FILE_K3D="${PORT_FORWARD_CONFIG_FILE_K3D:-}"
PORT_FORWARD_CONFIG_FILE_K3S="${PORT_FORWARD_CONFIG_FILE_K3S:-}"
usage() { usage() {
cat <<EOF cat <<EOF
@ -21,7 +29,7 @@ Usage:
$PROG [-v|--verbose] [-f|--force] [-c|--config-file=FILE] <start|stop|restart|status> [component] $PROG [-v|--verbose] [-f|--force] [-c|--config-file=FILE] <start|stop|restart|status> [component]
Options: Options:
-c, --config-file=FILE Path to local-ports.properties (XML) -c, --config-file=FILE Path to port-mappings.properties (XML)
-v, --verbose Verbose output -v, --verbose Verbose output
-f, --force Force: kill existing processes blocking ports -f, --force Force: kill existing processes blocking ports
@ -85,6 +93,41 @@ is_truthy() {
return 1 return 1
} }
resolve_config_file() {
if [ -n "${PORT_FORWARD_CONFIG_FILE:-}" ]; then
printf '%s' "$PORT_FORWARD_CONFIG_FILE"
return 0
fi
case "${PROLE_MODE:-}" in
k3s)
if [ -n "${PORT_FORWARD_CONFIG_FILE_K3S:-}" ]; then
printf '%s' "$PORT_FORWARD_CONFIG_FILE_K3S"
return 0
fi
;;
k3d|"")
if [ -n "${PORT_FORWARD_CONFIG_FILE_K3D:-}" ]; then
printf '%s' "$PORT_FORWARD_CONFIG_FILE_K3D"
return 0
fi
;;
esac
printf '%s' "$PROLE_HOME/conf/port-mappings.properties"
}
port_forward_enabled_for_mode() {
local val=""
case "${PROLE_MODE:-}" in
k3s) val="${PORT_FORWARD_K3S_ENABLED:-${PORT_FORWARD_ENABLED:-}}" ;;
k3d|"") val="${PORT_FORWARD_K3D_ENABLED:-${PORT_FORWARD_ENABLED:-}}" ;;
*) val="${PORT_FORWARD_ENABLED:-}" ;;
esac
if [ -z "$val" ]; then
return 0
fi
is_truthy "$val"
}
port_in_use() { port_in_use() {
local port="$1" local port="$1"
if have lsof; then if have lsof; then
@ -630,6 +673,10 @@ scan_for_collisions() {
} }
do_start() { do_start() {
if ! port_forward_enabled_for_mode; then
log "Port forwards disabled for mode '${PROLE_MODE:-auto}'."
return 0
fi
validate_env validate_env
if is_local_mode; then if is_local_mode; then
# Preflight: require Docker daemon and k3d (if applicable) # Preflight: require Docker daemon and k3d (if applicable)
@ -651,6 +698,10 @@ do_stop() {
} }
do_restart() { do_restart() {
if ! port_forward_enabled_for_mode; then
log "Port forwards disabled for mode '${PROLE_MODE:-auto}'."
return 0
fi
validate_env validate_env
if is_local_mode; then if is_local_mode; then
# Preflight: require Docker daemon and k3d (if applicable) # Preflight: require Docker daemon and k3d (if applicable)
@ -758,10 +809,12 @@ while [ $# -gt 0 ]; do
shift shift
[ $# -gt 0 ] || { err "-c requires a file path"; usage; exit 2; } [ $# -gt 0 ] || { err "-c requires a file path"; usage; exit 2; }
CONFIG_FILE="$1" CONFIG_FILE="$1"
CONFIG_FILE_SOURCE="cli"
shift shift
;; ;;
--config-file=*) --config-file=*)
CONFIG_FILE="${1#*=}" CONFIG_FILE="${1#*=}"
CONFIG_FILE_SOURCE="cli"
shift shift
;; ;;
-h|--help) -h|--help)
@ -780,6 +833,10 @@ while [ $# -gt 0 ]; do
esac esac
done done
if [ "$CONFIG_FILE_SOURCE" = "auto" ]; then
CONFIG_FILE="$(resolve_config_file)"
fi
[ -n "$ACTION" ] || { usage; exit 2; } [ -n "$ACTION" ] || { usage; exit 2; }
case "$ACTION" in case "$ACTION" in

View File

@ -153,7 +153,7 @@ start() {
# Ensure cluster exists before attempting to patch image # Ensure cluster exists before attempting to patch image
if ! kubectl get cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then if ! kubectl get cluster "$CNPG_CLUSTER_NAME" -n "$NAMESPACE" >/dev/null 2>&1; then
echo "Cluster '$CNPG_CLUSTER_NAME' not found in namespace '$NAMESPACE'. Applying manifest..." echo "Cluster '$CNPG_CLUSTER_NAME' not found in namespace '$NAMESPACE'. Applying manifest..."
kubectl apply -n "$NAMESPACE" -f "$CNPG_MANIFEST" prole_render_manifest "$CNPG_MANIFEST" | kubectl apply -n "$NAMESPACE" -f -
fi fi
# Compare latest image with deployed # Compare latest image with deployed
@ -178,7 +178,7 @@ start() {
fi fi
echo "Starting prole-db cluster (ensuring manifest is applied)..." echo "Starting prole-db cluster (ensuring manifest is applied)..."
kubectl apply -n "$NAMESPACE" -f "$CNPG_MANIFEST" prole_render_manifest "$CNPG_MANIFEST" | kubectl apply -n "$NAMESPACE" -f -
} }
stop() { stop() {
@ -248,7 +248,7 @@ deploy() {
# Ensure image is updated if manifest has an older version # Ensure image is updated if manifest has an older version
# First, apply the manifest to ensure the cluster exists/is updated # First, apply the manifest to ensure the cluster exists/is updated
echo "Applying manifest $CNPG_MANIFEST..." echo "Applying manifest $CNPG_MANIFEST..."
kubectl apply -n "$NAMESPACE" -f "$CNPG_MANIFEST" prole_render_manifest "$CNPG_MANIFEST" | kubectl apply -n "$NAMESPACE" -f -
# Then force the specific image version via patch if different # Then force the specific image version via patch if different
local current_image local current_image

View File

@ -118,6 +118,25 @@ prole_set_mode() {
esac esac
} }
prole_render_manifest() {
local src="$1"
if [[ "${PROLE_MODE:-}" != "k3d" ]]; then
cat "$src"
return 0
fi
local renderer=""
if [[ -n "${PROLE_SERVICE:-}" && -f "$PROLE_SERVICE/render_manifest.py" ]]; then
renderer="$PROLE_SERVICE/render_manifest.py"
elif [[ -n "${PROLE_HOME:-}" && -f "$PROLE_HOME/etc/render_manifest.py" ]]; then
renderer="$PROLE_HOME/etc/render_manifest.py"
fi
if [[ -n "$renderer" ]]; then
python3 "$renderer" "$src"
return $?
fi
cat "$src"
}
_prole_cfg_file="" _prole_cfg_file=""
if [[ -n "${PROLE_CONF:-}" && -f "$PROLE_CONF/prole.cfg" ]]; then if [[ -n "${PROLE_CONF:-}" && -f "$PROLE_CONF/prole.cfg" ]]; then
_prole_cfg_file="$PROLE_CONF/prole.cfg" _prole_cfg_file="$PROLE_CONF/prole.cfg"

View File

@ -867,7 +867,7 @@ def _render_prole_cfg(inputs: dict, globals_to_save: dict, sections: dict, gener
content.append('') content.append('')
sections_order = [ sections_order = [
'Welcome', 'Dependencies', 'Network', 'System Environment', 'Welcome', 'Dependencies', 'Network', 'Port Forwards', 'System Environment',
'Monitoring', 'Kerberos Authentication', 'Optional Features', 'Database Creation', 'Monitoring', 'Kerberos Authentication', 'Optional Features', 'Database Creation',
'Initialize Cluster', 'Dev Cluster (k3d)', 'Service Cluster (k3s)', 'Prod Cluster (k8s)', 'Initialize Cluster', 'Dev Cluster (k3d)', 'Service Cluster (k3s)', 'Prod Cluster (k8s)',
'Docker Build', 'Initialization Scripts', 'Deployment', 'Install' 'Docker Build', 'Initialization Scripts', 'Deployment', 'Install'
@ -1197,6 +1197,7 @@ class ProleInstaller:
'Welcome': {}, 'Welcome': {},
'Dependencies': {}, 'Dependencies': {},
'Network': {}, 'Network': {},
'Port Forwards': {},
'System Environment': {}, 'System Environment': {},
'Kerberos Authentication': {}, 'Kerberos Authentication': {},
'Optional Features': {}, 'Optional Features': {},