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" {