apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: name: environment-provisioner namespace: argo-workflows spec: serviceAccountName: argo-workflow # Clean up pods after workflow completion podGC: strategy: OnWorkflowSuccess # Delete pods when workflow succeeds deleteDelayDuration: 60s # Wait 60s before deleting (allows log collection) # Auto-delete workflow after completion ttlStrategy: secondsAfterCompletion: 3600 # Delete workflow 1 hour after completion secondsAfterSuccess: 1800 # Delete workflow 30 min after success secondsAfterFailure: 86400 # Keep failed workflows for 24 hours for debugging arguments: parameters: - name: namespace value: default - name: services value: "" - name: ttl value: "8" - name: owner value: unknown - name: componentMetadata value: '{}' - name: dependencyPreferences value: '{}' - name: valuesFileOverrides value: '{}' - name: manualDependencyUrls value: '{}' - name: valuesContentEdits value: '{}' - name: helmExternalParams value: '{}' - name: sharedMysqlConfig value: '[]' entrypoint: provision-environment templates: - inputs: parameters: - name: namespace - name: services - name: ttl - name: owner - name: componentMetadata - name: dependencyPreferences - name: valuesFileOverrides - name: manualDependencyUrls - name: valuesContentEdits - name: helmExternalParams - name: sharedMysqlConfig name: provision-environment steps: - - arguments: parameters: - name: namespace value: '{{inputs.parameters.namespace}}' - name: services value: '{{inputs.parameters.services}}' - name: ttl value: '{{inputs.parameters.ttl}}' - name: owner value: '{{inputs.parameters.owner}}' - name: componentMetadata value: '{{inputs.parameters.componentMetadata}}' - name: dependencyPreferences value: '{{inputs.parameters.dependencyPreferences}}' - name: valuesFileOverrides value: '{{inputs.parameters.valuesFileOverrides}}' - name: manualDependencyUrls value: '{{inputs.parameters.manualDependencyUrls}}' - name: valuesContentEdits value: '{{inputs.parameters.valuesContentEdits}}' - name: helmExternalParams value: '{{inputs.parameters.helmExternalParams}}' - name: sharedMysqlConfig value: '{{inputs.parameters.sharedMysqlConfig}}' name: create-git-repo template: create-repo - - arguments: parameters: - name: namespace value: '{{inputs.parameters.namespace}}' - name: owner value: '{{inputs.parameters.owner}}' name: create-argocd-app template: create-argo-app - - arguments: parameters: - name: namespace value: '{{inputs.parameters.namespace}}' - name: services value: '{{inputs.parameters.services}}' name: register-hades-ips template: register-hades-ips - inputs: parameters: - name: namespace - name: services - name: ttl - name: owner - name: componentMetadata - name: dependencyPreferences - name: valuesFileOverrides - name: manualDependencyUrls - name: valuesContentEdits - name: helmExternalParams - name: sharedMysqlConfig name: create-repo script: command: - sh env: # Credentials from External Secrets (GCP Secret Manager) - name: GITEA_URL valueFrom: secretKeyRef: name: gitea-credentials key: url - name: GITEA_USER valueFrom: secretKeyRef: name: gitea-credentials key: username - name: GITEA_PASSWORD valueFrom: secretKeyRef: name: gitea-credentials key: password image: alpine/git:latest source: | #!/bin/sh set -e NAMESPACE="{{inputs.parameters.namespace}}" SERVICES="{{inputs.parameters.services}}" TTL="{{inputs.parameters.ttl}}" OWNER="{{inputs.parameters.owner}}" COMPONENT_METADATA=$(cat <<'EOFPARAM' {{inputs.parameters.componentMetadata}} EOFPARAM ) DEPENDENCY_PREFS=$(cat <<'EOFPARAM' {{inputs.parameters.dependencyPreferences}} EOFPARAM ) VALUES_FILE_OVERRIDES=$(cat <<'EOFPARAM' {{inputs.parameters.valuesFileOverrides}} EOFPARAM ) MANUAL_DEPENDENCY_URLS=$(cat <<'EOFPARAM' {{inputs.parameters.manualDependencyUrls}} EOFPARAM ) VALUES_CONTENT_EDITS=$(cat <<'EOFPARAM' {{inputs.parameters.valuesContentEdits}} EOFPARAM ) HELM_EXTERNAL_PARAMS=$(cat <<'EOFPARAM' {{inputs.parameters.helmExternalParams}} EOFPARAM ) SHARED_MYSQL_CONFIG=$(cat <<'EOFPARAM' {{inputs.parameters.sharedMysqlConfig}} EOFPARAM ) # Sanitize namespace name (replace spaces and special chars with hyphens) NAMESPACE=$(echo "$NAMESPACE" | tr '[:upper:]' '[:lower:]' | tr ' _' '-' | sed 's/[^a-z0-9-]//g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//') # Sanitize owner name for labels (Kubernetes labels have strict requirements) OWNER_LABEL=$(echo "$OWNER" | tr '[:upper:]' '[:lower:]' | tr ' _' '-' | sed 's/[^a-z0-9-]//g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//') echo "Creating Git repository for namespace: $NAMESPACE" # Install curl and jq apk add --no-cache curl jq # Create repository in Gitea REPO_NAME="environment-$NAMESPACE" # Extract hostname from GITEA_URL for git credentials GITEA_HOST=$(echo "$GITEA_URL" | sed 's|https://||' | sed 's|http://||' | cut -d'/' -f1) echo "Creating Gitea repository: $REPO_NAME" curl -X POST "$GITEA_URL/api/v1/user/repos" \ -u "$GITEA_USER:$GITEA_PASSWORD" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"$REPO_NAME\", \"description\": \"Environment for $NAMESPACE (owner: $OWNER, ttl: ${TTL}h)\", \"private\": false, \"auto_init\": true }" || echo "Repository may already exist" # Clone the repository git config --global http.sslVerify true git config --global user.email "backstage@osmos.io" git config --global user.name "Backstage" # URL encode the password for git credentials ENCODED_PASSWORD=$(printf %s "$GITEA_PASSWORD" | jq -sRr @uri) # Configure credentials git config --global credential.helper store printf "https://%s:%s@%s\n" "$GITEA_USER" "$ENCODED_PASSWORD" "$GITEA_HOST" > $HOME/.git-credentials REPO_URL="$GITEA_URL/$GITEA_USER/$REPO_NAME.git" git clone "$REPO_URL" /tmp/repo 2>&1 || (cd /tmp/repo && git pull 2>&1) cd /tmp/repo # Create manifests directory mkdir -p manifests # Create namespace manifest cat > manifests/namespace.yaml < manifests/networkpolicy.yaml </dev/null | while read -r instance; do INSTANCE_NAME=$(echo "$instance" | jq -r '.instanceName') VERSION=$(echo "$instance" | jq -r '.version') if [ -z "$INSTANCE_NAME" ] || [ "$INSTANCE_NAME" = "null" ]; then continue fi echo "Creating shared MySQL instance: $INSTANCE_NAME (MySQL $VERSION)" # Build SQL to create all databases and grant permissions DB_INIT_SQL="-- Auto-generated database initialization" for db in $(echo "$instance" | jq -c '.databases[]'); do DB_NAME=$(echo "$db" | jq -r '.databaseName') SERVICE_NAME=$(echo "$db" | jq -r '.serviceName') DB_INIT_SQL="${DB_INIT_SQL} -- Database for ${SERVICE_NAME} CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO 'mysql_user'@'%';" done DB_INIT_SQL="${DB_INIT_SQL} FLUSH PRIVILEGES;" # Create ArgoCD Application for shared MySQL cat > "manifests/${INSTANCE_NAME}-application.yaml" < "manifests/$APP_NAME-application.yaml" < "$VALUES_TMP" # Debug: show temp file content echo " DEBUG: Temp file content:" cat "$VALUES_TMP" echo " DEBUG: End of temp file" # Use awk with getline to read from temp file (preserves multi-line content) awk ' /valueFiles:/ { print getline print while ((getline line < "'"$VALUES_TMP"'") > 0) print line next } { print } ' "manifests/$APP_NAME-application.yaml" > "manifests/$APP_NAME-application.yaml.tmp" mv "manifests/$APP_NAME-application.yaml.tmp" "manifests/$APP_NAME-application.yaml" rm -f "$VALUES_TMP" # Debug: show generated manifest echo " DEBUG: Generated manifest helm section:" grep -A 30 "helm:" "manifests/$APP_NAME-application.yaml" || true else echo " WARNING: EDITED_CONTENT is empty, skipping inline values insertion" fi fi echo " HTTPRoute URL: https://$HTTPROUTE_HOST" else # No Helm chart - create simple deployment manifest echo "Creating deployment manifest for service: $service" cat > "manifests/$SANITIZED_SERVICE-deployment.yaml" < README.md < /dev/null 2>&1; then echo " Creating allhosts ConfigMap: $CONFIGMAP_NAME" kubectl create configmap "$CONFIGMAP_NAME" -n "$NAMESPACE" \ --from-literal="allhosts.tsv=Host Ipaddress HostGroup Team Type" echo " ConfigMap created successfully" else echo " ConfigMap already exists: $CONFIGMAP_NAME" fi fi done echo "" # Wait for pods to be created by ArgoCD (may take time to sync) echo "Waiting for Hades MySQL pod to be created..." MYSQL_POD="" POD_WAIT=0 POD_MAX_WAIT=180 while [ -z "$MYSQL_POD" ] && [ $POD_WAIT -lt $POD_MAX_WAIT ]; do # Try label used by WSO2 MySQL chart (hades uses this) MYSQL_POD=$(kubectl get pods -n "$NAMESPACE" -l "app=hades-mysql-db-service" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "") if [ -z "$MYSQL_POD" ]; then # Try Bitnami MySQL chart label MYSQL_POD=$(kubectl get pods -n "$NAMESPACE" -l "app.kubernetes.io/name=mysql" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || echo "") fi if [ -z "$MYSQL_POD" ]; then # Fallback: grep for hades mysql in pod names MYSQL_POD=$(kubectl get pods -n "$NAMESPACE" -o name 2>/dev/null | grep -i "hades.*mysql" | head -1 | sed 's/pod\///') fi if [ -z "$MYSQL_POD" ]; then echo " Waiting for MySQL pod... (${POD_WAIT}s)" sleep 10 POD_WAIT=$((POD_WAIT + 10)) fi done if [ -z "$MYSQL_POD" ]; then echo "Warning: Could not find Hades MySQL pod after ${POD_MAX_WAIT}s" echo "Skipping IP registration - Hades may not be deployed" exit 0 fi echo "Found MySQL pod: $MYSQL_POD" # Wait for Hades MySQL to be ready (should already be running from previous steps) echo "Waiting for Hades MySQL to be ready..." MAX_WAIT=120 WAIT_TIME=0 while [ $WAIT_TIME -lt $MAX_WAIT ]; do if kubectl exec -n "$NAMESPACE" "$MYSQL_POD" -- mysql -u "$MYSQL_USER" -p"$MYSQL_PWD" -e "SELECT 1" "$MYSQL_DB" > /dev/null 2>&1; then echo "Hades MySQL is ready" break fi echo " Waiting for MySQL... (${WAIT_TIME}s)" sleep 5 WAIT_TIME=$((WAIT_TIME + 5)) done if [ $WAIT_TIME -ge $MAX_WAIT ]; then echo "Warning: Could not connect to Hades MySQL after ${MAX_WAIT}s" echo "Skipping IP registration - Hades may not be deployed" exit 0 fi # Wait for Hades dbinit job to complete (creates the database schema) echo "Waiting for Hades dbinit job to complete..." DBINIT_MAX_WAIT=300 DBINIT_WAIT_TIME=0 DBINIT_COMPLETED=false while [ $DBINIT_WAIT_TIME -lt $DBINIT_MAX_WAIT ]; do # Check if dbinit job exists and is complete DBINIT_JOB=$(kubectl get jobs -n "$NAMESPACE" -o name 2>/dev/null | grep -i "hades.*dbinit" | head -1) || DBINIT_JOB="" if [ -n "$DBINIT_JOB" ]; then echo " Found dbinit job: $DBINIT_JOB" # Check if job failed DBINIT_FAILED=$(kubectl get "$DBINIT_JOB" -n "$NAMESPACE" -o jsonpath='{.status.failed}' 2>/dev/null) || DBINIT_FAILED="0" if [ "$DBINIT_FAILED" != "" ] && [ "$DBINIT_FAILED" != "0" ]; then echo "ERROR: Hades dbinit job failed!" kubectl logs "$DBINIT_JOB" -n "$NAMESPACE" --tail=50 2>/dev/null || true exit 1 fi # Check if job succeeded DBINIT_COMPLETE=$(kubectl get "$DBINIT_JOB" -n "$NAMESPACE" -o jsonpath='{.status.succeeded}' 2>/dev/null) || DBINIT_COMPLETE="0" if [ "$DBINIT_COMPLETE" = "1" ]; then echo "Hades dbinit job completed successfully" DBINIT_COMPLETED=true break fi # Show job status DBINIT_ACTIVE=$(kubectl get "$DBINIT_JOB" -n "$NAMESPACE" -o jsonpath='{.status.active}' 2>/dev/null) || DBINIT_ACTIVE="0" echo " Job status - active: $DBINIT_ACTIVE, succeeded: $DBINIT_COMPLETE, failed: $DBINIT_FAILED" else echo " Waiting for dbinit job to be created... (${DBINIT_WAIT_TIME}s)" fi sleep 10 DBINIT_WAIT_TIME=$((DBINIT_WAIT_TIME + 10)) done if [ "$DBINIT_COMPLETED" != "true" ]; then echo "ERROR: Hades dbinit job not completed after ${DBINIT_MAX_WAIT}s" echo "Cannot proceed without database schema. Exiting." exit 1 fi # Additional wait for database to be fully ready after dbinit echo "Waiting 10s for database schema to be fully applied..." sleep 10 # Helper function to run MySQL commands via kubectl exec # Using -c to specify container explicitly to avoid kubectl container selection messages MYSQL_CONTAINER=$(kubectl get pod -n "$NAMESPACE" "$MYSQL_POD" -o jsonpath='{.spec.containers[0].name}' 2>/dev/null) echo "MySQL container name: $MYSQL_CONTAINER" run_mysql() { local result local exit_code # Run kubectl exec with explicit container, redirect kubectl stderr to /dev/null # but capture MySQL output (including MySQL warnings on stderr) result=$(kubectl exec -n "$NAMESPACE" "$MYSQL_POD" -c "$MYSQL_CONTAINER" -- \ mysql -u "$MYSQL_USER" -p"$MYSQL_PWD" "$MYSQL_DB" -sN -e "$1" 2>&1) exit_code=$? # Filter out MySQL password warning result=$(echo "$result" | grep -v "Using a password on the command line interface can be insecure") if [ $exit_code -ne 0 ]; then echo " MySQL error: $result" >&2 return 1 fi echo "$result" } # Function to register IP for an app register_ip() { local app_name="$1" local ip="$2" if [ -z "$app_name" ] || [ -z "$ip" ]; then return 0 fi # Try to find existing app with various name formats # Database may have: HADES, hades, hades-service, hadesService, etc. echo " Looking for existing app matching: $app_name" # Extract base name (remove -service suffix if present) BASE_NAME=$(echo "$app_name" | sed 's/-service$//' | sed 's/Service$//') BASE_NAME_UPPER=$(echo "$BASE_NAME" | tr '[:lower:]' '[:upper:]') BASE_NAME_LOWER=$(echo "$BASE_NAME" | tr '[:upper:]' '[:lower:]') # Try multiple name variations (case-insensitive search) APP_ID="" for try_name in "$app_name" "$BASE_NAME" "$BASE_NAME_UPPER" "$BASE_NAME_LOWER"; do APP_ID=$(run_mysql "SELECT id FROM apps WHERE LOWER(name)=LOWER('$try_name')" 2>&1) || true APP_ID=$(echo "$APP_ID" | grep -o '[0-9]*' | head -1) if [ -n "$APP_ID" ]; then echo " Found existing app with name variation: $try_name (id: $APP_ID)" break fi done # If not found, create new app if [ -z "$APP_ID" ]; then echo " No existing app found, creating: $app_name" run_mysql "INSERT INTO apps (name, token, creation_date, last_update, is_deleted) VALUES ('$app_name', UUID(), NOW(), NOW(), 0)" 2>&1 || true APP_ID=$(run_mysql "SELECT id FROM apps WHERE name='$app_name'" 2>&1) || true APP_ID=$(echo "$APP_ID" | grep -o '[0-9]*' | head -1) fi if [ -z "$APP_ID" ]; then echo " Failed to get app_id for: $app_name" return 1 fi echo " Using app_id: $APP_ID" # Register IP (using INSERT IGNORE to handle duplicates gracefully) echo " Registering IP $ip for app $app_name (app_id: $APP_ID)" if run_mysql "INSERT IGNORE INTO app_ips_accesses (app_id, ipaddress, creation_date, last_update, is_deleted) VALUES ($APP_ID, '$ip', NOW(), NOW(), 0)"; then echo " Successfully registered IP $ip" else echo " Failed to register IP $ip" fi return 0 } # Verify required tables exist (with retry) echo "Checking database tables..." TABLE_CHECK_MAX_WAIT=60 TABLE_CHECK_WAIT=0 TABLES_FOUND=false while [ $TABLE_CHECK_WAIT -lt $TABLE_CHECK_MAX_WAIT ]; do TABLES=$(run_mysql "SHOW TABLES" 2>&1) || TABLES="" echo "Available tables: $TABLES" if echo "$TABLES" | grep -q "apps" && echo "$TABLES" | grep -q "app_ips_accesses"; then echo "Required tables found!" TABLES_FOUND=true break fi echo " Required tables not yet available, retrying... (${TABLE_CHECK_WAIT}s)" sleep 5 TABLE_CHECK_WAIT=$((TABLE_CHECK_WAIT + 5)) done if [ "$TABLES_FOUND" != "true" ]; then echo "ERROR: Required tables not found after ${TABLE_CHECK_MAX_WAIT}s" echo "Missing: apps and/or app_ips_accesses tables" echo "Database may not be initialized properly. Exiting." exit 1 fi echo "Proceeding with IP registration..." echo "" # 1. Collect common bridge/gateway IPs (handles SNAT across different clusters) COMMON_BRIDGE_IPS="10.244.0.1 10.96.0.1 172.17.0.1" echo "Common bridge IPs: $COMMON_BRIDGE_IPS" # 2. Get node internal IPs NODE_IPS=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null || echo "") echo "Node IPs: $NODE_IPS" # 3. Get pod network CIDR gateways from ALL nodes echo "Collecting pod CIDR gateways from all nodes..." POD_CIDRS=$(kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' 2>/dev/null || echo "") for cidr in $POD_CIDRS; do if [ -n "$cidr" ]; then GATEWAY_IP=$(echo "$cidr" | sed 's/\.[0-9]*\/.*/.1/') COMMON_BRIDGE_IPS="$COMMON_BRIDGE_IPS $GATEWAY_IP" echo " Pod CIDR gateway: $GATEWAY_IP (from $cidr)" fi done # Combine all IPs to register ALL_IPS="$COMMON_BRIDGE_IPS $NODE_IPS" echo "" # Process each service SERVICES_LIST=$(echo "$SERVICES" | tr ',' ' ') for service in $SERVICES_LIST; do service=$(echo "$service" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [ -z "$service" ] && continue # Skip resource references if echo "$service" | grep -q "^resource:"; then continue fi echo "Processing service: $service" # Get actual pod IPs for this service # Try multiple label selectors as different charts use different labels POD_IPS=$(kubectl get pods -n "$NAMESPACE" \ -l "app.kubernetes.io/instance=${NAMESPACE}-${service}" \ -o jsonpath='{.items[*].status.podIP}' 2>/dev/null) || POD_IPS="" if [ -z "$POD_IPS" ]; then # Try backstage.io/kubernetes-id label POD_IPS=$(kubectl get pods -n "$NAMESPACE" \ -l "backstage.io/kubernetes-id=${service}" \ -o jsonpath='{.items[*].status.podIP}' 2>/dev/null) || POD_IPS="" fi echo " Found pod IPs: ${POD_IPS:-none}" # Register pod IPs for ip in $POD_IPS; do [ -n "$ip" ] && register_ip "$service" "$ip" done # Register bridge/gateway/node IPs for ip in $ALL_IPS; do [ -n "$ip" ] && register_ip "$service" "$ip" done echo "" done # Register IPs for 'nagios' app (used by health probes) echo "Registering IPs for nagios (health probes)..." for ip in $ALL_IPS; do [ -n "$ip" ] && register_ip "nagios" "$ip" done echo "" echo "=== IP Registration Summary ===" kubectl exec -n "$NAMESPACE" "$MYSQL_POD" -- mysql -u "$MYSQL_USER" -p"$MYSQL_PWD" "$MYSQL_DB" -e \ "SELECT a.name as app_name, i.ipaddress FROM apps a JOIN app_ips_accesses i ON a.id = i.app_id WHERE i.is_deleted = 0 ORDER BY a.name, i.ipaddress" 2>/dev/null || echo "Could not retrieve summary" # Update allhosts ConfigMap with registered services echo "" echo "=== Updating allhosts.tsv ConfigMap ===" # Find the allhosts ConfigMap (name depends on Helm release/fullnameOverride) CONFIGMAP_NAME=$(kubectl get configmap -n "$NAMESPACE" -o name 2>/dev/null | grep -E "allhosts$" | head -1 | sed 's|configmap/||') if [ -n "$CONFIGMAP_NAME" ]; then echo "Found allhosts ConfigMap: $CONFIGMAP_NAME" # Get current content CURRENT_CONTENT=$(kubectl get configmap "$CONFIGMAP_NAME" -n "$NAMESPACE" -o jsonpath='{.data.allhosts\.tsv}') # Build new entries NEW_ENTRIES="" # Add node gateway IPs (pod CIDR gateways from all nodes) echo "Adding node gateway IPs to ConfigMap..." GATEWAY_COUNT=1 for gateway_ip in $COMMON_BRIDGE_IPS; do [ -z "$gateway_ip" ] && continue HOSTNAME="k8s-gateway-${GATEWAY_COUNT}.${NAMESPACE}.internal" if ! echo "$CURRENT_CONTENT" | grep -q "${gateway_ip}"; then NEW_ENTRY="${HOSTNAME}\t${gateway_ip}\tinfra\tplatform\tk8s.gateway" NEW_ENTRIES="${NEW_ENTRIES}${NEW_ENTRY}\n" echo " Adding gateway: $HOSTNAME -> $gateway_ip" GATEWAY_COUNT=$((GATEWAY_COUNT + 1)) else echo " Skipping (exists): $gateway_ip" fi done # Add node internal IPs echo "Adding node IPs to ConfigMap..." NODE_COUNT=1 for node_ip in $NODE_IPS; do [ -z "$node_ip" ] && continue HOSTNAME="k8s-node-${NODE_COUNT}.${NAMESPACE}.internal" if ! echo "$CURRENT_CONTENT" | grep -q "${node_ip}"; then NEW_ENTRY="${HOSTNAME}\t${node_ip}\tinfra\tplatform\tk8s.node" NEW_ENTRIES="${NEW_ENTRIES}${NEW_ENTRY}\n" echo " Adding node: $HOSTNAME -> $node_ip" NODE_COUNT=$((NODE_COUNT + 1)) else echo " Skipping (exists): $node_ip" fi done # Add service pod IPs echo "Adding service pod IPs to ConfigMap..." for service in $SERVICES_LIST; do service=$(echo "$service" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [ -z "$service" ] && continue # Skip resource references if echo "$service" | grep -q "^resource:"; then continue fi # Get pod IP for this service POD_IP=$(kubectl get pods -n "$NAMESPACE" \ -l "app.kubernetes.io/instance=${NAMESPACE}-${service}" \ -o jsonpath='{.items[0].status.podIP}' 2>/dev/null) || POD_IP="" if [ -z "$POD_IP" ]; then POD_IP=$(kubectl get pods -n "$NAMESPACE" \ -l "backstage.io/kubernetes-id=${service}" \ -o jsonpath='{.items[0].status.podIP}' 2>/dev/null) || POD_IP="" fi if [ -n "$POD_IP" ]; then HOSTNAME="${service}.${NAMESPACE}.onlinesales.ai" # Check if entry already exists if ! echo "$CURRENT_CONTENT" | grep -q "^${HOSTNAME}[[:space:]]"; then NEW_ENTRY="${HOSTNAME}\t${POD_IP}\t${service}\tplatform\tk8s.pod" NEW_ENTRIES="${NEW_ENTRIES}${NEW_ENTRY}\n" echo " Adding: $HOSTNAME -> $POD_IP" else echo " Skipping (exists): $HOSTNAME" fi fi done if [ -n "$NEW_ENTRIES" ]; then # Append new entries to content UPDATED_CONTENT="${CURRENT_CONTENT} $(echo -e "$NEW_ENTRIES")" # Patch the ConfigMap using kubectl patch (avoids last-applied-configuration warning) kubectl patch configmap "$CONFIGMAP_NAME" -n "$NAMESPACE" \ --type merge -p "{\"data\":{\"allhosts.tsv\":$(echo "$UPDATED_CONTENT" | jq -Rs .)}}" echo "ConfigMap updated successfully" else echo "No new entries to add" fi else echo "Warning: No allhosts ConfigMap found in namespace $NAMESPACE" echo "Looking for ConfigMaps ending with '-allhosts'..." kubectl get configmap -n "$NAMESPACE" -o name 2>/dev/null || echo " No ConfigMaps found" echo "Skipping ConfigMap update" fi echo "" echo "Hades IP registration completed"