Skip to content

Feature/infra changes - #246

Open
MauroCominotti wants to merge 34 commits into
developfrom
feature/infra-changes
Open

Feature/infra changes#246
MauroCominotti wants to merge 34 commits into
developfrom
feature/infra-changes

Conversation

@MauroCominotti

Copy link
Copy Markdown
Collaborator

Fixes #<issue_number_goes_here>

It's a good idea to open an issue first for discussion.

  • Tests pass
  • Appropriate changes to documentation are included in the PR

MauroCominotti and others added 13 commits April 8, 2026 16:11
Gallery overhaul and media tagging system
chore: update workspace membership check in brand guideline service
docs: add comprehensive development guide
Implement admin dashboard with backend services and UI components, fix reference images on media detail page
…cc-creative-studio into feature/infra-changes
…Izumi Agent deployment into bootstrap script

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This Pull Request introduces significant, high-quality infrastructure changes, transitioning Creative Studio to a secure private network with a Private PostgreSQL instance, Cloud Armor WAF protection, and an automated Izumi agent deployment pipeline. The overall organization of Terraform modules and scripting is excellent and aligns with enterprise-grade deployment practices. However, several critical issues must be resolved before merging, including invalid probe endpoints, a non-existent database version, a broken SQL CLI command in the migration script, and a missing utility installation in the release workflow.

🔍 General Feedback

  • Excellent Security Design: Locking the database to a private VPC and enforcing Direct VPC egress for Cloud Run is a strong best practice.
  • Great Deployment Scripting: The addition of deploy.sh makes the bootstrapping and configuration process highly intuitive and seamless.
  • Dynamic Credentials: Great use of Secret Manager and ephemeral secrets in Terraform to keep sensitive passwords out of the state file.
  • Documentation Note: Clean up legacy file references in comment sections (such as platform.tf references) to keep the repository maintainable.

Comment thread .github/workflows/release.yml
Comment thread infrastructure/main.tf Outdated
Comment thread infrastructure/modules/database/variables.tf
Comment thread infrastructure/modules/gateway/main.tf Outdated
Comment thread infrastructure/modules/compute/main.tf
Comment thread infrastructure/migration/migrate_to_private_db.sh Outdated
Comment thread infrastructure/migration/migrate_to_private_db.sh
Comment thread bootstrap.sh Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This pull request introduces significant infrastructure enhancements to Google Cloud Creative Studio, including modularizing the Terraform setup under a new infrastructure directory, adding Private VPC and Private Services Access configurations, and incorporating automated data migration and Izumi Agent deployment steps. The architectural enhancements follow enterprise security best practices by isolating database traffic and utilizing modern Terraform features.

🔍 General Feedback

  • Directory Reorganization: Moving/renaming infra to infrastructure is a positive step towards clean modularity, but ensure that all shell scripts referencing these directories have been updated completely to avoid broken paths.
  • Modern Terraform Features: The adoption of Terraform 1.11+ write-only attributes (password_wo) and ephemeral blocks is a great security choice that prevents plaintext password exposure in State files.
  • Enterprise Network Isolation: Transitioning Cloud Run to Direct VPC Egress and Cloud SQL to Private IP only significantly improves the security profile of the application.

Comment thread .github/workflows/release.yml
Comment thread bootstrap.sh
Comment thread infrastructure/migration/migrate_to_private_db.sh Outdated
Comment thread deploy.sh Outdated
Comment thread bootstrap.sh

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This pull request introduces significant infrastructure enhancements, renaming the Terraform workspace from infra to infrastructure, modernizing the GCP resources with private network isolation (Cloud SQL Private IP and Direct VPC Egress for Cloud Run), and adding automated deployment pipelines. While the architectural improvements are excellent and security-focused, several critical regression blockers and syntax mismatches must be addressed before merging.

🔍 General Feedback

  • Pre-commit Build Regression (Critical): In infrastructure/pre-commit/Dockerfile.pre-commit (which was renamed with 100% similarity), the file still attempts to copy the entrypoint script using the old path COPY infra/pre-commit/entrypoint.sh /entrypoint.sh. Since that folder has been renamed, building the pre-commit environment will fail with a file-not-found error. Please update this to use the new path.
  • Bootstrapping Mismatches: With the flattening of the Terraform workspace structure directly under infrastructure/, the bootstrap.sh script continues to reference deleted environments templates and subdirectories. This completely breaks the setup workflow for new environments.
  • Provider & Version Sanity: The database version (POSTGRES_18) is non-existent in GCP Cloud SQL, and the CPU capacity format (1000m) is invalid for Cloud Run v2 (which rejects Kubernetes millicore syntax). Correcting these defaults prevents runtime provisioning failures.

Comment thread .github/workflows/release.yml Outdated
Comment thread deploy.sh Outdated
Comment thread infrastructure/hosting.tf
Comment thread infrastructure/modules/compute/variables.tf Outdated
Comment thread infrastructure/modules/database/variables.tf
Comment thread bootstrap.sh Outdated
…e connections, and update vertex AI agent configuration

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This pull request transitions the Google Cloud Creative Studio deployment pattern to use a secure, private VPC network with Private Services Access for PostgreSQL database connections. This transition significantly enhances database resilience and aligns with enterprise-level Google Cloud architectural best practices. The updated infrastructure modules, backend database helper updates, and deployment scripts are exceptionally cohesive, robust, and well-designed.

🔍 General Feedback

  • State-of-the-Art Security: The integration of Terraform v1.11+ write-only attributes (password_wo in google_sql_user and secret_data_wo in google_secret_manager_secret_version) paired with ephemeral resources is an outstanding, highly secure practice that ensures sensitive plaintext passwords never touch the persistent .tfstate files.
  • VPC Boundary Security: Moving database connectivity to a private network using serverless NEGs, Direct VPC egress on Cloud Run, and Cloud Armor with OWASP rules is highly professional and enforces correct network isolation.
  • Excellent Migration Tooling: Providing automated schema migration checks and data transfer scripts (migrate_to_private_db.sh) prevents data loss when upgrading existing deployments.
  • Proper Test Coverage: Unit and integration tests are properly updated to mock the new IPTypes.PRIVATE connection configurations and Vertex AI reasoning engine initialization flows.

Comment thread infrastructure/locals.tf
Comment thread infrastructure/modules/database/main.tf
Comment thread backend/src/agents/agent_service.py

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This pull request introduces significant architectural upgrades to transition the Google Cloud Creative Studio database to a Private VPC connection and reorganize the Terraform infrastructure structure into modular components. While the overall goal and direction are excellent for enterprise security and maintainability, there are several critical configuration mismatches and pathing issues that would prevent successful deployment or execution.

🔍 General Feedback

  • Terraform Provider Mismatch (🔴 Critical): The infrastructure/main.tf defines provider constraints of ~> 6.50.0, but employs new Google provider features (password_wo and secret_data_wo) that require 7.0.0 or later, which will cause deployment validation failures.
  • Duplicate Environment Variables (🔴 Critical): In infrastructure/app.tf, AGENT_ENGINE_RESOURCE_NAME and AGENT_ENGINE_USER_AUTH_TOKEN_KEY are defined twice (both as plaintext env vars and as Secret Manager secrets), leading to a 400 Bad Request error from the Cloud Run v2 API.
  • Incorrect Pathing in Rename (🔴 Critical): The pre-commit Dockerfile (infrastructure/pre-commit/Dockerfile.pre-commit line 23) still attempts to copy infra/pre-commit/entrypoint.sh from the old path instead of the renamed infrastructure/pre-commit/entrypoint.sh path, which will fail during docker image builds.
  • AI Agent Service Account Permissions (🟠 High): The dedicated service account for AI Agents (agent_sa) is provisioned but lacks project-level IAM roles (such as roles/aiplatform.user), which will cause Vertex AI Gemini model invocations by the reasoning engine to fail with 403 Permission Denied errors.
  • Sensitive Data Lifecycle (🟠 High): The database migration script migrate_to_private_db.sh leaves the decrypted SQL database dump file in the GCS bucket indefinitely after successful import, presenting a major security risk for sensitive database content.

Comment thread infrastructure/main.tf Outdated
Comment thread infrastructure/app.tf
Comment thread infrastructure/modules/compute/main.tf
Comment thread infrastructure/migration/migrate_to_private_db.sh
Comment thread .github/workflows/release.yml
…gration cleanup, and remove unused agent configuration

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This Pull Request introduces significant infrastructure updates to establish private, secure database connections for the Google Cloud Creative Studio Platform, migrating from public Cloud SQL access to Private Services Access (VPC Peering) and Serverless Direct VPC Egress for Cloud Run. While the architectural transition is highly positive for enterprise security, there are several critical deployment and integration bugs—specifically related to missing GCP service API enablements and randomized authentication variables—that must be resolved before this PR can be safely merged.

🔍 General Feedback

  • Positive Highlight on Data Migration: The database migration script (infrastructure/migration/migrate_to_private_db.sh) is exceptionally well designed. It employs robust retry loops, manages IAM policy bindings dynamically for export/import, and utilizes shell traps to guarantee clean-up of temporary Cloud Storage assets and permissions.
  • Improved Git Hygiene: Properly ignoring public/assets/config.json while introducing the .example template is an excellent standard practice that prevents developers from accidentally committing local config profiles.
  • Consistent Testing: Solid updates to the unit tests in backend/tests/test_database.py and backend/tests/agents/test_agent_service.py ensure that private IP routing and agent initialization are continuously verified going forward.

Comment thread bootstrap.sh Outdated
Comment thread deploy.sh Outdated
Comment thread infrastructure/variables.tf
Comment thread deploy.sh Outdated
Comment thread bootstrap.sh Outdated
Comment thread backend/alembic/env.py

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

The Pull Request implements crucial architectural improvements by modernizing and flattening the infrastructure setup, introducing Private Services Access for PostgreSQL database connections, and updating the database lifecycle cleanup. The separation of environment-specific states using a GCS backend config pattern and the use of ephemeral resources in Terraform represent outstanding Cloud architecture practices. However, critical logic errors in bootstrap.sh (such as unassigned variables and an empty .tfvars file creation) currently block successful execution and need to be resolved before merging.

🔍 General Feedback

  • Great job on transitioning the database connection to Private Services Access for enhanced VPC security. This aligns perfectly with enterprise-grade GCP landing zone patterns.
  • Excellent usage of modern Terraform features (like ephemeral resources and write-only properties) to ensure database credentials never leak into state files.
  • Excellent addition of the database connector cleanup process in the FastAPI lifespan shutdown handler to prevent resource leaks during container recycling.
  • Please address the critical bugs identified in the bootstrap.sh script to ensure developers can successfully deploy the environment end-to-end.

Comment thread infrastructure/modules/artifact/main.tf
Comment thread infrastructure/hosting.tf
Comment thread backend/src/agents/agent_service.py
Comment thread bootstrap.sh
Comment thread bootstrap.sh Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This pull request introduces critical infrastructure and deployment enhancements, transitioning to secure private Cloud SQL connectivity via Direct VPC egress, and a robust offline test suite for the bootstrapper script. The overall architectural design is exceptional, utilizing dynamic secret mapping, VPC peering, and secure parameter state persistence to achieve production-grade isolation. However, several critical version constraints and invalid argument mismatches in the Terraform modules will cause execution to fail immediately on initialization and must be corrected before merge.

🔍 General Feedback

  • Dynamic Orchestration: The enhancements to bootstrap.sh to support automated, idempotent, profile-driven executions are extremely well implemented and handle resuming from late-stage steps cleanly.
  • VPC & Network Isolation: Transitioning the application components (FastAPI Backend, Cloud SQL, and migrations) to a private IP model represents a significant security improvement.
  • Code Quality & Verification: The addition of test_bootstrap_offline.sh is an excellent initiative to ensure the complex initialization logic is completely testable and verified offline.
  • Provider Consistency: Ensure that provider version requirements set in root and child files remain fully synchronized with the .terraform.lock.hcl and environment constraints.

Comment thread infrastructure/main.tf
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
Comment thread infrastructure/modules/database/main.tf Outdated
Comment thread infrastructure/modules/database/main.tf Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This Pull Request successfully transitions the Creative Studio platform to a secure, private infrastructure footprint on Google Cloud. It shifts the Cloud SQL connectivity from public endpoints to a Private Service Access peering setup and introduces robust multi-profile state management for bootstrapping. The changes in both the backend application code and the deployment scripts are exceptionally clean, well-tested, and of high professional quality.

🔍 General Feedback

  • Excellent modularization of the Terraform infrastructure files by transitioning from the legacy infra/ directory to a clean, flattened infrastructure/ layout with dedicated modules.
  • Strong introduction of automated offline unit testing in test_bootstrap_offline.sh, validating setup robustness against multiple configurations and non-interactive profiles without requiring actual cloud resources.
  • High consistency and alignment with security and cloud-native best practices across the application, deployment automation, and CI/CD pipelines.

vertexai.init(
project=config_service.PROJECT_ID,
location=config_service.WORKFLOWS_LOCATION,
location=config_service.AGENT_LOCATION,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Repeatedly calling `vertexai.init(...)` inside `_get_remote_agent()` (which is called on each chat request or agent invocation) introduces unnecessary performance overhead and runtime warnings. Since the Vertex AI SDK is already initialized at the module level (on line 43), the entire `vertexai.init(...)` block inside this method is redundant and can be safely removed.

Comment thread backend/src/database.py
password=config_service.DB_PASS,
db=config_service.DB_NAME,
ip_type=IPTypes.PUBLIC, # Adjust if using Private IP
ip_type=IPTypes.PRIVATE, # Default to Private IP for secure VPC connection

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 **Event Loop Robustness in Singleton `DatabaseConnector`**

With the migration to IPTypes.PRIVATE as the default for secure VPC connections, the singleton DatabaseConnector is more heavily relied upon.

To prevent ConnectorLoopError in multi-loop environments (e.g., during parallel unit tests or background celery/worker tasks), the singleton's get_connector should track the active event loop and re-initialize the connector if the loop changes.

Consider updating the DatabaseConnector class (around lines 75-100) to:

class DatabaseConnector:
    _instance = None
    _connector: Connector | None = None
    _loop: asyncio.AbstractEventLoop | None = None

    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def get_connector(self) -> Connector:
        current_loop = asyncio.get_running_loop()
        if self._connector is None or self._loop != current_loop:
            self._connector = Connector(loop=current_loop)
            self._loop = current_loop
        return self._connector

Comment thread backend/main.py
try:
from src.database import cleanup_connector

await cleanup_connector()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 **Dispose of global SQLAlchemy Engine on Shutdown**

While cleaning up the Google Cloud SQL connector is crucial, it is also highly recommended to explicitly dispose of the global SQLAlchemy engine during the application lifespan shutdown. This ensures that any remaining pooled database connections are gracefully closed and database resources are freed immediately.

You can update cleanup_connector in backend/src/database.py to also dispose of the engine:

async def cleanup_connector():
    """Closes the Connector and disposes the engine to release resources."""
    if "engine" in globals() and engine is not None:
        await engine.dispose()
    await DatabaseConnector.get_instance().cleanup()

Comment thread bootstrap.sh
if [ -n "$AUTO_FIREBASE_SITE_ID" ] && [ "$AUTO_FIREBASE_SITE_ID" != "null" ] && [ "$AUTO_FIREBASE_SITE_ID" != "unassigned" ]; then
info "Using confirmed Firebase Hosting Site ID from profile: ${C_YELLOW}${AUTO_FIREBASE_SITE_ID}${C_RESET}"
else
local default_site_name=$( (firebase hosting:sites:list --project "$GCP_PROJECT_ID" --json 2>/dev/null || echo "{}") | jq -r 'try ((.result.sites // []) | (map(select(.type == "DEFAULT_SITE"))[0].name // .[0].name // "")) catch ""' 2>/dev/null || echo "" )

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 **Timing Robustness for Firebase Web App Creation & SDK Config Fetching**

When creating a new Firebase web app via firebase apps:create WEB, propagation through the Firebase API can sometimes take a few seconds. Currently, the script immediately attempts to fetch the APP_ID (line 447) and SDK_CONFIG_JSON (line 448). If the app has not fully propagated, these calls can return empty, causing the script to fail with:
Could not automatically fetch Firebase API Key. Please check your Firebase setup. on line 458.

Recommendation: Consider introducing a brief retry loop (e.g., up to 3 attempts with a 2-second sleep) when resolving APP_ID to make the script significantly more robust against timing/API latency issues.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This pull request introduces critical modernizations to the infrastructure and deployment automation of Creative Studio. The updates include transitioning Cloud SQL to a secure Private VPC, implementing highly professional offline bootstrap tests, and streamlining the database seeding process via containerized Cloud Run Jobs. The overall code quality, test coverage, and documentation improvements are excellent and represent a major architectural step forward.

🔍 General Feedback

  • Exceptional Testing Rigor: The new automated offline unit test suite (test_bootstrap_offline.sh) is exceptionally comprehensive, incorporating robust mock cli implementations and cleanly verifying multiple deployment edge cases.
  • Modern Terraform Features: The usage of Terraform 1.10+ ephemeral resources and write-only properties is highly professional and ensures database credentials are never stored in plaintext state files.
  • Secure Network Architecture: Migrating the PostgreSQL database to a Private VPC and utilizing Direct VPC Egress for Cloud Run represents a solid enterprise security enhancement.
  • Clean DB Seeding: Implementing serverless Cloud Run Jobs inside the VPC boundary for database migrations and seeding cleanly resolves security and access boundary issues.
  • Minor Docker and CI/CD Cleanup: Please review the inline suggestions regarding regional parameter overrides in the backend trigger, missing frontend compilation parameters, and uppercase Docker tag requirements in the GitHub release workflow.

Comment thread infrastructure/ci_cd.tf
Comment on lines +56 to +58
filename = "frontend/cloudbuild-deploy.yaml"
substitutions = {
_GCP_PROJECT_ID = var.project_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The frontend deployment build file (`frontend/cloudbuild-deploy.yaml`) expects multiple substitutions, such as `_BACKEND_URL`, `_FE_SERVICE_NAME`, `_BACKEND_SERVICE_ID`, and `_FIREBASE_SITE_ID`, to dynamically inject environment configuration during compilation. These variables are completely missing from the Cloud Build trigger configuration in Terraform, which will cause frontend deployment builds to fail or build with empty placeholders.
Suggested change
filename = "frontend/cloudbuild-deploy.yaml"
substitutions = {
_GCP_PROJECT_ID = var.project_id
substitutions = {
_GCP_PROJECT_ID = var.project_id
_BACKEND_URL = module.compute.service_uri
_FE_SERVICE_NAME = "${var.resource_prefix}-${var.environment}-frontend"
_BACKEND_SERVICE_ID = module.compute.service_name
_FIREBASE_SITE_ID = var.firebase_site_id
}

Comment thread infrastructure/ci_cd.tf
Comment on lines +75 to +81
service_account = google_service_account.trigger_sa.id
filename = "backend/cloudbuild.yaml"
substitutions = {
_GCP_PROJECT_ID = var.project_id
_GCP_REGION = var.region
_REPO_NAME = module.artifact.repository_name
_SERVICE_NAME = module.compute.service_name

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 In `backend/cloudbuild.yaml`, the region parameter is referenced as `_REGION` (e.g., `${_REGION}-docker.pkg.dev/...`), which defaults to `us-central1`. However, in `infrastructure/ci_cd.tf`'s `backend_trigger`, you have provided `_GCP_REGION = var.region`. Since `_REGION` is not overridden in the substitutions, any deployment to a non-default region will fail to push or pull images to/from the correct regional registry. Adding `_REGION = var.region` to the substitutions ensures consistent multi-region support.
Suggested change
service_account = google_service_account.trigger_sa.id
filename = "backend/cloudbuild.yaml"
substitutions = {
_GCP_PROJECT_ID = var.project_id
_GCP_REGION = var.region
_REPO_NAME = module.artifact.repository_name
_SERVICE_NAME = module.compute.service_name
substitutions = {
_GCP_PROJECT_ID = var.project_id
_GCP_REGION = var.region
_REGION = var.region
_REPO_NAME = module.artifact.repository_name
_SERVICE_NAME = module.compute.service_name
_DOCKER_IMAGE_URL = "${var.region}-docker.pkg.dev/${var.project_id}/${module.artifact.repository_name}/${var.backend_image_name}"
}

Comment on lines +43 to +64
- name: Build and push Backend Image
id: build-push
uses: docker/build-push-action@v6
with:
context: ./backend
push: true
# Tags both 'latest' and the specific release version (e.g., v1.0.0)
tags: |
${{ env.REGISTRY }}/${{ github.repository }}/backend:latest
${{ env.REGISTRY }}/${{ github.repository }}/backend:${{ github.event.release.tag_name || github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Install Cosign
uses: sigstore/cosign-installer@v3.5.0

- name: Sign Backend Image
run: |
cosign sign --yes "${{ env.REGISTRY }}/${{ github.repository }}/backend@${{ steps.build-push.outputs.digest }}"

# --- JOB 2: FRONTEND (ZIP Archive for Release Assets) ---
build-frontend:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Docker repository and image names must be strictly lowercase. Since the GitHub repository name (`GoogleCloudPlatform/gcc-creative-studio`) contains uppercase letters, using `${{ github.repository }}` directly will cause the build and push steps to fail with `invalid reference format: repository name must be lowercase`.

To fix this, we can introduce a step to lowercase the repository name and store it in an environment variable (REPO_LOWER), then reference ${{ env.REPO_LOWER }} in the build, tag, and sign steps.

Suggested change
- name: Build and push Backend Image
id: build-push
uses: docker/build-push-action@v6
with:
context: ./backend
push: true
# Tags both 'latest' and the specific release version (e.g., v1.0.0)
tags: |
${{ env.REGISTRY }}/${{ github.repository }}/backend:latest
${{ env.REGISTRY }}/${{ github.repository }}/backend:${{ github.event.release.tag_name || github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Install Cosign
uses: sigstore/cosign-installer@v3.5.0
- name: Sign Backend Image
run: |
cosign sign --yes "${{ env.REGISTRY }}/${{ github.repository }}/backend@${{ steps.build-push.outputs.digest }}"
# --- JOB 2: FRONTEND (ZIP Archive for Release Assets) ---
build-frontend:
- name: Lowercase repository name
id: lowercase-repo
run: |
echo "REPO_LOWER=${GITHUB_REPOSITORY,,}" >> ${GITHUB_ENV}
- name: Build and push Backend Image
id: build-push
uses: docker/build-push-action@v6
with:
context: ./backend
push: true
# Tags both 'latest' and the specific release version (e.g., v1.0.0)
tags: |
${{ env.REGISTRY }}/${{ env.REPO_LOWER }}/backend:latest
${{ env.REGISTRY }}/${{ env.REPO_LOWER }}/backend:${{ github.event.release.tag_name || github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Install Cosign
uses: sigstore/cosign-installer@v3.5.0
- name: Sign Backend Image
run: |
cosign sign --yes "${{ env.REGISTRY }}/${{ env.REPO_LOWER }}/backend@${{ steps.build-push.outputs.digest }}"

Comment on lines 43 to 48
# Initialize Vertex AI SDK
vertexai.init(
project=config_service.PROJECT_ID,
location=config_service.WORKFLOWS_LOCATION,
location=config_service.AGENT_LOCATION,
api_transport="grpc", # Options: "grpc" or "rest"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Initializing the Vertex AI SDK (`vertexai.init`) at the module/import level is a minor design issue / anti-pattern. It can make module imports fragile if the environment variables or configurations are not fully set or validated yet during application startup. It also complicates unit testing by requiring module-level mocking.

Since vertexai.init is already explicitly called when retrieving the remote agent inside the _get_remote_agent method, this module-level initialization is redundant and can be safely removed.

Suggested change
# Initialize Vertex AI SDK
vertexai.init(
project=config_service.PROJECT_ID,
location=config_service.WORKFLOWS_LOCATION,
location=config_service.AGENT_LOCATION,
api_transport="grpc", # Options: "grpc" or "rest"
)
# Vertex AI SDK is initialized lazily when constructing or retrieving the remote agent

…remove sequential step tracking in bootstrap script
…nt secret access, and update bootstrap environment variables

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This pull request introduces a highly robust, secure, and production-ready infrastructure refactoring, transitioning database access to a Private IP VPC-peered Cloud SQL PostgreSQL deployment. It also implements an automated database migration and seeding workflow using Google Cloud Run Jobs, alongside modular Terraform configurations that establish excellent architectural guardrails. While the structural quality and security postures are exceptional, a few critical errors—including unrecognized/hallucinated Terraform arguments, password generation characters, and local Unix socket parsing mismatches—must be resolved before merging to prevent direct provisioning and bootstrap execution failures.

🔍 General Feedback

  • Exceptional Security and Isolation Design: Moving the database to a private-IP-only instance within a custom VPC is an outstanding enterprise-grade security enhancement.
  • Robust Peering Guardrails: The introduction of the peering_completed output dependency block to serialize Private Services Access network peering propagation before database creation is an excellent, production-grade fix for classic GCP deployment race conditions.
  • Clever Cloud Run Secret Version Management: Creating app secret shells with dummy placeholder versions inside Terraform, ignoring changes on secret_data, and then populating actual secrets cleanly via bootstrap.sh is an extremely smart and secure pattern that keeps plaintext secrets out of state files.
  • Unused API Activation: The API list (apis_to_enable in variables.tf) includes drive.googleapis.com (Google Drive), but a search of the backend codebase indicates it is not used. We suggest removing it to minimize the project's default API enablement footprint.
  • Empty Locals File: The file infrastructure/locals.tf only contains a Google Project data source declaration and no actual local values. Consider consolidating this or renaming it appropriately.

Comment on lines +26 to +33
resource "google_secret_manager_secret_version" "db_secret_version" {
secret = google_secret_manager_secret.db_secret.id

# Using a write-only argument prevents the password
# from being captured in the terraform.tfstate file.
secret_data_wo = ephemeral.random_password.db_pass.result
secret_data_wo_version = var.db_password_version
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Invalid write-only arguments `secret_data_wo` and `secret_data_wo_version` are used on the `google_secret_manager_secret_version` resource. These do not exist in the HashiCorp Google provider. The correct argument to write secret data is `secret_data`. Since `db_pass` is an ephemeral resource, assigning its value directly to `secret_data` keeps the password secure and prevents it from being stored in the state file.
Suggested change
resource "google_secret_manager_secret_version" "db_secret_version" {
secret = google_secret_manager_secret.db_secret.id
# Using a write-only argument prevents the password
# from being captured in the terraform.tfstate file.
secret_data_wo = ephemeral.random_password.db_pass.result
secret_data_wo_version = var.db_password_version
}
resource "google_secret_manager_secret_version" "db_secret_version" {
secret = google_secret_manager_secret.db_secret.id
secret_data = ephemeral.random_password.db_pass.result
}

Comment on lines +91 to +100
resource "google_sql_user" "app_user" {
name = var.db_user
instance = google_sql_database_instance.default.name
project = var.project_id

# We read the ephemeral value while creating the DB user,
# keeping the DB state clean of plaintext passwords.
password_wo = ephemeral.random_password.db_pass.result
password_wo_version = var.db_password_version
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Invalid write-only arguments `password_wo` and `password_wo_version` are used on the `google_sql_user` resource. These do not exist in the HashiCorp Google provider. The correct argument is `password`. Since `db_pass` is an ephemeral resource, assigning its value directly to `password` keeps the password secure and prevents it from being stored in the state file.
Suggested change
resource "google_sql_user" "app_user" {
name = var.db_user
instance = google_sql_database_instance.default.name
project = var.project_id
# We read the ephemeral value while creating the DB user,
# keeping the DB state clean of plaintext passwords.
password_wo = ephemeral.random_password.db_pass.result
password_wo_version = var.db_password_version
}
resource "google_sql_user" "app_user" {
name = var.db_user
instance = google_sql_database_instance.default.name
project = var.project_id
password = ephemeral.random_password.db_pass.result
}

Comment thread infrastructure/modules/database/main.tf Outdated
Comment on lines +3 to +6
ephemeral "random_password" "db_pass" {
length = 24
special = true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Generating a random database password with `special = true` can result in characters like `@`, `/`, `:`, `?` or other special characters that will break standard PostgreSQL connection string parsing in Python (e.g., in `get_conn_string()` and Alembic migrations) unless explicitly URL-encoded. It is highly recommended to set `special = false` to avoid connection or parsing failures in your Python backend environment.
Suggested change
ephemeral "random_password" "db_pass" {
length = 24
special = true
}
ephemeral "random_password" "db_pass" {
length = 24
special = false
}

Comment thread bootstrap.sh
--subnet="$SUBNET_NAME" \
--command="python" \
--args="-m,bootstrap.bootstrap" \
--add-cloudsql-instances="$DB_CONN_NAME" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 The database seeding job is configured with `USE_CLOUD_SQL_AUTH_PROXY=true` and `DB_HOST=/cloudsql/${DB_CONN_NAME}`.

When USE_CLOUD_SQL_AUTH_PROXY is true, get_conn_string() in database.py constructs a TCP connection URL using DB_HOST as the hostname. Placing the Unix socket folder /cloudsql/... in the hostname field of a postgresql+asyncpg:// URL is invalid because SQLAlchemy's URL parser will treat /cloudsql/... as part of the database path, leaving the host as None (empty), which causes asyncpg to fall back to connecting to 127.0.0.1:5432 over TCP. Since there is no TCP proxy running inside the Cloud Run Job container, this job will fail immediately with Connection refused.

To fix this, align this configuration with the main Cloud Run backend service: set USE_CLOUD_SQL_AUTH_PROXY=false and completely omit the /cloudsql/ DB_HOST reference. The backend's Python database connector will then securely initialize the connection using the Cloud SQL Python Connector library via private IP within the VPC, exactly as it does in production.

Suggested change
--add-cloudsql-instances="$DB_CONN_NAME" \
--set-env-vars="INSTANCE_CONNECTION_NAME=${DB_CONN_NAME},DB_NAME=${DB_NAME},DB_USER=${DB_USER},USE_CLOUD_SQL_AUTH_PROXY=false,PROJECT_ID=${GCP_PROJECT_ID},GENMEDIA_BUCKET=${BUCKET_ASSETS},ADMIN_USER_EMAIL=${CURRENT_USER},ENVIRONMENT=development" \

Comment thread infrastructure/hosting.tf
Comment on lines +2 to +6
# Creates the Firebase Hosting site to deploy to
resource "google_firebase_hosting_site" "frontend" {
provider = google-beta
project = var.project_id
site_id = var.firebase_site_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can map the Custom Domain conditionally if provided, but the site site_id must always be a non-empty string. The variable `firebase_site_id` is defined with the description "If empty, defaults to the project ID.", but the resource sets `site_id = var.firebase_site_id` directly. If `firebase_site_id` is left as its default empty string (`""`), Terraform will attempt to create a resource with an empty `site_id`, which will result in an API validation error and block the deployment.

Please implement the conditional fallback directly in the resource configuration.

Suggested change
# Creates the Firebase Hosting site to deploy to
resource "google_firebase_hosting_site" "frontend" {
provider = google-beta
project = var.project_id
site_id = var.firebase_site_id
resource "google_firebase_hosting_site" "frontend" {
provider = google-beta
project = var.project_id
site_id = var.firebase_site_id != "" ? var.firebase_site_id : var.project_id
}

Comment thread infrastructure/ci_cd.tf
Comment on lines +31 to +35
resource "google_project_iam_member" "secret_accessor" {
project = var.project_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.trigger_sa.email}"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Granting the `roles/secretmanager.secretAccessor` role at the project level gives the Cloud Build trigger service account complete read access to ALL secrets in this Google Cloud project. To follow the principle of least privilege, we should restrict secret access only to the specific secrets that the build and deployment process actually require. This can be achieved by using `google_secret_manager_secret_iam_member` resources on individual secrets instead of a project-wide binding.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Review Summary

This Pull Request is of exceptionally high quality, implementing a clean and secure "Idempotent Shell" pattern for infrastructure deployment. By moving from public to private Google Cloud SQL and configuring Direct VPC routing and Private Services Access, the deployment achieves production-grade security defaults. The corresponding refactoring of bootstrap.sh to support automated CLI profiles, resumption states, and data migration is beautifully engineered and extremely robust.

🔍 General Feedback

  • Clean Architectural Alignment: Transitioning to a flattened infrastructure/ directory and utilizing environment-specific .tfvars file configurations significantly simplifies Terraform state management and variables mapping.
  • Robust Unit Testing: Introducing the test_bootstrap_offline.sh test suite inside Github Actions is an outstanding engineering practice that guarantees the stability of bash scripts.
  • Idiomatic Code & Resource Lifecycle Management: Adding Python-level connector cleanup to the FastAPI lifespan lifecycle in backend/main.py is an excellent touch for preventing resource leaks.
  • Secure Default Configurations: Random password generation through Terraform and direct, automated injection into Secret Manager guarantees zero credential leakage.

Comment thread bootstrap.sh
step 12 "Updating Remaining Secrets"; info "Navigating to $REPO_ROOT/infrastructure..."; cd "$REPO_ROOT/infrastructure"
info "Populating values in Secret Manager..."; local TERRAFORM_OUTPUTS=$(terraform output -json 2>/dev/null || echo "{}")
local FRONTEND_SECRETS=$( (echo "$TERRAFORM_OUTPUTS" 2>/dev/null || echo "{}") | jq -r 'try (.frontend_secrets.value[]?) catch ""' 2>/dev/null || echo "" ); local BACKEND_SECRETS=$( (echo "$TERRAFORM_OUTPUTS" 2>/dev/null || echo "{}") | jq -r 'try (.backend_secrets.value[]?) catch ""' 2>/dev/null || echo "" )
local ALL_SECRETS=$(echo "${FRONTEND_SECRETS} ${BACKEND_SECRETS}" | tr ' ' '\n' | sort -u | grep .)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Potential script crash due to set -e when grep finds no matches

In bootstrap.sh, set -e is enabled globally. When FRONTEND_SECRETS and BACKEND_SECRETS are both empty, the pipeline grep . returns a non-zero exit status (exit code 1). Since grep is the final command in the pipeline and pipefail is not set, the pipeline itself exits with 1, which causes the entire script to terminate immediately at this assignment line.

The subsequent check if [ -z "$ALL_SECRETS" ]; ... is never reached because the script has already aborted.

Appending || true to the grep . command (or using grep . || :) ensures that the assignment is safe under set -e and empty input can be handled gracefully by the subsequent conditional check.

Suggested change
local ALL_SECRETS=$(echo "${FRONTEND_SECRETS} ${BACKEND_SECRETS}" | tr ' ' '\n' | sort -u | grep .)
local ALL_SECRETS=$(echo "${FRONTEND_SECRETS} ${BACKEND_SECRETS}" | tr ' ' '\n' | sort -u | grep . || true)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants