Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

f/New Resource Authorize VPC Endpoint Access #39846

Merged
merged 17 commits into from
Oct 29, 2024
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
Loading