chore(bootstrap): Added base script for adding new apps to clusters
This commit is contained in:
145
init-app.sh
145
init-app.sh
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# init-app.sh
|
# init-app.sh
|
||||||
# Creates clusters/<cluster>/apps/<application> relative to this script, renders templates,
|
# Creates clusters/<cluster>/apps/<application>, renders templates (APPLICATION_NAME, DESCRIPTION, CLUSTER_NAME),
|
||||||
# updates parent clusters/<cluster>/apps/kustomization.yaml to include the new application,
|
# appends the app at the end of the parent clusters/<cluster>/apps/kustomization.yaml list (removing trailing blank line if present),
|
||||||
# and stages changes in Git.
|
# and stages changes in Git.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -23,18 +23,14 @@ SCRIPT_DIR="$(resolve_script_dir)"
|
|||||||
# Application name
|
# Application name
|
||||||
read -r -p "Application name (lowercase, letters/numbers/dashes): " APPLICATION_NAME
|
read -r -p "Application name (lowercase, letters/numbers/dashes): " APPLICATION_NAME
|
||||||
APPLICATION_NAME="$(echo "$APPLICATION_NAME" | awk '{$1=$1;print}')"
|
APPLICATION_NAME="$(echo "$APPLICATION_NAME" | awk '{$1=$1;print}')"
|
||||||
|
|
||||||
if [[ -z "$APPLICATION_NAME" ]]; then
|
if [[ -z "$APPLICATION_NAME" ]]; then
|
||||||
echo "Error: Application name cannot be empty." >&2
|
echo "Error: Application name cannot be empty." >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
if ! [[ "$APPLICATION_NAME" =~ ^[a-z0-9-]+$ ]]; then
|
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
|
echo "Error: Application name must be lowercase and contain only letters (a–z), numbers (0–9), or dashes (-)." >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
if [[ "$APPLICATION_NAME" == *"/"* || "$APPLICATION_NAME" == *"\\"* ]]; then
|
if [[ "$APPLICATION_NAME" == *"/"* || "$APPLICATION_NAME" == *"\\"* ]]; then
|
||||||
echo "Error: Application name must not contain path separators (/ or \\)." >&2
|
echo "Error: Application name must not contain path separators (/ or \\)." >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Cluster name
|
# Cluster name
|
||||||
@@ -42,42 +38,31 @@ DEFAULT_CLUSTER="artemis"
|
|||||||
read -r -p "Cluster name [${DEFAULT_CLUSTER}] (lowercase, letters/numbers/dashes): " CLUSTER_NAME
|
read -r -p "Cluster name [${DEFAULT_CLUSTER}] (lowercase, letters/numbers/dashes): " CLUSTER_NAME
|
||||||
CLUSTER_NAME="${CLUSTER_NAME:-$DEFAULT_CLUSTER}"
|
CLUSTER_NAME="${CLUSTER_NAME:-$DEFAULT_CLUSTER}"
|
||||||
CLUSTER_NAME="$(echo "$CLUSTER_NAME" | awk '{$1=$1;print}')"
|
CLUSTER_NAME="$(echo "$CLUSTER_NAME" | awk '{$1=$1;print}')"
|
||||||
|
|
||||||
if [[ -z "$CLUSTER_NAME" ]]; then
|
if [[ -z "$CLUSTER_NAME" ]]; then
|
||||||
echo "Error: Cluster name cannot be empty." >&2
|
echo "Error: Cluster name cannot be empty." >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
if ! [[ "$CLUSTER_NAME" =~ ^[a-z0-9-]+$ ]]; then
|
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
|
echo "Error: Cluster name must be lowercase and contain only letters (a–z), numbers (0–9), or dashes (-)." >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
if [[ "$CLUSTER_NAME" == *"/"* || "$CLUSTER_NAME" == *"\\"* ]]; then
|
if [[ "$CLUSTER_NAME" == *"/"* || "$CLUSTER_NAME" == *"\\"* ]]; then
|
||||||
echo "Error: Cluster name must not contain path separators (/ or \\)." >&2
|
echo "Error: Cluster name must not contain path separators (/ or \\)." >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Description (allows spaces, letters upper/lower, digits, dot, comma)
|
# Description
|
||||||
read -r -p "Description (spaces, letters, numbers, dots, commas): " DESCRIPTION
|
read -r -p "Description (spaces, letters, numbers, dots, commas): " DESCRIPTION
|
||||||
DESCRIPTION="$(echo "$DESCRIPTION" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
|
DESCRIPTION="$(echo "$DESCRIPTION" | sed 's/^[[:space:]]\+//; s/[[:space:]]\+$//')"
|
||||||
|
|
||||||
if [ -z "$DESCRIPTION" ]; then
|
if [ -z "$DESCRIPTION" ]; then
|
||||||
echo "Error: Description cannot be empty." >&2
|
echo "Error: Description cannot be empty." >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
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
|
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
|
echo "Error: Description may only contain spaces, letters (A–Z, a–z), numbers (0–9), dots (.), and commas (,)." >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
TARGET_DIR="${SCRIPT_DIR}/clusters/${CLUSTER_NAME}/apps/${APPLICATION_NAME}"
|
TARGET_DIR="${SCRIPT_DIR}/clusters/${CLUSTER_NAME}/apps/${APPLICATION_NAME}"
|
||||||
|
|
||||||
# Abort if target directory already exists
|
# Abort if target directory already exists
|
||||||
if [[ -d "$TARGET_DIR" ]]; then
|
if [[ -d "$TARGET_DIR" ]]; then
|
||||||
echo "Error: Directory already exists: $TARGET_DIR" >&2
|
echo "Error: Directory already exists: $TARGET_DIR" >&2; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Templates
|
# Templates
|
||||||
@@ -88,38 +73,33 @@ KUSTOMIZATION_SRC="${TEMPLATE_DIR}/kustomization.yaml"
|
|||||||
|
|
||||||
missing=false
|
missing=false
|
||||||
for f in "$APP_PROJECT_SRC" "$APPLICATION_SRC" "$KUSTOMIZATION_SRC"; do
|
for f in "$APP_PROJECT_SRC" "$APPLICATION_SRC" "$KUSTOMIZATION_SRC"; do
|
||||||
if [[ ! -f "$f" ]]; then
|
if [[ ! -f "$f" ]]; then echo "Error: Template not found: ${f}" >&2; missing=true; fi
|
||||||
echo "Error: Template not found: ${f}" >&2
|
|
||||||
missing=true
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
if [[ "$missing" == true ]]; then
|
if [[ "$missing" == true ]]; then exit 1; fi
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create target directory
|
# Create target directory
|
||||||
mkdir -p "$TARGET_DIR"
|
mkdir -p "$TARGET_DIR"
|
||||||
|
|
||||||
# Prepare safe replacements
|
# Prepare safe replacements
|
||||||
safe_app_name="${APPLICATION_NAME//\\/\\\\}"
|
safe_app_name="${APPLICATION_NAME//\\/\\\\}"; safe_app_name="${safe_app_name//&/\\&}"
|
||||||
safe_app_name="${safe_app_name//&/\\&}"
|
safe_description="${DESCRIPTION//\\/\\\\}"; safe_description="${safe_description//&/\\&}"
|
||||||
safe_description="${DESCRIPTION//\\/\\\\}"
|
safe_cluster_name="${CLUSTER_NAME//\\/\\\\}"; safe_cluster_name="${safe_cluster_name//&/\\&}"
|
||||||
safe_description="${safe_description//&/\\&}"
|
|
||||||
safe_cluster_name="${CLUSTER_NAME//\\/\\\\}"
|
# Temp files (init for trap)
|
||||||
safe_cluster_name="${safe_cluster_name//&/\\&}"
|
tmp1=""; tmp2=""
|
||||||
|
trap 'rm -f "$tmp1" "$tmp2"' EXIT
|
||||||
|
|
||||||
# Render app-project.yaml
|
# Render app-project.yaml
|
||||||
APP_PROJECT_DEST="${TARGET_DIR}/app-project.yaml"
|
|
||||||
tmp1="$(mktemp)"
|
tmp1="$(mktemp)"
|
||||||
trap 'rm -f "$tmp1" "$tmp2" "$tmp3" "$tmp_parent" "$tmp_norm" "$tmp_slice"' EXIT
|
APP_PROJECT_DEST="${TARGET_DIR}/app-project.yaml"
|
||||||
sed -e "s/\${APPLICATION_NAME}/${safe_app_name}/g" \
|
sed -e "s/\${APPLICATION_NAME}/${safe_app_name}/g" \
|
||||||
-e "s/\${DESCRIPTION}/${safe_description}/g" \
|
-e "s/\${DESCRIPTION}/${safe_description}/g" \
|
||||||
"$APP_PROJECT_SRC" > "$tmp1"
|
"$APP_PROJECT_SRC" > "$tmp1"
|
||||||
mv "$tmp1" "$APP_PROJECT_DEST"
|
mv "$tmp1" "$APP_PROJECT_DEST"
|
||||||
|
|
||||||
# Render application.yaml (replace APPLICATION_NAME and CLUSTER_NAME)
|
# Render application.yaml (APPLICATION_NAME + CLUSTER_NAME)
|
||||||
APPLICATION_DEST="${TARGET_DIR}/application.yaml"
|
|
||||||
tmp2="$(mktemp)"
|
tmp2="$(mktemp)"
|
||||||
|
APPLICATION_DEST="${TARGET_DIR}/application.yaml"
|
||||||
sed -e "s/\${APPLICATION_NAME}/${safe_app_name}/g" \
|
sed -e "s/\${APPLICATION_NAME}/${safe_app_name}/g" \
|
||||||
-e "s/\${CLUSTER_NAME}/${safe_cluster_name}/g" \
|
-e "s/\${CLUSTER_NAME}/${safe_cluster_name}/g" \
|
||||||
"$APPLICATION_SRC" > "$tmp2"
|
"$APPLICATION_SRC" > "$tmp2"
|
||||||
@@ -134,77 +114,26 @@ echo "Wrote: $APP_PROJECT_DEST (APPLICATION_NAME='${APPLICATION_NAME}', DESCRI
|
|||||||
echo "Wrote: $APPLICATION_DEST (APPLICATION_NAME='${APPLICATION_NAME}', CLUSTER_NAME='${CLUSTER_NAME}')"
|
echo "Wrote: $APPLICATION_DEST (APPLICATION_NAME='${APPLICATION_NAME}', CLUSTER_NAME='${CLUSTER_NAME}')"
|
||||||
echo "Wrote: $KUSTOMIZATION_DEST (copied as-is)"
|
echo "Wrote: $KUSTOMIZATION_DEST (copied as-is)"
|
||||||
|
|
||||||
# Update parent kustomization.yaml one level above the new folder
|
# Parent kustomization.yaml: remove trailing blank line if present, then append new item
|
||||||
PARENT_KUSTOMIZATION="${SCRIPT_DIR}/clusters/${CLUSTER_NAME}/apps/kustomization.yaml"
|
PARENT_KUSTOMIZATION="${SCRIPT_DIR}/clusters/${CLUSTER_NAME}/apps/kustomization.yaml"
|
||||||
if [[ ! -f "$PARENT_KUSTOMIZATION" ]]; then
|
if [[ ! -f "$PARENT_KUSTOMIZATION" ]]; then
|
||||||
echo "Error: Parent kustomization not found: ${PARENT_KUSTOMIZATION}" >&2
|
echo "Error: Parent kustomization not found: ${PARENT_KUSTOMIZATION}" >&2; exit 1
|
||||||
echo "Hint: Ensure a kustomization.yaml exists in clusters/${CLUSTER_NAME}/apps/" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Normalize CRLF line endings if present
|
# 1) If the last line is empty, delete it (BSD sed)
|
||||||
if file "$PARENT_KUSTOMIZATION" | grep -q "CRLF"; then
|
# This prints all lines except a final empty line at EOF.
|
||||||
tmp_norm="$(mktemp)"
|
# Explanation:
|
||||||
tr -d '\r' < "$PARENT_KUSTOMIZATION" > "$tmp_norm"
|
# - $: matches the last line
|
||||||
mv "$tmp_norm" "$PARENT_KUSTOMIZATION"
|
# - /^$/: last line is empty
|
||||||
fi
|
# - d: delete that line
|
||||||
|
sed -i '' -e '${/^$/d;}' "$PARENT_KUSTOMIZATION"
|
||||||
|
|
||||||
# If already present, skip
|
# 2) Append the new application line at the end (with two-space indent to match your file)
|
||||||
if grep -Eq "^[[:space:]]*-[[:space:]]*${APPLICATION_NAME}[[:space:]]*$" "$PARENT_KUSTOMIZATION"; then
|
printf " - %s\n" "$APPLICATION_NAME" >> "$PARENT_KUSTOMIZATION"
|
||||||
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)
|
echo "Updated parent kustomization.yaml: appended '${APPLICATION_NAME}' at end of resources list."
|
||||||
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
|
# Stage changes in Git
|
||||||
# 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
|
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)"
|
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
|
||||||
REL_TARGET="${TARGET_DIR#$REPO_ROOT/}"
|
REL_TARGET="${TARGET_DIR#$REPO_ROOT/}"
|
||||||
|
|||||||
Reference in New Issue
Block a user