diff --git a/nullplatform/cloud/aws/cloud/README.md b/nullplatform/cloud/aws/cloud/README.md index 76f60077..9a4bad9b 100644 --- a/nullplatform/cloud/aws/cloud/README.md +++ b/nullplatform/cloud/aws/cloud/README.md @@ -66,7 +66,7 @@ resource "example_resource" "this" { | [dimensions](#input\_dimensions) | Map of dimension values to configure nullplatform | `map(string)` | `{}` | no | | [domain\_name](#input\_domain\_name) | Domain name for the configuration | `string` | n/a | yes | | [hosted\_private\_zone\_id](#input\_hosted\_private\_zone\_id) | Hosted zone ID for private DNS | `string` | n/a | yes | -| [hosted\_public\_zone\_id](#input\_hosted\_public\_zone\_id) | Hosted zone ID for public DNS | `string` | n/a | yes | +| [hosted\_public\_zone\_id](#input\_hosted\_public\_zone\_id) | Hosted zone ID for public DNS. Leave empty for private-only installs: when empty it is omitted from the provider config payload (the API rejects an empty string). | `string` | `""` | no | | [include\_environment](#input\_include\_environment) | Whether to use Environment as a default dimension | `bool` | `true` | no | | [nrn](#input\_nrn) | Identifier Nullplatform Resources Name | `string` | n/a | yes | | [scope\_manager\_assume\_role](#input\_scope\_manager\_assume\_role) | ARN of the IAM role for scope and deploy manager | `string` | `"arn:aws:iam::283477532906:role/scope_and_deploy_manager"` | no | @@ -80,7 +80,7 @@ resource "example_resource" "this" { "features": [ "Creates a Nullplatform AWS provider configuration resource linking account identity and region", "Dynamically resolves current AWS account ID and region using data sources", - "Configures both private and public Route53 hosted zone IDs for DNS integration", + "Configures the private Route53 hosted zone ID for DNS integration; the public zone is optional and omitted for private-only installs", "Supports configurable dimension maps for multi-environment Nullplatform scoping", "Optionally includes account name in domain configuration via application_domain flag" ], @@ -102,8 +102,8 @@ resource "example_resource" "this" { }, { "name": "hosted_public_zone_id", - "description": "Hosted zone ID for public DNS", - "required": true + "description": "Hosted zone ID for public DNS. Leave empty for private-only installs: when empty it is omitted from the provider config payload (the API rejects an empty string).", + "required": false }, { "name": "scope_manager_assume_role", diff --git a/nullplatform/cloud/aws/cloud/main.tf b/nullplatform/cloud/aws/cloud/main.tf index eba697db..c27ed969 100644 --- a/nullplatform/cloud/aws/cloud/main.tf +++ b/nullplatform/cloud/aws/cloud/main.tf @@ -1,3 +1,19 @@ +locals { + # La API de nullplatform valida hosted_public_zone_id con ^Z[A-Z0-9]{10,}$ + # (verificado empíricamente) y rechaza el string vacío. Se incluye en el payload + # sólo cuando tiene valor → habilita instalaciones private-only (sin zona pública). + # El guard != null es defensivo (el default es "", pero un módulo caller podría + # pasar null explícitamente); la API no distingue null de "". + networking = merge( + { + application_domain = var.application_domain, + domain_name = var.domain_name + hosted_zone_id = var.hosted_private_zone_id + }, + var.hosted_public_zone_id != null && var.hosted_public_zone_id != "" ? { hosted_public_zone_id = var.hosted_public_zone_id } : {} + ) +} + resource "nullplatform_provider_config" "aws" { provider = nullplatform nrn = var.nrn @@ -8,11 +24,6 @@ resource "nullplatform_provider_config" "aws" { id = data.aws_caller_identity.current.id region = data.aws_region.current.region } - networking = { - application_domain = var.application_domain, - domain_name = var.domain_name - hosted_zone_id = var.hosted_private_zone_id - hosted_public_zone_id = var.hosted_public_zone_id - } + networking = local.networking }) -} \ No newline at end of file +} diff --git a/nullplatform/cloud/aws/cloud/tests/aws_cloud.tftest.hcl b/nullplatform/cloud/aws/cloud/tests/aws_cloud.tftest.hcl index 975e001a..3e391dcb 100644 --- a/nullplatform/cloud/aws/cloud/tests/aws_cloud.tftest.hcl +++ b/nullplatform/cloud/aws/cloud/tests/aws_cloud.tftest.hcl @@ -71,3 +71,56 @@ run "attributes_contain_networking" { error_message = "Attributes should contain the public hosted zone ID" } } + +run "private_only_omits_public_zone" { + command = plan + + variables { + hosted_public_zone_id = "" + } + + assert { + condition = !strcontains(nullplatform_provider_config.aws.attributes, "hosted_public_zone_id") + error_message = "hosted_public_zone_id must be omitted from the payload when empty (private-only); the API rejects an empty string" + } + + assert { + condition = strcontains(nullplatform_provider_config.aws.attributes, "Z1234567890PRIV") + error_message = "Private hosted zone ID must still be present in private-only mode" + } + + assert { + condition = strcontains(nullplatform_provider_config.aws.attributes, "example.com") + error_message = "domain_name must still be present in private-only mode" + } +} + +run "private_only_omits_public_zone_when_null" { + command = plan + + variables { + hosted_public_zone_id = null + } + + assert { + condition = !strcontains(nullplatform_provider_config.aws.attributes, "hosted_public_zone_id") + error_message = "hosted_public_zone_id must be omitted from the payload when null (private-only)" + } + + assert { + condition = strcontains(nullplatform_provider_config.aws.attributes, "Z1234567890PRIV") + error_message = "Private hosted zone ID must still be present when public zone is null" + } +} + +run "rejects_malformed_public_zone_id" { + command = plan + + variables { + hosted_public_zone_id = "not-a-zone-id" + } + + expect_failures = [ + var.hosted_public_zone_id, + ] +} diff --git a/nullplatform/cloud/aws/cloud/variables.tf b/nullplatform/cloud/aws/cloud/variables.tf index 8bafef14..d17df49c 100644 --- a/nullplatform/cloud/aws/cloud/variables.tf +++ b/nullplatform/cloud/aws/cloud/variables.tf @@ -26,8 +26,14 @@ variable "hosted_private_zone_id" { } variable "hosted_public_zone_id" { - description = "Hosted zone ID for public DNS" + description = "Hosted zone ID for public DNS. Leave empty for private-only installs: when empty it is omitted from the provider config payload (the API rejects an empty string)." type = string + default = "" + + validation { + condition = var.hosted_public_zone_id == null || var.hosted_public_zone_id == "" || can(regex("^Z[A-Z0-9]{10,}$", var.hosted_public_zone_id)) + error_message = "hosted_public_zone_id must be empty/null for private-only, or a valid Route53 hosted zone ID (^Z[A-Z0-9]{10,}$)." + } } variable "dimensions" {