Managed domains - #4
Conversation
use_ravion_managed_domains toggle: ravion_certificate (shared_wildcard) issues *.<name>-<hash>.<apex>; this module owns the public ALB HTTPS listener with that cert as default (alb submodule skips its HTTPS listener + cert ARNs); opens SG 443. Outputs the wildcard fqdn + listener arn + cert arn + aws account/region for ecs_service to nest under. Provider pinned ravion.com/ravion/ravion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cluster_parent_fqdn enables Ravion domains. Mode A (no domains): ravion_domain auto-FQDN <name>.<cluster-apex> rides the cluster wildcard via a listener rule. Mode B (domains): per-service ravion_certificate (<=10 SANs) attached to the cluster listener + ravion_domain custom routing records; auto-FQDN retires once customs are healthy (ravion_auto_domain_status). Skips caller listener rules in Ravion mode. Outputs auto fqdn/url + custom cert arn. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
use_ravion_managed_domains: ravion_certificate (instance, target_arn = the CloudFront distribution ARN, region us-east-1) covering custom domains or a generated auto-FQDN; ravion_domain custom routing records (ALIAS to the distribution, CloudFront zone Z2FDTNDATAQYW2). Configure var.distributions without aliases/cert in this mode (Ravion sets them server-side). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cluster only wired the Ravion wildcard cert + 443 HTTPS listener on the public ALB, so private services (web-private / private-network-server) had no Ravion-owned listener to attach to. Mirror the public wiring onto the private ALB — one wildcard cert backs both listeners (an ACM ARN can default many). ecs_cluster: - enable_ravion_domain now triggers on public OR private ALB (was public-only); precondition requires at least one ALB instead of mandating the public one. - Add aws_lb_listener.ravion_https_private on the private ALB (same cluster cert) + a private-ALB 443 ingress rule; private alb submodule now skips its own HTTPS listener/cert in Ravion mode, same as the public submodule. - public/private_alb_https_listener_arn outputs surface the Ravion-owned listener when present (length()-guarded so a private-only cluster is valid). ecs_service: - Generalize cluster_https_listener_arn / cluster_alb_dns_name / cluster_alb_zone_id descriptions: pipe the public OR private ALB outputs per the service's visibility. The resources were already visibility-agnostic; only the docs hardcoded "public". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…iven id The cluster wildcard FQDN used var.name (the project-environment slug, e.g. testttsss-prod-modules), not the user-facing instance given id (elysia-ecs-cluster). Declare module_instance_given_id (injected by the runner as TF_VAR_module_instance_given_id) and default the cert leaf to it, so the wildcard becomes <given-id>-<hash>.<ravion-apex>. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the ecs_cluster fix: the service auto-domain (<leaf>.<cluster-wildcard>) used var.name (project-env slug) instead of the user-facing instance given id. Declare module_instance_given_id (runner-injected) and default the leaf to it, so the auto-FQDN becomes <given-id>.<cluster-apex>. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ravion_certificate.cluster now passes target_dns_name/target_zone_id (public ALB if present, else private) so Ravion publishes a *.<apex> ALIAS to the cluster ALB. Service auto-FQDNs riding the wildcard then resolve with no per-service DNS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ollide Fixed name + create_before_destroy = true means any attribute change that forces TG replacement (e.g. container_port change) fails apply because the new TG can't share the same name as the existing one. Switching to name_prefix lets AWS allocate a unique suffix on each create. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Existing 'spa' and 'filesystem' modes append /index.html to extensionless paths, which is wrong for object sites whose URLs ARE the S3 keys — namely a Terraform provider registry, where the viewer requests `/v1/providers/<ns>/<type>/versions` and must get back the literal JSON file at that key, not `/<...>/versions/index.html`. `raw` is a 1:1 viewer-URI → /<version>/<URI> mapping. KVS-driven versioning still applies. Use for terraform registries, S3-like content APIs, or any case where viewer URLs must equal S3 keys 1:1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move all required_providers source references from `providers.siddharthsuresh.dev/ravion/ravion` (cloudflared → local registry on a laptop) to `provider-cf.siddharthsuresh.dev/ravion/ravion` (CloudFront → S3, multi-version, KMS-signed). The local-cloudflared path stays alive during the cutover so existing stacks pinning the old hostname keep working until they're migrated or destroyed. Migration note: existing stacks have provider addresses recorded in their cloud-backend state under the old hostname. A subsequent apply will need a state-replace-provider pass, OR be done as part of a destroy+recreate. New stacks pick up the new hostname automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The runner's terraformrc network-mirror config is hardcoded for the canonical hostname (providers.siddharthsuresh.dev). Pointing modules at provider-cf.siddharthsuresh.dev triggers terraform's "requires authentication credentials" error since the runner doesn't emit a credentials/mirror block for that host. The proper migration is stage B — keep the source unchanged and point the canonical hostname's DNS at CloudFront — not a per-module source rewrite. Reverting `2f78c63`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esh.dev" This reverts commit 638ab14.
This reverts commit 291cac2.
…ains state ecs_cluster always owns the public/private HTTPS listener at a stable TF address so toggling use_ravion_managed_domains is an in-place certificate swap, not a destroy+create across two addresses. Only the default cert (Ravion wildcard vs the customer's first ARN), the SNI cert set, and ravion_certificate.cluster change on toggle. - alb submodule: additive force_http_to_https_redirect keeps the HTTP->HTTPS redirect when a parent owns port 443; redirect deduped into locals - new ravion_managed_domains_enabled output for service-level show/hide - moved blocks for the in-root renames + submodule->root migration - focused tests/listeners.tftest.hcl (8/8 pass) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sification Each domains entry is classified per-entry instead of all-or-nothing: <leaf>.<apex> (one label under the cluster apex) rides the cluster wildcard cert via SNI (no per-service cert, no DNS record); everything else gets one per-service instance cert + a customer routing record. Empty list falls back to the auto-FQDN <given-id>.<apex>. Removes the ravion_auto_domain_status retirement flow — the domains list is the single source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lowercase + strip trailing dot/whitespace + drop empties, and require a non-empty leaf for the wildcard bucket, so mixed-case / trailing-dot / empty-leaf entries classify correctly and never yield an invalid ALB host header. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Domains under the cluster apex that aren't a single-label <leaf>.<apex> (the bare apex, or names more than one label deep) can't ride the *.<apex> wildcard cert and can't be satisfied with a customer record (the record would live in the Ravion-managed zone). Add an invalid_apex_domains local + a lifecycle.precondition on ravion_certificate.svc that fails the plan with the offending entries and the fix, instead of silently mis-routing them into a per-service cert + an unwritable routing record. Pairs with the server-side RejectCustomDomainUnderApex backstop for direct-API callers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- B3: mock the ravion provider in the pre-existing ecs_cluster + ecs_service basic.tftest.hcl so the suites stop aborting on "Missing Ravion API key" (declaring the provider configures it even when all ravion resources are count=0 on the BYO path). - M13: split the ravion listener rule's host headers into chunks of <=5 values (AWS ALB's per-rule condition-value quota), each chunk with its own priority. - M14: keep the rolling target group's pre-branch stable name substr(var.name,0,28)+"-tg" instead of name_prefix — avoids the one-time ForceNew that deadlocks against the listener rule's ignore_changes=[action], and stays within ALB's 32-char TG-name limit. - #39: gate the ravion listener rule (and cert/domain) on enable_load_balancer so it can't be created with a null target_group_arn. - #40: widen the auto-derived rule-priority hash entropy to cut collisions on the shared cluster listener. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in-place rotation Rotating the shared_wildcard cert (any RequiresReplace change, e.g. a renamed apex) destroyed the old cert before swapping the listener, hitting ACM ResourceInUse and deadlocking. create_before_destroy issues the new cert and swaps it onto the listener in-place before deleting the old one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ision_check A second cluster claiming an apex another cluster already owns silently hijacked the *.<apex> routing ALIAS. Add a plan-time precondition backed by the ravion_dns_collision_check data source (the backend resolves the name leaf to *.<name>.<apex> and reports a collision only when a DIFFERENT module instance owns it, so a self re-apply passes). The allocator enforces the same rule server-side as an apply-time backstop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ider >= 1.0.0 Add a ravion_parent_apex_check data source + a precondition on the service's nested wildcard domains: the plan fails if cluster_parent_fqdn points at an apex the service isn't entitled to (e.g. another cluster's apex). The control plane enforces the same rule at apply (Dns:PARENT_APEX_UNAUTHORIZED), so this only surfaces the failure earlier — users never write the guard. The empty-domains auto-FQDN fallback (effective_domains) is unchanged: a service with no custom domains still gets <given-id>.<apex> and stays accessible. Bump the ravion provider pin to >= 1.0.0 across ecs_service, ecs_cluster, and static_site (the build that ships the parent-apex data source + the server-side apex guards). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ependents Wire the second new data source: read the live service domains nested under the cluster wildcard apex and expose them as the ravion_cluster_dependent_domains output for the UI / safe-teardown orchestration. Deliberately an output, not a precondition: a cluster legitimately has dependents during normal operation and Terraform can't scope a precondition to destroy-time, so a dependent_count == 0 gate would block every apply. The control plane already refuses a wildcard-cert teardown while dependents exist (Dns:CERT_APEX_IN_USE). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… rule The control plane now authorizes parent-apex nesting from a signed token claim (the clusters the run references), not same-environment. Update the guard comment + precondition error message to say "references that cluster" instead of "in this environment." Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| moved { | ||
| from = aws_vpc_security_group_ingress_rule.ravion_https_private_ipv4 | ||
| to = aws_vpc_security_group_ingress_rule.private_https_ipv4 | ||
| } |
There was a problem hiding this comment.
The private IPv6 SG rule rename is missing a
moved block. Three of the four symmetrical SG rule renames have moved blocks (ravion_https_ipv4, ravion_https_ipv6, ravion_https_private_ipv4), but ravion_https_private_ipv6 → private_https_ipv6 is absent. For any cluster already in Ravion mode with a private ALB, Terraform will destroy the old rule and create a new one, creating a brief window where IPv6 traffic on port 443 to the private ALB is not covered.
| moved { | |
| from = aws_vpc_security_group_ingress_rule.ravion_https_private_ipv4 | |
| to = aws_vpc_security_group_ingress_rule.private_https_ipv4 | |
| } | |
| moved { | |
| from = aws_vpc_security_group_ingress_rule.ravion_https_private_ipv4 | |
| to = aws_vpc_security_group_ingress_rule.private_https_ipv4 | |
| } | |
| moved { | |
| from = aws_vpc_security_group_ingress_rule.ravion_https_private_ipv6 | |
| to = aws_vpc_security_group_ingress_rule.private_https_ipv6 | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: compute/ecs_cluster/listeners.tf
Line: 185-188
Comment:
The private IPv6 SG rule rename is missing a `moved` block. Three of the four symmetrical SG rule renames have `moved` blocks (`ravion_https_ipv4`, `ravion_https_ipv6`, `ravion_https_private_ipv4`), but `ravion_https_private_ipv6` → `private_https_ipv6` is absent. For any cluster already in Ravion mode with a private ALB, Terraform will destroy the old rule and create a new one, creating a brief window where IPv6 traffic on port 443 to the private ALB is not covered.
```suggestion
moved {
from = aws_vpc_security_group_ingress_rule.ravion_https_private_ipv4
to = aws_vpc_security_group_ingress_rule.private_https_ipv4
}
moved {
from = aws_vpc_security_group_ingress_rule.ravion_https_private_ipv6
to = aws_vpc_security_group_ingress_rule.private_https_ipv6
}
```
How can I resolve this? If you propose a fix, please make it concise.| precondition { | ||
| condition = length(local.custom_domains) == 0 || (var.cluster_https_listener_arn != null && var.cluster_https_listener_arn != "") | ||
| error_message = "cluster_https_listener_arn is required when the domains list includes a custom (non-wildcard) domain." | ||
| } |
There was a problem hiding this comment.
ravion_domain.custom uses var.cluster_alb_dns_name and var.cluster_alb_zone_id as target_dns_name/target_zone_id, but there are no preconditions validating that these are non-null when custom domains are present. The sibling ravion_certificate.svc resource guards ravion_aws_account_id and cluster_https_listener_arn, but the two ALB variables are left unchecked. A user who provides custom domains and forgets these variables will get a cryptic provider-side null error instead of a clear Terraform message.
| precondition { | |
| condition = length(local.custom_domains) == 0 || (var.cluster_https_listener_arn != null && var.cluster_https_listener_arn != "") | |
| error_message = "cluster_https_listener_arn is required when the domains list includes a custom (non-wildcard) domain." | |
| } | |
| precondition { | |
| condition = length(local.custom_domains) == 0 || (var.cluster_https_listener_arn != null && var.cluster_https_listener_arn != "") | |
| error_message = "cluster_https_listener_arn is required when the domains list includes a custom (non-wildcard) domain." | |
| } | |
| precondition { | |
| condition = length(local.custom_domains) == 0 || (var.cluster_alb_dns_name != null && var.cluster_alb_dns_name != "") | |
| error_message = "cluster_alb_dns_name is required when the domains list includes a custom (non-wildcard) domain." | |
| } | |
| precondition { | |
| condition = length(local.custom_domains) == 0 || (var.cluster_alb_zone_id != null && var.cluster_alb_zone_id != "") | |
| error_message = "cluster_alb_zone_id is required when the domains list includes a custom (non-wildcard) domain." | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: compute/ecs_service/ravion_domains.tf
Line: 136-139
Comment:
`ravion_domain.custom` uses `var.cluster_alb_dns_name` and `var.cluster_alb_zone_id` as `target_dns_name`/`target_zone_id`, but there are no preconditions validating that these are non-null when custom domains are present. The sibling `ravion_certificate.svc` resource guards `ravion_aws_account_id` and `cluster_https_listener_arn`, but the two ALB variables are left unchecked. A user who provides custom domains and forgets these variables will get a cryptic provider-side null error instead of a clear Terraform message.
```suggestion
precondition {
condition = length(local.custom_domains) == 0 || (var.cluster_https_listener_arn != null && var.cluster_https_listener_arn != "")
error_message = "cluster_https_listener_arn is required when the domains list includes a custom (non-wildcard) domain."
}
precondition {
condition = length(local.custom_domains) == 0 || (var.cluster_alb_dns_name != null && var.cluster_alb_dns_name != "")
error_message = "cluster_alb_dns_name is required when the domains list includes a custom (non-wildcard) domain."
}
precondition {
condition = length(local.custom_domains) == 0 || (var.cluster_alb_zone_id != null && var.cluster_alb_zone_id != "")
error_message = "cluster_alb_zone_id is required when the domains list includes a custom (non-wildcard) domain."
}
```
How can I resolve this? If you propose a fix, please make it concise.| lifecycle { | ||
| precondition { | ||
| condition = !var.use_ravion_managed_domains || (var.ravion_aws_account_id != null && var.ravion_aws_account_id != "") | ||
| error_message = "ravion_aws_account_id (aws_*) is required when use_ravion_managed_domains = true." | ||
| } | ||
| precondition { | ||
| condition = length(var.domains) <= 10 | ||
| error_message = "A static site may declare at most 10 custom domains." | ||
| } | ||
| } |
There was a problem hiding this comment.
ravion_distribution_arn and ravion_distribution_domain both use try(...[0], null), which silently returns null if the module.cdn distribution maps are empty (i.e., no distributions are configured). These null values are then passed as target_arn to ravion_certificate.site and as target_dns_name to ravion_domain.custom with no guard, producing a cryptic provider-side error instead of a clear Terraform message. A precondition on ravion_certificate.site would surface this early.
| lifecycle { | |
| precondition { | |
| condition = !var.use_ravion_managed_domains || (var.ravion_aws_account_id != null && var.ravion_aws_account_id != "") | |
| error_message = "ravion_aws_account_id (aws_*) is required when use_ravion_managed_domains = true." | |
| } | |
| precondition { | |
| condition = length(var.domains) <= 10 | |
| error_message = "A static site may declare at most 10 custom domains." | |
| } | |
| } | |
| lifecycle { | |
| precondition { | |
| condition = !var.use_ravion_managed_domains || (var.ravion_aws_account_id != null && var.ravion_aws_account_id != "") | |
| error_message = "ravion_aws_account_id (aws_*) is required when use_ravion_managed_domains = true." | |
| } | |
| precondition { | |
| condition = length(var.domains) <= 10 | |
| error_message = "A static site may declare at most 10 custom domains." | |
| } | |
| precondition { | |
| condition = !var.use_ravion_managed_domains || local.ravion_distribution_arn != null | |
| error_message = "use_ravion_managed_domains = true requires at least one CloudFront distribution in var.distributions." | |
| } | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: hosting/static_site/ravion_domains.tf
Line: 34-43
Comment:
`ravion_distribution_arn` and `ravion_distribution_domain` both use `try(...[0], null)`, which silently returns `null` if the `module.cdn` distribution maps are empty (i.e., no distributions are configured). These null values are then passed as `target_arn` to `ravion_certificate.site` and as `target_dns_name` to `ravion_domain.custom` with no guard, producing a cryptic provider-side error instead of a clear Terraform message. A precondition on `ravion_certificate.site` would surface this early.
```suggestion
lifecycle {
precondition {
condition = !var.use_ravion_managed_domains || (var.ravion_aws_account_id != null && var.ravion_aws_account_id != "")
error_message = "ravion_aws_account_id (aws_*) is required when use_ravion_managed_domains = true."
}
precondition {
condition = length(var.domains) <= 10
error_message = "A static site may declare at most 10 custom domains."
}
precondition {
condition = !var.use_ravion_managed_domains || local.ravion_distribution_arn != null
error_message = "use_ravion_managed_domains = true requires at least one CloudFront distribution in var.distributions."
}
}
```
How can I resolve this? If you propose a fix, please make it concise.ravion_certificate/ravion_domain now require module_instance_id so the resources work outside a Ravion stack run (service-account API key) as well as inside it. Add a module_instance_id variable to ecs_cluster + ecs_service and forward it to every ravion_certificate/ravion_domain block (cluster wildcard cert; service wildcard/custom domains + instance cert). Inside a stack run the runner injects TF_VAR_module_instance_id, so this is populated automatically; external/API-key runs set it explicitly. The control plane still prefers the signed token's instance when a stack-run JWT is present. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Conflict resolution + cross-repo reconciliation: - alb/ecs_cluster listener config re-expressed in main's renamed variables (http_listener_enabled, https_listener_enabled, http_to_https_redirect_enabled, public/private_alb_enabled, *_https_enabled) while keeping this branch's cluster-owned HTTPS listener architecture and force_http_to_https_redirect - target_groups.tf taken from main: its tg_1/tg_2 + ignore_changes=[name] design subsumes this branch's stable-name comment; ravion_target_group_arn now reads tg_1 - dropped ravion_dns_collision_check / ravion_apex_dependents / ravion_parent_apex_check data sources and the ravion_cluster_dependent_domains output: the provider now runs these checks in each resource's ModifyPlan (PR 6456 review rework) and removed the data sources - dropped role= on ravion_aws_acm_certificate (now computed from wildcard) - tofu validate passes for networking/alb, compute/ecs_cluster, compute/ecs_service against the provider built from merged domains-restore Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ravion Module Publish PlanDry run only. No Ravion API mutations were made.
Diffsrvn-aws-alb 0.1.0 -> 0.1.1--- remote
+++ compiled
The load balancer intentionally serves no traffic by itself. Services that reference this module, such as EC2 services, create their own target groups and listener rules on the shared listeners.
- Terraform source: [flightcontrolhq/modules/networking/alb](https://github.com/flightcontrolhq/modules/tree/rvn-aws-alb@0.1.0/networking/alb)
+ Terraform source: [flightcontrolhq/modules/networking/alb](https://github.com/flightcontrolhq/modules/tree/rvn-aws-alb@0.1.1/networking/alb)
## Use cases
@@
base_path: networking/alb
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-aws-alb@0.1.0
+ ref: rvn-aws-alb@0.1.1
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables:rvn-aws-static 0.3.5 -> 0.3.6--- remote
+++ compiled
label: Default version prefix
required: true
type: string
+ - id: section_domains
+ label: Domains
+ type: section
+ - add_button_label: Add domain alias
+ default: []
+ description: Custom domain names like app.example.com. Leave empty to use the default cloudfront.net domain.
+ id: distribution_aliases
+ label: Domain aliases
+ type: string_array
+ - description: Required if using domain aliases. The certificate module must be in us-east-1.
+ id: distribution_acm_certificate
+ label: ACM certificate
+ mapped_inputs:
+ - default: <<ref.stack.output.certificate_arn>>
+ id: distribution_acm_certificate_arn
+ label: CloudFront certificate ARN
+ type: string
+ required: false
+ type: $ref:rvn-acm-certificate
- id: section_build
label: Build config
type: section
@@
- id: section_cloudfront
label: CloudFront settings
type: section
- - description: Required if using domain aliases. The certificate module must be in us-east-1.
- id: distribution_acm_certificate
- label: ACM certificate
- mapped_inputs:
- - default: <<ref.stack.output.certificate_arn>>
- id: distribution_acm_certificate_arn
- label: CloudFront certificate ARN
- type: string
- required: false
- type: $ref:rvn-acm-certificate
- - add_button_label: Add domain alias
- default: []
- description: Custom domain names like app.example.com. Leave empty to use the default cloudfront.net domain.
- id: distribution_aliases
- label: Domain aliases
- type: string_array
- default: PriceClass_All
description: You can reduce edge locations for some cost savings if it becomes an issue.
id: price_class
@@
Every deployment is versioned. The deploy step promotes an S3 directory by updating the CloudFront KeyValueStore active pointer. CloudFront rewrites viewer requests to the active version prefix before it reads from S3.
- Terraform source: [flightcontrolhq/modules/hosting/static_site](https://github.com/flightcontrolhq/modules/tree/rvn-aws-static@0.3.5/hosting/static_site)
+ Terraform source: [flightcontrolhq/modules/hosting/static_site](https://github.com/flightcontrolhq/modules/tree/rvn-aws-static@0.3.6/hosting/static_site)
## Use cases
@@
base_path: hosting/static_site
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-aws-static@0.3.5
+ ref: rvn-aws-static@0.3.6
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables:rvn-ecs-cluster 0.5.0 -> 0.6.2--- remote
+++ compiled
value: enabled
- label: Disabled
value: disabled
+ - id: section_managed_domains
+ label: Managed domains
+ type: section
+ - default: false
+ description: Have Ravion issue and manage a shared wildcard TLS certificate and DNS for this cluster. The selected ALB HTTPS listener serves the wildcard certificate, and services get automatic domains under the cluster apex. Requires exactly one HTTPS-enabled ALB.
+ id: use_ravion_managed_domains
+ label: Ravion-managed domains
+ type: boolean
+ - description: Subdomain for the cluster's wildcard domain. Defaults to the module given ID when empty.
+ id: ravion_cluster_name
+ label: Cluster subdomain
+ patterns:
+ - message: The subdomain must contain only lowercase letters, numbers, and hyphens, and start and end with a letter or number.
+ pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$
+ required: false
+ show_when:
+ use_ravion_managed_domains: true
+ suffix: .<< platform.apex >>
+ type: string
- id: section_capacity
label: Capacity providers
type: section
@@
show_when:
public_alb_enabled: true
public_alb_https_enabled: true
+ use_ravion_managed_domains: false
type: $ref:rvn-acm-certificate
- add_button_label: Add certificate ARN
collapsible: true
@@
show_when:
public_alb_enabled: true
public_alb_https_enabled: true
+ use_ravion_managed_domains: false
type: string_array
- collapsible: true
description: SSL policy for public ALB HTTPS.
@@
show_when:
private_alb_enabled: true
private_alb_https_enabled: true
+ use_ravion_managed_domains: false
type: $ref:rvn-acm-certificate
- add_button_label: Add certificate ARN
collapsible: true
@@
show_when:
private_alb_enabled: true
private_alb_https_enabled: true
+ use_ravion_managed_domains: false
type: string_array
- collapsible: true
description: SSL policy for private ALB HTTPS.
@@
- **Public and private Network Load Balancers** for TCP/UDP and static IP use cases
- **CloudWatch Container Insights** dashboard metrics for production visibility
- Terraform source: [flightcontrolhq/modules/compute/ecs_cluster](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-cluster@0.5.0/compute/ecs_cluster)
+ Terraform source: [flightcontrolhq/modules/compute/ecs_cluster](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-cluster@0.6.2/compute/ecs_cluster)
## Use cases
@@
base_path: compute/ecs_cluster
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-ecs-cluster@0.5.0
+ ref: rvn-ecs-cluster@0.6.2
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables:
@@
public_nlb_enabled: << module.input.public_nlb_enabled >>
public_nlb_security_group_ids: << module.input.public_nlb_security_group_ids >>
public_subnet_ids: << module.input.public_subnet_ids || [] >>
+ ravion_aws_account_id: << module.input.aws_account_id >>
+ ravion_aws_region: << module.input.aws_region >>
+ ravion_cluster_name: << module.input.ravion_cluster_name >>
region: << module.input.aws_region >>
tags:
...overrides: << module.input.tags >>
@@
ModuleId: <<module.id>>
Owner: Ravion
ProjectGivenId: <<project.given_id>>
+ use_ravion_managed_domains: << module.input.use_ravion_managed_domains >>
vpc_id: << module.input.vpc_id >>
tool: opentofu
tool_version: << module.input.opentofu_version || defaults.opentofu_version >>rvn-ecs-nlb 0.3.1 -> 0.3.2--- remote
+++ compiled
The module is intentionally focused on Layer 4 services behind a Network Load Balancer. Use ECS Web Service for HTTP host and path routing through an Application Load Balancer.
- Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-nlb@0.3.1/compute/ecs_service)
+ Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-nlb@0.3.2/compute/ecs_service)
## Use cases
@@
base_path: compute/ecs_service
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-ecs-nlb@0.3.1
+ ref: rvn-ecs-nlb@0.3.2
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables:rvn-ecs-web 0.8.4 -> 0.9.1--- remote
+++ compiled
immutable: true
label: Private ALB security group ID
type: string
+ - collapsible: true
+ default: << ref.stack.output.ravion_managed_domains_enabled || false >>
+ description: Whether the selected cluster has Ravion-managed domains enabled (its ALB HTTPS listener serves the cluster wildcard certificate).
+ id: ravion_managed_domains_enabled
+ label: Cluster-managed domains
+ type: boolean
+ - collapsible: true
+ default: <<ref.stack.output.ravion_cluster_domain_fqdn>>
+ description: Wildcard apex of the selected cluster. Service domains one label under it ride the cluster wildcard certificate automatically.
+ id: cluster_parent_fqdn
+ label: Cluster domain apex
+ required: false
+ type: string
+ - collapsible: true
+ default: <<ref.stack.output.public_alb_dns_name>>
+ id: public_alb_dns_name
+ immutable: true
+ label: Public ALB DNS name
+ required: false
+ type: string
+ - collapsible: true
+ default: <<ref.stack.output.public_alb_zone_id>>
+ id: public_alb_zone_id
+ immutable: true
+ label: Public ALB hosted zone ID
+ required: false
+ type: string
+ - collapsible: true
+ default: <<ref.stack.output.private_alb_dns_name>>
+ id: private_alb_dns_name
+ immutable: true
+ label: Private ALB DNS name
+ required: false
+ type: string
+ - collapsible: true
+ default: <<ref.stack.output.private_alb_zone_id>>
+ id: private_alb_zone_id
+ immutable: true
+ label: Private ALB hosted zone ID
+ required: false
+ type: string
required: true
type: $ref:rvn-ecs-cluster
- id: section_service
@@
id: private_subnet_placement_enabled
label: Run in private subnets
type: boolean
+ - id: section_domains
+ label: Domains
+ show_when:
+ ravion_managed_domains_enabled: true
+ type: section
+ - add_button_label: Add domain
+ description: Fully-qualified domains for this service. A name one label under the cluster apex rides the cluster wildcard certificate automatically; any other name gets a dedicated certificate with DNS validation records you add at your DNS host. Empty = an automatic <service>.<cluster apex> domain.
+ id: domains
+ label: Domains
+ max_length: 10
+ placeholder: app.example.com
+ show_when:
+ ravion_managed_domains_enabled: true
+ type: string_array
- id: section_build
label: Build config
type: section
@@
- description: Add at least one domain host rule or path rule.
id: section_routing
label: HTTP listener rules
+ show_when:
+ ravion_managed_domains_enabled: false
type: section
- add_button_label: Add domain host
default: null
@@
label: Domain host rules
placeholder: app.example.com
required: false
+ show_when:
+ ravion_managed_domains_enabled: false
type: string_array
- add_button_label: Add path pattern
collapsible: true
@@
id: path_pattern_values
label: Path rules
required: false
+ show_when:
+ ravion_managed_domains_enabled: false
type: string_array
- collapsible: true
description: Optional ALB listener rule priority. Leave blank to let AWS assign the next available priority.
@@
label: Listener rule priority
max: 50000
min: 1
+ show_when:
+ ravion_managed_domains_enabled: false
type: number
- id: section_task
label: Container resources
@@
The module is intentionally focused on web services behind an Application Load Balancer. It uses the selected ECS cluster to inherit AWS account, region, VPC, subnets, capacity providers, load balancer listeners, and load balancer security groups.
- Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-web@0.8.4/compute/ecs_service)
+ Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-web@0.9.1/compute/ecs_service)
## Use cases
@@
base_path: compute/ecs_service
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-ecs-web@0.8.4
+ ref: rvn-ecs-web@0.9.1
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables:
@@
- '...<< module.input.capacity_provider == "fargate" || module.input.additional_fargate_capacity_enabled ? [{capacity_provider: module.input.fargate_capacity_provider_name, weight: 1, base: 0}] : [] >>'
- '...<< module.input.capacity_provider == "fargate_spot" || module.input.additional_fargate_spot_capacity_enabled ? [{capacity_provider: module.input.fargate_spot_capacity_provider_name, weight: 1, base: 0}] : [] >>'
- '...<< module.input.capacity_provider == "ec2" || module.input.additional_ec2_capacity_enabled ? [{capacity_provider: module.input.ec2_capacity_provider_name, weight: 1, base: 0}] : [] >>'
+ cluster_alb_dns_name: "<< module.input.public_web_service_enabled ? module.input.public_alb_dns_name : module.input.private_alb_dns_name >>"
+ cluster_alb_zone_id: "<< module.input.public_web_service_enabled ? module.input.public_alb_zone_id : module.input.private_alb_zone_id >>"
cluster_arn: << module.input.cluster_arn >>
+ cluster_https_listener_arn: "<< module.input.public_web_service_enabled ? module.input.public_alb_https_listener_arn : module.input.private_alb_https_listener_arn >>"
+ cluster_parent_fqdn: << module.input.cluster_parent_fqdn >>
container_port: << module.input.container_port >>
deployment_maximum_percent: << module.input.deployment_maximum_percent >>
deployment_minimum_healthy_percent: << module.input.deployment_minimum_healthy_percent >>
deployment_type: rolling
desired_count: "<< module.input.auto_scaling_enabled ? module.input.min_capacity : module.input.desired_count >>"
+ domains: << module.input.domains || [] >>
ecr_default_lifecycle_policy_enabled: true
ecr_force_deletion_enabled: << module.input.ecr_force_deletion_enabled >>
ecr_repository_creation_enabled: << module.input.build_source == "dockerfile" || module.input.build_source == "railpack" || module.input.build_source == "nixpacks" >>
@@
network_mode: awsvpc
new_deployment_forcing_enabled: << module.input.new_deployment_forcing_enabled >>
public_ip_assignment_enabled: "<< module.input.private_subnet_placement_enabled ? false : true >>"
+ ravion_aws_account_id: << module.input.aws_account_id >>
+ ravion_aws_region: << module.input.aws_region >>
region: << module.input.aws_region >>
requires_compatibilities: '<< module.input.capacity_provider == "ec2" ? ["EC2"] : ["FARGATE"] >>'
runtime_platform:rvn-ecs-worker 0.3.3 -> 0.3.4--- remote
+++ compiled
The ECS Worker module creates an ECS service for background jobs, queue consumers, event processors, and other private workloads in an existing Ravion ECS cluster. It uses the same ECS service Terraform module as ECS Web Server, but does not create or attach a load balancer target group and does not expose a primary container port.
- Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-worker@0.3.3/compute/ecs_service)
+ Terraform source: [flightcontrolhq/modules/compute/ecs_service](https://github.com/flightcontrolhq/modules/tree/rvn-ecs-worker@0.3.4/compute/ecs_service)
## Use cases
@@
base_path: compute/ecs_service
branch: main
execution_environment_id: << module.input.execution_environment_id >>
- ref: rvn-ecs-worker@0.3.3
+ ref: rvn-ecs-worker@0.3.4
repo: https://github.com/flightcontrolhq/modules
stack_id: <<stack.id>>
terraform_variables: |
- listeners.tftest.hcl: rename to main's *_enabled variable names (terraform test silently ignores undeclared vars, so all 8 runs were planning with the ALBs disabled and failing their assertions) - hosting/static_site: port ravion_domains.tf off the pre-review provider API (ravion_certificate -> ravion_aws_acm_certificate, role removed, cert_arn/ fqdn -> arn/domain_name) and add the now-required module_instance_id to the certificate and routing-record resources; tofu validate passes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d of indexing the suppressed alb rule In managed mode aws_lb_listener_rule.alb is never created, so advanced_configuration.production_listener_rule (and the production_listener_rule_arn output) indexed an empty map and every Ravion-managed ALB service failed at plan with 'Invalid index'. - production listener rule in managed mode is now ravion chunk "0", so native traffic-shift deploys rewrite a rule that actually routes the managed hostnames - new precondition: managed ALB services require cluster_https_listener_arn (previously a clean apply that 404'd every hostname) - the listener_rules-required precondition no longer fires for managed services, which create their own rules from domains - the single-production-rule traffic-shift cap now covers managed chunks: blue_green/linear/canary need all hostnames in one chunk (<=5); rolling is uncapped - the green test rule is suppressed in managed mode (it mirrors caller listener_rules[0], which managed mode discards) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n YAMLs Closes the repo-vs-API divergence: the managed-domains Terraform was only reachable through API-published module versions, so the next repo publish would have silently dropped the feature. - rvn-ecs-cluster 0.6.0: Managed domains section (use_ravion_managed_domains toggle + ravion_cluster_name with a '.<< platform.apex >>' suffix adornment) wired to the stack terraform variables - rvn-ecs-web 0.9.0: cluster-ref mapped inputs (ravion_managed_domains_enabled, cluster_parent_fqdn, ALB dns/zone outputs), a domains string_array gated on the cluster toggle, and terraform variables selecting the public/private HTTPS listener + ALB target to match the service placement - rvn-aws-static 0.4.0: managed-domains toggle + domains, wired to the ported static_site variables All three pass make publish-local-dev DRY_RUN=1 against local api-go. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed-domains mode Instead of documenting 'leave these empty', gate them with show_when: - ecs_cluster: public/private ALB certificate inputs hide when use_ravion_managed_domains is on (the cluster serves the Ravion wildcard) - ecs_web: the manual HTTP listener-rule inputs hide when the selected cluster manages domains (the module creates host-header rules from domains); the mapped ravion_managed_domains_enabled default gets a || false fallback so services on older cluster versions keep them - static_site: CloudFront ACM certificate + domain aliases hide in managed mode All three still pass make publish-local-dev DRY_RUN=1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ing that error A cluster with use_ravion_managed_domains=true but *_alb_https_enabled=false issued the wildcard certificate and then had nowhere to put it: the HTTPS listener is gated on *_alb_https_enabled, so the cert was orphaned, the *_alb_https_listener_arn outputs stayed null, and every service nesting under the apex hit the cluster_https_listener_arn precondition with no hint that the cluster was the problem. - ecs_cluster: the managed-domains precondition now requires an ALB with HTTPS enabled, so this fails at the cluster with an actionable message instead of issuing a certificate that can never serve traffic - ecs_service: production_listener_rule and the production_listener_rule_arn output degrade to null when the ravion rules are absent, instead of failing with 'Invalid index' and burying the precondition that explains the cause Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s-first form UX - aws_lb_listener_rule.ravion now emits the same group-stickiness forward the BYO production rule has: ECS rewrites chunk 0 to a weighted forward during native traffic-shift deploys, and ELBv2 rejects that rewrite when a sticky target group lacks group-level stickiness on the action — managed-domains services with stickiness + blue/green failed where BYO worked - ravion_domain.custom preconditions on cluster_alb_dns_name/zone_id: a null routing target previously produced a clean apply and a domain that never resolves; now it is a plan-time message like the sibling cert's listener guard - module_instance_id preconditions on every ravion_* resource, mirroring the ravion_aws_account_id guard, so external runs get an actionable message instead of the provider's generic missing-required-argument - definitions: Managed domains section moves directly under the cluster basics (rvn-ecs-cluster 0.6.1) and the Domains input directly under the service basics (rvn-ecs-web 0.9.1); release notes carry the managed-domains headline since these are the first published releases containing the feature Suites: ecs_service 25/25, ecs_cluster managed_domains 6/6, defs compile + guardrails clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bling definitions - ecs_cluster/ecs_service now floor the ravion provider at 1.0.2 (published to the dev registry), the first version carrying the Create aws-id stamping, Unknown backstops, JSON-gated poll fail-fast, orphan-free create timeout, and Configure env fallback; lockfiles re-pinned accordingly - rvn-aws-alb 0.1.1 / rvn-ecs-nlb 0.3.2 / rvn-ecs-worker 0.3.4: their module source drifted since the last published versions and an identical version makes the publisher silently skip, so the source changes never ship Suites re-run on 1.0.2: ecs_cluster managed_domains 6/6, ecs_service 25/25. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ally runs The alb and ecs_cluster basic suites have been red on main since the variable renames, and OpenTofu aborts a file on first failure — so 12 alb runs and 22 cluster runs (including the entire public/private ALB + HTTPS matrix that managed domains rewires) were silently skipped, on main and here. - alb tests: internal -> internal_load_balancer_enabled, preserve_host_header -> host_header_preservation_enabled, waf_enabled now sets the waf_association_enabled gate, and the security-group assertions read module OUTPUTS instead of child-module internals (never addressable from tftest) - ecs_cluster tests: the ec2 egress/ingress assertions referenced root-module resources that never existed there (the rules live inside the security-groups child module); they now assert via module outputs, and the stale ALB security-group mock targets point into the alb module's nested security_group module so the sg- id validation is satisfied - networking/security-groups gains all_egress_rule_ids, compute/autoscaling gains mixed_instances_policy_enabled + spot_instances_distribution — small plan-known outputs so parents and tests can assert behavior without reaching into module internals Suites now: networking/alb 28/28 (was 13 pass / 3 fail / 12 skip), compute/ecs_cluster 58/58 across all files (was 35/1/22). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…naged mode takes over Enabling managed domains previously narrowed certificate_arns to just the Ravion wildcard, so the alb submodule's SNI attachments (arns[1..]) went empty and every hostname served off an existing BYO certificate lost TLS at cutover. The wildcard now becomes the DEFAULT certificate while all BYO certificates are carried forward as SNI attachments — the zero-downtime choice over refusing the toggle, matching the in-place-swap design of the feature. - networking/alb gains an additional_certificate_arns output so parents and tests can observe the SNI set without reaching into module internals - new test: managed_toggle_keeps_byo_certificates_attached (wildcard default + both BYO certs in the SNI set) - rvn-ecs-cluster 0.6.2 Suites: networking/alb 28/28, compute/ecs_cluster 59/59. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g basics distribution_aliases and the ACM certificate ref lived six sections deep inside CloudFront settings, so setting a custom domain meant scrolling past the entire build/Docker/Railpack block. They now form a Domains section (aliases first — the certificate is only meaningful once an alias exists) directly after the hosting basics, mirroring the cluster and web-service definitions. CloudFront settings keeps its own header starting at price_class, so nothing is orphaned. rvn-aws-static 0.3.6 (0.3.5 is already published). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Greptile Summary
This PR introduces Ravion-managed domains across ECS clusters, ECS services, and static sites.
Confidence Score: 3/5
The PR is not yet safe to merge because the incomplete private IPv6 state migration can interrupt HTTPS traffic during upgrades.
Existing managed-domain clusters can still recreate the renamed private IPv6 ingress rule because its moved block is absent, and ECS service custom domains still allow nullable ALB routing targets to reach the provider without clear validation.
Files Needing Attention: compute/ecs_cluster/listeners.tf; compute/ecs_service/ravion_domains.tf
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR Cluster["ECS cluster"] --> Cert["Ravion wildcard certificate"] Cert --> Listener["Cluster HTTPS listener"] Service["ECS service"] --> Rule["ALB host routing rule"] Rule --> Listener Service --> Domain["Managed or custom domain"] Domain --> ALB["Cluster ALB"] Site["Static site"] --> SiteCert["Ravion certificate"] SiteCert --> CDN["CloudFront distribution"] Site --> SiteDomain["Custom domain"] SiteDomain --> CDNReviews (2): Last reviewed commit: "feat(definitions): hide BYO certificate/..." | Re-trigger Greptile