160 lines
6.4 KiB
Bash
Executable File
160 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# init-app.sh
|
||
# Creates clusters/<cluster>/apps/<application>, renders templates (APPLICATION_NAME, DESCRIPTION, CLUSTER_NAME),
|
||
# appends the app to the end of the parent clusters/<cluster>/apps/kustomization.yaml list (removing trailing blank line if present),
|
||
# creates a placeholder under manifests/<cluster>/<application>,
|
||
# and stages changes in Git (including the placeholder file).
|
||
|
||
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
|
||
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
|
||
if ! echo "$DESCRIPTION" | grep -Eq '^[A-Za-z0-9 .,]+$'; then
|
||
echo "Error: Description may only contain spaces, letters (A–Z, a–z), numbers (0–9), dots (.), and commas (,)." >&2; exit 1
|
||
fi
|
||
|
||
# Paths
|
||
TARGET_DIR="${SCRIPT_DIR}/clusters/${CLUSTER_NAME}/apps/${APPLICATION_NAME}"
|
||
MANIFESTS_DIR="${SCRIPT_DIR}/manifests/${CLUSTER_NAME}/${APPLICATION_NAME}"
|
||
PARENT_KUSTOMIZATION="${SCRIPT_DIR}/clusters/${CLUSTER_NAME}/apps/kustomization.yaml"
|
||
|
||
# Abort if clusters target 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 directories
|
||
mkdir -p "$TARGET_DIR"
|
||
mkdir -p "$MANIFESTS_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//&/\\&}"
|
||
|
||
# Temp files (init for trap)
|
||
tmp1=""; tmp2=""
|
||
trap 'rm -f "$tmp1" "$tmp2"' EXIT
|
||
|
||
# Render app-project.yaml
|
||
tmp1="$(mktemp)"
|
||
APP_PROJECT_DEST="${TARGET_DIR}/app-project.yaml"
|
||
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 (APPLICATION_NAME + CLUSTER_NAME)
|
||
tmp2="$(mktemp)"
|
||
APPLICATION_DEST="${TARGET_DIR}/application.yaml"
|
||
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"
|
||
|
||
# Create placeholder in manifests/<cluster>/<application>
|
||
PLACEHOLDER_FILE="${MANIFESTS_DIR}/.placeholder"
|
||
if [[ ! -f "$PLACEHOLDER_FILE" ]]; then
|
||
printf "Placeholder for %s/%s\n" "$CLUSTER_NAME" "$APPLICATION_NAME" > "$PLACEHOLDER_FILE"
|
||
fi
|
||
|
||
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)"
|
||
echo "Created: $PLACEHOLDER_FILE"
|
||
|
||
# Parent kustomization.yaml: remove trailing blank line if present, then append new item
|
||
if [[ ! -f "$PARENT_KUSTOMIZATION" ]]; then
|
||
echo "Error: Parent kustomization not found: ${PARENT_KUSTOMIZATION}" >&2; exit 1
|
||
fi
|
||
|
||
# Remove trailing empty last line (BSD sed)
|
||
sed -i '' -e '${/^$/d;}' "$PARENT_KUSTOMIZATION"
|
||
|
||
# Append the new application entry at end (two-space indent)
|
||
printf " - %s\n" "$APPLICATION_NAME" >> "$PARENT_KUSTOMIZATION"
|
||
|
||
echo "Updated parent kustomization.yaml: appended '${APPLICATION_NAME}' at end of resources list."
|
||
|
||
# Stage changes in Git (including placeholder file)
|
||
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/}"
|
||
REL_MANIFESTS_DIR="${MANIFESTS_DIR#$REPO_ROOT/}"
|
||
REL_PLACEHOLDER="${PLACEHOLDER_FILE#$REPO_ROOT/}"
|
||
git -C "$REPO_ROOT" add "$REL_TARGET" "$REL_PARENT" "$REL_MANIFESTS_DIR" "$REL_PLACEHOLDER"
|
||
echo "Git: staged new app dir, parent kustomization, manifests dir, and placeholder:"
|
||
echo " $REL_TARGET"
|
||
echo " $REL_PARENT"
|
||
echo " $REL_MANIFESTS_DIR"
|
||
echo " $REL_PLACEHOLDER"
|
||
echo "Next: commit with a message, e.g.:"
|
||
echo " git -C \"$REPO_ROOT\" commit -m \"feat(${CLUSTER_NAME}): add ${APPLICATION_NAME} app (\"${DESCRIPTION}\") and manifests placeholder; update kustomization\""
|
||
else
|
||
echo "Note: Not inside a Git repository (no .git found). Skipping git add."
|
||
fi
|