Skip to content

Commit

Permalink
Merge pull request #39846 from hashicorp/f/opensearch-auth-vpc-endpt-…
Browse files Browse the repository at this point in the history
…access

f/New Resource Authorize VPC Endpoint Access
  • Loading branch information
nam054 authored Oct 29, 2024
2 parents f337f4f + 9881d7a commit 1fefd6b
Show file tree
Hide file tree
Showing 8 changed files with 499 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .changelog/39846.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_opensearch_authorize_vpc_endpoint_access
```
223 changes: 223 additions & 0 deletions internal/service/opensearch/authorize_vpc_endpoint_access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package opensearch

import (
"context"
"errors"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/opensearch"
awstypes "github.com/aws/aws-sdk-go-v2/service/opensearch/types"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
"github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @FrameworkResource("aws_opensearch_authorize_vpc_endpoint_access", name="Authorize VPC Endpoint Access")
func newResourceAuthorizeVPCEndpointAccess(_ context.Context) (resource.ResourceWithConfigure, error) {
r := &resourceAuthorizeVPCEndpointAccess{}

return r, nil
}

const (
ResNameAuthorizeVPCEndpointAccess = "Authorize Vpc Endpoint Access"
)

type resourceAuthorizeVPCEndpointAccess struct {
framework.ResourceWithConfigure
framework.WithImportByID
framework.WithNoUpdate
}

func (r *resourceAuthorizeVPCEndpointAccess) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "aws_opensearch_authorize_vpc_endpoint_access"
}

func (r *resourceAuthorizeVPCEndpointAccess) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"account": schema.StringAttribute{
Required: true, PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
names.AttrDomainName: schema.StringAttribute{
Required: true,
},
"authorized_principal": schema.ListAttribute{
CustomType: fwtypes.NewListNestedObjectTypeOf[authorizedPrincipalData](ctx),
Computed: true,
PlanModifiers: []planmodifier.List{
listplanmodifier.UseStateForUnknown(),
},
},
},
}
}

func (r *resourceAuthorizeVPCEndpointAccess) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
conn := r.Meta().OpenSearchClient(ctx)

var plan resourceAuthorizeVPCEndpointAccessData
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

in := &opensearch.AuthorizeVpcEndpointAccessInput{
Account: plan.Account.ValueStringPointer(),
DomainName: plan.DomainName.ValueStringPointer(),
}

resp.Diagnostics.Append(flex.Expand(ctx, plan, in)...)
if resp.Diagnostics.HasError() {
return
}

out, err := conn.AuthorizeVpcEndpointAccess(ctx, in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.OpenSearch, create.ErrActionCreating, ResNameAuthorizeVPCEndpointAccess, plan.DomainName.String(), err),
err.Error(),
)
return
}

if out == nil || out.AuthorizedPrincipal == nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.OpenSearch, create.ErrActionCreating, ResNameAuthorizeVPCEndpointAccess, plan.DomainName.String(), nil),
errors.New("empty output").Error(),
)
return
}

resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}

func (r *resourceAuthorizeVPCEndpointAccess) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
conn := r.Meta().OpenSearchClient(ctx)

var state resourceAuthorizeVPCEndpointAccessData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

out, err := findAuthorizeVPCEndpointAccessByName(ctx, conn, state.DomainName.ValueString())
if tfresource.NotFound(err) {
resp.State.RemoveResource(ctx)
return
}
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.OpenSearch, create.ErrActionSetting, ResNameAuthorizeVPCEndpointAccess, state.DomainName.String(), err),
err.Error(),
)
return
}

resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *resourceAuthorizeVPCEndpointAccess) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
conn := r.Meta().OpenSearchClient(ctx)

var state resourceAuthorizeVPCEndpointAccessData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

in := &opensearch.RevokeVpcEndpointAccessInput{
Account: state.Account.ValueStringPointer(),
DomainName: state.DomainName.ValueStringPointer(),
}

_, err := conn.RevokeVpcEndpointAccess(ctx, in)
if err != nil {
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return
}
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.OpenSearch, create.ErrActionDeleting, ResNameAuthorizeVPCEndpointAccess, state.DomainName.String(), err),
err.Error(),
)
return
}
}

func (r *resourceAuthorizeVPCEndpointAccess) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root(names.AttrDomainName), req, resp)
}

func findAuthorizeVPCEndpointAccessByName(ctx context.Context, conn *opensearch.Client, domainName string) (*awstypes.AuthorizedPrincipal, error) {
in := &opensearch.ListVpcEndpointAccessInput{
DomainName: aws.String(domainName),
}

return findAuthorizeVPCEndpointAccess(ctx, conn, in)
}

func findAuthorizeVPCEndpointAccess(ctx context.Context, conn *opensearch.Client, input *opensearch.ListVpcEndpointAccessInput) (*awstypes.AuthorizedPrincipal, error) {
output, err := findAuthorizeVPCEndpointAccesses(ctx, conn, input)

if err != nil {
return nil, err
}

return tfresource.AssertSingleValueResult(output)
}

func findAuthorizeVPCEndpointAccesses(ctx context.Context, conn *opensearch.Client, input *opensearch.ListVpcEndpointAccessInput) ([]awstypes.AuthorizedPrincipal, error) {
var output []awstypes.AuthorizedPrincipal

err := listVPCEndpointAccessPages(ctx, conn, input, func(page *opensearch.ListVpcEndpointAccessOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

output = append(output, page.AuthorizedPrincipalList...)

return !lastPage
})

if err != nil {
return nil, err
}

return output, nil
}

type resourceAuthorizeVPCEndpointAccessData struct {
Account types.String `tfsdk:"account"`
DomainName types.String `tfsdk:"domain_name"`
AuthorizedPrincipal fwtypes.ListNestedObjectValueOf[authorizedPrincipalData] `tfsdk:"authorized_principal"`
}

type authorizedPrincipalData struct {
Principal types.String `tfsdk:"principal"`
PrincipalType types.String `tfsdk:"principal_type"`
}
168 changes: 168 additions & 0 deletions internal/service/opensearch/authorize_vpc_endpoint_access_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package opensearch_test

import (
"context"
"errors"
"fmt"
"testing"

awstypes "github.com/aws/aws-sdk-go-v2/service/opensearch/types"
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/create"
tfopensearch "github.com/hashicorp/terraform-provider-aws/internal/service/opensearch"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

func TestAccOpenSearchAuthorizeVPCEndpointAccess_basic(t *testing.T) {
ctx := acctest.Context(t)
if testing.Short() {
t.Skip("skipping long-running test in short mode")
}

var authorizevpcendpointaccess awstypes.AuthorizedPrincipal
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_opensearch_authorize_vpc_endpoint_access.test"
domainName := testAccRandomDomainName()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
},
ErrorCheck: acctest.ErrorCheck(t, names.OpenSearchServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckAuthorizeVPCEndpointAccessDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccAuthorizeVPCEndpointAccessConfig_basic(rName, domainName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAuthorizeVPCEndpointAccessExists(ctx, resourceName, &authorizevpcendpointaccess),
resource.TestCheckResourceAttrSet(resourceName, "account"),
resource.TestCheckResourceAttrSet(resourceName, names.AttrDomainName),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateId: domainName,
ImportStateVerifyIdentifierAttribute: names.AttrDomainName,
ImportStateIdFunc: testAccAuthorizeVPCEndpointAccessImportStateIDFunc(resourceName),
},
},
})
}

func TestAccOpenSearchAuthorizeVPCEndpointAccess_disappears(t *testing.T) {
ctx := acctest.Context(t)

var authorizevpcendpointaccess awstypes.AuthorizedPrincipal
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_opensearch_authorize_vpc_endpoint_access.test"
domainName := testAccRandomDomainName()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
},
ErrorCheck: acctest.ErrorCheck(t, names.OpenSearchServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckAuthorizeVPCEndpointAccessDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccAuthorizeVPCEndpointAccessConfig_basic(rName, domainName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAuthorizeVPCEndpointAccessExists(ctx, resourceName, &authorizevpcendpointaccess),
acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfopensearch.ResourceAuthorizeVPCEndpointAccess, resourceName),
),
ExpectNonEmptyPlan: true,
},
},
})
}

func testAccCheckAuthorizeVPCEndpointAccessDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).OpenSearchClient(ctx)

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_opensearch_authorize_vpc_endpoint_access" {
continue
}

_, err := tfopensearch.FindAuthorizeVPCEndpointAccessByName(ctx, conn, rs.Primary.Attributes[names.AttrDomainName])

if tfresource.NotFound(err) {
continue
}

if err != nil {
return err
}

return fmt.Errorf("Elastic Beanstalk Application Version %s still exists", rs.Primary.ID)
}

return nil
}
}

func testAccCheckAuthorizeVPCEndpointAccessExists(ctx context.Context, name string, authorizevpcendpointaccess *awstypes.AuthorizedPrincipal) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return create.Error(names.OpenSearch, create.ErrActionCheckingExistence, tfopensearch.ResNameAuthorizeVPCEndpointAccess, name, errors.New("not found"))
}

if rs.Primary.ID == "" {
return create.Error(names.Route53Profiles, create.ErrActionCheckingExistence, tfopensearch.ResNameAuthorizeVPCEndpointAccess, name, errors.New("not set"))
}

conn := acctest.Provider.Meta().(*conns.AWSClient).OpenSearchClient(ctx)

resp, err := tfopensearch.FindAuthorizeVPCEndpointAccessByName(ctx, conn, rs.Primary.Attributes[names.AttrDomainName])
if err != nil {
return create.Error(names.OpenSearch, create.ErrActionCheckingExistence, tfopensearch.ResNameAuthorizeVPCEndpointAccess, rs.Primary.ID, err)
}

*authorizevpcendpointaccess = *resp

return nil
}
}

func testAccAuthorizeVPCEndpointAccessImportStateIDFunc(resourceName string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return "", fmt.Errorf("Not found: %s", resourceName)
}

return rs.Primary.Attributes[names.AttrDomainName], nil
}
}

func testAccAuthorizeVPCEndpointAccessConfig_basic(rName, domainName string) string {
return acctest.ConfigCompose(testAccVPCEndpointConfig_base(rName, domainName), `
data "aws_caller_identity" "current" {}
resource "aws_opensearch_vpc_endpoint" "test" {
domain_arn = aws_opensearch_domain.test.arn
vpc_options {
subnet_ids = aws_subnet.client[*].id
}
}
resource "aws_opensearch_authorize_vpc_endpoint_access" "test" {
domain_name = aws_opensearch_domain.test.domain_name
account = data.aws_caller_identity.current.account_id
}
`)
}
Loading

0 comments on commit 1fefd6b

Please sign in to comment.