chore(bootstrap): Added templating
This commit is contained in:
217
init-app.sh
217
init-app.sh
@@ -0,0 +1,217 @@
|
|||||||
|
#!/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 (A–Z, a–z), numbers (0–9), 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
|
||||||
|
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
|
||||||
|
tmp_slice="$(mktemp)"
|
||||||
|
tail -n +"$last_res_start_line" "$PARENT_KUSTOMIZATION" > "$tmp_slice"
|
||||||
|
|
||||||
|
slice_last_line="$(nl -ba "$tmp_slice" \
|
||||||
|
| awk '/^[[:space:]]*[0-9]+\s+[[:space:]]*-[[:space:]]/ { print $1 }' \
|
||||||
|
| tail -n1)"
|
||||||
|
|
||||||
|
if [[ -n "${slice_last_line:-}" ]]; then
|
||||||
|
abs_last_line=$(( last_res_start_line + slice_last_line - 1 ))
|
||||||
|
tmp_parent="$(mktemp)"
|
||||||
|
{
|
||||||
|
head -n "$abs_last_line" "$PARENT_KUSTOMIZATION"
|
||||||
|
printf "%s- %s\n" "$indent" "$APPLICATION_NAME"
|
||||||
|
tail -n +"$((abs_last_line + 1))" "$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"
|
||||||
|
printf "%s- %s\n" "$indent" "$APPLICATION_NAME"
|
||||||
|
tail -n +"$((last_res_start_line + 1))" "$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
|
||||||
|
tmp_parent="$(mktemp)"
|
||||||
|
{
|
||||||
|
cat "$PARENT_KUSTOMIZATION"
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user