From a0c9688bfc8e74b3651efb7a0f2cbfc61483c9e1 Mon Sep 17 00:00:00 2001 From: Deep Kumar Singh Kushwah Date: Sun, 24 May 2026 00:16:51 +0530 Subject: [PATCH 1/3] =?UTF-8?q?feat(infra):=20IaC=20foundation=20=E2=80=94?= =?UTF-8?q?=20Terraform=20modules=20+=20Helm=20chart=20skeleton=20(Step=20?= =?UTF-8?q?0.9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Terraform (Kubernetes-native, helm + kubernetes providers): - modules/postgres — Bitnami postgresql chart, pgvector image override, K8s Secret - modules/redis — Bitnami redis chart, standalone/replication modes - modules/qdrant — Official qdrant chart, configurable replicas + storage - modules/elasticsearch — Bitnami elasticsearch chart, security toggle dev/prod - Root module wires all four backends with per-backend enable flags - environments/dev — local cluster (docker-desktop/kind), minimal resources, local state - environments/prod — production sizing (3× Qdrant replicas, 100–200 Gi), S3 backend stub Helm chart (infra/helm/rag-platform/): - Deployment with ConfigMap-mounted rag.yaml, Secret env var injection, read-only rootfs - Service, HPA (CPU+memory), PodDisruptionBudget, Ingress, ServiceAccount - values.dev.yaml override for single-replica, minimal-resource local deploys - NOTES.txt with post-install port-forward instructions CI gate (ci.yml): - New infra path filter (infra/terraform/**, infra/helm/**) - New iac job: terraform init -backend=false + validate for dev + prod; helm lint + template dry-run - iac job added to ci-pass required gate Task targets: task infra:init / infra:validate / infra:plan / helm:lint / helm:template Docs: docs/architecture/iac.md, docs/adr/ADR-0003-iac-kubernetes-native.md Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 45 +++++- Makefile | 21 ++- TRACKER.md | 10 +- Taskfile.yml | 31 ++++ docs/README.md | 2 + docs/adr/ADR-0003-iac-kubernetes-native.md | 48 ++++++ docs/architecture/iac.md | 133 ++++++++++++++++ infra/helm/rag-platform/Chart.yaml | 17 +++ infra/helm/rag-platform/templates/NOTES.txt | 22 +++ .../helm/rag-platform/templates/_helpers.tpl | 69 +++++++++ .../rag-platform/templates/configmap.yaml | 14 ++ .../rag-platform/templates/deployment.yaml | 143 ++++++++++++++++++ infra/helm/rag-platform/templates/hpa.yaml | 29 ++++ .../helm/rag-platform/templates/ingress.yaml | 36 +++++ infra/helm/rag-platform/templates/pdb.yaml | 14 ++ .../helm/rag-platform/templates/service.yaml | 22 +++ .../templates/serviceaccount.yaml | 13 ++ infra/helm/rag-platform/values.dev.yaml | 41 +++++ infra/helm/rag-platform/values.yaml | 130 ++++++++++++++++ infra/terraform/.gitignore | 7 + infra/terraform/environments/dev/main.tf | 68 +++++++++ infra/terraform/environments/dev/outputs.tf | 12 ++ .../environments/dev/terraform.tfvars.example | 6 + infra/terraform/environments/dev/variables.tf | 17 +++ infra/terraform/environments/prod/main.tf | 74 +++++++++ infra/terraform/environments/prod/outputs.tf | 18 +++ .../prod/terraform.tfvars.example | 18 +++ .../terraform/environments/prod/variables.tf | 52 +++++++ infra/terraform/main.tf | 62 ++++++++ infra/terraform/modules/elasticsearch/main.tf | 66 ++++++++ .../modules/elasticsearch/outputs.tf | 9 ++ .../modules/elasticsearch/variables.tf | 61 ++++++++ infra/terraform/modules/postgres/main.tf | 110 ++++++++++++++ infra/terraform/modules/postgres/outputs.tf | 24 +++ infra/terraform/modules/postgres/variables.tf | 62 ++++++++ infra/terraform/modules/qdrant/main.tf | 49 ++++++ infra/terraform/modules/qdrant/outputs.tf | 19 +++ infra/terraform/modules/qdrant/variables.tf | 49 ++++++ infra/terraform/modules/redis/main.tf | 44 ++++++ infra/terraform/modules/redis/outputs.tf | 9 ++ infra/terraform/modules/redis/variables.tf | 53 +++++++ infra/terraform/outputs.tf | 49 ++++++ infra/terraform/variables.tf | 104 +++++++++++++ infra/terraform/versions.tf | 14 ++ 44 files changed, 1889 insertions(+), 7 deletions(-) create mode 100644 docs/adr/ADR-0003-iac-kubernetes-native.md create mode 100644 docs/architecture/iac.md create mode 100644 infra/helm/rag-platform/Chart.yaml create mode 100644 infra/helm/rag-platform/templates/NOTES.txt create mode 100644 infra/helm/rag-platform/templates/_helpers.tpl create mode 100644 infra/helm/rag-platform/templates/configmap.yaml create mode 100644 infra/helm/rag-platform/templates/deployment.yaml create mode 100644 infra/helm/rag-platform/templates/hpa.yaml create mode 100644 infra/helm/rag-platform/templates/ingress.yaml create mode 100644 infra/helm/rag-platform/templates/pdb.yaml create mode 100644 infra/helm/rag-platform/templates/service.yaml create mode 100644 infra/helm/rag-platform/templates/serviceaccount.yaml create mode 100644 infra/helm/rag-platform/values.dev.yaml create mode 100644 infra/helm/rag-platform/values.yaml create mode 100644 infra/terraform/.gitignore create mode 100644 infra/terraform/environments/dev/main.tf create mode 100644 infra/terraform/environments/dev/outputs.tf create mode 100644 infra/terraform/environments/dev/terraform.tfvars.example create mode 100644 infra/terraform/environments/dev/variables.tf create mode 100644 infra/terraform/environments/prod/main.tf create mode 100644 infra/terraform/environments/prod/outputs.tf create mode 100644 infra/terraform/environments/prod/terraform.tfvars.example create mode 100644 infra/terraform/environments/prod/variables.tf create mode 100644 infra/terraform/main.tf create mode 100644 infra/terraform/modules/elasticsearch/main.tf create mode 100644 infra/terraform/modules/elasticsearch/outputs.tf create mode 100644 infra/terraform/modules/elasticsearch/variables.tf create mode 100644 infra/terraform/modules/postgres/main.tf create mode 100644 infra/terraform/modules/postgres/outputs.tf create mode 100644 infra/terraform/modules/postgres/variables.tf create mode 100644 infra/terraform/modules/qdrant/main.tf create mode 100644 infra/terraform/modules/qdrant/outputs.tf create mode 100644 infra/terraform/modules/qdrant/variables.tf create mode 100644 infra/terraform/modules/redis/main.tf create mode 100644 infra/terraform/modules/redis/outputs.tf create mode 100644 infra/terraform/modules/redis/variables.tf create mode 100644 infra/terraform/outputs.tf create mode 100644 infra/terraform/variables.tf create mode 100644 infra/terraform/versions.tf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d0c526..333bc37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: pull-requests: read outputs: python: ${{ steps.filter.outputs.python }} + infra: ${{ steps.filter.outputs.infra }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v4 @@ -34,6 +35,9 @@ jobs: - 'tests/**' - 'pyproject.toml' - 'uv.lock' + infra: + - 'infra/terraform/**' + - 'infra/helm/**' # --------------------------------------------------------------------------- # Lint + type-check + unit tests (Ubuntu + macOS + Windows) @@ -116,6 +120,45 @@ jobs: - run: uv sync --all-packages - run: uv run pytest tests/logs/ -v + # --------------------------------------------------------------------------- + # IaC validation — terraform validate + helm lint (no live cluster needed) + # --------------------------------------------------------------------------- + iac: + name: IaC validate (terraform + helm) + needs: changes + if: needs.changes.outputs.infra == 'true' + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "~1.9" + + - name: Setup Helm + uses: azure/setup-helm@v4 + with: + version: "3.15.x" + + - name: Terraform validate — dev + working-directory: infra/terraform/environments/dev + run: | + terraform init -backend=false + terraform validate + + - name: Terraform validate — prod + working-directory: infra/terraform/environments/prod + run: | + terraform init -backend=false + terraform validate + + - name: Helm lint — rag-platform + run: helm lint infra/helm/rag-platform/ + + - name: Helm template — dry run + run: helm template rag-platform infra/helm/rag-platform/ > /dev/null + # --------------------------------------------------------------------------- # Dependency CVE audit # --------------------------------------------------------------------------- @@ -140,7 +183,7 @@ jobs: # --------------------------------------------------------------------------- ci-pass: name: CI passed - needs: [lint-test, secrets-scan, log-gates, audit] + needs: [lint-test, secrets-scan, log-gates, audit, iac] if: always() runs-on: ubuntu-22.04 steps: diff --git a/Makefile b/Makefile index 6cd8fb5..1f058fe 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ PYTEST := uv run pytest RUFF := uv run ruff MYPY := uv run mypy -.PHONY: help bootstrap lint fmt test test-contract test-logs schemas config-schema config-validate dev dev-full dev-down dev-wait dev-seed dev-reset docker clean audit +.PHONY: help bootstrap lint fmt test test-contract test-logs schemas config-schema config-validate dev dev-full dev-down dev-wait dev-seed dev-reset docker clean audit infra-init infra-validate infra-plan helm-lint helm-template help: ## Show this help @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ @@ -97,6 +97,25 @@ docker: ## Build all Docker images audit: ## Run pip-audit for dependency CVEs uv run pip-audit +# --------------------------------------------------------------------------- +# IaC — Terraform + Helm +# --------------------------------------------------------------------------- +infra-init: ## terraform init for the dev environment + terraform -chdir=infra/terraform/environments/dev init + +infra-validate: ## terraform validate for dev + prod environments + terraform -chdir=infra/terraform/environments/dev validate + terraform -chdir=infra/terraform/environments/prod validate + +infra-plan: ## terraform plan for the dev environment + terraform -chdir=infra/terraform/environments/dev plan + +helm-lint: ## Lint the rag-platform Helm chart + helm lint infra/helm/rag-platform/ + +helm-template: ## Render Helm templates (dry run) + helm template rag-platform infra/helm/rag-platform/ + # --------------------------------------------------------------------------- # Cleanup # --------------------------------------------------------------------------- diff --git a/TRACKER.md b/TRACKER.md index 717d5ec..b331a7f 100644 --- a/TRACKER.md +++ b/TRACKER.md @@ -8,9 +8,9 @@ > Use `Taskfile.yml` for task targets (cross-platform), `Makefile` for Unix convenience. > CI matrix always includes `ubuntu-22.04`, `macos-14`, and `windows-latest`. -**Last updated:** 2026-05-23 +**Last updated:** 2026-05-24 **Current phase:** Phase 0 — Foundation -**Next action:** Phase 0 Step 0.9 — IaC foundation +**Next action:** Phase 0 Step 0.10 — ragctl CLI scaffold --- @@ -29,7 +29,7 @@ | Phase | Title | Steps | ✅ Done | Remaining | |-------|-------|------:|-------:|----------:| -| 0 | Foundation | 13 | **11** | 2 | +| 0 | Foundation | 13 | **12** | 1 | | 1 | Ingestion + Knowledge Store | 10 | 0 | 10 | | 2 | Retrieval Engine | 10 | 0 | 10 | | 3 | Gateway & Agent Runtime | 11 | 0 | 11 | @@ -37,7 +37,7 @@ | 5 | Eval & Observability | 7 | 0 | 7 | | 6 | Governance & Tenancy | 10 | 0 | 10 | | 7 | Pilot, Harden, GA | 10 | 0 | 10 | -| **Total** | | **77** | **11** | **66** | +| **Total** | | **77** | **12** | **65** | --- @@ -56,7 +56,7 @@ | 0.7b | Structured logging foundation | ✅ | `build/phase-0/step-0.7b-structured-logging` | — | Shared logger package (`packages/core/src/rag_core/logging.py`), event registry (`events.py`), RAG001 pre-commit hook + ruff T201 rule, schema+PII CI gates in `tests/logs/` (50 tests; log-gates job now blocking). **Follow-up (0.7b-observability-package):** extracted `packages/observability/` (`rag-observability`) with full 7-field JSON schema (ts, level, service, module, msg, env, version), `set_log_context()` contextvar-based context manager, `_context.py`; `rag_core.logging` and `rag_core.events` reduced to backwards-compat shims (391 tests pass). | | 0.7c | Audit log skeleton | ✅ | `build/phase-0/step-0.7c-audit-log-skeleton` | — | `AuditStore` SPI (append/events/verify_chain), `NoopAuditStore` (SHA-256 hash chain), `AuditWriter` facade (store + structured log), 14 conformance tests | | 0.8 | Eval skeleton | ✅ | `build/phase-0/step-0.8-eval-skeleton` | — | `rag_core.eval` domain types (GoldenSample, EvalMetrics, EvalReport), `rag_config.eval` metric functions (recall@k, MRR, citation_precision), RagasAdapter spike, `ragctl eval run/show`, 5-sample golden JSONL fixture, `tests/eval/` harness (39 tests) | -| 0.9 | IaC foundation | ⏳ | — | — | Terraform modules for core backends, Helm chart skeleton, `infra/` layout | +| 0.9 | IaC foundation | ✅ | `build/phase-0/step-0.9-iac-foundation` | — | Terraform modules for Postgres/pgvector, Redis, Qdrant, Elasticsearch (Kubernetes-native, Helm provider); `rag-platform` Helm chart (Deployment, Service, ConfigMap, ServiceAccount, HPA, PDB, Ingress); dev + prod environments; `task infra:*` + `task helm:*` targets; ADR-0003 | | 0.10 | `ragctl` CLI scaffold | ⏳ | — | — | `packages/ragctl/` CLI (Typer), `ragctl ingest/query/eval/logs/traces/config` top-level commands, shell completion | --- diff --git a/Taskfile.yml b/Taskfile.yml index 201fc7c..47fe4a3 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -156,6 +156,37 @@ tasks: cmds: - uv run pip-audit + # --------------------------------------------------------------------------- + # IaC — Terraform + Helm + # --------------------------------------------------------------------------- + infra:init: + desc: terraform init for the dev environment + dir: infra/terraform/environments/dev + cmds: + - terraform init + + infra:validate: + desc: terraform validate for dev + prod environments + cmds: + - terraform -chdir=infra/terraform/environments/dev validate + - terraform -chdir=infra/terraform/environments/prod validate + + infra:plan: + desc: terraform plan for the dev environment (requires live cluster) + dir: infra/terraform/environments/dev + cmds: + - terraform plan + + helm:lint: + desc: Lint the rag-platform Helm chart + cmds: + - helm lint infra/helm/rag-platform/ + + helm:template: + desc: Render Helm templates to stdout (dry run) + cmds: + - helm template rag-platform infra/helm/rag-platform/ + # --------------------------------------------------------------------------- # Cleanup — platform-specific commands via platforms: key # --------------------------------------------------------------------------- diff --git a/docs/README.md b/docs/README.md index c16f9ea..9c89ed8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,7 @@ | [RAG-Platform-HLD.md](architecture/RAG-Platform-HLD.md) | High-Level Design: problem statement, layered architecture, pluggable backends, `rag.yaml` contract, deployment topologies, KPIs, risks, glossary | | [high-level-architecture.svg](architecture/high-level-architecture.svg) | Layered architecture diagram (SVG) | | [eval-skeleton.md](architecture/eval-skeleton.md) | Eval framework architecture: golden-set schema, metric functions, RAGAS spike, `ragctl eval` CLI, extension points | +| [iac.md](architecture/iac.md) | IaC overview: Terraform module design, Helm chart structure, dev/prod environments, extension points | ## adr/ @@ -14,6 +15,7 @@ |------|-------------| | [ADR-0001-monorepo-and-tech-stack.md](adr/ADR-0001-monorepo-and-tech-stack.md) | Decision: uv workspaces, Pydantic v2, Taskfile, Python 3.12+ | | [ADR-0002-eval-framework.md](adr/ADR-0002-eval-framework.md) | Decision: pure-Python Tier 1 metrics always-on; RAGAS as optional Tier 2 | +| [ADR-0003-iac-kubernetes-native.md](adr/ADR-0003-iac-kubernetes-native.md) | Decision: Kubernetes-native Terraform modules over cloud-provider-specific RDS/ElastiCache | ## research/ diff --git a/docs/adr/ADR-0003-iac-kubernetes-native.md b/docs/adr/ADR-0003-iac-kubernetes-native.md new file mode 100644 index 0000000..cb8a0fd --- /dev/null +++ b/docs/adr/ADR-0003-iac-kubernetes-native.md @@ -0,0 +1,48 @@ +# ADR-0003 — Kubernetes-native IaC (Terraform + Helm providers) + +**Status:** Accepted +**Date:** 2026-05-24 +**Deciders:** Core team + +--- + +## Context + +Phase 0 requires an IaC foundation that provisions the four core backends (Postgres/pgvector, Redis, Qdrant, Elasticsearch) and a gateway deployment template. + +Two approaches were considered: + +1. **Cloud-provider-specific modules** — AWS Terraform modules (RDS, ElastiCache, EC2/ECS for Qdrant). Concrete, well-tested, but locks the platform to AWS for Phase 0. +2. **Kubernetes-native modules** — Terraform `helm` + `kubernetes` providers wrapping official Helm charts. Works on any conformant K8s cluster (local kind, EKS, GKE, AKS, bare-metal). + +--- + +## Decision + +Use **Kubernetes-native Terraform modules** (option 2) for Phase 0. + +Each backend module wraps a mature, actively-maintained Helm chart: +- `bitnami/postgresql` (pgvector image override) +- `bitnami/redis` +- `qdrant/qdrant` +- `bitnami/elasticsearch` + +The gateway is deployed via a first-party `rag-platform` Helm chart. + +--- + +## Rationale + +- **No cloud lock-in at Phase 0.** Design partners may run on GKE or AKS; committing to AWS-specific resources before Phase 1 would create unnecessary switching cost. +- **Works locally.** Developers can run `terraform apply` against `docker-desktop` or `kind` — no cloud account required. This is consistent with the project's Docker Compose-first local dev story. +- **Gradual escape hatch.** When a design partner needs managed RDS instead of in-cluster Postgres, they set `enable_postgres = false` and point the gateway at an external DSN. No Terraform module rewrite needed. +- **Helm chart reuse.** The same Bitnami charts are the de facto standard for K8s backend deployments. Wrapping them in Terraform gives declarative lifecycle management without reimplementing health checks, PVCs, or RBAC. + +--- + +## Consequences + +- **Requires a running K8s cluster** for `terraform plan/apply`. `terraform validate` is cluster-free and runs in CI. +- **In-cluster Postgres is not HA at Phase 0.** The Bitnami chart supports replication; the prod environment variables expose `master_replicas` and `data_replicas` for Elasticsearch, and `replicas` for Qdrant. Postgres primary-standby replication will be configured in Phase 4 (Reliability). +- **State management** — dev uses local state; prod requires an S3 backend to be configured before first apply. The `backend "s3"` block in `environments/prod/main.tf` has commented-out placeholders. +- **Cloud-specific modules deferred** — AWS RDS, ElastiCache, and managed Qdrant Cloud integrations are Phase 1+ concerns and will live under `infra/terraform/modules/aws/` when introduced. diff --git a/docs/architecture/iac.md b/docs/architecture/iac.md new file mode 100644 index 0000000..25b56ff --- /dev/null +++ b/docs/architecture/iac.md @@ -0,0 +1,133 @@ +# Infrastructure as Code (IaC) + +## Overview + +AgentContextOS uses **Terraform** to provision all platform backends and a **Helm chart** to deploy the gateway service on Kubernetes. +Both tools target Kubernetes as the common runtime so the same configuration works on local clusters (kind, Docker Desktop), EKS, GKE, and AKS without modification. + +See [ADR-0003](../adr/ADR-0003-iac-kubernetes-native.md) for the decision to use Kubernetes-native IaC rather than cloud-provider-specific modules. + +--- + +## Directory layout + +``` +infra/ +├── terraform/ +│ ├── versions.tf # provider version constraints +│ ├── variables.tf # root module inputs +│ ├── outputs.tf # root module outputs (hostnames, secrets) +│ ├── main.tf # wires backend modules + namespace +│ ├── modules/ +│ │ ├── postgres/ # pgvector via Bitnami chart +│ │ ├── redis/ # Redis via Bitnami chart +│ │ ├── qdrant/ # Qdrant via official chart +│ │ └── elasticsearch/ # Elasticsearch via Bitnami chart +│ ├── environments/ +│ │ ├── dev/ # local cluster, minimal resources +│ │ └── prod/ # production cluster, HA sizing +│ └── .gitignore # excludes state, .terraform/, tfvars +└── helm/ + └── rag-platform/ + ├── Chart.yaml + ├── values.yaml # default values + ├── values.dev.yaml # dev overrides (single replica, small resources) + └── templates/ + ├── deployment.yaml # rag-gateway Deployment + ├── service.yaml + ├── configmap.yaml # rag.yaml config + ├── serviceaccount.yaml + ├── hpa.yaml # HorizontalPodAutoscaler + ├── pdb.yaml # PodDisruptionBudget + ├── ingress.yaml + └── NOTES.txt +``` + +--- + +## Terraform modules + +Each module wraps a Helm chart deployment so backends are managed declaratively alongside application code. +All four modules share the same contract: `namespace`, `environment`, `storage_size`, and `resources` inputs; `host`, `port`, and (where applicable) `secret_name` outputs. + +| Module | Helm chart | Purpose | +|--------|-----------|---------| +| `postgres` | `bitnami/postgresql` | pgvector store for documents, chunks, embeddings, audit events | +| `redis` | `bitnami/redis` | Semantic cache L1, rate-limit counters, session state | +| `qdrant` | `qdrant/qdrant` | Primary vector store for Phase 1 retrieval | +| `elasticsearch` | `bitnami/elasticsearch` | BM25 keyword retrieval for Phase 2 hybrid search | + +### Switching to managed cloud services (Phase 1+) + +In production it is common to replace in-cluster stateful backends with managed equivalents (RDS, ElastiCache, Qdrant Cloud, OpenSearch). +To do this: set `enable_ = false` in the environment `main.tf` and provide the external connection string via environment variables or a Kubernetes Secret. +The Helm chart reads backends via `RAG_POSTGRES_HOST`, `RAG_REDIS_HOST`, etc., so no application code changes are needed. + +--- + +## Helm chart + +The `rag-platform` chart deploys the `rag-gateway` FastAPI service. + +Key design points: + +- **ConfigMap-mounted config** — `rag.yaml` is rendered from Helm values and mounted at `/app/config/rag.yaml`. The Deployment `checksum/config` annotation triggers a rolling restart on config changes. +- **Secret references** — database credentials are read from a Kubernetes Secret (provisioned by the Terraform postgres module). The chart never hardcodes credentials. +- **HPA + PDB** — enabled by default in prod values; disabled in `values.dev.yaml`. +- **Pod anti-affinity** — prefers scheduling replicas on separate nodes to survive single-node failures. +- **Read-only root filesystem** — only `/tmp` is writable (emptyDir volume). + +### Usage + +```bash +# Dev (single replica, local backends) +helm upgrade --install rag-platform infra/helm/rag-platform \ + -f infra/helm/rag-platform/values.yaml \ + -f infra/helm/rag-platform/values.dev.yaml \ + --namespace rag-dev --create-namespace + +# Lint +task helm:lint + +# Dry run (render templates) +task helm:template +``` + +--- + +## Environments + +### dev + +- State stored locally (`terraform.tfstate`) — not shared. +- Minimal resource requests (CPU 100m, memory 128 Mi for most backends). +- Single Qdrant replica, 5 Gi storage per backend. +- Targets `docker-desktop` Kubernetes context by default; override via `kube_context` variable. + +### prod + +- Remote state backend (`s3` block) — fill in bucket/key before first `terraform init`. +- Production sizing: 3 Qdrant replicas, 100–200 Gi storage. +- Elasticsearch security enabled, Redis auth can be toggled on. +- Cluster credentials passed as variables (never hardcoded). + +--- + +## Task targets + +| Target | Description | +|--------|-------------| +| `task infra:init` | `terraform init` for the dev environment | +| `task infra:validate` | `terraform validate` for dev + prod | +| `task infra:plan` | `terraform plan` for dev (requires a running cluster) | +| `task helm:lint` | `helm lint` the rag-platform chart | +| `task helm:template` | Render templates to stdout (dry run) | + +--- + +## Extension points + +- **New backend module** — add a directory under `infra/terraform/modules/`, follow the `variables.tf` / `main.tf` / `outputs.tf` convention, and wire it in `main.tf` with a `count = var.enable_ ? 1 : 0` guard. +- **New environment** — copy `environments/dev/` to `environments/staging/`, update the backend block and sizing variables. +- **Additional Helm templates** — add files under `templates/`. Use `_helpers.tpl` macros for consistent label generation. +- **ServiceMonitor** — once Prometheus Operator is deployed, enable `metrics.serviceMonitor.enabled` in the Qdrant module values to wire up scraping automatically. diff --git a/infra/helm/rag-platform/Chart.yaml b/infra/helm/rag-platform/Chart.yaml new file mode 100644 index 0000000..709709a --- /dev/null +++ b/infra/helm/rag-platform/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: rag-platform +description: AgentContextOS — production-grade multi-tenant RAG platform +type: application +version: 0.1.0 +appVersion: "0.1.0" +keywords: + - rag + - llm + - retrieval + - embeddings + - agents +home: https://github.com/officialCodeWork/AgentContextOS +sources: + - https://github.com/officialCodeWork/AgentContextOS +maintainers: + - name: AgentContextOS diff --git a/infra/helm/rag-platform/templates/NOTES.txt b/infra/helm/rag-platform/templates/NOTES.txt new file mode 100644 index 0000000..f7d1415 --- /dev/null +++ b/infra/helm/rag-platform/templates/NOTES.txt @@ -0,0 +1,22 @@ +AgentContextOS RAG Platform — {{ .Chart.Version }} + +Gateway deployed to namespace: {{ .Release.Namespace }} + +Get the service URL: +{{- if .Values.ingress.enabled }} + {{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }} + {{- end }} +{{- else if eq .Values.service.type "LoadBalancer" }} + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "rag-platform.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo "http://$SERVICE_IP:{{ .Values.service.port }}" +{{- else }} + kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ include "rag-platform.fullname" . }} 8000:{{ .Values.service.port }} + echo "Gateway: http://127.0.0.1:8000" +{{- end }} + +Health endpoints: + http:///healthz — liveness + http:///readyz — readiness + +Docs: https://github.com/officialCodeWork/AgentContextOS/tree/main/docs diff --git a/infra/helm/rag-platform/templates/_helpers.tpl b/infra/helm/rag-platform/templates/_helpers.tpl new file mode 100644 index 0000000..66e8798 --- /dev/null +++ b/infra/helm/rag-platform/templates/_helpers.tpl @@ -0,0 +1,69 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "rag-platform.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "rag-platform.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart label. +*/}} +{{- define "rag-platform.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "rag-platform.labels" -}} +helm.sh/chart: {{ include "rag-platform.chart" . }} +{{ include "rag-platform.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: rag-platform +{{- end }} + +{{/* +Selector labels. +*/}} +{{- define "rag-platform.selectorLabels" -}} +app.kubernetes.io/name: {{ include "rag-platform.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +ServiceAccount name. +*/}} +{{- define "rag-platform.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "rag-platform.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Gateway image reference — falls back to Chart.AppVersion. +*/}} +{{- define "rag-platform.image" -}} +{{- $tag := .Values.image.tag | default .Chart.AppVersion }} +{{- printf "%s:%s" .Values.image.repository $tag }} +{{- end }} diff --git a/infra/helm/rag-platform/templates/configmap.yaml b/infra/helm/rag-platform/templates/configmap.yaml new file mode 100644 index 0000000..f1bd83c --- /dev/null +++ b/infra/helm/rag-platform/templates/configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rag-platform.fullname" . }}-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "rag-platform.labels" . | nindent 4 }} +data: + rag.yaml: | + env: {{ .Values.config.env }} + log_level: {{ .Values.config.log_level }} + service: + name: {{ .Values.config.service.name | default "rag-gateway" }} + version: {{ .Values.config.service.version | default .Chart.AppVersion }} diff --git a/infra/helm/rag-platform/templates/deployment.yaml b/infra/helm/rag-platform/templates/deployment.yaml new file mode 100644 index 0000000..231205e --- /dev/null +++ b/infra/helm/rag-platform/templates/deployment.yaml @@ -0,0 +1,143 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "rag-platform.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "rag-platform.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "rag-platform.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "rag-platform.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "rag-platform.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: gateway + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ include "rag-platform.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- if .Values.observability.metrics.enabled }} + - name: metrics + containerPort: {{ .Values.observability.metrics.port }} + protocol: TCP + {{- end }} + env: + # Application config + - name: RAG_ENV + value: {{ .Values.config.env | quote }} + - name: RAG_LOG_LEVEL + value: {{ .Values.config.log_level | quote }} + - name: RAG_CONFIG_PATH + value: /app/config/rag.yaml + + # Postgres + - name: RAG_POSTGRES_HOST + value: {{ .Values.backends.postgres.host | quote }} + - name: RAG_POSTGRES_PORT + value: {{ .Values.backends.postgres.port | quote }} + - name: RAG_POSTGRES_DB + value: {{ .Values.backends.postgres.database | quote }} + - name: RAG_POSTGRES_USER + valueFrom: + secretKeyRef: + name: {{ .Values.backends.postgres.secretName }} + key: {{ .Values.backends.postgres.userKey }} + - name: RAG_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.backends.postgres.secretName }} + key: {{ .Values.backends.postgres.passwordKey }} + + # Redis + - name: RAG_REDIS_HOST + value: {{ .Values.backends.redis.host | quote }} + - name: RAG_REDIS_PORT + value: {{ .Values.backends.redis.port | quote }} + + # Qdrant + - name: RAG_QDRANT_HOST + value: {{ .Values.backends.qdrant.host | quote }} + - name: RAG_QDRANT_HTTP_PORT + value: {{ .Values.backends.qdrant.httpPort | quote }} + - name: RAG_QDRANT_GRPC_PORT + value: {{ .Values.backends.qdrant.grpcPort | quote }} + + # Elasticsearch + - name: RAG_ES_HOST + value: {{ .Values.backends.elasticsearch.host | quote }} + - name: RAG_ES_PORT + value: {{ .Values.backends.elasticsearch.port | quote }} + + {{- if .Values.observability.otel.enabled }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: {{ .Values.observability.otel.endpoint | quote }} + - name: OTEL_SERVICE_NAME + value: rag-gateway + {{- end }} + + volumeMounts: + - name: config + mountPath: /app/config + readOnly: true + - name: tmp + mountPath: /tmp + + livenessProbe: + httpGet: + path: /healthz + port: http + initialDelaySeconds: 15 + periodSeconds: 20 + + readinessProbe: + httpGet: + path: /readyz + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + + resources: + {{- toYaml .Values.resources | nindent 12 }} + + volumes: + - name: config + configMap: + name: {{ include "rag-platform.fullname" . }}-config + - name: tmp + emptyDir: {} + + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/infra/helm/rag-platform/templates/hpa.yaml b/infra/helm/rag-platform/templates/hpa.yaml new file mode 100644 index 0000000..e44967b --- /dev/null +++ b/infra/helm/rag-platform/templates/hpa.yaml @@ -0,0 +1,29 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "rag-platform.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "rag-platform.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "rag-platform.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} +{{- end }} diff --git a/infra/helm/rag-platform/templates/ingress.yaml b/infra/helm/rag-platform/templates/ingress.yaml new file mode 100644 index 0000000..b861c65 --- /dev/null +++ b/infra/helm/rag-platform/templates/ingress.yaml @@ -0,0 +1,36 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "rag-platform.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "rag-platform.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "rag-platform.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/infra/helm/rag-platform/templates/pdb.yaml b/infra/helm/rag-platform/templates/pdb.yaml new file mode 100644 index 0000000..184ed89 --- /dev/null +++ b/infra/helm/rag-platform/templates/pdb.yaml @@ -0,0 +1,14 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "rag-platform.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "rag-platform.labels" . | nindent 4 }} +spec: + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + selector: + matchLabels: + {{- include "rag-platform.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/infra/helm/rag-platform/templates/service.yaml b/infra/helm/rag-platform/templates/service.yaml new file mode 100644 index 0000000..4cdd962 --- /dev/null +++ b/infra/helm/rag-platform/templates/service.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "rag-platform.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "rag-platform.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + {{- if .Values.observability.metrics.enabled }} + - port: {{ .Values.observability.metrics.port }} + targetPort: metrics + protocol: TCP + name: metrics + {{- end }} + selector: + {{- include "rag-platform.selectorLabels" . | nindent 4 }} diff --git a/infra/helm/rag-platform/templates/serviceaccount.yaml b/infra/helm/rag-platform/templates/serviceaccount.yaml new file mode 100644 index 0000000..579246b --- /dev/null +++ b/infra/helm/rag-platform/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "rag-platform.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "rag-platform.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/infra/helm/rag-platform/values.dev.yaml b/infra/helm/rag-platform/values.dev.yaml new file mode 100644 index 0000000..b006c9e --- /dev/null +++ b/infra/helm/rag-platform/values.dev.yaml @@ -0,0 +1,41 @@ +# Development overrides — minimal resources, single replica, local backends. +# Usage: helm upgrade --install rag-platform . -f values.yaml -f values.dev.yaml + +replicaCount: 1 + +image: + pullPolicy: IfNotPresent + +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + +autoscaling: + enabled: false + +podDisruptionBudget: + enabled: false + +affinity: {} + +config: + env: development + log_level: DEBUG + +backends: + postgres: + host: rag-postgres-postgresql.rag-dev.svc.cluster.local + redis: + host: rag-redis-master.rag-dev.svc.cluster.local + qdrant: + host: rag-qdrant.rag-dev.svc.cluster.local + elasticsearch: + host: rag-elasticsearch.rag-dev.svc.cluster.local + +observability: + otel: + enabled: false diff --git a/infra/helm/rag-platform/values.yaml b/infra/helm/rag-platform/values.yaml new file mode 100644 index 0000000..957230e --- /dev/null +++ b/infra/helm/rag-platform/values.yaml @@ -0,0 +1,130 @@ +# Default values for rag-platform. +# Override per-environment via values.dev.yaml or --set on the command line. + +replicaCount: 2 + +image: + repository: ghcr.io/officialcodework/agentcontextos/rag-gateway + pullPolicy: IfNotPresent + tag: "" # defaults to Chart.appVersion + +imagePullSecrets: [] + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +podAnnotations: {} + +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + readOnlyRootFilesystem: true + +service: + type: ClusterIP + port: 8000 + +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: rag.example.com + paths: + - path: / + pathType: Prefix + tls: [] + +resources: + requests: + cpu: 250m + memory: 256Mi + limits: + cpu: 1000m + memory: 512Mi + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 80 + +podDisruptionBudget: + enabled: true + minAvailable: 1 + +nodeSelector: {} + +tolerations: [] + +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - rag-platform + topologyKey: kubernetes.io/hostname + +# --------------------------------------------------------------------------- +# Application configuration — mounted as /app/config/rag.yaml +# --------------------------------------------------------------------------- +config: + env: production + log_level: INFO + service: + name: rag-gateway + version: "0.1.0" + +# --------------------------------------------------------------------------- +# Backend connection settings (injected as env vars from Secret/ConfigMap) +# --------------------------------------------------------------------------- +backends: + postgres: + host: "" # set via --set or values override + port: 5432 + database: rag + secretName: rag-postgres-credentials + userKey: username + passwordKey: postgres-password + + redis: + host: "" + port: 6379 + + qdrant: + host: "" + httpPort: 6333 + grpcPort: 6334 + + elasticsearch: + host: "" + port: 9200 + +# --------------------------------------------------------------------------- +# Observability +# --------------------------------------------------------------------------- +observability: + otel: + enabled: false + endpoint: "" # e.g. http://otel-collector:4318 + metrics: + enabled: true + port: 9090 + path: /metrics diff --git a/infra/terraform/.gitignore b/infra/terraform/.gitignore new file mode 100644 index 0000000..bb5e7d0 --- /dev/null +++ b/infra/terraform/.gitignore @@ -0,0 +1,7 @@ +# Terraform state and secrets — never commit these +**/.terraform/ +**/.terraform.lock.hcl +**/terraform.tfstate +**/terraform.tfstate.backup +**/terraform.tfvars +**/.terraform.tfstate.lock.info diff --git a/infra/terraform/environments/dev/main.tf b/infra/terraform/environments/dev/main.tf new file mode 100644 index 0000000..f90fe53 --- /dev/null +++ b/infra/terraform/environments/dev/main.tf @@ -0,0 +1,68 @@ +terraform { + required_version = ">= 1.9.0" + + required_providers { + helm = { + source = "hashicorp/helm" + version = "~> 2.14" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.32" + } + } + + # For dev, state is local. Switch to S3/GCS backend for shared environments. + backend "local" { + path = "terraform.tfstate" + } +} + +# --------------------------------------------------------------------------- +# Providers — point at the local cluster (kind / minikube / Docker Desktop) +# --------------------------------------------------------------------------- +provider "kubernetes" { + config_path = var.kubeconfig_path + config_context = var.kube_context +} + +provider "helm" { + kubernetes { + config_path = var.kubeconfig_path + config_context = var.kube_context + } +} + +# --------------------------------------------------------------------------- +# Platform backends (minimal resource footprint for dev) +# --------------------------------------------------------------------------- +module "platform" { + source = "../../" + + namespace = var.namespace + environment = "dev" + + postgres = { + db_name = "rag" + db_user = "rag" + storage_size = "2Gi" + version = "16" + } + + redis = { + storage_size = "512Mi" + version = "7" + } + + qdrant = { + storage_size = "5Gi" + version = "v1.10.1" + replicas = 1 + } + + elasticsearch = { + storage_size = "5Gi" + version = "8.14.3" + heap_size = "256m" + } +} diff --git a/infra/terraform/environments/dev/outputs.tf b/infra/terraform/environments/dev/outputs.tf new file mode 100644 index 0000000..dbf14e2 --- /dev/null +++ b/infra/terraform/environments/dev/outputs.tf @@ -0,0 +1,12 @@ +output "postgres_host" { + value = module.platform.postgres_host +} +output "redis_host" { + value = module.platform.redis_host +} +output "qdrant_http_host" { + value = module.platform.qdrant_http_host +} +output "elasticsearch_host" { + value = module.platform.elasticsearch_host +} diff --git a/infra/terraform/environments/dev/terraform.tfvars.example b/infra/terraform/environments/dev/terraform.tfvars.example new file mode 100644 index 0000000..97c78b6 --- /dev/null +++ b/infra/terraform/environments/dev/terraform.tfvars.example @@ -0,0 +1,6 @@ +# Copy to terraform.tfvars and adjust for your local cluster. +# terraform.tfvars is gitignored — never commit real credentials. + +namespace = "rag-dev" +kubeconfig_path = "~/.kube/config" +kube_context = "docker-desktop" # or "kind-agentcontextos", "minikube", etc. diff --git a/infra/terraform/environments/dev/variables.tf b/infra/terraform/environments/dev/variables.tf new file mode 100644 index 0000000..c7f18f2 --- /dev/null +++ b/infra/terraform/environments/dev/variables.tf @@ -0,0 +1,17 @@ +variable "namespace" { + description = "Kubernetes namespace for dev platform components" + type = string + default = "rag-dev" +} + +variable "kubeconfig_path" { + description = "Path to kubeconfig file" + type = string + default = "~/.kube/config" +} + +variable "kube_context" { + description = "Kubernetes context name (e.g. kind-agentcontextos, docker-desktop)" + type = string + default = "docker-desktop" +} diff --git a/infra/terraform/environments/prod/main.tf b/infra/terraform/environments/prod/main.tf new file mode 100644 index 0000000..b67fe9f --- /dev/null +++ b/infra/terraform/environments/prod/main.tf @@ -0,0 +1,74 @@ +terraform { + required_version = ">= 1.9.0" + + required_providers { + helm = { + source = "hashicorp/helm" + version = "~> 2.14" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.32" + } + } + + # Remote state — configure bucket/key before first `terraform init` + backend "s3" { + # bucket = "your-tfstate-bucket" + # key = "rag-platform/prod/terraform.tfstate" + # region = "us-east-1" + # dynamodb_table = "terraform-locks" + # encrypt = true + } +} + +# --------------------------------------------------------------------------- +# Providers — point at an existing EKS / GKE / AKS cluster +# --------------------------------------------------------------------------- +provider "kubernetes" { + host = var.cluster_endpoint + cluster_ca_certificate = base64decode(var.cluster_ca_certificate) + token = var.cluster_token +} + +provider "helm" { + kubernetes { + host = var.cluster_endpoint + cluster_ca_certificate = base64decode(var.cluster_ca_certificate) + token = var.cluster_token + } +} + +# --------------------------------------------------------------------------- +# Platform backends (production sizing) +# --------------------------------------------------------------------------- +module "platform" { + source = "../../" + + namespace = var.namespace + environment = "prod" + + postgres = { + db_name = "rag" + db_user = "rag" + storage_size = var.postgres_storage_size + version = "16" + } + + redis = { + storage_size = var.redis_storage_size + version = "7" + } + + qdrant = { + storage_size = var.qdrant_storage_size + version = "v1.10.1" + replicas = var.qdrant_replicas + } + + elasticsearch = { + storage_size = var.es_storage_size + version = "8.14.3" + heap_size = var.es_heap_size + } +} diff --git a/infra/terraform/environments/prod/outputs.tf b/infra/terraform/environments/prod/outputs.tf new file mode 100644 index 0000000..292089b --- /dev/null +++ b/infra/terraform/environments/prod/outputs.tf @@ -0,0 +1,18 @@ +output "postgres_host" { + value = module.platform.postgres_host +} +output "postgres_secret_name" { + value = module.platform.postgres_secret_name +} +output "redis_host" { + value = module.platform.redis_host +} +output "qdrant_http_host" { + value = module.platform.qdrant_http_host +} +output "qdrant_grpc_host" { + value = module.platform.qdrant_grpc_host +} +output "elasticsearch_host" { + value = module.platform.elasticsearch_host +} diff --git a/infra/terraform/environments/prod/terraform.tfvars.example b/infra/terraform/environments/prod/terraform.tfvars.example new file mode 100644 index 0000000..fcb7ea0 --- /dev/null +++ b/infra/terraform/environments/prod/terraform.tfvars.example @@ -0,0 +1,18 @@ +# Copy to terraform.tfvars and fill in values. +# terraform.tfvars is gitignored — never commit real credentials. +# Retrieve cluster credentials from your cloud provider CLI before running: +# AWS EKS: aws eks update-kubeconfig --name +# GKE: gcloud container clusters get-credentials +# AKS: az aks get-credentials --name + +namespace = "rag-prod" +cluster_endpoint = "https://your-cluster-api-endpoint" +cluster_ca_certificate = "BASE64_ENCODED_CA_CERT" +cluster_token = "SERVICE_ACCOUNT_TOKEN" + +postgres_storage_size = "100Gi" +redis_storage_size = "10Gi" +qdrant_storage_size = "200Gi" +qdrant_replicas = 3 +es_storage_size = "200Gi" +es_heap_size = "4g" diff --git a/infra/terraform/environments/prod/variables.tf b/infra/terraform/environments/prod/variables.tf new file mode 100644 index 0000000..2945c10 --- /dev/null +++ b/infra/terraform/environments/prod/variables.tf @@ -0,0 +1,52 @@ +variable "namespace" { + description = "Kubernetes namespace for prod platform components" + type = string + default = "rag-prod" +} + +variable "cluster_endpoint" { + description = "Kubernetes API server URL" + type = string +} + +variable "cluster_ca_certificate" { + description = "Base64-encoded cluster CA certificate" + type = string + sensitive = true +} + +variable "cluster_token" { + description = "Bearer token for Kubernetes API authentication" + type = string + sensitive = true +} + +variable "postgres_storage_size" { + type = string + default = "100Gi" +} + +variable "redis_storage_size" { + type = string + default = "10Gi" +} + +variable "qdrant_storage_size" { + type = string + default = "200Gi" +} + +variable "qdrant_replicas" { + type = number + default = 3 +} + +variable "es_storage_size" { + type = string + default = "200Gi" +} + +variable "es_heap_size" { + type = string + default = "4g" +} diff --git a/infra/terraform/main.tf b/infra/terraform/main.tf new file mode 100644 index 0000000..4280c35 --- /dev/null +++ b/infra/terraform/main.tf @@ -0,0 +1,62 @@ +resource "kubernetes_namespace" "rag_platform" { + metadata { + name = var.namespace + labels = { + "app.kubernetes.io/managed-by" = "terraform" + "app.kubernetes.io/part-of" = "rag-platform" + environment = var.environment + } + } +} + +module "postgres" { + count = var.enable_postgres ? 1 : 0 + source = "./modules/postgres" + + namespace = kubernetes_namespace.rag_platform.metadata[0].name + environment = var.environment + db_name = var.postgres.db_name + db_user = var.postgres.db_user + storage_size = var.postgres.storage_size + pg_version = var.postgres.version + + depends_on = [kubernetes_namespace.rag_platform] +} + +module "redis" { + count = var.enable_redis ? 1 : 0 + source = "./modules/redis" + + namespace = kubernetes_namespace.rag_platform.metadata[0].name + environment = var.environment + storage_size = var.redis.storage_size + version = var.redis.version + + depends_on = [kubernetes_namespace.rag_platform] +} + +module "qdrant" { + count = var.enable_qdrant ? 1 : 0 + source = "./modules/qdrant" + + namespace = kubernetes_namespace.rag_platform.metadata[0].name + environment = var.environment + storage_size = var.qdrant.storage_size + version = var.qdrant.version + replicas = var.qdrant.replicas + + depends_on = [kubernetes_namespace.rag_platform] +} + +module "elasticsearch" { + count = var.enable_elasticsearch ? 1 : 0 + source = "./modules/elasticsearch" + + namespace = kubernetes_namespace.rag_platform.metadata[0].name + environment = var.environment + storage_size = var.elasticsearch.storage_size + version = var.elasticsearch.version + heap_size = var.elasticsearch.heap_size + + depends_on = [kubernetes_namespace.rag_platform] +} diff --git a/infra/terraform/modules/elasticsearch/main.tf b/infra/terraform/modules/elasticsearch/main.tf new file mode 100644 index 0000000..d2acab0 --- /dev/null +++ b/infra/terraform/modules/elasticsearch/main.tf @@ -0,0 +1,66 @@ +locals { + release_name = "rag-elasticsearch" +} + +resource "helm_release" "elasticsearch" { + name = local.release_name + repository = "https://charts.bitnami.com/bitnami" + chart = "elasticsearch" + version = var.chart_version + namespace = var.namespace + + set { + name = "image.tag" + value = var.version + } + + set { + name = "master.replicaCount" + value = var.master_replicas + } + set { + name = "data.replicaCount" + value = var.data_replicas + } + + set { + name = "master.heapSize" + value = var.heap_size + } + set { + name = "data.heapSize" + value = var.heap_size + } + + # Disable security for in-cluster dev; enable in prod + set { + name = "security.enabled" + value = var.environment == "prod" ? "true" : "false" + } + + set { + name = "master.persistence.size" + value = var.storage_size + } + set { + name = "data.persistence.size" + value = var.storage_size + } + + set { + name = "master.resources.requests.cpu" + value = var.resources.requests_cpu + } + set { + name = "master.resources.requests.memory" + value = var.resources.requests_memory + } + set { + name = "master.resources.limits.cpu" + value = var.resources.limits_cpu + } + set { + name = "master.resources.limits.memory" + value = var.resources.limits_memory + } +} diff --git a/infra/terraform/modules/elasticsearch/outputs.tf b/infra/terraform/modules/elasticsearch/outputs.tf new file mode 100644 index 0000000..cdf046f --- /dev/null +++ b/infra/terraform/modules/elasticsearch/outputs.tf @@ -0,0 +1,9 @@ +output "host" { + description = "In-cluster service hostname for Elasticsearch" + value = "${helm_release.elasticsearch.name}.${var.namespace}.svc.cluster.local" +} + +output "port" { + description = "Elasticsearch HTTP port" + value = 9200 +} diff --git a/infra/terraform/modules/elasticsearch/variables.tf b/infra/terraform/modules/elasticsearch/variables.tf new file mode 100644 index 0000000..523322e --- /dev/null +++ b/infra/terraform/modules/elasticsearch/variables.tf @@ -0,0 +1,61 @@ +variable "namespace" { + description = "Kubernetes namespace to deploy into" + type = string +} + +variable "environment" { + description = "Deployment environment" + type = string +} + +variable "version" { + description = "Elasticsearch version (e.g. 8.14.3)" + type = string + default = "8.14.3" +} + +variable "storage_size" { + description = "PersistentVolumeClaim size for Elasticsearch data" + type = string + default = "30Gi" +} + +variable "heap_size" { + description = "JVM heap size (ES_JAVA_OPTS -Xms/-Xmx value)" + type = string + default = "512m" +} + +variable "chart_version" { + description = "Bitnami elasticsearch Helm chart version" + type = string + default = "21.3.11" +} + +variable "master_replicas" { + description = "Number of master-eligible nodes" + type = number + default = 1 +} + +variable "data_replicas" { + description = "Number of data nodes (set >1 for prod)" + type = number + default = 1 +} + +variable "resources" { + description = "CPU/memory requests and limits" + type = object({ + requests_cpu = string + requests_memory = string + limits_cpu = string + limits_memory = string + }) + default = { + requests_cpu = "500m" + requests_memory = "1Gi" + limits_cpu = "2000m" + limits_memory = "2Gi" + } +} diff --git a/infra/terraform/modules/postgres/main.tf b/infra/terraform/modules/postgres/main.tf new file mode 100644 index 0000000..46bba0b --- /dev/null +++ b/infra/terraform/modules/postgres/main.tf @@ -0,0 +1,110 @@ +locals { + release_name = "rag-postgres" + secret_name = "${local.release_name}-credentials" + + # Use pgvector image instead of vanilla Postgres + image_repository = "pgvector/pgvector" + image_tag = "pg${var.pg_version}" + + # In production, pass db_password via TF_VAR_db_password or a secrets manager. + # The placeholder below is intentionally weak and suitable for dev only. + effective_password = var.db_password != "" ? var.db_password : "dev-only-change-in-prod" +} + +resource "kubernetes_secret" "postgres_credentials" { + metadata { + name = local.secret_name + namespace = var.namespace + labels = { + "app.kubernetes.io/component" = "postgres" + "app.kubernetes.io/part-of" = "rag-platform" + environment = var.environment + } + } + + data = { + postgres-password = local.effective_password + username = var.db_user + database = var.db_name + } + + type = "Opaque" +} + +resource "helm_release" "postgres" { + name = local.release_name + repository = "https://charts.bitnami.com/bitnami" + chart = "postgresql" + version = var.chart_version + namespace = var.namespace + + set { + name = "image.repository" + value = local.image_repository + } + set { + name = "image.tag" + value = local.image_tag + } + + set { + name = "auth.username" + value = var.db_user + } + set { + name = "auth.database" + value = var.db_name + } + set { + name = "auth.existingSecret" + value = kubernetes_secret.postgres_credentials.metadata[0].name + } + set { + name = "auth.secretKeys.adminPasswordKey" + value = "postgres-password" + } + + # pgvector extension — enabled in init SQL + set { + name = "primary.initdb.scriptsConfigMap" + value = kubernetes_config_map.postgres_init.metadata[0].name + } + + set { + name = "primary.persistence.size" + value = var.storage_size + } + + set { + name = "primary.resources.requests.cpu" + value = var.resources.requests_cpu + } + set { + name = "primary.resources.requests.memory" + value = var.resources.requests_memory + } + set { + name = "primary.resources.limits.cpu" + value = var.resources.limits_cpu + } + set { + name = "primary.resources.limits.memory" + value = var.resources.limits_memory + } + + depends_on = [ + kubernetes_secret.postgres_credentials, + kubernetes_config_map.postgres_init, + ] +} + +resource "kubernetes_config_map" "postgres_init" { + metadata { + name = "${local.release_name}-initdb" + namespace = var.namespace + } + + data = { + "01_pgvector.sql" = file("${path.module}/../../../postgres/init.sql") + } +} diff --git a/infra/terraform/modules/postgres/outputs.tf b/infra/terraform/modules/postgres/outputs.tf new file mode 100644 index 0000000..8a5815a --- /dev/null +++ b/infra/terraform/modules/postgres/outputs.tf @@ -0,0 +1,24 @@ +output "host" { + description = "In-cluster service hostname for Postgres" + value = "${helm_release.postgres.name}-postgresql.${var.namespace}.svc.cluster.local" +} + +output "port" { + description = "Postgres service port" + value = 5432 +} + +output "secret_name" { + description = "Name of the Kubernetes Secret holding DB credentials" + value = kubernetes_secret.postgres_credentials.metadata[0].name +} + +output "db_name" { + description = "Database name" + value = var.db_name +} + +output "db_user" { + description = "Database user" + value = var.db_user +} diff --git a/infra/terraform/modules/postgres/variables.tf b/infra/terraform/modules/postgres/variables.tf new file mode 100644 index 0000000..7771697 --- /dev/null +++ b/infra/terraform/modules/postgres/variables.tf @@ -0,0 +1,62 @@ +variable "namespace" { + description = "Kubernetes namespace to deploy into" + type = string +} + +variable "environment" { + description = "Deployment environment" + type = string +} + +variable "db_name" { + description = "Database name" + type = string + default = "rag" +} + +variable "db_user" { + description = "Database superuser name" + type = string + default = "rag" +} + +variable "db_password" { + description = "Database password — pass via TF_VAR_db_password or a secrets manager" + type = string + sensitive = true + default = "" +} + +variable "storage_size" { + description = "PersistentVolumeClaim size for Postgres data" + type = string + default = "10Gi" +} + +variable "pg_version" { + description = "PostgreSQL major version (must be compatible with pgvector/pgvector image)" + type = string + default = "16" +} + +variable "chart_version" { + description = "Bitnami postgresql Helm chart version" + type = string + default = "15.5.38" +} + +variable "resources" { + description = "CPU/memory requests and limits" + type = object({ + requests_cpu = string + requests_memory = string + limits_cpu = string + limits_memory = string + }) + default = { + requests_cpu = "250m" + requests_memory = "256Mi" + limits_cpu = "1000m" + limits_memory = "1Gi" + } +} diff --git a/infra/terraform/modules/qdrant/main.tf b/infra/terraform/modules/qdrant/main.tf new file mode 100644 index 0000000..fb4a531 --- /dev/null +++ b/infra/terraform/modules/qdrant/main.tf @@ -0,0 +1,49 @@ +locals { + release_name = "rag-qdrant" +} + +resource "helm_release" "qdrant" { + name = local.release_name + repository = "https://qdrant.github.io/qdrant-helm" + chart = "qdrant" + version = var.chart_version + namespace = var.namespace + + set { + name = "image.tag" + value = var.version + } + + set { + name = "replicaCount" + value = var.replicas + } + + set { + name = "persistence.size" + value = var.storage_size + } + + set { + name = "resources.requests.cpu" + value = var.resources.requests_cpu + } + set { + name = "resources.requests.memory" + value = var.resources.requests_memory + } + set { + name = "resources.limits.cpu" + value = var.resources.limits_cpu + } + set { + name = "resources.limits.memory" + value = var.resources.limits_memory + } + + # Expose metrics for Prometheus scraping + set { + name = "metrics.serviceMonitor.enabled" + value = "false" + } +} diff --git a/infra/terraform/modules/qdrant/outputs.tf b/infra/terraform/modules/qdrant/outputs.tf new file mode 100644 index 0000000..3b406b0 --- /dev/null +++ b/infra/terraform/modules/qdrant/outputs.tf @@ -0,0 +1,19 @@ +output "http_host" { + description = "In-cluster HTTP service hostname for Qdrant" + value = "${helm_release.qdrant.name}.${var.namespace}.svc.cluster.local" +} + +output "grpc_host" { + description = "In-cluster gRPC service hostname for Qdrant (same host, different port)" + value = "${helm_release.qdrant.name}.${var.namespace}.svc.cluster.local" +} + +output "http_port" { + description = "Qdrant HTTP/REST port" + value = 6333 +} + +output "grpc_port" { + description = "Qdrant gRPC port" + value = 6334 +} diff --git a/infra/terraform/modules/qdrant/variables.tf b/infra/terraform/modules/qdrant/variables.tf new file mode 100644 index 0000000..e57c530 --- /dev/null +++ b/infra/terraform/modules/qdrant/variables.tf @@ -0,0 +1,49 @@ +variable "namespace" { + description = "Kubernetes namespace to deploy into" + type = string +} + +variable "environment" { + description = "Deployment environment" + type = string +} + +variable "version" { + description = "Qdrant image tag (e.g. v1.10.1)" + type = string + default = "v1.10.1" +} + +variable "replicas" { + description = "Number of Qdrant replicas (1 for dev, 3+ for prod HA)" + type = number + default = 1 +} + +variable "storage_size" { + description = "PersistentVolumeClaim size for Qdrant vector storage" + type = string + default = "20Gi" +} + +variable "chart_version" { + description = "Qdrant Helm chart version" + type = string + default = "0.10.1" +} + +variable "resources" { + description = "CPU/memory requests and limits" + type = object({ + requests_cpu = string + requests_memory = string + limits_cpu = string + limits_memory = string + }) + default = { + requests_cpu = "500m" + requests_memory = "512Mi" + limits_cpu = "2000m" + limits_memory = "4Gi" + } +} diff --git a/infra/terraform/modules/redis/main.tf b/infra/terraform/modules/redis/main.tf new file mode 100644 index 0000000..89895fc --- /dev/null +++ b/infra/terraform/modules/redis/main.tf @@ -0,0 +1,44 @@ +locals { + release_name = "rag-redis" +} + +resource "helm_release" "redis" { + name = local.release_name + repository = "https://charts.bitnami.com/bitnami" + chart = "redis" + version = var.chart_version + namespace = var.namespace + + set { + name = "architecture" + value = var.architecture + } + + # Disable authentication for in-cluster use; enable in prod via existingSecret + set { + name = "auth.enabled" + value = "false" + } + + set { + name = "master.persistence.size" + value = var.storage_size + } + + set { + name = "master.resources.requests.cpu" + value = var.resources.requests_cpu + } + set { + name = "master.resources.requests.memory" + value = var.resources.requests_memory + } + set { + name = "master.resources.limits.cpu" + value = var.resources.limits_cpu + } + set { + name = "master.resources.limits.memory" + value = var.resources.limits_memory + } +} diff --git a/infra/terraform/modules/redis/outputs.tf b/infra/terraform/modules/redis/outputs.tf new file mode 100644 index 0000000..11984ea --- /dev/null +++ b/infra/terraform/modules/redis/outputs.tf @@ -0,0 +1,9 @@ +output "host" { + description = "In-cluster service hostname for Redis" + value = "${helm_release.redis.name}-master.${var.namespace}.svc.cluster.local" +} + +output "port" { + description = "Redis service port" + value = 6379 +} diff --git a/infra/terraform/modules/redis/variables.tf b/infra/terraform/modules/redis/variables.tf new file mode 100644 index 0000000..328263f --- /dev/null +++ b/infra/terraform/modules/redis/variables.tf @@ -0,0 +1,53 @@ +variable "namespace" { + description = "Kubernetes namespace to deploy into" + type = string +} + +variable "environment" { + description = "Deployment environment" + type = string +} + +variable "version" { + description = "Redis major version" + type = string + default = "7" +} + +variable "storage_size" { + description = "PersistentVolumeClaim size for Redis data" + type = string + default = "2Gi" +} + +variable "chart_version" { + description = "Bitnami redis Helm chart version" + type = string + default = "19.6.4" +} + +variable "architecture" { + description = "Redis deployment architecture: standalone | replication" + type = string + default = "standalone" + validation { + condition = contains(["standalone", "replication"], var.architecture) + error_message = "architecture must be standalone or replication" + } +} + +variable "resources" { + description = "CPU/memory requests and limits" + type = object({ + requests_cpu = string + requests_memory = string + limits_cpu = string + limits_memory = string + }) + default = { + requests_cpu = "100m" + requests_memory = "128Mi" + limits_cpu = "500m" + limits_memory = "512Mi" + } +} diff --git a/infra/terraform/outputs.tf b/infra/terraform/outputs.tf new file mode 100644 index 0000000..fae4b3b --- /dev/null +++ b/infra/terraform/outputs.tf @@ -0,0 +1,49 @@ +output "namespace" { + description = "Kubernetes namespace housing all platform components" + value = var.namespace +} + +output "postgres_host" { + description = "Postgres service hostname (in-cluster)" + value = var.enable_postgres ? module.postgres[0].host : null +} + +output "postgres_port" { + description = "Postgres service port" + value = var.enable_postgres ? module.postgres[0].port : null +} + +output "postgres_secret_name" { + description = "Kubernetes Secret holding Postgres credentials" + value = var.enable_postgres ? module.postgres[0].secret_name : null +} + +output "redis_host" { + description = "Redis service hostname (in-cluster)" + value = var.enable_redis ? module.redis[0].host : null +} + +output "redis_port" { + description = "Redis service port" + value = var.enable_redis ? module.redis[0].port : null +} + +output "qdrant_http_host" { + description = "Qdrant HTTP service hostname (in-cluster)" + value = var.enable_qdrant ? module.qdrant[0].http_host : null +} + +output "qdrant_grpc_host" { + description = "Qdrant gRPC service hostname (in-cluster)" + value = var.enable_qdrant ? module.qdrant[0].grpc_host : null +} + +output "elasticsearch_host" { + description = "Elasticsearch service hostname (in-cluster)" + value = var.enable_elasticsearch ? module.elasticsearch[0].host : null +} + +output "elasticsearch_port" { + description = "Elasticsearch service port" + value = var.enable_elasticsearch ? module.elasticsearch[0].port : null +} diff --git a/infra/terraform/variables.tf b/infra/terraform/variables.tf new file mode 100644 index 0000000..14d228f --- /dev/null +++ b/infra/terraform/variables.tf @@ -0,0 +1,104 @@ +variable "namespace" { + description = "Kubernetes namespace for all platform components" + type = string + default = "rag-platform" +} + +variable "environment" { + description = "Deployment environment: dev | staging | prod" + type = string + validation { + condition = contains(["dev", "staging", "prod"], var.environment) + error_message = "environment must be one of: dev, staging, prod" + } +} + +variable "image_pull_policy" { + description = "Kubernetes image pull policy" + type = string + default = "IfNotPresent" + validation { + condition = contains(["Always", "IfNotPresent", "Never"], var.image_pull_policy) + error_message = "image_pull_policy must be Always, IfNotPresent, or Never" + } +} + +variable "enable_postgres" { + description = "Deploy in-cluster Postgres (disable when using external RDS)" + type = bool + default = true +} + +variable "enable_redis" { + description = "Deploy in-cluster Redis (disable when using external ElastiCache)" + type = bool + default = true +} + +variable "enable_qdrant" { + description = "Deploy in-cluster Qdrant (disable when using Qdrant Cloud)" + type = bool + default = true +} + +variable "enable_elasticsearch" { + description = "Deploy in-cluster Elasticsearch" + type = bool + default = true +} + +variable "postgres" { + description = "Postgres module configuration" + type = object({ + db_name = string + db_user = string + storage_size = string + version = string + }) + default = { + db_name = "rag" + db_user = "rag" + storage_size = "10Gi" + version = "16" + } +} + +variable "redis" { + description = "Redis module configuration" + type = object({ + storage_size = string + version = string + }) + default = { + storage_size = "2Gi" + version = "7" + } +} + +variable "qdrant" { + description = "Qdrant module configuration" + type = object({ + storage_size = string + version = string + replicas = number + }) + default = { + storage_size = "20Gi" + version = "v1.10.1" + replicas = 1 + } +} + +variable "elasticsearch" { + description = "Elasticsearch module configuration" + type = object({ + storage_size = string + version = string + heap_size = string + }) + default = { + storage_size = "30Gi" + version = "8.14.3" + heap_size = "512m" + } +} diff --git a/infra/terraform/versions.tf b/infra/terraform/versions.tf new file mode 100644 index 0000000..9648368 --- /dev/null +++ b/infra/terraform/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.9.0" + + required_providers { + helm = { + source = "hashicorp/helm" + version = "~> 2.14" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.32" + } + } +} From b96d97067df2d7cbeda1d5bd204553083bcd79a4 Mon Sep 17 00:00:00 2001 From: Deep Kumar Singh Kushwah Date: Sun, 24 May 2026 00:24:29 +0530 Subject: [PATCH 2/3] =?UTF-8?q?fix(ci):=20use=20helm=20version=20'latest'?= =?UTF-8?q?=20=E2=80=94=20azure/setup-helm=20does=20not=20support=20semver?= =?UTF-8?q?=20ranges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 333bc37..a23c3e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,7 +139,7 @@ jobs: - name: Setup Helm uses: azure/setup-helm@v4 with: - version: "3.15.x" + version: "latest" - name: Terraform validate — dev working-directory: infra/terraform/environments/dev From f36cc9fbefd3cf05717013a9ead5505cbce2dbef Mon Sep 17 00:00:00 2001 From: Deep Kumar Singh Kushwah Date: Sun, 24 May 2026 00:27:44 +0530 Subject: [PATCH 3/3] =?UTF-8?q?fix(infra):=20rename=20version=20=E2=86=92?= =?UTF-8?q?=20app=5Fversion=20to=20avoid=20Terraform=20meta-argument=20con?= =?UTF-8?q?flict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'version' is reserved for registry modules in a module block. Using it with a local-path source caused Terraform to reject the source as an invalid registry address. Renamed to app_version across all four backend modules, root variables.tf, root main.tf, and both environment main.tf files. Co-Authored-By: Claude Sonnet 4.6 --- infra/terraform/environments/dev/main.tf | 8 ++++---- infra/terraform/environments/prod/main.tf | 8 ++++---- infra/terraform/main.tf | 8 ++++---- infra/terraform/modules/elasticsearch/main.tf | 2 +- .../terraform/modules/elasticsearch/variables.tf | 2 +- infra/terraform/modules/qdrant/main.tf | 2 +- infra/terraform/modules/qdrant/variables.tf | 2 +- infra/terraform/modules/redis/variables.tf | 2 +- infra/terraform/variables.tf | 16 ++++++++-------- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/infra/terraform/environments/dev/main.tf b/infra/terraform/environments/dev/main.tf index f90fe53..d61671c 100644 --- a/infra/terraform/environments/dev/main.tf +++ b/infra/terraform/environments/dev/main.tf @@ -46,23 +46,23 @@ module "platform" { db_name = "rag" db_user = "rag" storage_size = "2Gi" - version = "16" + app_version = "16" } redis = { storage_size = "512Mi" - version = "7" + app_version = "7" } qdrant = { storage_size = "5Gi" - version = "v1.10.1" + app_version = "v1.10.1" replicas = 1 } elasticsearch = { storage_size = "5Gi" - version = "8.14.3" + app_version = "8.14.3" heap_size = "256m" } } diff --git a/infra/terraform/environments/prod/main.tf b/infra/terraform/environments/prod/main.tf index b67fe9f..b30baf6 100644 --- a/infra/terraform/environments/prod/main.tf +++ b/infra/terraform/environments/prod/main.tf @@ -52,23 +52,23 @@ module "platform" { db_name = "rag" db_user = "rag" storage_size = var.postgres_storage_size - version = "16" + app_version = "16" } redis = { storage_size = var.redis_storage_size - version = "7" + app_version = "7" } qdrant = { storage_size = var.qdrant_storage_size - version = "v1.10.1" + app_version = "v1.10.1" replicas = var.qdrant_replicas } elasticsearch = { storage_size = var.es_storage_size - version = "8.14.3" + app_version = "8.14.3" heap_size = var.es_heap_size } } diff --git a/infra/terraform/main.tf b/infra/terraform/main.tf index 4280c35..06c9b3f 100644 --- a/infra/terraform/main.tf +++ b/infra/terraform/main.tf @@ -18,7 +18,7 @@ module "postgres" { db_name = var.postgres.db_name db_user = var.postgres.db_user storage_size = var.postgres.storage_size - pg_version = var.postgres.version + pg_version = var.postgres.app_version depends_on = [kubernetes_namespace.rag_platform] } @@ -30,7 +30,7 @@ module "redis" { namespace = kubernetes_namespace.rag_platform.metadata[0].name environment = var.environment storage_size = var.redis.storage_size - version = var.redis.version + app_version = var.redis.app_version depends_on = [kubernetes_namespace.rag_platform] } @@ -42,7 +42,7 @@ module "qdrant" { namespace = kubernetes_namespace.rag_platform.metadata[0].name environment = var.environment storage_size = var.qdrant.storage_size - version = var.qdrant.version + app_version = var.qdrant.app_version replicas = var.qdrant.replicas depends_on = [kubernetes_namespace.rag_platform] @@ -55,7 +55,7 @@ module "elasticsearch" { namespace = kubernetes_namespace.rag_platform.metadata[0].name environment = var.environment storage_size = var.elasticsearch.storage_size - version = var.elasticsearch.version + app_version = var.elasticsearch.app_version heap_size = var.elasticsearch.heap_size depends_on = [kubernetes_namespace.rag_platform] diff --git a/infra/terraform/modules/elasticsearch/main.tf b/infra/terraform/modules/elasticsearch/main.tf index d2acab0..e1607c1 100644 --- a/infra/terraform/modules/elasticsearch/main.tf +++ b/infra/terraform/modules/elasticsearch/main.tf @@ -11,7 +11,7 @@ resource "helm_release" "elasticsearch" { set { name = "image.tag" - value = var.version + value = var.app_version } set { diff --git a/infra/terraform/modules/elasticsearch/variables.tf b/infra/terraform/modules/elasticsearch/variables.tf index 523322e..9589bd8 100644 --- a/infra/terraform/modules/elasticsearch/variables.tf +++ b/infra/terraform/modules/elasticsearch/variables.tf @@ -8,7 +8,7 @@ variable "environment" { type = string } -variable "version" { +variable "app_version" { description = "Elasticsearch version (e.g. 8.14.3)" type = string default = "8.14.3" diff --git a/infra/terraform/modules/qdrant/main.tf b/infra/terraform/modules/qdrant/main.tf index fb4a531..02bc260 100644 --- a/infra/terraform/modules/qdrant/main.tf +++ b/infra/terraform/modules/qdrant/main.tf @@ -11,7 +11,7 @@ resource "helm_release" "qdrant" { set { name = "image.tag" - value = var.version + value = var.app_version } set { diff --git a/infra/terraform/modules/qdrant/variables.tf b/infra/terraform/modules/qdrant/variables.tf index e57c530..9e49fc8 100644 --- a/infra/terraform/modules/qdrant/variables.tf +++ b/infra/terraform/modules/qdrant/variables.tf @@ -8,7 +8,7 @@ variable "environment" { type = string } -variable "version" { +variable "app_version" { description = "Qdrant image tag (e.g. v1.10.1)" type = string default = "v1.10.1" diff --git a/infra/terraform/modules/redis/variables.tf b/infra/terraform/modules/redis/variables.tf index 328263f..1575ef6 100644 --- a/infra/terraform/modules/redis/variables.tf +++ b/infra/terraform/modules/redis/variables.tf @@ -8,7 +8,7 @@ variable "environment" { type = string } -variable "version" { +variable "app_version" { description = "Redis major version" type = string default = "7" diff --git a/infra/terraform/variables.tf b/infra/terraform/variables.tf index 14d228f..a9a62b4 100644 --- a/infra/terraform/variables.tf +++ b/infra/terraform/variables.tf @@ -53,13 +53,13 @@ variable "postgres" { db_name = string db_user = string storage_size = string - version = string + app_version = string }) default = { db_name = "rag" db_user = "rag" storage_size = "10Gi" - version = "16" + app_version = "16" } } @@ -67,11 +67,11 @@ variable "redis" { description = "Redis module configuration" type = object({ storage_size = string - version = string + app_version = string }) default = { storage_size = "2Gi" - version = "7" + app_version = "7" } } @@ -79,12 +79,12 @@ variable "qdrant" { description = "Qdrant module configuration" type = object({ storage_size = string - version = string + app_version = string replicas = number }) default = { storage_size = "20Gi" - version = "v1.10.1" + app_version = "v1.10.1" replicas = 1 } } @@ -93,12 +93,12 @@ variable "elasticsearch" { description = "Elasticsearch module configuration" type = object({ storage_size = string - version = string + app_version = string heap_size = string }) default = { storage_size = "30Gi" - version = "8.14.3" + app_version = "8.14.3" heap_size = "512m" } }