Files
kubernetes/init-app.sh
2025-10-23 14:21:11 +02:00

219 lines
8.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# init-app.sh
# Creates clusters/<cluster>/apps/<application> relative to this script, renders templates,
# updates parent clusters/<cluster>/apps/kustomization.yaml to include the new application,
# and stages changes in Git.
set -euo pipefail
resolve_script_dir() {
local src="${BASH_SOURCE[0]:-$0}"
while [ -h "$src" ]; do
local dir
dir="$(cd -P "$(dirname "$src")" && pwd)"
src="$(readlink "$src")"
[[ "$src" != /* ]] && src="$dir/$src"
done
cd -P "$(dirname "$src")" >/dev/null 2>&1
pwd
}
SCRIPT_DIR="$(resolve_script_dir)"
# Application name
read -r -p "Application name (lowercase, letters/numbers/dashes): " APPLICATION_NAME
APPLICATION_NAME="$(echo "$APPLICATION_NAME" | awk '{$1=$1;print}')"
if [[ -z "$APPLICATION_NAME" ]]; then
echo "Error: Application name cannot be empty." >&2
exit 1
fi
if ! [[ "$APPLICATION_NAME" =~ ^[a-z0-9-]+$ ]]; then
echo "Error: Application name must be lowercase and contain only letters (a-z), numbers (0-9), or dashes (-)." >&2
exit 1
fi
if [[ "$APPLICATION_NAME" == *"/"* || "$APPLICATION_NAME" == *"\\"* ]]; then
echo "Error: Application name must not contain path separators (/ or \\)." >&2
exit 1
fi
# Cluster name
DEFAULT_CLUSTER="artemis"
read -r -p "Cluster name [${DEFAULT_CLUSTER}] (lowercase, letters/numbers/dashes): " CLUSTER_NAME
CLUSTER_NAME="${CLUSTER_NAME:-$DEFAULT_CLUSTER}"
CLUSTER_NAME="$(echo "$CLUSTER_NAME" | awk '{$1=$1;print}')"
if [[ -z "$CLUSTER_NAME" ]]; then
echo "Error: Cluster name cannot be empty." >&2
exit 1
fi
if ! [[ "$CLUSTER_NAME" =~ ^[a-z0-9-]+$ ]]; then
echo "Error: Cluster name must be lowercase and contain only letters (a-z), numbers (0-9), or dashes (-)." >&2
exit 1
fi
if [[ "$CLUSTER_NAME" == *"/"* || "$CLUSTER_NAME" == *"\\"* ]]; then
echo "Error: Cluster name must not contain path separators (/ or \\)." >&2
exit 1
fi
# Description (allows spaces, letters upper/lower, digits, dot, comma)
read -r -p "Description (spaces, letters, numbers, dots, commas): " DESCRIPTION
DESCRIPTION="$(echo "$DESCRIPTION" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
if [ -z "$DESCRIPTION" ]; then
echo "Error: Description cannot be empty." >&2
exit 1
fi
# Portable validation using grep -Eq (works under sh, dash, bash)
# Allowed chars: A-Za-z0-9 space dot comma
if ! echo "$DESCRIPTION" | grep -Eq '^[A-Za-z0-9 .,]+$'; then
echo "Error: Description may only contain spaces, letters (AZ, az), numbers (09), dots (.), and commas (,)." >&2
exit 1
fi
TARGET_DIR="${SCRIPT_DIR}/clusters/${CLUSTER_NAME}/apps/${APPLICATION_NAME}"
# Abort if target directory already exists
if [[ -d "$TARGET_DIR" ]]; then
echo "Error: Directory already exists: $TARGET_DIR" >&2
exit 1
fi
# Templates
TEMPLATE_DIR="${SCRIPT_DIR}/.templates"
APP_PROJECT_SRC="${TEMPLATE_DIR}/app-project.yaml"
APPLICATION_SRC="${TEMPLATE_DIR}/application.yaml"
KUSTOMIZATION_SRC="${TEMPLATE_DIR}/kustomization.yaml"
missing=false
for f in "$APP_PROJECT_SRC" "$APPLICATION_SRC" "$KUSTOMIZATION_SRC"; do
if [[ ! -f "$f" ]]; then
echo "Error: Template not found: ${f}" >&2
missing=true
fi
done
if [[ "$missing" == true ]]; then
exit 1
fi
# Create target directory
mkdir -p "$TARGET_DIR"
# Prepare safe replacements
safe_app_name="${APPLICATION_NAME//\\/\\\\}"
safe_app_name="${safe_app_name//&/\\&}"
safe_description="${DESCRIPTION//\\/\\\\}"
safe_description="${safe_description//&/\\&}"
safe_cluster_name="${CLUSTER_NAME//\\/\\\\}"
safe_cluster_name="${safe_cluster_name//&/\\&}"
# Render app-project.yaml
APP_PROJECT_DEST="${TARGET_DIR}/app-project.yaml"
tmp1="$(mktemp)"
trap 'rm -f "$tmp1" "$tmp2" "$tmp3" "$tmp_parent" "$tmp_norm" "$tmp_slice"' EXIT
sed -e "s/\${APPLICATION_NAME}/${safe_app_name}/g" \
-e "s/\${DESCRIPTION}/${safe_description}/g" \
"$APP_PROJECT_SRC" > "$tmp1"
mv "$tmp1" "$APP_PROJECT_DEST"
# Render application.yaml (replace APPLICATION_NAME and CLUSTER_NAME)
APPLICATION_DEST="${TARGET_DIR}/application.yaml"
tmp2="$(mktemp)"
sed -e "s/\${APPLICATION_NAME}/${safe_app_name}/g" \
-e "s/\${CLUSTER_NAME}/${safe_cluster_name}/g" \
"$APPLICATION_SRC" > "$tmp2"
mv "$tmp2" "$APPLICATION_DEST"
# Copy kustomization.yaml as-is
KUSTOMIZATION_DEST="${TARGET_DIR}/kustomization.yaml"
cp "$KUSTOMIZATION_SRC" "$KUSTOMIZATION_DEST"
echo "Created: $TARGET_DIR"
echo "Wrote: $APP_PROJECT_DEST (APPLICATION_NAME='${APPLICATION_NAME}', DESCRIPTION set)"
echo "Wrote: $APPLICATION_DEST (APPLICATION_NAME='${APPLICATION_NAME}', CLUSTER_NAME='${CLUSTER_NAME}')"
echo "Wrote: $KUSTOMIZATION_DEST (copied as-is)"
# Update parent kustomization.yaml one level above the new folder
PARENT_KUSTOMIZATION="${SCRIPT_DIR}/clusters/${CLUSTER_NAME}/apps/kustomization.yaml"
if [[ ! -f "$PARENT_KUSTOMIZATION" ]]; then
echo "Error: Parent kustomization not found: ${PARENT_KUSTOMIZATION}" >&2
echo "Hint: Ensure a kustomization.yaml exists in clusters/${CLUSTER_NAME}/apps/" >&2
exit 1
fi
# Normalize CRLF line endings if present
if file "$PARENT_KUSTOMIZATION" | grep -q "CRLF"; then
tmp_norm="$(mktemp)"
tr -d '\r' < "$PARENT_KUSTOMIZATION" > "$tmp_norm"
mv "$tmp_norm" "$PARENT_KUSTOMIZATION"
fi
# If already present, skip
if grep -Eq "^[[:space:]]*-[[:space:]]*${APPLICATION_NAME}[[:space:]]*$" "$PARENT_KUSTOMIZATION"; then
echo "Parent kustomization.yaml already contains resource '${APPLICATION_NAME}', skipping insertion."
else
# Determine indentation from the first existing resource item (fallback two spaces)
indent="$(grep -E '^[[:space:]]*-[[:space:]]' "$PARENT_KUSTOMIZATION" | sed -n '1s/^\([[:space:]]*\)-.*/\1/p')"
[[ -z "${indent:-}" ]] && indent=" "
# Work within the most recent 'resources:' block (if any)
last_res_start_line="$(nl -ba "$PARENT_KUSTOMIZATION" | awk '/^[[:space:]]*[0-9]+\s+resources:[[:space:]]*$/ { line=$1 } END { if (line!="") print line }')"
if [[ -n "${last_res_start_line:-}" ]]; then
# Slice parent from resources start to EOF
tmp_slice="$(mktemp)"
tail -n +"$last_res_start_line" "$PARENT_KUSTOMIZATION" > "$tmp_slice"
# In the slice, find last '-' resource line number
slice_last_line="$(nl -ba "$tmp_slice" \
| awk '/^[[:space:]]*[0-9]+\s+[[:space:]]*-[[:space:]]/ { print $1 }' \
| tail -n1)"
if [[ -n "${slice_last_line:-}" ]]; then
# Map slice line back to parent absolute line
abs_last_line=$(( last_res_start_line + slice_last_line - 1 ))
# Build new file via tmp_parent; avoid arithmetic in command args
tmp_parent="$(mktemp)"
head -n "$abs_last_line" "$PARENT_KUSTOMIZATION" > "$tmp_parent"
printf "%s- %s\n" "$indent" "$APPLICATION_NAME" >> "$tmp_parent"
next_line=$(( abs_last_line + 1 ))
tail -n +"$next_line" "$PARENT_KUSTOMIZATION" >> "$tmp_parent"
mv "$tmp_parent" "$PARENT_KUSTOMIZATION"
echo "Updated parent kustomization.yaml: appended '${APPLICATION_NAME}' to resources."
else
# No items yet → insert directly after the resources key line
tmp_parent="$(mktemp)"
head -n "$last_res_start_line" "$PARENT_KUSTOMIZATION" > "$tmp_parent"
printf "%s- %s\n" "$indent" "$APPLICATION_NAME" >> "$tmp_parent"
next_line=$(( last_res_start_line + 1 ))
tail -n +"$next_line" "$PARENT_KUSTOMIZATION" >> "$tmp_parent"
mv "$tmp_parent" "$PARENT_KUSTOMIZATION"
echo "Updated parent kustomization.yaml: added first resource '${APPLICATION_NAME}' under resources."
fi
rm -f "$tmp_slice"
else
# No resources key exists at all → create section at the end ONCE via tmp_parent
tmp_parent="$(mktemp)"
cat "$PARENT_KUSTOMIZATION" > "$tmp_parent"
printf "\nresources:\n%s- %s\n" "$indent" "$APPLICATION_NAME" >> "$tmp_parent"
mv "$tmp_parent" "$PARENT_KUSTOMIZATION"
echo "Updated parent kustomization.yaml: created resources section with '${APPLICATION_NAME}'."
fi
fi
# Stage changes in Git (if inside a Git repo)
if git -C "$SCRIPT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
REL_TARGET="${TARGET_DIR#$REPO_ROOT/}"
REL_PARENT="${PARENT_KUSTOMIZATION#$REPO_ROOT/}"
git -C "$REPO_ROOT" add "$REL_TARGET" "$REL_PARENT"
echo "Git: staged new directory/files and parent kustomization: $REL_TARGET, $REL_PARENT"
echo "Next: commit with a message, e.g.:"
echo " git -C \"$REPO_ROOT\" commit -m \"feat(${CLUSTER_NAME}): add ${APPLICATION_NAME} app (\"${DESCRIPTION}\") and update kustomization\""
else
echo "Note: Not inside a Git repository (no .git found). Skipping git add."
fi