Merge pull request #18 from manmohan659/feat/cicd-pipeline

feat(ci): CI/CD pipeline + Helm umbrella chart for samosaChaat (#8)
This commit is contained in:
Manmohan 2026-04-16 15:12:45 -04:00 committed by GitHub
commit d98f50f64e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1421 additions and 11 deletions

7
.commitlintrc.json Normal file
View File

@ -0,0 +1,7 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"header-max-length": [2, "always", 100],
"body-max-line-length": [1, "always", 120]
}
}

49
.github/workflows/build-dev.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Build & Push Dev Images
on:
push:
branches: [master, main]
concurrency:
group: build-dev-${{ github.ref }}
cancel-in-progress: false
permissions:
id-token: write
contents: read
jobs:
build:
name: Build ${{ matrix.service }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
service: [frontend, auth, chat-api, inference]
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION || 'us-east-1' }}
- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build & push image
uses: docker/build-push-action@v6
with:
context: services/${{ matrix.service }}
file: services/${{ matrix.service }}/Dockerfile
push: true
tags: |
${{ steps.ecr-login.outputs.registry }}/samosachaat/${{ matrix.service }}:dev-${{ github.sha }}
${{ steps.ecr-login.outputs.registry }}/samosachaat/${{ matrix.service }}:dev-latest
cache-from: type=gha,scope=${{ matrix.service }}
cache-to: type=gha,mode=max,scope=${{ matrix.service }}

185
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,185 @@
name: CI
on:
push:
branches: [master, main]
pull_request:
branches: [master, main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: read
jobs:
changes:
name: Detect changed paths
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
auth: ${{ steps.filter.outputs.auth }}
chat-api: ${{ steps.filter.outputs.chat-api }}
inference: ${{ steps.filter.outputs.inference }}
terraform: ${{ steps.filter.outputs.terraform }}
steps:
- uses: actions/checkout@v4
- name: Filter paths
id: filter
uses: dorny/paths-filter@v3
with:
filters: |
frontend:
- 'services/frontend/**'
auth:
- 'services/auth/**'
chat-api:
- 'services/chat-api/**'
inference:
- 'services/inference/**'
terraform:
- 'terraform/**'
test-frontend:
name: Frontend — lint/type-check/test
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: services/frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: services/frontend/package-lock.json
- name: Install deps
run: npm ci
- name: Lint
run: npm run lint
- name: Type-check
run: npm run typecheck
- name: Test
run: npm test --if-present
test-auth:
name: Auth — pytest
needs: changes
if: needs.changes.outputs.auth == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: services/auth
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install uv
run: pip install uv==0.4.30
- name: Sync deps
run: uv sync
- name: Run pytest
run: uv run pytest
test-chat-api:
name: Chat-API — pytest (postgres service)
needs: changes
if: needs.changes.outputs.chat-api == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: services/chat-api
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: samosachaat_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/samosachaat_test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install uv
run: pip install uv==0.4.30
- name: Sync deps
run: uv sync
- name: Run pytest
run: uv run pytest
test-inference:
name: Inference — pytest
needs: changes
if: needs.changes.outputs.inference == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: services/inference
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install uv
run: pip install uv==0.4.30
- name: Sync deps
run: uv sync
- name: Run pytest
run: uv run pytest
terraform-validate:
name: Terraform — validate
needs: changes
if: needs.changes.outputs.terraform == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: terraform
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: '1.9.8'
- name: Terraform fmt check
run: terraform fmt -check -recursive
- name: Terraform init (no backend)
run: terraform init -backend=false
- name: Terraform validate
run: terraform validate

37
.github/workflows/nightly.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: Nightly integration
on:
schedule:
- cron: '0 6 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
compose-integration:
name: docker compose integration suite
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Run compose integration
run: |
set -euo pipefail
compose_files=(-f docker-compose.yml)
if [ -f docker-compose.test.yml ]; then
compose_files+=(-f docker-compose.test.yml)
fi
docker compose "${compose_files[@]}" up --build --abort-on-container-exit --exit-code-from tests
- name: Compose logs on failure
if: failure()
run: docker compose logs --no-color
- name: Tear down
if: always()
run: docker compose down -v --remove-orphans

83
.github/workflows/promote-uat.yml vendored Normal file
View File

@ -0,0 +1,83 @@
name: Promote to UAT
on:
push:
tags:
- 'RC*'
concurrency:
group: promote-uat
cancel-in-progress: false
permissions:
id-token: write
contents: read
env:
AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }}
UAT_CLUSTER: samosachaat-uat
UAT_NAMESPACE: samosachaat-uat
SERVICES: frontend auth chat-api inference
jobs:
promote:
name: Re-tag dev → uat and deploy
runs-on: ubuntu-latest
environment: uat
steps:
- uses: actions/checkout@v4
- name: Resolve tag
id: tag
run: echo "name=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Re-tag dev images as uat-${{ steps.tag.outputs.name }}
env:
REGISTRY: ${{ steps.ecr-login.outputs.registry }}
SRC_REF: dev-latest
DST_REF: uat-${{ steps.tag.outputs.name }}
run: |
set -euo pipefail
for svc in $SERVICES; do
repo="samosachaat/${svc}"
echo "Re-tagging $repo:$SRC_REF -> $repo:$DST_REF"
manifest=$(aws ecr batch-get-image \
--repository-name "$repo" \
--image-ids imageTag="$SRC_REF" \
--query 'images[0].imageManifest' \
--output text)
aws ecr put-image \
--repository-name "$repo" \
--image-tag "$DST_REF" \
--image-manifest "$manifest" >/dev/null
done
- name: Update kubeconfig
run: |
aws eks update-kubeconfig \
--name "$UAT_CLUSTER" \
--region "$AWS_REGION"
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: 'v3.16.2'
- name: Helm upgrade (UAT)
run: |
helm upgrade --install samosachaat helm/samosachaat \
-f helm/samosachaat/values-uat.yaml \
--set global.imageTag=uat-${{ steps.tag.outputs.name }} \
--namespace "$UAT_NAMESPACE" \
--create-namespace \
--wait --timeout 10m

119
.github/workflows/release-prod.yml vendored Normal file
View File

@ -0,0 +1,119 @@
name: Release to Prod (Blue/Green)
on:
push:
tags:
- 'v*'
concurrency:
group: release-prod
cancel-in-progress: false
permissions:
id-token: write
contents: read
env:
AWS_REGION: ${{ vars.AWS_REGION || 'us-east-1' }}
PROD_CLUSTER: samosachaat-prod
PROD_NAMESPACE: samosachaat-prod
SERVICES: frontend auth chat-api inference
jobs:
release:
name: Blue/Green release ${{ github.ref_name }}
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Resolve tag
id: tag
run: echo "name=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: ecr-login
uses: aws-actions/amazon-ecr-login@v2
- name: Promote uat images to prod tag
env:
REGISTRY: ${{ steps.ecr-login.outputs.registry }}
DST_REF: prod-${{ steps.tag.outputs.name }}
run: |
set -euo pipefail
for svc in $SERVICES; do
repo="samosachaat/${svc}"
src=$(aws ecr describe-images \
--repository-name "$repo" \
--query 'sort_by(imageDetails,&imagePushedAt)[?starts_with(imageTags[0], `uat-`)]|[-1].imageTags[0]' \
--output text)
if [ -z "$src" ] || [ "$src" = "None" ]; then
echo "No uat-* image found for $repo" >&2
exit 1
fi
echo "Promoting $repo:$src -> $repo:$DST_REF"
manifest=$(aws ecr batch-get-image \
--repository-name "$repo" \
--image-ids imageTag="$src" \
--query 'images[0].imageManifest' \
--output text)
aws ecr put-image \
--repository-name "$repo" \
--image-tag "$DST_REF" \
--image-manifest "$manifest" >/dev/null
done
- name: Update kubeconfig
run: |
aws eks update-kubeconfig \
--name "$PROD_CLUSTER" \
--region "$AWS_REGION"
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: 'v3.16.2'
- name: Deploy green slot
run: |
helm upgrade --install samosachaat-green helm/samosachaat \
-f helm/samosachaat/values-prod.yaml \
--set global.imageTag=prod-${{ steps.tag.outputs.name }} \
--set deployment.slot=green \
--set ingress.enabled=false \
--namespace "$PROD_NAMESPACE" \
--create-namespace \
--wait --timeout 15m
- name: Smoke test green
run: |
set -euo pipefail
kubectl -n "$PROD_NAMESPACE" rollout status deploy/frontend-green --timeout=5m
kubectl -n "$PROD_NAMESPACE" run smoke-${{ github.run_id }} \
--rm -i --restart=Never \
--image=curlimages/curl:8.10.1 \
--command -- curl -fsS --max-time 10 \
http://frontend-green.${PROD_NAMESPACE}.svc.cluster.local:3000/health
- name: Swap ingress → green
run: |
helm upgrade --install samosachaat helm/samosachaat \
-f helm/samosachaat/values-prod.yaml \
--set global.imageTag=prod-${{ steps.tag.outputs.name }} \
--set deployment.slot=green \
--set ingress.enabled=true \
--namespace "$PROD_NAMESPACE" \
--wait --timeout 10m
- name: Retain blue as rollback standby
run: |
echo "Blue slot retained for rollback. To roll back:"
echo " helm upgrade samosachaat helm/samosachaat \\"
echo " -f helm/samosachaat/values-prod.yaml \\"
echo " --set deployment.slot=blue --namespace $PROD_NAMESPACE"

4
.husky/commit-msg Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"

View File

@ -1,6 +1,6 @@
apiVersion: v2
name: samosachaat
description: Application chart scaffold for the samosaChaat platform.
description: Umbrella chart for the samosaChaat platform (frontend, auth, chat-api, inference).
type: application
version: 0.1.0
appVersion: "0.1.0"

View File

@ -1,4 +1,10 @@
The samosaChaat application chart is scaffolded.
samosaChaat release {{ .Release.Name }} deployed to namespace {{ include "samosachaat.namespace" . }}.
Populate this chart with Deployments, Services, Ingress, Secrets, and ConfigMaps
as the platform services are implemented.
Image tag: {{ .Values.global.imageTag }}
Slot: {{ default "single" .Values.deployment.slot }}
Ingress host: {{ .Values.ingress.host }}
Useful commands:
kubectl -n {{ include "samosachaat.namespace" . }} get pods
kubectl -n {{ include "samosachaat.namespace" . }} get ingress samosachaat
helm -n {{ include "samosachaat.namespace" . }} status {{ .Release.Name }}

View File

@ -0,0 +1,78 @@
{{/*
Common helpers for the samosachaat umbrella chart.
*/}}
{{/* Chart name truncated to 63 chars (k8s label limit). */}}
{{- define "samosachaat.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/* Fully-qualified release name used as the chart-wide prefix. */}}
{{- define "samosachaat.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 -}}
{{/* Common labels. */}}
{{- define "samosachaat.labels" -}}
app.kubernetes.io/name: {{ include "samosachaat.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: samosachaat
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end -}}
{{/*
Compose a service-specific name. Honors .Values.deployment.slot so that a
"green" slot (during blue/green prod releases) produces e.g. "frontend-green".
Usage: {{ include "samosachaat.svcName" (dict "root" . "svc" "frontend") }}
*/}}
{{- define "samosachaat.svcName" -}}
{{- $root := .root -}}
{{- $svc := .svc -}}
{{- $slot := default "" $root.Values.deployment.slot -}}
{{- if $slot -}}
{{- printf "%s-%s" $svc $slot | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $svc | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{/* Per-service selector labels. */}}
{{- define "samosachaat.selectorLabels" -}}
{{- $root := .root -}}
{{- $svc := .svc -}}
app.kubernetes.io/name: {{ $svc }}
app.kubernetes.io/instance: {{ $root.Release.Name }}
app.kubernetes.io/component: {{ $svc }}
{{- with $root.Values.deployment.slot }}
app.kubernetes.io/slot: {{ . }}
{{- end }}
{{- end -}}
{{/* Render a full image reference given a service's .image block. */}}
{{- define "samosachaat.image" -}}
{{- $root := .root -}}
{{- $svc := .svc -}}
{{- $registry := $root.Values.global.imageRegistry | default "" -}}
{{- $repo := $svc.image.repository -}}
{{- $tag := $root.Values.global.imageTag | default "dev-latest" -}}
{{- if $registry -}}
{{- printf "%s/%s:%s" $registry $repo $tag -}}
{{- else -}}
{{- printf "%s:%s" $repo $tag -}}
{{- end -}}
{{- end -}}
{{/* Namespace that every resource should land in. */}}
{{- define "samosachaat.namespace" -}}
{{- default .Release.Namespace .Values.namespace.name -}}
{{- end -}}

View File

@ -0,0 +1,60 @@
{{- if .Values.auth.enabled -}}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "auth") -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "auth") | nindent 4 }}
spec:
replicas: {{ .Values.auth.replicaCount }}
selector:
matchLabels:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "auth") | nindent 6 }}
template:
metadata:
labels:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "auth") | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: auth
image: {{ include "samosachaat.image" (dict "root" . "svc" .Values.auth) }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- name: http
containerPort: {{ .Values.auth.port }}
protocol: TCP
envFrom:
- configMapRef:
name: samosachaat-config
- secretRef:
name: samosachaat-secrets
optional: true
{{- with .Values.auth.env }}
env:
{{- range $k, $v := . }}
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}
{{- end }}
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
resources:
{{- toYaml .Values.auth.resources | nindent 12 }}
{{- end }}

View File

@ -0,0 +1,20 @@
{{- if .Values.auth.enabled -}}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "auth") -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "auth") | nindent 4 }}
spec:
type: ClusterIP
selector:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "auth") | nindent 4 }}
ports:
- name: http
port: {{ .Values.auth.port }}
targetPort: http
protocol: TCP
{{- end }}

View File

@ -0,0 +1,60 @@
{{- if .Values.chatApi.enabled -}}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "chat-api") -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "chat-api") | nindent 4 }}
spec:
replicas: {{ .Values.chatApi.replicaCount }}
selector:
matchLabels:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "chat-api") | nindent 6 }}
template:
metadata:
labels:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "chat-api") | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: chat-api
image: {{ include "samosachaat.image" (dict "root" . "svc" .Values.chatApi) }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- name: http
containerPort: {{ .Values.chatApi.port }}
protocol: TCP
envFrom:
- configMapRef:
name: samosachaat-config
- secretRef:
name: samosachaat-secrets
optional: true
{{- with .Values.chatApi.env }}
env:
{{- range $k, $v := . }}
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}
{{- end }}
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
resources:
{{- toYaml .Values.chatApi.resources | nindent 12 }}
{{- end }}

View File

@ -0,0 +1,20 @@
{{- if .Values.chatApi.enabled -}}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "chat-api") -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "chat-api") | nindent 4 }}
spec:
type: ClusterIP
selector:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "chat-api") | nindent 4 }}
ports:
- name: http
port: {{ .Values.chatApi.port }}
targetPort: http
protocol: TCP
{{- end }}

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: samosachaat-config
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
data:
{{- range $k, $v := .Values.config }}
{{ $k }}: {{ $v | quote }}
{{- end }}

View File

@ -0,0 +1,48 @@
{{- if .Values.dbMigrate.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: samosachaat-db-migrate
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
app.kubernetes.io/component: db-migrate
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-1"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 300
template:
metadata:
labels:
{{- include "samosachaat.labels" . | nindent 8 }}
app.kubernetes.io/component: db-migrate
spec:
restartPolicy: Never
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: db-migrate
image: {{ include "samosachaat.image" (dict "root" . "svc" .Values.dbMigrate) }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
workingDir: {{ .Values.dbMigrate.workingDir }}
command:
{{- toYaml .Values.dbMigrate.command | nindent 12 }}
envFrom:
- configMapRef:
name: samosachaat-config
- secretRef:
name: samosachaat-secrets
optional: true
{{- with .Values.dbMigrate.env }}
env:
{{- range $k, $v := . }}
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,60 @@
{{- if .Values.frontend.enabled -}}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "frontend") -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "frontend") | nindent 4 }}
spec:
replicas: {{ .Values.frontend.replicaCount }}
selector:
matchLabels:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "frontend") | nindent 6 }}
template:
metadata:
labels:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "frontend") | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: frontend
image: {{ include "samosachaat.image" (dict "root" . "svc" .Values.frontend) }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- name: http
containerPort: {{ .Values.frontend.port }}
protocol: TCP
envFrom:
- configMapRef:
name: samosachaat-config
- secretRef:
name: samosachaat-secrets
optional: true
{{- with .Values.frontend.env }}
env:
{{- range $k, $v := . }}
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}
{{- end }}
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
resources:
{{- toYaml .Values.frontend.resources | nindent 12 }}
{{- end }}

View File

@ -0,0 +1,20 @@
{{- if .Values.frontend.enabled -}}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "frontend") -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "frontend") | nindent 4 }}
spec:
type: ClusterIP
selector:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "frontend") | nindent 4 }}
ports:
- name: http
port: {{ .Values.frontend.port }}
targetPort: http
protocol: TCP
{{- end }}

View File

@ -0,0 +1,51 @@
{{- if .Values.chatApi.hpa.enabled }}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "chat-api") }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "chat-api") | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ $svcName }}
minReplicas: {{ .Values.chatApi.hpa.minReplicas }}
maxReplicas: {{ .Values.chatApi.hpa.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.chatApi.hpa.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.inference.hpa.enabled }}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "inference") }}
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "inference") | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ $svcName }}
minReplicas: {{ .Values.inference.hpa.minReplicas }}
maxReplicas: {{ .Values.inference.hpa.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.inference.hpa.targetCPUUtilizationPercentage }}
{{- end }}

View File

@ -0,0 +1,60 @@
{{- if .Values.inference.enabled -}}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "inference") -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "inference") | nindent 4 }}
spec:
replicas: {{ .Values.inference.replicaCount }}
selector:
matchLabels:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "inference") | nindent 6 }}
template:
metadata:
labels:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "inference") | nindent 8 }}
spec:
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: inference
image: {{ include "samosachaat.image" (dict "root" . "svc" .Values.inference) }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- name: http
containerPort: {{ .Values.inference.port }}
protocol: TCP
envFrom:
- configMapRef:
name: samosachaat-config
- secretRef:
name: samosachaat-secrets
optional: true
{{- with .Values.inference.env }}
env:
{{- range $k, $v := . }}
- name: {{ $k }}
value: {{ $v | quote }}
{{- end }}
{{- end }}
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 10
resources:
{{- toYaml .Values.inference.resources | nindent 12 }}
{{- end }}

View File

@ -0,0 +1,20 @@
{{- if .Values.inference.enabled -}}
{{- $svcName := include "samosachaat.svcName" (dict "root" . "svc" "inference") -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "inference") | nindent 4 }}
spec:
type: ClusterIP
selector:
{{- include "samosachaat.selectorLabels" (dict "root" . "svc" "inference") | nindent 4 }}
ports:
- name: http
port: {{ .Values.inference.port }}
targetPort: http
protocol: TCP
{{- end }}

View File

@ -0,0 +1,57 @@
{{- if .Values.ingress.enabled -}}
{{- $frontendSvc := include "samosachaat.svcName" (dict "root" . "svc" "frontend") -}}
{{- $authSvc := include "samosachaat.svcName" (dict "root" . "svc" "auth") -}}
{{- $chatApiSvc := include "samosachaat.svcName" (dict "root" . "svc" "chat-api") -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: samosachaat
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
alb.ingress.kubernetes.io/ssl-redirect: "443"
{{- with .Values.ingress.acmCertArn }}
alb.ingress.kubernetes.io/certificate-arn: {{ . | quote }}
{{- end }}
spec:
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /api/auth
pathType: Prefix
backend:
service:
name: {{ $authSvc }}
port:
number: {{ .Values.auth.port }}
- path: /api
pathType: Prefix
backend:
service:
name: {{ $chatApiSvc }}
port:
number: {{ .Values.chatApi.port }}
- path: /
pathType: Prefix
backend:
service:
name: {{ $frontendSvc }}
port:
number: {{ .Values.frontend.port }}
- host: {{ .Values.ingress.grafanaHost | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Values.ingress.grafanaServiceName }}
port:
number: {{ .Values.ingress.grafanaServicePort }}
{{- end }}

View File

@ -0,0 +1,8 @@
{{- if .Values.namespace.create }}
apiVersion: v1
kind: Namespace
metadata:
name: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
{{- end }}

View File

@ -0,0 +1,19 @@
{{- if .Values.pdb.enabled }}
{{- range $svc := list "frontend" "auth" "chat-api" "inference" }}
{{- $svcName := include "samosachaat.svcName" (dict "root" $ "svc" $svc) }}
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ $svcName }}
namespace: {{ include "samosachaat.namespace" $ }}
labels:
{{- include "samosachaat.labels" $ | nindent 4 }}
{{- include "samosachaat.selectorLabels" (dict "root" $ "svc" $svc) | nindent 4 }}
spec:
minAvailable: {{ $.Values.pdb.minAvailable }}
selector:
matchLabels:
{{- include "samosachaat.selectorLabels" (dict "root" $ "svc" $svc) | nindent 6 }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,16 @@
{{- if .Values.secrets.create }}
apiVersion: v1
kind: Secret
metadata:
name: samosachaat-secrets
namespace: {{ include "samosachaat.namespace" . }}
labels:
{{- include "samosachaat.labels" . | nindent 4 }}
type: Opaque
{{- with .Values.secrets.data }}
stringData:
{{- range $k, $v := . }}
{{ $k }}: {{ $v | quote }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,58 @@
## Dev overrides — single-slot, 1 replica, small resources, no HPA.
global:
imageTag: dev-latest
namespace:
name: samosachaat-dev
config:
ENVIRONMENT: "dev"
LOG_LEVEL: "debug"
frontend:
replicaCount: 1
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
auth:
replicaCount: 1
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
chatApi:
replicaCount: 1
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
hpa:
enabled: false
inference:
replicaCount: 1
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: '1'
memory: 2Gi
hpa:
enabled: false
pdb:
enabled: false

View File

@ -0,0 +1,62 @@
## Production overrides — 3+ replicas, HPA enabled for chat-api/inference.
namespace:
name: samosachaat-prod
config:
ENVIRONMENT: "prod"
LOG_LEVEL: "info"
frontend:
replicaCount: 3
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: '1'
memory: 1Gi
auth:
replicaCount: 3
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: '1'
memory: 1Gi
chatApi:
replicaCount: 3
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: '2'
memory: 2Gi
hpa:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
inference:
replicaCount: 3
resources:
requests:
cpu: '1'
memory: 2Gi
limits:
cpu: '4'
memory: 8Gi
hpa:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
pdb:
enabled: true
minAvailable: 1

View File

@ -0,0 +1,56 @@
## UAT overrides — 2 replicas each, medium resources.
namespace:
name: samosachaat-uat
config:
ENVIRONMENT: "uat"
LOG_LEVEL: "info"
frontend:
replicaCount: 2
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
auth:
replicaCount: 2
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
chatApi:
replicaCount: 2
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: '1'
memory: 1Gi
hpa:
enabled: false
inference:
replicaCount: 2
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: '2'
memory: 4Gi
hpa:
enabled: false
pdb:
enabled: true
minAvailable: 1

View File

@ -1,10 +1,131 @@
image:
repository: ghcr.io/manmohan659/nanochat/frontend
tag: latest
pullPolicy: IfNotPresent
## Base values for the samosaChaat umbrella chart.
## Environment overrides live in values-{dev,uat,prod}.yaml.
replicaCount: 1
global:
## Set by CI (promote-uat / release-prod) or `helm install --set global.imageTag=...`.
imageTag: dev-latest
imagePullPolicy: Always
## Optional ECR / registry prefix. e.g. 1234.dkr.ecr.us-east-1.amazonaws.com
imageRegistry: ""
imagePullSecrets: []
service:
type: ClusterIP
namespace:
create: true
name: samosachaat
## Shared env piped into every service.
config:
LOG_LEVEL: "info"
ENVIRONMENT: "dev"
## Secret names/values. Values here are placeholders; real values should come
## from --set-file or an external secret manager.
secrets:
create: true
data: {}
## ---------------- Deployment-wide knobs ----------------
deployment:
## blue / green slot name — empty string means single-slot (dev/uat).
slot: ""
## ---------------- Per-service definitions ----------------
frontend:
enabled: true
image:
repository: samosachaat/frontend
replicaCount: 1
port: 3000
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
env:
NEXT_PUBLIC_API_URL: "https://samosachaat.art/api"
hpa:
enabled: false
auth:
enabled: true
image:
repository: samosachaat/auth
replicaCount: 1
port: 8001
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
env: {}
hpa:
enabled: false
chatApi:
enabled: true
image:
repository: samosachaat/chat-api
replicaCount: 1
port: 8002
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: '1'
memory: 1Gi
env: {}
hpa:
enabled: false
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 70
inference:
enabled: true
image:
repository: samosachaat/inference
replicaCount: 1
port: 8003
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: '2'
memory: 4Gi
env: {}
hpa:
enabled: false
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 70
## ---------------- Ingress (AWS ALB) ----------------
ingress:
enabled: true
host: samosachaat.art
grafanaHost: grafana.samosachaat.art
acmCertArn: ""
grafanaServiceName: grafana
grafanaServicePort: 3000
## ---------------- PodDisruptionBudgets ----------------
pdb:
enabled: false
minAvailable: 1
## ---------------- DB migration Helm hook ----------------
dbMigrate:
enabled: true
## Uses the chat-api image to run alembic; alembic config ships with the auth
## service but the image build for chat-api also includes migrations.
image:
repository: samosachaat/chat-api
command: ["alembic", "upgrade", "head"]
workingDir: /app
env: {}

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "samosachaat-monorepo",
"version": "0.1.0",
"private": true,
"description": "Root tooling for the samosaChaat monorepo (commitlint + husky).",
"scripts": {
"prepare": "husky install",
"commitlint": "commitlint --edit"
},
"devDependencies": {
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"husky": "^9.1.6"
}
}