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