From c6e42134165c9f6d69498bf6eab50784aa3e2beb Mon Sep 17 00:00:00 2001 From: RandithaK Date: Wed, 5 Nov 2025 21:43:18 +0530 Subject: [PATCH 1/4] chore: Add bin/ to .gitignore to exclude build artifacts from version control --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index bb2abca..de38456 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,7 @@ Thumbs.db # JetBrains workspace.xml +# Build Artefacts +bin/ + # Keep go.mod and source files tracked (do not ignore go.mod/go.sum) From 342a8d9540ece61b05aaaa9dfe941cfe42765468 Mon Sep 17 00:00:00 2001 From: RandithaK Date: Wed, 5 Nov 2025 21:43:22 +0530 Subject: [PATCH 2/4] feat: Add env_var support in ServiceConfig for custom environment variable names --- cmd/gateway/main.go | 43 ++++++++++++++++++++++--------------------- config.yaml | 18 ++++++++++++++++++ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/cmd/gateway/main.go b/cmd/gateway/main.go index 00b9c0e..704bcc1 100644 --- a/cmd/gateway/main.go +++ b/cmd/gateway/main.go @@ -37,6 +37,7 @@ type ServiceConfig struct { TargetURL string `yaml:"target_url"` StripPrefix string `yaml:"strip_prefix"` AuthRequired bool `yaml:"auth_required"` + EnvVar string `yaml:"env_var"` // Optional: custom environment variable name } // --- Global Logger --- @@ -59,28 +60,28 @@ func loadConfig(path string) (*Config, error) { config.JWTSecret = secret } - // It maps the 'name' from config.yaml to the environment variable name - // from docker-compose.yml. - serviceURLEnvMap := map[string]string{ - "auth": "AUTH_SERVICE_URL", - "users": "AUTH_SERVICE_URL", // "users" also points to the auth service - "vehicles": "VEHICLE_SERVICE_URL", - "appointments": "APPOINTMENTS_SERVICE_URL", - "services": "PROJECT_SERVICE_URL", // As defined in docker-compose, this is the project service - "projects": "PROJECT_SERVICE_URL", // "projects" also points to this service - "time-logs": "TIME_LOGGING_SERVICE_URL", - "payments": "PAYMENT_SERVICE_URL", - "invoices": "PAYMENT_SERVICE_URL", // "invoices" also points to this service - "admin": "ADMIN_SERVICE_URL", - "websocket": "WS_SERVICE_URL", - "ai": "AI_SERVICE_URL", - } - + // Override service URLs from environment variables + // If env_var is specified in config, use it; otherwise generate from service name for i := range config.Services { - if envVar, ok := serviceURLEnvMap[config.Services[i].Name]; ok { - if url := os.Getenv(envVar); url != "" { - config.Services[i].TargetURL = url - } + var envVarName string + + // Use custom env_var if specified, otherwise auto-generate + if config.Services[i].EnvVar != "" { + envVarName = config.Services[i].EnvVar + } else { + // Auto-generate: convert "service-name" to "SERVICE_NAME_SERVICE_URL" + // e.g., "time-logs" -> "TIME_LOGS_SERVICE_URL" + normalizedName := strings.ToUpper(strings.ReplaceAll(config.Services[i].Name, "-", "_")) + envVarName = normalizedName + "_SERVICE_URL" + } + + // Override target URL if environment variable is set + if envURL := os.Getenv(envVarName); envURL != "" { + config.Services[i].TargetURL = envURL + logger.Info("Service URL overridden from environment", + "service", config.Services[i].Name, + "env_var", envVarName, + "url", envURL) } } diff --git a/config.yaml b/config.yaml index 6063dd4..c7fa4f1 100644 --- a/config.yaml +++ b/config.yaml @@ -12,12 +12,14 @@ services: target_url: "http://localhost:8081" strip_prefix: "/api/v1/auth" auth_required: false # Login/Register must be public + env_var: "AUTH_SERVICE_URL" - name: "users" path_prefix: "/api/v1/users/" target_url: "http://localhost:8081" # Also points to the auth service strip_prefix: "/api/v1/users" auth_required: true + env_var: "AUTH_SERVICE_URL" # Shares the same backend as auth # --- Vehicle Service (Port 8082) --- - name: "vehicles" @@ -25,6 +27,7 @@ services: target_url: "http://localhost:8082" strip_prefix: "/api/v1/vehicles" auth_required: true + # env_var not specified - will auto-generate: VEHICLES_SERVICE_URL # --- Appointment & Scheduling Service (Port 8083) --- - name: "appointments" @@ -32,6 +35,7 @@ services: target_url: "http://localhost:8083" strip_prefix: "/api/v1/appointments" auth_required: true + # env_var not specified - will auto-generate: APPOINTMENTS_SERVICE_URL # --- Service & Project Management Service (Port 8084) --- - name: "services" @@ -39,12 +43,14 @@ services: target_url: "http://localhost:8084" strip_prefix: "/api/v1/services" auth_required: true + env_var: "PROJECT_SERVICE_URL" # Points to project service - name: "projects" path_prefix: "/api/v1/projects/" target_url: "http://localhost:8084" # Also points to the project service strip_prefix: "/api/v1/projects" auth_required: true + env_var: "PROJECT_SERVICE_URL" # Shares the same backend # --- Time Logging Service (Port 8085) --- - name: "time-logs" @@ -52,6 +58,7 @@ services: target_url: "http://localhost:8085" strip_prefix: "/api/v1/time-logs" auth_required: true + # env_var not specified - will auto-generate: TIME_LOGS_SERVICE_URL # --- Payment & Billing Service (Port 8086) --- - name: "payments" @@ -59,12 +66,14 @@ services: target_url: "http://localhost:8086" strip_prefix: "/api/v1/payments" auth_required: true + env_var: "PAYMENT_SERVICE_URL" - name: "invoices" path_prefix: "/api/v1/invoices/" target_url: "http://localhost:8086" # Also points to the payment service strip_prefix: "/api/v1/invoices" auth_required: true + env_var: "PAYMENT_SERVICE_URL" # Shares the same backend # --- Admin & Reporting Service (Port 8087) --- - name: "admin" @@ -72,6 +81,15 @@ services: target_url: "http://localhost:8087" strip_prefix: "/api/v1/admin" auth_required: true + # env_var not specified - will auto-generate: ADMIN_SERVICE_URL + + # --- Notification Service (Port 8088) --- + - name: "notifications" + path_prefix: "/api/v1/notifications/" + target_url: "http://localhost:8088" + strip_prefix: "/api/v1/notifications" + auth_required: true + # env_var not specified - will auto-generate: NOTIFICATIONS_SERVICE_URL # --- Bonus Features (For Later Implementation) --- - name: "websocket" From 49dd609b25269363d3ea4f0317849035307115d0 Mon Sep 17 00:00:00 2001 From: RandithaK Date: Thu, 6 Nov 2025 01:30:45 +0530 Subject: [PATCH 3/4] refactor: Update path_prefix and strip_prefix for services in config.yaml for consistency --- config.yaml | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/config.yaml b/config.yaml index c7fa4f1..fe442b5 100644 --- a/config.yaml +++ b/config.yaml @@ -8,86 +8,94 @@ jwt_secret: "YourSuperSecretKeyForJWTGoesHereAndItMustBeVeryLongForSecurityPurpo services: # --- Authentication Service (Port 8081) --- - name: "auth" - path_prefix: "/api/v1/auth/" + path_prefix: "/api/v1/auth" target_url: "http://localhost:8081" strip_prefix: "/api/v1/auth" auth_required: false # Login/Register must be public env_var: "AUTH_SERVICE_URL" - name: "users" - path_prefix: "/api/v1/users/" + path_prefix: "/api/v1/users" target_url: "http://localhost:8081" # Also points to the auth service - strip_prefix: "/api/v1/users" + strip_prefix: "/api/v1" auth_required: true env_var: "AUTH_SERVICE_URL" # Shares the same backend as auth # --- Vehicle Service (Port 8082) --- - name: "vehicles" - path_prefix: "/api/v1/vehicles/" + path_prefix: "/api/v1/vehicles" target_url: "http://localhost:8082" - strip_prefix: "/api/v1/vehicles" + strip_prefix: "/api/v1" auth_required: true # env_var not specified - will auto-generate: VEHICLES_SERVICE_URL # --- Appointment & Scheduling Service (Port 8083) --- + # Public availability check (no authentication required) + - name: "appointments-availability" + path_prefix: "/api/v1/appointments/availability" + target_url: "http://localhost:8083" + strip_prefix: "/api/v1" + auth_required: false # Public endpoint + + # Protected appointment endpoints - name: "appointments" - path_prefix: "/api/v1/appointments/" + path_prefix: "/api/v1/appointments" target_url: "http://localhost:8083" - strip_prefix: "/api/v1/appointments" + strip_prefix: "/api/v1" auth_required: true # env_var not specified - will auto-generate: APPOINTMENTS_SERVICE_URL # --- Service & Project Management Service (Port 8084) --- - name: "services" - path_prefix: "/api/v1/services/" + path_prefix: "/api/v1/services" target_url: "http://localhost:8084" - strip_prefix: "/api/v1/services" + strip_prefix: "/api/v1" auth_required: true env_var: "PROJECT_SERVICE_URL" # Points to project service - name: "projects" - path_prefix: "/api/v1/projects/" + path_prefix: "/api/v1/projects" target_url: "http://localhost:8084" # Also points to the project service - strip_prefix: "/api/v1/projects" + strip_prefix: "/api/v1" auth_required: true env_var: "PROJECT_SERVICE_URL" # Shares the same backend # --- Time Logging Service (Port 8085) --- - name: "time-logs" - path_prefix: "/api/v1/time-logs/" + path_prefix: "/api/v1/time-logs" target_url: "http://localhost:8085" - strip_prefix: "/api/v1/time-logs" + strip_prefix: "/api/v1" auth_required: true # env_var not specified - will auto-generate: TIME_LOGS_SERVICE_URL # --- Payment & Billing Service (Port 8086) --- - name: "payments" - path_prefix: "/api/v1/payments/" + path_prefix: "/api/v1/payments" target_url: "http://localhost:8086" - strip_prefix: "/api/v1/payments" + strip_prefix: "/api/v1" auth_required: true env_var: "PAYMENT_SERVICE_URL" - name: "invoices" - path_prefix: "/api/v1/invoices/" + path_prefix: "/api/v1/invoices" target_url: "http://localhost:8086" # Also points to the payment service - strip_prefix: "/api/v1/invoices" + strip_prefix: "/api/v1" auth_required: true env_var: "PAYMENT_SERVICE_URL" # Shares the same backend # --- Admin & Reporting Service (Port 8087) --- - name: "admin" - path_prefix: "/api/v1/admin/" + path_prefix: "/api/v1/admin" target_url: "http://localhost:8087" - strip_prefix: "/api/v1/admin" + strip_prefix: "/api/v1" auth_required: true # env_var not specified - will auto-generate: ADMIN_SERVICE_URL # --- Notification Service (Port 8088) --- - name: "notifications" - path_prefix: "/api/v1/notifications/" + path_prefix: "/api/v1/notifications" target_url: "http://localhost:8088" - strip_prefix: "/api/v1/notifications" + strip_prefix: "/api/v1" auth_required: true # env_var not specified - will auto-generate: NOTIFICATIONS_SERVICE_URL From 172c805013fe49db3eacc48b17eaaac7c9580b78 Mon Sep 17 00:00:00 2001 From: RandithaK Date: Sat, 8 Nov 2025 00:56:37 +0530 Subject: [PATCH 4/4] feat: Implement CI/CD workflows for building, testing, and deploying the API Gateway to Kubernetes --- .github/workflows/build.yaml | 108 +++++++++++++++++++++++++++++++ .github/workflows/buildtest.yaml | 60 ----------------- .github/workflows/deploy.yaml | 63 ++++++++++++++++++ 3 files changed, 171 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/buildtest.yaml create mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..834691a --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,108 @@ +# API_Gateway/.github/workflows/build.yml +# This workflow builds, tests, AND packages the Go API Gateway + +name: Build, Test, and Package Gateway + +on: + push: + branches: + - 'main' + - 'devOps' + - 'dev' + pull_request: + branches: + - 'main' + - 'devOps' + - 'dev' + +permissions: + contents: read + packages: write + +jobs: + # --- JOB 1: Build and Test (From your file) --- + # This runs on all pushes AND all pull requests to verify the code + build-and-test: + name: Build and Smoke Test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.22' + cache-dependency-path: go.mod + + - name: Download dependencies + run: go mod download + + - name: Build Go Application + run: go build -v -o ./app ./cmd/gateway + + - name: Verify config.yaml exists + run: | + if [ ! -f config.yaml ]; then + echo "Error: config.yaml not found!" + exit 1 + fi + echo "✓ config.yaml found" + + - name: Start Gateway (smoke test) + run: | + echo "Starting API Gateway with config.yaml..." + # We need to set a dummy secret for the smoke test + export JWT_SECRET="dummy-secret-for-test" + timeout 5s ./app || code=$? + if [ ${code:-0} -eq 124 ]; then + echo "✓ Gateway started successfully (terminated after 5s)" + exit 0 + elif [ ${code:-0} -eq 0 ]; then + echo "✓ Gateway started and stopped cleanly" + exit 0 + else + echo "✗ Gateway failed to start (exit code: ${code})" + exit 1 + fi + + # --- JOB 2: Package as Docker Image (From my file) --- + # This runs ONLY after Job 1 succeeds, AND + # This runs ONLY on pushes to your main branches (not PRs) + build-and-push-docker: + name: Build & Push Docker Image + needs: build-and-test # Depends on the first job + + # This logic ensures it only runs on pushes, not PRs + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/devOps' || github.ref == 'refs/heads/dev') + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha,prefix= + type:raw,value=latest,enable={{is_default_branch}} + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/buildtest.yaml b/.github/workflows/buildtest.yaml deleted file mode 100644 index ef2bc01..0000000 --- a/.github/workflows/buildtest.yaml +++ /dev/null @@ -1,60 +0,0 @@ -name: Build and Test API Gateway - -on: - push: - branches: - - '**' - pull_request: - branches: - - '**' - -jobs: - build-test: - name: Build and Start Check - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.22' - cache-dependency-path: go.mod - - - name: Download dependencies - run: go mod download - - - name: Build Go Application - run: go build -v -o ./app ./cmd/gateway - - - name: Verify config.yaml exists - run: | - if [ ! -f config.yaml ]; then - echo "Error: config.yaml not found!" - exit 1 - fi - echo "✓ config.yaml found" - - - name: Start Gateway (smoke test) - run: | - echo "Starting API Gateway with config.yaml..." - timeout 5s ./app || code=$? - if [ ${code:-0} -eq 124 ]; then - echo "✓ Gateway started successfully (terminated after 5s)" - exit 0 - elif [ ${code:-0} -eq 0 ]; then - echo "✓ Gateway started and stopped cleanly" - exit 0 - else - echo "✗ Gateway failed to start (exit code: ${code})" - exit 1 - fi - - - name: Upload Build Artifact - if: success() - uses: actions/upload-artifact@v4 - with: - name: api-gateway-binary - path: app diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..22a1623 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,63 @@ +# API_Gateway/.github/workflows/deploy.yml + +name: Deploy Gateway to Kubernetes + +on: + workflow_run: + # This MUST match the 'name:' of your build.yml file + workflows: ["Build, Test, and Package Gateway"] + types: + - completed # Run only after the build workflow finishes + branches: + - 'main' # Deploy on main + - 'devOps' # Deploy on devOps + # Note: We are not deploying the 'dev' branch, as requested + +jobs: + deploy: + name: Deploy Gateway to Kubernetes + # We only deploy if the build job was successful + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + + steps: + # Checks out the code so we can access the K8s files + - name: Checkout Code + uses: actions/checkout@v4 + + # Finds the 7-character commit SHA that triggered the build + - name: Get Commit SHA + id: get_sha + run: | + echo "sha=$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7)" >> $GITHUB_OUTPUT + + # Installs kubectl + - name: Install kubectl + uses: azure/setup-kubectl@v3 + + # Installs yq, the tool we use to safely edit YAML + - name: Install yq + run: | + sudo wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq + sudo chmod +x /usr/bin/yq + + # Logs into your Azure VM's K3s cluster + - name: Set Kubernetes context + uses: azure/k8s-set-context@v4 + with: + # This uses the Org-level secret you created + kubeconfig: ${{ secrets.KUBE_CONFIG_DATA }} + + # This is the magic step! + # It replaces the image tag in your K8s file with the new one + - name: Update image tag in YAML + run: | + yq -i '.spec.template.spec.containers[0].image = "ghcr.io/techtorque-2025/api_gateway:${{ steps.get_sha.outputs.sha }}"' Kubernetes/services/gateway-deployment.yaml + + # Applies the updated file to your cluster + - name: Deploy to Kubernetes + run: | + kubectl apply -f Kubernetes/services/gateway-deployment.yaml + + # Waits for the new version to be fully rolled out + kubectl rollout status deployment/api-gateway-deployment \ No newline at end of file