diff --git a/.changelog/40233.txt b/.changelog/40233.txt new file mode 100644 index 00000000000..3c2a76c2a68 --- /dev/null +++ b/.changelog/40233.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_vpc_block_public_access_options +``` \ No newline at end of file diff --git a/.github/labeler-pr-triage.yml b/.github/labeler-pr-triage.yml index 006d30413bf..04b40b53617 100644 --- a/.github/labeler-pr-triage.yml +++ b/.github/labeler-pr-triage.yml @@ -2241,6 +2241,7 @@ service/vpc: - 'website/**/vpc_security_group_*' - 'website/**/vpc\.*' - 'website/**/vpcs\.*' + - 'website/**/vpc_block_public_access_*' service/vpclattice: - any: - changed-files: diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index e457400dc95..5d75a01493f 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -6577,3 +6577,19 @@ func findCapacityBlockOfferings(ctx context.Context, conn *ec2.Client, input *ec return output, nil } + +func findVPCBlockPublicAccessOptions(ctx context.Context, conn *ec2.Client) (*awstypes.VpcBlockPublicAccessOptions, error) { + input := &ec2.DescribeVpcBlockPublicAccessOptionsInput{} + + output, err := conn.DescribeVpcBlockPublicAccessOptions(ctx, input) + + if err != nil { + return nil, err + } + + if output == nil || output.VpcBlockPublicAccessOptions == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.VpcBlockPublicAccessOptions, nil +} diff --git a/internal/service/ec2/service_package_gen.go b/internal/service/ec2/service_package_gen.go index de1267ac1e7..80614b1fc85 100644 --- a/internal/service/ec2/service_package_gen.go +++ b/internal/service/ec2/service_package_gen.go @@ -87,6 +87,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newTransitGatewayDefaultRouteTablePropagationResource, Name: "Transit Gateway Default Route Table Propagation", }, + { + Factory: newVPCBlockPublicAccessOptionsResource, + Name: "VPC Block Public Access Options", + }, { Factory: newVPCEndpointPrivateDNSResource, Name: "VPC Endpoint Private DNS", diff --git a/internal/service/ec2/status.go b/internal/service/ec2/status.go index 8b13352bb69..bd367ffac56 100644 --- a/internal/service/ec2/status.go +++ b/internal/service/ec2/status.go @@ -1572,3 +1572,19 @@ func statusNetworkInsightsAnalysis(ctx context.Context, conn *ec2.Client, id str return output, string(output.Status), nil } } + +func statusVPCBlockPublicAccessOptions(ctx context.Context, conn *ec2.Client) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findVPCBlockPublicAccessOptions(ctx, conn) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.State), nil + } +} diff --git a/internal/service/ec2/vpc_block_public_access_options.go b/internal/service/ec2/vpc_block_public_access_options.go new file mode 100644 index 00000000000..529de85cc07 --- /dev/null +++ b/internal/service/ec2/vpc_block_public_access_options.go @@ -0,0 +1,216 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ec2 + +import ( + "context" + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "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/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "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_vpc_block_public_access_options", name="VPC Block Public Access Options") +func newVPCBlockPublicAccessOptionsResource(context.Context) (resource.ResourceWithConfigure, error) { + r := &vpcBlockPublicAccessOptionsResource{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +type vpcBlockPublicAccessOptionsResource struct { + framework.ResourceWithConfigure + framework.WithTimeouts + framework.WithImportByID +} + +func (*vpcBlockPublicAccessOptionsResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_vpc_block_public_access_options" +} + +func (r *vpcBlockPublicAccessOptionsResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrAWSAccountID: schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "aws_region": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + names.AttrID: framework.IDAttribute(), + "internet_gateway_block_mode": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.InternetGatewayBlockMode](), + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *vpcBlockPublicAccessOptionsResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data vpcBlockPublicAccessOptionsResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().EC2Client(ctx) + + input := &ec2.ModifyVpcBlockPublicAccessOptionsInput{ + InternetGatewayBlockMode: data.InternetGatewayBlockMode.ValueEnum(), + } + + output, err := conn.ModifyVpcBlockPublicAccessOptions(ctx, input) + + if err != nil { + response.Diagnostics.AddError("creating VPC Block Public Access Options", err.Error()) + + return + } + + options, err := waitVPCBlockPublicAccessOptionsUpdated(ctx, conn, r.CreateTimeout(ctx, data.Timeouts)) + + if err != nil { + response.State.SetAttribute(ctx, path.Root(names.AttrID), fwflex.StringToFramework(ctx, output.VpcBlockPublicAccessOptions.AwsRegion)) // Set 'id' so as to taint the resource. + response.Diagnostics.AddError("waiting for VPC Block Public Access Options create", err.Error()) + + return + } + + // Set values for unknowns. + data.AWSAccountID = fwflex.StringToFramework(ctx, options.AwsAccountId) + data.AWSRegion = fwflex.StringToFramework(ctx, options.AwsRegion) + data.ID = data.AWSRegion + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *vpcBlockPublicAccessOptionsResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data vpcBlockPublicAccessOptionsResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().EC2Client(ctx) + + options, err := findVPCBlockPublicAccessOptions(ctx, conn) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading VPC Block Public Access Options (%s)", data.ID.ValueString()), err.Error()) + + return + } + + // Set attributes for import. + response.Diagnostics.Append(fwflex.Flatten(ctx, options, &data)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *vpcBlockPublicAccessOptionsResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var new vpcBlockPublicAccessOptionsResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().EC2Client(ctx) + + input := &ec2.ModifyVpcBlockPublicAccessOptionsInput{ + InternetGatewayBlockMode: new.InternetGatewayBlockMode.ValueEnum(), + } + + _, err := conn.ModifyVpcBlockPublicAccessOptions(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating VPC Block Public Access Options (%s)", new.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitVPCBlockPublicAccessOptionsUpdated(ctx, conn, r.UpdateTimeout(ctx, new.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for VPC Block Public Access Options (%s) update", new.ID.ValueString()), err.Error()) + + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *vpcBlockPublicAccessOptionsResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data vpcBlockPublicAccessOptionsResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().EC2Client(ctx) + + // On deletion of this resource set the VPC Block Public Access Options to off. + input := &ec2.ModifyVpcBlockPublicAccessOptionsInput{ + InternetGatewayBlockMode: awstypes.InternetGatewayBlockModeOff, + } + + _, err := conn.ModifyVpcBlockPublicAccessOptions(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting VPC Block Public Access Options (%s)", data.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitVPCBlockPublicAccessOptionsUpdated(ctx, conn, r.DeleteTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for VPC Block Public Access Options (%s) delete", data.ID.ValueString()), err.Error()) + + return + } +} + +type vpcBlockPublicAccessOptionsResourceModel struct { + AWSAccountID types.String `tfsdk:"aws_account_id"` + AWSRegion types.String `tfsdk:"aws_region"` + ID types.String `tfsdk:"id"` + InternetGatewayBlockMode fwtypes.StringEnum[awstypes.InternetGatewayBlockMode] `tfsdk:"internet_gateway_block_mode"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/service/ec2/vpc_block_public_access_options_test.go b/internal/service/ec2/vpc_block_public_access_options_test.go new file mode 100644 index 00000000000..2f119210514 --- /dev/null +++ b/internal/service/ec2/vpc_block_public_access_options_test.go @@ -0,0 +1,128 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ec2_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/ec2" + awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccVPCBlockPublicAccessOptions_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]func(t *testing.T){ + acctest.CtBasic: testAccVPCBlockPublicAccessOptions_basic, + "update": testAccVPCBlockPublicAccessOptions_update, + } + + acctest.RunSerialTests1Level(t, testCases, 0) +} + +func testAccVPCBlockPublicAccessOptions_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_vpc_block_public_access_options.test" + rMode := string(awstypes.InternetGatewayBlockModeBlockBidirectional) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckVPCBlockPublicAccess(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccVPCBlockPublicAccessOptionsConfig_basic(rMode), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrAWSAccountID), knownvalue.NotNull()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("aws_region"), knownvalue.NotNull()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("internet_gateway_block_mode"), knownvalue.StringExact(rMode)), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccVPCBlockPublicAccessOptions_update(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_vpc_block_public_access_options.test" + rMode1 := string(awstypes.InternetGatewayBlockModeBlockBidirectional) + rMode2 := string(awstypes.InternetGatewayBlockModeBlockIngress) + rMode3 := string(awstypes.InternetGatewayBlockModeOff) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.EC2) + testAccPreCheckVPCBlockPublicAccess(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.EC2), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + { + Config: testAccVPCBlockPublicAccessOptionsConfig_basic(rMode1), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("internet_gateway_block_mode"), knownvalue.StringExact(rMode1)), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccVPCBlockPublicAccessOptionsConfig_basic(rMode2), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("internet_gateway_block_mode"), knownvalue.StringExact(rMode2)), + }, + }, + { + Config: testAccVPCBlockPublicAccessOptionsConfig_basic(rMode3), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("internet_gateway_block_mode"), knownvalue.StringExact(rMode3)), + }, + }, + }, + }) +} + +func testAccPreCheckVPCBlockPublicAccess(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx) + + input := &ec2.DescribeVpcBlockPublicAccessOptionsInput{} + _, err := conn.DescribeVpcBlockPublicAccessOptions(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccVPCBlockPublicAccessOptionsConfig_basic(rMode string) string { + return fmt.Sprintf(` +resource "aws_vpc_block_public_access_options" "test" { + internet_gateway_block_mode = %[1]q +} +`, rMode) +} diff --git a/internal/service/ec2/wait.go b/internal/service/ec2/wait.go index 60b60024b13..eb1320658c2 100644 --- a/internal/service/ec2/wait.go +++ b/internal/service/ec2/wait.go @@ -3195,3 +3195,23 @@ func waitVPNGatewayVPCAttachmentDetached(ctx context.Context, conn *ec2.Client, return nil, err } + +func waitVPCBlockPublicAccessOptionsUpdated(ctx context.Context, conn *ec2.Client, timeout time.Duration) (*awstypes.VpcBlockPublicAccessOptions, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.VpcBlockPublicAccessStateUpdateInProgress), + Target: enum.Slice(awstypes.VpcBlockPublicAccessStateUpdateComplete), + Refresh: statusVPCBlockPublicAccessOptions(ctx, conn), + Timeout: timeout, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.VpcBlockPublicAccessOptions); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.Reason))) + + return output, err + } + + return nil, err +} diff --git a/names/data/names_data.hcl b/names/data/names_data.hcl index b46f324edbd..23c87836909 100644 --- a/names/data/names_data.hcl +++ b/names/data/names_data.hcl @@ -9454,7 +9454,7 @@ service "ec2" { split_package = "ec2" file_prefix = "vpc_" - doc_prefix = ["default_network_", "default_route_", "default_security_", "default_subnet", "default_vpc", "ec2_managed_", "ec2_network_", "ec2_subnet_", "ec2_traffic_", "egress_only_", "flow_log", "internet_gateway", "main_route_", "nat_", "network_", "prefix_list", "route_", "route\\.", "security_group", "subnet", "vpc_dhcp_", "vpc_endpoint", "vpc_ipv", "vpc_network_performance", "vpc_peering_", "vpc_security_group_", "vpc\\.", "vpcs\\."] + doc_prefix = ["default_network_", "default_route_", "default_security_", "default_subnet", "default_vpc", "ec2_managed_", "ec2_network_", "ec2_subnet_", "ec2_traffic_", "egress_only_", "flow_log", "internet_gateway", "main_route_", "nat_", "network_", "prefix_list", "route_", "route\\.", "security_group", "subnet", "vpc_dhcp_", "vpc_endpoint", "vpc_ipv", "vpc_network_performance", "vpc_peering_", "vpc_security_group_", "vpc\\.", "vpcs\\.", "vpc_block_public_access_"] brand = "Amazon" exclude = true allowed_subcategory = true diff --git a/website/docs/r/vpc_block_public_access_options.html.markdown b/website/docs/r/vpc_block_public_access_options.html.markdown new file mode 100644 index 00000000000..87c82d3ff21 --- /dev/null +++ b/website/docs/r/vpc_block_public_access_options.html.markdown @@ -0,0 +1,59 @@ +--- +subcategory: "VPC (Virtual Private Cloud)" +layout: "aws" +page_title: "AWS: aws_vpc_block_public_access_options" +description: |- + Terraform resource for managing AWS VPC Block Public Access Options in a region. +--- + +# Resource: aws_vpc_block_public_access_options + +Terraform resource for managing an AWS VPC Block Public Access Options. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_vpc_block_public_access_options" "example" { + internet_gateway_block_mode = "block-bidirectional" +} +``` + +## Argument Reference + +The following arguments are required: + +* `internet_gateway_block_mode` - (Required) Block mode. Needs to be one of `block-bidirectional`, `block-ingress`, `off`. If this resource is deleted, then this value will be set to `off` in the AWS account and region. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `aws_account_id` - The AWS account id to which these options apply. +* `aws_region` - The AWS region to which these options apply. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import VPC Block Public Access Options using the `aws_region`. For example: + +```terraform +import { + to = aws_vpc_block_public_access_options.example + id = "us-east-1" +} +``` + +Using `terraform import`, import VPC Block Public Access Options using the `aws_region`. For example: + +```console +% terraform import aws_vpc_block_public_access_options.example us-east-1 +```