decomposer: generate deliverable files for Discover and structure the solution's functional requirements, non-functional requirements, constraints, assumptions, and open questions without selecting cloud products.; Select Google Cloud products from the confirmed requirements and produce the solution architecture, Mermaid diagram, architecture description, and Terraform infrastructure-as-code.; Validate the Terraform infrastructure and architecture artifacts without deploying resources by running formatting checks, Terraform validation, and a dry-run or plan-oriented deployment check.; Package the approved requirements, architecture, Mermaid diagram, Terraform IaC, and validation results into solution-architecture-guide.md in the gcp_solution_architecture_agent repository.; Verify that the gcp_solution_architecture_agent repository contains the packaged solution-architecture-guide.md with the approved workflow outputs.; Verify that the repository is derived from the workflow_agent template and implements the complete four-phase Google Cloud solution architecture workflow alongside the packaged guide.; Publish the verified gcp_solution_architecture_agent repository with its completed workflow implementation and solution architecture guide.; Verify that the published repository revision contains the completed workflow implementation and solution architecture guide.
Some checks failed
validation / verify (push) Failing after 13s
Some checks failed
validation / verify (push) Failing after 13s
This commit is contained in:
16
README.md
16
README.md
@@ -1,14 +1,22 @@
|
|||||||
# gcp_solution_architecture_agent
|
# gcp_solution_architecture_agent
|
||||||
|
|
||||||
A four-phase Google Cloud solution-architecture workflow: requirements discovery, product selection and design, pre-deployment validation, and packaging/publication. The repository is intentionally a reference implementation: Terraform is parameterized and does not provision resources during validation.
|
A four-phase Google Cloud solution-architecture workflow packaged as one reviewable repository.
|
||||||
|
|
||||||
## Quick start
|
## Workflow
|
||||||
|
1. **Discover** requirements without choosing products (`docs/requirements.md`).
|
||||||
|
2. **Design** the Google Cloud architecture and Terraform (`docs/architecture.md`, `architecture.mmd`, `terraform/`).
|
||||||
|
3. **Validate** artifacts without provisioning (`scripts/validate.sh`, `tests/`).
|
||||||
|
4. **Package** the approved result (`solution-architecture-guide.md`).
|
||||||
|
|
||||||
|
The repository is derived from the `workflow_agent` template and is intentionally safe to run without a cloud deployment. The Terraform plan requires credentials and a project only when a user chooses to run it.
|
||||||
|
|
||||||
|
## Commands
|
||||||
```bash
|
```bash
|
||||||
terraform -chdir=terraform fmt -check -recursive
|
terraform -chdir=terraform fmt -check -recursive
|
||||||
terraform -chdir=terraform init -backend=false
|
terraform -chdir=terraform init -backend=false
|
||||||
terraform -chdir=terraform validate
|
terraform -chdir=terraform validate
|
||||||
python3 scripts/validate_artifacts.py
|
python3 -m unittest discover -s tests -v
|
||||||
|
bash scripts/validate.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `terraform.tfvars.example` as the starting point for a deployment-specific variable file. Review `solution-architecture-guide.md` before applying any plan.
|
`terraform plan` is optional and must be run with an explicitly supplied project and credentials; CI only performs static validation.
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
flowchart LR
|
flowchart LR
|
||||||
C[Client] -->|HTTPS + auth| API[Cloud Run API]
|
C[External clients] -->|HTTPS| R[Cloud Run service]
|
||||||
API --> DB[(Cloud SQL PostgreSQL)]
|
R --> F[(Firestore)]
|
||||||
API --> BUS[Pub/Sub events]
|
R --> S[(Cloud Storage)]
|
||||||
BUS --> W[Cloud Run worker]
|
R --> P[Pub/Sub topic]
|
||||||
W --> DB
|
P --> W[Worker subscriber]
|
||||||
BUS -. exhausted retries .-> DLQ[Pub/Sub dead-letter topic]
|
P --> DLQ[Dead-letter topic]
|
||||||
API --> LOG[Cloud Logging/Monitoring/Trace]
|
R --> L[Cloud Logging]
|
||||||
W --> LOG
|
R --> M[Cloud Monitoring]
|
||||||
AR[Artifact Registry] -. images .-> API
|
AR[Artifact Registry] --> R
|
||||||
AR -. images .-> W
|
R -. private egress .-> VPC[VPC / Serverless VPC Access]
|
||||||
SM[Secret Manager] -. references .-> API
|
|
||||||
SM -. references .-> W
|
|
||||||
|
|||||||
23
docs/architecture.md
Normal file
23
docs/architecture.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Steps 1–2 — Architecture and validation
|
||||||
|
|
||||||
|
## Selected products
|
||||||
|
- Cloud Run: managed HTTPS, stateless container runtime.
|
||||||
|
- Pub/Sub: durable asynchronous event transport.
|
||||||
|
- Cloud Storage: object persistence.
|
||||||
|
- Firestore: document/application state persistence.
|
||||||
|
- VPC and Serverless VPC Access: controlled private egress foundation.
|
||||||
|
- Cloud Logging and Cloud Monitoring: operational telemetry.
|
||||||
|
- Artifact Registry: container image source.
|
||||||
|
- IAM and Service Usage: identities and API enablement.
|
||||||
|
|
||||||
|
## Request flow
|
||||||
|
Clients call Cloud Run over HTTPS. The service writes application state to Firestore, stores binary objects in Cloud Storage, and publishes domain events to Pub/Sub. A separately deployed worker can subscribe to the topic; this baseline creates the topic and dead-letter topic but intentionally does not invent worker application code. Logs and platform metrics feed the operational plane.
|
||||||
|
|
||||||
|
## Security and reliability
|
||||||
|
Cloud Run uses a dedicated service account and configurable ingress. Firestore and Storage use managed encryption. Pub/Sub dead-lettering limits poison-message impact. Runtime configuration is supplied as variables rather than secrets in source. Production hardening should add Secret Manager, customer-managed keys, edge protection, private ingress, backups, and multi-region DR if the open questions require them.
|
||||||
|
|
||||||
|
## Terraform notes
|
||||||
|
The `terraform/` directory is a module-like root configuration. It enables required APIs, creates the network, storage bucket, Firestore database, Pub/Sub topics, Artifact Registry repository, service account, and Cloud Run service. Supply `project_id`, `region`, and `container_image`; do not commit a state backend or credentials. The Cloud Run resource is a deployable placeholder whose image must already exist.
|
||||||
|
|
||||||
|
## Validation result
|
||||||
|
Static artifact tests check required sections, Mermaid markers, Terraform file presence, and absence of obvious credential material. Terraform formatting/validation and an optional plan are defined in `scripts/validate.sh`; they are not executed by repository generation because this environment has no filesystem or cloud credentials. No resources are provisioned by the workflow.
|
||||||
48
docs/requirements.md
Normal file
48
docs/requirements.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Step 0 — Requirements discovery
|
||||||
|
|
||||||
|
## Workflow request
|
||||||
|
No application-specific workflow request was supplied. This baseline therefore documents an event-driven HTTP application reference architecture and marks all product choices as deferred during discovery.
|
||||||
|
|
||||||
|
## Functional requirements
|
||||||
|
- Accept authenticated HTTPS requests from external clients.
|
||||||
|
- Execute stateless application logic behind a versioned service endpoint.
|
||||||
|
- Publish asynchronous domain events from the application.
|
||||||
|
- Process events independently and tolerate retry/redelivery.
|
||||||
|
- Persist durable objects and application state separately.
|
||||||
|
- Expose operational logs, metrics, and audit-relevant events.
|
||||||
|
- Support repeatable infrastructure changes through declarative IaC.
|
||||||
|
|
||||||
|
## Non-functional requirements
|
||||||
|
- High availability within a selected Google Cloud region.
|
||||||
|
- Horizontal scale for bursty HTTP traffic and asynchronous work.
|
||||||
|
- At-least-once event delivery with idempotent consumers.
|
||||||
|
- Encryption in transit and at rest using managed defaults initially.
|
||||||
|
- Least-privilege runtime identities and private network egress where practical.
|
||||||
|
- Observable deployments with structured logs and actionable health signals.
|
||||||
|
- Reproducible, reviewable, non-deployment validation in CI.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Google Cloud is the target cloud; exact products are not selected in discovery.
|
||||||
|
- Terraform must be deployable without embedding secrets or credentials.
|
||||||
|
- The baseline must not provision resources during validation.
|
||||||
|
- A container image must be supplied by the application delivery pipeline.
|
||||||
|
- State backends, DNS ownership, identity federation, and organization policies are external concerns.
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
- A single region is acceptable for the initial deployment.
|
||||||
|
- The application can be packaged as an OCI container listening on port 8080.
|
||||||
|
- Events can use at-least-once semantics and consumers can deduplicate.
|
||||||
|
- A dedicated Google Cloud project is available.
|
||||||
|
- Managed encryption keys and public ingress are acceptable defaults pending review.
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
- What are the actual API, event, data-retention, and compliance requirements?
|
||||||
|
- Which clients and identity provider must authenticate requests?
|
||||||
|
- What are traffic, payload-size, latency, RTO, and RPO targets?
|
||||||
|
- Which data is relational, document, object, or analytical?
|
||||||
|
- Should ingress be public, private, or protected by an enterprise edge?
|
||||||
|
- Are customer-managed keys, VPC Service Controls, or regional DR required?
|
||||||
|
- What image registry, CI identity, environment promotion, and rollback policy apply?
|
||||||
|
- What budget, quota, naming, tagging, and organization-policy constraints apply?
|
||||||
|
|
||||||
|
**Product selection deferred:** `true` for this phase.
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
command -v terraform >/dev/null || { echo "Terraform 1.6.x is required" >&2; exit 1; }
|
command -v terraform >/dev/null || { echo 'terraform is required'; exit 2; }
|
||||||
terraform -chdir=terraform fmt -check -diff
|
terraform -chdir="$root/terraform" fmt -check -recursive
|
||||||
terraform -chdir=terraform init -backend=false -input=false
|
terraform -chdir="$root/terraform" init -backend=false -input=false -no-color >/dev/null
|
||||||
terraform -chdir=terraform validate
|
terraform -chdir="$root/terraform" validate -no-color
|
||||||
if [[ -z "${TF_VAR_project_id:-}" || -z "${TF_VAR_invoker_service_account:-}" ]]; then
|
if [[ "${RUN_PLAN:-false}" == "true" ]]; then
|
||||||
echo "Set TF_VAR_project_id and TF_VAR_invoker_service_account for plan check" >&2
|
: "${TF_VAR_project_id:?Set TF_VAR_project_id for plan}"
|
||||||
exit 2
|
terraform -chdir="$root/terraform" plan -refresh=false -lock=false -input=false -no-color
|
||||||
fi
|
fi
|
||||||
terraform -chdir=terraform plan -refresh=false -input=false -lock=false -out=/tmp/gcp-solution.tfplan >/tmp/gcp-solution.plan
|
python3 -m unittest discover -s "$root/tests" -v
|
||||||
cat /tmp/gcp-solution.plan
|
|
||||||
|
|||||||
@@ -1,69 +1,49 @@
|
|||||||
# Google Cloud Solution Architecture Guide
|
# Google Cloud solution architecture guide
|
||||||
|
|
||||||
**Repository:** `gcp_solution_architecture_agent`
|
## Scope and status
|
||||||
**Template:** `https://github.com/example/workflow_agent`
|
This guide packages manifest steps 0 through 7 for `gcp_solution_architecture_agent`, derived from the workflow-agent template. It is a reference baseline because no application-specific workflow request was supplied. Product selection was deferred during discovery and then made explicitly from the documented assumptions.
|
||||||
**Status:** reference design; no resources provisioned.
|
|
||||||
|
|
||||||
## 1. Approved requirements (Step 0)
|
## Requirements
|
||||||
|
See [`docs/requirements.md`](docs/requirements.md). It contains functional requirements, non-functional requirements, constraints, assumptions, and open questions. The principal unresolved items are identity, scale/SLOs, data model, edge exposure, compliance, DR, and delivery governance.
|
||||||
|
|
||||||
### Functional requirements
|
## Selected products
|
||||||
- Authenticated HTTPS stateless API.
|
Cloud Run, Pub/Sub, Cloud Storage, Firestore, VPC, Serverless VPC Access, Cloud Logging, Cloud Monitoring, Artifact Registry, IAM, and Service Usage.
|
||||||
- Transactional application persistence and indexed queries.
|
|
||||||
- Asynchronous domain events with retries and dead-letter handling.
|
|
||||||
- Operational logs, metrics, traces, and repeatable Terraform deployment.
|
|
||||||
|
|
||||||
### Non-functional requirements
|
|
||||||
- Managed horizontal scaling; 99.9% availability target; p95 normal reads under 500 ms.
|
|
||||||
- Regional initial deployment, encryption, least privilege, auditability, and no-resource validation.
|
|
||||||
|
|
||||||
### Constraints
|
|
||||||
Google Cloud target; Terraform IaC; parameterized project/region/environment; no committed secrets; one template-derived repository.
|
|
||||||
|
|
||||||
### Assumptions and open questions
|
|
||||||
See `requirements.md`. Production approval is conditional on confirming traffic, identity, RPO/RTO, data residency, retention, networking, CI identity, and event-schema answers.
|
|
||||||
|
|
||||||
Product selection was deferred in Step 0 and performed only after recording this baseline.
|
|
||||||
|
|
||||||
## 2. Product selection and architecture (Step 1)
|
|
||||||
|
|
||||||
Selected products are Cloud Run (API and worker), Cloud SQL for PostgreSQL, Pub/Sub, Artifact Registry, Secret Manager, Cloud Logging, Cloud Monitoring, Cloud Trace, IAM, and Service Usage. Cloud Run receives HTTPS traffic and emits events after database writes. Pub/Sub delivers at least once to a worker; the dead-letter topic captures exhausted delivery attempts. Cloud SQL is the system of record. Artifact Registry stores immutable image references. Secret Manager supplies secret references, while runtime and deployment identities are separated by IAM policy.
|
|
||||||
|
|
||||||
### Mermaid diagram
|
|
||||||
|
|
||||||
|
## Architecture
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
C[Client] -->|HTTPS + auth| API[Cloud Run API]
|
C[External clients] -->|HTTPS| R[Cloud Run service]
|
||||||
API --> DB[(Cloud SQL PostgreSQL)]
|
R --> F[(Firestore)]
|
||||||
API --> BUS[Pub/Sub events]
|
R --> S[(Cloud Storage)]
|
||||||
BUS --> W[Cloud Run worker]
|
R --> P[Pub/Sub topic]
|
||||||
W --> DB
|
P --> W[Worker subscriber]
|
||||||
BUS -. exhausted retries .-> DLQ[Pub/Sub dead-letter topic]
|
P --> DLQ[Dead-letter topic]
|
||||||
API --> OBS[Cloud Logging / Monitoring / Trace]
|
R --> L[Cloud Logging]
|
||||||
W --> OBS
|
R --> M[Cloud Monitoring]
|
||||||
AR[Artifact Registry] -. immutable images .-> API
|
AR[Artifact Registry] --> R
|
||||||
SM[Secret Manager] -. secret reference .-> API
|
R -. private egress .-> VPC[VPC / Serverless VPC Access]
|
||||||
```
|
```
|
||||||
|
|
||||||
The full source is in `architecture.mmd`; Terraform is in `terraform/`. Terraform provisions foundational resources only and deliberately leaves application container builds, secret values, ingress policy, alert policies, database users, and migrations to deployment-specific hardening.
|
Clients use the HTTPS service. The service persists structured state and objects, emits events, and relies on a separate idempotent worker for asynchronous processing. The runtime has a dedicated identity, controlled egress, and managed encryption defaults. Public invocation is a deliberate baseline pending the ingress and identity answers in the open questions.
|
||||||
|
|
||||||
## 3. Validation (Step 2)
|
## Infrastructure as code
|
||||||
|
The deployable Terraform root is in [`terraform/`](terraform/). It enables APIs, creates a custom VPC/subnet and serverless connector, runtime service account, uniformly private object bucket, Firestore database, event/dead-letter topics, Artifact Registry repository, and Cloud Run service. Variables keep project, region, image, and labels configurable. No credentials or backend state are committed.
|
||||||
|
|
||||||
Validation is offline and plan-oriented; it must not create cloud resources.
|
Apply only after review:
|
||||||
|
```bash
|
||||||
|
terraform -chdir=terraform init
|
||||||
|
terraform -chdir=terraform plan -var='project_id=PROJECT_ID' -var='container_image=IMAGE_URI'
|
||||||
|
terraform -chdir=terraform apply
|
||||||
|
```
|
||||||
|
|
||||||
| Check | Result |
|
## Validation
|
||||||
|---|---|
|
See [`validation-results.md`](validation-results.md). The repository supplies formatting, initialization without a backend, Terraform validation, an opt-in refresh-free plan, and Python artifact tests. Resource deployment is not part of validation. In this generation environment, these commands are **not_run** because required executables, provider downloads, and credentials are unavailable; CI must run them and record the results.
|
||||||
| Terraform formatting (`terraform fmt -check -recursive`) | PASS by artifact review; run in CI/operator environment |
|
|
||||||
| Terraform initialization without backend (`terraform init -backend=false`) | PASS expected; provider download required |
|
|
||||||
| Terraform configuration validation (`terraform validate`) | PASS expected after provider initialization |
|
|
||||||
| Artifact/package validator (`python3 scripts/validate_artifacts.py`) | PASS |
|
|
||||||
| Provisioning/dry run | NOT EXECUTED; no credentials or cloud calls used |
|
|
||||||
|
|
||||||
The exact commands are declared in `workflow.yaml` and `VALIDATION.md`. A real deployment must additionally run `terraform plan` with an approved variable file and review IAM, networking, SQL sizing, and costs.
|
## Repository verification
|
||||||
|
- Step 4 guide persistence: represented by this non-empty file at the required path.
|
||||||
|
- Step 5 template/workflow conformance: represented by `workflow.yaml`, four phase entries, Terraform, diagram, requirements, and validation artifacts.
|
||||||
|
- Step 6 publication: performed by the repository generation commit.
|
||||||
|
- Step 7 remote verification: must compare the published revision with this file and `workflow.yaml` in the source-control system.
|
||||||
|
|
||||||
## 4. Delivery verification (Steps 3–7)
|
## Risks and follow-up
|
||||||
|
Confirm the open questions before production. Add Secret Manager, stronger ingress/edge controls, CMEK, backups, alert policies, quota budgets, dead-letter IAM, and multi-region recovery where required. The Cloud Run public invoker and one-year bucket deletion rule are baseline choices, not universal policy.
|
||||||
This guide packages the requirements, selected products, architecture narrative, diagram, Terraform, and validation record. Repository conformance is represented by `workflow.yaml`, which covers all four phases and maps manifest steps 0–7. Required artifacts are checked by `scripts/validate_artifacts.py` and `tests/test_package.py`. Publication should be a single commit containing this guide and all source artifacts; the commit hash is reported by the delivery automation.
|
|
||||||
|
|
||||||
## 5. Operations and risks
|
|
||||||
|
|
||||||
Set Cloud Run concurrency and minimum instances from measured load. Configure SQL HA, PITR, maintenance windows, and private IP before production. Grant Pub/Sub subscriber/publisher and Secret Manager accessor roles to dedicated service accounts, not broad project roles. Add SLO-based alerts, structured correlation IDs, trace propagation, log retention, vulnerability scanning, and an incident runbook. Event consumers must deduplicate by event ID. Open questions in `requirements.md` are release blockers for a production architecture decision.
|
|
||||||
|
|||||||
@@ -1,110 +1,106 @@
|
|||||||
terraform {
|
locals {
|
||||||
required_version = ">= 1.6.0, < 2.0.0"
|
common_labels = merge({ managed_by = "terraform", workload = var.name }, var.labels)
|
||||||
required_providers {
|
services = toset([
|
||||||
google = {
|
"run.googleapis.com", "pubsub.googleapis.com", "storage.googleapis.com",
|
||||||
source = "hashicorp/google"
|
"firestore.googleapis.com", "artifactregistry.googleapis.com",
|
||||||
version = "~> 6.0"
|
"logging.googleapis.com", "monitoring.googleapis.com", "vpcaccess.googleapis.com"
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "google" {
|
|
||||||
project = var.project_id
|
|
||||||
region = var.region
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_project_service" "services" {
|
|
||||||
for_each = toset([
|
|
||||||
"run.googleapis.com", "sqladmin.googleapis.com", "pubsub.googleapis.com",
|
|
||||||
"artifactregistry.googleapis.com", "secretmanager.googleapis.com",
|
|
||||||
"logging.googleapis.com", "monitoring.googleapis.com", "cloudtrace.googleapis.com"
|
|
||||||
])
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_project_service" "apis" {
|
||||||
|
for_each = local.services
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
service = each.value
|
service = each.value
|
||||||
disable_on_destroy = false
|
disable_on_destroy = false
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_artifact_registry_repository" "images" {
|
resource "google_compute_network" "app" {
|
||||||
location = var.region
|
name = "${var.name}-vpc"
|
||||||
repository_id = "${var.name}-images"
|
auto_create_subnetworks = false
|
||||||
format = "DOCKER"
|
depends_on = [google_project_service.apis]
|
||||||
depends_on = [google_project_service.services]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_sql_database_instance" "primary" {
|
resource "google_compute_subnetwork" "app" {
|
||||||
name = "${var.name}-sql"
|
name = "${var.name}-subnet"
|
||||||
database_version = "POSTGRES_15"
|
ip_cidr_range = "10.10.0.0/24"
|
||||||
region = var.region
|
region = var.region
|
||||||
settings {
|
network = google_compute_network.app.id
|
||||||
tier = var.sql_tier
|
|
||||||
availability_type = var.sql_ha ? "REGIONAL" : "ZONAL"
|
|
||||||
disk_type = "PD_SSD"
|
|
||||||
disk_autoresize = true
|
|
||||||
backup_configuration { enabled = true }
|
|
||||||
ip_configuration { ipv4_enabled = true }
|
|
||||||
}
|
|
||||||
deletion_protection = var.deletion_protection
|
|
||||||
depends_on = [google_project_service.services]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_sql_database" "app" {
|
resource "google_vpc_access_connector" "app" {
|
||||||
name = var.name
|
name = substr("${var.name}-connector", 0, 24)
|
||||||
instance = google_sql_database_instance.primary.name
|
region = var.region
|
||||||
}
|
network = google_compute_network.app.name
|
||||||
|
ip_cidr_range = "10.8.0.0/28"
|
||||||
resource "google_pubsub_topic" "events" { name = "${var.name}-events" }
|
depends_on = [google_project_service.apis]
|
||||||
resource "google_pubsub_topic" "dead_letter" { name = "${var.name}-dead-letter" }
|
|
||||||
|
|
||||||
resource "google_pubsub_subscription" "worker" {
|
|
||||||
name = "${var.name}-worker"
|
|
||||||
topic = google_pubsub_topic.events.name
|
|
||||||
dead_letter_policy {
|
|
||||||
dead_letter_topic = google_pubsub_topic.dead_letter.id
|
|
||||||
max_delivery_attempts = 10
|
|
||||||
}
|
|
||||||
ack_deadline_seconds = 30
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "google_secret_manager_secret" "database_url" {
|
|
||||||
secret_id = "${var.name}-database-url"
|
|
||||||
replication { auto {} }
|
|
||||||
depends_on = [google_project_service.services]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_service_account" "runtime" {
|
resource "google_service_account" "runtime" {
|
||||||
account_id = "${var.name}-runtime"
|
account_id = substr("${var.name}-runtime", 0, 30)
|
||||||
display_name = "${var.name} runtime identity"
|
display_name = "${var.name} runtime identity"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_project_iam_member" "runtime_log_writer" {
|
resource "google_storage_bucket" "objects" {
|
||||||
|
name = "${var.project_id}-${var.name}-objects"
|
||||||
|
location = var.region
|
||||||
|
uniform_bucket_level_access = true
|
||||||
|
public_access_prevention = "enforced"
|
||||||
|
labels = local.common_labels
|
||||||
|
lifecycle_rule {
|
||||||
|
condition { age = 365 }
|
||||||
|
action { type = "Delete" }
|
||||||
|
}
|
||||||
|
depends_on = [google_project_service.apis]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_firestore_database" "app" {
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
role = "roles/logging.logWriter"
|
name = "(default)"
|
||||||
member = "serviceAccount:${google_service_account.runtime.email}"
|
location_id = var.region
|
||||||
|
type = "FIRESTORE_NATIVE"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_cloud_run_v2_service" "api" {
|
resource "google_pubsub_topic" "events" {
|
||||||
name = "${var.name}-api"
|
name = "${var.name}-events"
|
||||||
location = var.region
|
labels = local.common_labels
|
||||||
template {
|
|
||||||
service_account = google_service_account.runtime.email
|
|
||||||
containers {
|
|
||||||
image = var.api_image
|
|
||||||
env { name = "PUBSUB_TOPIC" value = google_pubsub_topic.events.name }
|
|
||||||
env { name = "DATABASE_SECRET" value = google_secret_manager_secret.database_url.secret_id }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
depends_on = [google_project_service.services]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "google_cloud_run_v2_service" "worker" {
|
resource "google_pubsub_topic" "dead_letter" {
|
||||||
name = "${var.name}-worker"
|
name = "${var.name}-events-dead-letter"
|
||||||
|
labels = local.common_labels
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_artifact_registry_repository" "containers" {
|
||||||
location = var.region
|
location = var.region
|
||||||
|
repository_id = var.name
|
||||||
|
format = "DOCKER"
|
||||||
|
labels = local.common_labels
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_cloud_run_v2_service" "app" {
|
||||||
|
name = var.name
|
||||||
|
location = var.region
|
||||||
|
ingress = "INGRESS_TRAFFIC_ALL"
|
||||||
|
labels = local.common_labels
|
||||||
template {
|
template {
|
||||||
service_account = google_service_account.runtime.email
|
service_account = google_service_account.runtime.email
|
||||||
|
scaling { max_instance_count = 20 }
|
||||||
|
vpc_access {
|
||||||
|
connector = google_vpc_access_connector.app.id
|
||||||
|
egress = "PRIVATE_RANGES_ONLY"
|
||||||
|
}
|
||||||
containers {
|
containers {
|
||||||
image = var.worker_image
|
image = var.container_image
|
||||||
env { name = "DATABASE_SECRET" value = google_secret_manager_secret.database_url.secret_id }
|
ports { container_port = 8080 }
|
||||||
|
resources { limits = { cpu = "1", memory = "512Mi" } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
depends_on = [google_project_service.services]
|
depends_on = [google_project_service.apis]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "google_cloud_run_v2_service_iam_member" "public_invoker" {
|
||||||
|
name = google_cloud_run_v2_service.app.name
|
||||||
|
location = var.region
|
||||||
|
role = "roles/run.invoker"
|
||||||
|
member = "allUsers"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
output "api_service_name" { value = google_cloud_run_v2_service.api.name }
|
output "service_uri" {
|
||||||
output "worker_service_name" { value = google_cloud_run_v2_service.worker.name }
|
value = google_cloud_run_v2_service.app.uri
|
||||||
output "sql_connection_name" { value = google_sql_database_instance.primary.connection_name }
|
description = "HTTPS endpoint of the Cloud Run service."
|
||||||
output "events_topic" { value = google_pubsub_topic.events.name }
|
}
|
||||||
output "artifact_repository" { value = google_artifact_registry_repository.images.name }
|
|
||||||
|
output "events_topic" {
|
||||||
|
value = google_pubsub_topic.events.id
|
||||||
|
}
|
||||||
|
|
||||||
|
output "objects_bucket" {
|
||||||
|
value = google_storage_bucket.objects.name
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,28 @@
|
|||||||
variable "project_id" { type = string; description = "Google Cloud project ID" }
|
variable "project_id" {
|
||||||
variable "region" { type = string; default = "us-central1" }
|
description = "Google Cloud project hosting the baseline."
|
||||||
variable "name" { type = string; default = "architecture-demo"; validation { condition = can(regex("^[a-z][a-z0-9-]{0,29}$", var.name)); error_message = "name must be lowercase and up to 30 characters." } }
|
type = string
|
||||||
variable "api_image" { type = string; description = "Immutable API container image reference" }
|
}
|
||||||
variable "worker_image" { type = string; description = "Immutable worker container image reference" }
|
|
||||||
variable "sql_tier" { type = string; default = "db-f1-micro" }
|
variable "region" {
|
||||||
variable "sql_ha" { type = bool; default = false }
|
description = "Primary deployment region."
|
||||||
variable "deletion_protection" { type = bool; default = true }
|
type = string
|
||||||
|
default = "us-central1"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "name" {
|
||||||
|
description = "Lowercase application name."
|
||||||
|
type = string
|
||||||
|
default = "reference-app"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "container_image" {
|
||||||
|
description = "Fully qualified, prebuilt container image."
|
||||||
|
type = string
|
||||||
|
default = "us-docker.pkg.dev/cloudrun/container/hello"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "labels" {
|
||||||
|
description = "Additional resource labels."
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
}
|
||||||
|
|||||||
14
terraform/versions.tf
Normal file
14
terraform/versions.tf
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.5.0"
|
||||||
|
required_providers {
|
||||||
|
google = {
|
||||||
|
source = "hashicorp/google"
|
||||||
|
version = "~> 6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "google" {
|
||||||
|
project = var.project_id
|
||||||
|
region = var.region
|
||||||
|
}
|
||||||
@@ -1,15 +1,37 @@
|
|||||||
from pathlib import Path
|
import pathlib
|
||||||
|
import re
|
||||||
|
import unittest
|
||||||
|
|
||||||
ROOT = Path(__file__).parents[1]
|
ROOT = pathlib.Path(__file__).parents[1]
|
||||||
|
|
||||||
def test_required_artifacts_exist():
|
|
||||||
for path in ["requirements-spec.md", "architecture.md", "solution-architecture-guide.md", "terraform/main.tf", "scripts/validate_artifacts.py"]:
|
|
||||||
assert (ROOT / path).is_file()
|
|
||||||
|
|
||||||
def test_workflow_has_four_phases():
|
class ArtifactTests(unittest.TestCase):
|
||||||
text = (ROOT / "README.md").read_text()
|
def test_requirements_defer_products(self):
|
||||||
assert all(f"{n}." in text for n in range(1, 5))
|
text = (ROOT / "docs/requirements.md").read_text()
|
||||||
|
self.assertIn("Product selection deferred:", text)
|
||||||
|
self.assertIn("Open questions", text)
|
||||||
|
|
||||||
def test_terraform_is_not_an_apply_script():
|
def test_architecture_has_products_and_flow(self):
|
||||||
text = (ROOT / "architecture.md").read_text()
|
text = (ROOT / "docs/architecture.md").read_text()
|
||||||
assert "No apply is used" in text
|
for product in ("Cloud Run", "Pub/Sub", "Cloud Storage", "Firestore"):
|
||||||
|
self.assertIn(product, text)
|
||||||
|
|
||||||
|
def test_mermaid_is_flowchart(self):
|
||||||
|
text = (ROOT / "architecture.mmd").read_text()
|
||||||
|
self.assertTrue(text.startswith("flowchart"))
|
||||||
|
self.assertIn("Cloud Run", text)
|
||||||
|
|
||||||
|
def test_terraform_root_is_complete(self):
|
||||||
|
main = (ROOT / "terraform/main.tf").read_text()
|
||||||
|
self.assertIn("google_cloud_run_v2_service", main)
|
||||||
|
self.assertIn("google_pubsub_topic", main)
|
||||||
|
self.assertNotRegex(main, r"(?i)(password|secret|private_key)\\s*=")
|
||||||
|
|
||||||
|
def test_guide_packages_outputs(self):
|
||||||
|
guide = (ROOT / "solution-architecture-guide.md").read_text()
|
||||||
|
for heading in ("Requirements", "Architecture", "Terraform", "Validation", "Verification"):
|
||||||
|
self.assertIn(heading, guide)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|||||||
10
validation-results.md
Normal file
10
validation-results.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Step 2 — Validation results
|
||||||
|
|
||||||
|
These checks are defined for pre-deployment execution. They intentionally do not provision cloud resources.
|
||||||
|
|
||||||
|
- Terraform formatting: **defined** by `terraform fmt -check -recursive`.
|
||||||
|
- Terraform configuration validation: **defined** by `terraform init -backend=false` followed by `terraform validate`.
|
||||||
|
- Dry-run deployment check: **defined** as an opt-in `RUN_PLAN=true` execution of `terraform plan -refresh=false`; it requires a project and credentials and is not run by default.
|
||||||
|
- Repository artifact tests: **defined** by `python3 -m unittest discover -s tests -v`.
|
||||||
|
|
||||||
|
Generation environment limitation: the repository was created without filesystem, Terraform binary, provider download, or Google Cloud credentials. Consequently, execution results are `not_run`, not a claim of pass. Run `bash scripts/validate.sh` in CI before approval.
|
||||||
@@ -1,20 +1,31 @@
|
|||||||
name: gcp-solution-architecture-workflow
|
name: gcp-solution-architecture-workflow
|
||||||
template_url: https://github.com/example/workflow_agent
|
source_template: https://github.com/workflow_agent/template
|
||||||
target_repository: gcp_solution_architecture_agent
|
repository: gcp_solution_architecture_agent
|
||||||
phases:
|
phases:
|
||||||
- id: requirements
|
- id: discover
|
||||||
steps: [0]
|
step: 0
|
||||||
output: requirements.md
|
input: workflow_request
|
||||||
|
output: docs/requirements.md
|
||||||
product_selection_deferred: true
|
product_selection_deferred: true
|
||||||
- id: architecture
|
- id: design
|
||||||
steps: [1]
|
step: 1
|
||||||
inputs: [requirements.md]
|
input: docs/requirements.md
|
||||||
outputs: [architecture.md, architecture.mmd, terraform/]
|
outputs: [docs/architecture.md, architecture.mmd, terraform/]
|
||||||
- id: validation
|
- id: validate
|
||||||
steps: [2]
|
step: 2
|
||||||
inputs: [architecture.md, architecture.mmd, terraform/]
|
input: [terraform/, architecture.mmd]
|
||||||
commands: [terraform fmt -check -recursive, terraform init -backend=false, terraform validate, python3 scripts/validate_artifacts.py]
|
outputs: [validation-results.md]
|
||||||
provisioning: false
|
deploy_resources: false
|
||||||
- id: packaging
|
- id: package
|
||||||
steps: [3, 4, 5, 6, 7]
|
step: 3
|
||||||
|
input: [docs/requirements.md, docs/architecture.md, architecture.mmd, terraform/, validation-results.md]
|
||||||
output: solution-architecture-guide.md
|
output: solution-architecture-guide.md
|
||||||
|
verification:
|
||||||
|
- step: 4
|
||||||
|
check: solution-architecture-guide.md exists and is non-empty
|
||||||
|
- step: 5
|
||||||
|
check: workflow.yaml covers discover, design, validate, package and required artifacts exist
|
||||||
|
- step: 6
|
||||||
|
check: committed revision is published
|
||||||
|
- step: 7
|
||||||
|
check: remote revision contains workflow.yaml and solution-architecture-guide.md
|
||||||
|
|||||||
Reference in New Issue
Block a user