From 8f33d7c3920e76a5dbf18fcb429a6bfd5b114b6a Mon Sep 17 00:00:00 2001 From: Javier Castiarena Date: Wed, 24 Jun 2026 17:42:17 -0300 Subject: [PATCH] feat(cloud/aws): make hosted_public_zone_id optional for private-only The aws-configuration provider config always included hosted_public_zone_id in the networking payload, and the API validates its format (^Z[A-Z0-9]{10,}$), rejecting an empty string. Private-only installs (no public hosted zone) had no valid value to pass, so the cloud config could not be registered. Make the variable optional (default "") and omit it from the payload when empty. Backward compatible: a non-empty value is still included. Adds a test asserting the key is absent when empty and the private zone is still present. Co-Authored-By: Claude Opus 4.8 --- nullplatform/cloud/aws/cloud/README.md | 8 +-- nullplatform/cloud/aws/cloud/main.tf | 25 ++++++--- .../aws/cloud/tests/aws_cloud.tftest.hcl | 53 +++++++++++++++++++ nullplatform/cloud/aws/cloud/variables.tf | 8 ++- 4 files changed, 82 insertions(+), 12 deletions(-) diff --git a/nullplatform/cloud/aws/cloud/README.md b/nullplatform/cloud/aws/cloud/README.md index 7bb080b5..bdec171a 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" {