From dd17a6b5dca74d2291343826c01c1842dc5f566c Mon Sep 17 00:00:00 2001 From: Vaibhav Pathak Date: Fri, 16 Jan 2026 16:22:51 +0530 Subject: [PATCH] intial commit --- README.md | 174 ++++ argocd-apps/environment-provisioner.yaml | 26 + environment-provisioner/backstage-rbac.yaml | 49 ++ environment-provisioner/external-secrets.yaml | 71 ++ environment-provisioner/workflow-rbac.yaml | 77 ++ .../workflow-template.yaml | 770 ++++++++++++++++++ 6 files changed, 1167 insertions(+) create mode 100644 README.md create mode 100644 argocd-apps/environment-provisioner.yaml create mode 100644 environment-provisioner/backstage-rbac.yaml create mode 100644 environment-provisioner/external-secrets.yaml create mode 100644 environment-provisioner/workflow-rbac.yaml create mode 100644 environment-provisioner/workflow-template.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..21d9f64 --- /dev/null +++ b/README.md @@ -0,0 +1,174 @@ +# OSMOS Infrastructure + +This repository contains infrastructure configurations for the OSMOS platform, managed via ArgoCD GitOps. + +## Repository Structure + +``` +osmos-infra/ +├── README.md +├── argocd-apps/ +│ └── environment-provisioner.yaml # ArgoCD App for provisioner +└── environment-provisioner/ + ├── workflow-template.yaml # Argo Workflows WorkflowTemplate + ├── workflow-rbac.yaml # ClusterRole for workflow execution + ├── backstage-rbac.yaml # ClusterRole for Backstage + └── external-secrets.yaml # ExternalSecret for GCP Secret Manager +``` + +## Environment Provisioner + +The environment provisioner is an Argo Workflows WorkflowTemplate that creates isolated test environments on-demand via Backstage. + +### Features + +- **Self-service environments**: Users can create test environments via Backstage UI +- **GitOps-based**: Each environment gets its own Git repository with manifests +- **ArgoCD managed**: Environments are synced and managed by ArgoCD +- **Isolated namespaces**: Each environment is isolated with NetworkPolicies +- **Automatic cleanup**: TTL-based cleanup (configurable) + +### Prerequisites + +1. **External Secrets Operator** installed with GCP Secret Manager provider +2. **ClusterSecretStore** `gcpsm-secret-store` configured +3. **ArgoCD** installed in `argocd` namespace +4. **Argo Workflows** installed in `argo` namespace +5. **Gitea** accessible for environment repositories + +### GCP Secret Manager Secrets Required + +Create these secrets in GCP Secret Manager before deployment: + +| Secret Name | Description | +|-------------|-------------| +| `gitea-url` | Gitea server URL (e.g., `https://gitea.os-tf-qa.onlinesales.ai`) | +| `gitea-username` | Gitea admin username | +| `gitea-password` | Gitea admin password | +| `argocd-server` | ArgoCD server address (e.g., `argocd.os-tf-qa.onlinesales.ai:443`) | +| `argocd-username` | ArgoCD admin username | +| `argocd-password` | ArgoCD admin password | +| `argo-workflows-token` | Long-lived token for Argo Workflows API | + +### Deployment + +1. **Create GCP Secret Manager secrets** (if not already created): + ```bash + # Using gcloud CLI + gcloud secrets create gitea-url --data-file=- <<< "https://gitea.os-tf-qa.onlinesales.ai" + gcloud secrets create gitea-username --data-file=- <<< "giteaAdmin" + gcloud secrets create gitea-password --data-file=- <<< "" + gcloud secrets create argocd-server --data-file=- <<< "argocd.os-tf-qa.onlinesales.ai:443" + gcloud secrets create argocd-username --data-file=- <<< "admin" + gcloud secrets create argocd-password --data-file=- <<< "" + + # Generate Argo Workflows token and store it + ARGO_TOKEN=$(kubectl create token argo-server -n argo --duration=999999h) + gcloud secrets create argo-workflows-token --data-file=- <<< "$ARGO_TOKEN" + ``` + +2. **Create repository** in Gitea: + ```bash + # Create osmos-infra repository in Gitea via UI or API + ``` + +3. **Push this repository** to Gitea: + ```bash + git remote add origin https://gitea.os-tf-qa.onlinesales.ai/giteaAdmin/osmos-infra.git + git push -u origin main + ``` + +4. **Verify ClusterSecretStore exists**: + ```bash + kubectl get clustersecretstore gcpsm-secret-store + ``` + +5. **Apply ArgoCD Application**: + ```bash + kubectl apply -f argocd-apps/environment-provisioner.yaml + ``` + +6. **Verify deployment**: + ```bash + # Check ExternalSecrets synced successfully + kubectl get externalsecret -n argo + kubectl get externalsecret -n backstage + + # Check secrets were created + kubectl get secret gitea-credentials argocd-credentials -n argo + kubectl get secret argo-workflows-token -n backstage + + # Check WorkflowTemplate + kubectl get workflowtemplate -n argo + + # Check RBAC + kubectl get clusterrole workflow-executor backstage-environment-manager + ``` + +### Usage + +Once deployed, environments can be created via: +1. **Backstage UI**: Navigate to the Environments page and click "Create Environment" +2. **Direct API**: Submit a workflow with the required parameters + +### Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ ArgoCD (GitOps) │ +├─────────────────────────────────────────────────────────────────┤ +│ App: environment-provisioner-infra │ +│ ├── WorkflowTemplate (environment-provisioner) │ +│ ├── ClusterRole (workflow-executor) │ +│ ├── ClusterRoleBinding (workflow-executor-binding) │ +│ ├── ClusterRole (backstage-environment-manager) │ +│ ├── ClusterRoleBinding (backstage-environment-manager-binding) │ +│ └── ExternalSecrets (gitea-credentials, argocd-credentials) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Argo Workflows │ +├─────────────────────────────────────────────────────────────────┤ +│ WorkflowTemplate: environment-provisioner │ +│ ├── Step 1: create-repo (creates Gitea repo with manifests) │ +│ ├── Step 2: create-argo-app (creates ArgoCD Application) │ +│ └── Step 3: register-hades-ips (optional health registration) │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Per-Environment │ +├─────────────────────────────────────────────────────────────────┤ +│ Gitea Repo: environment-{namespace} │ +│ └── manifests/ │ +│ ├── namespace.yaml │ +│ ├── networkpolicy.yaml │ +│ └── {service}-application.yaml (ArgoCD App per service) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Security + +- **Secrets**: All credentials are stored in GCP Secret Manager and synced via External Secrets Operator +- **RBAC**: Minimal permissions granted to workflow and backstage service accounts +- **TLS**: All connections use TLS in production +- **Network Isolation**: Each environment is isolated via NetworkPolicies + +### Troubleshooting + +**ExternalSecret not syncing:** +```bash +kubectl describe externalsecret gitea-credentials -n argo +``` + +**WorkflowTemplate not found:** +```bash +kubectl get workflowtemplate -n argo +kubectl describe workflowtemplate environment-provisioner -n argo +``` + +**Permission denied errors:** +```bash +kubectl auth can-i create namespaces --as=system:serviceaccount:argo:argo +``` diff --git a/argocd-apps/environment-provisioner.yaml b/argocd-apps/environment-provisioner.yaml new file mode 100644 index 0000000..a90a085 --- /dev/null +++ b/argocd-apps/environment-provisioner.yaml @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: environment-provisioner-infra + namespace: argocd + labels: + app.kubernetes.io/name: environment-provisioner + app.kubernetes.io/component: infrastructure + app.kubernetes.io/managed-by: argocd +spec: + project: default + source: + # Update this URL to point to your osmos-infra repository in Gitea + repoURL: https://gitea.os-tf-qa.onlinesales.ai/giteaAdmin/osmos-infra.git + targetRevision: HEAD + path: environment-provisioner + destination: + server: https://kubernetes.default.svc + namespace: argo + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true + - ServerSideApply=true diff --git a/environment-provisioner/backstage-rbac.yaml b/environment-provisioner/backstage-rbac.yaml new file mode 100644 index 0000000..68b19bd --- /dev/null +++ b/environment-provisioner/backstage-rbac.yaml @@ -0,0 +1,49 @@ +--- +# ClusterRole for Backstage service account - allows managing environments +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: backstage-environment-manager +rules: + # Namespace management for environment lifecycle + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list", "watch", "delete"] + + # Read access to environment resources for display + - apiGroups: [""] + resources: ["resourcequotas", "limitranges", "pods", "services"] + verbs: ["get", "list", "watch"] + + - apiGroups: ["apps"] + resources: ["deployments", "replicasets"] + verbs: ["get", "list", "watch"] + + - apiGroups: ["networking.k8s.io"] + resources: ["networkpolicies"] + verbs: ["get", "list", "watch"] + + # ArgoCD Applications - for cleanup when deleting environments + - apiGroups: ["argoproj.io"] + resources: ["applications"] + verbs: ["get", "list", "watch", "delete"] + + # Argo Workflows - for monitoring environment provisioning status + - apiGroups: ["argoproj.io"] + resources: ["workflows"] + verbs: ["get", "list", "watch"] + +--- +# Bind the role to backstage service account +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: backstage-environment-manager-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: backstage-environment-manager +subjects: + - kind: ServiceAccount + name: backstage + namespace: backstage diff --git a/environment-provisioner/external-secrets.yaml b/environment-provisioner/external-secrets.yaml new file mode 100644 index 0000000..864bd4e --- /dev/null +++ b/environment-provisioner/external-secrets.yaml @@ -0,0 +1,71 @@ +--- +# ExternalSecret for Gitea credentials +# Syncs secrets from GCP Secret Manager to Kubernetes +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: gitea-credentials + namespace: argo +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: gcpsm-secret-store + target: + name: gitea-credentials + creationPolicy: Owner + data: + - secretKey: url + remoteRef: + key: gitea-url + - secretKey: username + remoteRef: + key: gitea-username + - secretKey: password + remoteRef: + key: gitea-password +--- +# ExternalSecret for ArgoCD credentials +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: argocd-credentials + namespace: argo +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: gcpsm-secret-store + target: + name: argocd-credentials + creationPolicy: Owner + data: + - secretKey: server + remoteRef: + key: argocd-server + - secretKey: username + remoteRef: + key: argocd-username + - secretKey: password + remoteRef: + key: argocd-password +--- +# ExternalSecret for Argo Workflows token (for Backstage) +# This secret is created in the backstage namespace +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: argo-workflows-token + namespace: backstage +spec: + refreshInterval: 1h + secretStoreRef: + kind: ClusterSecretStore + name: gcpsm-secret-store + target: + name: argo-workflows-token + creationPolicy: Owner + data: + - secretKey: token + remoteRef: + key: argo-workflows-token diff --git a/environment-provisioner/workflow-rbac.yaml b/environment-provisioner/workflow-rbac.yaml new file mode 100644 index 0000000..10f5137 --- /dev/null +++ b/environment-provisioner/workflow-rbac.yaml @@ -0,0 +1,77 @@ +--- +# ClusterRole for workflow execution - allows creating namespaces and deploying resources +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: workflow-executor +rules: + # Namespace management + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["create", "get", "list", "patch", "update", "delete"] + + # Apps - Deployments, DaemonSets, StatefulSets, ReplicaSets + - apiGroups: ["apps"] + resources: ["deployments", "daemonsets", "statefulsets", "replicasets"] + verbs: ["create", "get", "list", "watch", "patch", "update", "delete"] + + # Core resources - Services, ConfigMaps, Secrets, ReplicationControllers + - apiGroups: [""] + resources: ["services", "configmaps", "secrets", "replicationcontrollers"] + verbs: ["create", "get", "list", "watch", "patch", "update", "delete"] + + # Batch - Jobs and CronJobs + - apiGroups: ["batch"] + resources: ["jobs", "cronjobs"] + verbs: ["create", "get", "list", "watch", "patch", "update", "delete"] + + # Autoscaling - HorizontalPodAutoscalers + - apiGroups: ["autoscaling"] + resources: ["horizontalpodautoscalers"] + verbs: ["get", "list", "watch"] + + # Resource Quotas and Limit Ranges + - apiGroups: [""] + resources: ["resourcequotas", "limitranges"] + verbs: ["create", "get", "list", "patch", "update", "delete"] + + # Network Policies + - apiGroups: ["networking.k8s.io"] + resources: ["networkpolicies"] + verbs: ["create", "get", "list", "patch", "update", "delete"] + + # Pods for verification and workflow execution + - apiGroups: [""] + resources: ["pods", "pods/log"] + verbs: ["get", "list", "watch", "patch", "update"] + + # ArgoCD Applications (CRDs) + - apiGroups: ["argoproj.io"] + resources: ["applications"] + verbs: ["create", "get", "list", "patch", "update", "delete"] + + # Argo Workflows - for labeling workflows + - apiGroups: ["argoproj.io"] + resources: ["workflows"] + verbs: ["get", "list", "patch", "update"] + +--- +# Bind the workflow-executor role to the default service account in argo namespace +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: workflow-executor-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: workflow-executor +subjects: + - kind: ServiceAccount + name: default + namespace: argo + - kind: ServiceAccount + name: argo + namespace: argo + - kind: ServiceAccount + name: argo-server + namespace: argo diff --git a/environment-provisioner/workflow-template.yaml b/environment-provisioner/workflow-template.yaml new file mode 100644 index 0000000..37c678a --- /dev/null +++ b/environment-provisioner/workflow-template.yaml @@ -0,0 +1,770 @@ +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + name: environment-provisioner + namespace: argo +spec: + serviceAccountName: argo + 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: helmParameterOverrides + value: '{}' + - name: helmExternalParams + value: '{}' + entrypoint: provision-environment + templates: + - inputs: + parameters: + - name: namespace + - name: services + - name: ttl + - name: owner + - name: componentMetadata + - name: dependencyPreferences + - name: valuesFileOverrides + - name: manualDependencyUrls + - name: helmParameterOverrides + - name: helmExternalParams + 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: helmParameterOverrides + value: '{{inputs.parameters.helmParameterOverrides}}' + - name: helmExternalParams + value: '{{inputs.parameters.helmExternalParams}}' + 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: helmParameterOverrides + - name: helmExternalParams + 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='{{inputs.parameters.componentMetadata}}' + DEPENDENCY_PREFS='{{inputs.parameters.dependencyPreferences}}' + VALUES_FILE_OVERRIDES='{{inputs.parameters.valuesFileOverrides}}' + MANUAL_DEPENDENCY_URLS='{{inputs.parameters.manualDependencyUrls}}' + HELM_PARAMETER_OVERRIDES='{{inputs.parameters.helmParameterOverrides}}' + HELM_EXTERNAL_PARAMS='{{inputs.parameters.helmExternalParams}}' + + # 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/giteaAdmin/$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 < "manifests/$APP_NAME-application.yaml" < "manifests/$SANITIZED_SERVICE-deployment.yaml" < README.md </dev/null || echo "") + + if [ -z "$MYSQL_POD" ]; then + # Try alternative label + MYSQL_POD=$(kubectl get pods -n "$NAMESPACE" -o name 2>/dev/null | grep -i "mysql" | head -1 | sed 's/pod\///') + fi + + if [ -z "$MYSQL_POD" ]; then + echo "Warning: Could not find Hades MySQL pod" + 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=120 + DBINIT_WAIT_TIME=0 + 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 + 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" + break + fi + fi + + echo " Waiting for dbinit job... (${DBINIT_WAIT_TIME}s)" + sleep 5 + DBINIT_WAIT_TIME=$((DBINIT_WAIT_TIME + 5)) + done + + if [ $DBINIT_WAIT_TIME -ge $DBINIT_MAX_WAIT ]; then + echo "Warning: Hades dbinit job not completed after ${DBINIT_MAX_WAIT}s" + echo "Tables may not exist - attempting registration anyway" + fi + + # Helper function to run MySQL commands via kubectl exec + run_mysql() { + kubectl exec -n "$NAMESPACE" "$MYSQL_POD" -- mysql -u "$MYSQL_USER" -p"$MYSQL_PWD" "$MYSQL_DB" -sN -e "$1" 2>/dev/null || true + } + + # 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 + + # Get or create app_id + APP_ID=$(run_mysql "SELECT id FROM apps WHERE name='$app_name'" || echo "") + + if [ -z "$APP_ID" ]; then + echo " Creating app entry for: $app_name" + run_mysql "INSERT INTO apps (name, token, creation_date, last_update, is_deleted) VALUES ('$app_name', UUID(), NOW(), NOW(), 0)" || true + APP_ID=$(run_mysql "SELECT id FROM apps WHERE name='$app_name'" || echo "") + fi + + if [ -n "$APP_ID" ]; then + # Check if IP already exists + EXISTING=$(run_mysql "SELECT id FROM app_ips_accesses WHERE app_id=$APP_ID AND ipaddress='$ip'" || echo "") + + if [ -z "$EXISTING" ]; then + echo " Registering IP $ip for app $app_name (app_id: $APP_ID)" + run_mysql "INSERT INTO app_ips_accesses (app_id, ipaddress, creation_date, last_update, is_deleted) VALUES ($APP_ID, '$ip', NOW(), NOW(), 0)" || true + else + echo " IP $ip already registered for $app_name" + fi + fi + return 0 + } + + # 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 gateway + POD_CIDR=$(kubectl get nodes -o jsonpath='{.items[0].spec.podCIDR}' 2>/dev/null || echo "") + if [ -n "$POD_CIDR" ]; then + GATEWAY_IP=$(echo "$POD_CIDR" | sed 's/\.[0-9]*\/.*/.1/') + COMMON_BRIDGE_IPS="$COMMON_BRIDGE_IPS $GATEWAY_IP" + echo "Pod CIDR gateway: $GATEWAY_IP" + fi + + # 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 + POD_IPS=$(kubectl get pods -n "$NAMESPACE" \ + -l "app.kubernetes.io/instance=${service}" \ + -o jsonpath='{.items[*].status.podIP}' 2>/dev/null) || POD_IPS="" + + # 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" + + echo "" + echo "Hades IP registration completed"