diff --git a/.changelog/43282.txt b/.changelog/43282.txt new file mode 100644 index 000000000000..b21cdac7b483 --- /dev/null +++ b/.changelog/43282.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +data-source/aws_route53_resolver_rule: Add `delegation_record` argument +``` + +```release-note:enhancement +resource/aws_route53_resolver_rule: Add `delegation_record` argument +``` \ No newline at end of file diff --git a/internal/service/route53resolver/rule.go b/internal/service/route53resolver/rule.go index 831e697d6bab..ddcdabf0612d 100644 --- a/internal/service/route53resolver/rule.go +++ b/internal/service/route53resolver/rule.go @@ -51,9 +51,17 @@ func resourceRule() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "delegation_record": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 256), + ConflictsWith: []string{names.AttrDomainName}, + StateFunc: trimTrailingPeriod, + }, names.AttrDomainName: { Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 256), StateFunc: trimTrailingPeriod, @@ -125,11 +133,18 @@ func resourceRuleCreate(ctx context.Context, d *schema.ResourceData, meta any) d input := &route53resolver.CreateResolverRuleInput{ CreatorRequestId: aws.String(id.PrefixedUniqueId("tf-r53-resolver-rule-")), - DomainName: aws.String(d.Get(names.AttrDomainName).(string)), RuleType: awstypes.RuleTypeOption(d.Get("rule_type").(string)), Tags: getTagsIn(ctx), } + if v, ok := d.GetOk("delegation_record"); ok { + input.DelegationRecord = aws.String(v.(string)) + } + + if v, ok := d.GetOk(names.AttrDomainName); ok { + input.DomainName = aws.String(v.(string)) + } + if v, ok := d.GetOk(names.AttrName); ok { input.Name = aws.String(v.(string)) } @@ -174,9 +189,14 @@ func resourceRuleRead(ctx context.Context, d *schema.ResourceData, meta any) dia } d.Set(names.AttrARN, rule.Arn) + if rule.DelegationRecord != nil { + d.Set("delegation_record", trimTrailingPeriod(aws.ToString(rule.DelegationRecord))) + } // To be consistent with other AWS services that do not accept a trailing period, // we remove the suffix from the Domain Name returned from the API - d.Set(names.AttrDomainName, trimTrailingPeriod(aws.ToString(rule.DomainName))) + if rule.DomainName != nil { + d.Set(names.AttrDomainName, trimTrailingPeriod(aws.ToString(rule.DomainName))) + } d.Set(names.AttrName, rule.Name) d.Set(names.AttrOwnerID, rule.OwnerId) d.Set("resolver_endpoint_id", rule.ResolverEndpointId) @@ -415,8 +435,8 @@ func flattenRuleTargetIPs(targetAddresses []awstypes.TargetAddress) []any { } // trimTrailingPeriod is used to remove the trailing period -// of "name" or "domain name" attributes often returned from -// the Route53 API or provided as user input. +// of "name", "domain name" or "delegation_record" attributes +// often returned from the Route53 API or provided as user input. // The single dot (".") domain name is returned as-is. func trimTrailingPeriod(v any) string { var str string diff --git a/internal/service/route53resolver/rule_data_source.go b/internal/service/route53resolver/rule_data_source.go index dc6d53e097b6..339176dd9e2b 100644 --- a/internal/service/route53resolver/rule_data_source.go +++ b/internal/service/route53resolver/rule_data_source.go @@ -29,6 +29,12 @@ func dataSourceRule() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "delegation_record": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringLenBetween(1, 256), + }, names.AttrDomainName: { Type: schema.TypeString, Optional: true, @@ -124,9 +130,14 @@ func dataSourceRuleRead(ctx context.Context, d *schema.ResourceData, meta any) d d.SetId(aws.ToString(rule.Id)) arn := aws.ToString(rule.Arn) d.Set(names.AttrARN, arn) + if rule.DelegationRecord != nil { + d.Set("delegation_record", trimTrailingPeriod(aws.ToString(rule.DelegationRecord))) + } // To be consistent with other AWS services that do not accept a trailing period, // we remove the suffix from the Domain Name returned from the API - d.Set(names.AttrDomainName, trimTrailingPeriod(aws.ToString(rule.DomainName))) + if rule.DomainName != nil { + d.Set(names.AttrDomainName, trimTrailingPeriod(aws.ToString(rule.DomainName))) + } d.Set(names.AttrName, rule.Name) d.Set(names.AttrOwnerID, rule.OwnerId) d.Set("resolver_endpoint_id", rule.ResolverEndpointId) diff --git a/internal/service/route53resolver/rule_data_source_test.go b/internal/service/route53resolver/rule_data_source_test.go index 3ef69b635263..9173fcd3f4a5 100644 --- a/internal/service/route53resolver/rule_data_source_test.go +++ b/internal/service/route53resolver/rule_data_source_test.go @@ -180,6 +180,49 @@ func TestAccRoute53ResolverRuleDataSource_sharedWithMe(t *testing.T) { }) } +func TestAccRoute53ResolverRuleDataSource_delegationRecord(t *testing.T) { + ctx := acctest.Context(t) + delegationRecord := acctest.RandomDomainName() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_route53_resolver_rule.test" + ds1ResourceName := "data.aws_route53_resolver_rule.by_resolver_rule_id" + ds2ResourceName := "data.aws_route53_resolver_rule.by_name_and_rule_type" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ResolverServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccRuleDataSourceConfig_delegatationRecord(rName, delegationRecord), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(ds1ResourceName, names.AttrID, resourceName, names.AttrID), + resource.TestCheckResourceAttrPair(ds1ResourceName, names.AttrARN, resourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(ds1ResourceName, "delegation_record", resourceName, "delegation_record"), + resource.TestCheckResourceAttrPair(ds1ResourceName, names.AttrName, resourceName, names.AttrName), + resource.TestCheckResourceAttrPair(ds1ResourceName, names.AttrOwnerID, resourceName, names.AttrOwnerID), + resource.TestCheckResourceAttrPair(ds1ResourceName, "resolver_endpoint_id", resourceName, "resolver_endpoint_id"), + resource.TestCheckResourceAttrPair(ds1ResourceName, "resolver_rule_id", resourceName, names.AttrID), + resource.TestCheckResourceAttrPair(ds1ResourceName, "rule_type", resourceName, "rule_type"), + resource.TestCheckResourceAttrPair(ds1ResourceName, "share_status", resourceName, "share_status"), + resource.TestCheckResourceAttrPair(ds1ResourceName, acctest.CtTagsPercent, resourceName, acctest.CtTagsPercent), + + resource.TestCheckResourceAttrPair(ds2ResourceName, names.AttrID, resourceName, names.AttrID), + resource.TestCheckResourceAttrPair(ds2ResourceName, names.AttrARN, resourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(ds2ResourceName, "delegation_record", resourceName, "delegation_record"), + resource.TestCheckResourceAttrPair(ds2ResourceName, names.AttrName, resourceName, names.AttrName), + resource.TestCheckResourceAttrPair(ds2ResourceName, names.AttrOwnerID, resourceName, names.AttrOwnerID), + resource.TestCheckResourceAttrPair(ds2ResourceName, "resolver_endpoint_id", resourceName, "resolver_endpoint_id"), + resource.TestCheckResourceAttrPair(ds2ResourceName, "resolver_rule_id", resourceName, names.AttrID), + resource.TestCheckResourceAttrPair(ds2ResourceName, "rule_type", resourceName, "rule_type"), + resource.TestCheckResourceAttrPair(ds2ResourceName, "share_status", resourceName, "share_status"), + resource.TestCheckResourceAttrPair(ds2ResourceName, acctest.CtTagsPercent, resourceName, acctest.CtTagsPercent), + ), + }, + }, + }) +} + func testAccRuleDataSourceConfig_basic(rName, domainName string) string { return fmt.Sprintf(` resource "aws_route53_resolver_rule" "test" { @@ -203,6 +246,26 @@ data "aws_route53_resolver_rule" "by_name_and_rule_type" { `, rName, domainName) } +func testAccRuleDataSourceConfig_delegatationRecord(rName, delegationRecord string) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + delegation_record = %[2]q + rule_type = "DELEGATE" + name = %[1]q + resolver_endpoint_id = aws_route53_resolver_endpoint.test[1].id +} + +data "aws_route53_resolver_rule" "by_resolver_rule_id" { + resolver_rule_id = aws_route53_resolver_rule.test.id +} + +data "aws_route53_resolver_rule" "by_name_and_rule_type" { + name = aws_route53_resolver_rule.test.name + rule_type = aws_route53_resolver_rule.test.rule_type +} +`, rName, delegationRecord)) +} + func testAccRuleDataSourceConfig_resolverEndpointIDTags(rName, domainName string) string { return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), fmt.Sprintf(` resource "aws_route53_resolver_rule" "test" { diff --git a/internal/service/route53resolver/rule_test.go b/internal/service/route53resolver/rule_test.go index 074f5f8f449f..61874cf7aca3 100644 --- a/internal/service/route53resolver/rule_test.go +++ b/internal/service/route53resolver/rule_test.go @@ -227,6 +227,50 @@ func TestAccRoute53ResolverRule_updateName(t *testing.T) { }) } +func TestAccRoute53ResolverRule_delegationRecord(t *testing.T) { + ctx := acctest.Context(t) + var rule1 awstypes.ResolverRule + resourceName := "aws_route53_resolver_rule.test" + delegationRecord := acctest.RandomDomainName() + ep1ResourceName := "aws_route53_resolver_endpoint.test.0" + ep2ResourceName := "aws_route53_resolver_endpoint.test.1" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.Route53ResolverServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckRuleDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccRuleConfig_delegationRecord(rName, delegationRecord, 0), + Check: resource.ComposeTestCheckFunc( + testAccCheckRuleExists(ctx, resourceName, &rule1), + resource.TestCheckResourceAttr(resourceName, "delegation_record", delegationRecord), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, "rule_type", "DELEGATE"), + resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", ep1ResourceName, names.AttrID), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccRuleConfig_delegationRecord(rName, delegationRecord, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckRuleExists(ctx, resourceName, &rule1), + resource.TestCheckResourceAttr(resourceName, "delegation_record", delegationRecord), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, "rule_type", "DELEGATE"), + resource.TestCheckResourceAttrPair(resourceName, "resolver_endpoint_id", ep2ResourceName, names.AttrID), + ), + }, + }, + }) +} + func TestAccRoute53ResolverRule_forward(t *testing.T) { ctx := acctest.Context(t) var rule1, rule2, rule3 awstypes.ResolverRule @@ -670,6 +714,17 @@ resource "aws_route53_resolver_rule" "test" { `, rName, domainName) } +func testAccRuleConfig_delegationRecord(rName, delegationRecord string, resolverEndpointId int) string { + return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), fmt.Sprintf(` +resource "aws_route53_resolver_rule" "test" { + delegation_record = %[2]q + rule_type = "DELEGATE" + name = %[1]q + + resolver_endpoint_id = aws_route53_resolver_endpoint.test[%[3]d].id +} +`, rName, delegationRecord, resolverEndpointId)) +} func testAccRuleConfig_forward(rName, domainName string) string { return acctest.ConfigCompose(testAccRuleConfig_resolverEndpointBase(rName), fmt.Sprintf(` resource "aws_route53_resolver_rule" "test" { diff --git a/website/docs/d/route53_resolver_rule.html.markdown b/website/docs/d/route53_resolver_rule.html.markdown index 0ebf14efaff7..8e79a82af995 100644 --- a/website/docs/d/route53_resolver_rule.html.markdown +++ b/website/docs/d/route53_resolver_rule.html.markdown @@ -26,11 +26,12 @@ data "aws_route53_resolver_rule" "example" { This data source supports the following arguments: * `region` - (Optional) Region where this resource will be [managed](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference). +* `delegation_record` - (Optional) DNS queries with the delegation records that match this domain name are forwarded to the resolvers on your network. * `domain_name` - (Optional) Domain name the desired resolver rule forwards DNS queries for. Conflicts with `resolver_rule_id`. * `name` - (Optional) Friendly name of the desired resolver rule. Conflicts with `resolver_rule_id`. * `resolver_endpoint_id` (Optional) ID of the outbound resolver endpoint of the desired resolver rule. Conflicts with `resolver_rule_id`. * `resolver_rule_id` (Optional) ID of the desired resolver rule. Conflicts with `domain_name`, `name`, `resolver_endpoint_id` and `rule_type`. -* `rule_type` - (Optional) Rule type of the desired resolver rule. Valid values are `FORWARD`, `SYSTEM` and `RECURSIVE`. Conflicts with `resolver_rule_id`. +* `rule_type` - (Optional) Rule type of the desired resolver rule. Valid values are `DELEGATE`, `FORWARD`, `SYSTEM` and `RECURSIVE`. Conflicts with `resolver_rule_id`. ## Attribute Reference diff --git a/website/docs/r/route53_resolver_rule.html.markdown b/website/docs/r/route53_resolver_rule.html.markdown index 1cec8dc1ebeb..9b62adcb09a9 100644 --- a/website/docs/r/route53_resolver_rule.html.markdown +++ b/website/docs/r/route53_resolver_rule.html.markdown @@ -64,8 +64,9 @@ resource "aws_route53_resolver_rule" "fwd" { This resource supports the following arguments: * `region` - (Optional) Region where this resource will be [managed](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference). -* `domain_name` - (Required) DNS queries for this domain name are forwarded to the IP addresses that are specified using `target_ip`. -* `rule_type` - (Required) Rule type. Valid values are `FORWARD`, `SYSTEM` and `RECURSIVE`. +* `delegation_record` - (Optional) DNS queries with the delegation records that match this domain name are forwarded to the resolvers on your network. +* `domain_name` - (Optional) DNS queries for this domain name are forwarded to the IP addresses that are specified using `target_ip`. +* `rule_type` - (Required) Rule type. Valid values are `DELEGATE`, `FORWARD`, `SYSTEM` and `RECURSIVE`. * `name` - (Optional) Friendly name that lets you easily find a rule in the Resolver dashboard in the Route 53 console. * `resolver_endpoint_id` (Optional) ID of the outbound resolver endpoint that you want to use to route DNS queries to the IP addresses that you specify using `target_ip`. This argument should only be specified for `FORWARD` type rules.