#!/usr/bin/env bash # Config trees: what a Kubernetes ConfigMap or Secret actually looks like to Spring Boot. # # A ConfigMap mounted as a volume is not a properties file. Kubernetes writes one file per # key, named after the key, containing only the value. `configtree:` is the loader that reads # that shape. This script builds the same directory layout on disk, so the demonstration is # the real mechanism rather than a description of it. set -euo pipefail set +m cd "$(dirname "$0")/.." source scripts/env.sh TREE="${TMPDIR:-/tmp}/demo-configmap" SECRET="${TMPDIR:-/tmp}/demo-secret" rm -rf "$TREE" "$SECRET"; mkdir -p "$TREE" "$SECRET" # Exactly what `kubectl create configmap demo --from-literal=demo.greeting=...` produces # once mounted: one file per key, the filename IS the property name. printf 'from-configmap-volume' > "$TREE/demo.greeting" printf 'jdbc:postgresql://configmap-db:5432/o' > "$TREE/demo.datasource-url" printf '25' > "$TREE/demo.pool-size" # Nested keys use a directory per level, or a dotted filename. Both work. mkdir -p "$TREE/demo/nested" printf 'from-nested-directory' > "$TREE/demo/nested/value" # A Secret mount looks identical; only the permissions differ. printf 'sk_live_not_a_real_key' > "$SECRET/demo.api-key" { echo "== what Kubernetes actually mounts ==" echo "\$ find $TREE $SECRET -type f | sort" find "$TREE" "$SECRET" -type f | sort | sed "s|$TREE||;s|$SECRET||" echo echo "\$ cat /demo.greeting; echo" cat "$TREE/demo.greeting"; echo echo echo "Each file holds a bare value with no trailing newline and no key. There is no" echo "properties syntax to parse -- the filename is the key." echo echo "== importing it ==" echo "\$ java -jar $JAR \\" echo " --spring.config.import=configtree:$TREE/,configtree:$SECRET/" echo start_app "--spring.config.import=configtree:$TREE/,configtree:$SECRET/" > /dev/null report demo.greeting echo echo " demo.pool-size = $(curl -s "http://127.0.0.1:${APP_PORT}/precedence?name=demo.pool-size" | python3 -c 'import json,sys; print(json.load(sys.stdin)["effectiveValue"])')" echo " demo.nested.value = $(curl -s "http://127.0.0.1:${APP_PORT}/precedence?name=demo.nested.value" | python3 -c 'import json,sys; print(json.load(sys.stdin)["effectiveValue"])')" echo " demo.api-key = $(curl -s "http://127.0.0.1:${APP_PORT}/precedence?name=demo.api-key" | python3 -c 'import json,sys; print(json.load(sys.stdin)["effectiveValue"])')" echo echo "A directory under the mount becomes a nested property: demo/nested/value is" echo "demo.nested.value. That is how a ConfigMap with slashes in its keys arrives." echo echo "== the part that surprises people ==" echo "An imported config tree outranks application.yaml, but it is still config data," echo "so it still loses to an environment variable:" echo APP_ENV="DEMO_GREETING=from-environment-variable" \ start_app "--spring.config.import=configtree:$TREE/" > /dev/null report demo.greeting echo echo "There is also no such thing as a profile-specific config tree. There is no" echo "-prod directory convention; a per-environment ConfigMap is a different mount" echo "chosen by the deployment, not by spring.profiles.active." stop_app } > docs/output/03-config-tree.txt 2>&1 cat docs/output/03-config-tree.txt