From 2f2e55dd13dbab235013afefb027bdd499360865 Mon Sep 17 00:00:00 2001 From: Anthony Wat Date: Sun, 21 Jul 2024 15:27:58 -0400 Subject: [PATCH] feat: Add attrs related to endpoint v2 for aws_opensearch_domain --- .changelog/38456.txt | 7 ++ internal/service/opensearch/domain.go | 40 ++++-- .../service/opensearch/domain_data_source.go | 36 +++++- internal/service/opensearch/domain_test.go | 118 +++++++++++++++++- .../docs/d/opensearch_domain.html.markdown | 3 + .../docs/r/opensearch_domain.html.markdown | 3 + 6 files changed, 189 insertions(+), 18 deletions(-) create mode 100644 .changelog/38456.txt diff --git a/.changelog/38456.txt b/.changelog/38456.txt new file mode 100644 index 00000000000..8290457cc11 --- /dev/null +++ b/.changelog/38456.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +resource/aws_opensearch_domain: Add `dashboard_endpoint_v2`, `domain_endpoint_v2_hosted_zone_id`, and `endpoint_v2` attributes +``` + +```release-note:enhancement +data-source/aws_opensearch_domain: Add `dashboard_endpoint_v2`, `domain_endpoint_v2_hosted_zone_id`, and `endpoint_v2` attributes +``` \ No newline at end of file diff --git a/internal/service/opensearch/domain.go b/internal/service/opensearch/domain.go index 3fbf4b443e0..d9e9b29dfee 100644 --- a/internal/service/opensearch/domain.go +++ b/internal/service/opensearch/domain.go @@ -352,6 +352,10 @@ func ResourceDomain() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "dashboard_endpoint_v2": { + Type: schema.TypeString, + Computed: true, + }, "domain_endpoint_options": { Type: schema.TypeList, Optional: true, @@ -389,6 +393,10 @@ func ResourceDomain() *schema.Resource { }, }, }, + "domain_endpoint_v2_hosted_zone_id": { + Type: schema.TypeString, + Computed: true, + }, "domain_id": { Type: schema.TypeString, Computed: true, @@ -461,6 +469,10 @@ func ResourceDomain() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "endpoint_v2": { + Type: schema.TypeString, + Computed: true, + }, names.AttrEngineVersion: { Type: schema.TypeString, Optional: true, @@ -859,6 +871,7 @@ func resourceDomainRead(ctx context.Context, d *schema.ResourceData, meta interf d.SetId(aws.StringValue(ds.ARN)) d.Set(names.AttrARN, ds.ARN) + d.Set("domain_endpoint_v2_hosted_zone_id", ds.DomainEndpointV2HostedZoneId) d.Set("domain_id", ds.DomainId) d.Set(names.AttrDomainName, ds.DomainName) d.Set(names.AttrEngineVersion, ds.EngineVersion) @@ -918,16 +931,27 @@ func resourceDomainRead(ctx context.Context, d *schema.ResourceData, meta interf endpoints := flex.FlattenStringMap(ds.Endpoints) d.Set(names.AttrEndpoint, endpoints["vpc"]) - d.Set("dashboard_endpoint", getDashboardEndpoint(d)) - d.Set("kibana_endpoint", getKibanaEndpoint(d)) + d.Set("dashboard_endpoint", getDashboardEndpoint(d.Get(names.AttrEndpoint).(string))) + d.Set("kibana_endpoint", getKibanaEndpoint(d.Get(names.AttrEndpoint).(string))) + if endpoints["vpcv2"] != nil { + d.Set("endpoint_v2", endpoints["vpcv2"]) + d.Set("dashboard_endpoint_v2", getDashboardEndpoint(d.Get("endpoint_v2").(string))) + } if ds.Endpoint != nil { return sdkdiag.AppendErrorf(diags, "%q: OpenSearch Domain in VPC expected to have null Endpoint value", d.Id()) } + if ds.EndpointV2 != nil { + return sdkdiag.AppendErrorf(diags, "%q: OpenSearch Domain in VPC expected to have null EndpointV2 value", d.Id()) + } } else { if ds.Endpoint != nil { d.Set(names.AttrEndpoint, ds.Endpoint) - d.Set("dashboard_endpoint", getDashboardEndpoint(d)) - d.Set("kibana_endpoint", getKibanaEndpoint(d)) + d.Set("dashboard_endpoint", getDashboardEndpoint(d.Get(names.AttrEndpoint).(string))) + d.Set("kibana_endpoint", getKibanaEndpoint(d.Get(names.AttrEndpoint).(string))) + } + if ds.EndpointV2 != nil { + d.Set("endpoint_v2", ds.EndpointV2) + d.Set("dashboard_endpoint_v2", getDashboardEndpoint(d.Get("endpoint_v2").(string))) } if ds.Endpoints != nil { return sdkdiag.AppendErrorf(diags, "%q: OpenSearch Domain not in VPC expected to have null Endpoints value", d.Id()) @@ -1193,12 +1217,12 @@ func suppressEquivalentKMSKeyIDs(k, old, new string, d *schema.ResourceData) boo return strings.Contains(old, new) } -func getDashboardEndpoint(d *schema.ResourceData) string { - return d.Get(names.AttrEndpoint).(string) + "/_dashboards" +func getDashboardEndpoint(endpoint string) string { + return endpoint + "/_dashboards" } -func getKibanaEndpoint(d *schema.ResourceData) string { - return d.Get(names.AttrEndpoint).(string) + "/_plugin/kibana/" +func getKibanaEndpoint(endpoint string) string { + return endpoint + "/_plugin/kibana/" } func suppressComputedDedicatedMaster(k, old, new string, d *schema.ResourceData) bool { diff --git a/internal/service/opensearch/domain_data_source.go b/internal/service/opensearch/domain_data_source.go index 851b0d7eea2..34edb2d0d94 100644 --- a/internal/service/opensearch/domain_data_source.go +++ b/internal/service/opensearch/domain_data_source.go @@ -213,10 +213,18 @@ func DataSourceDomain() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "dashboard_endpoint_v2": { + Type: schema.TypeString, + Computed: true, + }, "deleted": { Type: schema.TypeBool, Computed: true, }, + "domain_endpoint_v2_hosted_zone_id": { + Type: schema.TypeString, + Computed: true, + }, "domain_id": { Type: schema.TypeString, Computed: true, @@ -273,6 +281,10 @@ func DataSourceDomain() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "endpoint_v2": { + Type: schema.TypeString, + Computed: true, + }, names.AttrEngineVersion: { Type: schema.TypeString, Computed: true, @@ -455,10 +467,11 @@ func dataSourceDomainRead(ctx context.Context, d *schema.ResourceData, meta inte } d.Set(names.AttrARN, ds.ARN) + d.Set("domain_endpoint_v2_hosted_zone_id", ds.DomainEndpointV2HostedZoneId) d.Set("domain_id", ds.DomainId) d.Set(names.AttrEndpoint, ds.Endpoint) - d.Set("dashboard_endpoint", getDashboardEndpoint(d)) - d.Set("kibana_endpoint", getKibanaEndpoint(d)) + d.Set("dashboard_endpoint", getDashboardEndpoint(d.Get(names.AttrEndpoint).(string))) + d.Set("kibana_endpoint", getKibanaEndpoint(d.Get(names.AttrEndpoint).(string))) if err := d.Set("advanced_security_options", flattenAdvancedSecurityOptions(ds.AdvancedSecurityOptions)); err != nil { return sdkdiag.AppendErrorf(diags, "setting advanced_security_options: %s", err) @@ -503,16 +516,27 @@ func dataSourceDomainRead(ctx context.Context, d *schema.ResourceData, meta inte if err := d.Set(names.AttrEndpoint, endpoints["vpc"]); err != nil { return sdkdiag.AppendErrorf(diags, "setting endpoint: %s", err) } - d.Set("dashboard_endpoint", getDashboardEndpoint(d)) - d.Set("kibana_endpoint", getKibanaEndpoint(d)) + d.Set("dashboard_endpoint", getDashboardEndpoint(d.Get(names.AttrEndpoint).(string))) + d.Set("kibana_endpoint", getKibanaEndpoint(d.Get(names.AttrEndpoint).(string))) + if endpoints["vpcv2"] != nil { + d.Set("endpoint_v2", endpoints["vpcv2"]) + d.Set("dashboard_endpoint_v2", getDashboardEndpoint(d.Get("endpoint_v2").(string))) + } if ds.Endpoint != nil { return sdkdiag.AppendErrorf(diags, "%q: OpenSearch domain in VPC expected to have null Endpoint value", d.Id()) } + if ds.EndpointV2 != nil { + return sdkdiag.AppendErrorf(diags, "%q: OpenSearch Domain in VPC expected to have null EndpointV2 value", d.Id()) + } } else { if ds.Endpoint != nil { d.Set(names.AttrEndpoint, ds.Endpoint) - d.Set("dashboard_endpoint", getDashboardEndpoint(d)) - d.Set("kibana_endpoint", getKibanaEndpoint(d)) + d.Set("dashboard_endpoint", getDashboardEndpoint(d.Get(names.AttrEndpoint).(string))) + d.Set("kibana_endpoint", getKibanaEndpoint(d.Get(names.AttrEndpoint).(string))) + } + if ds.EndpointV2 != nil { + d.Set("endpoint_v2", ds.EndpointV2) + d.Set("dashboard_endpoint_v2", getDashboardEndpoint(d.Get("endpoint_v2").(string))) } if ds.Endpoints != nil { return sdkdiag.AppendErrorf(diags, "%q: OpenSearch domain not in VPC expected to have null Endpoints value", d.Id()) diff --git a/internal/service/opensearch/domain_test.go b/internal/service/opensearch/domain_test.go index a216cdda0b7..797f0311112 100644 --- a/internal/service/opensearch/domain_test.go +++ b/internal/service/opensearch/domain_test.go @@ -763,6 +763,50 @@ func TestAccOpenSearchDomain_VPC_internetToVPCEndpoint(t *testing.T) { }) } +func TestAccOpenSearchDomain_VPC_ipAddressType(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var domain opensearchservice.DomainStatus + rName := testAccRandomDomainName() + resourceName := "aws_opensearch_domain.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheckIAMServiceLinkedRole(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.OpenSearchServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckDomainDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccDomainConfig_vpcIPAddressType(rName, "dualstack"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainExists(ctx, resourceName, &domain), + resource.TestMatchResourceAttr(resourceName, "dashboard_endpoint_v2", regexache.MustCompile(`.+?\.on\.aws\/_dashboards`)), + resource.TestMatchResourceAttr(resourceName, "endpoint_v2", regexache.MustCompile(`.+?\.on\.aws`)), + resource.TestCheckResourceAttr(resourceName, names.AttrIPAddressType, "dualstack"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateId: rName, + ImportStateVerify: true, + }, + { + Config: testAccDomainConfig_vpcIPAddressType(rName, "ipv4"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDomainExists(ctx, resourceName, &domain), + resource.TestCheckNoResourceAttr(resourceName, "dashboard_endpoint_v2"), + resource.TestCheckNoResourceAttr(resourceName, "endpoint_v2"), + resource.TestCheckResourceAttr(resourceName, names.AttrIPAddressType, "ipv4"), + ), + }, + }, + }) +} + func TestAccOpenSearchDomain_ipAddressType(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -783,8 +827,8 @@ func TestAccOpenSearchDomain_ipAddressType(t *testing.T) { Config: testAccDomainConfig_ipAddressType(rName, "dualstack"), Check: resource.ComposeTestCheckFunc( testAccCheckDomainExists(ctx, resourceName, &domain), - resource.TestMatchResourceAttr(resourceName, "dashboard_endpoint", regexache.MustCompile(`.*(opensearch|es)\..*/_dashboards`)), - resource.TestCheckResourceAttrSet(resourceName, names.AttrEngineVersion), + resource.TestMatchResourceAttr(resourceName, "dashboard_endpoint_v2", regexache.MustCompile(`.+?\.on\.aws\/_dashboards`)), + resource.TestMatchResourceAttr(resourceName, "endpoint_v2", regexache.MustCompile(`.+?\.on\.aws`)), resource.TestCheckResourceAttr(resourceName, names.AttrIPAddressType, "dualstack"), ), }, @@ -798,8 +842,8 @@ func TestAccOpenSearchDomain_ipAddressType(t *testing.T) { Config: testAccDomainConfig_ipAddressType(rName, "ipv4"), Check: resource.ComposeTestCheckFunc( testAccCheckDomainExists(ctx, resourceName, &domain), - resource.TestMatchResourceAttr(resourceName, "dashboard_endpoint", regexache.MustCompile(`.*(opensearch|es)\..*/_dashboards`)), - resource.TestCheckResourceAttrSet(resourceName, names.AttrEngineVersion), + resource.TestCheckNoResourceAttr(resourceName, "dashboard_endpoint_v2"), + resource.TestCheckNoResourceAttr(resourceName, "endpoint_v2"), resource.TestCheckResourceAttr(resourceName, names.AttrIPAddressType, "ipv4"), ), }, @@ -3137,6 +3181,72 @@ resource "aws_opensearch_domain" "test" { `, rName)) } +func testAccDomainConfig_vpcIPAddressType(rName, ipAddressType string) string { + return acctest.ConfigCompose( + acctest.ConfigAvailableAZsNoOptIn(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "192.168.0.0/22" + assign_generated_ipv6_cidr_block = true + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "192.168.0.0/24" + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 4, 0) + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test2" { + vpc_id = aws_vpc.test.id + availability_zone = data.aws_availability_zones.available.names[1] + cidr_block = "192.168.1.0/24" + ipv6_cidr_block = cidrsubnet(aws_vpc.test.ipv6_cidr_block, 4, 1) + + tags = { + Name = %[1]q + } +} + +resource "aws_security_group" "test" { + vpc_id = aws_vpc.test.id +} + +resource "aws_security_group" "test2" { + vpc_id = aws_vpc.test.id +} + +resource "aws_opensearch_domain" "test" { + domain_name = %[1]q + ip_address_type = %[2]q + + ebs_options { + ebs_enabled = true + volume_size = 10 + } + + cluster_config { + instance_count = 2 + zone_awareness_enabled = true + instance_type = "t2.small.search" + } + + vpc_options { + security_group_ids = [aws_security_group.test.id, aws_security_group.test2.id] + subnet_ids = [aws_subnet.test.id, aws_subnet.test2.id] + } +} +`, rName, ipAddressType)) +} + func testAccDomainConfig_vpcUpdate1(rName string) string { return acctest.ConfigCompose( acctest.ConfigAvailableAZsNoOptIn(), diff --git a/website/docs/d/opensearch_domain.html.markdown b/website/docs/d/opensearch_domain.html.markdown index 074b591433e..051cd873ebd 100644 --- a/website/docs/d/opensearch_domain.html.markdown +++ b/website/docs/d/opensearch_domain.html.markdown @@ -66,7 +66,9 @@ This data source exports the following attributes in addition to the arguments a * `role_arn` - IAM Role with the AmazonOpenSearchServiceCognitoAccess policy attached. * `created` – Status of the creation of the domain. * `dashboard_endpoint` - Domain-specific endpoint used to access the [Dashboard application](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/dashboards.html). +* `dashboard_endpoint_v2` - V2 domain-specific endpoint used to access the [Dashboard application](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/dashboards.html) * `deleted` – Status of the deletion of the domain. +* `domain_endpoint_v2_hosted_zone_id` - Dual stack hosted zone ID for the domain. * `domain_id` – Unique identifier for the domain. * `ebs_options` - EBS Options for the instances in the domain. * `ebs_enabled` - Whether EBS volumes are attached to data nodes in the domain. @@ -79,6 +81,7 @@ This data source exports the following attributes in addition to the arguments a * `enabled` - Whether encryption at rest is enabled in the domain. * `kms_key_id` - KMS key id used to encrypt data at rest. * `endpoint` – Domain-specific endpoint used to submit index, search, and data upload requests. +* `endpoint_v2` - V2 domain-specific endpoint that works with both IPv4 and IPv6 addresses, used to submit index, search, and data upload requests. * `ip_address_type` - Type of IP addresses supported by the endpoint for the domain. * `kibana_endpoint` - (**Deprecated**) Domain-specific endpoint for kibana without https scheme. Use the `dashboard_endpoint` attribute instead. * `log_publishing_options` - Domain log publishing related options. diff --git a/website/docs/r/opensearch_domain.html.markdown b/website/docs/r/opensearch_domain.html.markdown index a43b3bb7c18..9797816697e 100644 --- a/website/docs/r/opensearch_domain.html.markdown +++ b/website/docs/r/opensearch_domain.html.markdown @@ -475,10 +475,13 @@ AWS documentation: [Off Peak Hours Support for Amazon OpenSearch Service Domains This resource exports the following attributes in addition to the arguments above: * `arn` - ARN of the domain. +* `domain_endpoint_v2_hosted_zone_id` - Dual stack hosted zone ID for the domain. * `domain_id` - Unique identifier for the domain. * `domain_name` - Name of the OpenSearch domain. * `endpoint` - Domain-specific endpoint used to submit index, search, and data upload requests. +* `endpoint_v2` - V2 domain endpoint that works with both IPv4 and IPv6 addresses, used to submit index, search, and data upload requests. * `dashboard_endpoint` - Domain-specific endpoint for Dashboard without https scheme. +* `dashboard_endpoint_v2` - V2 domain endpoint for Dashboard that works with both IPv4 and IPv6 addresses, without https scheme. * `kibana_endpoint` - (**Deprecated**) Domain-specific endpoint for kibana without https scheme. Use the `dashboard_endpoint` attribute instead. * `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). * `vpc_options.0.availability_zones` - If the domain was created inside a VPC, the names of the availability zones the configured `subnet_ids` were created inside.