diff --git a/docs/data-sources/dns_zone.md b/docs/data-sources/dns_zone.md index d78b49d6..1b7a5cec 100644 --- a/docs/data-sources/dns_zone.md +++ b/docs/data-sources/dns_zone.md @@ -25,6 +25,10 @@ data "stackit_dns_zone" "example" { ### Required - `project_id` (String) STACKIT project ID to which the dns zone is associated. + +### Optional + +- `dns_name` (String) The zone name. E.g. `example.com` - `zone_id` (String) The zone ID. ### Read-Only @@ -34,7 +38,6 @@ data "stackit_dns_zone" "example" { - `contact_email` (String) A contact e-mail for the zone. - `default_ttl` (Number) Default time to live. - `description` (String) Description of the zone. -- `dns_name` (String) The zone name. E.g. `example.com` - `expire_time` (Number) Expire time. - `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`zone_id`". - `is_reverse_zone` (Boolean) Specifies, if the zone is a reverse zone or not. diff --git a/stackit/internal/services/dns/dns_acc_test.go b/stackit/internal/services/dns/dns_acc_test.go index eeb391fa..201ab118 100644 --- a/stackit/internal/services/dns/dns_acc_test.go +++ b/stackit/internal/services/dns/dns_acc_test.go @@ -131,8 +131,8 @@ func TestAccDnsMinResource(t *testing.T) { Config: resourceMinConfig, ConfigVariables: testConfigVarsMin, Check: resource.ComposeAggregateTestCheckFunc( - // Zone data - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "project_id", testutil.ProjectId), + // Zone data by zone_id + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "project_id", testutil.ProjectId), resource.TestCheckResourceAttrPair( "stackit_dns_zone.zone", "zone_id", "data.stackit_dns_zone.zone", "zone_id", @@ -150,6 +150,21 @@ func TestAccDnsMinResource(t *testing.T) { "data.stackit_dns_record_set.record_set", "project_id", ), + // Zone data by dns_name + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttrPair( + "stackit_dns_zone.zone", "zone_id", + "data.stackit_dns_zone.zone_name", "zone_id", + ), + resource.TestCheckResourceAttrPair( + "data.stackit_dns_record_set.record_set", "zone_id", + "data.stackit_dns_zone.zone_name", "zone_id", + ), + resource.TestCheckResourceAttrPair( + "data.stackit_dns_record_set.record_set", "project_id", + "data.stackit_dns_zone.zone_name", "project_id", + ), + // Record set data resource.TestCheckResourceAttrSet("data.stackit_dns_record_set.record_set", "record_set_id"), resource.TestCheckResourceAttrSet("data.stackit_dns_record_set.record_set", "name"), @@ -297,8 +312,8 @@ func TestAccDnsMaxResource(t *testing.T) { Config: resourceMaxConfig, ConfigVariables: testConfigVarsMax, Check: resource.ComposeAggregateTestCheckFunc( - // Zone data - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "project_id", testutil.ProjectId), + // Zone data by zone_id + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "project_id", testutil.ProjectId), resource.TestCheckResourceAttrPair( "stackit_dns_zone.zone", "zone_id", "data.stackit_dns_zone.zone", "zone_id", @@ -316,24 +331,59 @@ func TestAccDnsMaxResource(t *testing.T) { "stackit_dns_record_set.record_set", "project_id", ), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "acl", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "active", testutil.ConvertConfigVariable(testConfigVarsMax["active"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "contact_email", testutil.ConvertConfigVariable(testConfigVarsMax["contact_email"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "default_ttl", testutil.ConvertConfigVariable(testConfigVarsMax["default_ttl"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "description", testutil.ConvertConfigVariable(testConfigVarsMax["description"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "expire_time", testutil.ConvertConfigVariable(testConfigVarsMax["expire_time"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "is_reverse_zone", testutil.ConvertConfigVariable(testConfigVarsMax["is_reverse_zone"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "primaries.#", "1"), - resource.TestCheckResourceAttrSet("stackit_dns_zone.zone", "primaries.0"), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "refresh_time", testutil.ConvertConfigVariable(testConfigVarsMax["refresh_time"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "retry_time", testutil.ConvertConfigVariable(testConfigVarsMax["retry_time"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "type", testutil.ConvertConfigVariable(testConfigVarsMax["type"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "dns_name", testutil.ConvertConfigVariable(testConfigVarsMax["dns_name"])), - resource.TestCheckResourceAttr("stackit_dns_zone.zone", "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])), - // resource.TestCheckResourceAttrSet("stackit_dns_zone.zone", "negative_cache"), - resource.TestCheckResourceAttrSet("stackit_dns_zone.zone", "serial_number"), - resource.TestCheckResourceAttrSet("stackit_dns_zone.zone", "state"), - resource.TestCheckResourceAttrSet("stackit_dns_zone.zone", "visibility"), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "acl", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "active", testutil.ConvertConfigVariable(testConfigVarsMax["active"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "contact_email", testutil.ConvertConfigVariable(testConfigVarsMax["contact_email"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "default_ttl", testutil.ConvertConfigVariable(testConfigVarsMax["default_ttl"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "description", testutil.ConvertConfigVariable(testConfigVarsMax["description"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "expire_time", testutil.ConvertConfigVariable(testConfigVarsMax["expire_time"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "is_reverse_zone", testutil.ConvertConfigVariable(testConfigVarsMax["is_reverse_zone"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "primaries.#", "1"), + resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone", "primaries.0"), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "refresh_time", testutil.ConvertConfigVariable(testConfigVarsMax["refresh_time"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "retry_time", testutil.ConvertConfigVariable(testConfigVarsMax["retry_time"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "type", testutil.ConvertConfigVariable(testConfigVarsMax["type"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "dns_name", testutil.ConvertConfigVariable(testConfigVarsMax["dns_name"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone", "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])), + // resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone", "negative_cache"), + resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone", "serial_number"), + resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone", "state"), + resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone", "visibility"), + + // Zone data by dns_name + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttrPair( + "stackit_dns_zone.zone", "zone_id", + "data.stackit_dns_zone.zone_name", "zone_id", + ), + resource.TestCheckResourceAttrPair( + "data.stackit_dns_record_set.record_set", "zone_id", + "data.stackit_dns_zone.zone_name", "zone_id", + ), + resource.TestCheckResourceAttrPair( + "data.stackit_dns_record_set.record_set", "project_id", + "data.stackit_dns_zone.zone_name", "project_id", + ), + + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "acl", testutil.ConvertConfigVariable(testConfigVarsMax["acl"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "active", testutil.ConvertConfigVariable(testConfigVarsMax["active"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "contact_email", testutil.ConvertConfigVariable(testConfigVarsMax["contact_email"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "default_ttl", testutil.ConvertConfigVariable(testConfigVarsMax["default_ttl"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "description", testutil.ConvertConfigVariable(testConfigVarsMax["description"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "expire_time", testutil.ConvertConfigVariable(testConfigVarsMax["expire_time"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "is_reverse_zone", testutil.ConvertConfigVariable(testConfigVarsMax["is_reverse_zone"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "primaries.#", "1"), + resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone_name", "primaries.0"), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "refresh_time", testutil.ConvertConfigVariable(testConfigVarsMax["refresh_time"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "retry_time", testutil.ConvertConfigVariable(testConfigVarsMax["retry_time"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "type", testutil.ConvertConfigVariable(testConfigVarsMax["type"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "dns_name", testutil.ConvertConfigVariable(testConfigVarsMax["dns_name"])), + resource.TestCheckResourceAttr("data.stackit_dns_zone.zone_name", "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])), + // resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone_name", "negative_cache"), + resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone_name", "serial_number"), + resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone_name", "state"), + resource.TestCheckResourceAttrSet("data.stackit_dns_zone.zone_name", "visibility"), + // Record set data resource.TestCheckResourceAttrSet("data.stackit_dns_record_set.record_set", "record_set_id"), resource.TestCheckResourceAttrSet("data.stackit_dns_record_set.record_set", "name"), diff --git a/stackit/internal/services/dns/testdata/resource-max.tf b/stackit/internal/services/dns/testdata/resource-max.tf index 6903e1b5..27d6894e 100644 --- a/stackit/internal/services/dns/testdata/resource-max.tf +++ b/stackit/internal/services/dns/testdata/resource-max.tf @@ -36,10 +36,10 @@ resource "stackit_dns_zone" "zone" { expire_time = var.expire_time is_reverse_zone = var.is_reverse_zone # negative_cache = var.negative_cache - primaries = var.primaries - refresh_time = var.refresh_time - retry_time = var.retry_time - type = var.type + primaries = var.primaries + refresh_time = var.refresh_time + retry_time = var.retry_time + type = var.type } @@ -62,6 +62,11 @@ data "stackit_dns_zone" "zone" { zone_id = stackit_dns_zone.zone.zone_id } +data "stackit_dns_zone" "zone_name" { + project_id = var.project_id + dns_name = stackit_dns_zone.zone.dns_name +} + data "stackit_dns_record_set" "record_set" { project_id = var.project_id zone_id = stackit_dns_zone.zone.zone_id diff --git a/stackit/internal/services/dns/testdata/resource-min.tf b/stackit/internal/services/dns/testdata/resource-min.tf index 18b160d9..2a99b33c 100644 --- a/stackit/internal/services/dns/testdata/resource-min.tf +++ b/stackit/internal/services/dns/testdata/resource-min.tf @@ -29,6 +29,11 @@ data "stackit_dns_zone" "zone" { zone_id = stackit_dns_zone.zone.zone_id } +data "stackit_dns_zone" "zone_name" { + project_id = var.project_id + dns_name = stackit_dns_zone.zone.dns_name +} + data "stackit_dns_record_set" "record_set" { project_id = var.project_id zone_id = stackit_dns_zone.zone.zone_id diff --git a/stackit/internal/services/dns/zone/datasource.go b/stackit/internal/services/dns/zone/datasource.go index 4a9ee500..682c87df 100644 --- a/stackit/internal/services/dns/zone/datasource.go +++ b/stackit/internal/services/dns/zone/datasource.go @@ -5,6 +5,8 @@ import ( "fmt" "net/http" + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" dnsUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/dns/utils" @@ -39,6 +41,16 @@ func (d *zoneDataSource) Metadata(_ context.Context, req datasource.MetadataRequ resp.TypeName = req.ProviderTypeName + "_dns_zone" } +// ConfigValidators validates the resource configuration +func (d *zoneDataSource) ConfigValidators(_ context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.ExactlyOneOf( + path.MatchRoot("zone_id"), + path.MatchRoot("dns_name"), + ), + } +} + func (d *zoneDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) if !ok { @@ -72,7 +84,7 @@ func (d *zoneDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r }, "zone_id": schema.StringAttribute{ Description: "The zone ID.", - Required: true, + Optional: true, Validators: []validator.String{ validate.UUID(), validate.NoSeparator(), @@ -84,7 +96,7 @@ func (d *zoneDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r }, "dns_name": schema.StringAttribute{ Description: "The zone name. E.g. `example.com`", - Computed: true, + Optional: true, }, "description": schema.StringAttribute{ Description: "Description of the zone.", @@ -169,24 +181,65 @@ func (d *zoneDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } projectId := model.ProjectId.ValueString() zoneId := model.ZoneId.ValueString() + dnsName := model.DnsName.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "zone_id", zoneId) + ctx = tflog.SetField(ctx, "dns_name", dnsName) - zoneResp, err := d.client.GetZone(ctx, projectId, zoneId).Execute() - if err != nil { - utils.LogError( - ctx, - &resp.Diagnostics, - err, - "Reading zone", - fmt.Sprintf("Zone with ID %q does not exist in project %q.", zoneId, projectId), - map[int]string{ - http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), - }, - ) - resp.State.RemoveResource(ctx) - return + var zoneResp *dns.ZoneResponse + var err error + + if zoneId != "" { + zoneResp, err = d.client.GetZone(ctx, projectId, zoneId).Execute() + if err != nil { + utils.LogError( + ctx, + &resp.Diagnostics, + err, + "Reading zone", + fmt.Sprintf("Zone with ID %q does not exist in project %q.", zoneId, projectId), + map[int]string{ + http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), + }, + ) + resp.State.RemoveResource(ctx) + return + } + } else { + listZoneResp, err := d.client.ListZones(ctx, projectId). + DnsNameEq(dnsName). + ActiveEq(true). + Execute() + if err != nil { + utils.LogError( + ctx, + &resp.Diagnostics, + err, + "Reading zone", + fmt.Sprintf("Zone with DNS name %q does not exist in project %q.", dnsName, projectId), + map[int]string{ + http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId), + }, + ) + resp.State.RemoveResource(ctx) + return + } + if *listZoneResp.TotalItems != 1 { + utils.LogError( + ctx, + &resp.Diagnostics, + fmt.Errorf("zone with DNS name %q does not exist in project %q", dnsName, projectId), + "Reading zone", + fmt.Sprintf("Zone with DNS name %q does not exist in project %q.", dnsName, projectId), + nil, + ) + resp.State.RemoveResource(ctx) + return + } + zones := *listZoneResp.Zones + zoneResp = dns.NewZoneResponse(zones[0]) } + if zoneResp != nil && zoneResp.Zone.State != nil && *zoneResp.Zone.State == dns.ZONESTATE_DELETE_SUCCEEDED { resp.State.RemoveResource(ctx) core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading zone", "Zone was deleted successfully")