#!/bin/bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TOFU_ROOT="$(dirname "$SCRIPT_DIR")" PROJECT_ROOT="$(dirname "$TOFU_ROOT")" # ─── Usage ──────────────────────────────────────────────────────────── usage() { cat < [options] Provision a Kubernetes cluster using OpenTofu. Mirrors bootstrap.sh convention: cluster = - Clusters: aks-dev | aks-prod | eks-dev | eks-prod gke-dev | gke-prod | upc-dev | upc-prod -workload (for workload clusters) Options: --plan Plan only, don't apply --destroy Destroy the cluster (use teardown-cluster.sh instead) --auto Skip confirmation prompts -h, --help Show this help Examples: $0 aks-dev $0 eks-prod --plan $0 upc-dev --auto Prerequisites: - tofu, kubectl, helm installed - Platform credentials in .tofu/configs/.env - Cluster config in clusters/.yaml After provisioning, run: ./bootstrap.sh EOF exit "${1:-0}" } # ─── Parse arguments ────────────────────────────────────────────────── CLUSTER="" PLAN_ONLY=false DESTROY=false AUTO_APPROVE=false while [[ $# -gt 0 ]]; do case "$1" in --plan) PLAN_ONLY=true; shift ;; --destroy) DESTROY=true; shift ;; --auto) AUTO_APPROVE=true; shift ;; -h|--help) usage 0 ;; -*) echo "Unknown option: $1"; usage 1 ;; *) if [[ -z "$CLUSTER" ]]; then CLUSTER="$1" else echo "Error: unexpected argument '$1'" usage 1 fi shift ;; esac done [[ -z "$CLUSTER" ]] && { echo "Error: argument required"; usage 1; } # ─── Map cluster → platform + env ──────────────────────────────────── PLATFORM="${CLUSTER%%-*}" # aks-dev → aks ENV="${CLUSTER#*-}" # aks-dev → dev case "$PLATFORM" in aks|eks|gke|upc) ;; *) echo "Error: unknown platform '$PLATFORM'. Expected: aks, eks, gke, upc"; exit 1 ;; esac TOFU_DIR="$TOFU_ROOT/platforms/$PLATFORM/$ENV" if [[ ! -d "$TOFU_DIR" ]]; then echo "Error: tofu directory not found: $TOFU_DIR" echo "Available environments for $PLATFORM:" ls -1 "$TOFU_ROOT/platforms/$PLATFORM/" 2>/dev/null | grep -v modules || echo " (none)" exit 1 fi echo "=========================================" echo " Kubernetes Cluster Setup" echo "=========================================" echo "" echo " Cluster: $CLUSTER" echo " Platform: $PLATFORM" echo " Env: $ENV" echo " Tofu dir: $TOFU_DIR" echo "" # ─── Prerequisites ──────────────────────────────────────────────────── echo "=== Checking Prerequisites ===" command -v tofu >/dev/null 2>&1 || { echo "Error: tofu is not installed."; exit 1; } command -v kubectl >/dev/null 2>&1 || { echo "Error: kubectl is not installed."; exit 1; } command -v helm >/dev/null 2>&1 || { echo "Error: helm is not installed."; exit 1; } echo " tofu, kubectl, helm: OK" # ─── Load platform credentials ──────────────────────────────────────── ENV_FILE="$TOFU_ROOT/configs/$PLATFORM.env" if [[ -f "$ENV_FILE" ]]; then echo " Loading credentials from configs/$PLATFORM.env" set -a # shellcheck disable=SC1090 source "$ENV_FILE" set +a else echo " Warning: $ENV_FILE not found — using existing environment/CLI auth" echo " Copy configs/$PLATFORM.env.example → configs/$PLATFORM.env to configure" fi # ─── Load cluster config (if exists) ────────────────────────────────── CLUSTER_CONFIG="$PROJECT_ROOT/clusters/$CLUSTER.yaml" if [[ -f "$CLUSTER_CONFIG" ]]; then echo " Loading cluster config from clusters/$CLUSTER.yaml" if command -v yq >/dev/null 2>&1; then eval "$(yq -r 'to_entries[] | "export CLUSTER_\(.key)=\"\(.value)\""' "$CLUSTER_CONFIG")" echo " Cluster name: ${CLUSTER_clusterName:-$CLUSTER}" else echo " Warning: yq not installed — cluster config not loaded" fi else echo " Warning: $CLUSTER_CONFIG not found — using defaults" fi echo "" # ─── Run OpenTofu ───────────────────────────────────────────────────── cd "$TOFU_DIR" echo "=== Initializing OpenTofu ===" tofu init echo "" if $DESTROY; then echo "=== Planning Destruction ===" tofu plan -destroy -out=tfplan if ! $AUTO_APPROVE; then echo "" read -rp "DESTROY cluster $CLUSTER? This is irreversible. (yes/no) " REPLY [[ "$REPLY" == "yes" ]] || { echo "Cancelled."; exit 1; } fi echo "Destroying infrastructure..." tofu apply tfplan echo "" echo "=== Cluster $CLUSTER Destroyed ===" elif $PLAN_ONLY; then echo "=== Planning Infrastructure ===" tofu plan echo "" echo "=== Plan complete (--plan mode, no changes applied) ===" else echo "=== Planning Infrastructure ===" tofu plan -out=tfplan if ! $AUTO_APPROVE; then echo "" read -rp "Apply this plan for $CLUSTER? (y/n) " -n 1 REPLY echo [[ "$REPLY" =~ ^[Yy]$ ]] || { echo "Cancelled."; exit 1; } fi echo "Applying infrastructure..." tofu apply tfplan # ─── Save kubeconfig ────────────────────────────────────────────── KUBECONFIG_DIR="$PROJECT_ROOT/private/$CLUSTER" mkdir -p "$KUBECONFIG_DIR" KUBECONFIG_FILE="$KUBECONFIG_DIR/kubeconfig" echo "" echo "=== Saving Kubeconfig ===" case "$PLATFORM" in aks) if tofu output -raw kubeconfig > "$KUBECONFIG_FILE" 2>/dev/null; then echo " Saved from tofu output" else echo " Fetching from Azure CLI..." RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg") NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}") az aks get-credentials --resource-group "$RG" --name "$NAME" --file "$KUBECONFIG_FILE" --overwrite-existing fi ;; eks) NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}") REGION=$(tofu output -raw aws_region 2>/dev/null || echo "${AWS_REGION:-eu-west-1}") aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE" ;; gke) NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}") REGION=$(tofu output -raw region 2>/dev/null || echo "${GCP_REGION:-europe-west4}") PROJECT=$(tofu output -raw project_id 2>/dev/null || echo "${GCP_PROJECT_ID:-}") gcloud container clusters get-credentials "$NAME" --region "$REGION" --project "$PROJECT" 2>/dev/null \ && cp ~/.kube/config "$KUBECONFIG_FILE" \ || echo " Warning: could not fetch kubeconfig via gcloud" ;; upc) if tofu output -raw kubeconfig > "$KUBECONFIG_FILE" 2>/dev/null; then echo " Saved from tofu output" else CLUSTER_ID=$(tofu output -raw cluster_id 2>/dev/null || echo "${UPCLOUD_CLUSTER_ID:-}") if [[ -n "$CLUSTER_ID" ]]; then upctl kubernetes config "$CLUSTER_ID" > "$KUBECONFIG_FILE" else echo " Warning: could not determine cluster ID for kubeconfig" fi fi ;; esac if [[ -f "$KUBECONFIG_FILE" ]]; then chmod 600 "$KUBECONFIG_FILE" echo " Kubeconfig: $KUBECONFIG_FILE" fi # ─── Wait for nodes ────────────────────────────────────────────── echo "" echo "=== Waiting for Cluster Nodes ===" export KUBECONFIG="$KUBECONFIG_FILE" if kubectl wait --for=condition=Ready nodes --all --timeout=300s 2>/dev/null; then echo " All nodes ready" else echo " Warning: nodes not ready within timeout — check cluster status" fi # ─── Summary ───────────────────────────────────────────────────── echo "" echo "=========================================" echo " Cluster $CLUSTER Provisioned" echo "=========================================" echo "" echo " Kubeconfig: $KUBECONFIG_FILE" echo "" echo " Next steps:" echo " export KUBECONFIG=$KUBECONFIG_FILE" echo " ./bootstrap.sh $CLUSTER" echo "" fi