diff --git a/internal/services/domain/helpers.go b/internal/services/domain/helpers.go index 3adbd7536b..428b01f8f1 100644 --- a/internal/services/domain/helpers.go +++ b/internal/services/domain/helpers.go @@ -623,3 +623,11 @@ func FlattenDSRecord(dsRecords []*domain.DSRecord) []any { return results } + +func BuildZoneName(subdomain, domain string) string { + if subdomain == "" { + return domain + } + + return fmt.Sprintf("%s.%s", subdomain, domain) +} diff --git a/internal/services/domain/testdata/domain-zone-root-zone.cassette.yaml b/internal/services/domain/testdata/domain-zone-root-zone.cassette.yaml new file mode 100644 index 0000000000..c31257c694 --- /dev/null +++ b/internal/services/domain/testdata/domain-zone-root-zone.cassette.yaml @@ -0,0 +1,346 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/domain/v2beta1/dns-zones?dns_zones=scaleway-terraform.com&domain=&order_by=domain_asc + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 353 + uncompressed: false + body: '{"dns_zones":[{"domain":"scaleway-terraform.com","linked_products":[],"message":null,"ns":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_default":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_master":[],"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","status":"active","subdomain":"","updated_at":"2025-07-10T01:32:17Z"}],"total_count":1}' + headers: + Content-Length: + - "353" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 10 Jul 2025 12:28:13 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge02) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 9795d279-d40b-4308-829a-7c05455e20fc + status: 200 OK + code: 200 + duration: 580.768208ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/domain/v2beta1/dns-zones?dns_zones=scaleway-terraform.com&domain=&order_by=domain_asc + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 353 + uncompressed: false + body: '{"dns_zones":[{"domain":"scaleway-terraform.com","linked_products":[],"message":null,"ns":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_default":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_master":[],"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","status":"active","subdomain":"","updated_at":"2025-07-10T01:32:17Z"}],"total_count":1}' + headers: + Content-Length: + - "353" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 10 Jul 2025 12:28:13 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge02) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - d9179ec6-2a4a-4ce8-bb71-a9f221ea9a45 + status: 200 OK + code: 200 + duration: 124.617584ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/domain/v2beta1/dns-zones?dns_zones=scaleway-terraform.com&domain=&order_by=domain_asc + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 353 + uncompressed: false + body: '{"dns_zones":[{"domain":"scaleway-terraform.com","linked_products":[],"message":null,"ns":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_default":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_master":[],"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","status":"active","subdomain":"","updated_at":"2025-07-10T01:32:17Z"}],"total_count":1}' + headers: + Content-Length: + - "353" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 10 Jul 2025 12:28:13 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge02) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - e9c2c2e2-552c-44ad-a258-540c78454539 + status: 200 OK + code: 200 + duration: 165.313583ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/domain/v2beta1/dns-zones?dns_zones=scaleway-terraform.com&domain=&order_by=domain_asc&project_id=105bdce1-64c0-48ab-899d-868455867ecf + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 353 + uncompressed: false + body: '{"dns_zones":[{"domain":"scaleway-terraform.com","linked_products":[],"message":null,"ns":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_default":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_master":[],"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","status":"active","subdomain":"","updated_at":"2025-07-10T01:32:17Z"}],"total_count":1}' + headers: + Content-Length: + - "353" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 10 Jul 2025 12:28:14 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge02) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - e79b782a-af86-4049-8e88-4a9977d76b38 + status: 200 OK + code: 200 + duration: 342.27975ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/domain/v2beta1/dns-zones?dns_zone=scaleway-terraform.com&domain=&order_by=domain_asc + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 353 + uncompressed: false + body: '{"dns_zones":[{"domain":"scaleway-terraform.com","linked_products":[],"message":null,"ns":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_default":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_master":[],"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","status":"active","subdomain":"","updated_at":"2025-07-10T01:32:17Z"}],"total_count":1}' + headers: + Content-Length: + - "353" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 10 Jul 2025 12:28:15 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge02) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 25a108ab-06fe-4b02-a6a0-d72bddf2fd66 + status: 200 OK + code: 200 + duration: 130.822292ms + - id: 5 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/domain/v2beta1/dns-zones/scaleway-terraform.com?project_id=105bdce1-64c0-48ab-899d-868455867ecf + method: DELETE + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 40 + uncompressed: false + body: '{"message":"root zone can''t be deleted"}' + headers: + Content-Length: + - "40" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 10 Jul 2025 12:28:15 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge02) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 4e28cbd4-9e4e-4f58-8c5b-f7b8f33613d9 + status: 403 Forbidden + code: 403 + duration: 73.538916ms + - id: 6 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.scaleway.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + User-Agent: + - scaleway-sdk-go/v1.0.0-beta.7+dev (go1.24.0; darwin; arm64) terraform-provider/develop terraform/terraform-tests + url: https://api.scaleway.com/domain/v2beta1/dns-zones?dns_zones=scaleway-terraform.com&domain=&order_by=domain_asc + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 353 + uncompressed: false + body: '{"dns_zones":[{"domain":"scaleway-terraform.com","linked_products":[],"message":null,"ns":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_default":["ns0.dom.scw.cloud","ns1.dom.scw.cloud"],"ns_master":[],"project_id":"105bdce1-64c0-48ab-899d-868455867ecf","status":"active","subdomain":"","updated_at":"2025-07-10T01:32:17Z"}],"total_count":1}' + headers: + Content-Length: + - "353" + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none' + Content-Type: + - application/json + Date: + - Thu, 10 Jul 2025 12:28:15 GMT + Server: + - Scaleway API Gateway (fr-par-3;edge02) + Strict-Transport-Security: + - max-age=63072000 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + X-Request-Id: + - 3b2e5b39-7a1a-4ec1-9e8d-428f51ade5a1 + status: 200 OK + code: 200 + duration: 123.945083ms diff --git a/internal/services/domain/zone.go b/internal/services/domain/zone.go index 4f5cc21806..f35e56ab9b 100644 --- a/internal/services/domain/zone.go +++ b/internal/services/domain/zone.go @@ -88,7 +88,7 @@ func resourceDomainZoneCreate(ctx context.Context, d *schema.ResourceData, m any domainName := strings.ToLower(d.Get("domain").(string)) subdomainName := strings.ToLower(d.Get("subdomain").(string)) - zoneName := fmt.Sprintf("%s.%s", subdomainName, domainName) + zoneName := BuildZoneName(subdomainName, domainName) zones, err := domainAPI.ListDNSZones(&domain.ListDNSZonesRequest{ ProjectID: types.ExpandStringPtr(d.Get("project_id")), @@ -100,7 +100,7 @@ func resourceDomainZoneCreate(ctx context.Context, d *schema.ResourceData, m any for i := range zones.DNSZones { if zones.DNSZones[i].Domain == domainName && zones.DNSZones[i].Subdomain == subdomainName { - d.SetId(fmt.Sprintf("%s.%s", subdomainName, domainName)) + d.SetId(BuildZoneName(subdomainName, domainName)) return resourceDomainZoneRead(ctx, d, m) } @@ -121,7 +121,7 @@ func resourceDomainZoneCreate(ctx context.Context, d *schema.ResourceData, m any return diag.FromErr(err) } - d.SetId(fmt.Sprintf("%s.%s", dnsZone.Subdomain, dnsZone.Domain)) + d.SetId(BuildZoneName(dnsZone.Subdomain, dnsZone.Domain)) return resourceDomainZoneRead(ctx, d, m) } diff --git a/internal/services/domain/zone_data_source.go b/internal/services/domain/zone_data_source.go index 24ed38606b..e8aaf0c1ab 100644 --- a/internal/services/domain/zone_data_source.go +++ b/internal/services/domain/zone_data_source.go @@ -2,7 +2,6 @@ package domain import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -22,7 +21,7 @@ func DataSourceZone() *schema.Resource { } func DataSourceZoneRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - d.SetId(fmt.Sprintf("%s.%s", d.Get("subdomain").(string), d.Get("domain").(string))) + d.SetId(BuildZoneName(d.Get("subdomain").(string), d.Get("domain").(string))) return resourceDomainZoneRead(ctx, d, m) } diff --git a/internal/services/domain/zone_test.go b/internal/services/domain/zone_test.go index 1b617a030d..d97ac1a20e 100644 --- a/internal/services/domain/zone_test.go +++ b/internal/services/domain/zone_test.go @@ -43,6 +43,36 @@ func TestAccDomainZone_Basic(t *testing.T) { }) } +func TestAccDomainZone_RootZone(t *testing.T) { + tt := acctest.NewTestTools(t) + defer tt.Cleanup() + + logging.L.Debugf("TestAccDomainZone_RootZone: test root zone with domain: %s", acctest.TestDomain) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProviderFactories: tt.ProviderFactories, + CheckDestroy: testAccCheckDomainZoneDestroy(tt), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "scaleway_domain_zone" "test" { + domain = "%s" + subdomain = "" + } + `, acctest.TestDomain), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainZoneExists(tt, "scaleway_domain_zone.test"), + resource.TestCheckResourceAttr("scaleway_domain_zone.test", "subdomain", ""), + resource.TestCheckResourceAttr("scaleway_domain_zone.test", "domain", acctest.TestDomain), + resource.TestCheckResourceAttr("scaleway_domain_zone.test", "status", "active"), + resource.TestCheckResourceAttr("scaleway_domain_zone.test", "id", acctest.TestDomain), + ), + }, + }, + }) +} + func testAccCheckDomainZoneExists(tt *acctest.TestTools, n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -53,7 +83,7 @@ func testAccCheckDomainZoneExists(tt *acctest.TestTools, n string) resource.Test domainAPI := domain.NewDomainAPI(tt.Meta) listDNSZones, err := domainAPI.ListDNSZones(&domainSDK.ListDNSZonesRequest{ - DNSZones: []string{fmt.Sprintf("%s.%s", rs.Primary.Attributes["subdomain"], rs.Primary.Attributes["domain"])}, + DNSZones: []string{domain.BuildZoneName(rs.Primary.Attributes["subdomain"], rs.Primary.Attributes["domain"])}, }) if err != nil { return err @@ -80,7 +110,7 @@ func testAccCheckDomainZoneDestroy(tt *acctest.TestTools) resource.TestCheckFunc // check if the zone still exists domainAPI := domain.NewDomainAPI(tt.Meta) listDNSZones, err := domainAPI.ListDNSZones(&domainSDK.ListDNSZonesRequest{ - DNSZones: []string{fmt.Sprintf("%s.%s", rs.Primary.Attributes["subdomain"], rs.Primary.Attributes["domain"])}, + DNSZones: []string{domain.BuildZoneName(rs.Primary.Attributes["subdomain"], rs.Primary.Attributes["domain"])}, }) if httperrors.Is403(err) { // forbidden: subdomain not found @@ -92,6 +122,11 @@ func testAccCheckDomainZoneDestroy(tt *acctest.TestTools) resource.TestCheckFunc } if listDNSZones.TotalCount > 0 { + // Root zones cannot be deleted, so we accept that they still exist + if rs.Primary.Attributes["subdomain"] == "" { + return nil + } + return fmt.Errorf("zone %s still exist for domain: %s", rs.Primary.Attributes["subdomain"], rs.Primary.Attributes["domain"])