Cross-Stack References
Cross-stack references allow components in one stack to access outputs from components in another stack. This is essential for sharing infrastructure across environments and maintaining clean separation of concerns.
Overview
When you need to reference infrastructure from another stack, Comet provides the state()
template function that automatically handles remote state data sources and dependencies.
Syntax
'{{ (state "STACK_NAME" "COMPONENT_NAME").OUTPUT_PROPERTY }}'
Parameters:
STACK_NAME
- The name of the stack containing the componentCOMPONENT_NAME
- The name of the componentOUTPUT_PROPERTY
- The output value you want to reference
Basic Example
Infrastructure Stack
First, create a base infrastructure stack:
stack('infrastructure', {
project_id: 'my-gcp-project',
region: 'us-central1'
})
backend('gcs', {
bucket: 'my-terraform-state',
prefix: 'comet/{{ .stack }}/{{ .component }}'
})
const project = component('project', 'modules/gcp-project', {
name: 'my-app',
project_id: '{{ .settings.project_id }}'
})
const vpc = component('vpc', 'modules/vpc', {
project_id: project.id,
cidr_block: '10.0.0.0/16'
})
const gke = component('gke', 'modules/gke', {
project_id: project.id,
network: vpc.id,
cluster_name: 'main-cluster'
})
Application Stack
Now reference the infrastructure from an application stack:
stack('application', {
app_name: 'my-webapp'
})
backend('gcs', {
bucket: 'my-terraform-state',
prefix: 'comet/{{ .stack }}/{{ .component }}'
})
const webapp = component('webapp', 'modules/k8s-deployment', {
// Reference outputs from the infrastructure stack
project_id: '{{ (state "infrastructure" "project").id }}',
cluster_endpoint: '{{ (state "infrastructure" "gke").endpoint }}',
cluster_ca_cert: '{{ (state "infrastructure" "gke").ca_certificate }}',
vpc_id: '{{ (state "infrastructure" "vpc").id }}'
})
How It Works
When Comet encounters a state()
reference, it automatically:
- Generates a remote state data source for the referenced component
- Creates safe fallbacks using Terraform's
try()
function - Configures the backend to match the referenced stack's backend
For the example above, Comet generates:
data "terraform_remote_state" "infrastructure_gke" {
backend = "gcs"
config = {
bucket = "my-terraform-state"
prefix = "comet/infrastructure/gke"
}
}
locals {
cluster_endpoint = try(
data.terraform_remote_state.infrastructure_gke.outputs.endpoint,
null
)
}
Multiple References
You can reference multiple components from multiple stacks:
const app = component('app', 'modules/application', {
// Reference from infrastructure stack
network: '{{ (state "infrastructure" "vpc").id }}',
cluster: '{{ (state "infrastructure" "gke").name }}',
// Reference from data stack
database_host: '{{ (state "data" "cloudsql").connection_name }}',
redis_host: '{{ (state "data" "redis").host }}',
// Reference from monitoring stack
prometheus_url: '{{ (state "monitoring" "prometheus").url }}'
})
Environment-Specific References
Reference different stacks based on the environment:
const app = component('app', 'modules/application', {
// Reference dev infrastructure
vpc_id: '{{ (state "infrastructure-dev" "vpc").id }}'
})
const app = component('app', 'modules/application', {
// Reference production infrastructure
vpc_id: '{{ (state "infrastructure-prod" "vpc").id }}'
})
Best Practices
1. Separate Infrastructure Layers
Organize stacks by lifecycle and responsibility:
stacks/
├── foundation.stack.js # GCP project, IAM, etc.
├── networking.stack.js # VPCs, subnets, firewall rules
├── kubernetes.stack.js # GKE clusters
├── data.stack.js # Databases, storage
└── applications.stack.js # Applications and services
2. Minimize Cross-Stack Dependencies
Only reference what you actually need:
// ✅ Good: Only reference specific outputs
const app = component('app', 'modules/app', {
cluster_endpoint: '{{ (state "infra" "gke").endpoint }}'
})
// ❌ Bad: Don't create unnecessary dependencies
const app = component('app', 'modules/app', {
// Don't reference things you don't use
vpc_id: '{{ (state "infra" "vpc").id }}',
random_output: '{{ (state "infra" "something").value }}'
})
3. Use Clear Naming
Make stack and component names descriptive:
// ✅ Good: Clear what's being referenced
cluster: '{{ (state "infrastructure-prod" "gke-primary").endpoint }}'
// ❌ Bad: Unclear names
cluster: '{{ (state "infra" "k1").e }}'
4. Deploy in Order
Ensure referenced stacks are deployed before dependent stacks:
# Deploy in dependency order
comet apply foundation
comet apply networking
comet apply kubernetes
comet apply applications
Common Patterns
Shared VPC Pattern
const vpc = component('shared-vpc', 'modules/vpc', {
cidr_block: '10.0.0.0/16'
})
const service_a = component('api', 'modules/service', {
vpc_id: '{{ (state "shared-vpc" "shared-vpc").id }}'
})
const service_b = component('worker', 'modules/service', {
vpc_id: '{{ (state "shared-vpc" "shared-vpc").id }}'
})
Multi-Environment Pattern
const gke = component('gke', 'modules/gke', {
cluster_name: 'prod-cluster'
})
const app = component('app', 'modules/app', {
cluster: '{{ (state "infra-prod" "gke").endpoint }}'
})
Limitations
- Referenced components must already exist and have been successfully applied
- Both stacks must use compatible backend configurations
- Changes to referenced outputs require re-planning dependent stacks
- Circular dependencies between stacks are not supported