feat(backend): add isolated v2 foundation stack and query service scaffold
This commit is contained in:
@@ -36,7 +36,41 @@ BACKEND_VERIFICATION_PROVIDER_TIMEOUT_MS ?= 8000
|
||||
BACKEND_MAX_SIGNED_URL_SECONDS ?= 900
|
||||
BACKEND_LLM_RATE_LIMIT_PER_MINUTE ?= 20
|
||||
|
||||
.PHONY: backend-help backend-enable-apis backend-bootstrap-dev backend-migrate-idempotency backend-deploy-core backend-deploy-commands backend-deploy-workers backend-smoke-core backend-smoke-commands backend-logs-core
|
||||
BACKEND_V2_ARTIFACT_REPO ?= krow-backend-v2
|
||||
BACKEND_V2_CORE_SERVICE_NAME ?= krow-core-api-v2
|
||||
BACKEND_V2_COMMAND_SERVICE_NAME ?= krow-command-api-v2
|
||||
BACKEND_V2_QUERY_SERVICE_NAME ?= krow-query-api-v2
|
||||
BACKEND_V2_RUNTIME_SA_NAME ?= krow-backend-v2-runtime
|
||||
BACKEND_V2_RUNTIME_SA_EMAIL := $(BACKEND_V2_RUNTIME_SA_NAME)@$(GCP_PROJECT_ID).iam.gserviceaccount.com
|
||||
|
||||
BACKEND_V2_CORE_DIR ?= backend/core-api
|
||||
BACKEND_V2_COMMAND_DIR ?= backend/command-api
|
||||
BACKEND_V2_QUERY_DIR ?= backend/query-api
|
||||
|
||||
BACKEND_V2_SQL_INSTANCE ?= krow-sql-v2
|
||||
BACKEND_V2_SQL_DATABASE ?= krow_v2_db
|
||||
BACKEND_V2_SQL_TIER ?= $(SQL_TIER)
|
||||
|
||||
BACKEND_V2_DEV_PUBLIC_BUCKET ?= krow-workforce-dev-v2-public
|
||||
BACKEND_V2_DEV_PRIVATE_BUCKET ?= krow-workforce-dev-v2-private
|
||||
BACKEND_V2_STAGING_PUBLIC_BUCKET ?= krow-workforce-staging-v2-public
|
||||
BACKEND_V2_STAGING_PRIVATE_BUCKET ?= krow-workforce-staging-v2-private
|
||||
|
||||
ifeq ($(ENV),staging)
|
||||
BACKEND_V2_PUBLIC_BUCKET := $(BACKEND_V2_STAGING_PUBLIC_BUCKET)
|
||||
BACKEND_V2_PRIVATE_BUCKET := $(BACKEND_V2_STAGING_PRIVATE_BUCKET)
|
||||
BACKEND_V2_RUN_AUTH_FLAG := --no-allow-unauthenticated
|
||||
else
|
||||
BACKEND_V2_PUBLIC_BUCKET := $(BACKEND_V2_DEV_PUBLIC_BUCKET)
|
||||
BACKEND_V2_PRIVATE_BUCKET := $(BACKEND_V2_DEV_PRIVATE_BUCKET)
|
||||
BACKEND_V2_RUN_AUTH_FLAG := --allow-unauthenticated
|
||||
endif
|
||||
|
||||
BACKEND_V2_CORE_IMAGE ?= $(BACKEND_REGION)-docker.pkg.dev/$(GCP_PROJECT_ID)/$(BACKEND_V2_ARTIFACT_REPO)/core-api-v2:latest
|
||||
BACKEND_V2_COMMAND_IMAGE ?= $(BACKEND_REGION)-docker.pkg.dev/$(GCP_PROJECT_ID)/$(BACKEND_V2_ARTIFACT_REPO)/command-api-v2:latest
|
||||
BACKEND_V2_QUERY_IMAGE ?= $(BACKEND_REGION)-docker.pkg.dev/$(GCP_PROJECT_ID)/$(BACKEND_V2_ARTIFACT_REPO)/query-api-v2:latest
|
||||
|
||||
.PHONY: backend-help backend-enable-apis backend-bootstrap-dev backend-migrate-idempotency backend-deploy-core backend-deploy-commands backend-deploy-workers backend-smoke-core backend-smoke-commands backend-logs-core backend-bootstrap-v2-dev backend-deploy-core-v2 backend-deploy-commands-v2 backend-deploy-query-v2 backend-smoke-core-v2 backend-smoke-commands-v2 backend-smoke-query-v2 backend-logs-core-v2 backend-v2-migrate-idempotency
|
||||
|
||||
backend-help:
|
||||
@echo "--> Backend Foundation Commands"
|
||||
@@ -49,6 +83,17 @@ backend-help:
|
||||
@echo " make backend-smoke-core [ENV=dev] Smoke test core /health"
|
||||
@echo " make backend-smoke-commands [ENV=dev] Smoke test commands /health"
|
||||
@echo " make backend-logs-core [ENV=dev] Read core service logs"
|
||||
@echo ""
|
||||
@echo "--> Backend Foundation Commands (isolated v2 stack)"
|
||||
@echo " make backend-bootstrap-v2-dev [ENV=dev] Bootstrap isolated v2 resources and SQL instance"
|
||||
@echo " make backend-deploy-core-v2 [ENV=dev] Build + deploy core API v2 service"
|
||||
@echo " make backend-deploy-commands-v2 [ENV=dev] Build + deploy command API v2 service"
|
||||
@echo " make backend-deploy-query-v2 [ENV=dev] Build + deploy query API v2 scaffold service"
|
||||
@echo " make backend-v2-migrate-idempotency Apply command idempotency migration against v2 DB"
|
||||
@echo " make backend-smoke-core-v2 [ENV=dev] Smoke test core API v2 /health"
|
||||
@echo " make backend-smoke-commands-v2 [ENV=dev] Smoke test command API v2 /health"
|
||||
@echo " make backend-smoke-query-v2 [ENV=dev] Smoke test query API v2 /health"
|
||||
@echo " make backend-logs-core-v2 [ENV=dev] Read core API v2 logs"
|
||||
|
||||
backend-enable-apis:
|
||||
@echo "--> Enabling backend APIs on project [$(GCP_PROJECT_ID)]..."
|
||||
@@ -190,3 +235,167 @@ backend-logs-core:
|
||||
--region=$(BACKEND_REGION) \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--limit=$(BACKEND_LOG_LIMIT)
|
||||
|
||||
backend-bootstrap-v2-dev: backend-enable-apis
|
||||
@echo "--> Bootstrapping isolated backend v2 foundation for [$(ENV)] on project [$(GCP_PROJECT_ID)]..."
|
||||
@echo "--> Ensuring Artifact Registry repo [$(BACKEND_V2_ARTIFACT_REPO)] exists..."
|
||||
@if ! gcloud artifacts repositories describe $(BACKEND_V2_ARTIFACT_REPO) --location=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud artifacts repositories create $(BACKEND_V2_ARTIFACT_REPO) \
|
||||
--repository-format=docker \
|
||||
--location=$(BACKEND_REGION) \
|
||||
--description="KROW backend v2 services" \
|
||||
--project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Artifact Registry repo already exists."; \
|
||||
fi
|
||||
@echo "--> Ensuring v2 runtime service account [$(BACKEND_V2_RUNTIME_SA_NAME)] exists..."
|
||||
@if ! gcloud iam service-accounts describe $(BACKEND_V2_RUNTIME_SA_EMAIL) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud iam service-accounts create $(BACKEND_V2_RUNTIME_SA_NAME) \
|
||||
--display-name="KROW Backend Runtime V2" \
|
||||
--project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Runtime service account already exists."; \
|
||||
fi
|
||||
@echo "--> Ensuring v2 runtime service account IAM roles..."
|
||||
@gcloud projects add-iam-policy-binding $(GCP_PROJECT_ID) \
|
||||
--member="serviceAccount:$(BACKEND_V2_RUNTIME_SA_EMAIL)" \
|
||||
--role="roles/storage.objectAdmin" \
|
||||
--quiet >/dev/null
|
||||
@gcloud projects add-iam-policy-binding $(GCP_PROJECT_ID) \
|
||||
--member="serviceAccount:$(BACKEND_V2_RUNTIME_SA_EMAIL)" \
|
||||
--role="roles/aiplatform.user" \
|
||||
--quiet >/dev/null
|
||||
@gcloud projects add-iam-policy-binding $(GCP_PROJECT_ID) \
|
||||
--member="serviceAccount:$(BACKEND_V2_RUNTIME_SA_EMAIL)" \
|
||||
--role="roles/cloudsql.client" \
|
||||
--quiet >/dev/null
|
||||
@gcloud projects add-iam-policy-binding $(GCP_PROJECT_ID) \
|
||||
--member="serviceAccount:$(BACKEND_V2_RUNTIME_SA_EMAIL)" \
|
||||
--role="roles/secretmanager.secretAccessor" \
|
||||
--quiet >/dev/null
|
||||
@gcloud iam service-accounts add-iam-policy-binding $(BACKEND_V2_RUNTIME_SA_EMAIL) \
|
||||
--member="serviceAccount:$(BACKEND_V2_RUNTIME_SA_EMAIL)" \
|
||||
--role="roles/iam.serviceAccountTokenCreator" \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--quiet >/dev/null
|
||||
@echo "--> Ensuring v2 storage buckets exist..."
|
||||
@if ! gcloud storage buckets describe gs://$(BACKEND_V2_PUBLIC_BUCKET) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud storage buckets create gs://$(BACKEND_V2_PUBLIC_BUCKET) --location=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Public bucket already exists: $(BACKEND_V2_PUBLIC_BUCKET)"; \
|
||||
fi
|
||||
@if ! gcloud storage buckets describe gs://$(BACKEND_V2_PRIVATE_BUCKET) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud storage buckets create gs://$(BACKEND_V2_PRIVATE_BUCKET) --location=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Private bucket already exists: $(BACKEND_V2_PRIVATE_BUCKET)"; \
|
||||
fi
|
||||
@echo "--> Ensuring v2 Cloud SQL instance [$(BACKEND_V2_SQL_INSTANCE)] exists..."
|
||||
@if ! gcloud sql instances describe $(BACKEND_V2_SQL_INSTANCE) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud sql instances create $(BACKEND_V2_SQL_INSTANCE) \
|
||||
--database-version=POSTGRES_15 \
|
||||
--tier=$(BACKEND_V2_SQL_TIER) \
|
||||
--region=$(BACKEND_REGION) \
|
||||
--storage-size=10 \
|
||||
--storage-auto-increase \
|
||||
--availability-type=zonal \
|
||||
--backup-start-time=03:00 \
|
||||
--project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Cloud SQL instance already exists: $(BACKEND_V2_SQL_INSTANCE)"; \
|
||||
fi
|
||||
@echo "--> Ensuring v2 Cloud SQL database [$(BACKEND_V2_SQL_DATABASE)] exists..."
|
||||
@if ! gcloud sql databases describe $(BACKEND_V2_SQL_DATABASE) --instance=$(BACKEND_V2_SQL_INSTANCE) --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \
|
||||
gcloud sql databases create $(BACKEND_V2_SQL_DATABASE) --instance=$(BACKEND_V2_SQL_INSTANCE) --project=$(GCP_PROJECT_ID); \
|
||||
else \
|
||||
echo " - Cloud SQL database already exists: $(BACKEND_V2_SQL_DATABASE)"; \
|
||||
fi
|
||||
@echo "✅ Backend v2 foundation bootstrap complete for [$(ENV)]."
|
||||
|
||||
backend-deploy-core-v2:
|
||||
@echo "--> Deploying core backend v2 service [$(BACKEND_V2_CORE_SERVICE_NAME)] to [$(ENV)]..."
|
||||
@test -d $(BACKEND_V2_CORE_DIR) || (echo "❌ Missing directory: $(BACKEND_V2_CORE_DIR)" && exit 1)
|
||||
@test -f $(BACKEND_V2_CORE_DIR)/Dockerfile || (echo "❌ Missing Dockerfile: $(BACKEND_V2_CORE_DIR)/Dockerfile" && exit 1)
|
||||
@gcloud builds submit $(BACKEND_V2_CORE_DIR) --tag $(BACKEND_V2_CORE_IMAGE) --project=$(GCP_PROJECT_ID)
|
||||
@gcloud run deploy $(BACKEND_V2_CORE_SERVICE_NAME) \
|
||||
--image=$(BACKEND_V2_CORE_IMAGE) \
|
||||
--region=$(BACKEND_REGION) \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--service-account=$(BACKEND_V2_RUNTIME_SA_EMAIL) \
|
||||
--set-env-vars=APP_ENV=$(ENV),APP_STACK=v2,GCP_PROJECT_ID=$(GCP_PROJECT_ID),PUBLIC_BUCKET=$(BACKEND_V2_PUBLIC_BUCKET),PRIVATE_BUCKET=$(BACKEND_V2_PRIVATE_BUCKET),UPLOAD_MOCK=false,SIGNED_URL_MOCK=false,LLM_MOCK=false,LLM_LOCATION=$(BACKEND_REGION),LLM_MODEL=$(BACKEND_LLM_MODEL),LLM_TIMEOUT_MS=20000,MAX_SIGNED_URL_SECONDS=$(BACKEND_MAX_SIGNED_URL_SECONDS),LLM_RATE_LIMIT_PER_MINUTE=$(BACKEND_LLM_RATE_LIMIT_PER_MINUTE),VERIFICATION_ACCESS_MODE=authenticated,VERIFICATION_REQUIRE_FILE_EXISTS=true,VERIFICATION_ATTIRE_PROVIDER=vertex,VERIFICATION_ATTIRE_MODEL=$(BACKEND_VERIFICATION_ATTIRE_MODEL),VERIFICATION_PROVIDER_TIMEOUT_MS=$(BACKEND_VERIFICATION_PROVIDER_TIMEOUT_MS) \
|
||||
$(BACKEND_V2_RUN_AUTH_FLAG)
|
||||
@echo "✅ Core backend v2 service deployed."
|
||||
|
||||
backend-deploy-commands-v2:
|
||||
@echo "--> Deploying command backend v2 service [$(BACKEND_V2_COMMAND_SERVICE_NAME)] to [$(ENV)]..."
|
||||
@test -d $(BACKEND_V2_COMMAND_DIR) || (echo "❌ Missing directory: $(BACKEND_V2_COMMAND_DIR)" && exit 1)
|
||||
@test -f $(BACKEND_V2_COMMAND_DIR)/Dockerfile || (echo "❌ Missing Dockerfile: $(BACKEND_V2_COMMAND_DIR)/Dockerfile" && exit 1)
|
||||
@gcloud builds submit $(BACKEND_V2_COMMAND_DIR) --tag $(BACKEND_V2_COMMAND_IMAGE) --project=$(GCP_PROJECT_ID)
|
||||
@EXTRA_ENV="APP_ENV=$(ENV),APP_STACK=v2,GCP_PROJECT_ID=$(GCP_PROJECT_ID),PUBLIC_BUCKET=$(BACKEND_V2_PUBLIC_BUCKET),PRIVATE_BUCKET=$(BACKEND_V2_PRIVATE_BUCKET),IDEMPOTENCY_STORE=memory"; \
|
||||
if [ -n "$(IDEMPOTENCY_DATABASE_URL)" ]; then \
|
||||
EXTRA_ENV="APP_ENV=$(ENV),APP_STACK=v2,GCP_PROJECT_ID=$(GCP_PROJECT_ID),PUBLIC_BUCKET=$(BACKEND_V2_PUBLIC_BUCKET),PRIVATE_BUCKET=$(BACKEND_V2_PRIVATE_BUCKET),IDEMPOTENCY_STORE=sql,IDEMPOTENCY_DATABASE_URL=$(IDEMPOTENCY_DATABASE_URL)"; \
|
||||
fi; \
|
||||
gcloud run deploy $(BACKEND_V2_COMMAND_SERVICE_NAME) \
|
||||
--image=$(BACKEND_V2_COMMAND_IMAGE) \
|
||||
--region=$(BACKEND_REGION) \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--service-account=$(BACKEND_V2_RUNTIME_SA_EMAIL) \
|
||||
--set-env-vars=$$EXTRA_ENV \
|
||||
$(BACKEND_V2_RUN_AUTH_FLAG)
|
||||
@echo "✅ Command backend v2 service deployed."
|
||||
|
||||
backend-deploy-query-v2:
|
||||
@echo "--> Deploying query backend v2 service [$(BACKEND_V2_QUERY_SERVICE_NAME)] to [$(ENV)]..."
|
||||
@test -d $(BACKEND_V2_QUERY_DIR) || (echo "❌ Missing directory: $(BACKEND_V2_QUERY_DIR)" && exit 1)
|
||||
@test -f $(BACKEND_V2_QUERY_DIR)/Dockerfile || (echo "❌ Missing Dockerfile: $(BACKEND_V2_QUERY_DIR)/Dockerfile" && exit 1)
|
||||
@gcloud builds submit $(BACKEND_V2_QUERY_DIR) --tag $(BACKEND_V2_QUERY_IMAGE) --project=$(GCP_PROJECT_ID)
|
||||
@gcloud run deploy $(BACKEND_V2_QUERY_SERVICE_NAME) \
|
||||
--image=$(BACKEND_V2_QUERY_IMAGE) \
|
||||
--region=$(BACKEND_REGION) \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--service-account=$(BACKEND_V2_RUNTIME_SA_EMAIL) \
|
||||
--set-env-vars=APP_ENV=$(ENV),APP_STACK=v2,GCP_PROJECT_ID=$(GCP_PROJECT_ID) \
|
||||
$(BACKEND_V2_RUN_AUTH_FLAG)
|
||||
@echo "✅ Query backend v2 service deployed."
|
||||
|
||||
backend-v2-migrate-idempotency:
|
||||
@echo "--> Applying idempotency table migration for command API v2..."
|
||||
@test -n "$(IDEMPOTENCY_DATABASE_URL)" || (echo "❌ IDEMPOTENCY_DATABASE_URL is required" && exit 1)
|
||||
@cd $(BACKEND_V2_COMMAND_DIR) && IDEMPOTENCY_DATABASE_URL="$(IDEMPOTENCY_DATABASE_URL)" npm run migrate:idempotency
|
||||
@echo "✅ Idempotency migration applied for command API v2."
|
||||
|
||||
backend-smoke-core-v2:
|
||||
@echo "--> Running core v2 smoke check..."
|
||||
@URL=$$(gcloud run services describe $(BACKEND_V2_CORE_SERVICE_NAME) --region=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID) --format='value(status.url)'); \
|
||||
if [ -z "$$URL" ]; then \
|
||||
echo "❌ Could not resolve URL for service $(BACKEND_V2_CORE_SERVICE_NAME)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
TOKEN=$$(gcloud auth print-identity-token); \
|
||||
curl -fsS -H "Authorization: Bearer $$TOKEN" "$$URL/health" >/dev/null && echo "✅ Core v2 smoke check passed: $$URL/health"
|
||||
|
||||
backend-smoke-commands-v2:
|
||||
@echo "--> Running command v2 smoke check..."
|
||||
@URL=$$(gcloud run services describe $(BACKEND_V2_COMMAND_SERVICE_NAME) --region=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID) --format='value(status.url)'); \
|
||||
if [ -z "$$URL" ]; then \
|
||||
echo "❌ Could not resolve URL for service $(BACKEND_V2_COMMAND_SERVICE_NAME)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
TOKEN=$$(gcloud auth print-identity-token); \
|
||||
curl -fsS -H "Authorization: Bearer $$TOKEN" "$$URL/health" >/dev/null && echo "✅ Command v2 smoke check passed: $$URL/health"
|
||||
|
||||
backend-smoke-query-v2:
|
||||
@echo "--> Running query v2 smoke check..."
|
||||
@URL=$$(gcloud run services describe $(BACKEND_V2_QUERY_SERVICE_NAME) --region=$(BACKEND_REGION) --project=$(GCP_PROJECT_ID) --format='value(status.url)'); \
|
||||
if [ -z "$$URL" ]; then \
|
||||
echo "❌ Could not resolve URL for service $(BACKEND_V2_QUERY_SERVICE_NAME)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
TOKEN=$$(gcloud auth print-identity-token); \
|
||||
curl -fsS -H "Authorization: Bearer $$TOKEN" "$$URL/health" >/dev/null && echo "✅ Query v2 smoke check passed: $$URL/health"
|
||||
|
||||
backend-logs-core-v2:
|
||||
@echo "--> Reading logs for core backend v2 service [$(BACKEND_V2_CORE_SERVICE_NAME)]..."
|
||||
@gcloud run services logs read $(BACKEND_V2_CORE_SERVICE_NAME) \
|
||||
--region=$(BACKEND_REGION) \
|
||||
--project=$(GCP_PROJECT_ID) \
|
||||
--limit=$(BACKEND_LOG_LIMIT)
|
||||
|
||||
Reference in New Issue
Block a user