diff --git a/init-app.sh b/init-app.sh index 5b4c596..bd3ed26 100755 --- a/init-app.sh +++ b/init-app.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # init-app.sh -# Creates clusters//apps/ relative to this script, renders templates, -# updates parent clusters//apps/kustomization.yaml to include the new application, +# Creates clusters//apps/, renders templates (APPLICATION_NAME, DESCRIPTION, CLUSTER_NAME), +# appends the app at the end of the parent clusters//apps/kustomization.yaml list (removing trailing blank line if present), # and stages changes in Git. set -euo pipefail @@ -23,18 +23,14 @@ 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 + 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 + 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 + echo "Error: Application name must not contain path separators (/ or \\)." >&2; exit 1 fi # Cluster name @@ -42,42 +38,31 @@ 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 + 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 + 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 + echo "Error: Cluster name must not contain path separators (/ or \\)." >&2; exit 1 fi -# Description (allows spaces, letters upper/lower, digits, dot, comma) +# 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 + 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 + 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 + echo "Error: Directory already exists: $TARGET_DIR" >&2; exit 1 fi # Templates @@ -88,38 +73,33 @@ 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 + if [[ ! -f "$f" ]]; then echo "Error: Template not found: ${f}" >&2; missing=true; fi done -if [[ "$missing" == true ]]; then - exit 1 -fi +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//&/\\&}" +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 -APP_PROJECT_DEST="${TARGET_DIR}/app-project.yaml" 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" \ -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" +# 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" @@ -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: $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" 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 + echo "Error: Parent kustomization not found: ${PARENT_KUSTOMIZATION}" >&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 +# 1) If the last line is empty, delete it (BSD sed) +# This prints all lines except a final empty line at EOF. +# Explanation: +# - $: matches the last line +# - /^$/: last line is empty +# - d: delete that line +sed -i '' -e '${/^$/d;}' "$PARENT_KUSTOMIZATION" -# 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=" " +# 2) Append the new application line at the end (with two-space indent to match your file) +printf " - %s\n" "$APPLICATION_NAME" >> "$PARENT_KUSTOMIZATION" - # 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 }')" +echo "Updated parent kustomization.yaml: appended '${APPLICATION_NAME}' at end of resources list." - 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) +# Stage changes in Git 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/}"