From 1f98226742efdc75b731e8d9674e4a4054d20a10 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Sat, 27 Dec 2025 22:03:42 +0800 Subject: [PATCH 01/19] networkfirewall proxy configuration --- .../service/networkfirewall/exports_test.go | 2 + .../networkfirewall/proxy_configuration.go | 275 ++++++++++++++++++ .../proxy_configuration_test.go | 147 ++++++++++ .../networkfirewall/service_package_gen.go | 13 + 4 files changed, 437 insertions(+) create mode 100644 internal/service/networkfirewall/proxy_configuration.go create mode 100644 internal/service/networkfirewall/proxy_configuration_test.go diff --git a/internal/service/networkfirewall/exports_test.go b/internal/service/networkfirewall/exports_test.go index ef08fe00abbc..e243617c6657 100644 --- a/internal/service/networkfirewall/exports_test.go +++ b/internal/service/networkfirewall/exports_test.go @@ -9,6 +9,7 @@ var ( ResourceFirewallPolicy = resourceFirewallPolicy ResourceFirewallTransitGatewayAttachmentAccepter = newFirewallTransitGatewayAttachmentAccepterResource ResourceLoggingConfiguration = resourceLoggingConfiguration + ResourceProxyConfiguration = newResourceProxyConfiguration ResourceResourcePolicy = resourceResourcePolicy ResourceRuleGroup = resourceRuleGroup ResourceTLSInspectionConfiguration = newTLSInspectionConfigurationResource @@ -17,6 +18,7 @@ var ( FindFirewallByARN = findFirewallByARN FindFirewallPolicyByARN = findFirewallPolicyByARN FindLoggingConfigurationByARN = findLoggingConfigurationByARN + FindProxyConfigurationByARN = findProxyConfigurationByARN FindResourcePolicyByARN = findResourcePolicyByARN FindRuleGroupByARN = findRuleGroupByARN FindTLSInspectionConfigurationByARN = findTLSInspectionConfigurationByARN diff --git a/internal/service/networkfirewall/proxy_configuration.go b/internal/service/networkfirewall/proxy_configuration.go new file mode 100644 index 000000000000..9f2c7e378b8c --- /dev/null +++ b/internal/service/networkfirewall/proxy_configuration.go @@ -0,0 +1,275 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + "errors" + + "github.com/YakDriver/smarterr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "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/retry" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_networkfirewall_proxy_configuration", name="Proxy Configuration") +// @Tags(identifierAttribute="arn") +// @ArnIdentity +func newResourceProxyConfiguration(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProxyConfiguration{} + + return r, nil +} + +type resourceProxyConfiguration struct { + framework.ResourceWithModel[resourceProxyConfigurationModel] + framework.WithImportByIdentity +} + +func (r *resourceProxyConfiguration) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrARN: framework.ARNAttributeComputedOnly(), + names.AttrDescription: schema.StringAttribute{ + Optional: true, + }, + names.AttrName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "rule_group_arns": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Optional: true, + }, + "rule_group_names": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Optional: true, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + "update_token": schema.StringAttribute{ + Computed: true, + }, + }, + Blocks: map[string]schema.Block{ + "default_rule_phase_actions": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[defaultRulePhaseActionsModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "post_response": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), + Required: true, + }, + "pre_dns": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), + Required: true, + }, + "pre_request": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), + Required: true, + }, + }, + }, + }, + }, + } +} + +func (r *resourceProxyConfiguration) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var data resourceProxyConfigurationModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &data)) + if resp.Diagnostics.HasError() { + return + } + + var input networkfirewall.CreateProxyConfigurationInput + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, data, &input, flex.WithFieldNamePrefix("ProxyConfiguration"))) + if resp.Diagnostics.HasError() { + return + } + + input.Tags = getTagsIn(ctx) + + out, err := conn.CreateProxyConfiguration(ctx, &input) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, data.Name.String()) + return + } + if out == nil || out.ProxyConfiguration == nil { + smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, data.Name.String()) + return + } + + data.ARN = flex.StringToFramework(ctx, out.ProxyConfiguration.ProxyConfigurationArn) + data.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, data)) +} + +func (r *resourceProxyConfiguration) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyConfigurationModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + out, err := findProxyConfigurationByARN(ctx, conn, state.ARN.ValueString()) + if retry.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ARN.String()) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, out.ProxyConfiguration, &state)) + if resp.Diagnostics.HasError() { + return + } + + state.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) + + setTagsOut(ctx, out.ProxyConfiguration.Tags) + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) +} + +func (r *resourceProxyConfiguration) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan, state resourceProxyConfigurationModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + diff, d := flex.Diff(ctx, plan, state) + smerr.AddEnrich(ctx, &resp.Diagnostics, d) + if resp.Diagnostics.HasError() { + return + } + + if diff.HasChanges() { + var input networkfirewall.UpdateProxyConfigurationInput + input.ProxyConfigurationArn = state.ARN.ValueStringPointer() + input.UpdateToken = state.UpdateToken.ValueStringPointer() + + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, plan.DefaultRulePhaseActions, &input.DefaultRulePhaseActions)) + if resp.Diagnostics.HasError() { + return + } + + out, err := conn.UpdateProxyConfiguration(ctx, &input) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ARN.String()) + return + } + if out == nil || out.ProxyConfiguration == nil { + smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, state.ARN.String()) + return + } + + plan.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &plan)) +} + +func (r *resourceProxyConfiguration) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyConfigurationModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + input := networkfirewall.DeleteProxyConfigurationInput{ + ProxyConfigurationArn: state.ARN.ValueStringPointer(), + } + + _, err := conn.DeleteProxyConfiguration(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ARN.String()) + return + } +} + +type resourceProxyConfigurationModel struct { + framework.WithRegionModel + ARN types.String `tfsdk:"arn"` + DefaultRulePhaseActions fwtypes.ListNestedObjectValueOf[defaultRulePhaseActionsModel] `tfsdk:"default_rule_phase_actions"` + Description types.String `tfsdk:"description"` + Name types.String `tfsdk:"name"` + RuleGroupArns fwtypes.ListValueOf[types.String] `tfsdk:"rule_group_arns"` + RuleGroupNames fwtypes.ListValueOf[types.String] `tfsdk:"rule_group_names"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` + UpdateToken types.String `tfsdk:"update_token"` +} + +type defaultRulePhaseActionsModel struct { + PostResponse fwtypes.StringEnum[awstypes.ProxyRulePhaseAction] `tfsdk:"post_response"` + PreDNS fwtypes.StringEnum[awstypes.ProxyRulePhaseAction] `tfsdk:"pre_dns"` + PreRequest fwtypes.StringEnum[awstypes.ProxyRulePhaseAction] `tfsdk:"pre_request"` +} + +func findProxyConfigurationByARN(ctx context.Context, conn *networkfirewall.Client, arn string) (*networkfirewall.DescribeProxyConfigurationOutput, error) { + input := networkfirewall.DescribeProxyConfigurationInput{ + ProxyConfigurationArn: aws.String(arn), + } + + out, err := conn.DescribeProxyConfiguration(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, smarterr.NewError(&retry.NotFoundError{ + LastError: err, + }) + } + + return nil, smarterr.NewError(err) + } + + if out == nil || out.ProxyConfiguration == nil { + return nil, smarterr.NewError(tfresource.NewEmptyResultError(&input)) + } + + return out, nil +} diff --git a/internal/service/networkfirewall/proxy_configuration_test.go b/internal/service/networkfirewall/proxy_configuration_test.go new file mode 100644 index 000000000000..336fbc783c37 --- /dev/null +++ b/internal/service/networkfirewall/proxy_configuration_test.go @@ -0,0 +1,147 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "context" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + 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/retry" + tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxyConfiguration_basic(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyConfigurationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_configuration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfigurationConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "network-firewall", regexache.MustCompile(`proxy-configuration/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, "default_rule_phase_actions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_rule_phase_actions.0.post_response", "ALLOW"), + resource.TestCheckResourceAttr(resourceName, "default_rule_phase_actions.0.pre_dns", "ALLOW"), + resource.TestCheckResourceAttr(resourceName, "default_rule_phase_actions.0.pre_request", "ALLOW"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + resource.TestCheckResourceAttrSet(resourceName, "update_token"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: names.AttrName, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyConfigurationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_configuration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfigurationConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyConfiguration, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckProxyConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkfirewall_proxy_configuration" { + continue + } + + out, err := tfnetworkfirewall.FindProxyConfigurationByARN(ctx, conn, rs.Primary.ID) + + if retry.NotFound(err) { + continue + } + + if out != nil && out.ProxyConfiguration != nil && out.ProxyConfiguration.DeleteTime != nil { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("NetworkFirewall Proxy Configuration %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckProxyConfigurationExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + output, err := tfnetworkfirewall.FindProxyConfigurationByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccProxyConfigurationConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_configuration" "test" { + name = %[1]q + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} +`, rName) +} diff --git a/internal/service/networkfirewall/service_package_gen.go b/internal/service/networkfirewall/service_package_gen.go index aba3b82e72da..ab3a294ac0f3 100644 --- a/internal/service/networkfirewall/service_package_gen.go +++ b/internal/service/networkfirewall/service_package_gen.go @@ -32,6 +32,19 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser Name: "Firewall Transit Gateway Attachment Accepter", Region: unique.Make(inttypes.ResourceRegionDefault()), }, + { + Factory: newResourceProxyConfiguration, + TypeName: "aws_networkfirewall_proxy_configuration", + Name: "Proxy Configuration", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }), + Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalARNIdentity(), + Import: inttypes.FrameworkImport{ + WrappedImport: true, + }, + }, { Factory: newTLSInspectionConfigurationResource, TypeName: "aws_networkfirewall_tls_inspection_configuration", From 96722cb1509f26f20df7164c08290f640ce14320 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Wed, 31 Dec 2025 23:06:39 +0800 Subject: [PATCH 02/19] proxy configuration wip --- .../networkfirewall/proxy_configuration.go | 43 +++++++++++++------ .../proxy_configuration_test.go | 9 ++-- .../networkfirewall/service_package_gen.go | 2 +- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/internal/service/networkfirewall/proxy_configuration.go b/internal/service/networkfirewall/proxy_configuration.go index 9f2c7e378b8c..af3855c293d3 100644 --- a/internal/service/networkfirewall/proxy_configuration.go +++ b/internal/service/networkfirewall/proxy_configuration.go @@ -12,8 +12,10 @@ import ( "github.com/aws/aws-sdk-go-v2/service/networkfirewall" awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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/schema/validator" @@ -32,7 +34,8 @@ import ( // @FrameworkResource("aws_networkfirewall_proxy_configuration", name="Proxy Configuration") // @Tags(identifierAttribute="arn") -// @ArnIdentity +// @ArnIdentity(identityDuplicateAttributes="id") +// @ArnFormat("proxy-configuration/{name}") func newResourceProxyConfiguration(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxyConfiguration{} @@ -50,7 +53,11 @@ func (r *resourceProxyConfiguration) Schema(ctx context.Context, req resource.Sc names.AttrARN: framework.ARNAttributeComputedOnly(), names.AttrDescription: schema.StringAttribute{ Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, + names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root(names.AttrARN)), names.AttrName: schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -61,11 +68,17 @@ func (r *resourceProxyConfiguration) Schema(ctx context.Context, req resource.Sc CustomType: fwtypes.ListOfStringType, ElementType: types.StringType, Optional: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, }, "rule_group_names": schema.ListAttribute{ CustomType: fwtypes.ListOfStringType, ElementType: types.StringType, Optional: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, }, names.AttrTags: tftags.TagsAttribute(), names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), @@ -120,15 +133,19 @@ func (r *resourceProxyConfiguration) Create(ctx context.Context, req resource.Cr out, err := conn.CreateProxyConfiguration(ctx, &input) if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, data.Name.String()) + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, data.ProxyConfigurationName.String()) return } if out == nil || out.ProxyConfiguration == nil { - smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, data.Name.String()) + smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, data.ProxyConfigurationName.String()) + return + } + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, out.ProxyConfiguration, &data)) + if resp.Diagnostics.HasError() { return } - data.ARN = flex.StringToFramework(ctx, out.ProxyConfiguration.ProxyConfigurationArn) + data.ID = data.ProxyConfigurationArn data.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, data)) @@ -143,14 +160,14 @@ func (r *resourceProxyConfiguration) Read(ctx context.Context, req resource.Read return } - out, err := findProxyConfigurationByARN(ctx, conn, state.ARN.ValueString()) + out, err := findProxyConfigurationByARN(ctx, conn, state.ID.ValueString()) if retry.NotFound(err) { resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) resp.State.RemoveResource(ctx) return } if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ARN.String()) + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) return } @@ -184,7 +201,6 @@ func (r *resourceProxyConfiguration) Update(ctx context.Context, req resource.Up if diff.HasChanges() { var input networkfirewall.UpdateProxyConfigurationInput - input.ProxyConfigurationArn = state.ARN.ValueStringPointer() input.UpdateToken = state.UpdateToken.ValueStringPointer() smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, plan.DefaultRulePhaseActions, &input.DefaultRulePhaseActions)) @@ -194,11 +210,11 @@ func (r *resourceProxyConfiguration) Update(ctx context.Context, req resource.Up out, err := conn.UpdateProxyConfiguration(ctx, &input) if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ARN.String()) + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) return } if out == nil || out.ProxyConfiguration == nil { - smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, state.ARN.String()) + smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, state.ID.String()) return } @@ -218,7 +234,7 @@ func (r *resourceProxyConfiguration) Delete(ctx context.Context, req resource.De } input := networkfirewall.DeleteProxyConfigurationInput{ - ProxyConfigurationArn: state.ARN.ValueStringPointer(), + ProxyConfigurationArn: state.ProxyConfigurationArn.ValueStringPointer(), } _, err := conn.DeleteProxyConfiguration(ctx, &input) @@ -227,17 +243,18 @@ func (r *resourceProxyConfiguration) Delete(ctx context.Context, req resource.De return } - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ARN.String()) + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) return } } type resourceProxyConfigurationModel struct { framework.WithRegionModel - ARN types.String `tfsdk:"arn"` DefaultRulePhaseActions fwtypes.ListNestedObjectValueOf[defaultRulePhaseActionsModel] `tfsdk:"default_rule_phase_actions"` Description types.String `tfsdk:"description"` - Name types.String `tfsdk:"name"` + ID types.String `tfsdk:"id"` + ProxyConfigurationArn types.String `tfsdk:"arn"` + ProxyConfigurationName types.String `tfsdk:"name"` RuleGroupArns fwtypes.ListValueOf[types.String] `tfsdk:"rule_group_arns"` RuleGroupNames fwtypes.ListValueOf[types.String] `tfsdk:"rule_group_names"` Tags tftags.Map `tfsdk:"tags"` diff --git a/internal/service/networkfirewall/proxy_configuration_test.go b/internal/service/networkfirewall/proxy_configuration_test.go index 336fbc783c37..4795419cffb9 100644 --- a/internal/service/networkfirewall/proxy_configuration_test.go +++ b/internal/service/networkfirewall/proxy_configuration_test.go @@ -47,11 +47,10 @@ func TestAccNetworkFirewallProxyConfiguration_basic(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIdentifierAttribute: names.AttrName, - ImportStateVerifyIgnore: []string{"update_token"}, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, }, }, }) diff --git a/internal/service/networkfirewall/service_package_gen.go b/internal/service/networkfirewall/service_package_gen.go index ab3a294ac0f3..d04262387f80 100644 --- a/internal/service/networkfirewall/service_package_gen.go +++ b/internal/service/networkfirewall/service_package_gen.go @@ -40,7 +40,7 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser IdentifierAttribute: names.AttrARN, }), Region: unique.Make(inttypes.ResourceRegionDefault()), - Identity: inttypes.RegionalARNIdentity(), + Identity: inttypes.RegionalARNIdentity(inttypes.WithIdentityDuplicateAttrs(names.AttrID)), Import: inttypes.FrameworkImport{ WrappedImport: true, }, From aa3f95a401567776b7d3e81ed7f0000c04242b1c Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Wed, 31 Dec 2025 23:18:49 +0800 Subject: [PATCH 03/19] proxy configuration done without groups --- internal/service/networkfirewall/proxy_configuration.go | 6 ++++++ .../service/networkfirewall/proxy_configuration_test.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/internal/service/networkfirewall/proxy_configuration.go b/internal/service/networkfirewall/proxy_configuration.go index af3855c293d3..1ee880955527 100644 --- a/internal/service/networkfirewall/proxy_configuration.go +++ b/internal/service/networkfirewall/proxy_configuration.go @@ -288,5 +288,11 @@ func findProxyConfigurationByARN(ctx context.Context, conn *networkfirewall.Clie return nil, smarterr.NewError(tfresource.NewEmptyResultError(&input)) } + if out.ProxyConfiguration.DeleteTime != nil { + return nil, smarterr.NewError(&retry.NotFoundError{ + Message: "resource is deleted", + }) + } + return out, nil } diff --git a/internal/service/networkfirewall/proxy_configuration_test.go b/internal/service/networkfirewall/proxy_configuration_test.go index 4795419cffb9..efdd4f1688b9 100644 --- a/internal/service/networkfirewall/proxy_configuration_test.go +++ b/internal/service/networkfirewall/proxy_configuration_test.go @@ -12,6 +12,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/networkfirewall" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -75,6 +76,11 @@ func TestAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyConfiguration, resourceName), ), ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, }, }, }) From 8410ebd46895d16ef8df58e3f31c9264c900c671 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Thu, 1 Jan 2026 18:43:33 +0800 Subject: [PATCH 04/19] removal of role groups arguments from proxy configuration --- .../networkfirewall/proxy_configuration.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/internal/service/networkfirewall/proxy_configuration.go b/internal/service/networkfirewall/proxy_configuration.go index 1ee880955527..f2d079d61e02 100644 --- a/internal/service/networkfirewall/proxy_configuration.go +++ b/internal/service/networkfirewall/proxy_configuration.go @@ -15,7 +15,6 @@ import ( "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/schema/validator" @@ -64,22 +63,6 @@ func (r *resourceProxyConfiguration) Schema(ctx context.Context, req resource.Sc stringplanmodifier.RequiresReplace(), }, }, - "rule_group_arns": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, - ElementType: types.StringType, - Optional: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.RequiresReplace(), - }, - }, - "rule_group_names": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, - ElementType: types.StringType, - Optional: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.RequiresReplace(), - }, - }, names.AttrTags: tftags.TagsAttribute(), names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), "update_token": schema.StringAttribute{ @@ -255,8 +238,6 @@ type resourceProxyConfigurationModel struct { ID types.String `tfsdk:"id"` ProxyConfigurationArn types.String `tfsdk:"arn"` ProxyConfigurationName types.String `tfsdk:"name"` - RuleGroupArns fwtypes.ListValueOf[types.String] `tfsdk:"rule_group_arns"` - RuleGroupNames fwtypes.ListValueOf[types.String] `tfsdk:"rule_group_names"` Tags tftags.Map `tfsdk:"tags"` TagsAll tftags.Map `tfsdk:"tags_all"` UpdateToken types.String `tfsdk:"update_token"` From 4abdd125e9a8a8b3da58adb132f8deccf6ef06cf Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Thu, 1 Jan 2026 20:02:57 +0800 Subject: [PATCH 05/19] proxy configuration added tags testing --- .../networkfirewall/proxy_configuration.go | 3 + .../proxy_configuration_test.go | 84 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/internal/service/networkfirewall/proxy_configuration.go b/internal/service/networkfirewall/proxy_configuration.go index f2d079d61e02..fb8068c60587 100644 --- a/internal/service/networkfirewall/proxy_configuration.go +++ b/internal/service/networkfirewall/proxy_configuration.go @@ -67,6 +67,9 @@ func (r *resourceProxyConfiguration) Schema(ctx context.Context, req resource.Sc names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), "update_token": schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, }, Blocks: map[string]schema.Block{ diff --git a/internal/service/networkfirewall/proxy_configuration_test.go b/internal/service/networkfirewall/proxy_configuration_test.go index efdd4f1688b9..364d7552e2d9 100644 --- a/internal/service/networkfirewall/proxy_configuration_test.go +++ b/internal/service/networkfirewall/proxy_configuration_test.go @@ -86,6 +86,53 @@ func TestAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { }) } +func TestAccNetworkFirewallProxyConfiguration_tags(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyConfigurationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_configuration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfigurationConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + { + Config: testAccProxyConfigurationConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "2"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccProxyConfigurationConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + func testAccCheckProxyConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) @@ -150,3 +197,40 @@ resource "aws_networkfirewall_proxy_configuration" "test" { } `, rName) } + +func testAccProxyConfigurationConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_configuration" "test" { + name = %[1]q + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccProxyConfigurationConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_configuration" "test" { + name = %[1]q + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} From 266d8fa8ba56dd882d9dd4820156831ef6b54fee Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Thu, 1 Jan 2026 20:03:48 +0800 Subject: [PATCH 06/19] added proxy rule group resource --- .../service/networkfirewall/exports_test.go | 2 + .../networkfirewall/proxy_rule_group.go | 222 ++++++++++++++++++ .../networkfirewall/proxy_rule_group_test.go | 214 +++++++++++++++++ .../networkfirewall/service_package_gen.go | 13 + 4 files changed, 451 insertions(+) create mode 100644 internal/service/networkfirewall/proxy_rule_group.go create mode 100644 internal/service/networkfirewall/proxy_rule_group_test.go diff --git a/internal/service/networkfirewall/exports_test.go b/internal/service/networkfirewall/exports_test.go index e243617c6657..fe9a8b50db6b 100644 --- a/internal/service/networkfirewall/exports_test.go +++ b/internal/service/networkfirewall/exports_test.go @@ -10,6 +10,7 @@ var ( ResourceFirewallTransitGatewayAttachmentAccepter = newFirewallTransitGatewayAttachmentAccepterResource ResourceLoggingConfiguration = resourceLoggingConfiguration ResourceProxyConfiguration = newResourceProxyConfiguration + ResourceProxyRuleGroup = newResourceProxyRuleGroup ResourceResourcePolicy = resourceResourcePolicy ResourceRuleGroup = resourceRuleGroup ResourceTLSInspectionConfiguration = newTLSInspectionConfigurationResource @@ -19,6 +20,7 @@ var ( FindFirewallPolicyByARN = findFirewallPolicyByARN FindLoggingConfigurationByARN = findLoggingConfigurationByARN FindProxyConfigurationByARN = findProxyConfigurationByARN + FindProxyRuleGroupByARN = findProxyRuleGroupByARN FindResourcePolicyByARN = findResourcePolicyByARN FindRuleGroupByARN = findRuleGroupByARN FindTLSInspectionConfigurationByARN = findTLSInspectionConfigurationByARN diff --git a/internal/service/networkfirewall/proxy_rule_group.go b/internal/service/networkfirewall/proxy_rule_group.go new file mode 100644 index 000000000000..8fd28f8dcbdf --- /dev/null +++ b/internal/service/networkfirewall/proxy_rule_group.go @@ -0,0 +1,222 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/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/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" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_networkfirewall_proxy_rule_group", name="Proxy Rule Group") +// @Tags(identifierAttribute="arn") +// @ArnIdentity(identityDuplicateAttributes="id") +// @ArnFormat("proxy-rule-group/{name}") +func newResourceProxyRuleGroup(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProxyRuleGroup{} + + return r, nil +} + +const ( + ResNameProxyRuleGroup = "Proxy Rule Group" +) + +type resourceProxyRuleGroup struct { + framework.ResourceWithModel[resourceProxyRuleGroupModel] + framework.WithImportByIdentity +} + +func (r *resourceProxyRuleGroup) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrARN: framework.ARNAttributeComputedOnly(), + names.AttrDescription: schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root(names.AttrARN)), + names.AttrName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + "update_token": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *resourceProxyRuleGroup) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan resourceProxyRuleGroupModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + input := &networkfirewall.CreateProxyRuleGroupInput{ + ProxyRuleGroupName: aws.String(plan.ProxyRuleGroupName.ValueString()), + Tags: getTagsIn(ctx), + } + + if !plan.Description.IsNull() { + input.Description = aws.String(plan.Description.ValueString()) + } + + out, err := conn.CreateProxyRuleGroup(ctx, input) + if err != nil { + resp.Diagnostics.AddError( + "creating Network Firewall Proxy Rule Group", + err.Error(), + ) + return + } + + if out == nil || out.ProxyRuleGroup == nil { + resp.Diagnostics.AddError( + "creating Network Firewall Proxy Rule Group", + "empty output", + ) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, out.ProxyRuleGroup, &plan)) + if resp.Diagnostics.HasError() { + return + } + + plan.ID = plan.ProxyRuleGroupArn + plan.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceProxyRuleGroup) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyRuleGroupModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findProxyRuleGroupByARN(ctx, conn, state.ID.ValueString()) + if retry.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + "reading Network Firewall Proxy Rule Group", + err.Error(), + ) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, out.ProxyRuleGroup, &state)) + if resp.Diagnostics.HasError() { + return + } + + state.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) + + setTagsOut(ctx, out.ProxyRuleGroup.Tags) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceProxyRuleGroup) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyRuleGroupModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + input := &networkfirewall.DeleteProxyRuleGroupInput{ + ProxyRuleGroupArn: aws.String(state.ID.ValueString()), + } + + _, err := conn.DeleteProxyRuleGroup(ctx, input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + resp.Diagnostics.AddError( + "deleting Network Firewall Proxy Rule Group", + err.Error(), + ) + return + } +} + +func findProxyRuleGroupByARN(ctx context.Context, conn *networkfirewall.Client, arn string) (*networkfirewall.DescribeProxyRuleGroupOutput, error) { + input := &networkfirewall.DescribeProxyRuleGroupInput{ + ProxyRuleGroupArn: aws.String(arn), + } + + output, err := conn.DescribeProxyRuleGroup(ctx, input) + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + } + } + if err != nil { + return nil, err + } + + if output == nil || output.ProxyRuleGroup == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if output.ProxyRuleGroup.DeleteTime != nil { + return nil, &retry.NotFoundError{ + Message: "resource is deleted", + } + } + + return output, nil +} + +type resourceProxyRuleGroupModel struct { + framework.WithRegionModel + Description types.String `tfsdk:"description"` + ID types.String `tfsdk:"id"` + ProxyRuleGroupArn types.String `tfsdk:"arn"` + ProxyRuleGroupName types.String `tfsdk:"name"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` + UpdateToken types.String `tfsdk:"update_token"` +} diff --git a/internal/service/networkfirewall/proxy_rule_group_test.go b/internal/service/networkfirewall/proxy_rule_group_test.go new file mode 100644 index 000000000000..c697c27af594 --- /dev/null +++ b/internal/service/networkfirewall/proxy_rule_group_test.go @@ -0,0 +1,214 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "context" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "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/retry" + tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxyRuleGroup_basic(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rule_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRuleGroupConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "network-firewall", regexache.MustCompile(`proxy-rule-group/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + resource.TestCheckResourceAttrSet(resourceName, "update_token"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRuleGroup_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rule_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRuleGroupConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyRuleGroup, resourceName), + ), + ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRuleGroup_tags(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rule_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRuleGroupConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + { + Config: testAccProxyRuleGroupConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "2"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccProxyRuleGroupConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + +func testAccCheckProxyRuleGroupDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkfirewall_proxy_rule_group" { + continue + } + + out, err := tfnetworkfirewall.FindProxyRuleGroupByARN(ctx, conn, rs.Primary.ID) + + if retry.NotFound(err) { + continue + } + + if out != nil && out.ProxyRuleGroup != nil && out.ProxyRuleGroup.DeleteTime != nil { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("NetworkFirewall Proxy Rule Group %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckProxyRuleGroupExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + output, err := tfnetworkfirewall.FindProxyRuleGroupByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccProxyRuleGroupConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_rule_group" "test" { + name = %[1]q +} +`, rName) +} + +func testAccProxyRuleGroupConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_rule_group" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccProxyRuleGroupConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_rule_group" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/networkfirewall/service_package_gen.go b/internal/service/networkfirewall/service_package_gen.go index d04262387f80..4b4003762706 100644 --- a/internal/service/networkfirewall/service_package_gen.go +++ b/internal/service/networkfirewall/service_package_gen.go @@ -45,6 +45,19 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser WrappedImport: true, }, }, + { + Factory: newResourceProxyRuleGroup, + TypeName: "aws_networkfirewall_proxy_rule_group", + Name: "Proxy Rule Group", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }), + Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalARNIdentity(inttypes.WithIdentityDuplicateAttrs(names.AttrID)), + Import: inttypes.FrameworkImport{ + WrappedImport: true, + }, + }, { Factory: newTLSInspectionConfigurationResource, TypeName: "aws_networkfirewall_tls_inspection_configuration", From 9db544b8e28094d538dd9d338273e59a1841452b Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Fri, 2 Jan 2026 20:07:11 +0800 Subject: [PATCH 07/19] proxy rules WIP --- .../service/networkfirewall/exports_test.go | 1 + .../service/networkfirewall/proxy_rules.go | 969 ++++++++++++++++++ .../networkfirewall/proxy_rules_test.go | 374 +++++++ .../networkfirewall/service_package_gen.go | 6 + 4 files changed, 1350 insertions(+) create mode 100644 internal/service/networkfirewall/proxy_rules.go create mode 100644 internal/service/networkfirewall/proxy_rules_test.go diff --git a/internal/service/networkfirewall/exports_test.go b/internal/service/networkfirewall/exports_test.go index fe9a8b50db6b..8072f0b0abf3 100644 --- a/internal/service/networkfirewall/exports_test.go +++ b/internal/service/networkfirewall/exports_test.go @@ -11,6 +11,7 @@ var ( ResourceLoggingConfiguration = resourceLoggingConfiguration ResourceProxyConfiguration = newResourceProxyConfiguration ResourceProxyRuleGroup = newResourceProxyRuleGroup + ResourceProxyRules = newResourceProxyRules ResourceResourcePolicy = resourceResourcePolicy ResourceRuleGroup = resourceRuleGroup ResourceTLSInspectionConfiguration = newTLSInspectionConfigurationResource diff --git a/internal/service/networkfirewall/proxy_rules.go b/internal/service/networkfirewall/proxy_rules.go new file mode 100644 index 000000000000..2244bec0b610 --- /dev/null +++ b/internal/service/networkfirewall/proxy_rules.go @@ -0,0 +1,969 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "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" + tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_networkfirewall_proxy_rules", name="Proxy Rules") +func newResourceProxyRules(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProxyRules{} + + return r, nil +} + +const ( + ResNameProxyRules = "Proxy Rules" +) + +type resourceProxyRules struct { + framework.ResourceWithModel[resourceProxyRulesModel] +} + +func (r *resourceProxyRules) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrID: framework.IDAttribute(), + "proxy_rule_group_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.AtLeastOneOf(path.MatchRoot("proxy_rule_group_name")), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "proxy_rule_group_name": schema.StringAttribute{ + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.AtLeastOneOf(path.MatchRoot("proxy_rule_group_arn")), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "post_response": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), + Required: true, + }, + names.AttrDescription: schema.StringAttribute{ + Optional: true, + }, + "insert_position": schema.Int64Attribute{ + Optional: true, + }, + "proxy_rule_name": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "conditions": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[conditionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "condition_key": schema.StringAttribute{ + Required: true, + }, + "condition_operator": schema.StringAttribute{ + Required: true, + }, + "condition_values": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "pre_dns": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), + Required: true, + }, + names.AttrDescription: schema.StringAttribute{ + Optional: true, + }, + "insert_position": schema.Int64Attribute{ + Optional: true, + }, + "proxy_rule_name": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "conditions": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[conditionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "condition_key": schema.StringAttribute{ + Required: true, + }, + "condition_operator": schema.StringAttribute{ + Required: true, + }, + "condition_values": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "pre_request": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), + Required: true, + }, + names.AttrDescription: schema.StringAttribute{ + Optional: true, + }, + "insert_position": schema.Int64Attribute{ + Optional: true, + }, + "proxy_rule_name": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "conditions": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[conditionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "condition_key": schema.StringAttribute{ + Required: true, + }, + "condition_operator": schema.StringAttribute{ + Required: true, + }, + "condition_values": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (r *resourceProxyRules) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan resourceProxyRulesModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + if resp.Diagnostics.HasError() { + return + } + + var input networkfirewall.CreateProxyRulesInput + input.ProxyRuleGroupArn = plan.ProxyRuleGroupArn.ValueStringPointer() + + // Create the Rules structure organized by phase + var rulesByPhase awstypes.CreateProxyRulesByRequestPhase + + // Process PostRESPONSE rules + if !plan.PostRESPONSE.IsNull() && !plan.PostRESPONSE.IsUnknown() { + var postRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PostRESPONSE.ElementsAs(ctx, &postRules, false)) + if resp.Diagnostics.HasError() { + return + } + + for _, ruleModel := range postRules { + var rule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) + if resp.Diagnostics.HasError() { + return + } + rulesByPhase.PostRESPONSE = append(rulesByPhase.PostRESPONSE, rule) + } + } + + // Process PreDNS rules + if !plan.PreDNS.IsNull() && !plan.PreDNS.IsUnknown() { + var preDNSRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreDNS.ElementsAs(ctx, &preDNSRules, false)) + if resp.Diagnostics.HasError() { + return + } + + for _, ruleModel := range preDNSRules { + var rule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) + if resp.Diagnostics.HasError() { + return + } + rulesByPhase.PreDNS = append(rulesByPhase.PreDNS, rule) + } + } + + // Process PreREQUEST rules + if !plan.PreREQUEST.IsNull() && !plan.PreREQUEST.IsUnknown() { + var preRequestRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreREQUEST.ElementsAs(ctx, &preRequestRules, false)) + if resp.Diagnostics.HasError() { + return + } + + for _, ruleModel := range preRequestRules { + var rule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) + if resp.Diagnostics.HasError() { + return + } + rulesByPhase.PreREQUEST = append(rulesByPhase.PreREQUEST, rule) + } + } + + input.Rules = &rulesByPhase + + out, err := conn.CreateProxyRules(ctx, &input) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) + return + } + if out == nil || out.ProxyRuleGroup == nil { + smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, plan.ProxyRuleGroupArn.String()) + return + } + + // Set ID to the proxy rule group ARN + plan.ID = plan.ProxyRuleGroupArn.StringValue + + // Read back to get full state + readOut, err := findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, readOut, &plan)) + if resp.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, plan)) +} + +func (r *resourceProxyRules) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyRulesModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + out, err := findProxyRulesByGroupARN(ctx, conn, state.ID.ValueString()) + if tfretry.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, out, &state)) + if resp.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) +} + +func (r *resourceProxyRules) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan, state resourceProxyRulesModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + // Get current state to obtain update token + currentRules, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) + if err != nil && !tfretry.NotFound(err) { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) + return + } + + updateToken := currentRules.UpdateToken + + // Build maps of rules by name for each phase + stateRulesByName := make(map[string]proxyRuleModel) + planRulesByName := make(map[string]proxyRuleModel) + + // Extract state rules + if !state.PostRESPONSE.IsNull() && !state.PostRESPONSE.IsUnknown() { + var rules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, state.PostRESPONSE.ElementsAs(ctx, &rules, false)) + for _, rule := range rules { + stateRulesByName[rule.ProxyRuleName.ValueString()] = rule + } + } + if !state.PreDNS.IsNull() && !state.PreDNS.IsUnknown() { + var rules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, state.PreDNS.ElementsAs(ctx, &rules, false)) + for _, rule := range rules { + stateRulesByName[rule.ProxyRuleName.ValueString()] = rule + } + } + if !state.PreREQUEST.IsNull() && !state.PreREQUEST.IsUnknown() { + var rules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, state.PreREQUEST.ElementsAs(ctx, &rules, false)) + for _, rule := range rules { + stateRulesByName[rule.ProxyRuleName.ValueString()] = rule + } + } + + // Extract plan rules + if !plan.PostRESPONSE.IsNull() && !plan.PostRESPONSE.IsUnknown() { + var rules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PostRESPONSE.ElementsAs(ctx, &rules, false)) + for _, rule := range rules { + planRulesByName[rule.ProxyRuleName.ValueString()] = rule + } + } + if !plan.PreDNS.IsNull() && !plan.PreDNS.IsUnknown() { + var rules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreDNS.ElementsAs(ctx, &rules, false)) + for _, rule := range rules { + planRulesByName[rule.ProxyRuleName.ValueString()] = rule + } + } + if !plan.PreREQUEST.IsNull() && !plan.PreREQUEST.IsUnknown() { + var rules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreREQUEST.ElementsAs(ctx, &rules, false)) + for _, rule := range rules { + planRulesByName[rule.ProxyRuleName.ValueString()] = rule + } + } + + if resp.Diagnostics.HasError() { + return + } + + // Determine which rules to add, update, or delete + var rulesToCreate []proxyRuleModel + var rulesToUpdate []proxyRuleModel + var rulesToDelete []string + + // Check for new or modified rules + for name, planRule := range planRulesByName { + if stateRule, exists := stateRulesByName[name]; !exists { + // New rule - needs to be created + rulesToCreate = append(rulesToCreate, planRule) + } else if !ruleModelsEqual(ctx, stateRule, planRule) { + // Modified rule - needs to be updated + rulesToUpdate = append(rulesToUpdate, planRule) + } + } + + // Check for deleted rules + for name := range stateRulesByName { + if _, exists := planRulesByName[name]; !exists { + rulesToDelete = append(rulesToDelete, name) + } + } + + // Delete removed rules + if len(rulesToDelete) > 0 { + deleteInput := networkfirewall.DeleteProxyRulesInput{ + ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), + Rules: rulesToDelete, + } + + _, err = conn.DeleteProxyRules(ctx, &deleteInput) + if err != nil && !errs.IsA[*awstypes.ResourceNotFoundException](err) { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + + // Refresh update token after deletion + currentRules, err = findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + updateToken = currentRules.UpdateToken + } + + // Update modified rules + for _, ruleModel := range rulesToUpdate { + updateInput := networkfirewall.UpdateProxyRuleInput{ + ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), + ProxyRuleName: ruleModel.ProxyRuleName.ValueStringPointer(), + UpdateToken: updateToken, + } + + // Set action if not null + if !ruleModel.Action.IsNull() && !ruleModel.Action.IsUnknown() { + updateInput.Action = ruleModel.Action.ValueEnum() + } + + // Set description if not null + if !ruleModel.Description.IsNull() && !ruleModel.Description.IsUnknown() { + updateInput.Description = ruleModel.Description.ValueStringPointer() + } + + // Handle conditions update by removing all old conditions and adding new ones + if stateRule, exists := stateRulesByName[ruleModel.ProxyRuleName.ValueString()]; exists { + // Remove old conditions + if !stateRule.Conditions.IsNull() && !stateRule.Conditions.IsUnknown() { + var oldConditions []conditionModel + smerr.AddEnrich(ctx, &resp.Diagnostics, stateRule.Conditions.ElementsAs(ctx, &oldConditions, false)) + for _, cond := range oldConditions { + var removeCondition awstypes.ProxyRuleCondition + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, cond, &removeCondition)) + updateInput.RemoveConditions = append(updateInput.RemoveConditions, removeCondition) + } + } + } + + // Add new conditions + if !ruleModel.Conditions.IsNull() && !ruleModel.Conditions.IsUnknown() { + var newConditions []conditionModel + smerr.AddEnrich(ctx, &resp.Diagnostics, ruleModel.Conditions.ElementsAs(ctx, &newConditions, false)) + for _, cond := range newConditions { + var addCondition awstypes.ProxyRuleCondition + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, cond, &addCondition)) + updateInput.AddConditions = append(updateInput.AddConditions, addCondition) + } + } + + if resp.Diagnostics.HasError() { + return + } + + out, err := conn.UpdateProxyRule(ctx, &updateInput) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + + // Update token for next operation + updateToken = out.UpdateToken + } + + // Create new rules + if len(rulesToCreate) > 0 { + var rulesByPhase awstypes.CreateProxyRulesByRequestPhase + + // Organize rules to create by phase + for _, ruleModel := range rulesToCreate { + var createRule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &createRule)) + if resp.Diagnostics.HasError() { + return + } + + // Determine which phase this rule belongs to + ruleName := ruleModel.ProxyRuleName.ValueString() + if _, exists := planRulesByName[ruleName]; exists { + // Check plan to determine phase + if !plan.PostRESPONSE.IsNull() { + var postRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PostRESPONSE.ElementsAs(ctx, &postRules, false)) + for _, r := range postRules { + if r.ProxyRuleName.ValueString() == ruleName { + rulesByPhase.PostRESPONSE = append(rulesByPhase.PostRESPONSE, createRule) + goto nextRule + } + } + } + if !plan.PreDNS.IsNull() { + var preDNSRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreDNS.ElementsAs(ctx, &preDNSRules, false)) + for _, r := range preDNSRules { + if r.ProxyRuleName.ValueString() == ruleName { + rulesByPhase.PreDNS = append(rulesByPhase.PreDNS, createRule) + goto nextRule + } + } + } + if !plan.PreREQUEST.IsNull() { + var preRequestRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreREQUEST.ElementsAs(ctx, &preRequestRules, false)) + for _, r := range preRequestRules { + if r.ProxyRuleName.ValueString() == ruleName { + rulesByPhase.PreREQUEST = append(rulesByPhase.PreREQUEST, createRule) + goto nextRule + } + } + } + } + nextRule: + } + + createInput := networkfirewall.CreateProxyRulesInput{ + ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), + Rules: &rulesByPhase, + } + + _, err = conn.CreateProxyRules(ctx, &createInput) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + } + + // Read back to get full state + readOut, err := findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, readOut, &plan)) + if resp.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &plan)) +} + +// ruleModelsEqual compares two proxyRuleModel instances to determine if they're equal +func ruleModelsEqual(ctx context.Context, a, b proxyRuleModel) bool { + // Compare action + if a.Action.ValueEnum() != b.Action.ValueEnum() { + return false + } + + // Compare description + if a.Description.ValueString() != b.Description.ValueString() { + return false + } + + // Compare insert position + if a.InsertPosition.ValueInt64() != b.InsertPosition.ValueInt64() { + return false + } + + // Compare conditions count + if a.Conditions.IsNull() != b.Conditions.IsNull() { + return false + } + + if !a.Conditions.IsNull() { + var aConditions, bConditions []conditionModel + a.Conditions.ElementsAs(ctx, &aConditions, false) + b.Conditions.ElementsAs(ctx, &bConditions, false) + + if len(aConditions) != len(bConditions) { + return false + } + + // Compare each condition + for i := range aConditions { + if aConditions[i].ConditionKey.ValueString() != bConditions[i].ConditionKey.ValueString() { + return false + } + if aConditions[i].ConditionOperator.ValueString() != bConditions[i].ConditionOperator.ValueString() { + return false + } + + var aValues, bValues []types.String + aConditions[i].ConditionValues.ElementsAs(ctx, &aValues, false) + bConditions[i].ConditionValues.ElementsAs(ctx, &bValues, false) + + if len(aValues) != len(bValues) { + return false + } + + for j := range aValues { + if aValues[j].ValueString() != bValues[j].ValueString() { + return false + } + } + } + } + + return true +} + +func (r *resourceProxyRules) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyRulesModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + // Get all rule names for this group + out, err := findProxyRulesByGroupARN(ctx, conn, state.ID.ValueString()) + if err != nil && !tfretry.NotFound(err) { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) + return + } + + if out != nil && out.ProxyRuleGroup != nil && out.ProxyRuleGroup.Rules != nil { + var ruleNames []string + rules := out.ProxyRuleGroup.Rules + + // Collect rule names from all phases + for _, rule := range rules.PostRESPONSE { + if rule.ProxyRuleName != nil { + ruleNames = append(ruleNames, *rule.ProxyRuleName) + } + } + for _, rule := range rules.PreDNS { + if rule.ProxyRuleName != nil { + ruleNames = append(ruleNames, *rule.ProxyRuleName) + } + } + for _, rule := range rules.PreREQUEST { + if rule.ProxyRuleName != nil { + ruleNames = append(ruleNames, *rule.ProxyRuleName) + } + } + + if len(ruleNames) > 0 { + input := networkfirewall.DeleteProxyRulesInput{ + ProxyRuleGroupArn: state.ProxyRuleGroupArn.ValueStringPointer(), + Rules: ruleNames, + } + + _, err = conn.DeleteProxyRules(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) + return + } + } + } +} + +func (r *resourceProxyRules) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + // Parse the composite ID (ProxyRuleGroupArn,RuleName1,RuleName2,...) + // Minimum 2 parts: ARN and at least one rule name + parts := strings.Split(req.ID, ",") + + if len(parts) < 2 { + resp.Diagnostics.AddError( + "Invalid Import ID", + fmt.Sprintf("Expected import ID format: 'proxy_rule_group_arn,rule_name1[,rule_name2,...]'. Got: %s", req.ID), + ) + return + } + + proxyRuleGroupArn := parts[0] + ruleNames := parts[1:] + + // Fetch all rules for the group + out, err := findProxyRulesByGroupARN(ctx, conn, proxyRuleGroupArn) + if err != nil { + resp.Diagnostics.AddError( + "Import Failed", + fmt.Sprintf("Could not find proxy rule group %s: %s", proxyRuleGroupArn, err.Error()), + ) + return + } + + if out.ProxyRuleGroup == nil || out.ProxyRuleGroup.Rules == nil { + resp.Diagnostics.AddError( + "Import Failed", + fmt.Sprintf("Proxy rule group %s has no rules", proxyRuleGroupArn), + ) + return + } + + rules := out.ProxyRuleGroup.Rules + + // Create a map to track which rule names we're looking for + ruleNamesToFind := make(map[string]bool) + for _, name := range ruleNames { + ruleNamesToFind[name] = true + } + + // Collect rules by phase + var postResponseRules []proxyRuleModel + var preDNSRules []proxyRuleModel + var preRequestRules []proxyRuleModel + var diags diag.Diagnostics + + // Search for rules in PostRESPONSE phase + for _, rule := range rules.PostRESPONSE { + if rule.ProxyRuleName != nil && ruleNamesToFind[*rule.ProxyRuleName] { + var ruleModel proxyRuleModel + diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + postResponseRules = append(postResponseRules, ruleModel) + delete(ruleNamesToFind, *rule.ProxyRuleName) // Mark as found + } + } + + // Search for rules in PreDNS phase + for _, rule := range rules.PreDNS { + if rule.ProxyRuleName != nil && ruleNamesToFind[*rule.ProxyRuleName] { + var ruleModel proxyRuleModel + diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + preDNSRules = append(preDNSRules, ruleModel) + delete(ruleNamesToFind, *rule.ProxyRuleName) // Mark as found + } + } + + // Search for rules in PreREQUEST phase + for _, rule := range rules.PreREQUEST { + if rule.ProxyRuleName != nil && ruleNamesToFind[*rule.ProxyRuleName] { + var ruleModel proxyRuleModel + diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + preRequestRules = append(preRequestRules, ruleModel) + delete(ruleNamesToFind, *rule.ProxyRuleName) // Mark as found + } + } + + // Check if any rules were not found + if len(ruleNamesToFind) > 0 { + var notFoundRules []string + for ruleName := range ruleNamesToFind { + notFoundRules = append(notFoundRules, ruleName) + } + resp.Diagnostics.AddError( + "Import Failed", + fmt.Sprintf("The following rules were not found in proxy rule group %s: %v", proxyRuleGroupArn, notFoundRules), + ) + return + } + + // Create model with the found rules + var model resourceProxyRulesModel + model.ID = types.StringValue(proxyRuleGroupArn) + model.ProxyRuleGroupArn = fwtypes.ARNValue(proxyRuleGroupArn) + + if out.ProxyRuleGroup.ProxyRuleGroupName != nil { + model.ProxyRuleGroupName = flex.StringToFramework(ctx, out.ProxyRuleGroup.ProxyRuleGroupName) + } + + // Set rules for each phase + if len(postResponseRules) > 0 { + postList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, postResponseRules) + diags.Append(d...) + model.PostRESPONSE = postList + } + + if len(preDNSRules) > 0 { + preDNSList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, preDNSRules) + diags.Append(d...) + model.PreDNS = preDNSList + } + + if len(preRequestRules) > 0 { + preRequestList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, preRequestRules) + diags.Append(d...) + model.PreREQUEST = preRequestList + } + + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func findProxyRulesByGroupARN(ctx context.Context, conn *networkfirewall.Client, groupARN string) (*networkfirewall.DescribeProxyRuleGroupOutput, error) { + input := networkfirewall.DescribeProxyRuleGroupInput{ + ProxyRuleGroupArn: aws.String(groupARN), + } + + out, err := conn.DescribeProxyRuleGroup(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &tfretry.NotFoundError{ + LastError: err, + } + } + + return nil, err + } + + if out == nil || out.ProxyRuleGroup == nil { + return nil, &tfretry.NotFoundError{ + Message: "proxy rule group not found", + } + } + + return out, nil +} + +func flattenProxyRules(ctx context.Context, out *networkfirewall.DescribeProxyRuleGroupOutput, model *resourceProxyRulesModel) diag.Diagnostics { + var diags diag.Diagnostics + + if out.ProxyRuleGroup == nil || out.ProxyRuleGroup.Rules == nil { + return diags + } + + rules := out.ProxyRuleGroup.Rules + + // Process PostRESPONSE rules + if len(rules.PostRESPONSE) > 0 { + var postResponseRules []proxyRuleModel + for _, rule := range rules.PostRESPONSE { + var ruleModel proxyRuleModel + diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) + if diags.HasError() { + return diags + } + postResponseRules = append(postResponseRules, ruleModel) + } + postList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, postResponseRules) + diags.Append(d...) + if diags.HasError() { + return diags + } + model.PostRESPONSE = postList + } + + // Process PreDNS rules + if len(rules.PreDNS) > 0 { + var preDNSRules []proxyRuleModel + for _, rule := range rules.PreDNS { + var ruleModel proxyRuleModel + diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) + if diags.HasError() { + return diags + } + preDNSRules = append(preDNSRules, ruleModel) + } + preDNSList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, preDNSRules) + diags.Append(d...) + if diags.HasError() { + return diags + } + model.PreDNS = preDNSList + } + + // Process PreREQUEST rules + if len(rules.PreREQUEST) > 0 { + var preRequestRules []proxyRuleModel + for _, rule := range rules.PreREQUEST { + var ruleModel proxyRuleModel + diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) + if diags.HasError() { + return diags + } + preRequestRules = append(preRequestRules, ruleModel) + } + preRequestList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, preRequestRules) + diags.Append(d...) + if diags.HasError() { + return diags + } + model.PreREQUEST = preRequestList + } + + if out.ProxyRuleGroup.ProxyRuleGroupName != nil { + model.ProxyRuleGroupName = flex.StringToFramework(ctx, out.ProxyRuleGroup.ProxyRuleGroupName) + } + + if out.ProxyRuleGroup.ProxyRuleGroupArn != nil { + model.ProxyRuleGroupArn = fwtypes.ARNValue(aws.ToString(out.ProxyRuleGroup.ProxyRuleGroupArn)) + } + + return diags +} + +type resourceProxyRulesModel struct { + framework.WithRegionModel + ID types.String `tfsdk:"id"` + PostRESPONSE fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"post_response"` + PreDNS fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_dns"` + PreREQUEST fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_request"` + ProxyRuleGroupArn fwtypes.ARN `tfsdk:"proxy_rule_group_arn"` + ProxyRuleGroupName types.String `tfsdk:"proxy_rule_group_name"` +} + +type proxyRuleModel struct { + Action fwtypes.StringEnum[awstypes.ProxyRulePhaseAction] `tfsdk:"action"` + Conditions fwtypes.ListNestedObjectValueOf[conditionModel] `tfsdk:"conditions"` + Description types.String `tfsdk:"description"` + InsertPosition types.Int64 `tfsdk:"insert_position"` + ProxyRuleName types.String `tfsdk:"proxy_rule_name"` +} + +type conditionModel struct { + ConditionKey types.String `tfsdk:"condition_key"` + ConditionOperator types.String `tfsdk:"condition_operator"` + ConditionValues fwtypes.ListValueOf[types.String] `tfsdk:"condition_values"` +} diff --git a/internal/service/networkfirewall/proxy_rules_test.go b/internal/service/networkfirewall/proxy_rules_test.go new file mode 100644 index 000000000000..2bf45969afb2 --- /dev/null +++ b/internal/service/networkfirewall/proxy_rules_test.go @@ -0,0 +1,374 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxyRules_basic(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rules.test" + ruleGroupResourceName := "aws_networkfirewall_proxy_rule_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRulesConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, "proxy_rule_group_arn", ruleGroupResourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(resourceName, "proxy_rule_group_name", ruleGroupResourceName, names.AttrName), + // Pre-DNS phase + resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.proxy_rule_name", fmt.Sprintf("%s-predns", rName)), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "ALLOW"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_key", "request:DestinationDomain"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_operator", "StringEquals"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "amazonaws.com"), + // Pre-REQUEST phase + resource.TestCheckResourceAttr(resourceName, "pre_request.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.proxy_rule_name", fmt.Sprintf("%s-prerequest", rName)), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.action", "DENY"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.conditions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.conditions.0.condition_key", "request:Http:Method"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.conditions.0.condition_operator", "StringEquals"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.conditions.0.condition_values.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.conditions.0.condition_values.0", "DELETE"), + // Post-RESPONSE phase + resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), + resource.TestCheckResourceAttr(resourceName, "post_response.0.proxy_rule_name", fmt.Sprintf("%s-postresponse", rName)), + resource.TestCheckResourceAttr(resourceName, "post_response.0.action", "ALERT"), + resource.TestCheckResourceAttr(resourceName, "post_response.0.conditions.#", "1"), + resource.TestCheckResourceAttr(resourceName, "post_response.0.conditions.0.condition_key", "response:Http:StatusCode"), + resource.TestCheckResourceAttr(resourceName, "post_response.0.conditions.0.condition_operator", "NumericGreaterThanEquals"), + resource.TestCheckResourceAttr(resourceName, "post_response.0.conditions.0.condition_values.#", "1"), + resource.TestCheckResourceAttr(resourceName, "post_response.0.conditions.0.condition_values.0", "500"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRules_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rules.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRulesConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyRules, resourceName), + ), + ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRules_updateAdd(t *testing.T) { + ctx := acctest.Context(t) + var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rules.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRulesConfig_single(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_request.#", "0"), + resource.TestCheckResourceAttr(resourceName, "post_response.#", "0"), + ), + }, + { + Config: testAccProxyRulesConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v2), + resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_request.#", "1"), + resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), + ), + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRules_updateModify(t *testing.T) { + ctx := acctest.Context(t) + var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rules.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRulesConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "ALLOW"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "amazonaws.com"), + ), + }, + { + Config: testAccProxyRulesConfig_modified(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v2), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "DENY"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "amazonaws.com"), + ), + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRules_updateRemove(t *testing.T) { + ctx := acctest.Context(t) + var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rules.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRulesConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_request.#", "1"), + resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), + ), + }, + { + Config: testAccProxyRulesConfig_single(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v2), + resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_request.#", "0"), + resource.TestCheckResourceAttr(resourceName, "post_response.#", "0"), + ), + }, + }, + }) +} + +func testAccCheckProxyRulesDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkfirewall_proxy_rules" { + continue + } + + // The resource ID is the proxy rule group ARN + out, err := tfnetworkfirewall.FindProxyRuleGroupByARN(ctx, conn, rs.Primary.ID) + + if tfretry.NotFound(err) { + continue + } + + if err != nil { + return err + } + + // Check if there are any rules in the group + if out != nil && out.ProxyRuleGroup != nil && out.ProxyRuleGroup.Rules != nil { + rules := out.ProxyRuleGroup.Rules + if len(rules.PreDNS) > 0 || len(rules.PreREQUEST) > 0 || len(rules.PostRESPONSE) > 0 { + return fmt.Errorf("NetworkFirewall Proxy Rules still exist in group %s", rs.Primary.ID) + } + } + } + + return nil + } +} + +func testAccCheckProxyRulesExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + output, err := tfnetworkfirewall.FindProxyRuleGroupByARN(ctx, conn, rs.Primary.Attributes["proxy_rule_group_arn"]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccProxyRulesConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_rule_group" "test" { + name = %[1]q +} + +resource "aws_networkfirewall_proxy_rules" "test" { + proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn + + pre_dns { + proxy_rule_name = "%[1]s-predns" + action = "ALLOW" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["amazonaws.com"] + } + } + + pre_request { + proxy_rule_name = "%[1]s-prerequest" + action = "DENY" + + conditions { + condition_key = "request:Http:Method" + condition_operator = "StringEquals" + condition_values = ["DELETE"] + } + } + + post_response { + proxy_rule_name = "%[1]s-postresponse" + action = "ALERT" + + conditions { + condition_key = "response:Http:StatusCode" + condition_operator = "NumericGreaterThanEquals" + condition_values = ["500"] + } + } +} +`, rName) +} + +func testAccProxyRulesConfig_single(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_rule_group" "test" { + name = %[1]q +} + +resource "aws_networkfirewall_proxy_rules" "test" { + proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn + + pre_dns { + proxy_rule_name = "%[1]s-predns" + action = "ALLOW" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["amazonaws.com"] + } + } +} +`, rName) +} + +func testAccProxyRulesConfig_modified(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_rule_group" "test" { + name = %[1]q +} + +resource "aws_networkfirewall_proxy_rules" "test" { + proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn + + pre_dns { + proxy_rule_name = "%[1]s-predns" + action = "DENY" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["amazonaws.com"] + } + } + + pre_request { + proxy_rule_name = "%[1]s-prerequest" + action = "DENY" + + conditions { + condition_key = "request:Http:Method" + condition_operator = "StringEquals" + condition_values = ["DELETE"] + } + } + + post_response { + proxy_rule_name = "%[1]s-postresponse" + action = "ALERT" + + conditions { + condition_key = "response:Http:StatusCode" + condition_operator = "NumericGreaterThanEquals" + condition_values = ["500"] + } + } +} +`, rName) +} diff --git a/internal/service/networkfirewall/service_package_gen.go b/internal/service/networkfirewall/service_package_gen.go index 4b4003762706..1925a0d13ee7 100644 --- a/internal/service/networkfirewall/service_package_gen.go +++ b/internal/service/networkfirewall/service_package_gen.go @@ -58,6 +58,12 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser WrappedImport: true, }, }, + { + Factory: newResourceProxyRules, + TypeName: "aws_networkfirewall_proxy_rules", + Name: "Proxy Rules", + Region: unique.Make(inttypes.ResourceRegionDefault()), + }, { Factory: newTLSInspectionConfigurationResource, TypeName: "aws_networkfirewall_tls_inspection_configuration", From e2b609320e1032f66837e2c9ec07a7a0262417e7 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Sat, 3 Jan 2026 21:24:36 +0800 Subject: [PATCH 08/19] proxy rules resource, UpdateProxyRule seems not working properly --- .../service/networkfirewall/proxy_rules.go | 381 ++++++------------ .../networkfirewall/proxy_rules_test.go | 210 +++++++++- .../networkfirewall/service_package_gen.go | 4 + 3 files changed, 338 insertions(+), 257 deletions(-) diff --git a/internal/service/networkfirewall/proxy_rules.go b/internal/service/networkfirewall/proxy_rules.go index 2244bec0b610..bfc79d21589f 100644 --- a/internal/service/networkfirewall/proxy_rules.go +++ b/internal/service/networkfirewall/proxy_rules.go @@ -6,16 +6,12 @@ package networkfirewall import ( "context" "errors" - "fmt" - "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/networkfirewall" awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" - "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" @@ -33,6 +29,8 @@ import ( ) // @FrameworkResource("aws_networkfirewall_proxy_rules", name="Proxy Rules") +// @ArnIdentity("proxy_rule_group_arn",identityDuplicateAttributes="id") +// @ArnFormat("proxy-rule-group/{name}") func newResourceProxyRules(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxyRules{} @@ -45,6 +43,7 @@ const ( type resourceProxyRules struct { framework.ResourceWithModel[resourceProxyRulesModel] + framework.WithImportByIdentity } func (r *resourceProxyRules) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -54,20 +53,6 @@ func (r *resourceProxyRules) Schema(ctx context.Context, req resource.SchemaRequ "proxy_rule_group_arn": schema.StringAttribute{ CustomType: fwtypes.ARNType, Optional: true, - Computed: true, - Validators: []validator.String{ - stringvalidator.AtLeastOneOf(path.MatchRoot("proxy_rule_group_name")), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "proxy_rule_group_name": schema.StringAttribute{ - Optional: true, - Computed: true, - Validators: []validator.String{ - stringvalidator.AtLeastOneOf(path.MatchRoot("proxy_rule_group_arn")), - }, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, @@ -85,9 +70,6 @@ func (r *resourceProxyRules) Schema(ctx context.Context, req resource.SchemaRequ names.AttrDescription: schema.StringAttribute{ Optional: true, }, - "insert_position": schema.Int64Attribute{ - Optional: true, - }, "proxy_rule_name": schema.StringAttribute{ Required: true, }, @@ -128,9 +110,6 @@ func (r *resourceProxyRules) Schema(ctx context.Context, req resource.SchemaRequ names.AttrDescription: schema.StringAttribute{ Optional: true, }, - "insert_position": schema.Int64Attribute{ - Optional: true, - }, "proxy_rule_name": schema.StringAttribute{ Required: true, }, @@ -171,9 +150,6 @@ func (r *resourceProxyRules) Schema(ctx context.Context, req resource.SchemaRequ names.AttrDescription: schema.StringAttribute{ Optional: true, }, - "insert_position": schema.Int64Attribute{ - Optional: true, - }, "proxy_rule_name": schema.StringAttribute{ Required: true, }, @@ -216,8 +192,9 @@ func (r *resourceProxyRules) Create(ctx context.Context, req resource.CreateRequ return } - var input networkfirewall.CreateProxyRulesInput - input.ProxyRuleGroupArn = plan.ProxyRuleGroupArn.ValueStringPointer() + input := networkfirewall.CreateProxyRulesInput{ + ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), + } // Create the Rules structure organized by phase var rulesByPhase awstypes.CreateProxyRulesByRequestPhase @@ -230,12 +207,15 @@ func (r *resourceProxyRules) Create(ctx context.Context, req resource.CreateRequ return } - for _, ruleModel := range postRules { + for i, ruleModel := range postRules { var rule awstypes.CreateProxyRule smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) if resp.Diagnostics.HasError() { return } + // Set InsertPosition based on index + insertPos := int32(i) + rule.InsertPosition = &insertPos rulesByPhase.PostRESPONSE = append(rulesByPhase.PostRESPONSE, rule) } } @@ -248,12 +228,15 @@ func (r *resourceProxyRules) Create(ctx context.Context, req resource.CreateRequ return } - for _, ruleModel := range preDNSRules { + for i, ruleModel := range preDNSRules { var rule awstypes.CreateProxyRule smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) if resp.Diagnostics.HasError() { return } + // Set InsertPosition based on index + insertPos := int32(i) + rule.InsertPosition = &insertPos rulesByPhase.PreDNS = append(rulesByPhase.PreDNS, rule) } } @@ -266,12 +249,15 @@ func (r *resourceProxyRules) Create(ctx context.Context, req resource.CreateRequ return } - for _, ruleModel := range preRequestRules { + for i, ruleModel := range preRequestRules { var rule awstypes.CreateProxyRule smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) if resp.Diagnostics.HasError() { return } + // Set InsertPosition based on index + insertPos := int32(i) + rule.InsertPosition = &insertPos rulesByPhase.PreREQUEST = append(rulesByPhase.PreREQUEST, rule) } } @@ -289,7 +275,7 @@ func (r *resourceProxyRules) Create(ctx context.Context, req resource.CreateRequ } // Set ID to the proxy rule group ARN - plan.ID = plan.ProxyRuleGroupArn.StringValue + plan.setID() // Read back to get full state readOut, err := findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) @@ -315,14 +301,14 @@ func (r *resourceProxyRules) Read(ctx context.Context, req resource.ReadRequest, return } - out, err := findProxyRulesByGroupARN(ctx, conn, state.ID.ValueString()) + out, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) if tfretry.NotFound(err) { resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) resp.State.RemoveResource(ctx) return } if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ProxyRuleGroupArn.String()) return } @@ -344,7 +330,7 @@ func (r *resourceProxyRules) Update(ctx context.Context, req resource.UpdateRequ return } - // Get current state to obtain update token + // Get current state to obtain update token and existing rules from AWS currentRules, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) if err != nil && !tfretry.NotFound(err) { smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) @@ -357,26 +343,25 @@ func (r *resourceProxyRules) Update(ctx context.Context, req resource.UpdateRequ stateRulesByName := make(map[string]proxyRuleModel) planRulesByName := make(map[string]proxyRuleModel) - // Extract state rules - if !state.PostRESPONSE.IsNull() && !state.PostRESPONSE.IsUnknown() { - var rules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, state.PostRESPONSE.ElementsAs(ctx, &rules, false)) - for _, rule := range rules { - stateRulesByName[rule.ProxyRuleName.ValueString()] = rule + // Extract existing rules from AWS (currentRules) instead of Terraform state + // This ensures we correctly identify which rules already exist in AWS + if currentRules != nil && currentRules.ProxyRuleGroup != nil && currentRules.ProxyRuleGroup.Rules != nil { + rules := currentRules.ProxyRuleGroup.Rules + + for _, rule := range rules.PostRESPONSE { + var ruleModel proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, &rule, &ruleModel)) + stateRulesByName[ruleModel.ProxyRuleName.ValueString()] = ruleModel } - } - if !state.PreDNS.IsNull() && !state.PreDNS.IsUnknown() { - var rules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, state.PreDNS.ElementsAs(ctx, &rules, false)) - for _, rule := range rules { - stateRulesByName[rule.ProxyRuleName.ValueString()] = rule + for _, rule := range rules.PreDNS { + var ruleModel proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, &rule, &ruleModel)) + stateRulesByName[ruleModel.ProxyRuleName.ValueString()] = ruleModel } - } - if !state.PreREQUEST.IsNull() && !state.PreREQUEST.IsUnknown() { - var rules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, state.PreREQUEST.ElementsAs(ctx, &rules, false)) - for _, rule := range rules { - stateRulesByName[rule.ProxyRuleName.ValueString()] = rule + for _, rule := range rules.PreREQUEST { + var ruleModel proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, &rule, &ruleModel)) + stateRulesByName[ruleModel.ProxyRuleName.ValueString()] = ruleModel } } @@ -443,7 +428,7 @@ func (r *resourceProxyRules) Update(ctx context.Context, req resource.UpdateRequ return } - // Refresh update token after deletion + // Refresh update token after deletion for subsequent UpdateProxyRule calls currentRules, err = findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) if err != nil { smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) @@ -454,6 +439,14 @@ func (r *resourceProxyRules) Update(ctx context.Context, req resource.UpdateRequ // Update modified rules for _, ruleModel := range rulesToUpdate { + // Refresh the update token before each update to ensure we have the latest + currentRules, err = findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + updateToken = currentRules.UpdateToken + updateInput := networkfirewall.UpdateProxyRuleInput{ ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), ProxyRuleName: ruleModel.ProxyRuleName.ValueStringPointer(), @@ -513,50 +506,80 @@ func (r *resourceProxyRules) Update(ctx context.Context, req resource.UpdateRequ if len(rulesToCreate) > 0 { var rulesByPhase awstypes.CreateProxyRulesByRequestPhase - // Organize rules to create by phase - for _, ruleModel := range rulesToCreate { - var createRule awstypes.CreateProxyRule - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &createRule)) - if resp.Diagnostics.HasError() { - return - } - - // Determine which phase this rule belongs to - ruleName := ruleModel.ProxyRuleName.ValueString() - if _, exists := planRulesByName[ruleName]; exists { - // Check plan to determine phase - if !plan.PostRESPONSE.IsNull() { - var postRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PostRESPONSE.ElementsAs(ctx, &postRules, false)) - for _, r := range postRules { - if r.ProxyRuleName.ValueString() == ruleName { - rulesByPhase.PostRESPONSE = append(rulesByPhase.PostRESPONSE, createRule) - goto nextRule + // Process each phase separately with proper InsertPosition + // Process PostRESPONSE rules + if !plan.PostRESPONSE.IsNull() { + var postRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PostRESPONSE.ElementsAs(ctx, &postRules, false)) + for i, r := range postRules { + if _, exists := planRulesByName[r.ProxyRuleName.ValueString()]; exists { + // Check if this rule is in the create list + for _, createRule := range rulesToCreate { + if createRule.ProxyRuleName.ValueString() == r.ProxyRuleName.ValueString() { + var rule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, createRule, &rule)) + if resp.Diagnostics.HasError() { + return + } + // Set InsertPosition based on index + insertPos := int32(i) + rule.InsertPosition = &insertPos + rulesByPhase.PostRESPONSE = append(rulesByPhase.PostRESPONSE, rule) + break } } } - if !plan.PreDNS.IsNull() { - var preDNSRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreDNS.ElementsAs(ctx, &preDNSRules, false)) - for _, r := range preDNSRules { - if r.ProxyRuleName.ValueString() == ruleName { - rulesByPhase.PreDNS = append(rulesByPhase.PreDNS, createRule) - goto nextRule + } + } + + // Process PreDNS rules + if !plan.PreDNS.IsNull() { + var preDNSRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreDNS.ElementsAs(ctx, &preDNSRules, false)) + for i, r := range preDNSRules { + if _, exists := planRulesByName[r.ProxyRuleName.ValueString()]; exists { + // Check if this rule is in the create list + for _, createRule := range rulesToCreate { + if createRule.ProxyRuleName.ValueString() == r.ProxyRuleName.ValueString() { + var rule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, createRule, &rule)) + if resp.Diagnostics.HasError() { + return + } + // Set InsertPosition based on index + insertPos := int32(i) + rule.InsertPosition = &insertPos + rulesByPhase.PreDNS = append(rulesByPhase.PreDNS, rule) + break } } } - if !plan.PreREQUEST.IsNull() { - var preRequestRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreREQUEST.ElementsAs(ctx, &preRequestRules, false)) - for _, r := range preRequestRules { - if r.ProxyRuleName.ValueString() == ruleName { - rulesByPhase.PreREQUEST = append(rulesByPhase.PreREQUEST, createRule) - goto nextRule + } + } + + // Process PreREQUEST rules + if !plan.PreREQUEST.IsNull() { + var preRequestRules []proxyRuleModel + smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreREQUEST.ElementsAs(ctx, &preRequestRules, false)) + for i, r := range preRequestRules { + if _, exists := planRulesByName[r.ProxyRuleName.ValueString()]; exists { + // Check if this rule is in the create list + for _, createRule := range rulesToCreate { + if createRule.ProxyRuleName.ValueString() == r.ProxyRuleName.ValueString() { + var rule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, createRule, &rule)) + if resp.Diagnostics.HasError() { + return + } + // Set InsertPosition based on index + insertPos := int32(i) + rule.InsertPosition = &insertPos + rulesByPhase.PreREQUEST = append(rulesByPhase.PreREQUEST, rule) + break } } } } - nextRule: } createInput := networkfirewall.CreateProxyRulesInput{ @@ -598,10 +621,7 @@ func ruleModelsEqual(ctx context.Context, a, b proxyRuleModel) bool { return false } - // Compare insert position - if a.InsertPosition.ValueInt64() != b.InsertPosition.ValueInt64() { - return false - } + // Note: InsertPosition is not compared as it's auto-populated and not stored in state // Compare conditions count if a.Conditions.IsNull() != b.Conditions.IsNull() { @@ -701,147 +721,6 @@ func (r *resourceProxyRules) Delete(ctx context.Context, req resource.DeleteRequ } } -func (r *resourceProxyRules) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - conn := r.Meta().NetworkFirewallClient(ctx) - - // Parse the composite ID (ProxyRuleGroupArn,RuleName1,RuleName2,...) - // Minimum 2 parts: ARN and at least one rule name - parts := strings.Split(req.ID, ",") - - if len(parts) < 2 { - resp.Diagnostics.AddError( - "Invalid Import ID", - fmt.Sprintf("Expected import ID format: 'proxy_rule_group_arn,rule_name1[,rule_name2,...]'. Got: %s", req.ID), - ) - return - } - - proxyRuleGroupArn := parts[0] - ruleNames := parts[1:] - - // Fetch all rules for the group - out, err := findProxyRulesByGroupARN(ctx, conn, proxyRuleGroupArn) - if err != nil { - resp.Diagnostics.AddError( - "Import Failed", - fmt.Sprintf("Could not find proxy rule group %s: %s", proxyRuleGroupArn, err.Error()), - ) - return - } - - if out.ProxyRuleGroup == nil || out.ProxyRuleGroup.Rules == nil { - resp.Diagnostics.AddError( - "Import Failed", - fmt.Sprintf("Proxy rule group %s has no rules", proxyRuleGroupArn), - ) - return - } - - rules := out.ProxyRuleGroup.Rules - - // Create a map to track which rule names we're looking for - ruleNamesToFind := make(map[string]bool) - for _, name := range ruleNames { - ruleNamesToFind[name] = true - } - - // Collect rules by phase - var postResponseRules []proxyRuleModel - var preDNSRules []proxyRuleModel - var preRequestRules []proxyRuleModel - var diags diag.Diagnostics - - // Search for rules in PostRESPONSE phase - for _, rule := range rules.PostRESPONSE { - if rule.ProxyRuleName != nil && ruleNamesToFind[*rule.ProxyRuleName] { - var ruleModel proxyRuleModel - diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) - if diags.HasError() { - resp.Diagnostics.Append(diags...) - return - } - postResponseRules = append(postResponseRules, ruleModel) - delete(ruleNamesToFind, *rule.ProxyRuleName) // Mark as found - } - } - - // Search for rules in PreDNS phase - for _, rule := range rules.PreDNS { - if rule.ProxyRuleName != nil && ruleNamesToFind[*rule.ProxyRuleName] { - var ruleModel proxyRuleModel - diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) - if diags.HasError() { - resp.Diagnostics.Append(diags...) - return - } - preDNSRules = append(preDNSRules, ruleModel) - delete(ruleNamesToFind, *rule.ProxyRuleName) // Mark as found - } - } - - // Search for rules in PreREQUEST phase - for _, rule := range rules.PreREQUEST { - if rule.ProxyRuleName != nil && ruleNamesToFind[*rule.ProxyRuleName] { - var ruleModel proxyRuleModel - diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) - if diags.HasError() { - resp.Diagnostics.Append(diags...) - return - } - preRequestRules = append(preRequestRules, ruleModel) - delete(ruleNamesToFind, *rule.ProxyRuleName) // Mark as found - } - } - - // Check if any rules were not found - if len(ruleNamesToFind) > 0 { - var notFoundRules []string - for ruleName := range ruleNamesToFind { - notFoundRules = append(notFoundRules, ruleName) - } - resp.Diagnostics.AddError( - "Import Failed", - fmt.Sprintf("The following rules were not found in proxy rule group %s: %v", proxyRuleGroupArn, notFoundRules), - ) - return - } - - // Create model with the found rules - var model resourceProxyRulesModel - model.ID = types.StringValue(proxyRuleGroupArn) - model.ProxyRuleGroupArn = fwtypes.ARNValue(proxyRuleGroupArn) - - if out.ProxyRuleGroup.ProxyRuleGroupName != nil { - model.ProxyRuleGroupName = flex.StringToFramework(ctx, out.ProxyRuleGroup.ProxyRuleGroupName) - } - - // Set rules for each phase - if len(postResponseRules) > 0 { - postList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, postResponseRules) - diags.Append(d...) - model.PostRESPONSE = postList - } - - if len(preDNSRules) > 0 { - preDNSList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, preDNSRules) - diags.Append(d...) - model.PreDNS = preDNSList - } - - if len(preRequestRules) > 0 { - preRequestList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, preRequestRules) - diags.Append(d...) - model.PreREQUEST = preRequestList - } - - if diags.HasError() { - resp.Diagnostics.Append(diags...) - return - } - - resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) -} - func findProxyRulesByGroupARN(ctx context.Context, conn *networkfirewall.Client, groupARN string) (*networkfirewall.DescribeProxyRuleGroupOutput, error) { input := networkfirewall.DescribeProxyRuleGroupInput{ ProxyRuleGroupArn: aws.String(groupARN), @@ -893,6 +772,8 @@ func flattenProxyRules(ctx context.Context, out *networkfirewall.DescribeProxyRu return diags } model.PostRESPONSE = postList + } else { + model.PostRESPONSE = fwtypes.NewListNestedObjectValueOfNull[proxyRuleModel](ctx) } // Process PreDNS rules @@ -912,6 +793,8 @@ func flattenProxyRules(ctx context.Context, out *networkfirewall.DescribeProxyRu return diags } model.PreDNS = preDNSList + } else { + model.PreDNS = fwtypes.NewListNestedObjectValueOfNull[proxyRuleModel](ctx) } // Process PreREQUEST rules @@ -931,10 +814,8 @@ func flattenProxyRules(ctx context.Context, out *networkfirewall.DescribeProxyRu return diags } model.PreREQUEST = preRequestList - } - - if out.ProxyRuleGroup.ProxyRuleGroupName != nil { - model.ProxyRuleGroupName = flex.StringToFramework(ctx, out.ProxyRuleGroup.ProxyRuleGroupName) + } else { + model.PreREQUEST = fwtypes.NewListNestedObjectValueOfNull[proxyRuleModel](ctx) } if out.ProxyRuleGroup.ProxyRuleGroupArn != nil { @@ -946,20 +827,18 @@ func flattenProxyRules(ctx context.Context, out *networkfirewall.DescribeProxyRu type resourceProxyRulesModel struct { framework.WithRegionModel - ID types.String `tfsdk:"id"` - PostRESPONSE fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"post_response"` - PreDNS fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_dns"` - PreREQUEST fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_request"` - ProxyRuleGroupArn fwtypes.ARN `tfsdk:"proxy_rule_group_arn"` - ProxyRuleGroupName types.String `tfsdk:"proxy_rule_group_name"` + ID types.String `tfsdk:"id"` + PostRESPONSE fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"post_response"` + PreDNS fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_dns"` + PreREQUEST fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_request"` + ProxyRuleGroupArn fwtypes.ARN `tfsdk:"proxy_rule_group_arn"` } type proxyRuleModel struct { - Action fwtypes.StringEnum[awstypes.ProxyRulePhaseAction] `tfsdk:"action"` - Conditions fwtypes.ListNestedObjectValueOf[conditionModel] `tfsdk:"conditions"` - Description types.String `tfsdk:"description"` - InsertPosition types.Int64 `tfsdk:"insert_position"` - ProxyRuleName types.String `tfsdk:"proxy_rule_name"` + Action fwtypes.StringEnum[awstypes.ProxyRulePhaseAction] `tfsdk:"action"` + Conditions fwtypes.ListNestedObjectValueOf[conditionModel] `tfsdk:"conditions"` + Description types.String `tfsdk:"description"` + ProxyRuleName types.String `tfsdk:"proxy_rule_name"` } type conditionModel struct { @@ -967,3 +846,7 @@ type conditionModel struct { ConditionOperator types.String `tfsdk:"condition_operator"` ConditionValues fwtypes.ListValueOf[types.String] `tfsdk:"condition_values"` } + +func (data *resourceProxyRulesModel) setID() { + data.ID = data.ProxyRuleGroupArn.StringValue +} diff --git a/internal/service/networkfirewall/proxy_rules_test.go b/internal/service/networkfirewall/proxy_rules_test.go index 2bf45969afb2..ebf3702aeb38 100644 --- a/internal/service/networkfirewall/proxy_rules_test.go +++ b/internal/service/networkfirewall/proxy_rules_test.go @@ -38,7 +38,6 @@ func TestAccNetworkFirewallProxyRules_basic(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckProxyRulesExists(ctx, resourceName, &v), resource.TestCheckResourceAttrPair(resourceName, "proxy_rule_group_arn", ruleGroupResourceName, names.AttrARN), - resource.TestCheckResourceAttrPair(resourceName, "proxy_rule_group_name", ruleGroupResourceName, names.AttrName), // Pre-DNS phase resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.proxy_rule_name", fmt.Sprintf("%s-predns", rName)), @@ -69,9 +68,9 @@ func TestAccNetworkFirewallProxyRules_basic(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + Config: testAccProxyRulesConfig_single(rName), + ResourceName: resourceName, + ImportState: true, }, }, }) @@ -98,7 +97,7 @@ func TestAccNetworkFirewallProxyRules_disappears(t *testing.T) { ExpectNonEmptyPlan: true, ConfigPlanChecks: resource.ConfigPlanChecks{ PostApplyPostRefresh: []plancheck.PlanCheck{ - plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), }, }, }, @@ -128,12 +127,19 @@ func TestAccNetworkFirewallProxyRules_updateAdd(t *testing.T) { ), }, { - Config: testAccProxyRulesConfig_basic(rName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccProxyRulesConfig_add(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckProxyRulesExists(ctx, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "1"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.proxy_rule_name", fmt.Sprintf("%s-prerequest-new", rName)), + resource.TestCheckResourceAttr(resourceName, "post_response.0.proxy_rule_name", fmt.Sprintf("%s-postresponse-new", rName)), ), }, }, @@ -157,15 +163,23 @@ func TestAccNetworkFirewallProxyRules_updateModify(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckProxyRulesExists(ctx, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "ALLOW"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "amazonaws.com"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccProxyRulesConfig_modified(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckProxyRulesExists(ctx, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "DENY"), - resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "amazonaws.com"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.#", "2"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "example.com"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.1", "test.com"), ), }, }, @@ -193,6 +207,11 @@ func TestAccNetworkFirewallProxyRules_updateRemove(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccProxyRulesConfig_single(rName), Check: resource.ComposeAggregateTestCheckFunc( @@ -206,6 +225,58 @@ func TestAccNetworkFirewallProxyRules_updateRemove(t *testing.T) { }) } +func TestAccNetworkFirewallProxyRules_multipleRulesPerPhase(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyRuleGroupOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_rules.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyRulesConfig_multiplePerPhase(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExists(ctx, resourceName, &v), + // Pre-DNS phase - 2 rules + resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "2"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.proxy_rule_name", fmt.Sprintf("%s-predns-1", rName)), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "ALLOW"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "amazonaws.com"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.1.proxy_rule_name", fmt.Sprintf("%s-predns-2", rName)), + resource.TestCheckResourceAttr(resourceName, "pre_dns.1.action", "DENY"), + resource.TestCheckResourceAttr(resourceName, "pre_dns.1.conditions.0.condition_values.0", "malicious.com"), + // Pre-REQUEST phase - 2 rules + resource.TestCheckResourceAttr(resourceName, "pre_request.#", "2"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.proxy_rule_name", fmt.Sprintf("%s-prerequest-1", rName)), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.action", "DENY"), + resource.TestCheckResourceAttr(resourceName, "pre_request.0.conditions.0.condition_values.0", "DELETE"), + resource.TestCheckResourceAttr(resourceName, "pre_request.1.proxy_rule_name", fmt.Sprintf("%s-prerequest-2", rName)), + resource.TestCheckResourceAttr(resourceName, "pre_request.1.action", "DENY"), + resource.TestCheckResourceAttr(resourceName, "pre_request.1.conditions.0.condition_values.0", "PUT"), + // Post-RESPONSE phase - 2 rules + resource.TestCheckResourceAttr(resourceName, "post_response.#", "2"), + resource.TestCheckResourceAttr(resourceName, "post_response.0.proxy_rule_name", fmt.Sprintf("%s-postresponse-1", rName)), + resource.TestCheckResourceAttr(resourceName, "post_response.0.action", "ALERT"), + resource.TestCheckResourceAttr(resourceName, "post_response.0.conditions.0.condition_values.0", "500"), + resource.TestCheckResourceAttr(resourceName, "post_response.1.proxy_rule_name", fmt.Sprintf("%s-postresponse-2", rName)), + resource.TestCheckResourceAttr(resourceName, "post_response.1.action", "ALERT"), + resource.TestCheckResourceAttr(resourceName, "post_response.1.conditions.0.condition_values.0", "404"), + ), + }, + { + + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckProxyRulesDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) @@ -344,7 +415,7 @@ resource "aws_networkfirewall_proxy_rules" "test" { conditions { condition_key = "request:DestinationDomain" condition_operator = "StringEquals" - condition_values = ["amazonaws.com"] + condition_values = ["example.com", "test.com"] } } @@ -372,3 +443,126 @@ resource "aws_networkfirewall_proxy_rules" "test" { } `, rName) } + +func testAccProxyRulesConfig_add(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_rule_group" "test" { + name = %[1]q +} + +resource "aws_networkfirewall_proxy_rules" "test" { + proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn + + pre_dns { + proxy_rule_name = "%[1]s-predns" + action = "ALLOW" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["amazonaws.com"] + } + } + + pre_request { + proxy_rule_name = "%[1]s-prerequest-new" + action = "DENY" + + conditions { + condition_key = "request:Http:Method" + condition_operator = "StringEquals" + condition_values = ["POST"] + } + } + + post_response { + proxy_rule_name = "%[1]s-postresponse-new" + action = "ALERT" + + conditions { + condition_key = "response:Http:StatusCode" + condition_operator = "NumericGreaterThanEquals" + condition_values = ["400"] + } + } +} +`, rName) +} + +func testAccProxyRulesConfig_multiplePerPhase(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_rule_group" "test" { + name = %[1]q +} + +resource "aws_networkfirewall_proxy_rules" "test" { + proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn + + pre_dns { + proxy_rule_name = "%[1]s-predns-1" + action = "ALLOW" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["amazonaws.com"] + } + } + + pre_dns { + proxy_rule_name = "%[1]s-predns-2" + action = "DENY" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["malicious.com"] + } + } + + pre_request { + proxy_rule_name = "%[1]s-prerequest-1" + action = "DENY" + + conditions { + condition_key = "request:Http:Method" + condition_operator = "StringEquals" + condition_values = ["DELETE"] + } + } + + pre_request { + proxy_rule_name = "%[1]s-prerequest-2" + action = "DENY" + + conditions { + condition_key = "request:Http:Method" + condition_operator = "StringEquals" + condition_values = ["PUT"] + } + } + + post_response { + proxy_rule_name = "%[1]s-postresponse-1" + action = "ALERT" + + conditions { + condition_key = "response:Http:StatusCode" + condition_operator = "NumericGreaterThanEquals" + condition_values = ["500"] + } + } + + post_response { + proxy_rule_name = "%[1]s-postresponse-2" + action = "ALERT" + + conditions { + condition_key = "response:Http:StatusCode" + condition_operator = "NumericEquals" + condition_values = ["404"] + } + } +} +`, rName) +} diff --git a/internal/service/networkfirewall/service_package_gen.go b/internal/service/networkfirewall/service_package_gen.go index 1925a0d13ee7..26af315313a9 100644 --- a/internal/service/networkfirewall/service_package_gen.go +++ b/internal/service/networkfirewall/service_package_gen.go @@ -63,6 +63,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser TypeName: "aws_networkfirewall_proxy_rules", Name: "Proxy Rules", Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalARNIdentityNamed("proxy_rule_group_arn", inttypes.WithIdentityDuplicateAttrs(names.AttrID)), + Import: inttypes.FrameworkImport{ + WrappedImport: true, + }, }, { Factory: newTLSInspectionConfigurationResource, From 4a9e4aaafc30b4c289578502f84641e0a753f372 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Sun, 4 Jan 2026 21:03:25 +0800 Subject: [PATCH 09/19] added proxy configuration rule groups attachment --- ...xy_configuration_rule_groups_attachment.go | 436 ++++++++++++++++++ ...nfiguration_rule_groups_attachment_test.go | 377 +++++++++++++++ 2 files changed, 813 insertions(+) create mode 100644 internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go create mode 100644 internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go diff --git a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go b/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go new file mode 100644 index 000000000000..f58af41e5673 --- /dev/null +++ b/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go @@ -0,0 +1,436 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + "errors" + "sort" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "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" + tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_networkfirewall_proxy_configuration_rule_groups_attachment", name="Proxy Configuration Rule Groups Attachment") +// @ArnIdentity("proxy_configuration_arn",identityDuplicateAttributes="id") +func newResourceProxyConfigurationRuleGroupsAttachmentResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProxyConfigurationRuleGroupsAttachment{} + + return r, nil +} + +const ( + ResNameProxyConfigurationRuleGroupsAttachment = "Proxy Configuration Rule Groups Attachment" +) + +type resourceProxyConfigurationRuleGroupsAttachment struct { + framework.ResourceWithModel[resourceProxyConfigurationRuleGroupsAttachmentModel] + framework.WithImportByIdentity +} + +func (r *resourceProxyConfigurationRuleGroupsAttachment) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_networkfirewall_proxy_configuration_rule_groups_attachment" +} + +func (r *resourceProxyConfigurationRuleGroupsAttachment) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root(names.AttrARN)), + "proxy_configuration_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "update_token": schema.StringAttribute{ + Computed: true, + }, + }, + Blocks: map[string]schema.Block{ + "rule_group": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[RuleGroupAttachmentModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "proxy_rule_group_name": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + } +} + +func (r *resourceProxyConfigurationRuleGroupsAttachment) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan resourceProxyConfigurationRuleGroupsAttachmentModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + if resp.Diagnostics.HasError() { + return + } + + proxyConfigArn := plan.ProxyConfigurationArn.ValueString() + + // First, get the current update token + out, err := findProxyConfigurationByARN(ctx, conn, proxyConfigArn) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + + // Build the list of rule groups to attach with InsertPosition based on order + var ruleGroups []awstypes.ProxyRuleGroupAttachment + planRuleGroups, d := plan.RuleGroups.ToSlice(ctx) + smerr.AddEnrich(ctx, &resp.Diagnostics, d) + if resp.Diagnostics.HasError() { + return + } + + for i, rg := range planRuleGroups { + ruleGroups = append(ruleGroups, awstypes.ProxyRuleGroupAttachment{ + ProxyRuleGroupName: aws.String(rg.ProxyRuleGroupName.ValueString()), + InsertPosition: aws.Int32(int32(i)), + }) + } + + input := &networkfirewall.AttachRuleGroupsToProxyConfigurationInput{ + ProxyConfigurationArn: aws.String(proxyConfigArn), + RuleGroups: ruleGroups, + UpdateToken: out.UpdateToken, + } + + _, err = conn.AttachRuleGroupsToProxyConfiguration(ctx, input) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + + plan.setID() + + // Read back the update token from AWS + refreshedOut, err := findProxyConfigurationByARN(ctx, conn, proxyConfigArn) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + + // Set the update token from the refreshed state, but keep the plan's rule group order + // since we've just applied that order via the API + plan.UpdateToken = flex.StringToFramework(ctx, refreshedOut.UpdateToken) + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, plan)) +} + +func (r *resourceProxyConfigurationRuleGroupsAttachment) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyConfigurationRuleGroupsAttachmentModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + proxyConfigArn := state.ID.ValueString() + + out, err := findProxyConfigurationByARN(ctx, conn, proxyConfigArn) + if tfretry.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + + // If there are no rule groups attached, the attachment resource no longer exists + if out.ProxyConfiguration == nil || out.ProxyConfiguration.RuleGroups == nil || len(out.ProxyConfiguration.RuleGroups) == 0 { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(errors.New("no rule groups attached to proxy configuration"))) + resp.State.RemoveResource(ctx) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyConfigurationRuleGroups(ctx, out, &state)) + if resp.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) +} + +func (r *resourceProxyConfigurationRuleGroupsAttachment) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan, state resourceProxyConfigurationRuleGroupsAttachmentModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + proxyConfigArn := state.ID.ValueString() + + // Get current update token from AWS (required for attach/detach operations) + out, err := findProxyConfigurationByARN(ctx, conn, proxyConfigArn) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + + updateToken := out.UpdateToken + + // Get the current rule groups from Terraform state + stateRuleGroups, d := state.RuleGroups.ToSlice(ctx) + smerr.AddEnrich(ctx, &resp.Diagnostics, d) + if resp.Diagnostics.HasError() { + return + } + + // Get the planned rule groups + planRuleGroups, d := plan.RuleGroups.ToSlice(ctx) + smerr.AddEnrich(ctx, &resp.Diagnostics, d) + if resp.Diagnostics.HasError() { + return + } + + // Build maps for comparison + stateRuleGroupNames := make(map[string]bool) + for _, rg := range stateRuleGroups { + stateRuleGroupNames[rg.ProxyRuleGroupName.ValueString()] = true + } + + planRuleGroupNames := make(map[string]bool) + for _, rg := range planRuleGroups { + planRuleGroupNames[rg.ProxyRuleGroupName.ValueString()] = true + } + + // Detach rule groups that are in state but not in plan + var ruleGroupsToDetach []string + for name := range stateRuleGroupNames { + if !planRuleGroupNames[name] { + ruleGroupsToDetach = append(ruleGroupsToDetach, name) + } + } + + if len(ruleGroupsToDetach) > 0 { + detachInput := &networkfirewall.DetachRuleGroupsFromProxyConfigurationInput{ + ProxyConfigurationArn: aws.String(proxyConfigArn), + RuleGroupNames: ruleGroupsToDetach, + UpdateToken: updateToken, + } + + detachOut, err := conn.DetachRuleGroupsFromProxyConfiguration(ctx, detachInput) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + updateToken = detachOut.UpdateToken + } + + // Attach rule groups that are in plan but not in state + var ruleGroupsToAttach []awstypes.ProxyRuleGroupAttachment + for i, rg := range planRuleGroups { + if !stateRuleGroupNames[rg.ProxyRuleGroupName.ValueString()] { + ruleGroupsToAttach = append(ruleGroupsToAttach, awstypes.ProxyRuleGroupAttachment{ + ProxyRuleGroupName: aws.String(rg.ProxyRuleGroupName.ValueString()), + InsertPosition: aws.Int32(int32(i)), + }) + } + } + + if len(ruleGroupsToAttach) > 0 { + attachInput := &networkfirewall.AttachRuleGroupsToProxyConfigurationInput{ + ProxyConfigurationArn: aws.String(proxyConfigArn), + RuleGroups: ruleGroupsToAttach, + UpdateToken: updateToken, + } + + attachOut, err := conn.AttachRuleGroupsToProxyConfiguration(ctx, attachInput) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + updateToken = attachOut.UpdateToken + } + + // Check if reordering is needed (same groups but different order) + needsReorder := false + if len(stateRuleGroups) == len(planRuleGroups) && len(ruleGroupsToAttach) == 0 && len(ruleGroupsToDetach) == 0 { + // Same groups exist, check if order has changed + for i := range planRuleGroups { + if planRuleGroups[i].ProxyRuleGroupName.ValueString() != stateRuleGroups[i].ProxyRuleGroupName.ValueString() { + needsReorder = true + break + } + } + } + + if needsReorder { + var ruleGroupPriorities []awstypes.ProxyRuleGroupPriority + for i, rg := range planRuleGroups { + ruleGroupPriorities = append(ruleGroupPriorities, awstypes.ProxyRuleGroupPriority{ + ProxyRuleGroupName: aws.String(rg.ProxyRuleGroupName.ValueString()), + NewPosition: aws.Int32(int32(i)), + }) + } + + priorityInput := &networkfirewall.UpdateProxyRuleGroupPrioritiesInput{ + ProxyConfigurationArn: aws.String(proxyConfigArn), + RuleGroups: ruleGroupPriorities, + UpdateToken: updateToken, + } + + priorityOut, err := conn.UpdateProxyRuleGroupPriorities(ctx, priorityInput) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + updateToken = priorityOut.UpdateToken + } + + // Read back the update token from AWS + refreshedOut, err := findProxyConfigurationByARN(ctx, conn, proxyConfigArn) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + + // Set the update token from the refreshed state, but keep the plan's rule group order + // since we've just applied that order via the API + plan.UpdateToken = flex.StringToFramework(ctx, refreshedOut.UpdateToken) + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &plan)) +} + +func (r *resourceProxyConfigurationRuleGroupsAttachment) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyConfigurationRuleGroupsAttachmentModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + proxyConfigArn := state.ID.ValueString() + + // Get current state to retrieve update token + out, err := findProxyConfigurationByARN(ctx, conn, proxyConfigArn) + if tfretry.NotFound(err) { + return + } + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } + + // Get all rule groups to detach + stateRuleGroups, d := state.RuleGroups.ToSlice(ctx) + smerr.AddEnrich(ctx, &resp.Diagnostics, d) + if resp.Diagnostics.HasError() { + return + } + + if len(stateRuleGroups) == 0 { + return + } + + var ruleGroupNames []string + for _, rg := range stateRuleGroups { + ruleGroupNames = append(ruleGroupNames, rg.ProxyRuleGroupName.ValueString()) + } + + input := &networkfirewall.DetachRuleGroupsFromProxyConfigurationInput{ + ProxyConfigurationArn: aws.String(proxyConfigArn), + RuleGroupNames: ruleGroupNames, + UpdateToken: out.UpdateToken, + } + + _, err = conn.DetachRuleGroupsFromProxyConfiguration(ctx, input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + // Ignore error if rule groups are already detached (specific InvalidRequestException message) + var invalidRequestErr *awstypes.InvalidRequestException + if errors.As(err, &invalidRequestErr) && invalidRequestErr.Message != nil { + if strings.Contains(*invalidRequestErr.Message, "not currently attached") { + return + } + } + + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) + return + } +} + +type resourceProxyConfigurationRuleGroupsAttachmentModel struct { + framework.WithRegionModel + ID types.String `tfsdk:"id"` + ProxyConfigurationArn fwtypes.ARN `tfsdk:"proxy_configuration_arn"` + RuleGroups fwtypes.ListNestedObjectValueOf[RuleGroupAttachmentModel] `tfsdk:"rule_group"` + UpdateToken types.String `tfsdk:"update_token"` +} + +// RuleGroupAttachmentModel is exported for use in tests +type RuleGroupAttachmentModel struct { + ProxyRuleGroupName types.String `tfsdk:"proxy_rule_group_name"` +} + +func (data *resourceProxyConfigurationRuleGroupsAttachmentModel) setID() { + data.ID = data.ProxyConfigurationArn.StringValue +} + +func flattenProxyConfigurationRuleGroups(ctx context.Context, out *networkfirewall.DescribeProxyConfigurationOutput, model *resourceProxyConfigurationRuleGroupsAttachmentModel) diag.Diagnostics { + var diags diag.Diagnostics + + if out.ProxyConfiguration == nil || out.ProxyConfiguration.RuleGroups == nil { + model.RuleGroups = fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []RuleGroupAttachmentModel{}) + model.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) + return diags + } + + // Sort by Priority to maintain order (lower priority number = higher priority = first in list) + sortedRuleGroups := make([]awstypes.ProxyConfigRuleGroup, len(out.ProxyConfiguration.RuleGroups)) + copy(sortedRuleGroups, out.ProxyConfiguration.RuleGroups) + sort.SliceStable(sortedRuleGroups, func(i, j int) bool { + return aws.ToInt32(sortedRuleGroups[i].Priority) < aws.ToInt32(sortedRuleGroups[j].Priority) + }) + + var ruleGroups []RuleGroupAttachmentModel + for _, rg := range sortedRuleGroups { + ruleGroups = append(ruleGroups, RuleGroupAttachmentModel{ + ProxyRuleGroupName: flex.StringToFramework(ctx, rg.ProxyRuleGroupName), + }) + } + + model.RuleGroups = fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, ruleGroups) + model.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) + + return diags +} diff --git a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go b/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go new file mode 100644 index 000000000000..96f7f2d2fef9 --- /dev/null +++ b/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go @@ -0,0 +1,377 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/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" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_basic(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyConfigurationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + proxyConfigResourceName := "aws_networkfirewall_proxy_configuration.test" + ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v), + resource.TestCheckResourceAttrPair(resourceName, names.AttrID, proxyConfigResourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(resourceName, "proxy_configuration_arn", proxyConfigResourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "rule_group.0.proxy_rule_group_name", ruleGroup1ResourceName, names.AttrName), + resource.TestCheckResourceAttrSet(resourceName, "update_token"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v networkfirewall.DescribeProxyConfigurationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyConfigurationRuleGroupsAttachment, resourceName, proxyConfigurationRuleGroupsAttachmentDisappearsStateFunc), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateAdd(t *testing.T) { + ctx := acctest.Context(t) + var v1, v2 networkfirewall.DescribeProxyConfigurationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + ruleGroup3ResourceName := "aws_networkfirewall_proxy_rule_group.test3" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroups(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + { + Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_threeRuleGroups(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v2), + resource.TestCheckResourceAttr(resourceName, "rule_group.#", "3"), + resource.TestCheckResourceAttrPair(resourceName, "rule_group.2.proxy_rule_group_name", ruleGroup3ResourceName, names.AttrName), + ), + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateRemove(t *testing.T) { + ctx := acctest.Context(t) + var v1, v2 networkfirewall.DescribeProxyConfigurationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroups(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + { + Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v2), + resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"), + ), + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateReorder(t *testing.T) { + ctx := acctest.Context(t) + var v1, v2 networkfirewall.DescribeProxyConfigurationOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" + ruleGroup2ResourceName := "aws_networkfirewall_proxy_rule_group.test2" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroups(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v1), + resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), + resource.TestCheckResourceAttrPair(resourceName, "rule_group.0.proxy_rule_group_name", ruleGroup1ResourceName, names.AttrName), + resource.TestCheckResourceAttrPair(resourceName, "rule_group.1.proxy_rule_group_name", ruleGroup2ResourceName, names.AttrName), + ), + }, + { + Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroupsReversed(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v2), + resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), + resource.TestCheckResourceAttrPair(resourceName, "rule_group.0.proxy_rule_group_name", ruleGroup2ResourceName, names.AttrName), + resource.TestCheckResourceAttrPair(resourceName, "rule_group.1.proxy_rule_group_name", ruleGroup1ResourceName, names.AttrName), + ), + }, + }, + }) +} + +func testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkfirewall_proxy_configuration_rule_groups_attachment" { + continue + } + + out, err := tfnetworkfirewall.FindProxyConfigurationByARN(ctx, conn, rs.Primary.ID) + + if tfretry.NotFound(err) { + continue + } + + if err != nil { + return err + } + + // Check if there are any rule groups attached + if out != nil && out.ProxyConfiguration != nil && out.ProxyConfiguration.RuleGroups != nil { + if len(out.ProxyConfiguration.RuleGroups) > 0 { + return fmt.Errorf("NetworkFirewall Proxy Configuration Rule Groups Attachment still exists: %s has %d rule groups", rs.Primary.ID, len(out.ProxyConfiguration.RuleGroups)) + } + } + } + + return nil + } +} + +func testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + output, err := tfnetworkfirewall.FindProxyConfigurationByARN(ctx, conn, rs.Primary.Attributes["proxy_configuration_arn"]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func proxyConfigurationRuleGroupsAttachmentDisappearsStateFunc(ctx context.Context, state *tfsdk.State, is *terraform.InstanceState) error { + // Set the id attribute (needed for Delete to find the resource) + if v, ok := is.Attributes[names.AttrID]; ok { + if diags := state.SetAttribute(ctx, path.Root(names.AttrID), types.StringValue(v)); diags.HasError() { + return fmt.Errorf("setting id: %s", diags.Errors()[0].Detail()) + } + } + + // Set the rule_group nested block from the instance state + ruleGroupCount := 0 + if v, ok := is.Attributes["rule_group.#"]; ok { + fmt.Sscanf(v, "%d", &ruleGroupCount) + } + + if ruleGroupCount > 0 { + var ruleGroups []tfnetworkfirewall.RuleGroupAttachmentModel + for i := 0; i < ruleGroupCount; i++ { + key := fmt.Sprintf("rule_group.%d.proxy_rule_group_name", i) + if v, ok := is.Attributes[key]; ok { + ruleGroups = append(ruleGroups, tfnetworkfirewall.RuleGroupAttachmentModel{ + ProxyRuleGroupName: types.StringValue(v), + }) + } + } + + ruleGroupsList := fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, ruleGroups) + if diags := state.SetAttribute(ctx, path.Root("rule_group"), ruleGroupsList); diags.HasError() { + return fmt.Errorf("setting rule_group: %s", diags.Errors()[0].Detail()) + } + } + + return nil +} + +func testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_configuration" "test" { + name = %[1]q + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +resource "aws_networkfirewall_proxy_rule_group" "test1" { + name = "%[1]s-1" +} + +resource "aws_networkfirewall_proxy_rule_group" "test2" { + name = "%[1]s-2" +} + +resource "aws_networkfirewall_proxy_rule_group" "test3" { + name = "%[1]s-3" +} +`, rName) +} + +func testAccProxyConfigurationRuleGroupsAttachmentConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName), + ` +resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" { + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.test1.name + } +} +`) +} + +func testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroups(rName string) string { + return acctest.ConfigCompose( + testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName), + ` +resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" { + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.test1.name + } + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.test2.name + } +} +`) +} + +func testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroupsReversed(rName string) string { + return acctest.ConfigCompose( + testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName), + ` +resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" { + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.test2.name + } + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.test1.name + } +} +`) +} + +func testAccProxyConfigurationRuleGroupsAttachmentConfig_threeRuleGroups(rName string) string { + return acctest.ConfigCompose( + testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName), + ` +resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" { + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.test1.name + } + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.test2.name + } + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.test3.name + } +} +`) +} From 02f09879dcd484fd69f6c5c6b07f76b1ad0f82df Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Wed, 7 Jan 2026 10:35:49 +0800 Subject: [PATCH 10/19] proxy resource work in progress --- .../service/networkfirewall/exports_test.go | 3 + internal/service/networkfirewall/proxy.go | 536 ++++++++++++++++++ .../networkfirewall/proxy_configuration.go | 2 +- ...xy_configuration_rule_groups_attachment.go | 2 +- ...nfiguration_rule_groups_attachment_test.go | 2 +- .../networkfirewall/proxy_rule_group.go | 2 +- .../networkfirewall/proxy_rule_group_test.go | 2 +- .../service/networkfirewall/proxy_rules.go | 2 +- .../networkfirewall/proxy_rules_test.go | 2 +- .../service/networkfirewall/proxy_test.go | 442 +++++++++++++++ .../networkfirewall/service_package_gen.go | 23 + .../r/networkfirewall_proxy.html.markdown | 72 +++ ...firewall_proxy_configuration.html.markdown | 103 ++++ ...ration_rule_group_attachment.html.markdown | 62 ++ ...orkfirewall_proxy_rule_group.html.markdown | 84 +++ .../networkfirewall_proxy_rules.html.markdown | 187 ++++++ 16 files changed, 1519 insertions(+), 7 deletions(-) create mode 100644 internal/service/networkfirewall/proxy.go create mode 100644 internal/service/networkfirewall/proxy_test.go create mode 100644 website/docs/r/networkfirewall_proxy.html.markdown create mode 100644 website/docs/r/networkfirewall_proxy_configuration.html.markdown create mode 100644 website/docs/r/networkfirewall_proxy_configuration_rule_group_attachment.html.markdown create mode 100644 website/docs/r/networkfirewall_proxy_rule_group.html.markdown create mode 100644 website/docs/r/networkfirewall_proxy_rules.html.markdown diff --git a/internal/service/networkfirewall/exports_test.go b/internal/service/networkfirewall/exports_test.go index 8072f0b0abf3..da89b0be4eae 100644 --- a/internal/service/networkfirewall/exports_test.go +++ b/internal/service/networkfirewall/exports_test.go @@ -9,7 +9,9 @@ var ( ResourceFirewallPolicy = resourceFirewallPolicy ResourceFirewallTransitGatewayAttachmentAccepter = newFirewallTransitGatewayAttachmentAccepterResource ResourceLoggingConfiguration = resourceLoggingConfiguration + ResourceProxy = newResourceProxy ResourceProxyConfiguration = newResourceProxyConfiguration + ResourceProxyConfigurationRuleGroupsAttachment = newResourceProxyConfigurationRuleGroupsAttachmentResource ResourceProxyRuleGroup = newResourceProxyRuleGroup ResourceProxyRules = newResourceProxyRules ResourceResourcePolicy = resourceResourcePolicy @@ -20,6 +22,7 @@ var ( FindFirewallByARN = findFirewallByARN FindFirewallPolicyByARN = findFirewallPolicyByARN FindLoggingConfigurationByARN = findLoggingConfigurationByARN + FindProxyByARN = findProxyByARN FindProxyConfigurationByARN = findProxyConfigurationByARN FindProxyRuleGroupByARN = findProxyRuleGroupByARN FindResourcePolicyByARN = findResourcePolicyByARN diff --git a/internal/service/networkfirewall/proxy.go b/internal/service/networkfirewall/proxy.go new file mode 100644 index 000000000000..1104092f2163 --- /dev/null +++ b/internal/service/networkfirewall/proxy.go @@ -0,0 +1,536 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + sdkretry "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "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/retry" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_networkfirewall_proxy", name="Proxy") +// @Tags(identifierAttribute="arn") +// @ArnIdentity(identityDuplicateAttributes="id", hasNoPreExistingResource=true) +func newResourceProxy(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProxy{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +type resourceProxy struct { + framework.ResourceWithModel[resourceProxyModel] + framework.WithTimeouts + framework.WithImportByIdentity +} + +func (r *resourceProxy) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrARN: framework.ARNAttributeComputedOnly(), + "create_time": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root(names.AttrARN)), + "nat_gateway_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "private_dns_name": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "proxy_configuration_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("proxy_configuration_arn"), + path.MatchRoot("proxy_configuration_name"), + ), + }, + }, + "proxy_configuration_name": schema.StringAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf( + path.MatchRoot("proxy_configuration_arn"), + path.MatchRoot("proxy_configuration_name"), + ), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + "update_time": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + }, + "update_token": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "vpc_endpoint_service_name": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "listener_properties": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[listenerPropertiesModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(2), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "port": schema.Int32Attribute{ + Required: true, + }, + names.AttrType: schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ListenerPropertyType](), + Required: true, + }, + }, + }, + }, + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + "tls_intercept_properties": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[tlsInterceptPropertiesModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "pca_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Optional: true, + }, + "tls_intercept_mode": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.TlsInterceptMode](), + Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + }, + }, + } +} + +func (r *resourceProxy) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan resourceProxyModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var input networkfirewall.CreateProxyInput + resp.Diagnostics.Append(flex.Expand(ctx, plan, &input, flex.WithFieldNamePrefix("Proxy"))...) + if resp.Diagnostics.HasError() { + return + } + + input.Tags = getTagsIn(ctx) + + out, err := conn.CreateProxy(ctx, &input) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("creating NetworkFirewall Proxy (%s)", plan.ProxyName.ValueString()), err.Error()) + return + } + if out == nil || out.Proxy == nil { + resp.Diagnostics.AddError(fmt.Sprintf("creating NetworkFirewall Proxy (%s)", plan.ProxyName.ValueString()), errors.New("empty output").Error()) + return + } + + arn := aws.ToString(out.Proxy.ProxyArn) + + createTimeout := r.CreateTimeout(ctx, plan.Timeouts) + describeOut, err := waitProxyCreated(ctx, conn, arn, createTimeout) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("waiting for NetworkFirewall Proxy (%s) create", arn), err.Error()) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, describeOut.Proxy, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + plan.ID = plan.ProxyArn + plan.UpdateToken = flex.StringToFramework(ctx, describeOut.UpdateToken) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceProxy) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findProxyByARN(ctx, conn, state.ID.ValueString()) + if retry.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("reading NetworkFirewall Proxy (%s)", state.ID.ValueString()), err.Error()) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, out.Proxy, &state)...) + if resp.Diagnostics.HasError() { + return + } + + state.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) + + setTagsOut(ctx, out.Proxy.Tags) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceProxy) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan, state resourceProxyModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + diff, d := flex.Diff(ctx, plan, state, flex.WithIgnoredField("Tags"), flex.WithIgnoredField("TagsAll")) + resp.Diagnostics.Append(d...) + if resp.Diagnostics.HasError() { + return + } + + if diff.HasChanges() { + input := networkfirewall.UpdateProxyInput{ + ProxyArn: state.ProxyArn.ValueStringPointer(), + NatGatewayId: state.NatGatewayId.ValueStringPointer(), + UpdateToken: state.UpdateToken.ValueStringPointer(), + } + + // Handle TlsInterceptProperties + resp.Diagnostics.Append(flex.Expand(ctx, plan.TlsInterceptProperties, &input.TlsInterceptProperties)...) + if resp.Diagnostics.HasError() { + return + } + + // Handle ListenerProperties changes - determine what to add/remove + var planListeners, stateListeners []listenerPropertiesModel + resp.Diagnostics.Append(plan.ListenerProperties.ElementsAs(ctx, &planListeners, false)...) + resp.Diagnostics.Append(state.ListenerProperties.ElementsAs(ctx, &stateListeners, false)...) + if resp.Diagnostics.HasError() { + return + } + + // Build maps for comparison + stateListenerMap := make(map[string]listenerPropertiesModel) + for _, l := range stateListeners { + key := fmt.Sprintf("%d-%s", l.Port.ValueInt32(), l.Type.ValueString()) + stateListenerMap[key] = l + } + + planListenerMap := make(map[string]listenerPropertiesModel) + for _, l := range planListeners { + key := fmt.Sprintf("%d-%s", l.Port.ValueInt32(), l.Type.ValueString()) + planListenerMap[key] = l + } + + // Find listeners to add (in plan but not in state) + for key, l := range planListenerMap { + if _, exists := stateListenerMap[key]; !exists { + input.ListenerPropertiesToAdd = append(input.ListenerPropertiesToAdd, awstypes.ListenerPropertyRequest{ + Port: l.Port.ValueInt32Pointer(), + Type: awstypes.ListenerPropertyType(l.Type.ValueString()), + }) + } + } + + // Find listeners to remove (in state but not in plan) + for key, l := range stateListenerMap { + if _, exists := planListenerMap[key]; !exists { + input.ListenerPropertiesToRemove = append(input.ListenerPropertiesToRemove, awstypes.ListenerPropertyRequest{ + Port: l.Port.ValueInt32Pointer(), + Type: awstypes.ListenerPropertyType(l.Type.ValueString()), + }) + } + } + + out, err := conn.UpdateProxy(ctx, &input) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("updating NetworkFirewall Proxy (%s)", state.ID.ValueString()), err.Error()) + return + } + if out == nil || out.Proxy == nil { + resp.Diagnostics.AddError(fmt.Sprintf("updating NetworkFirewall Proxy (%s)", state.ID.ValueString()), errors.New("empty output").Error()) + return + } + + // Wait for modification to complete + updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) + describeOut, err := waitProxyUpdated(ctx, conn, state.ID.ValueString(), updateTimeout) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("waiting for NetworkFirewall Proxy (%s) update", state.ID.ValueString()), err.Error()) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, describeOut.Proxy, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + plan.UpdateToken = flex.StringToFramework(ctx, describeOut.UpdateToken) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceProxy) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + input := networkfirewall.DeleteProxyInput{ + ProxyArn: state.ProxyArn.ValueStringPointer(), + NatGatewayId: state.NatGatewayId.ValueStringPointer(), + } + + _, err := conn.DeleteProxy(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + resp.Diagnostics.AddError(fmt.Sprintf("deleting NetworkFirewall Proxy (%s)", state.ID.ValueString()), err.Error()) + return + } + + deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) + _, err = waitProxyDeleted(ctx, conn, state.ID.ValueString(), deleteTimeout) + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("waiting for NetworkFirewall Proxy (%s) delete", state.ID.ValueString()), err.Error()) + return + } +} + +func findProxyByARN(ctx context.Context, conn *networkfirewall.Client, arn string) (*networkfirewall.DescribeProxyOutput, error) { + input := networkfirewall.DescribeProxyInput{ + ProxyArn: aws.String(arn), + } + + out, err := conn.DescribeProxy(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &sdkretry.NotFoundError{ + LastError: err, + LastRequest: &input, + } + } + + return nil, err + } + + if out == nil || out.Proxy == nil { + return nil, tfresource.NewEmptyResultError(&input) + } + + if out.Proxy.DeleteTime != nil { + return nil, &sdkretry.NotFoundError{ + Message: "resource is deleted", + } + } + + return out, nil +} + +func statusProxy(ctx context.Context, conn *networkfirewall.Client, arn string) sdkretry.StateRefreshFunc { + return func() (any, string, error) { + out, err := findProxyByARN(ctx, conn, arn) + if retry.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.Proxy.ProxyState), nil + } +} + +func statusProxyModify(ctx context.Context, conn *networkfirewall.Client, arn string) sdkretry.StateRefreshFunc { + return func() (any, string, error) { + out, err := findProxyByARN(ctx, conn, arn) + if retry.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.Proxy.ProxyModifyState), nil + } +} + +func waitProxyCreated(ctx context.Context, conn *networkfirewall.Client, arn string, timeout time.Duration) (*networkfirewall.DescribeProxyOutput, error) { + stateConf := &sdkretry.StateChangeConf{ + Pending: enum.Slice(awstypes.ProxyStateAttaching), + Target: enum.Slice(awstypes.ProxyStateAttached), + Refresh: statusProxy(ctx, conn, arn), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*networkfirewall.DescribeProxyOutput); ok { + return out, err + } + + return nil, err +} + +func waitProxyUpdated(ctx context.Context, conn *networkfirewall.Client, arn string, timeout time.Duration) (*networkfirewall.DescribeProxyOutput, error) { + stateConf := &sdkretry.StateChangeConf{ + Pending: enum.Slice(awstypes.ProxyModifyStateModifying), + Target: enum.Slice(awstypes.ProxyModifyStateCompleted), + Refresh: statusProxyModify(ctx, conn, arn), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*networkfirewall.DescribeProxyOutput); ok { + return out, err + } + + return nil, err +} + +func waitProxyDeleted(ctx context.Context, conn *networkfirewall.Client, arn string, timeout time.Duration) (*networkfirewall.DescribeProxyOutput, error) { + stateConf := &sdkretry.StateChangeConf{ + Pending: enum.Slice(awstypes.ProxyStateAttached, awstypes.ProxyStateDetaching), + Target: []string{}, + Refresh: statusProxy(ctx, conn, arn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*networkfirewall.DescribeProxyOutput); ok { + return out, err + } + + return nil, err +} + +type resourceProxyModel struct { + framework.WithRegionModel + CreateTime timetypes.RFC3339 `tfsdk:"create_time"` + ID types.String `tfsdk:"id"` + ListenerProperties fwtypes.ListNestedObjectValueOf[listenerPropertiesModel] `tfsdk:"listener_properties"` + NatGatewayId types.String `tfsdk:"nat_gateway_id"` + PrivateDNSName types.String `tfsdk:"private_dns_name"` + ProxyArn types.String `tfsdk:"arn"` + ProxyConfigurationArn fwtypes.ARN `tfsdk:"proxy_configuration_arn"` + ProxyConfigurationName types.String `tfsdk:"proxy_configuration_name"` + ProxyName types.String `tfsdk:"name"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` + Timeouts timeouts.Value `tfsdk:"timeouts"` + TlsInterceptProperties fwtypes.ListNestedObjectValueOf[tlsInterceptPropertiesModel] `tfsdk:"tls_intercept_properties"` + UpdateTime timetypes.RFC3339 `tfsdk:"update_time"` + UpdateToken types.String `tfsdk:"update_token"` + VpcEndpointServiceName types.String `tfsdk:"vpc_endpoint_service_name"` +} + +type listenerPropertiesModel struct { + Port types.Int32 `tfsdk:"port"` + Type fwtypes.StringEnum[awstypes.ListenerPropertyType] `tfsdk:"type"` +} + +type tlsInterceptPropertiesModel struct { + PcaArn fwtypes.ARN `tfsdk:"pca_arn"` + TlsInterceptMode fwtypes.StringEnum[awstypes.TlsInterceptMode] `tfsdk:"tls_intercept_mode"` +} diff --git a/internal/service/networkfirewall/proxy_configuration.go b/internal/service/networkfirewall/proxy_configuration.go index fb8068c60587..2600dd76f137 100644 --- a/internal/service/networkfirewall/proxy_configuration.go +++ b/internal/service/networkfirewall/proxy_configuration.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package networkfirewall diff --git a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go b/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go index f58af41e5673..44c01fe41800 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package networkfirewall diff --git a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go b/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go index 96f7f2d2fef9..cd3364d65309 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package networkfirewall_test diff --git a/internal/service/networkfirewall/proxy_rule_group.go b/internal/service/networkfirewall/proxy_rule_group.go index 8fd28f8dcbdf..b32a40aed9d3 100644 --- a/internal/service/networkfirewall/proxy_rule_group.go +++ b/internal/service/networkfirewall/proxy_rule_group.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package networkfirewall diff --git a/internal/service/networkfirewall/proxy_rule_group_test.go b/internal/service/networkfirewall/proxy_rule_group_test.go index c697c27af594..e77cc512c61d 100644 --- a/internal/service/networkfirewall/proxy_rule_group_test.go +++ b/internal/service/networkfirewall/proxy_rule_group_test.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package networkfirewall_test diff --git a/internal/service/networkfirewall/proxy_rules.go b/internal/service/networkfirewall/proxy_rules.go index bfc79d21589f..2de1e1960b93 100644 --- a/internal/service/networkfirewall/proxy_rules.go +++ b/internal/service/networkfirewall/proxy_rules.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package networkfirewall diff --git a/internal/service/networkfirewall/proxy_rules_test.go b/internal/service/networkfirewall/proxy_rules_test.go index ebf3702aeb38..e7eb6ebaca11 100644 --- a/internal/service/networkfirewall/proxy_rules_test.go +++ b/internal/service/networkfirewall/proxy_rules_test.go @@ -1,4 +1,4 @@ -// Copyright (c) HashiCorp, Inc. +// Copyright IBM Corp. 2014, 2025 // SPDX-License-Identifier: MPL-2.0 package networkfirewall_test diff --git a/internal/service/networkfirewall/proxy_test.go b/internal/service/networkfirewall/proxy_test.go new file mode 100644 index 000000000000..09577a92f866 --- /dev/null +++ b/internal/service/networkfirewall/proxy_test.go @@ -0,0 +1,442 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "context" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "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/retry" + tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxy_basic(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v networkfirewall.DescribeProxyOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "network-firewall", regexache.MustCompile(`proxy/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttrSet(resourceName, "nat_gateway_id"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.#", "2"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.0.type", "HTTP"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.1.port", "443"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.1.type", "HTTPS"), + resource.TestCheckResourceAttr(resourceName, "tls_intercept_properties.0.tls_intercept_mode", "DISABLED"), + resource.TestCheckResourceAttrSet(resourceName, "create_time"), + resource.TestCheckResourceAttrSet(resourceName, "update_token"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + }, + }) +} + +func TestAccNetworkFirewallProxy_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v networkfirewall.DescribeProxyOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckProxyExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxy, resourceName), + ), + ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxy_tlsInterceptEnabled(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var v networkfirewall.DescribeProxyOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkfirewall_proxy.test" + pcaResourceName := "aws_acmpca_certificate_authority.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfig_tlsInterceptEnabled(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyExists(ctx, resourceName, &v), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "network-firewall", regexache.MustCompile(`proxy/.+$`)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttrSet(resourceName, "nat_gateway_id"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.#", "2"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.0.port", "8080"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.0.type", "HTTP"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.1.port", "443"), + resource.TestCheckResourceAttr(resourceName, "listener_properties.1.type", "HTTPS"), + resource.TestCheckResourceAttr(resourceName, "tls_intercept_properties.#", "1"), + resource.TestCheckResourceAttr(resourceName, "tls_intercept_properties.0.tls_intercept_mode", "ENABLED"), + resource.TestCheckResourceAttrPair(resourceName, "tls_intercept_properties.0.pca_arn", pcaResourceName, names.AttrARN), + resource.TestCheckResourceAttrSet(resourceName, "create_time"), + resource.TestCheckResourceAttrSet(resourceName, "update_token"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"update_token"}, + }, + }, + }) +} + +func testAccCheckProxyDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkfirewall_proxy" { + continue + } + + out, err := tfnetworkfirewall.FindProxyByARN(ctx, conn, rs.Primary.ID) + + if retry.NotFound(err) { + continue + } + + if out != nil && out.Proxy != nil && out.Proxy.DeleteTime != nil { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("NetworkFirewall Proxy %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckProxyExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + + output, err := tfnetworkfirewall.FindProxyByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +// testAccProxyConfig_baseVPC creates a reusable VPC configuration for proxy tests. +// It includes: +// - VPC with CIDR 10.0.0.0/16 +// - Public subnet (10.0.1.0/24) with Internet Gateway +// - Private subnet (10.0.2.0/24) +// - Internet Gateway +// - NAT Gateway in the public subnet +// - Route tables for public and private subnets +func testAccProxyConfig_baseVPC(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "public" { + vpc_id = aws_vpc.test.id + cidr_block = "10.0.1.0/24" + availability_zone = data.aws_availability_zones.available.names[0] + map_public_ip_on_launch = true + + tags = { + Name = "%[1]s-public" + } +} + +resource "aws_subnet" "private" { + vpc_id = aws_vpc.test.id + cidr_block = "10.0.2.0/24" + availability_zone = data.aws_availability_zones.available.names[0] + + tags = { + Name = "%[1]s-private" + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_eip" "test" { + domain = "vpc" + + tags = { + Name = %[1]q + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_nat_gateway" "test" { + allocation_id = aws_eip.test.id + subnet_id = aws_subnet.public.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_route_table" "public" { + vpc_id = aws_vpc.test.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.test.id + } + + tags = { + Name = "%[1]s-public" + } +} + +resource "aws_route_table" "private" { + vpc_id = aws_vpc.test.id + + route { + cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.test.id + } + + tags = { + Name = "%[1]s-private" + } +} + +resource "aws_route_table_association" "public" { + subnet_id = aws_subnet.public.id + route_table_id = aws_route_table.public.id +} + +resource "aws_route_table_association" "private" { + subnet_id = aws_subnet.private.id + route_table_id = aws_route_table.private.id +} +`, rName)) +} + +// testAccProxyConfig_baseProxyConfiguration creates a basic proxy configuration +// that can be reused across proxy tests. +func testAccProxyConfig_baseProxyConfiguration(rName string) string { + return fmt.Sprintf(` +resource "aws_networkfirewall_proxy_configuration" "test" { + name = %[1]q + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} +`, rName) +} + +func testAccProxyConfig_basic(rName string) string { + return acctest.ConfigCompose( + testAccProxyConfig_baseVPC(rName), + testAccProxyConfig_baseProxyConfiguration(rName), + fmt.Sprintf(` +resource "aws_networkfirewall_proxy" "test" { + name = %[1]q + nat_gateway_id = aws_nat_gateway.test.id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + tls_intercept_properties { + tls_intercept_mode = "DISABLED" + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } +} +`, rName)) +} + +func testAccProxyConfig_tlsInterceptEnabled(rName string) string { + return acctest.ConfigCompose( + testAccProxyConfig_baseVPC(rName), + testAccProxyConfig_baseProxyConfiguration(rName), + fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +data "aws_partition" "current" {} + +data "aws_region" "current" {} + +# Create a subordinate CA for TLS interception +resource "aws_acmpca_certificate_authority" "test" { + type = "SUBORDINATE" + + certificate_authority_configuration { + key_algorithm = "RSA_2048" + signing_algorithm = "SHA256WITHRSA" + + subject { + common_name = "%[1]s.example.com" + } + } + + permanent_deletion_time_in_days = 7 + + tags = { + Name = %[1]q + } +} + +# Create the CA certificate +resource "aws_acmpca_certificate" "test" { + certificate_authority_arn = aws_acmpca_certificate_authority.test.arn + certificate_signing_request = aws_acmpca_certificate_authority.test.certificate_signing_request + signing_algorithm = "SHA256WITHRSA" + + template_arn = "arn:${data.aws_partition.current.partition}:acm-pca:::template/SubordinateCACertificate_PathLen0/V1" + + validity { + type = "YEARS" + value = 1 + } +} + +# Import the signed certificate back into the CA +resource "aws_acmpca_certificate_authority_certificate" "test" { + certificate_authority_arn = aws_acmpca_certificate_authority.test.arn + certificate = aws_acmpca_certificate.test.certificate + certificate_chain = aws_acmpca_certificate.test.certificate_chain +} + +# Grant Network Firewall proxy permission to use the PCA +resource "aws_acmpca_permission" "test" { + certificate_authority_arn = aws_acmpca_certificate_authority.test.arn + principal = "proxy.network-firewall.amazonaws.com" + + actions = [ + "GetCertificate", + "DescribeCertificateAuthority", + "GetCertificateAuthorityCertificate", + "ListTags", + "ListPermissions", + "IssueCertificate", + ] + + source_arn = aws_networkfirewall_proxy.test.arn +} + +resource "aws_networkfirewall_proxy" "test" { + name = %[1]q + nat_gateway_id = aws_nat_gateway.test.id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + tls_intercept_properties { + tls_intercept_mode = "ENABLED" + pca_arn = aws_acmpca_certificate_authority.test.arn + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } + + depends_on = [aws_acmpca_certificate_authority_certificate.test] +} +`, rName)) +} diff --git a/internal/service/networkfirewall/service_package_gen.go b/internal/service/networkfirewall/service_package_gen.go index 26af315313a9..4eaea59cb65e 100644 --- a/internal/service/networkfirewall/service_package_gen.go +++ b/internal/service/networkfirewall/service_package_gen.go @@ -32,6 +32,19 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser Name: "Firewall Transit Gateway Attachment Accepter", Region: unique.Make(inttypes.ResourceRegionDefault()), }, + { + Factory: newResourceProxy, + TypeName: "aws_networkfirewall_proxy", + Name: "Proxy", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }), + Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalARNIdentity(inttypes.WithIdentityDuplicateAttrs(names.AttrID)), + Import: inttypes.FrameworkImport{ + WrappedImport: true, + }, + }, { Factory: newResourceProxyConfiguration, TypeName: "aws_networkfirewall_proxy_configuration", @@ -45,6 +58,16 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser WrappedImport: true, }, }, + { + Factory: newResourceProxyConfigurationRuleGroupsAttachmentResource, + TypeName: "aws_networkfirewall_proxy_configuration_rule_groups_attachment", + Name: "Proxy Configuration Rule Groups Attachment", + Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalARNIdentityNamed("proxy_configuration_arn", inttypes.WithIdentityDuplicateAttrs(names.AttrID)), + Import: inttypes.FrameworkImport{ + WrappedImport: true, + }, + }, { Factory: newResourceProxyRuleGroup, TypeName: "aws_networkfirewall_proxy_rule_group", diff --git a/website/docs/r/networkfirewall_proxy.html.markdown b/website/docs/r/networkfirewall_proxy.html.markdown new file mode 100644 index 000000000000..1fef61ed1cca --- /dev/null +++ b/website/docs/r/networkfirewall_proxy.html.markdown @@ -0,0 +1,72 @@ +--- +subcategory: "Network Firewall" +layout: "aws" +page_title: "AWS: aws_networkfirewall_proxy" +description: |- + Manages an AWS Network Firewall Proxy. +--- + + +# Resource: aws_networkfirewall_proxy + +Manages an AWS Network Firewall Proxy. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_networkfirewall_proxy" "example" { +} +``` + +## Argument Reference + +The following arguments are required: + +* `example_arg` - (Required) Brief description of the required argument. + +The following arguments are optional: + +* `optional_arg` - (Optional) Brief description of the optional argument. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Proxy. +* `example_attribute` - Brief description of the attribute. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `60m`) +* `update` - (Default `180m`) +* `delete` - (Default `90m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy using the `example_id_arg`. For example: + +```terraform +import { + to = aws_networkfirewall_proxy.example + id = "proxy-id-12345678" +} +``` + +Using `terraform import`, import Network Firewall Proxy using the `example_id_arg`. For example: + +```console +% terraform import aws_networkfirewall_proxy.example proxy-id-12345678 +``` diff --git a/website/docs/r/networkfirewall_proxy_configuration.html.markdown b/website/docs/r/networkfirewall_proxy_configuration.html.markdown new file mode 100644 index 000000000000..611303a9c9c9 --- /dev/null +++ b/website/docs/r/networkfirewall_proxy_configuration.html.markdown @@ -0,0 +1,103 @@ +--- +subcategory: "Network Firewall" +layout: "aws" +page_title: "AWS: aws_networkfirewall_proxy_configuration" +description: |- + Manages an AWS Network Firewall Proxy Configuration. +--- + + +# Resource: aws_networkfirewall_proxy_configuration + +Manages an AWS Network Firewall Proxy Configuration. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_networkfirewall_proxy_configuration" "example" { + name = "example" + + default_rule_phase_actions { + pre_dns = "ALLOW" + pre_request = "ALLOW" + post_response = "ALLOW" + } +} +``` + +### With Description and Tags + +```terraform +resource "aws_networkfirewall_proxy_configuration" "example" { + name = "example" + description = "Example proxy configuration" + + default_rule_phase_actions { + pre_dns = "DROP" + pre_request = "ALLOW" + post_response = "ALLOW" + } + + tags = { + Name = "example" + Environment = "production" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `default_rule_phase_actions` - (Required) Default actions to take on proxy traffic. See [Default Rule Phase Actions](#default-rule-phase-actions) below. +* `name` - (Required) Descriptive name of the proxy configuration. + +The following arguments are optional: + +* `description` - (Optional) Description of the proxy configuration. +* `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +### Default Rule Phase Actions + +The `default_rule_phase_actions` block supports the following: + +* `post_response` - (Required) Default action for the POST_RESPONSE phase. Valid values: `ALLOW`, `DROP`. +* `pre_dns` - (Required) Default action for the PRE_DNS phase. Valid values: `ALLOW`, `DROP`. +* `pre_request` - (Required) Default action for the PRE_REQUEST phase. Valid values: `ALLOW`, `DROP`. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Proxy Configuration. +* `id` - ARN of the Proxy Configuration. +* `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). +* `update_token` - Token used for optimistic locking. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Configuration using the `arn`. For example: + +```terraform +import { + to = aws_networkfirewall_proxy_configuration.example + id = "arn:aws:network-firewall:us-west-2:123456789012:proxy-configuration/example" +} +``` + +Using `terraform import`, import Network Firewall Proxy Configuration using the `arn`. For example: + +```console +% terraform import aws_networkfirewall_proxy_configuration.example arn:aws:network-firewall:us-west-2:123456789012:proxy-configuration/example +``` diff --git a/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachment.html.markdown b/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachment.html.markdown new file mode 100644 index 000000000000..50cb6dd5bccf --- /dev/null +++ b/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachment.html.markdown @@ -0,0 +1,62 @@ +--- +subcategory: "Network Firewall" +layout: "aws" +page_title: "AWS: aws_networkfirewall_proxy_configuration_rule_group_attachment" +description: |- + Manages an AWS Network Firewall Proxy Configuration Rule Group Attachment. +--- + +# Resource: aws_networkfirewall_proxy_configuration_rule_group_attachment + +Manages an AWS Network Firewall Proxy Configuration Rule Group Attachment. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_networkfirewall_proxy_configuration_rule_group_attachment" "example" { +} +``` + +## Argument Reference + +The following arguments are required: + +* `example_arg` - (Required) Brief description of the required argument. + +The following arguments are optional: + +* `optional_arg` - (Optional) Brief description of the optional argument. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Proxy Configuration Rule Group Attachment. +* `example_attribute` - Brief description of the attribute. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `60m`) +* `update` - (Default `180m`) +* `delete` - (Default `90m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Configuration Rule Group Attachment using the `example_id_arg`. For example: + +```terraform +import { + to = aws_networkfirewall_proxy_configuration_rule_group_attachment.example + id = "proxy_configuration_rule_group_attachment-id-12345678" +} +``` + +Using `terraform import`, import Network Firewall Proxy Configuration Rule Group Attachment using the `example_id_arg`. For example: + +```console +% terraform import aws_networkfirewall_proxy_configuration_rule_group_attachment.example proxy_configuration_rule_group_attachment-id-12345678 +``` diff --git a/website/docs/r/networkfirewall_proxy_rule_group.html.markdown b/website/docs/r/networkfirewall_proxy_rule_group.html.markdown new file mode 100644 index 000000000000..4950ece85b1a --- /dev/null +++ b/website/docs/r/networkfirewall_proxy_rule_group.html.markdown @@ -0,0 +1,84 @@ +--- +subcategory: "Network Firewall" +layout: "aws" +page_title: "AWS: aws_networkfirewall_proxy_rule_group" +description: |- + Manages an AWS Network Firewall Proxy Rule Group. +--- + + +# Resource: aws_networkfirewall_proxy_rule_group + +Manages an AWS Network Firewall Proxy Rule Group. A proxy rule group is a container for proxy rules that can be referenced by a proxy configuration. + +~> **NOTE:** This resource creates an empty proxy rule group. Use the [`aws_networkfirewall_proxy_rules`](networkfirewall_proxy_rules.html) resource to add rules to the group. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_networkfirewall_proxy_rule_group" "example" { + name = "example" +} +``` + +### With Description and Tags + +```terraform +resource "aws_networkfirewall_proxy_rule_group" "example" { + name = "example" + description = "Example proxy rule group for HTTP traffic" + + tags = { + Name = "example" + Environment = "production" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Descriptive name of the proxy rule group. + +The following arguments are optional: + +* `description` - (Optional) Description of the proxy rule group. +* `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Proxy Rule Group. +* `id` - ARN of the Proxy Rule Group. +* `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). +* `update_token` - Token used for optimistic locking. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Rule Group using the `arn`. For example: + +```terraform +import { + to = aws_networkfirewall_proxy_rule_group.example + id = "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example" +} +``` + +Using `terraform import`, import Network Firewall Proxy Rule Group using the `arn`. For example: + +```console +% terraform import aws_networkfirewall_proxy_rule_group.example arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example +``` diff --git a/website/docs/r/networkfirewall_proxy_rules.html.markdown b/website/docs/r/networkfirewall_proxy_rules.html.markdown new file mode 100644 index 000000000000..fc8bfd7b4bb0 --- /dev/null +++ b/website/docs/r/networkfirewall_proxy_rules.html.markdown @@ -0,0 +1,187 @@ +--- +subcategory: "Network Firewall" +layout: "aws" +page_title: "AWS: aws_networkfirewall_proxy_rules" +description: |- + Manages AWS Network Firewall Proxy Rules within a Proxy Rule Group. +--- + + +# Resource: aws_networkfirewall_proxy_rules + +Manages AWS Network Firewall Proxy Rules within a Proxy Rule Group. Proxy rules define conditions and actions for HTTP/HTTPS traffic inspection across three request/response phases: PRE_DNS, PRE_REQUEST, and POST_RESPONSE. + +~> **NOTE:** This resource requires an existing [`aws_networkfirewall_proxy_rule_group`](networkfirewall_proxy_rule_group.html). + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_networkfirewall_proxy_rule_group" "example" { + name = "example" +} + +resource "aws_networkfirewall_proxy_rules" "example" { + proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.example.arn + + pre_dns { + proxy_rule_name = "allow-example-com" + action = "ALLOW" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["example.com"] + } + } +} +``` + +### Multiple Rules Across Phases + +```terraform +resource "aws_networkfirewall_proxy_rule_group" "example" { + name = "example" +} + +resource "aws_networkfirewall_proxy_rules" "example" { + proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.example.arn + + # DNS phase rules + pre_dns { + proxy_rule_name = "block-malicious-domains" + action = "DROP" + description = "Block known malicious domains" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["malicious.com", "badactor.net"] + } + } + + # Request phase rules + pre_request { + proxy_rule_name = "allow-api-requests" + action = "ALLOW" + description = "Allow API endpoint access" + + conditions { + condition_key = "request:Http:Uri" + condition_operator = "StringEquals" + condition_values = ["/api/v1", "/api/v2"] + } + + conditions { + condition_key = "request:Http:Method" + condition_operator = "StringEquals" + condition_values = ["GET", "POST"] + } + } + + # Response phase rules + post_response { + proxy_rule_name = "block-large-responses" + action = "DROP" + description = "Block responses with status code >= 500" + + conditions { + condition_key = "response:Http:StatusCode" + condition_operator = "NumericGreaterThanEquals" + condition_values = ["500"] + } + } +} +``` + +### Using Proxy Rule Group Name + +```terraform +resource "aws_networkfirewall_proxy_rule_group" "example" { + name = "example" +} + +resource "aws_networkfirewall_proxy_rules" "example" { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.example.name + + pre_dns { + proxy_rule_name = "allow-corporate-domains" + action = "ALLOW" + + conditions { + condition_key = "request:DestinationDomain" + condition_operator = "StringEquals" + condition_values = ["example.com", "example.org"] + } + } +} +``` + +## Argument Reference + +The following arguments are optional: + +* `post_response` - (Optional) Rules to apply during the POST_RESPONSE phase. See [Rule Configuration](#rule-configuration) below. +* `pre_dns` - (Optional) Rules to apply during the PRE_DNS phase. See [Rule Configuration](#rule-configuration) below. +* `pre_request` - (Optional) Rules to apply during the PRE_REQUEST phase. See [Rule Configuration](#rule-configuration) below. +* `proxy_rule_group_arn` - (Optional) ARN of the proxy rule group. Conflicts with `proxy_rule_group_name`. Required if `proxy_rule_group_name` is not specified. +* `proxy_rule_group_name` - (Optional) Name of the proxy rule group. Conflicts with `proxy_rule_group_arn`. Required if `proxy_rule_group_arn` is not specified. + +### Rule Configuration + +Each rule block (`post_response`, `pre_dns`, `pre_request`) supports the following: + +* `action` - (Required) Action to take when conditions match. Valid values: `ALLOW`, `DROP`. +* `conditions` - (Required) One or more condition blocks. See [Conditions](#conditions) below. +* `proxy_rule_name` - (Required) Unique name for the proxy rule within the rule group. +* `description` - (Optional) Description of the rule. +* `insert_position` - (Optional) Position to insert the rule. Rules are evaluated in order. + +### Conditions + +Each `conditions` block supports the following: + +* `condition_key` - (Required) Attribute to evaluate. Valid values include: + - Request-based: `request:SourceAccount`, `request:SourceVpc`, `request:SourceVpce`, `request:Time`, `request:SourceIp`, `request:DestinationIp`, `request:SourcePort`, `request:DestinationPort`, `request:Protocol`, `request:DestinationDomain`, `request:Http:Uri`, `request:Http:Method`, `request:Http:UserAgent`, `request:Http:ContentType`, `request:Http:Header/` + - Response-based: `response:Http:StatusCode`, `response:Http:ContentType`, `response:Http:Header/` + + ~> **NOTE:** HTTP field matching for HTTPS requests requires TLS decryption to be enabled. Without TLS decryption, only IP-based filtering is available in the pre-request phase. +* `condition_operator` - (Required) Comparison operator. Valid values: `StringEquals`, `NumericGreaterThan`, `NumericGreaterThanEquals`. +* `condition_values` - (Required) List of values to compare against. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - ARN of the Proxy Rule Group. +* `proxy_rule_group_arn` - ARN of the Proxy Rule Group (computed if `proxy_rule_group_name` was provided). +* `proxy_rule_group_name` - Name of the Proxy Rule Group (computed if `proxy_rule_group_arn` was provided). + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Rules using the `proxy_rule_group_arn,rule_name1[,rule_name2,...]`. For example: + +```terraform +import { + to = aws_networkfirewall_proxy_rules.example + id = "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example,allow-example-com,block-malicious-domains" +} +``` + +Using `terraform import`, import Network Firewall Proxy Rules using the `proxy_rule_group_arn,rule_name1[,rule_name2,...]`. For example: + +```console +% terraform import aws_networkfirewall_proxy_rules.example "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example,allow-example-com,block-malicious-domains" +``` + +~> **NOTE:** When importing, specify the proxy rule group ARN followed by a comma-separated list of rule names to import. The import will fetch only the specified rules from the rule group. From 1cf3299a10174ed83fcc5f126feaecf8f990d26b Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Thu, 8 Jan 2026 22:29:24 +0800 Subject: [PATCH 11/19] proxy TLS testing needs RAM resources to support service principal --- .../service/networkfirewall/proxy_rules.go | 3 +- .../service/networkfirewall/proxy_test.go | 104 ++++++++++++------ 2 files changed, 73 insertions(+), 34 deletions(-) diff --git a/internal/service/networkfirewall/proxy_rules.go b/internal/service/networkfirewall/proxy_rules.go index 2de1e1960b93..8c8393ec1ad2 100644 --- a/internal/service/networkfirewall/proxy_rules.go +++ b/internal/service/networkfirewall/proxy_rules.go @@ -12,6 +12,7 @@ import ( awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" + "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" @@ -49,7 +50,7 @@ type resourceProxyRules struct { func (r *resourceProxyRules) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - names.AttrID: framework.IDAttribute(), + names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root("proxy_rule_group_arn")), "proxy_rule_group_arn": schema.StringAttribute{ CustomType: fwtypes.ARNType, Optional: true, diff --git a/internal/service/networkfirewall/proxy_test.go b/internal/service/networkfirewall/proxy_test.go index 09577a92f866..f14bd5ee3d45 100644 --- a/internal/service/networkfirewall/proxy_test.go +++ b/internal/service/networkfirewall/proxy_test.go @@ -358,16 +358,16 @@ data "aws_partition" "current" {} data "aws_region" "current" {} -# Create a subordinate CA for TLS interception +# Create a root CA for TLS interception resource "aws_acmpca_certificate_authority" "test" { - type = "SUBORDINATE" + type = "ROOT" certificate_authority_configuration { key_algorithm = "RSA_2048" signing_algorithm = "SHA256WITHRSA" subject { - common_name = "%[1]s.example.com" + common_name = "%[1]s Terraform Test CA" } } @@ -378,42 +378,76 @@ resource "aws_acmpca_certificate_authority" "test" { } } -# Create the CA certificate -resource "aws_acmpca_certificate" "test" { - certificate_authority_arn = aws_acmpca_certificate_authority.test.arn - certificate_signing_request = aws_acmpca_certificate_authority.test.certificate_signing_request - signing_algorithm = "SHA256WITHRSA" +# Grant Network Firewall proxy permission to use the PCA +resource "aws_acmpca_policy" "test" { + resource_arn = aws_acmpca_certificate_authority.test.arn + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "NetworkFirewallProxyReadAccess" + Effect = "Allow" + Principal = { + Service = "proxy.network-firewall.amazonaws.com" + } + Action = [ + "acm-pca:GetCertificate", + "acm-pca:DescribeCertificateAuthority", + "acm-pca:GetCertificateAuthorityCertificate", + "acm-pca:ListTags", + "acm-pca:ListPermissions", + ] + Resource = aws_acmpca_certificate_authority.test.arn + Condition = { + ArnEquals = { + "aws:SourceArn" = aws_networkfirewall_proxy.test.arn + } + } + }, + { + Sid = "NetworkFirewallProxyIssueCertificate" + Effect = "Allow" + Principal = { + Service = "proxy.network-firewall.amazonaws.com" + } + Action = [ + "acm-pca:IssueCertificate", + ] + Resource = aws_acmpca_certificate_authority.test.arn + Condition = { + StringEquals = { + "acm-pca:TemplateArn" = "arn:${data.aws_partition.current.partition}:acm-pca:::template/SubordinateCACertificate_PathLen0/V1" + } + ArnEquals = { + "aws:SourceArn" = aws_networkfirewall_proxy.test.arn + } + } + } + ] + }) +} - template_arn = "arn:${data.aws_partition.current.partition}:acm-pca:::template/SubordinateCACertificate_PathLen0/V1" +# Create RAM resource share for the PCA +resource "aws_ram_resource_share" "test" { + name = %[1]q + allow_external_principals = false - validity { - type = "YEARS" - value = 1 + tags = { + Name = %[1]q } } -# Import the signed certificate back into the CA -resource "aws_acmpca_certificate_authority_certificate" "test" { - certificate_authority_arn = aws_acmpca_certificate_authority.test.arn - certificate = aws_acmpca_certificate.test.certificate - certificate_chain = aws_acmpca_certificate.test.certificate_chain +# Associate the PCA with the RAM share +resource "aws_ram_resource_association" "test" { + resource_arn = aws_acmpca_certificate_authority.test.arn + resource_share_arn = aws_ram_resource_share.test.arn } -# Grant Network Firewall proxy permission to use the PCA -resource "aws_acmpca_permission" "test" { - certificate_authority_arn = aws_acmpca_certificate_authority.test.arn - principal = "proxy.network-firewall.amazonaws.com" - - actions = [ - "GetCertificate", - "DescribeCertificateAuthority", - "GetCertificateAuthorityCertificate", - "ListTags", - "ListPermissions", - "IssueCertificate", - ] - - source_arn = aws_networkfirewall_proxy.test.arn +# Share the PCA with Network Firewall proxy service +resource "aws_ram_principal_association" "test" { + principal = "proxy.network-firewall.amazonaws.com" + resource_share_arn = aws_ram_resource_share.test.arn } resource "aws_networkfirewall_proxy" "test" { @@ -436,7 +470,11 @@ resource "aws_networkfirewall_proxy" "test" { type = "HTTPS" } - depends_on = [aws_acmpca_certificate_authority_certificate.test] + depends_on = [ + aws_acmpca_certificate_authority.test, + aws_ram_resource_association.test, + aws_ram_principal_association.test, + ] } `, rName)) } From 46216951b8d64b5f0543645dc36144142f96ac1d Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Fri, 23 Jan 2026 21:10:42 +1100 Subject: [PATCH 12/19] WIP testing proxy with TLS --- .../service/networkfirewall/exports_test.go | 26 +- internal/service/networkfirewall/proxy.go | 11 +- .../networkfirewall/proxy_configuration.go | 3 +- ...ation_rule_group_attachments_exclusive.go} | 41 +- ..._rule_group_attachments_exclusive_test.go} | 100 +- .../proxy_configuration_test.go | 2 +- .../networkfirewall/proxy_rule_group.go | 7 +- .../networkfirewall/proxy_rule_group_test.go | 2 +- .../service/networkfirewall/proxy_rules.go | 853 ------------------ .../networkfirewall/proxy_rules_exclusive.go | 768 ++++++++++++++++ ..._test.go => proxy_rules_exclusive_test.go} | 109 +-- .../service/networkfirewall/proxy_test.go | 49 +- .../networkfirewall/service_package_gen.go | 12 +- ...firewall_proxy_configuration.html.markdown | 10 - ...ration_rule_group_attachment.html.markdown | 62 -- ..._group_attachments_exclusive.html.markdown | 115 +++ ...orkfirewall_proxy_rule_group.html.markdown | 16 +- ...ewall_proxy_rules_exclusive.html.markdown} | 32 +- 18 files changed, 1091 insertions(+), 1127 deletions(-) rename internal/service/networkfirewall/{proxy_configuration_rule_groups_attachment.go => proxy_configuration_rule_group_attachments_exclusive.go} (87%) rename internal/service/networkfirewall/{proxy_configuration_rule_groups_attachment_test.go => proxy_configuration_rule_group_attachments_exclusive_test.go} (72%) delete mode 100644 internal/service/networkfirewall/proxy_rules.go create mode 100644 internal/service/networkfirewall/proxy_rules_exclusive.go rename internal/service/networkfirewall/{proxy_rules_test.go => proxy_rules_exclusive_test.go} (82%) delete mode 100644 website/docs/r/networkfirewall_proxy_configuration_rule_group_attachment.html.markdown create mode 100644 website/docs/r/networkfirewall_proxy_configuration_rule_group_attachments_exclusive.html.markdown rename website/docs/r/{networkfirewall_proxy_rules.html.markdown => networkfirewall_proxy_rules_exclusive.html.markdown} (80%) diff --git a/internal/service/networkfirewall/exports_test.go b/internal/service/networkfirewall/exports_test.go index 239a20023bc0..35c1828cb275 100644 --- a/internal/service/networkfirewall/exports_test.go +++ b/internal/service/networkfirewall/exports_test.go @@ -5,19 +5,19 @@ package networkfirewall // Exports for use in tests only. var ( - ResourceFirewall = resourceFirewall - ResourceFirewallPolicy = resourceFirewallPolicy - ResourceFirewallTransitGatewayAttachmentAccepter = newFirewallTransitGatewayAttachmentAccepterResource - ResourceLoggingConfiguration = resourceLoggingConfiguration - ResourceProxy = newResourceProxy - ResourceProxyConfiguration = newResourceProxyConfiguration - ResourceProxyConfigurationRuleGroupsAttachment = newResourceProxyConfigurationRuleGroupsAttachmentResource - ResourceProxyRuleGroup = newResourceProxyRuleGroup - ResourceProxyRules = newResourceProxyRules - ResourceResourcePolicy = resourceResourcePolicy - ResourceRuleGroup = resourceRuleGroup - ResourceTLSInspectionConfiguration = newTLSInspectionConfigurationResource - ResourceVPCEndpointAssociation = newVPCEndpointAssociationResource + ResourceFirewall = resourceFirewall + ResourceFirewallPolicy = resourceFirewallPolicy + ResourceFirewallTransitGatewayAttachmentAccepter = newFirewallTransitGatewayAttachmentAccepterResource + ResourceLoggingConfiguration = resourceLoggingConfiguration + ResourceProxy = newResourceProxy + ResourceProxyConfiguration = newResourceProxyConfiguration + ResourceProxyConfigurationRuleGroupAttachmentsExclusive = newResourceProxyConfigurationRuleGroupAttachmentsExclusive + ResourceProxyRuleGroup = newResourceProxyRuleGroup + ResourceProxyRulesExclusive = newResourceProxyRulesExclusive + ResourceResourcePolicy = resourceResourcePolicy + ResourceRuleGroup = resourceRuleGroup + ResourceTLSInspectionConfiguration = newTLSInspectionConfigurationResource + ResourceVPCEndpointAssociation = newVPCEndpointAssociationResource FindFirewallByARN = findFirewallByARN FindFirewallPolicyByARN = findFirewallPolicyByARN diff --git a/internal/service/networkfirewall/proxy.go b/internal/service/networkfirewall/proxy.go index 1104092f2163..ab605be34cbc 100644 --- a/internal/service/networkfirewall/proxy.go +++ b/internal/service/networkfirewall/proxy.go @@ -38,13 +38,14 @@ import ( // @FrameworkResource("aws_networkfirewall_proxy", name="Proxy") // @Tags(identifierAttribute="arn") -// @ArnIdentity(identityDuplicateAttributes="id", hasNoPreExistingResource=true) +// @ArnIdentity(identityDuplicateAttributes="id") +// @Testing(hasNoPreExistingResource=true) func newResourceProxy(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxy{} - r.SetDefaultCreateTimeout(30 * time.Minute) - r.SetDefaultUpdateTimeout(30 * time.Minute) - r.SetDefaultDeleteTimeout(30 * time.Minute) + r.SetDefaultCreateTimeout(60 * time.Minute) + r.SetDefaultUpdateTimeout(60 * time.Minute) + r.SetDefaultDeleteTimeout(60 * time.Minute) return r, nil } @@ -411,7 +412,7 @@ func findProxyByARN(ctx context.Context, conn *networkfirewall.Client, arn strin } if out == nil || out.Proxy == nil { - return nil, tfresource.NewEmptyResultError(&input) + return nil, tfresource.NewEmptyResultError() } if out.Proxy.DeleteTime != nil { diff --git a/internal/service/networkfirewall/proxy_configuration.go b/internal/service/networkfirewall/proxy_configuration.go index 2600dd76f137..27c11d4c730a 100644 --- a/internal/service/networkfirewall/proxy_configuration.go +++ b/internal/service/networkfirewall/proxy_configuration.go @@ -35,6 +35,7 @@ import ( // @Tags(identifierAttribute="arn") // @ArnIdentity(identityDuplicateAttributes="id") // @ArnFormat("proxy-configuration/{name}") +// @Testing(hasNoPreExistingResource=true) func newResourceProxyConfiguration(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxyConfiguration{} @@ -269,7 +270,7 @@ func findProxyConfigurationByARN(ctx context.Context, conn *networkfirewall.Clie } if out == nil || out.ProxyConfiguration == nil { - return nil, smarterr.NewError(tfresource.NewEmptyResultError(&input)) + return nil, smarterr.NewError(tfresource.NewEmptyResultError()) } if out.ProxyConfiguration.DeleteTime != nil { diff --git a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go similarity index 87% rename from internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go rename to internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go index 44c01fe41800..cc75345893b5 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go @@ -31,28 +31,29 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @FrameworkResource("aws_networkfirewall_proxy_configuration_rule_groups_attachment", name="Proxy Configuration Rule Groups Attachment") +// @FrameworkResource("aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive", name="Proxy Configuration Rule Group Attachments Exclusive") // @ArnIdentity("proxy_configuration_arn",identityDuplicateAttributes="id") -func newResourceProxyConfigurationRuleGroupsAttachmentResource(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceProxyConfigurationRuleGroupsAttachment{} +// @Testing(hasNoPreExistingResource=true) +func newResourceProxyConfigurationRuleGroupAttachmentsExclusive(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProxyConfigurationRuleGroupAttachmentsExclusive{} return r, nil } const ( - ResNameProxyConfigurationRuleGroupsAttachment = "Proxy Configuration Rule Groups Attachment" + ResNameProxyConfigurationRuleGroupAttachmentsExclusive = "Proxy Configuration Rule Group Attachments Exclusive" ) -type resourceProxyConfigurationRuleGroupsAttachment struct { - framework.ResourceWithModel[resourceProxyConfigurationRuleGroupsAttachmentModel] +type resourceProxyConfigurationRuleGroupAttachmentsExclusive struct { + framework.ResourceWithModel[proxyConfigurationRuleGroupAttachmentModel] framework.WithImportByIdentity } -func (r *resourceProxyConfigurationRuleGroupsAttachment) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = "aws_networkfirewall_proxy_configuration_rule_groups_attachment" +func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" } -func (r *resourceProxyConfigurationRuleGroupsAttachment) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root(names.AttrARN)), @@ -85,10 +86,10 @@ func (r *resourceProxyConfigurationRuleGroupsAttachment) Schema(ctx context.Cont } } -func (r *resourceProxyConfigurationRuleGroupsAttachment) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { conn := r.Meta().NetworkFirewallClient(ctx) - var plan resourceProxyConfigurationRuleGroupsAttachmentModel + var plan proxyConfigurationRuleGroupAttachmentModel smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) if resp.Diagnostics.HasError() { return @@ -146,10 +147,10 @@ func (r *resourceProxyConfigurationRuleGroupsAttachment) Create(ctx context.Cont smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, plan)) } -func (r *resourceProxyConfigurationRuleGroupsAttachment) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { conn := r.Meta().NetworkFirewallClient(ctx) - var state resourceProxyConfigurationRuleGroupsAttachmentModel + var state proxyConfigurationRuleGroupAttachmentModel smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) if resp.Diagnostics.HasError() { return @@ -183,10 +184,10 @@ func (r *resourceProxyConfigurationRuleGroupsAttachment) Read(ctx context.Contex smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) } -func (r *resourceProxyConfigurationRuleGroupsAttachment) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { conn := r.Meta().NetworkFirewallClient(ctx) - var plan, state resourceProxyConfigurationRuleGroupsAttachmentModel + var plan, state proxyConfigurationRuleGroupAttachmentModel smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) if resp.Diagnostics.HasError() { @@ -327,10 +328,10 @@ func (r *resourceProxyConfigurationRuleGroupsAttachment) Update(ctx context.Cont smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &plan)) } -func (r *resourceProxyConfigurationRuleGroupsAttachment) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { conn := r.Meta().NetworkFirewallClient(ctx) - var state resourceProxyConfigurationRuleGroupsAttachmentModel + var state proxyConfigurationRuleGroupAttachmentModel smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) if resp.Diagnostics.HasError() { return @@ -389,7 +390,7 @@ func (r *resourceProxyConfigurationRuleGroupsAttachment) Delete(ctx context.Cont } } -type resourceProxyConfigurationRuleGroupsAttachmentModel struct { +type proxyConfigurationRuleGroupAttachmentModel struct { framework.WithRegionModel ID types.String `tfsdk:"id"` ProxyConfigurationArn fwtypes.ARN `tfsdk:"proxy_configuration_arn"` @@ -402,11 +403,11 @@ type RuleGroupAttachmentModel struct { ProxyRuleGroupName types.String `tfsdk:"proxy_rule_group_name"` } -func (data *resourceProxyConfigurationRuleGroupsAttachmentModel) setID() { +func (data *proxyConfigurationRuleGroupAttachmentModel) setID() { data.ID = data.ProxyConfigurationArn.StringValue } -func flattenProxyConfigurationRuleGroups(ctx context.Context, out *networkfirewall.DescribeProxyConfigurationOutput, model *resourceProxyConfigurationRuleGroupsAttachmentModel) diag.Diagnostics { +func flattenProxyConfigurationRuleGroups(ctx context.Context, out *networkfirewall.DescribeProxyConfigurationOutput, model *proxyConfigurationRuleGroupAttachmentModel) diag.Diagnostics { var diags diag.Diagnostics if out.ProxyConfiguration == nil || out.ProxyConfiguration.RuleGroups == nil { diff --git a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go similarity index 72% rename from internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go rename to internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go index cd3364d65309..742eb449d6ac 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_groups_attachment_test.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go @@ -23,11 +23,11 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_basic(t *testing.T) { +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_basic(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" proxyConfigResourceName := "aws_networkfirewall_proxy_configuration.test" ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" @@ -35,12 +35,12 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_basic(t *testi PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_basic(rName), + Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v), resource.TestCheckResourceAttrPair(resourceName, names.AttrID, proxyConfigResourceName, names.AttrARN), resource.TestCheckResourceAttrPair(resourceName, "proxy_configuration_arn", proxyConfigResourceName, names.AttrARN), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"), @@ -58,23 +58,23 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_basic(t *testi }) } -func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_disappears(t *testing.T) { +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_disappears(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_basic(rName), + Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v), - acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyConfigurationRuleGroupsAttachment, resourceName, proxyConfigurationRuleGroupsAttachmentDisappearsStateFunc), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, t, tfnetworkfirewall.ResourceProxyConfigurationRuleGroupAttachmentsExclusive, resourceName, proxyConfigurationRuleGroupAttachmentsExclusiveDisappearsStateFunc), ), ExpectNonEmptyPlan: true, }, @@ -82,23 +82,23 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_disappears(t * }) } -func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateAdd(t *testing.T) { +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateAdd(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" ruleGroup3ResourceName := "aws_networkfirewall_proxy_rule_group.test3" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroups(rName), + Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroups(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v1), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), ), }, @@ -109,9 +109,9 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateAdd(t *t ImportStateVerifyIgnore: []string{"update_token"}, }, { - Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_threeRuleGroups(rName), + Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_threeRuleGroups(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v2), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "3"), resource.TestCheckResourceAttrPair(resourceName, "rule_group.2.proxy_rule_group_name", ruleGroup3ResourceName, names.AttrName), ), @@ -120,22 +120,22 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateAdd(t *t }) } -func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateRemove(t *testing.T) { +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateRemove(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroups(rName), + Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroups(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v1), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), ), }, @@ -146,9 +146,9 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateRemove(t ImportStateVerifyIgnore: []string{"update_token"}, }, { - Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_basic(rName), + Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v2), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"), ), }, @@ -156,11 +156,11 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateRemove(t }) } -func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateReorder(t *testing.T) { +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateReorder(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_configuration_rule_groups_attachment.test" + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" ruleGroup2ResourceName := "aws_networkfirewall_proxy_rule_group.test2" @@ -168,21 +168,21 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateReorder( PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroups(rName), + Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroups(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v1), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), resource.TestCheckResourceAttrPair(resourceName, "rule_group.0.proxy_rule_group_name", ruleGroup1ResourceName, names.AttrName), resource.TestCheckResourceAttrPair(resourceName, "rule_group.1.proxy_rule_group_name", ruleGroup2ResourceName, names.AttrName), ), }, { - Config: testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroupsReversed(rName), + Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroupsReversed(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx, resourceName, &v2), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), resource.TestCheckResourceAttrPair(resourceName, "rule_group.0.proxy_rule_group_name", ruleGroup2ResourceName, names.AttrName), resource.TestCheckResourceAttrPair(resourceName, "rule_group.1.proxy_rule_group_name", ruleGroup1ResourceName, names.AttrName), @@ -192,12 +192,12 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupsAttachment_updateReorder( }) } -func testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_networkfirewall_proxy_configuration_rule_groups_attachment" { + if rs.Type != "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" { continue } @@ -214,7 +214,7 @@ func testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx context.Conte // Check if there are any rule groups attached if out != nil && out.ProxyConfiguration != nil && out.ProxyConfiguration.RuleGroups != nil { if len(out.ProxyConfiguration.RuleGroups) > 0 { - return fmt.Errorf("NetworkFirewall Proxy Configuration Rule Groups Attachment still exists: %s has %d rule groups", rs.Primary.ID, len(out.ProxyConfiguration.RuleGroups)) + return fmt.Errorf("NetworkFirewall Proxy Configuration Rule Group Attachment still exists: %s has %d rule groups", rs.Primary.ID, len(out.ProxyConfiguration.RuleGroups)) } } } @@ -223,7 +223,7 @@ func testAccCheckProxyConfigurationRuleGroupsAttachmentDestroy(ctx context.Conte } } -func testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { +func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -244,7 +244,7 @@ func testAccCheckProxyConfigurationRuleGroupsAttachmentExists(ctx context.Contex } } -func proxyConfigurationRuleGroupsAttachmentDisappearsStateFunc(ctx context.Context, state *tfsdk.State, is *terraform.InstanceState) error { +func proxyConfigurationRuleGroupAttachmentsExclusiveDisappearsStateFunc(ctx context.Context, state *tfsdk.State, is *terraform.InstanceState) error { // Set the id attribute (needed for Delete to find the resource) if v, ok := is.Attributes[names.AttrID]; ok { if diags := state.SetAttribute(ctx, path.Root(names.AttrID), types.StringValue(v)); diags.HasError() { @@ -278,7 +278,7 @@ func proxyConfigurationRuleGroupsAttachmentDisappearsStateFunc(ctx context.Conte return nil } -func testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName string) string { +func testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_base(rName string) string { return fmt.Sprintf(` resource "aws_networkfirewall_proxy_configuration" "test" { name = %[1]q @@ -304,11 +304,11 @@ resource "aws_networkfirewall_proxy_rule_group" "test3" { `, rName) } -func testAccProxyConfigurationRuleGroupsAttachmentConfig_basic(rName string) string { +func testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_basic(rName string) string { return acctest.ConfigCompose( - testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName), + testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_base(rName), ` -resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" { +resource "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" "test" { proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn rule_group { @@ -318,11 +318,11 @@ resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" `) } -func testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroups(rName string) string { +func testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroups(rName string) string { return acctest.ConfigCompose( - testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName), + testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_base(rName), ` -resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" { +resource "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" "test" { proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn rule_group { @@ -336,11 +336,11 @@ resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" `) } -func testAccProxyConfigurationRuleGroupsAttachmentConfig_twoRuleGroupsReversed(rName string) string { +func testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroupsReversed(rName string) string { return acctest.ConfigCompose( - testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName), + testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_base(rName), ` -resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" { +resource "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" "test" { proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn rule_group { @@ -354,11 +354,11 @@ resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" `) } -func testAccProxyConfigurationRuleGroupsAttachmentConfig_threeRuleGroups(rName string) string { +func testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_threeRuleGroups(rName string) string { return acctest.ConfigCompose( - testAccProxyConfigurationRuleGroupsAttachmentConfig_base(rName), + testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_base(rName), ` -resource "aws_networkfirewall_proxy_configuration_rule_groups_attachment" "test" { +resource "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" "test" { proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn rule_group { diff --git a/internal/service/networkfirewall/proxy_configuration_test.go b/internal/service/networkfirewall/proxy_configuration_test.go index 364d7552e2d9..6636d1b1b90c 100644 --- a/internal/service/networkfirewall/proxy_configuration_test.go +++ b/internal/service/networkfirewall/proxy_configuration_test.go @@ -73,7 +73,7 @@ func TestAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { Config: testAccProxyConfigurationConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckProxyConfigurationExists(ctx, resourceName, &v), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyConfiguration, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, t, tfnetworkfirewall.ResourceProxyConfiguration, resourceName), ), ExpectNonEmptyPlan: true, ConfigPlanChecks: resource.ConfigPlanChecks{ diff --git a/internal/service/networkfirewall/proxy_rule_group.go b/internal/service/networkfirewall/proxy_rule_group.go index b32a40aed9d3..6569e951517a 100644 --- a/internal/service/networkfirewall/proxy_rule_group.go +++ b/internal/service/networkfirewall/proxy_rule_group.go @@ -30,6 +30,7 @@ import ( // @Tags(identifierAttribute="arn") // @ArnIdentity(identityDuplicateAttributes="id") // @ArnFormat("proxy-rule-group/{name}") +// @Testing(hasNoPreExistingResource=true) func newResourceProxyRuleGroup(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxyRuleGroup{} @@ -45,6 +46,10 @@ type resourceProxyRuleGroup struct { framework.WithImportByIdentity } +func (r *resourceProxyRuleGroup) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_networkfirewall_proxy_rule_group" +} + func (r *resourceProxyRuleGroup) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ @@ -198,7 +203,7 @@ func findProxyRuleGroupByARN(ctx context.Context, conn *networkfirewall.Client, } if output == nil || output.ProxyRuleGroup == nil { - return nil, tfresource.NewEmptyResultError(input) + return nil, tfresource.NewEmptyResultError() } if output.ProxyRuleGroup.DeleteTime != nil { diff --git a/internal/service/networkfirewall/proxy_rule_group_test.go b/internal/service/networkfirewall/proxy_rule_group_test.go index e77cc512c61d..f7e61c8ff2e1 100644 --- a/internal/service/networkfirewall/proxy_rule_group_test.go +++ b/internal/service/networkfirewall/proxy_rule_group_test.go @@ -69,7 +69,7 @@ func TestAccNetworkFirewallProxyRuleGroup_disappears(t *testing.T) { Config: testAccProxyRuleGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyRuleGroup, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, t, tfnetworkfirewall.ResourceProxyRuleGroup, resourceName), ), ExpectNonEmptyPlan: true, ConfigPlanChecks: resource.ConfigPlanChecks{ diff --git a/internal/service/networkfirewall/proxy_rules.go b/internal/service/networkfirewall/proxy_rules.go deleted file mode 100644 index 8c8393ec1ad2..000000000000 --- a/internal/service/networkfirewall/proxy_rules.go +++ /dev/null @@ -1,853 +0,0 @@ -// Copyright IBM Corp. 2014, 2025 -// SPDX-License-Identifier: MPL-2.0 - -package networkfirewall - -import ( - "context" - "errors" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/networkfirewall" - awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework/diag" - "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/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-provider-aws/internal/errs" - "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" - "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" - tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" - "github.com/hashicorp/terraform-provider-aws/internal/smerr" - "github.com/hashicorp/terraform-provider-aws/names" -) - -// @FrameworkResource("aws_networkfirewall_proxy_rules", name="Proxy Rules") -// @ArnIdentity("proxy_rule_group_arn",identityDuplicateAttributes="id") -// @ArnFormat("proxy-rule-group/{name}") -func newResourceProxyRules(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceProxyRules{} - - return r, nil -} - -const ( - ResNameProxyRules = "Proxy Rules" -) - -type resourceProxyRules struct { - framework.ResourceWithModel[resourceProxyRulesModel] - framework.WithImportByIdentity -} - -func (r *resourceProxyRules) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root("proxy_rule_group_arn")), - "proxy_rule_group_arn": schema.StringAttribute{ - CustomType: fwtypes.ARNType, - Optional: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - }, - Blocks: map[string]schema.Block{ - "post_response": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleModel](ctx), - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "action": schema.StringAttribute{ - CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), - Required: true, - }, - names.AttrDescription: schema.StringAttribute{ - Optional: true, - }, - "proxy_rule_name": schema.StringAttribute{ - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "conditions": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[conditionModel](ctx), - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "condition_key": schema.StringAttribute{ - Required: true, - }, - "condition_operator": schema.StringAttribute{ - Required: true, - }, - "condition_values": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, - ElementType: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - }, - "pre_dns": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleModel](ctx), - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "action": schema.StringAttribute{ - CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), - Required: true, - }, - names.AttrDescription: schema.StringAttribute{ - Optional: true, - }, - "proxy_rule_name": schema.StringAttribute{ - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "conditions": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[conditionModel](ctx), - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "condition_key": schema.StringAttribute{ - Required: true, - }, - "condition_operator": schema.StringAttribute{ - Required: true, - }, - "condition_values": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, - ElementType: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - }, - "pre_request": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleModel](ctx), - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "action": schema.StringAttribute{ - CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), - Required: true, - }, - names.AttrDescription: schema.StringAttribute{ - Optional: true, - }, - "proxy_rule_name": schema.StringAttribute{ - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "conditions": schema.ListNestedBlock{ - CustomType: fwtypes.NewListNestedObjectTypeOf[conditionModel](ctx), - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "condition_key": schema.StringAttribute{ - Required: true, - }, - "condition_operator": schema.StringAttribute{ - Required: true, - }, - "condition_values": schema.ListAttribute{ - CustomType: fwtypes.ListOfStringType, - ElementType: types.StringType, - Required: true, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func (r *resourceProxyRules) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().NetworkFirewallClient(ctx) - - var plan resourceProxyRulesModel - smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) - if resp.Diagnostics.HasError() { - return - } - - input := networkfirewall.CreateProxyRulesInput{ - ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), - } - - // Create the Rules structure organized by phase - var rulesByPhase awstypes.CreateProxyRulesByRequestPhase - - // Process PostRESPONSE rules - if !plan.PostRESPONSE.IsNull() && !plan.PostRESPONSE.IsUnknown() { - var postRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PostRESPONSE.ElementsAs(ctx, &postRules, false)) - if resp.Diagnostics.HasError() { - return - } - - for i, ruleModel := range postRules { - var rule awstypes.CreateProxyRule - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) - if resp.Diagnostics.HasError() { - return - } - // Set InsertPosition based on index - insertPos := int32(i) - rule.InsertPosition = &insertPos - rulesByPhase.PostRESPONSE = append(rulesByPhase.PostRESPONSE, rule) - } - } - - // Process PreDNS rules - if !plan.PreDNS.IsNull() && !plan.PreDNS.IsUnknown() { - var preDNSRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreDNS.ElementsAs(ctx, &preDNSRules, false)) - if resp.Diagnostics.HasError() { - return - } - - for i, ruleModel := range preDNSRules { - var rule awstypes.CreateProxyRule - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) - if resp.Diagnostics.HasError() { - return - } - // Set InsertPosition based on index - insertPos := int32(i) - rule.InsertPosition = &insertPos - rulesByPhase.PreDNS = append(rulesByPhase.PreDNS, rule) - } - } - - // Process PreREQUEST rules - if !plan.PreREQUEST.IsNull() && !plan.PreREQUEST.IsUnknown() { - var preRequestRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreREQUEST.ElementsAs(ctx, &preRequestRules, false)) - if resp.Diagnostics.HasError() { - return - } - - for i, ruleModel := range preRequestRules { - var rule awstypes.CreateProxyRule - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleModel, &rule)) - if resp.Diagnostics.HasError() { - return - } - // Set InsertPosition based on index - insertPos := int32(i) - rule.InsertPosition = &insertPos - rulesByPhase.PreREQUEST = append(rulesByPhase.PreREQUEST, rule) - } - } - - input.Rules = &rulesByPhase - - out, err := conn.CreateProxyRules(ctx, &input) - if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) - return - } - if out == nil || out.ProxyRuleGroup == nil { - smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, plan.ProxyRuleGroupArn.String()) - return - } - - // Set ID to the proxy rule group ARN - plan.setID() - - // Read back to get full state - readOut, err := findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) - if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) - return - } - - smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, readOut, &plan)) - if resp.Diagnostics.HasError() { - return - } - - smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, plan)) -} - -func (r *resourceProxyRules) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().NetworkFirewallClient(ctx) - - var state resourceProxyRulesModel - smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) - if resp.Diagnostics.HasError() { - return - } - - out, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) - if tfretry.NotFound(err) { - resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) - resp.State.RemoveResource(ctx) - return - } - if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ProxyRuleGroupArn.String()) - return - } - - smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, out, &state)) - if resp.Diagnostics.HasError() { - return - } - - smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) -} - -func (r *resourceProxyRules) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().NetworkFirewallClient(ctx) - - var plan, state resourceProxyRulesModel - smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) - smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) - if resp.Diagnostics.HasError() { - return - } - - // Get current state to obtain update token and existing rules from AWS - currentRules, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) - if err != nil && !tfretry.NotFound(err) { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) - return - } - - updateToken := currentRules.UpdateToken - - // Build maps of rules by name for each phase - stateRulesByName := make(map[string]proxyRuleModel) - planRulesByName := make(map[string]proxyRuleModel) - - // Extract existing rules from AWS (currentRules) instead of Terraform state - // This ensures we correctly identify which rules already exist in AWS - if currentRules != nil && currentRules.ProxyRuleGroup != nil && currentRules.ProxyRuleGroup.Rules != nil { - rules := currentRules.ProxyRuleGroup.Rules - - for _, rule := range rules.PostRESPONSE { - var ruleModel proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, &rule, &ruleModel)) - stateRulesByName[ruleModel.ProxyRuleName.ValueString()] = ruleModel - } - for _, rule := range rules.PreDNS { - var ruleModel proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, &rule, &ruleModel)) - stateRulesByName[ruleModel.ProxyRuleName.ValueString()] = ruleModel - } - for _, rule := range rules.PreREQUEST { - var ruleModel proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Flatten(ctx, &rule, &ruleModel)) - stateRulesByName[ruleModel.ProxyRuleName.ValueString()] = ruleModel - } - } - - // Extract plan rules - if !plan.PostRESPONSE.IsNull() && !plan.PostRESPONSE.IsUnknown() { - var rules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PostRESPONSE.ElementsAs(ctx, &rules, false)) - for _, rule := range rules { - planRulesByName[rule.ProxyRuleName.ValueString()] = rule - } - } - if !plan.PreDNS.IsNull() && !plan.PreDNS.IsUnknown() { - var rules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreDNS.ElementsAs(ctx, &rules, false)) - for _, rule := range rules { - planRulesByName[rule.ProxyRuleName.ValueString()] = rule - } - } - if !plan.PreREQUEST.IsNull() && !plan.PreREQUEST.IsUnknown() { - var rules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreREQUEST.ElementsAs(ctx, &rules, false)) - for _, rule := range rules { - planRulesByName[rule.ProxyRuleName.ValueString()] = rule - } - } - - if resp.Diagnostics.HasError() { - return - } - - // Determine which rules to add, update, or delete - var rulesToCreate []proxyRuleModel - var rulesToUpdate []proxyRuleModel - var rulesToDelete []string - - // Check for new or modified rules - for name, planRule := range planRulesByName { - if stateRule, exists := stateRulesByName[name]; !exists { - // New rule - needs to be created - rulesToCreate = append(rulesToCreate, planRule) - } else if !ruleModelsEqual(ctx, stateRule, planRule) { - // Modified rule - needs to be updated - rulesToUpdate = append(rulesToUpdate, planRule) - } - } - - // Check for deleted rules - for name := range stateRulesByName { - if _, exists := planRulesByName[name]; !exists { - rulesToDelete = append(rulesToDelete, name) - } - } - - // Delete removed rules - if len(rulesToDelete) > 0 { - deleteInput := networkfirewall.DeleteProxyRulesInput{ - ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), - Rules: rulesToDelete, - } - - _, err = conn.DeleteProxyRules(ctx, &deleteInput) - if err != nil && !errs.IsA[*awstypes.ResourceNotFoundException](err) { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) - return - } - - // Refresh update token after deletion for subsequent UpdateProxyRule calls - currentRules, err = findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) - if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) - return - } - updateToken = currentRules.UpdateToken - } - - // Update modified rules - for _, ruleModel := range rulesToUpdate { - // Refresh the update token before each update to ensure we have the latest - currentRules, err = findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) - if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) - return - } - updateToken = currentRules.UpdateToken - - updateInput := networkfirewall.UpdateProxyRuleInput{ - ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), - ProxyRuleName: ruleModel.ProxyRuleName.ValueStringPointer(), - UpdateToken: updateToken, - } - - // Set action if not null - if !ruleModel.Action.IsNull() && !ruleModel.Action.IsUnknown() { - updateInput.Action = ruleModel.Action.ValueEnum() - } - - // Set description if not null - if !ruleModel.Description.IsNull() && !ruleModel.Description.IsUnknown() { - updateInput.Description = ruleModel.Description.ValueStringPointer() - } - - // Handle conditions update by removing all old conditions and adding new ones - if stateRule, exists := stateRulesByName[ruleModel.ProxyRuleName.ValueString()]; exists { - // Remove old conditions - if !stateRule.Conditions.IsNull() && !stateRule.Conditions.IsUnknown() { - var oldConditions []conditionModel - smerr.AddEnrich(ctx, &resp.Diagnostics, stateRule.Conditions.ElementsAs(ctx, &oldConditions, false)) - for _, cond := range oldConditions { - var removeCondition awstypes.ProxyRuleCondition - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, cond, &removeCondition)) - updateInput.RemoveConditions = append(updateInput.RemoveConditions, removeCondition) - } - } - } - - // Add new conditions - if !ruleModel.Conditions.IsNull() && !ruleModel.Conditions.IsUnknown() { - var newConditions []conditionModel - smerr.AddEnrich(ctx, &resp.Diagnostics, ruleModel.Conditions.ElementsAs(ctx, &newConditions, false)) - for _, cond := range newConditions { - var addCondition awstypes.ProxyRuleCondition - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, cond, &addCondition)) - updateInput.AddConditions = append(updateInput.AddConditions, addCondition) - } - } - - if resp.Diagnostics.HasError() { - return - } - - out, err := conn.UpdateProxyRule(ctx, &updateInput) - if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) - return - } - - // Update token for next operation - updateToken = out.UpdateToken - } - - // Create new rules - if len(rulesToCreate) > 0 { - var rulesByPhase awstypes.CreateProxyRulesByRequestPhase - - // Process each phase separately with proper InsertPosition - // Process PostRESPONSE rules - if !plan.PostRESPONSE.IsNull() { - var postRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PostRESPONSE.ElementsAs(ctx, &postRules, false)) - for i, r := range postRules { - if _, exists := planRulesByName[r.ProxyRuleName.ValueString()]; exists { - // Check if this rule is in the create list - for _, createRule := range rulesToCreate { - if createRule.ProxyRuleName.ValueString() == r.ProxyRuleName.ValueString() { - var rule awstypes.CreateProxyRule - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, createRule, &rule)) - if resp.Diagnostics.HasError() { - return - } - // Set InsertPosition based on index - insertPos := int32(i) - rule.InsertPosition = &insertPos - rulesByPhase.PostRESPONSE = append(rulesByPhase.PostRESPONSE, rule) - break - } - } - } - } - } - - // Process PreDNS rules - if !plan.PreDNS.IsNull() { - var preDNSRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreDNS.ElementsAs(ctx, &preDNSRules, false)) - for i, r := range preDNSRules { - if _, exists := planRulesByName[r.ProxyRuleName.ValueString()]; exists { - // Check if this rule is in the create list - for _, createRule := range rulesToCreate { - if createRule.ProxyRuleName.ValueString() == r.ProxyRuleName.ValueString() { - var rule awstypes.CreateProxyRule - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, createRule, &rule)) - if resp.Diagnostics.HasError() { - return - } - // Set InsertPosition based on index - insertPos := int32(i) - rule.InsertPosition = &insertPos - rulesByPhase.PreDNS = append(rulesByPhase.PreDNS, rule) - break - } - } - } - } - } - - // Process PreREQUEST rules - if !plan.PreREQUEST.IsNull() { - var preRequestRules []proxyRuleModel - smerr.AddEnrich(ctx, &resp.Diagnostics, plan.PreREQUEST.ElementsAs(ctx, &preRequestRules, false)) - for i, r := range preRequestRules { - if _, exists := planRulesByName[r.ProxyRuleName.ValueString()]; exists { - // Check if this rule is in the create list - for _, createRule := range rulesToCreate { - if createRule.ProxyRuleName.ValueString() == r.ProxyRuleName.ValueString() { - var rule awstypes.CreateProxyRule - smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, createRule, &rule)) - if resp.Diagnostics.HasError() { - return - } - // Set InsertPosition based on index - insertPos := int32(i) - rule.InsertPosition = &insertPos - rulesByPhase.PreREQUEST = append(rulesByPhase.PreREQUEST, rule) - break - } - } - } - } - } - - createInput := networkfirewall.CreateProxyRulesInput{ - ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), - Rules: &rulesByPhase, - } - - _, err = conn.CreateProxyRules(ctx, &createInput) - if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) - return - } - } - - // Read back to get full state - readOut, err := findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) - if err != nil { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) - return - } - - smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, readOut, &plan)) - if resp.Diagnostics.HasError() { - return - } - - smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &plan)) -} - -// ruleModelsEqual compares two proxyRuleModel instances to determine if they're equal -func ruleModelsEqual(ctx context.Context, a, b proxyRuleModel) bool { - // Compare action - if a.Action.ValueEnum() != b.Action.ValueEnum() { - return false - } - - // Compare description - if a.Description.ValueString() != b.Description.ValueString() { - return false - } - - // Note: InsertPosition is not compared as it's auto-populated and not stored in state - - // Compare conditions count - if a.Conditions.IsNull() != b.Conditions.IsNull() { - return false - } - - if !a.Conditions.IsNull() { - var aConditions, bConditions []conditionModel - a.Conditions.ElementsAs(ctx, &aConditions, false) - b.Conditions.ElementsAs(ctx, &bConditions, false) - - if len(aConditions) != len(bConditions) { - return false - } - - // Compare each condition - for i := range aConditions { - if aConditions[i].ConditionKey.ValueString() != bConditions[i].ConditionKey.ValueString() { - return false - } - if aConditions[i].ConditionOperator.ValueString() != bConditions[i].ConditionOperator.ValueString() { - return false - } - - var aValues, bValues []types.String - aConditions[i].ConditionValues.ElementsAs(ctx, &aValues, false) - bConditions[i].ConditionValues.ElementsAs(ctx, &bValues, false) - - if len(aValues) != len(bValues) { - return false - } - - for j := range aValues { - if aValues[j].ValueString() != bValues[j].ValueString() { - return false - } - } - } - } - - return true -} - -func (r *resourceProxyRules) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().NetworkFirewallClient(ctx) - - var state resourceProxyRulesModel - smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) - if resp.Diagnostics.HasError() { - return - } - - // Get all rule names for this group - out, err := findProxyRulesByGroupARN(ctx, conn, state.ID.ValueString()) - if err != nil && !tfretry.NotFound(err) { - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) - return - } - - if out != nil && out.ProxyRuleGroup != nil && out.ProxyRuleGroup.Rules != nil { - var ruleNames []string - rules := out.ProxyRuleGroup.Rules - - // Collect rule names from all phases - for _, rule := range rules.PostRESPONSE { - if rule.ProxyRuleName != nil { - ruleNames = append(ruleNames, *rule.ProxyRuleName) - } - } - for _, rule := range rules.PreDNS { - if rule.ProxyRuleName != nil { - ruleNames = append(ruleNames, *rule.ProxyRuleName) - } - } - for _, rule := range rules.PreREQUEST { - if rule.ProxyRuleName != nil { - ruleNames = append(ruleNames, *rule.ProxyRuleName) - } - } - - if len(ruleNames) > 0 { - input := networkfirewall.DeleteProxyRulesInput{ - ProxyRuleGroupArn: state.ProxyRuleGroupArn.ValueStringPointer(), - Rules: ruleNames, - } - - _, err = conn.DeleteProxyRules(ctx, &input) - if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return - } - - smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) - return - } - } - } -} - -func findProxyRulesByGroupARN(ctx context.Context, conn *networkfirewall.Client, groupARN string) (*networkfirewall.DescribeProxyRuleGroupOutput, error) { - input := networkfirewall.DescribeProxyRuleGroupInput{ - ProxyRuleGroupArn: aws.String(groupARN), - } - - out, err := conn.DescribeProxyRuleGroup(ctx, &input) - if err != nil { - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &tfretry.NotFoundError{ - LastError: err, - } - } - - return nil, err - } - - if out == nil || out.ProxyRuleGroup == nil { - return nil, &tfretry.NotFoundError{ - Message: "proxy rule group not found", - } - } - - return out, nil -} - -func flattenProxyRules(ctx context.Context, out *networkfirewall.DescribeProxyRuleGroupOutput, model *resourceProxyRulesModel) diag.Diagnostics { - var diags diag.Diagnostics - - if out.ProxyRuleGroup == nil || out.ProxyRuleGroup.Rules == nil { - return diags - } - - rules := out.ProxyRuleGroup.Rules - - // Process PostRESPONSE rules - if len(rules.PostRESPONSE) > 0 { - var postResponseRules []proxyRuleModel - for _, rule := range rules.PostRESPONSE { - var ruleModel proxyRuleModel - diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) - if diags.HasError() { - return diags - } - postResponseRules = append(postResponseRules, ruleModel) - } - postList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, postResponseRules) - diags.Append(d...) - if diags.HasError() { - return diags - } - model.PostRESPONSE = postList - } else { - model.PostRESPONSE = fwtypes.NewListNestedObjectValueOfNull[proxyRuleModel](ctx) - } - - // Process PreDNS rules - if len(rules.PreDNS) > 0 { - var preDNSRules []proxyRuleModel - for _, rule := range rules.PreDNS { - var ruleModel proxyRuleModel - diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) - if diags.HasError() { - return diags - } - preDNSRules = append(preDNSRules, ruleModel) - } - preDNSList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, preDNSRules) - diags.Append(d...) - if diags.HasError() { - return diags - } - model.PreDNS = preDNSList - } else { - model.PreDNS = fwtypes.NewListNestedObjectValueOfNull[proxyRuleModel](ctx) - } - - // Process PreREQUEST rules - if len(rules.PreREQUEST) > 0 { - var preRequestRules []proxyRuleModel - for _, rule := range rules.PreREQUEST { - var ruleModel proxyRuleModel - diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) - if diags.HasError() { - return diags - } - preRequestRules = append(preRequestRules, ruleModel) - } - preRequestList, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, preRequestRules) - diags.Append(d...) - if diags.HasError() { - return diags - } - model.PreREQUEST = preRequestList - } else { - model.PreREQUEST = fwtypes.NewListNestedObjectValueOfNull[proxyRuleModel](ctx) - } - - if out.ProxyRuleGroup.ProxyRuleGroupArn != nil { - model.ProxyRuleGroupArn = fwtypes.ARNValue(aws.ToString(out.ProxyRuleGroup.ProxyRuleGroupArn)) - } - - return diags -} - -type resourceProxyRulesModel struct { - framework.WithRegionModel - ID types.String `tfsdk:"id"` - PostRESPONSE fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"post_response"` - PreDNS fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_dns"` - PreREQUEST fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_request"` - ProxyRuleGroupArn fwtypes.ARN `tfsdk:"proxy_rule_group_arn"` -} - -type proxyRuleModel struct { - Action fwtypes.StringEnum[awstypes.ProxyRulePhaseAction] `tfsdk:"action"` - Conditions fwtypes.ListNestedObjectValueOf[conditionModel] `tfsdk:"conditions"` - Description types.String `tfsdk:"description"` - ProxyRuleName types.String `tfsdk:"proxy_rule_name"` -} - -type conditionModel struct { - ConditionKey types.String `tfsdk:"condition_key"` - ConditionOperator types.String `tfsdk:"condition_operator"` - ConditionValues fwtypes.ListValueOf[types.String] `tfsdk:"condition_values"` -} - -func (data *resourceProxyRulesModel) setID() { - data.ID = data.ProxyRuleGroupArn.StringValue -} diff --git a/internal/service/networkfirewall/proxy_rules_exclusive.go b/internal/service/networkfirewall/proxy_rules_exclusive.go new file mode 100644 index 000000000000..0f26a8a50dcb --- /dev/null +++ b/internal/service/networkfirewall/proxy_rules_exclusive.go @@ -0,0 +1,768 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + "errors" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "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" + tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_networkfirewall_proxy_rules_exclusive", name="Proxy Rules Exclusive") +// @ArnIdentity("proxy_rule_group_arn",identityDuplicateAttributes="id") +// @ArnFormat("proxy-rule-group/{name}") +// @Testing(hasNoPreExistingResource=true) +func newResourceProxyRulesExclusive(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProxyRulesExclusive{} + + return r, nil +} + +const ( + ResNameProxyRulesExclusive = "Proxy Rules Exclusive" +) + +type resourceProxyRulesExclusive struct { + framework.ResourceWithModel[resourceProxyRulesExclusiveModel] + framework.WithImportByIdentity +} + +func (r *resourceProxyRulesExclusive) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_networkfirewall_proxy_rules_exclusive" +} + +func (r *resourceProxyRulesExclusive) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrID: framework.IDAttributeDeprecatedWithAlternate(path.Root("proxy_rule_group_arn")), + "proxy_rule_group_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "post_response": proxyRuleSchemaBlock(ctx), + "pre_dns": proxyRuleSchemaBlock(ctx), + "pre_request": proxyRuleSchemaBlock(ctx), + }, + } +} + +func proxyRuleSchemaBlock(ctx context.Context) schema.Block { + return schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), + Required: true, + }, + names.AttrDescription: schema.StringAttribute{ + Optional: true, + }, + "proxy_rule_name": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "conditions": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleConditionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "condition_key": schema.StringAttribute{ + Required: true, + }, + "condition_operator": schema.StringAttribute{ + Required: true, + }, + "condition_values": schema.ListAttribute{ + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Required: true, + }, + }, + }, + }, + }, + }, + } +} + +func (r *resourceProxyRulesExclusive) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan resourceProxyRulesExclusiveModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + if resp.Diagnostics.HasError() { + return + } + + input := networkfirewall.CreateProxyRulesInput{ + ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), + } + + var rulesByPhase awstypes.CreateProxyRulesByRequestPhase + + rulesByPhase.PostRESPONSE = proxyExpandRulesForPhase(ctx, plan.PostRESPONSE, &resp.Diagnostics) + rulesByPhase.PreDNS = proxyExpandRulesForPhase(ctx, plan.PreDNS, &resp.Diagnostics) + rulesByPhase.PreREQUEST = proxyExpandRulesForPhase(ctx, plan.PreREQUEST, &resp.Diagnostics) + + if resp.Diagnostics.HasError() { + return + } + + input.Rules = &rulesByPhase + + out, err := conn.CreateProxyRules(ctx, &input) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) + return + } + if out == nil || out.ProxyRuleGroup == nil { + smerr.AddError(ctx, &resp.Diagnostics, errors.New("empty output"), smerr.ID, plan.ProxyRuleGroupArn.String()) + return + } + + plan.setID() + + readOut, err := findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, readOut, &plan)) + if resp.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, plan)) +} + +func (r *resourceProxyRulesExclusive) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyRulesExclusiveModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + out, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) + if tfretry.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ProxyRuleGroupArn.String()) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, out, &state)) + if resp.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) +} + +func (r *resourceProxyRulesExclusive) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var plan, state resourceProxyRulesExclusiveModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + // Get current state to obtain update token and existing rules from AWS + currentRules, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) + if err != nil && !tfretry.NotFound(err) { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) + return + } + + updateToken := currentRules.UpdateToken + + // Extract rules from AWS for each phase + var statePostRESPONSE, statePreDNS, statePreREQUEST []proxyRuleModel + if currentRules != nil && currentRules.ProxyRuleGroup != nil && currentRules.ProxyRuleGroup.Rules != nil { + rules := currentRules.ProxyRuleGroup.Rules + statePostRESPONSE = proxyExtractRulesFromPhase(ctx, rules.PostRESPONSE, &resp.Diagnostics) + statePreDNS = proxyExtractRulesFromPhase(ctx, rules.PreDNS, &resp.Diagnostics) + statePreREQUEST = proxyExtractRulesFromPhase(ctx, rules.PreREQUEST, &resp.Diagnostics) + } + + // Extract plan rules for each phase + planPostRESPONSE := proxyExtractPlanRules(ctx, plan.PostRESPONSE, &resp.Diagnostics) + planPreDNS := proxyExtractPlanRules(ctx, plan.PreDNS, &resp.Diagnostics) + planPreREQUEST := proxyExtractPlanRules(ctx, plan.PreREQUEST, &resp.Diagnostics) + + if resp.Diagnostics.HasError() { + return + } + + // Track rules to delete, update, and create + // Using map to avoid duplicates since we track by name + rulesToDelete := make(map[string]bool) + rulesToUpdate := make(map[string]proxyRuleModel) + rulesToRecreate := make(map[string]proxyRuleRecreateInfo) + + // Process each phase + processProxyRulesPhaseChanges(ctx, statePostRESPONSE, planPostRESPONSE, "PostRESPONSE", rulesToDelete, rulesToUpdate, rulesToRecreate) + processProxyRulesPhaseChanges(ctx, statePreDNS, planPreDNS, "PreDNS", rulesToDelete, rulesToUpdate, rulesToRecreate) + processProxyRulesPhaseChanges(ctx, statePreREQUEST, planPreREQUEST, "PreREQUEST", rulesToDelete, rulesToUpdate, rulesToRecreate) + + // Remove any rules from the update list that are being recreated + // This ensures we don't try to update a rule that's being deleted + for name := range rulesToRecreate { + delete(rulesToUpdate, name) + } + + // Step 1: Delete rules (including those being recreated) + // This must happen before Step 3 to satisfy proxy_rule_name uniqueness constraint + var deleteList []string + for name := range rulesToDelete { + deleteList = append(deleteList, name) + } + + if len(deleteList) > 0 { + deleteInput := networkfirewall.DeleteProxyRulesInput{ + ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), + Rules: deleteList, + } + + _, err = conn.DeleteProxyRules(ctx, &deleteInput) + if err != nil && !errs.IsA[*awstypes.ResourceNotFoundException](err) { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + + // Refresh update token after deletion and verify rules were deleted + currentRules, err = findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + updateToken = currentRules.UpdateToken + + // Verify that the deleted rules are actually gone + if currentRules.ProxyRuleGroup != nil && currentRules.ProxyRuleGroup.Rules != nil { + existingRuleNames := proxyCollectRuleNames(currentRules.ProxyRuleGroup.Rules) + + for _, deletedName := range deleteList { + if existingRuleNames[deletedName] { + smerr.AddError(ctx, &resp.Diagnostics, errors.New("rule deletion not yet complete"), smerr.ID, plan.ID.String()) + return + } + } + } + } + + // Step 2: Update rules (action, description, and conditions) + for _, ruleModel := range rulesToUpdate { + // Refresh the update token before each update + currentRules, err = findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + updateToken = currentRules.UpdateToken + + // Build a map of current rules by name to get old conditions + currentRulesByName := proxyBuildRuleMapByName(ctx, currentRules.ProxyRuleGroup, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + updateInput := networkfirewall.UpdateProxyRuleInput{ + ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), + ProxyRuleName: ruleModel.ProxyRuleName.ValueStringPointer(), + UpdateToken: updateToken, + } + + // Update action if specified + if !ruleModel.Action.IsNull() && !ruleModel.Action.IsUnknown() { + updateInput.Action = ruleModel.Action.ValueEnum() + } + + // Update description if specified + if !ruleModel.Description.IsNull() && !ruleModel.Description.IsUnknown() { + updateInput.Description = ruleModel.Description.ValueStringPointer() + } + + // Remove old conditions + if currentRule, exists := currentRulesByName[ruleModel.ProxyRuleName.ValueString()]; exists { + if !currentRule.Conditions.IsNull() && !currentRule.Conditions.IsUnknown() { + var oldConditions []proxyRuleConditionModel + smerr.AddEnrich(ctx, &resp.Diagnostics, currentRule.Conditions.ElementsAs(ctx, &oldConditions, false)) + for _, cond := range oldConditions { + var removeCondition awstypes.ProxyRuleCondition + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, cond, &removeCondition)) + updateInput.RemoveConditions = append(updateInput.RemoveConditions, removeCondition) + } + } + } + + // Add new conditions + if !ruleModel.Conditions.IsNull() && !ruleModel.Conditions.IsUnknown() { + var newConditions []proxyRuleConditionModel + smerr.AddEnrich(ctx, &resp.Diagnostics, ruleModel.Conditions.ElementsAs(ctx, &newConditions, false)) + for _, cond := range newConditions { + var addCondition awstypes.ProxyRuleCondition + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, cond, &addCondition)) + updateInput.AddConditions = append(updateInput.AddConditions, addCondition) + } + } + + if resp.Diagnostics.HasError() { + return + } + + out, err := conn.UpdateProxyRule(ctx, &updateInput) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + + updateToken = out.UpdateToken + } + + // Step 3: Create/recreate rules + // Safe to create rules now - all deletions completed in Step 1, ensuring proxy_rule_name uniqueness + if len(rulesToRecreate) > 0 { + var rulesByPhase awstypes.CreateProxyRulesByRequestPhase + + // Organize rules by phase + for _, ruleData := range rulesToRecreate { + var rule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, &resp.Diagnostics, flex.Expand(ctx, ruleData.rule, &rule)) + if resp.Diagnostics.HasError() { + return + } + rule.InsertPosition = &ruleData.position + + switch ruleData.phase { + case "PostRESPONSE": + rulesByPhase.PostRESPONSE = append(rulesByPhase.PostRESPONSE, rule) + case "PreDNS": + rulesByPhase.PreDNS = append(rulesByPhase.PreDNS, rule) + case "PreREQUEST": + rulesByPhase.PreREQUEST = append(rulesByPhase.PreREQUEST, rule) + } + } + + createInput := networkfirewall.CreateProxyRulesInput{ + ProxyRuleGroupArn: plan.ProxyRuleGroupArn.ValueStringPointer(), + Rules: &rulesByPhase, + } + + _, err = conn.CreateProxyRules(ctx, &createInput) + + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) + return + } + } + + // Read back to get full state + readOut, err := findProxyRulesByGroupARN(ctx, conn, plan.ProxyRuleGroupArn.ValueString()) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ProxyRuleGroupArn.String()) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, readOut, &plan)) + if resp.Diagnostics.HasError() { + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &plan)) +} + +// ruleNeedsRecreate determines if a rule needs to be deleted and recreated + +func (r *resourceProxyRulesExclusive) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var state resourceProxyRulesExclusiveModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + // Get all rule names for this group + out, err := findProxyRulesByGroupARN(ctx, conn, state.ID.ValueString()) + if err != nil && !tfretry.NotFound(err) { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) + return + } + + if out != nil && out.ProxyRuleGroup != nil && out.ProxyRuleGroup.Rules != nil { + ruleNamesMap := proxyCollectRuleNames(out.ProxyRuleGroup.Rules) + var ruleNames []string + for name := range ruleNamesMap { + ruleNames = append(ruleNames, name) + } + + if len(ruleNames) > 0 { + input := networkfirewall.DeleteProxyRulesInput{ + ProxyRuleGroupArn: state.ProxyRuleGroupArn.ValueStringPointer(), + Rules: ruleNames, + } + + _, err = conn.DeleteProxyRules(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) + return + } + } + } +} + +func findProxyRulesByGroupARN(ctx context.Context, conn *networkfirewall.Client, groupARN string) (*networkfirewall.DescribeProxyRuleGroupOutput, error) { + input := networkfirewall.DescribeProxyRuleGroupInput{ + ProxyRuleGroupArn: aws.String(groupARN), + } + + out, err := conn.DescribeProxyRuleGroup(ctx, &input) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &tfretry.NotFoundError{ + LastError: err, + } + } + + return nil, err + } + + if out == nil || out.ProxyRuleGroup == nil { + return nil, &tfretry.NotFoundError{ + Message: "proxy rule group not found", + } + } + + return out, nil +} + +func flattenProxyRules(ctx context.Context, out *networkfirewall.DescribeProxyRuleGroupOutput, model *resourceProxyRulesExclusiveModel) diag.Diagnostics { + var diags diag.Diagnostics + + if out.ProxyRuleGroup == nil || out.ProxyRuleGroup.Rules == nil { + return diags + } + + rules := out.ProxyRuleGroup.Rules + + model.PostRESPONSE = proxyFlattenRulesForPhase(ctx, rules.PostRESPONSE, &diags) + model.PreDNS = proxyFlattenRulesForPhase(ctx, rules.PreDNS, &diags) + model.PreREQUEST = proxyFlattenRulesForPhase(ctx, rules.PreREQUEST, &diags) + + if out.ProxyRuleGroup.ProxyRuleGroupArn != nil { + model.ProxyRuleGroupArn = fwtypes.ARNValue(aws.ToString(out.ProxyRuleGroup.ProxyRuleGroupArn)) + } + + return diags +} + +// conditionsEqual compares only the conditions of two rules +func proxyRuleConditionsEqual(ctx context.Context, a, b proxyRuleModel) bool { + // Compare conditions count + if a.Conditions.IsNull() != b.Conditions.IsNull() { + return false + } + + if a.Conditions.IsNull() { + return true + } + + var aConditions, bConditions []proxyRuleConditionModel + a.Conditions.ElementsAs(ctx, &aConditions, false) + b.Conditions.ElementsAs(ctx, &bConditions, false) + + if len(aConditions) != len(bConditions) { + return false + } + + // Compare each condition + for i := range aConditions { + if aConditions[i].ConditionKey.ValueString() != bConditions[i].ConditionKey.ValueString() { + return false + } + if aConditions[i].ConditionOperator.ValueString() != bConditions[i].ConditionOperator.ValueString() { + return false + } + + var aValues, bValues []types.String + aConditions[i].ConditionValues.ElementsAs(ctx, &aValues, false) + bConditions[i].ConditionValues.ElementsAs(ctx, &bValues, false) + + if len(aValues) != len(bValues) { + return false + } + + for j := range aValues { + if aValues[j].ValueString() != bValues[j].ValueString() { + return false + } + } + } + + return true +} + +// processProxyRulesPhaseChanges compares state and plan rules for a given phase and populates +// the maps tracking which rules need to be deleted, updated, or recreated +func processProxyRulesPhaseChanges( + ctx context.Context, + stateRules, planRules []proxyRuleModel, + phaseName string, + rulesToDelete map[string]bool, + rulesToUpdate map[string]proxyRuleModel, + rulesToRecreate map[string]proxyRuleRecreateInfo, +) { + // Compare position by position + for i, planRule := range planRules { + planName := planRule.ProxyRuleName.ValueString() + + // Check if a rule exists at this position in state + if i < len(stateRules) { + stateRule := stateRules[i] + stateName := stateRule.ProxyRuleName.ValueString() + + // Check what kind of change this is + if planName == stateName { + // Same name at same position - check if attributes changed + if !proxyRuleConditionsEqual(ctx, stateRule, planRule) || + stateRule.Action.ValueEnum() != planRule.Action.ValueEnum() || + stateRule.Description.ValueString() != planRule.Description.ValueString() { + // Action, description, or conditions changed - can use UpdateProxyRule + rulesToUpdate[planName] = planRule + } + // If no changes, do nothing + } else { + // Different names at same position + // The old rule at this position needs to be deleted + rulesToDelete[stateName] = true + // The new rule needs to be created at this position + rulesToRecreate[planName] = proxyRuleRecreateInfo{ + rule: planRule, + position: int32(i), + phase: phaseName, + } + } + } else { + // Plan has more rules than state - this is a new rule + rulesToRecreate[planName] = proxyRuleRecreateInfo{ + rule: planRule, + position: int32(i), + phase: phaseName, + } + } + } + + // If state has more rules than plan, mark extras for deletion + for i := len(planRules); i < len(stateRules); i++ { + rulesToDelete[stateRules[i].ProxyRuleName.ValueString()] = true + } +} + +// proxyExpandRulesForPhase converts plan rules to AWS CreateProxyRule format with positions +func proxyExpandRulesForPhase(ctx context.Context, rulesList fwtypes.ListNestedObjectValueOf[proxyRuleModel], diags *diag.Diagnostics) []awstypes.CreateProxyRule { + if rulesList.IsNull() || rulesList.IsUnknown() { + return nil + } + + var ruleModels []proxyRuleModel + smerr.AddEnrich(ctx, diags, rulesList.ElementsAs(ctx, &ruleModels, false)) + if diags.HasError() { + return nil + } + + var rules []awstypes.CreateProxyRule + for i, ruleModel := range ruleModels { + var rule awstypes.CreateProxyRule + smerr.AddEnrich(ctx, diags, flex.Expand(ctx, ruleModel, &rule)) + if diags.HasError() { + return nil + } + insertPos := int32(i) + rule.InsertPosition = &insertPos + rules = append(rules, rule) + } + + return rules +} + +// proxyExtractRulesFromPhase converts AWS ProxyRule format to proxyRuleModel +func proxyExtractRulesFromPhase(ctx context.Context, awsRules []awstypes.ProxyRule, diags *diag.Diagnostics) []proxyRuleModel { + var ruleModels []proxyRuleModel + for _, rule := range awsRules { + var ruleModel proxyRuleModel + smerr.AddEnrich(ctx, diags, flex.Flatten(ctx, &rule, &ruleModel)) + if diags.HasError() { + return nil + } + ruleModels = append(ruleModels, ruleModel) + } + return ruleModels +} + +// proxyExtractPlanRules extracts rules from plan's ListNestedObjectValue +func proxyExtractPlanRules(ctx context.Context, rulesList fwtypes.ListNestedObjectValueOf[proxyRuleModel], diags *diag.Diagnostics) []proxyRuleModel { + if rulesList.IsNull() || rulesList.IsUnknown() { + return nil + } + + var ruleModels []proxyRuleModel + smerr.AddEnrich(ctx, diags, rulesList.ElementsAs(ctx, &ruleModels, false)) + return ruleModels +} + +// proxyFlattenRulesForPhase converts AWS rules to framework list format +func proxyFlattenRulesForPhase(ctx context.Context, awsRules []awstypes.ProxyRule, diags *diag.Diagnostics) fwtypes.ListNestedObjectValueOf[proxyRuleModel] { + if len(awsRules) == 0 { + return fwtypes.NewListNestedObjectValueOfNull[proxyRuleModel](ctx) + } + + var ruleModels []proxyRuleModel + for _, rule := range awsRules { + var ruleModel proxyRuleModel + diags.Append(flex.Flatten(ctx, &rule, &ruleModel)...) + if diags.HasError() { + return fwtypes.NewListNestedObjectValueOfNull[proxyRuleModel](ctx) + } + ruleModels = append(ruleModels, ruleModel) + } + + list, d := fwtypes.NewListNestedObjectValueOfValueSlice(ctx, ruleModels) + diags.Append(d...) + return list +} + +// proxyCollectRuleNames collects all rule names from all phases into a map +func proxyCollectRuleNames(rules *awstypes.ProxyRulesByRequestPhase) map[string]bool { + ruleNames := make(map[string]bool) + + for _, rule := range rules.PostRESPONSE { + if rule.ProxyRuleName != nil { + ruleNames[*rule.ProxyRuleName] = true + } + } + for _, rule := range rules.PreDNS { + if rule.ProxyRuleName != nil { + ruleNames[*rule.ProxyRuleName] = true + } + } + for _, rule := range rules.PreREQUEST { + if rule.ProxyRuleName != nil { + ruleNames[*rule.ProxyRuleName] = true + } + } + + return ruleNames +} + +// proxyBuildRuleMapByName builds a map of rules by name from current AWS state +func proxyBuildRuleMapByName(ctx context.Context, proxyRuleGroup *awstypes.ProxyRuleGroup, diags *diag.Diagnostics) map[string]proxyRuleModel { + rulesByName := make(map[string]proxyRuleModel) + + if proxyRuleGroup == nil || proxyRuleGroup.Rules == nil { + return rulesByName + } + + rules := proxyRuleGroup.Rules + + for _, rule := range rules.PostRESPONSE { + var rm proxyRuleModel + smerr.AddEnrich(ctx, diags, flex.Flatten(ctx, &rule, &rm)) + if diags.HasError() { + return nil + } + rulesByName[rm.ProxyRuleName.ValueString()] = rm + } + for _, rule := range rules.PreDNS { + var rm proxyRuleModel + smerr.AddEnrich(ctx, diags, flex.Flatten(ctx, &rule, &rm)) + if diags.HasError() { + return nil + } + rulesByName[rm.ProxyRuleName.ValueString()] = rm + } + for _, rule := range rules.PreREQUEST { + var rm proxyRuleModel + smerr.AddEnrich(ctx, diags, flex.Flatten(ctx, &rule, &rm)) + if diags.HasError() { + return nil + } + rulesByName[rm.ProxyRuleName.ValueString()] = rm + } + + return rulesByName +} + +type resourceProxyRulesExclusiveModel struct { + framework.WithRegionModel + ID types.String `tfsdk:"id"` + PostRESPONSE fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"post_response"` + PreDNS fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_dns"` + PreREQUEST fwtypes.ListNestedObjectValueOf[proxyRuleModel] `tfsdk:"pre_request"` + ProxyRuleGroupArn fwtypes.ARN `tfsdk:"proxy_rule_group_arn"` +} + +type proxyRuleModel struct { + Action fwtypes.StringEnum[awstypes.ProxyRulePhaseAction] `tfsdk:"action"` + Conditions fwtypes.ListNestedObjectValueOf[proxyRuleConditionModel] `tfsdk:"conditions"` + Description types.String `tfsdk:"description"` + ProxyRuleName types.String `tfsdk:"proxy_rule_name"` +} + +type proxyRuleConditionModel struct { + ConditionKey types.String `tfsdk:"condition_key"` + ConditionOperator types.String `tfsdk:"condition_operator"` + ConditionValues fwtypes.ListValueOf[types.String] `tfsdk:"condition_values"` +} + +// proxyRuleRecreateInfo holds information about a rule that needs to be recreated +type proxyRuleRecreateInfo struct { + rule proxyRuleModel + position int32 + phase string +} + +func (data *resourceProxyRulesExclusiveModel) setID() { + data.ID = data.ProxyRuleGroupArn.StringValue +} diff --git a/internal/service/networkfirewall/proxy_rules_test.go b/internal/service/networkfirewall/proxy_rules_exclusive_test.go similarity index 82% rename from internal/service/networkfirewall/proxy_rules_test.go rename to internal/service/networkfirewall/proxy_rules_exclusive_test.go index e7eb6ebaca11..6013920de328 100644 --- a/internal/service/networkfirewall/proxy_rules_test.go +++ b/internal/service/networkfirewall/proxy_rules_exclusive_test.go @@ -20,23 +20,23 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccNetworkFirewallProxyRules_basic(t *testing.T) { +func TestAccNetworkFirewallProxyRulesExclusive_basic(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_rules.test" + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" ruleGroupResourceName := "aws_networkfirewall_proxy_rule_group.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyRulesConfig_basic(rName), + Config: testAccProxyRulesExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v), resource.TestCheckResourceAttrPair(resourceName, "proxy_rule_group_arn", ruleGroupResourceName, names.AttrARN), // Pre-DNS phase resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), @@ -68,7 +68,7 @@ func TestAccNetworkFirewallProxyRules_basic(t *testing.T) { ), }, { - Config: testAccProxyRulesConfig_single(rName), + Config: testAccProxyRulesExclusiveConfig_single(rName), ResourceName: resourceName, ImportState: true, }, @@ -76,23 +76,23 @@ func TestAccNetworkFirewallProxyRules_basic(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRules_disappears(t *testing.T) { +func TestAccNetworkFirewallProxyRulesExclusive_disappears(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_rules.test" + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyRulesConfig_basic(rName), + Config: testAccProxyRulesExclusiveConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxyRules, resourceName), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v), + acctest.CheckFrameworkResourceDisappears(ctx, t, tfnetworkfirewall.ResourceProxyRulesExclusive, resourceName), ), ExpectNonEmptyPlan: true, ConfigPlanChecks: resource.ConfigPlanChecks{ @@ -105,22 +105,22 @@ func TestAccNetworkFirewallProxyRules_disappears(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRules_updateAdd(t *testing.T) { +func TestAccNetworkFirewallProxyRulesExclusive_updateAdd(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_rules.test" + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyRulesConfig_single(rName), + Config: testAccProxyRulesExclusiveConfig_single(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v1), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "0"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "0"), @@ -132,9 +132,9 @@ func TestAccNetworkFirewallProxyRules_updateAdd(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccProxyRulesConfig_add(rName), + Config: testAccProxyRulesExclusiveConfig_add(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v2), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "1"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), @@ -146,22 +146,22 @@ func TestAccNetworkFirewallProxyRules_updateAdd(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRules_updateModify(t *testing.T) { +func TestAccNetworkFirewallProxyRulesExclusive_updateModify(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_rules.test" + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyRulesConfig_basic(rName), + Config: testAccProxyRulesExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v1), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "ALLOW"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "amazonaws.com"), @@ -173,9 +173,9 @@ func TestAccNetworkFirewallProxyRules_updateModify(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccProxyRulesConfig_modified(rName), + Config: testAccProxyRulesExclusiveConfig_modified(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v2), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "DENY"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.#", "2"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "example.com"), @@ -186,22 +186,22 @@ func TestAccNetworkFirewallProxyRules_updateModify(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRules_updateRemove(t *testing.T) { +func TestAccNetworkFirewallProxyRulesExclusive_updateRemove(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_rules.test" + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyRulesConfig_basic(rName), + Config: testAccProxyRulesExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v1), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "1"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), @@ -213,9 +213,9 @@ func TestAccNetworkFirewallProxyRules_updateRemove(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccProxyRulesConfig_single(rName), + Config: testAccProxyRulesExclusiveConfig_single(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v2), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "0"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "0"), @@ -225,22 +225,22 @@ func TestAccNetworkFirewallProxyRules_updateRemove(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRules_multipleRulesPerPhase(t *testing.T) { +func TestAccNetworkFirewallProxyRulesExclusive_multipleRulesPerPhase(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_networkfirewall_proxy_rules.test" + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProxyRulesConfig_multiplePerPhase(rName), + Config: testAccProxyRulesExclusiveConfig_multiplePerPhase(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExists(ctx, resourceName, &v), + testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v), // Pre-DNS phase - 2 rules resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "2"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.proxy_rule_name", fmt.Sprintf("%s-predns-1", rName)), @@ -277,12 +277,12 @@ func TestAccNetworkFirewallProxyRules_multipleRulesPerPhase(t *testing.T) { }) } -func testAccCheckProxyRulesDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckProxyRulesExclusiveDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_networkfirewall_proxy_rules" { + if rs.Type != "aws_networkfirewall_proxy_rules_exclusive" { continue } @@ -310,7 +310,7 @@ func testAccCheckProxyRulesDestroy(ctx context.Context) resource.TestCheckFunc { } } -func testAccCheckProxyRulesExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { +func testAccCheckProxyRulesExclusiveExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -331,18 +331,19 @@ func testAccCheckProxyRulesExists(ctx context.Context, n string, v *networkfirew } } -func testAccProxyRulesConfig_basic(rName string) string { +func testAccProxyRulesExclusiveConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_networkfirewall_proxy_rule_group" "test" { name = %[1]q } -resource "aws_networkfirewall_proxy_rules" "test" { +resource "aws_networkfirewall_proxy_rules_exclusive" "test" { proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn pre_dns { proxy_rule_name = "%[1]s-predns" action = "ALLOW" + description = "%[1]s-predns-description" conditions { condition_key = "request:DestinationDomain" @@ -354,6 +355,7 @@ resource "aws_networkfirewall_proxy_rules" "test" { pre_request { proxy_rule_name = "%[1]s-prerequest" action = "DENY" + description = "%[1]s-prerequest-description" conditions { condition_key = "request:Http:Method" @@ -365,6 +367,7 @@ resource "aws_networkfirewall_proxy_rules" "test" { post_response { proxy_rule_name = "%[1]s-postresponse" action = "ALERT" + description = "%[1]s-postresponse-description" conditions { condition_key = "response:Http:StatusCode" @@ -376,13 +379,13 @@ resource "aws_networkfirewall_proxy_rules" "test" { `, rName) } -func testAccProxyRulesConfig_single(rName string) string { +func testAccProxyRulesExclusiveConfig_single(rName string) string { return fmt.Sprintf(` resource "aws_networkfirewall_proxy_rule_group" "test" { name = %[1]q } -resource "aws_networkfirewall_proxy_rules" "test" { +resource "aws_networkfirewall_proxy_rules_exclusive" "test" { proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn pre_dns { @@ -399,18 +402,19 @@ resource "aws_networkfirewall_proxy_rules" "test" { `, rName) } -func testAccProxyRulesConfig_modified(rName string) string { +func testAccProxyRulesExclusiveConfig_modified(rName string) string { return fmt.Sprintf(` resource "aws_networkfirewall_proxy_rule_group" "test" { name = %[1]q } -resource "aws_networkfirewall_proxy_rules" "test" { +resource "aws_networkfirewall_proxy_rules_exclusive" "test" { proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn pre_dns { proxy_rule_name = "%[1]s-predns" - action = "DENY" + action = "ALLOW" + description = "%[1]s-predns-description" conditions { condition_key = "request:DestinationDomain" @@ -433,6 +437,7 @@ resource "aws_networkfirewall_proxy_rules" "test" { post_response { proxy_rule_name = "%[1]s-postresponse" action = "ALERT" + description = "%[1]s-postresponse-description" conditions { condition_key = "response:Http:StatusCode" @@ -444,13 +449,13 @@ resource "aws_networkfirewall_proxy_rules" "test" { `, rName) } -func testAccProxyRulesConfig_add(rName string) string { +func testAccProxyRulesExclusiveConfig_add(rName string) string { return fmt.Sprintf(` resource "aws_networkfirewall_proxy_rule_group" "test" { name = %[1]q } -resource "aws_networkfirewall_proxy_rules" "test" { +resource "aws_networkfirewall_proxy_rules_exclusive" "test" { proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn pre_dns { @@ -489,13 +494,13 @@ resource "aws_networkfirewall_proxy_rules" "test" { `, rName) } -func testAccProxyRulesConfig_multiplePerPhase(rName string) string { +func testAccProxyRulesExclusiveConfig_multiplePerPhase(rName string) string { return fmt.Sprintf(` resource "aws_networkfirewall_proxy_rule_group" "test" { name = %[1]q } -resource "aws_networkfirewall_proxy_rules" "test" { +resource "aws_networkfirewall_proxy_rules_exclusive" "test" { proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.test.arn pre_dns { diff --git a/internal/service/networkfirewall/proxy_test.go b/internal/service/networkfirewall/proxy_test.go index f14bd5ee3d45..a6300def9954 100644 --- a/internal/service/networkfirewall/proxy_test.go +++ b/internal/service/networkfirewall/proxy_test.go @@ -85,7 +85,7 @@ func TestAccNetworkFirewallProxy_disappears(t *testing.T) { Config: testAccProxyConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckProxyExists(ctx, resourceName, &v), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkfirewall.ResourceProxy, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, t, tfnetworkfirewall.ResourceProxy, resourceName), ), ExpectNonEmptyPlan: true, ConfigPlanChecks: resource.ConfigPlanChecks{ @@ -360,7 +360,8 @@ data "aws_region" "current" {} # Create a root CA for TLS interception resource "aws_acmpca_certificate_authority" "test" { - type = "ROOT" + type = "ROOT" + usage_mode = "SHORT_LIVED_CERTIFICATE" certificate_authority_configuration { key_algorithm = "RSA_2048" @@ -378,6 +379,27 @@ resource "aws_acmpca_certificate_authority" "test" { } } +resource "aws_acmpca_certificate" "test" { + certificate_authority_arn = aws_acmpca_certificate_authority.test.arn + certificate_signing_request = aws_acmpca_certificate_authority.test.certificate_signing_request + signing_algorithm = "SHA512WITHRSA" + + template_arn = "arn:${data.aws_partition.current.partition}:acm-pca:::template/RootCACertificate/V1" + + validity { + type = "YEARS" + value = 1 + } +} + +resource "aws_acmpca_certificate_authority_certificate" "test" { + certificate_authority_arn = aws_acmpca_certificate_authority.test.arn + + certificate = aws_acmpca_certificate.test.certificate + certificate_chain = aws_acmpca_certificate.test.certificate_chain +} + + # Grant Network Firewall proxy permission to use the PCA resource "aws_acmpca_policy" "test" { resource_arn = aws_acmpca_certificate_authority.test.arn @@ -431,23 +453,20 @@ resource "aws_acmpca_policy" "test" { # Create RAM resource share for the PCA resource "aws_ram_resource_share" "test" { name = %[1]q - allow_external_principals = false + allow_external_principals = true + permission_arns = ["arn:aws:ram::aws:permission/AWSRAMSubordinateCACertificatePathLen0IssuanceCertificateAuthority"] tags = { Name = %[1]q } } - + # Associate the PCA with the RAM share -resource "aws_ram_resource_association" "test" { - resource_arn = aws_acmpca_certificate_authority.test.arn - resource_share_arn = aws_ram_resource_share.test.arn -} - -# Share the PCA with Network Firewall proxy service -resource "aws_ram_principal_association" "test" { - principal = "proxy.network-firewall.amazonaws.com" +resource "aws_ram_resource_share_associations_exclusive" "test" { + principals = ["proxy.network-firewall.amazonaws.com"] + resource_arns = [aws_acmpca_certificate_authority.test.arn] resource_share_arn = aws_ram_resource_share.test.arn + sources = [data.aws_caller_identity.current.account_id] } resource "aws_networkfirewall_proxy" "test" { @@ -469,12 +488,6 @@ resource "aws_networkfirewall_proxy" "test" { port = 443 type = "HTTPS" } - - depends_on = [ - aws_acmpca_certificate_authority.test, - aws_ram_resource_association.test, - aws_ram_principal_association.test, - ] } `, rName)) } diff --git a/internal/service/networkfirewall/service_package_gen.go b/internal/service/networkfirewall/service_package_gen.go index 70ea9cc9803e..99483b09e2fb 100644 --- a/internal/service/networkfirewall/service_package_gen.go +++ b/internal/service/networkfirewall/service_package_gen.go @@ -59,9 +59,9 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser }, }, { - Factory: newResourceProxyConfigurationRuleGroupsAttachmentResource, - TypeName: "aws_networkfirewall_proxy_configuration_rule_groups_attachment", - Name: "Proxy Configuration Rule Groups Attachment", + Factory: newResourceProxyConfigurationRuleGroupAttachmentsExclusive, + TypeName: "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive", + Name: "Proxy Configuration Rule Group Attachments Exclusive", Region: unique.Make(inttypes.ResourceRegionDefault()), Identity: inttypes.RegionalARNIdentityNamed("proxy_configuration_arn", inttypes.WithIdentityDuplicateAttrs(names.AttrID)), Import: inttypes.FrameworkImport{ @@ -82,9 +82,9 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser }, }, { - Factory: newResourceProxyRules, - TypeName: "aws_networkfirewall_proxy_rules", - Name: "Proxy Rules", + Factory: newResourceProxyRulesExclusive, + TypeName: "aws_networkfirewall_proxy_rules_exclusive", + Name: "Proxy Rules Exclusive", Region: unique.Make(inttypes.ResourceRegionDefault()), Identity: inttypes.RegionalARNIdentityNamed("proxy_rule_group_arn", inttypes.WithIdentityDuplicateAttrs(names.AttrID)), Import: inttypes.FrameworkImport{ diff --git a/website/docs/r/networkfirewall_proxy_configuration.html.markdown b/website/docs/r/networkfirewall_proxy_configuration.html.markdown index 611303a9c9c9..2fec1561ca30 100644 --- a/website/docs/r/networkfirewall_proxy_configuration.html.markdown +++ b/website/docs/r/networkfirewall_proxy_configuration.html.markdown @@ -5,16 +5,6 @@ page_title: "AWS: aws_networkfirewall_proxy_configuration" description: |- Manages an AWS Network Firewall Proxy Configuration. --- - # Resource: aws_networkfirewall_proxy_configuration diff --git a/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachment.html.markdown b/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachment.html.markdown deleted file mode 100644 index 50cb6dd5bccf..000000000000 --- a/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachment.html.markdown +++ /dev/null @@ -1,62 +0,0 @@ ---- -subcategory: "Network Firewall" -layout: "aws" -page_title: "AWS: aws_networkfirewall_proxy_configuration_rule_group_attachment" -description: |- - Manages an AWS Network Firewall Proxy Configuration Rule Group Attachment. ---- - -# Resource: aws_networkfirewall_proxy_configuration_rule_group_attachment - -Manages an AWS Network Firewall Proxy Configuration Rule Group Attachment. - -## Example Usage - -### Basic Usage - -```terraform -resource "aws_networkfirewall_proxy_configuration_rule_group_attachment" "example" { -} -``` - -## Argument Reference - -The following arguments are required: - -* `example_arg` - (Required) Brief description of the required argument. - -The following arguments are optional: - -* `optional_arg` - (Optional) Brief description of the optional argument. - -## Attribute Reference - -This resource exports the following attributes in addition to the arguments above: - -* `arn` - ARN of the Proxy Configuration Rule Group Attachment. -* `example_attribute` - Brief description of the attribute. - -## Timeouts - -[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): - -* `create` - (Default `60m`) -* `update` - (Default `180m`) -* `delete` - (Default `90m`) - -## Import - -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Configuration Rule Group Attachment using the `example_id_arg`. For example: - -```terraform -import { - to = aws_networkfirewall_proxy_configuration_rule_group_attachment.example - id = "proxy_configuration_rule_group_attachment-id-12345678" -} -``` - -Using `terraform import`, import Network Firewall Proxy Configuration Rule Group Attachment using the `example_id_arg`. For example: - -```console -% terraform import aws_networkfirewall_proxy_configuration_rule_group_attachment.example proxy_configuration_rule_group_attachment-id-12345678 -``` diff --git a/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachments_exclusive.html.markdown b/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachments_exclusive.html.markdown new file mode 100644 index 000000000000..c40b02ae0006 --- /dev/null +++ b/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachments_exclusive.html.markdown @@ -0,0 +1,115 @@ +--- +subcategory: "Network Firewall" +layout: "aws" +page_title: "AWS: aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" +description: |- + Manages an AWS Network Firewall Proxy Configuration Rule Group Attachments Exclusive resource. +--- + +# Resource: aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive + +Manages an AWS Network Firewall Proxy Configuration Rule Group Attachments Exclusive resource. This resource attaches proxy rule groups to a proxy configuration. + +~> **NOTE:** This resource requires an existing [`aws_networkfirewall_proxy_configuration`](networkfirewall_proxy_configuration.html) and [`aws_networkfirewall_proxy_rule_group`](networkfirewall_proxy_rule_group.html). + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_networkfirewall_proxy_configuration" "example" { + name = "example" + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +resource "aws_networkfirewall_proxy_rule_group" "example" { + name = "example" +} + +resource "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" "example" { + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.example.arn + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.example.name + } +} +``` + +### Multiple Rule Groups + +```terraform +resource "aws_networkfirewall_proxy_configuration" "example" { + name = "example" + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +resource "aws_networkfirewall_proxy_rule_group" "first" { + name = "first" +} + +resource "aws_networkfirewall_proxy_rule_group" "second" { + name = "second" +} + +resource "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" "example" { + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.example.arn + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.first.name + } + + rule_group { + proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.second.name + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `proxy_configuration_arn` - (Required) ARN of the proxy configuration to attach rule groups to. + +The following arguments are optional: + +* `rule_group` - (Optional) One or more rule group blocks. See [Rule Group](#rule-group) below. + +### Rule Group + +Each `rule_group` block supports the following: + +* `proxy_rule_group_name` - (Required) Name of the proxy rule group to attach. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - ARN of the Proxy Configuration. +* `update_token` - Token used for optimistic locking. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Configuration Rule Group Attachments Exclusive using the `proxy_configuration_arn`. For example: + +```terraform +import { + to = aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.example + id = "arn:aws:network-firewall:us-west-2:123456789012:proxy-configuration/example" +} +``` + +Using `terraform import`, import Network Firewall Proxy Configuration Rule Group Attachments Exclusive using the `proxy_configuration_arn`. For example: + +```console +% terraform import aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.example arn:aws:network-firewall:us-west-2:123456789012:proxy-configuration/example +``` diff --git a/website/docs/r/networkfirewall_proxy_rule_group.html.markdown b/website/docs/r/networkfirewall_proxy_rule_group.html.markdown index 4950ece85b1a..555a202fae9c 100644 --- a/website/docs/r/networkfirewall_proxy_rule_group.html.markdown +++ b/website/docs/r/networkfirewall_proxy_rule_group.html.markdown @@ -3,24 +3,14 @@ subcategory: "Network Firewall" layout: "aws" page_title: "AWS: aws_networkfirewall_proxy_rule_group" description: |- - Manages an AWS Network Firewall Proxy Rule Group. + Manages an AWS Network Firewall Proxy Rule Group resource. --- - # Resource: aws_networkfirewall_proxy_rule_group -Manages an AWS Network Firewall Proxy Rule Group. A proxy rule group is a container for proxy rules that can be referenced by a proxy configuration. +Manages an AWS Network Firewall Proxy Rule Group resource. A proxy rule group is a container for proxy rules that can be referenced by a proxy configuration. -~> **NOTE:** This resource creates an empty proxy rule group. Use the [`aws_networkfirewall_proxy_rules`](networkfirewall_proxy_rules.html) resource to add rules to the group. +~> **NOTE:** This resource creates an empty proxy rule group. Use the [`aws_networkfirewall_proxy_rules_exclusive`](networkfirewall_proxy_rules_exclusive.html) resource to add rules to the group. ## Example Usage diff --git a/website/docs/r/networkfirewall_proxy_rules.html.markdown b/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown similarity index 80% rename from website/docs/r/networkfirewall_proxy_rules.html.markdown rename to website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown index fc8bfd7b4bb0..1716fb1ec45b 100644 --- a/website/docs/r/networkfirewall_proxy_rules.html.markdown +++ b/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown @@ -1,24 +1,14 @@ --- subcategory: "Network Firewall" layout: "aws" -page_title: "AWS: aws_networkfirewall_proxy_rules" +page_title: "AWS: aws_networkfirewall_proxy_rules_exclusive" description: |- - Manages AWS Network Firewall Proxy Rules within a Proxy Rule Group. + Manages AWS Network Firewall Proxy Rules Exclusive within a Proxy Rule Group. --- - -# Resource: aws_networkfirewall_proxy_rules +# Resource: aws_networkfirewall_proxy_rules_exclusive -Manages AWS Network Firewall Proxy Rules within a Proxy Rule Group. Proxy rules define conditions and actions for HTTP/HTTPS traffic inspection across three request/response phases: PRE_DNS, PRE_REQUEST, and POST_RESPONSE. +Manages AWS Network Firewall Proxy Rules Exclusive within a Proxy Rule Group. Proxy rules define conditions and actions for HTTP/HTTPS traffic inspection across three request/response phases: PRE_DNS, PRE_REQUEST, and POST_RESPONSE. ~> **NOTE:** This resource requires an existing [`aws_networkfirewall_proxy_rule_group`](networkfirewall_proxy_rule_group.html). @@ -31,7 +21,7 @@ resource "aws_networkfirewall_proxy_rule_group" "example" { name = "example" } -resource "aws_networkfirewall_proxy_rules" "example" { +resource "aws_networkfirewall_proxy_rules_exclusive" "example" { proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.example.arn pre_dns { @@ -54,7 +44,7 @@ resource "aws_networkfirewall_proxy_rule_group" "example" { name = "example" } -resource "aws_networkfirewall_proxy_rules" "example" { +resource "aws_networkfirewall_proxy_rules_exclusive" "example" { proxy_rule_group_arn = aws_networkfirewall_proxy_rule_group.example.arn # DNS phase rules @@ -111,7 +101,7 @@ resource "aws_networkfirewall_proxy_rule_group" "example" { name = "example" } -resource "aws_networkfirewall_proxy_rules" "example" { +resource "aws_networkfirewall_proxy_rules_exclusive" "example" { proxy_rule_group_name = aws_networkfirewall_proxy_rule_group.example.name pre_dns { @@ -169,19 +159,19 @@ This resource exports the following attributes in addition to the arguments abov ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Rules using the `proxy_rule_group_arn,rule_name1[,rule_name2,...]`. For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Rules Exclusive using the `proxy_rule_group_arn,rule_name1[,rule_name2,...]`. For example: ```terraform import { - to = aws_networkfirewall_proxy_rules.example + to = aws_networkfirewall_proxy_rules_exclusive.example id = "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example,allow-example-com,block-malicious-domains" } ``` -Using `terraform import`, import Network Firewall Proxy Rules using the `proxy_rule_group_arn,rule_name1[,rule_name2,...]`. For example: +Using `terraform import`, import Network Firewall Proxy Rules Exclusive using the `proxy_rule_group_arn,rule_name1[,rule_name2,...]`. For example: ```console -% terraform import aws_networkfirewall_proxy_rules.example "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example,allow-example-com,block-malicious-domains" +% terraform import aws_networkfirewall_proxy_rules_exclusive.example "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example,allow-example-com,block-malicious-domains" ``` ~> **NOTE:** When importing, specify the proxy rule group ARN followed by a comma-separated list of rule names to import. The import will fetch only the specified rules from the rule group. From 0b758bb35a43458855ebd2b74c0aaf157a207942 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Sat, 24 Jan 2026 13:03:32 +1100 Subject: [PATCH 13/19] fixed TLSIntercept testing --- internal/service/networkfirewall/proxy_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/service/networkfirewall/proxy_test.go b/internal/service/networkfirewall/proxy_test.go index a6300def9954..0aa4911f73fd 100644 --- a/internal/service/networkfirewall/proxy_test.go +++ b/internal/service/networkfirewall/proxy_test.go @@ -361,7 +361,6 @@ data "aws_region" "current" {} # Create a root CA for TLS interception resource "aws_acmpca_certificate_authority" "test" { type = "ROOT" - usage_mode = "SHORT_LIVED_CERTIFICATE" certificate_authority_configuration { key_algorithm = "RSA_2048" @@ -467,6 +466,15 @@ resource "aws_ram_resource_share_associations_exclusive" "test" { resource_arns = [aws_acmpca_certificate_authority.test.arn] resource_share_arn = aws_ram_resource_share.test.arn sources = [data.aws_caller_identity.current.account_id] + + lifecycle { + ignore_changes = [ + resource_arns + ] + replace_triggered_by = [ + aws_acmpca_certificate_authority.test + ] + } } resource "aws_networkfirewall_proxy" "test" { From 444778745a27e7422194c5ea69a22ac6c7daa76d Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Sun, 15 Mar 2026 15:27:54 +1100 Subject: [PATCH 14/19] added logging and additional fixes and improvements --- internal/service/networkfirewall/proxy.go | 3 +- .../networkfirewall/proxy_configuration.go | 5 +- ...ration_rule_group_attachments_exclusive.go | 11 +- ...n_rule_group_attachments_exclusive_test.go | 32 ++-- .../proxy_configuration_test.go | 20 +- .../networkfirewall/proxy_rule_group.go | 22 ++- .../networkfirewall/proxy_rule_group_test.go | 20 +- .../networkfirewall/proxy_rules_exclusive.go | 11 +- .../proxy_rules_exclusive_test.go | 38 ++-- .../networkfirewall/proxy_serial_test.go | 52 +++++ .../service/networkfirewall/proxy_test.go | 150 +++++++++++++-- internal/service/networkfirewall/sweep.go | 146 ++++++++++++++ .../r/networkfirewall_proxy.html.markdown | 179 ++++++++++++++++-- ...rewall_proxy_rules_exclusive.html.markdown | 10 +- 14 files changed, 587 insertions(+), 112 deletions(-) create mode 100644 internal/service/networkfirewall/proxy_serial_test.go diff --git a/internal/service/networkfirewall/proxy.go b/internal/service/networkfirewall/proxy.go index ab605be34cbc..2fb2fa15bf99 100644 --- a/internal/service/networkfirewall/proxy.go +++ b/internal/service/networkfirewall/proxy.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall @@ -40,6 +40,7 @@ import ( // @Tags(identifierAttribute="arn") // @ArnIdentity(identityDuplicateAttributes="id") // @Testing(hasNoPreExistingResource=true) +// @Testing(preIdentityVersion="v5.100.0") func newResourceProxy(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxy{} diff --git a/internal/service/networkfirewall/proxy_configuration.go b/internal/service/networkfirewall/proxy_configuration.go index 27c11d4c730a..98d463caa5bc 100644 --- a/internal/service/networkfirewall/proxy_configuration.go +++ b/internal/service/networkfirewall/proxy_configuration.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall @@ -36,6 +36,7 @@ import ( // @ArnIdentity(identityDuplicateAttributes="id") // @ArnFormat("proxy-configuration/{name}") // @Testing(hasNoPreExistingResource=true) +// @Testing(preIdentityVersion="v5.100.0") func newResourceProxyConfiguration(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxyConfiguration{} @@ -180,7 +181,7 @@ func (r *resourceProxyConfiguration) Update(ctx context.Context, req resource.Up return } - diff, d := flex.Diff(ctx, plan, state) + diff, d := flex.Diff(ctx, plan, state, flex.WithIgnoredField("Tags"), flex.WithIgnoredField("TagsAll")) smerr.AddEnrich(ctx, &resp.Diagnostics, d) if resp.Diagnostics.HasError() { return diff --git a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go index cc75345893b5..0b2cc29158ee 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall @@ -34,25 +34,18 @@ import ( // @FrameworkResource("aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive", name="Proxy Configuration Rule Group Attachments Exclusive") // @ArnIdentity("proxy_configuration_arn",identityDuplicateAttributes="id") // @Testing(hasNoPreExistingResource=true) +// @Testing(preIdentityVersion="v5.100.0") func newResourceProxyConfigurationRuleGroupAttachmentsExclusive(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxyConfigurationRuleGroupAttachmentsExclusive{} return r, nil } -const ( - ResNameProxyConfigurationRuleGroupAttachmentsExclusive = "Proxy Configuration Rule Group Attachments Exclusive" -) - type resourceProxyConfigurationRuleGroupAttachmentsExclusive struct { framework.ResourceWithModel[proxyConfigurationRuleGroupAttachmentModel] framework.WithImportByIdentity } -func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" -} - func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ diff --git a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go index 742eb449d6ac..45f141afe15f 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall_test @@ -23,7 +23,9 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_basic(t *testing.T) { +func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_basic(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -31,7 +33,7 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_basic proxyConfigResourceName := "aws_networkfirewall_proxy_configuration.test" ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -58,13 +60,15 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_basic }) } -func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_disappears(t *testing.T) { +func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_disappears(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -82,14 +86,16 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_disap }) } -func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateAdd(t *testing.T) { +func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateAdd(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" ruleGroup3ResourceName := "aws_networkfirewall_proxy_rule_group.test3" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -120,13 +126,15 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat }) } -func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateRemove(t *testing.T) { +func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateRemove(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -156,7 +164,9 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat }) } -func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateReorder(t *testing.T) { +func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateReorder(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -164,7 +174,7 @@ func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" ruleGroup2ResourceName := "aws_networkfirewall_proxy_rule_group.test2" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/networkfirewall/proxy_configuration_test.go b/internal/service/networkfirewall/proxy_configuration_test.go index 6636d1b1b90c..92c40fb122c0 100644 --- a/internal/service/networkfirewall/proxy_configuration_test.go +++ b/internal/service/networkfirewall/proxy_configuration_test.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall_test @@ -21,13 +21,15 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccNetworkFirewallProxyConfiguration_basic(t *testing.T) { +func testAccNetworkFirewallProxyConfiguration_basic(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -57,13 +59,15 @@ func TestAccNetworkFirewallProxyConfiguration_basic(t *testing.T) { }) } -func TestAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { +func testAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -86,13 +90,15 @@ func TestAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { }) } -func TestAccNetworkFirewallProxyConfiguration_tags(t *testing.T) { +func testAccNetworkFirewallProxyConfiguration_tags(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/networkfirewall/proxy_rule_group.go b/internal/service/networkfirewall/proxy_rule_group.go index 6569e951517a..19569d5721b2 100644 --- a/internal/service/networkfirewall/proxy_rule_group.go +++ b/internal/service/networkfirewall/proxy_rule_group.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall @@ -31,25 +31,18 @@ import ( // @ArnIdentity(identityDuplicateAttributes="id") // @ArnFormat("proxy-rule-group/{name}") // @Testing(hasNoPreExistingResource=true) +// @Testing(preIdentityVersion="v5.100.0") func newResourceProxyRuleGroup(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxyRuleGroup{} return r, nil } -const ( - ResNameProxyRuleGroup = "Proxy Rule Group" -) - type resourceProxyRuleGroup struct { framework.ResourceWithModel[resourceProxyRuleGroupModel] framework.WithImportByIdentity } -func (r *resourceProxyRuleGroup) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = "aws_networkfirewall_proxy_rule_group" -} - func (r *resourceProxyRuleGroup) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ @@ -160,6 +153,17 @@ func (r *resourceProxyRuleGroup) Read(ctx context.Context, req resource.ReadRequ resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } +func (r *resourceProxyRuleGroup) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan resourceProxyRuleGroupModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + // Tags are updated via the service package framework; only state sync is needed here. + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + func (r *resourceProxyRuleGroup) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { conn := r.Meta().NetworkFirewallClient(ctx) diff --git a/internal/service/networkfirewall/proxy_rule_group_test.go b/internal/service/networkfirewall/proxy_rule_group_test.go index f7e61c8ff2e1..ad72acc78597 100644 --- a/internal/service/networkfirewall/proxy_rule_group_test.go +++ b/internal/service/networkfirewall/proxy_rule_group_test.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall_test @@ -21,13 +21,15 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccNetworkFirewallProxyRuleGroup_basic(t *testing.T) { +func testAccNetworkFirewallProxyRuleGroup_basic(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -53,13 +55,15 @@ func TestAccNetworkFirewallProxyRuleGroup_basic(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRuleGroup_disappears(t *testing.T) { +func testAccNetworkFirewallProxyRuleGroup_disappears(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -82,13 +86,15 @@ func TestAccNetworkFirewallProxyRuleGroup_disappears(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRuleGroup_tags(t *testing.T) { +func testAccNetworkFirewallProxyRuleGroup_tags(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/networkfirewall/proxy_rules_exclusive.go b/internal/service/networkfirewall/proxy_rules_exclusive.go index 0f26a8a50dcb..67fd7f28528d 100644 --- a/internal/service/networkfirewall/proxy_rules_exclusive.go +++ b/internal/service/networkfirewall/proxy_rules_exclusive.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall @@ -33,25 +33,18 @@ import ( // @ArnIdentity("proxy_rule_group_arn",identityDuplicateAttributes="id") // @ArnFormat("proxy-rule-group/{name}") // @Testing(hasNoPreExistingResource=true) +// @Testing(preIdentityVersion="v5.100.0") func newResourceProxyRulesExclusive(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceProxyRulesExclusive{} return r, nil } -const ( - ResNameProxyRulesExclusive = "Proxy Rules Exclusive" -) - type resourceProxyRulesExclusive struct { framework.ResourceWithModel[resourceProxyRulesExclusiveModel] framework.WithImportByIdentity } -func (r *resourceProxyRulesExclusive) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = "aws_networkfirewall_proxy_rules_exclusive" -} - func (r *resourceProxyRulesExclusive) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ diff --git a/internal/service/networkfirewall/proxy_rules_exclusive_test.go b/internal/service/networkfirewall/proxy_rules_exclusive_test.go index 6013920de328..1d8e111879d0 100644 --- a/internal/service/networkfirewall/proxy_rules_exclusive_test.go +++ b/internal/service/networkfirewall/proxy_rules_exclusive_test.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall_test @@ -20,14 +20,16 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccNetworkFirewallProxyRulesExclusive_basic(t *testing.T) { +func testAccNetworkFirewallProxyRulesExclusive_basic(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" ruleGroupResourceName := "aws_networkfirewall_proxy_rule_group.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -76,13 +78,15 @@ func TestAccNetworkFirewallProxyRulesExclusive_basic(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRulesExclusive_disappears(t *testing.T) { +func testAccNetworkFirewallProxyRulesExclusive_disappears(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -105,13 +109,15 @@ func TestAccNetworkFirewallProxyRulesExclusive_disappears(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRulesExclusive_updateAdd(t *testing.T) { +func testAccNetworkFirewallProxyRulesExclusive_updateAdd(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -146,13 +152,15 @@ func TestAccNetworkFirewallProxyRulesExclusive_updateAdd(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRulesExclusive_updateModify(t *testing.T) { +func testAccNetworkFirewallProxyRulesExclusive_updateModify(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -186,13 +194,15 @@ func TestAccNetworkFirewallProxyRulesExclusive_updateModify(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRulesExclusive_updateRemove(t *testing.T) { +func testAccNetworkFirewallProxyRulesExclusive_updateRemove(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -225,13 +235,15 @@ func TestAccNetworkFirewallProxyRulesExclusive_updateRemove(t *testing.T) { }) } -func TestAccNetworkFirewallProxyRulesExclusive_multipleRulesPerPhase(t *testing.T) { +func testAccNetworkFirewallProxyRulesExclusive_multipleRulesPerPhase(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, diff --git a/internal/service/networkfirewall/proxy_serial_test.go b/internal/service/networkfirewall/proxy_serial_test.go new file mode 100644 index 000000000000..967a7c7e5eff --- /dev/null +++ b/internal/service/networkfirewall/proxy_serial_test.go @@ -0,0 +1,52 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +// Network Firewall Proxy resources are in preview and have strict concurrency limits, +// so all proxy acceptance tests must run serially. +func TestAccNetworkFirewallProxy_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]map[string]func(t *testing.T){ + "Proxy": { + acctest.CtBasic: testAccNetworkFirewallProxy_basic, + acctest.CtDisappears: testAccNetworkFirewallProxy_disappears, + "tlsInterceptEnabled": testAccNetworkFirewallProxy_tlsInterceptEnabled, + "logging": testAccNetworkFirewallProxy_logging, + }, + "ProxyConfiguration": { + acctest.CtBasic: testAccNetworkFirewallProxyConfiguration_basic, + acctest.CtDisappears: testAccNetworkFirewallProxyConfiguration_disappears, + "tags": testAccNetworkFirewallProxyConfiguration_tags, + }, + "ProxyRuleGroup": { + acctest.CtBasic: testAccNetworkFirewallProxyRuleGroup_basic, + acctest.CtDisappears: testAccNetworkFirewallProxyRuleGroup_disappears, + "tags": testAccNetworkFirewallProxyRuleGroup_tags, + }, + "ProxyRulesExclusive": { + acctest.CtBasic: testAccNetworkFirewallProxyRulesExclusive_basic, + acctest.CtDisappears: testAccNetworkFirewallProxyRulesExclusive_disappears, + "updateAdd": testAccNetworkFirewallProxyRulesExclusive_updateAdd, + "updateModify": testAccNetworkFirewallProxyRulesExclusive_updateModify, + "updateRemove": testAccNetworkFirewallProxyRulesExclusive_updateRemove, + "multipleRulesPerPhase": testAccNetworkFirewallProxyRulesExclusive_multipleRulesPerPhase, + }, + "ProxyConfigurationRuleGroupAttachmentsExclusive": { + acctest.CtBasic: testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_basic, + acctest.CtDisappears: testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_disappears, + "updateAdd": testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateAdd, + "updateRemove": testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateRemove, + "updateReorder": testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updateReorder, + }, + } + + acctest.RunSerialTests2Levels(t, testCases, 0) +} diff --git a/internal/service/networkfirewall/proxy_test.go b/internal/service/networkfirewall/proxy_test.go index 0aa4911f73fd..d544324f3a2a 100644 --- a/internal/service/networkfirewall/proxy_test.go +++ b/internal/service/networkfirewall/proxy_test.go @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2014, 2025 +// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: MPL-2.0 package networkfirewall_test @@ -21,17 +21,15 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccNetworkFirewallProxy_basic(t *testing.T) { - ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } +func testAccNetworkFirewallProxy_basic(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -65,17 +63,15 @@ func TestAccNetworkFirewallProxy_basic(t *testing.T) { }) } -func TestAccNetworkFirewallProxy_disappears(t *testing.T) { - ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } +func testAccNetworkFirewallProxy_disappears(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -98,18 +94,16 @@ func TestAccNetworkFirewallProxy_disappears(t *testing.T) { }) } -func TestAccNetworkFirewallProxy_tlsInterceptEnabled(t *testing.T) { - ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } +func testAccNetworkFirewallProxy_tlsInterceptEnabled(t *testing.T) { + t.Helper() + ctx := acctest.Context(t) var v networkfirewall.DescribeProxyOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy.test" pcaResourceName := "aws_acmpca_certificate_authority.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -145,6 +139,45 @@ func TestAccNetworkFirewallProxy_tlsInterceptEnabled(t *testing.T) { }) } +func testAccNetworkFirewallProxy_logging(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProxyConfig_logging(rName), + Check: resource.ComposeAggregateTestCheckFunc( + // CloudWatch Logs delivery source + resource.TestCheckResourceAttr("aws_cloudwatch_log_delivery_source.cwl", "log_type", "ALERT_LOGS"), + resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery_source.cwl", "resource_arn", "aws_networkfirewall_proxy.test", names.AttrARN), + // CloudWatch Logs delivery destination + resource.TestCheckResourceAttr("aws_cloudwatch_log_delivery_destination.cwl", "delivery_destination_type", "CWL"), + // CloudWatch Logs delivery + resource.TestCheckResourceAttrSet("aws_cloudwatch_log_delivery.cwl", names.AttrID), + resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery.cwl", "delivery_source_name", "aws_cloudwatch_log_delivery_source.cwl", names.AttrName), + resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery.cwl", "delivery_destination_arn", "aws_cloudwatch_log_delivery_destination.cwl", names.AttrARN), + // S3 delivery source + resource.TestCheckResourceAttr("aws_cloudwatch_log_delivery_source.s3", "log_type", "ALLOW_LOGS"), + resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery_source.s3", "resource_arn", "aws_networkfirewall_proxy.test", names.AttrARN), + // S3 delivery destination + resource.TestCheckResourceAttr("aws_cloudwatch_log_delivery_destination.s3", "delivery_destination_type", "S3"), + // S3 delivery + resource.TestCheckResourceAttrSet("aws_cloudwatch_log_delivery.s3", names.AttrID), + resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery.s3", "delivery_source_name", "aws_cloudwatch_log_delivery_source.s3", names.AttrName), + resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery.s3", "delivery_destination_arn", "aws_cloudwatch_log_delivery_destination.s3", names.AttrARN), + ), + }, + }, + }) +} + func testAccCheckProxyDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) @@ -347,6 +380,85 @@ resource "aws_networkfirewall_proxy" "test" { `, rName)) } +func testAccProxyConfig_logging(rName string) string { + return acctest.ConfigCompose( + testAccProxyConfig_baseVPC(rName), + testAccProxyConfig_baseProxyConfiguration(rName), + fmt.Sprintf(` +resource "aws_networkfirewall_proxy" "test" { + name = %[1]q + nat_gateway_id = aws_nat_gateway.test.id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + tls_intercept_properties { + tls_intercept_mode = "DISABLED" + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } +} + +# CloudWatch Logs delivery + +resource "aws_cloudwatch_log_group" "test" { + name = %[1]q + retention_in_days = 7 +} + +resource "aws_cloudwatch_log_delivery_source" "cwl" { + name = "%[1]s-cwl" + log_type = "ALERT_LOGS" + resource_arn = aws_networkfirewall_proxy.test.arn +} + +resource "aws_cloudwatch_log_delivery_destination" "cwl" { + name = "%[1]s-cwl" + + delivery_destination_configuration { + destination_resource_arn = aws_cloudwatch_log_group.test.arn + } +} + +resource "aws_cloudwatch_log_delivery" "cwl" { + delivery_source_name = aws_cloudwatch_log_delivery_source.cwl.name + delivery_destination_arn = aws_cloudwatch_log_delivery_destination.cwl.arn +} + +# S3 delivery + +resource "aws_s3_bucket" "test" { + bucket = %[1]q + force_destroy = true +} + +resource "aws_cloudwatch_log_delivery_source" "s3" { + name = "%[1]s-s3" + log_type = "ALLOW_LOGS" + resource_arn = aws_networkfirewall_proxy.test.arn +} + +resource "aws_cloudwatch_log_delivery_destination" "s3" { + name = "%[1]s-s3" + + delivery_destination_configuration { + destination_resource_arn = aws_s3_bucket.test.arn + } +} + +resource "aws_cloudwatch_log_delivery" "s3" { + delivery_source_name = aws_cloudwatch_log_delivery_source.s3.name + delivery_destination_arn = aws_cloudwatch_log_delivery_destination.s3.arn +} +`, rName)) +} + func testAccProxyConfig_tlsInterceptEnabled(rName string) string { return acctest.ConfigCompose( testAccProxyConfig_baseVPC(rName), diff --git a/internal/service/networkfirewall/sweep.go b/internal/service/networkfirewall/sweep.go index 03fd9aaaa17a..229fa761ccae 100644 --- a/internal/service/networkfirewall/sweep.go +++ b/internal/service/networkfirewall/sweep.go @@ -12,9 +12,32 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/sweep" "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv2" + sweepfw "github.com/hashicorp/terraform-provider-aws/internal/sweep/framework" + "github.com/hashicorp/terraform-provider-aws/names" ) func RegisterSweepers() { + resource.AddTestSweepers("aws_networkfirewall_proxy", &resource.Sweeper{ + Name: "aws_networkfirewall_proxy", + F: sweepProxies, + Dependencies: []string{ + "aws_networkfirewall_proxy_configuration", + }, + }) + + resource.AddTestSweepers("aws_networkfirewall_proxy_configuration", &resource.Sweeper{ + Name: "aws_networkfirewall_proxy_configuration", + F: sweepProxyConfigurations, + Dependencies: []string{ + "aws_networkfirewall_proxy_rule_group", + }, + }) + + resource.AddTestSweepers("aws_networkfirewall_proxy_rule_group", &resource.Sweeper{ + Name: "aws_networkfirewall_proxy_rule_group", + F: sweepProxyRuleGroups, + }) + resource.AddTestSweepers("aws_networkfirewall_firewall_policy", &resource.Sweeper{ Name: "aws_networkfirewall_firewall_policy", F: sweepFirewallPolicies, @@ -45,6 +68,129 @@ func RegisterSweepers() { }) } +func sweepProxies(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("getting client: %w", err) + } + conn := client.NetworkFirewallClient(ctx) + input := &networkfirewall.ListProxiesInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := networkfirewall.NewListProxiesPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping NetworkFirewall Proxy sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing NetworkFirewall Proxies (%s): %w", region, err) + } + + for _, v := range page.Proxies { + id := aws.ToString(v.Arn) + log.Printf("[INFO] Deleting NetworkFirewall Proxy: %s", id) + sweepResources = append(sweepResources, sweepfw.NewSweepResource(newResourceProxy, client, + sweepfw.NewAttribute(names.AttrID, id), + )) + } + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping NetworkFirewall Proxies (%s): %w", region, err) + } + + return nil +} + +func sweepProxyConfigurations(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("getting client: %w", err) + } + conn := client.NetworkFirewallClient(ctx) + input := &networkfirewall.ListProxyConfigurationsInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := networkfirewall.NewListProxyConfigurationsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping NetworkFirewall Proxy Configuration sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing NetworkFirewall Proxy Configurations (%s): %w", region, err) + } + + for _, v := range page.ProxyConfigurations { + id := aws.ToString(v.Arn) + log.Printf("[INFO] Deleting NetworkFirewall Proxy Configuration: %s", id) + sweepResources = append(sweepResources, sweepfw.NewSweepResource(newResourceProxyConfiguration, client, + sweepfw.NewAttribute(names.AttrID, id), + )) + } + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping NetworkFirewall Proxy Configurations (%s): %w", region, err) + } + + return nil +} + +func sweepProxyRuleGroups(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("getting client: %w", err) + } + conn := client.NetworkFirewallClient(ctx) + input := &networkfirewall.ListProxyRuleGroupsInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := networkfirewall.NewListProxyRuleGroupsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping NetworkFirewall Proxy Rule Group sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing NetworkFirewall Proxy Rule Groups (%s): %w", region, err) + } + + for _, v := range page.ProxyRuleGroups { + id := aws.ToString(v.Arn) + log.Printf("[INFO] Deleting NetworkFirewall Proxy Rule Group: %s", id) + sweepResources = append(sweepResources, sweepfw.NewSweepResource(newResourceProxyRuleGroup, client, + sweepfw.NewAttribute(names.AttrID, id), + )) + } + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping NetworkFirewall Proxy Rule Groups (%s): %w", region, err) + } + + return nil +} + func sweepFirewallPolicies(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) diff --git a/website/docs/r/networkfirewall_proxy.html.markdown b/website/docs/r/networkfirewall_proxy.html.markdown index 1fef61ed1cca..97ef780ad4aa 100644 --- a/website/docs/r/networkfirewall_proxy.html.markdown +++ b/website/docs/r/networkfirewall_proxy.html.markdown @@ -5,27 +5,141 @@ page_title: "AWS: aws_networkfirewall_proxy" description: |- Manages an AWS Network Firewall Proxy. --- - # Resource: aws_networkfirewall_proxy Manages an AWS Network Firewall Proxy. +~> **NOTE:** This resource is in preview and may change before general availability. + ## Example Usage ### Basic Usage ```terraform resource "aws_networkfirewall_proxy" "example" { + name = "example" + nat_gateway_id = aws_nat_gateway.example.id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.example.arn + + tls_intercept_properties { + tls_intercept_mode = "DISABLED" + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } +} +``` + +### With TLS Interception Enabled + +```terraform +resource "aws_networkfirewall_proxy" "example" { + name = "example" + nat_gateway_id = aws_nat_gateway.example.id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.example.arn + + tls_intercept_properties { + tls_intercept_mode = "ENABLED" + pca_arn = aws_acmpca_certificate_authority.example.arn + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } + + tags = { + Name = "example" + } +} +``` + +### With Logging + +```terraform +resource "aws_networkfirewall_proxy" "example" { + name = "example" + nat_gateway_id = aws_nat_gateway.example.id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.example.arn + + tls_intercept_properties { + tls_intercept_mode = "DISABLED" + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } +} + +# CloudWatch Logs delivery + +resource "aws_cloudwatch_log_group" "example" { + name = "example" + retention_in_days = 7 +} + +resource "aws_cloudwatch_log_delivery_source" "cwl" { + name = "example-cwl" + log_type = "ALERT_LOGS" + resource_arn = aws_networkfirewall_proxy.example.arn +} + +resource "aws_cloudwatch_log_delivery_destination" "cwl" { + name = "example-cwl" + + delivery_destination_configuration { + destination_resource_arn = aws_cloudwatch_log_group.example.arn + } +} + +resource "aws_cloudwatch_log_delivery" "cwl" { + delivery_source_name = aws_cloudwatch_log_delivery_source.cwl.name + delivery_destination_arn = aws_cloudwatch_log_delivery_destination.cwl.arn +} + +# S3 delivery + +resource "aws_s3_bucket" "example" { + bucket = "example" + force_destroy = true +} + +resource "aws_cloudwatch_log_delivery_source" "s3" { + name = "example-s3" + log_type = "ALLOW_LOGS" + resource_arn = aws_networkfirewall_proxy.example.arn +} + +resource "aws_cloudwatch_log_delivery_destination" "s3" { + name = "example-s3" + + delivery_destination_configuration { + destination_resource_arn = aws_s3_bucket.example.arn + } +} + +resource "aws_cloudwatch_log_delivery" "s3" { + delivery_source_name = aws_cloudwatch_log_delivery_source.s3.name + delivery_destination_arn = aws_cloudwatch_log_delivery_destination.s3.arn } ``` @@ -33,40 +147,67 @@ resource "aws_networkfirewall_proxy" "example" { The following arguments are required: -* `example_arg` - (Required) Brief description of the required argument. +* `name` - (Required, Forces new resource) Descriptive name of the proxy. +* `nat_gateway_id` - (Required, Forces new resource) ID of the NAT gateway to associate with the proxy. +* `tls_intercept_properties` - (Required) TLS interception properties block. See [TLS Intercept Properties](#tls-intercept-properties) below. The following arguments are optional: -* `optional_arg` - (Optional) Brief description of the optional argument. +* `listener_properties` - (Optional) One or more listener properties blocks defining the ports and protocols the proxy listens on. See [Listener Properties](#listener-properties) below. +* `proxy_configuration_arn` - (Optional, Forces new resource) ARN of the proxy configuration to use. Exactly one of `proxy_configuration_arn` or `proxy_configuration_name` must be provided. +* `proxy_configuration_name` - (Optional, Forces new resource) Name of the proxy configuration to use. Exactly one of `proxy_configuration_arn` or `proxy_configuration_name` must be provided. +* `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +### TLS Intercept Properties + +The `tls_intercept_properties` block supports the following: + +* `tls_intercept_mode` - (Optional) TLS interception mode. Valid values: `ENABLED`, `DISABLED`. +* `pca_arn` - (Optional) ARN of the AWS Private Certificate Authority (PCA) used for TLS interception. Required when `tls_intercept_mode` is `ENABLED`. + +### Listener Properties + +Each `listener_properties` block supports the following: + +* `port` - (Required) Port number the proxy listens on. +* `type` - (Required) Protocol type. Valid values: `HTTP`, `HTTPS`. ## Attribute Reference This resource exports the following attributes in addition to the arguments above: * `arn` - ARN of the Proxy. -* `example_attribute` - Brief description of the attribute. +* `create_time` - Creation timestamp of the proxy. +* `id` - ARN of the Proxy (deprecated, use `arn`). +* `private_dns_name` - Private DNS name assigned to the proxy. +* `proxy_configuration_arn` - ARN of the proxy configuration (populated when `proxy_configuration_name` is used). +* `proxy_configuration_name` - Name of the proxy configuration (populated when `proxy_configuration_arn` is used). +* `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). +* `update_time` - Last update timestamp of the proxy. +* `update_token` - Token for optimistic locking, required for update operations. +* `vpc_endpoint_service_name` - VPC endpoint service name for the proxy. ## Timeouts [Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): * `create` - (Default `60m`) -* `update` - (Default `180m`) -* `delete` - (Default `90m`) +* `update` - (Default `60m`) +* `delete` - (Default `60m`) ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy using the `example_id_arg`. For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy using the `arn`. For example: ```terraform import { to = aws_networkfirewall_proxy.example - id = "proxy-id-12345678" + id = "arn:aws:network-firewall:us-west-2:123456789012:proxy/example" } ``` -Using `terraform import`, import Network Firewall Proxy using the `example_id_arg`. For example: +Using `terraform import`, import Network Firewall Proxy using the `arn`. For example: ```console -% terraform import aws_networkfirewall_proxy.example proxy-id-12345678 +% terraform import aws_networkfirewall_proxy.example arn:aws:network-firewall:us-west-2:123456789012:proxy/example ``` diff --git a/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown b/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown index 1716fb1ec45b..6abf98ef8567 100644 --- a/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown +++ b/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown @@ -159,19 +159,17 @@ This resource exports the following attributes in addition to the arguments abov ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Rules Exclusive using the `proxy_rule_group_arn,rule_name1[,rule_name2,...]`. For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Firewall Proxy Rules Exclusive using the `proxy_rule_group_arn`. For example: ```terraform import { to = aws_networkfirewall_proxy_rules_exclusive.example - id = "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example,allow-example-com,block-malicious-domains" + id = "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example" } ``` -Using `terraform import`, import Network Firewall Proxy Rules Exclusive using the `proxy_rule_group_arn,rule_name1[,rule_name2,...]`. For example: +Using `terraform import`, import Network Firewall Proxy Rules Exclusive using the `proxy_rule_group_arn`. For example: ```console -% terraform import aws_networkfirewall_proxy_rules_exclusive.example "arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example,allow-example-com,block-malicious-domains" +% terraform import aws_networkfirewall_proxy_rules_exclusive.example arn:aws:network-firewall:us-west-2:123456789012:proxy-rule-group/example ``` - -~> **NOTE:** When importing, specify the proxy rule group ARN followed by a comma-separated list of rule names to import. The import will fetch only the specified rules from the rule group. From 6bec022063e4cd4a1c877d86b2f47df2d98d841f Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Sun, 15 Mar 2026 20:46:41 +1100 Subject: [PATCH 15/19] added changelog and fixed PR checks --- .changelog/46939.txt | 19 +++++++ internal/service/networkfirewall/proxy.go | 2 +- ...ration_rule_group_attachments_exclusive.go | 9 ++- ...n_rule_group_attachments_exclusive_test.go | 49 ++++++++--------- .../proxy_configuration_test.go | 31 +++++------ .../networkfirewall/proxy_rule_group_test.go | 31 +++++------ .../networkfirewall/proxy_rules_exclusive.go | 16 +++--- .../proxy_rules_exclusive_test.go | 55 +++++++++---------- .../service/networkfirewall/proxy_test.go | 39 +++++++------ .../r/networkfirewall_proxy.html.markdown | 1 + ...firewall_proxy_configuration.html.markdown | 1 + ..._group_attachments_exclusive.html.markdown | 1 + ...orkfirewall_proxy_rule_group.html.markdown | 1 + ...rewall_proxy_rules_exclusive.html.markdown | 1 + 14 files changed, 136 insertions(+), 120 deletions(-) create mode 100644 .changelog/46939.txt diff --git a/.changelog/46939.txt b/.changelog/46939.txt new file mode 100644 index 000000000000..b15737a1bfb7 --- /dev/null +++ b/.changelog/46939.txt @@ -0,0 +1,19 @@ +```release-note:new-resource +aws_networkfirewall_proxy +``` + +```release-note:new-resource +aws_networkfirewall_proxy_configuration +``` + +```release-note:new-resource +aws_networkfirewall_proxy_rule_group +``` + +```release-note:new-resource +aws_networkfirewall_proxy_rules_exclusive +``` + +```release-note:new-resource +aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive +``` diff --git a/internal/service/networkfirewall/proxy.go b/internal/service/networkfirewall/proxy.go index 2fb2fa15bf99..ef64dfb85a3c 100644 --- a/internal/service/networkfirewall/proxy.go +++ b/internal/service/networkfirewall/proxy.go @@ -61,7 +61,7 @@ func (r *resourceProxy) Schema(ctx context.Context, req resource.SchemaRequest, resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrARN: framework.ARNAttributeComputedOnly(), - "create_time": schema.StringAttribute{ + names.AttrCreateTime: schema.StringAttribute{ CustomType: timetypes.RFC3339Type{}, Computed: true, PlanModifiers: []planmodifier.String{ diff --git a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go index 0b2cc29158ee..b5f40c362119 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go @@ -26,7 +26,7 @@ import ( "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" - tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/retry" "github.com/hashicorp/terraform-provider-aws/internal/smerr" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -152,7 +152,7 @@ func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Read(ctx conte proxyConfigArn := state.ID.ValueString() out, err := findProxyConfigurationByARN(ctx, conn, proxyConfigArn) - if tfretry.NotFound(err) { + if retry.NotFound(err) { resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) resp.State.RemoveResource(ctx) return @@ -299,12 +299,11 @@ func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Update(ctx con UpdateToken: updateToken, } - priorityOut, err := conn.UpdateProxyRuleGroupPriorities(ctx, priorityInput) + _, err = conn.UpdateProxyRuleGroupPriorities(ctx, priorityInput) if err != nil { smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, proxyConfigArn) return } - updateToken = priorityOut.UpdateToken } // Read back the update token from AWS @@ -334,7 +333,7 @@ func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Delete(ctx con // Get current state to retrieve update token out, err := findProxyConfigurationByARN(ctx, conn, proxyConfigArn) - if tfretry.NotFound(err) { + if retry.NotFound(err) { return } if err != nil { diff --git a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go index 45f141afe15f..eaef7d76c6b8 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go @@ -16,9 +16,8 @@ import ( "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" fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" - tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/retry" tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -33,16 +32,16 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_basic proxyConfigResourceName := "aws_networkfirewall_proxy_configuration.test" ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName, &v), resource.TestCheckResourceAttrPair(resourceName, names.AttrID, proxyConfigResourceName, names.AttrARN), resource.TestCheckResourceAttrPair(resourceName, "proxy_configuration_arn", proxyConfigResourceName, names.AttrARN), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"), @@ -68,16 +67,16 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_disap rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName, &v), acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, t, tfnetworkfirewall.ResourceProxyConfigurationRuleGroupAttachmentsExclusive, resourceName, proxyConfigurationRuleGroupAttachmentsExclusiveDisappearsStateFunc), ), ExpectNonEmptyPlan: true, @@ -95,16 +94,16 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" ruleGroup3ResourceName := "aws_networkfirewall_proxy_rule_group.test3" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroups(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v1), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), ), }, @@ -117,7 +116,7 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat { Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_threeRuleGroups(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v2), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "3"), resource.TestCheckResourceAttrPair(resourceName, "rule_group.2.proxy_rule_group_name", ruleGroup3ResourceName, names.AttrName), ), @@ -134,16 +133,16 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroups(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v1), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), ), }, @@ -156,7 +155,7 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat { Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v2), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "1"), ), }, @@ -174,16 +173,16 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" ruleGroup2ResourceName := "aws_networkfirewall_proxy_rule_group.test2" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroups(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v1), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), resource.TestCheckResourceAttrPair(resourceName, "rule_group.0.proxy_rule_group_name", ruleGroup1ResourceName, names.AttrName), resource.TestCheckResourceAttrPair(resourceName, "rule_group.1.proxy_rule_group_name", ruleGroup2ResourceName, names.AttrName), @@ -192,7 +191,7 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat { Config: testAccProxyConfigurationRuleGroupAttachmentsExclusiveConfig_twoRuleGroupsReversed(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, resourceName, &v2), + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "rule_group.#", "2"), resource.TestCheckResourceAttrPair(resourceName, "rule_group.0.proxy_rule_group_name", ruleGroup2ResourceName, names.AttrName), resource.TestCheckResourceAttrPair(resourceName, "rule_group.1.proxy_rule_group_name", ruleGroup1ResourceName, names.AttrName), @@ -202,9 +201,9 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat }) } -func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive" { @@ -213,7 +212,7 @@ func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx cont out, err := tfnetworkfirewall.FindProxyConfigurationByARN(ctx, conn, rs.Primary.ID) - if tfretry.NotFound(err) { + if retry.NotFound(err) { continue } @@ -233,14 +232,14 @@ func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx cont } } -func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { +func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) output, err := tfnetworkfirewall.FindProxyConfigurationByARN(ctx, conn, rs.Primary.Attributes["proxy_configuration_arn"]) diff --git a/internal/service/networkfirewall/proxy_configuration_test.go b/internal/service/networkfirewall/proxy_configuration_test.go index 92c40fb122c0..c13988eb9448 100644 --- a/internal/service/networkfirewall/proxy_configuration_test.go +++ b/internal/service/networkfirewall/proxy_configuration_test.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/plancheck" "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/retry" tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" "github.com/hashicorp/terraform-provider-aws/names" @@ -29,16 +28,16 @@ func testAccNetworkFirewallProxyConfiguration_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfigurationConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + testAccCheckProxyConfigurationExists(ctx, t, resourceName, &v), acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "network-firewall", regexache.MustCompile(`proxy-configuration/.+$`)), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, "default_rule_phase_actions.#", "1"), @@ -67,16 +66,16 @@ func testAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfigurationConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + testAccCheckProxyConfigurationExists(ctx, t, resourceName, &v), acctest.CheckFrameworkResourceDisappears(ctx, t, tfnetworkfirewall.ResourceProxyConfiguration, resourceName), ), ExpectNonEmptyPlan: true, @@ -98,16 +97,16 @@ func testAccNetworkFirewallProxyConfiguration_tags(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx), + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfigurationConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + testAccCheckProxyConfigurationExists(ctx, t, resourceName, &v), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), ), @@ -121,7 +120,7 @@ func testAccNetworkFirewallProxyConfiguration_tags(t *testing.T) { { Config: testAccProxyConfigurationConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + testAccCheckProxyConfigurationExists(ctx, t, resourceName, &v), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "2"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), @@ -130,7 +129,7 @@ func testAccNetworkFirewallProxyConfiguration_tags(t *testing.T) { { Config: testAccProxyConfigurationConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyConfigurationExists(ctx, resourceName, &v), + testAccCheckProxyConfigurationExists(ctx, t, resourceName, &v), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), ), @@ -139,9 +138,9 @@ func testAccNetworkFirewallProxyConfiguration_tags(t *testing.T) { }) } -func testAccCheckProxyConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckProxyConfigurationDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_networkfirewall_proxy_configuration" { @@ -169,14 +168,14 @@ func testAccCheckProxyConfigurationDestroy(ctx context.Context) resource.TestChe } } -func testAccCheckProxyConfigurationExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { +func testAccCheckProxyConfigurationExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) output, err := tfnetworkfirewall.FindProxyConfigurationByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) diff --git a/internal/service/networkfirewall/proxy_rule_group_test.go b/internal/service/networkfirewall/proxy_rule_group_test.go index ad72acc78597..30b6b92f5ab4 100644 --- a/internal/service/networkfirewall/proxy_rule_group_test.go +++ b/internal/service/networkfirewall/proxy_rule_group_test.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/plancheck" "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/retry" tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" "github.com/hashicorp/terraform-provider-aws/names" @@ -29,16 +28,16 @@ func testAccNetworkFirewallProxyRuleGroup_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx), + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRuleGroupConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + testAccCheckProxyRuleGroupExists(ctx, t, resourceName, &v), acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "network-firewall", regexache.MustCompile(`proxy-rule-group/.+$`)), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), @@ -63,16 +62,16 @@ func testAccNetworkFirewallProxyRuleGroup_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx), + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRuleGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + testAccCheckProxyRuleGroupExists(ctx, t, resourceName, &v), acctest.CheckFrameworkResourceDisappears(ctx, t, tfnetworkfirewall.ResourceProxyRuleGroup, resourceName), ), ExpectNonEmptyPlan: true, @@ -94,16 +93,16 @@ func testAccNetworkFirewallProxyRuleGroup_tags(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx), + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRuleGroupConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + testAccCheckProxyRuleGroupExists(ctx, t, resourceName, &v), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), ), @@ -117,7 +116,7 @@ func testAccNetworkFirewallProxyRuleGroup_tags(t *testing.T) { { Config: testAccProxyRuleGroupConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + testAccCheckProxyRuleGroupExists(ctx, t, resourceName, &v), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "2"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), @@ -126,7 +125,7 @@ func testAccNetworkFirewallProxyRuleGroup_tags(t *testing.T) { { Config: testAccProxyRuleGroupConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyRuleGroupExists(ctx, resourceName, &v), + testAccCheckProxyRuleGroupExists(ctx, t, resourceName, &v), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "1"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), ), @@ -135,9 +134,9 @@ func testAccNetworkFirewallProxyRuleGroup_tags(t *testing.T) { }) } -func testAccCheckProxyRuleGroupDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckProxyRuleGroupDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_networkfirewall_proxy_rule_group" { @@ -165,14 +164,14 @@ func testAccCheckProxyRuleGroupDestroy(ctx context.Context) resource.TestCheckFu } } -func testAccCheckProxyRuleGroupExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { +func testAccCheckProxyRuleGroupExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) output, err := tfnetworkfirewall.FindProxyRuleGroupByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) diff --git a/internal/service/networkfirewall/proxy_rules_exclusive.go b/internal/service/networkfirewall/proxy_rules_exclusive.go index 67fd7f28528d..b216d552bde3 100644 --- a/internal/service/networkfirewall/proxy_rules_exclusive.go +++ b/internal/service/networkfirewall/proxy_rules_exclusive.go @@ -24,7 +24,7 @@ import ( "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" - tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/retry" "github.com/hashicorp/terraform-provider-aws/internal/smerr" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -169,7 +169,7 @@ func (r *resourceProxyRulesExclusive) Read(ctx context.Context, req resource.Rea } out, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) - if tfretry.NotFound(err) { + if retry.NotFound(err) { resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) resp.State.RemoveResource(ctx) return @@ -199,12 +199,12 @@ func (r *resourceProxyRulesExclusive) Update(ctx context.Context, req resource.U // Get current state to obtain update token and existing rules from AWS currentRules, err := findProxyRulesByGroupARN(ctx, conn, state.ProxyRuleGroupArn.ValueString()) - if err != nil && !tfretry.NotFound(err) { + if err != nil && !retry.NotFound(err) { smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) return } - updateToken := currentRules.UpdateToken + var updateToken *string // Extract rules from AWS for each phase var statePostRESPONSE, statePreDNS, statePreREQUEST []proxyRuleModel @@ -266,8 +266,6 @@ func (r *resourceProxyRulesExclusive) Update(ctx context.Context, req resource.U smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) return } - updateToken = currentRules.UpdateToken - // Verify that the deleted rules are actually gone if currentRules.ProxyRuleGroup != nil && currentRules.ProxyRuleGroup.Rules != nil { existingRuleNames := proxyCollectRuleNames(currentRules.ProxyRuleGroup.Rules) @@ -415,7 +413,7 @@ func (r *resourceProxyRulesExclusive) Delete(ctx context.Context, req resource.D // Get all rule names for this group out, err := findProxyRulesByGroupARN(ctx, conn, state.ID.ValueString()) - if err != nil && !tfretry.NotFound(err) { + if err != nil && !retry.NotFound(err) { smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, state.ID.String()) return } @@ -454,7 +452,7 @@ func findProxyRulesByGroupARN(ctx context.Context, conn *networkfirewall.Client, out, err := conn.DescribeProxyRuleGroup(ctx, &input) if err != nil { if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &tfretry.NotFoundError{ + return nil, &retry.NotFoundError{ LastError: err, } } @@ -463,7 +461,7 @@ func findProxyRulesByGroupARN(ctx context.Context, conn *networkfirewall.Client, } if out == nil || out.ProxyRuleGroup == nil { - return nil, &tfretry.NotFoundError{ + return nil, &retry.NotFoundError{ Message: "proxy rule group not found", } } diff --git a/internal/service/networkfirewall/proxy_rules_exclusive_test.go b/internal/service/networkfirewall/proxy_rules_exclusive_test.go index 1d8e111879d0..b61962200328 100644 --- a/internal/service/networkfirewall/proxy_rules_exclusive_test.go +++ b/internal/service/networkfirewall/proxy_rules_exclusive_test.go @@ -14,8 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" - "github.com/hashicorp/terraform-provider-aws/internal/conns" - tfretry "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/retry" tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -29,16 +28,16 @@ func testAccNetworkFirewallProxyRulesExclusive_basic(t *testing.T) { resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" ruleGroupResourceName := "aws_networkfirewall_proxy_rule_group.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRulesExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v), resource.TestCheckResourceAttrPair(resourceName, "proxy_rule_group_arn", ruleGroupResourceName, names.AttrARN), // Pre-DNS phase resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), @@ -86,16 +85,16 @@ func testAccNetworkFirewallProxyRulesExclusive_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRulesExclusiveConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v), acctest.CheckFrameworkResourceDisappears(ctx, t, tfnetworkfirewall.ResourceProxyRulesExclusive, resourceName), ), ExpectNonEmptyPlan: true, @@ -117,16 +116,16 @@ func testAccNetworkFirewallProxyRulesExclusive_updateAdd(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRulesExclusiveConfig_single(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v1), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "0"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "0"), @@ -140,7 +139,7 @@ func testAccNetworkFirewallProxyRulesExclusive_updateAdd(t *testing.T) { { Config: testAccProxyRulesExclusiveConfig_add(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v2), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "1"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), @@ -160,16 +159,16 @@ func testAccNetworkFirewallProxyRulesExclusive_updateModify(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRulesExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v1), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "ALLOW"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "amazonaws.com"), @@ -183,7 +182,7 @@ func testAccNetworkFirewallProxyRulesExclusive_updateModify(t *testing.T) { { Config: testAccProxyRulesExclusiveConfig_modified(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v2), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.action", "DENY"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.#", "2"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.conditions.0.condition_values.0", "example.com"), @@ -202,16 +201,16 @@ func testAccNetworkFirewallProxyRulesExclusive_updateRemove(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRulesExclusiveConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v1), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v1), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "1"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "1"), @@ -225,7 +224,7 @@ func testAccNetworkFirewallProxyRulesExclusive_updateRemove(t *testing.T) { { Config: testAccProxyRulesExclusiveConfig_single(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v2), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v2), resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "1"), resource.TestCheckResourceAttr(resourceName, "pre_request.#", "0"), resource.TestCheckResourceAttr(resourceName, "post_response.#", "0"), @@ -243,16 +242,16 @@ func testAccNetworkFirewallProxyRulesExclusive_multipleRulesPerPhase(t *testing. rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewall), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyRulesExclusiveConfig_multiplePerPhase(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyRulesExclusiveExists(ctx, resourceName, &v), + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName, &v), // Pre-DNS phase - 2 rules resource.TestCheckResourceAttr(resourceName, "pre_dns.#", "2"), resource.TestCheckResourceAttr(resourceName, "pre_dns.0.proxy_rule_name", fmt.Sprintf("%s-predns-1", rName)), @@ -289,9 +288,9 @@ func testAccNetworkFirewallProxyRulesExclusive_multipleRulesPerPhase(t *testing. }) } -func testAccCheckProxyRulesExclusiveDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckProxyRulesExclusiveDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_networkfirewall_proxy_rules_exclusive" { @@ -301,7 +300,7 @@ func testAccCheckProxyRulesExclusiveDestroy(ctx context.Context) resource.TestCh // The resource ID is the proxy rule group ARN out, err := tfnetworkfirewall.FindProxyRuleGroupByARN(ctx, conn, rs.Primary.ID) - if tfretry.NotFound(err) { + if retry.NotFound(err) { continue } @@ -322,14 +321,14 @@ func testAccCheckProxyRulesExclusiveDestroy(ctx context.Context) resource.TestCh } } -func testAccCheckProxyRulesExclusiveExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { +func testAccCheckProxyRulesExclusiveExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) output, err := tfnetworkfirewall.FindProxyRuleGroupByARN(ctx, conn, rs.Primary.Attributes["proxy_rule_group_arn"]) diff --git a/internal/service/networkfirewall/proxy_test.go b/internal/service/networkfirewall/proxy_test.go index d544324f3a2a..4ac211577f58 100644 --- a/internal/service/networkfirewall/proxy_test.go +++ b/internal/service/networkfirewall/proxy_test.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/terraform-plugin-testing/plancheck" "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/retry" tfnetworkfirewall "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" "github.com/hashicorp/terraform-provider-aws/names" @@ -29,16 +28,16 @@ func testAccNetworkFirewallProxy_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyDestroy(ctx), + CheckDestroy: testAccCheckProxyDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyExists(ctx, resourceName, &v), + testAccCheckProxyExists(ctx, t, resourceName, &v), acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "network-firewall", regexache.MustCompile(`proxy/.+$`)), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, "nat_gateway_id"), @@ -48,7 +47,7 @@ func testAccNetworkFirewallProxy_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "listener_properties.1.port", "443"), resource.TestCheckResourceAttr(resourceName, "listener_properties.1.type", "HTTPS"), resource.TestCheckResourceAttr(resourceName, "tls_intercept_properties.0.tls_intercept_mode", "DISABLED"), - resource.TestCheckResourceAttrSet(resourceName, "create_time"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrCreateTime), resource.TestCheckResourceAttrSet(resourceName, "update_token"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), ), @@ -71,16 +70,16 @@ func testAccNetworkFirewallProxy_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyDestroy(ctx), + CheckDestroy: testAccCheckProxyDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckProxyExists(ctx, resourceName, &v), + testAccCheckProxyExists(ctx, t, resourceName, &v), acctest.CheckFrameworkResourceDisappears(ctx, t, tfnetworkfirewall.ResourceProxy, resourceName), ), ExpectNonEmptyPlan: true, @@ -103,16 +102,16 @@ func testAccNetworkFirewallProxy_tlsInterceptEnabled(t *testing.T) { resourceName := "aws_networkfirewall_proxy.test" pcaResourceName := "aws_acmpca_certificate_authority.test" - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyDestroy(ctx), + CheckDestroy: testAccCheckProxyDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfig_tlsInterceptEnabled(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckProxyExists(ctx, resourceName, &v), + testAccCheckProxyExists(ctx, t, resourceName, &v), acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "network-firewall", regexache.MustCompile(`proxy/.+$`)), resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), resource.TestCheckResourceAttrSet(resourceName, "nat_gateway_id"), @@ -124,7 +123,7 @@ func testAccNetworkFirewallProxy_tlsInterceptEnabled(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tls_intercept_properties.#", "1"), resource.TestCheckResourceAttr(resourceName, "tls_intercept_properties.0.tls_intercept_mode", "ENABLED"), resource.TestCheckResourceAttrPair(resourceName, "tls_intercept_properties.0.pca_arn", pcaResourceName, names.AttrARN), - resource.TestCheckResourceAttrSet(resourceName, "create_time"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrCreateTime), resource.TestCheckResourceAttrSet(resourceName, "update_token"), resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), ), @@ -145,18 +144,18 @@ func testAccNetworkFirewallProxy_logging(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resource.Test(t, resource.TestCase{ + acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckProxyDestroy(ctx), + CheckDestroy: testAccCheckProxyDestroy(ctx, t), Steps: []resource.TestStep{ { Config: testAccProxyConfig_logging(rName), Check: resource.ComposeAggregateTestCheckFunc( // CloudWatch Logs delivery source resource.TestCheckResourceAttr("aws_cloudwatch_log_delivery_source.cwl", "log_type", "ALERT_LOGS"), - resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery_source.cwl", "resource_arn", "aws_networkfirewall_proxy.test", names.AttrARN), + resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery_source.cwl", names.AttrResourceARN, "aws_networkfirewall_proxy.test", names.AttrARN), // CloudWatch Logs delivery destination resource.TestCheckResourceAttr("aws_cloudwatch_log_delivery_destination.cwl", "delivery_destination_type", "CWL"), // CloudWatch Logs delivery @@ -165,7 +164,7 @@ func testAccNetworkFirewallProxy_logging(t *testing.T) { resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery.cwl", "delivery_destination_arn", "aws_cloudwatch_log_delivery_destination.cwl", names.AttrARN), // S3 delivery source resource.TestCheckResourceAttr("aws_cloudwatch_log_delivery_source.s3", "log_type", "ALLOW_LOGS"), - resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery_source.s3", "resource_arn", "aws_networkfirewall_proxy.test", names.AttrARN), + resource.TestCheckResourceAttrPair("aws_cloudwatch_log_delivery_source.s3", names.AttrResourceARN, "aws_networkfirewall_proxy.test", names.AttrARN), // S3 delivery destination resource.TestCheckResourceAttr("aws_cloudwatch_log_delivery_destination.s3", "delivery_destination_type", "S3"), // S3 delivery @@ -178,9 +177,9 @@ func testAccNetworkFirewallProxy_logging(t *testing.T) { }) } -func testAccCheckProxyDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckProxyDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_networkfirewall_proxy" { @@ -208,14 +207,14 @@ func testAccCheckProxyDestroy(ctx context.Context) resource.TestCheckFunc { } } -func testAccCheckProxyExists(ctx context.Context, n string, v *networkfirewall.DescribeProxyOutput) resource.TestCheckFunc { +func testAccCheckProxyExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFirewallClient(ctx) + conn := acctest.ProviderMeta(ctx, t).NetworkFirewallClient(ctx) output, err := tfnetworkfirewall.FindProxyByARN(ctx, conn, rs.Primary.Attributes[names.AttrARN]) diff --git a/website/docs/r/networkfirewall_proxy.html.markdown b/website/docs/r/networkfirewall_proxy.html.markdown index 97ef780ad4aa..b2882164c785 100644 --- a/website/docs/r/networkfirewall_proxy.html.markdown +++ b/website/docs/r/networkfirewall_proxy.html.markdown @@ -156,6 +156,7 @@ The following arguments are optional: * `listener_properties` - (Optional) One or more listener properties blocks defining the ports and protocols the proxy listens on. See [Listener Properties](#listener-properties) below. * `proxy_configuration_arn` - (Optional, Forces new resource) ARN of the proxy configuration to use. Exactly one of `proxy_configuration_arn` or `proxy_configuration_name` must be provided. * `proxy_configuration_name` - (Optional, Forces new resource) Name of the proxy configuration to use. Exactly one of `proxy_configuration_arn` or `proxy_configuration_name` must be provided. +* `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). * `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ### TLS Intercept Properties diff --git a/website/docs/r/networkfirewall_proxy_configuration.html.markdown b/website/docs/r/networkfirewall_proxy_configuration.html.markdown index 2fec1561ca30..4356a8914c03 100644 --- a/website/docs/r/networkfirewall_proxy_configuration.html.markdown +++ b/website/docs/r/networkfirewall_proxy_configuration.html.markdown @@ -56,6 +56,7 @@ The following arguments are required: The following arguments are optional: * `description` - (Optional) Description of the proxy configuration. +* `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). * `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ### Default Rule Phase Actions diff --git a/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachments_exclusive.html.markdown b/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachments_exclusive.html.markdown index c40b02ae0006..aa724717e52a 100644 --- a/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachments_exclusive.html.markdown +++ b/website/docs/r/networkfirewall_proxy_configuration_rule_group_attachments_exclusive.html.markdown @@ -82,6 +82,7 @@ The following arguments are required: The following arguments are optional: +* `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). * `rule_group` - (Optional) One or more rule group blocks. See [Rule Group](#rule-group) below. ### Rule Group diff --git a/website/docs/r/networkfirewall_proxy_rule_group.html.markdown b/website/docs/r/networkfirewall_proxy_rule_group.html.markdown index 555a202fae9c..194f87fcc8db 100644 --- a/website/docs/r/networkfirewall_proxy_rule_group.html.markdown +++ b/website/docs/r/networkfirewall_proxy_rule_group.html.markdown @@ -45,6 +45,7 @@ The following arguments are required: The following arguments are optional: * `description` - (Optional) Description of the proxy rule group. +* `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). * `tags` - (Optional) Map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ## Attribute Reference diff --git a/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown b/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown index 6abf98ef8567..93125da6c730 100644 --- a/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown +++ b/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown @@ -126,6 +126,7 @@ The following arguments are optional: * `pre_request` - (Optional) Rules to apply during the PRE_REQUEST phase. See [Rule Configuration](#rule-configuration) below. * `proxy_rule_group_arn` - (Optional) ARN of the proxy rule group. Conflicts with `proxy_rule_group_name`. Required if `proxy_rule_group_name` is not specified. * `proxy_rule_group_name` - (Optional) Name of the proxy rule group. Conflicts with `proxy_rule_group_arn`. Required if `proxy_rule_group_arn` is not specified. +* `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). ### Rule Configuration From a38bbe0e5e901d176678589b66058ce54a4bae8c Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Mon, 16 Mar 2026 20:24:39 +1100 Subject: [PATCH 16/19] fixed more issues --- internal/service/networkfirewall/proxy.go | 2 +- .../proxy_configuration_identity_gen_test.go | 353 ++++++++++++++++++ ...ration_rule_group_attachments_exclusive.go | 27 +- ...attachments_exclusive_identity_gen_test.go | 351 +++++++++++++++++ ...n_rule_group_attachments_exclusive_test.go | 19 +- .../proxy_configuration_test.go | 13 +- .../proxy_identity_gen_test.go | 351 +++++++++++++++++ .../networkfirewall/proxy_rule_group.go | 6 +- .../proxy_rule_group_identity_gen_test.go | 353 ++++++++++++++++++ .../networkfirewall/proxy_rule_group_test.go | 13 +- .../networkfirewall/proxy_rules_exclusive.go | 14 +- ...proxy_rules_exclusive_identity_gen_test.go | 353 ++++++++++++++++++ .../proxy_rules_exclusive_test.go | 27 +- .../service/networkfirewall/proxy_test.go | 36 +- ...rewall_proxy_rules_exclusive.html.markdown | 4 +- 15 files changed, 1839 insertions(+), 83 deletions(-) create mode 100644 internal/service/networkfirewall/proxy_configuration_identity_gen_test.go create mode 100644 internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_identity_gen_test.go create mode 100644 internal/service/networkfirewall/proxy_identity_gen_test.go create mode 100644 internal/service/networkfirewall/proxy_rule_group_identity_gen_test.go create mode 100644 internal/service/networkfirewall/proxy_rules_exclusive_identity_gen_test.go diff --git a/internal/service/networkfirewall/proxy.go b/internal/service/networkfirewall/proxy.go index ef64dfb85a3c..6edaaac78d2b 100644 --- a/internal/service/networkfirewall/proxy.go +++ b/internal/service/networkfirewall/proxy.go @@ -143,7 +143,7 @@ func (r *resourceProxy) Schema(ctx context.Context, req resource.SchemaRequest, }, NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "port": schema.Int32Attribute{ + names.AttrPort: schema.Int32Attribute{ Required: true, }, names.AttrType: schema.StringAttribute{ diff --git a/internal/service/networkfirewall/proxy_configuration_identity_gen_test.go b/internal/service/networkfirewall/proxy_configuration_identity_gen_test.go new file mode 100644 index 000000000000..551e66cfd5cf --- /dev/null +++ b/internal/service/networkfirewall/proxy_configuration_identity_gen_test.go @@ -0,0 +1,353 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by internal/generate/identitytests/main.go; DO NOT EDIT. + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxyConfiguration_Identity_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_configuration.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectRegionalARNFormat(resourceName, tfjsonpath.New(names.AttrARN), "network-firewall", "proxy-configuration/{name}"), + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 2: Import command + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import block with Import ID + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + + // Step 4: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfiguration_Identity_regionOverride(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_configuration.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: acctest.CheckDestroyNoop, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectRegionalARNAlternateRegionFormat(resourceName, tfjsonpath.New(names.AttrARN), "network-firewall", "proxy-configuration/{name}"), + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 2: Import command with appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import command without appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 4: Import block with Import ID and appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 5: Import block with Import ID and no appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 6: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfiguration_Identity_ExistingResource_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_configuration.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: v6.0 Identity set on refresh + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic_v6.0.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, t, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 3: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfiguration_Identity_ExistingResource_noRefreshNoChange(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_configuration.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx, t), + AdditionalCLIOptions: &resource.AdditionalCLIOptions{ + Plan: resource.PlanOptions{ + NoRefresh: true, + }, + }, + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + }, + }, + }) +} diff --git a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go index b5f40c362119..9aa9164de0a2 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive.go @@ -4,16 +4,16 @@ package networkfirewall import ( + "cmp" "context" "errors" - "sort" + "slices" "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/networkfirewall" awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -107,7 +107,7 @@ func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Create(ctx con for i, rg := range planRuleGroups { ruleGroups = append(ruleGroups, awstypes.ProxyRuleGroupAttachment{ - ProxyRuleGroupName: aws.String(rg.ProxyRuleGroupName.ValueString()), + ProxyRuleGroupName: rg.ProxyRuleGroupName.ValueStringPointer(), InsertPosition: aws.Int32(int32(i)), }) } @@ -169,10 +169,7 @@ func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Read(ctx conte return } - smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyConfigurationRuleGroups(ctx, out, &state)) - if resp.Diagnostics.HasError() { - return - } + setProxyConfigurationRuleGroupsState(ctx, out, &state) smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) } @@ -251,7 +248,7 @@ func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Update(ctx con for i, rg := range planRuleGroups { if !stateRuleGroupNames[rg.ProxyRuleGroupName.ValueString()] { ruleGroupsToAttach = append(ruleGroupsToAttach, awstypes.ProxyRuleGroupAttachment{ - ProxyRuleGroupName: aws.String(rg.ProxyRuleGroupName.ValueString()), + ProxyRuleGroupName: rg.ProxyRuleGroupName.ValueStringPointer(), InsertPosition: aws.Int32(int32(i)), }) } @@ -288,7 +285,7 @@ func (r *resourceProxyConfigurationRuleGroupAttachmentsExclusive) Update(ctx con var ruleGroupPriorities []awstypes.ProxyRuleGroupPriority for i, rg := range planRuleGroups { ruleGroupPriorities = append(ruleGroupPriorities, awstypes.ProxyRuleGroupPriority{ - ProxyRuleGroupName: aws.String(rg.ProxyRuleGroupName.ValueString()), + ProxyRuleGroupName: rg.ProxyRuleGroupName.ValueStringPointer(), NewPosition: aws.Int32(int32(i)), }) } @@ -399,20 +396,18 @@ func (data *proxyConfigurationRuleGroupAttachmentModel) setID() { data.ID = data.ProxyConfigurationArn.StringValue } -func flattenProxyConfigurationRuleGroups(ctx context.Context, out *networkfirewall.DescribeProxyConfigurationOutput, model *proxyConfigurationRuleGroupAttachmentModel) diag.Diagnostics { - var diags diag.Diagnostics - +func setProxyConfigurationRuleGroupsState(ctx context.Context, out *networkfirewall.DescribeProxyConfigurationOutput, model *proxyConfigurationRuleGroupAttachmentModel) { if out.ProxyConfiguration == nil || out.ProxyConfiguration.RuleGroups == nil { model.RuleGroups = fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []RuleGroupAttachmentModel{}) model.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) - return diags + return } // Sort by Priority to maintain order (lower priority number = higher priority = first in list) sortedRuleGroups := make([]awstypes.ProxyConfigRuleGroup, len(out.ProxyConfiguration.RuleGroups)) copy(sortedRuleGroups, out.ProxyConfiguration.RuleGroups) - sort.SliceStable(sortedRuleGroups, func(i, j int) bool { - return aws.ToInt32(sortedRuleGroups[i].Priority) < aws.ToInt32(sortedRuleGroups[j].Priority) + slices.SortStableFunc(sortedRuleGroups, func(a, b awstypes.ProxyConfigRuleGroup) int { + return cmp.Compare(aws.ToInt32(a.Priority), aws.ToInt32(b.Priority)) }) var ruleGroups []RuleGroupAttachmentModel @@ -424,6 +419,4 @@ func flattenProxyConfigurationRuleGroups(ctx context.Context, out *networkfirewa model.RuleGroups = fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, ruleGroups) model.UpdateToken = flex.StringToFramework(ctx, out.UpdateToken) - - return diags } diff --git a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_identity_gen_test.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_identity_gen_test.go new file mode 100644 index 000000000000..4662e66c35ba --- /dev/null +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_identity_gen_test.go @@ -0,0 +1,351 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by internal/generate/identitytests/main.go; DO NOT EDIT. + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_Identity_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New("proxy_configuration_arn"), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + "proxy_configuration_arn": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("proxy_configuration_arn")), + }, + }, + + // Step 2: Import command + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import block with Import ID + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_configuration_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + + // Step 4: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_configuration_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_Identity_regionOverride(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: acctest.CheckDestroyNoop, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New("proxy_configuration_arn"), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + "proxy_configuration_arn": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("proxy_configuration_arn")), + }, + }, + + // Step 2: Import command with appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import command without appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 4: Import block with Import ID and appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_configuration_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 5: Import block with Import ID and no appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_configuration_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 6: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_configuration_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_Identity_ExistingResource_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: v6.0 Identity set on refresh + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic_v6.0.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + "proxy_configuration_arn": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("proxy_configuration_arn")), + }, + }, + + // Step 3: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + "proxy_configuration_arn": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("proxy_configuration_arn")), + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_Identity_ExistingResource_noRefreshNoChange(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx, t), + AdditionalCLIOptions: &resource.AdditionalCLIOptions{ + Plan: resource.PlanOptions{ + NoRefresh: true, + }, + }, + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfigurationRuleGroupAttachmentsExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + }, + }, + }) +} diff --git a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go index eaef7d76c6b8..98bbf2f71258 100644 --- a/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go +++ b/internal/service/networkfirewall/proxy_configuration_rule_group_attachments_exclusive_test.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/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" @@ -27,7 +26,7 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_basic ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" proxyConfigResourceName := "aws_networkfirewall_proxy_configuration.test" ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" @@ -64,7 +63,7 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_disap ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" acctest.Test(ctx, t, resource.TestCase{ @@ -90,7 +89,7 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" ruleGroup3ResourceName := "aws_networkfirewall_proxy_rule_group.test3" @@ -130,7 +129,7 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" acctest.Test(ctx, t, resource.TestCase{ @@ -168,7 +167,7 @@ func testAccNetworkFirewallProxyConfigurationRuleGroupAttachmentsExclusive_updat ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyConfigurationOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive.test" ruleGroup1ResourceName := "aws_networkfirewall_proxy_rule_group.test1" ruleGroup2ResourceName := "aws_networkfirewall_proxy_rule_group.test2" @@ -232,7 +231,7 @@ func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveDestroy(ctx cont } } -func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { +func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx context.Context, t *testing.T, n string, v ...*networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -247,7 +246,9 @@ func testAccCheckProxyConfigurationRuleGroupAttachmentsExclusiveExists(ctx conte return err } - *v = *output + if len(v) > 0 { + *v[0] = *output + } return nil } @@ -264,7 +265,7 @@ func proxyConfigurationRuleGroupAttachmentsExclusiveDisappearsStateFunc(ctx cont // Set the rule_group nested block from the instance state ruleGroupCount := 0 if v, ok := is.Attributes["rule_group.#"]; ok { - fmt.Sscanf(v, "%d", &ruleGroupCount) + _, _ = fmt.Sscanf(v, "%d", &ruleGroupCount) } if ruleGroupCount > 0 { diff --git a/internal/service/networkfirewall/proxy_configuration_test.go b/internal/service/networkfirewall/proxy_configuration_test.go index c13988eb9448..1b1297f91319 100644 --- a/internal/service/networkfirewall/proxy_configuration_test.go +++ b/internal/service/networkfirewall/proxy_configuration_test.go @@ -10,7 +10,6 @@ import ( "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/service/networkfirewall" - sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -25,7 +24,7 @@ func testAccNetworkFirewallProxyConfiguration_basic(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" acctest.Test(ctx, t, resource.TestCase{ @@ -63,7 +62,7 @@ func testAccNetworkFirewallProxyConfiguration_disappears(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" acctest.Test(ctx, t, resource.TestCase{ @@ -94,7 +93,7 @@ func testAccNetworkFirewallProxyConfiguration_tags(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyConfigurationOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_configuration.test" acctest.Test(ctx, t, resource.TestCase{ @@ -168,7 +167,7 @@ func testAccCheckProxyConfigurationDestroy(ctx context.Context, t *testing.T) re } } -func testAccCheckProxyConfigurationExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { +func testAccCheckProxyConfigurationExists(ctx context.Context, t *testing.T, n string, v ...*networkfirewall.DescribeProxyConfigurationOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -183,7 +182,9 @@ func testAccCheckProxyConfigurationExists(ctx context.Context, t *testing.T, n s return err } - *v = *output + if len(v) > 0 { + *v[0] = *output + } return nil } diff --git a/internal/service/networkfirewall/proxy_identity_gen_test.go b/internal/service/networkfirewall/proxy_identity_gen_test.go new file mode 100644 index 000000000000..0ce2024aed2d --- /dev/null +++ b/internal/service/networkfirewall/proxy_identity_gen_test.go @@ -0,0 +1,351 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by internal/generate/identitytests/main.go; DO NOT EDIT. + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxy_Identity_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyDestroy(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 2: Import command + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import block with Import ID + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + + // Step 4: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxy_Identity_regionOverride(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: acctest.CheckDestroyNoop, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 2: Import command with appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import command without appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 4: Import block with Import ID and appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 5: Import block with Import ID and no appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 6: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxy_Identity_ExistingResource_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: v6.0 Identity set on refresh + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic_v6.0.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyExists(ctx, t, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 3: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxy_Identity_ExistingResource_noRefreshNoChange(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyDestroy(ctx, t), + AdditionalCLIOptions: &resource.AdditionalCLIOptions{ + Plan: resource.PlanOptions{ + NoRefresh: true, + }, + }, + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/Proxy/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + }, + }, + }) +} diff --git a/internal/service/networkfirewall/proxy_rule_group.go b/internal/service/networkfirewall/proxy_rule_group.go index 19569d5721b2..458432152f30 100644 --- a/internal/service/networkfirewall/proxy_rule_group.go +++ b/internal/service/networkfirewall/proxy_rule_group.go @@ -82,12 +82,12 @@ func (r *resourceProxyRuleGroup) Create(ctx context.Context, req resource.Create } input := &networkfirewall.CreateProxyRuleGroupInput{ - ProxyRuleGroupName: aws.String(plan.ProxyRuleGroupName.ValueString()), + ProxyRuleGroupName: plan.ProxyRuleGroupName.ValueStringPointer(), Tags: getTagsIn(ctx), } if !plan.Description.IsNull() { - input.Description = aws.String(plan.Description.ValueString()) + input.Description = plan.Description.ValueStringPointer() } out, err := conn.CreateProxyRuleGroup(ctx, input) @@ -174,7 +174,7 @@ func (r *resourceProxyRuleGroup) Delete(ctx context.Context, req resource.Delete } input := &networkfirewall.DeleteProxyRuleGroupInput{ - ProxyRuleGroupArn: aws.String(state.ID.ValueString()), + ProxyRuleGroupArn: state.ID.ValueStringPointer(), } _, err := conn.DeleteProxyRuleGroup(ctx, input) diff --git a/internal/service/networkfirewall/proxy_rule_group_identity_gen_test.go b/internal/service/networkfirewall/proxy_rule_group_identity_gen_test.go new file mode 100644 index 000000000000..bdb446c448a3 --- /dev/null +++ b/internal/service/networkfirewall/proxy_rule_group_identity_gen_test.go @@ -0,0 +1,353 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by internal/generate/identitytests/main.go; DO NOT EDIT. + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxyRuleGroup_Identity_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_rule_group.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectRegionalARNFormat(resourceName, tfjsonpath.New(names.AttrARN), "network-firewall", "proxy-rule-group/{name}"), + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 2: Import command + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import block with Import ID + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + + // Step 4: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRuleGroup_Identity_regionOverride(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_rule_group.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: acctest.CheckDestroyNoop, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectRegionalARNAlternateRegionFormat(resourceName, tfjsonpath.New(names.AttrARN), "network-firewall", "proxy-rule-group/{name}"), + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New(names.AttrARN), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 2: Import command with appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import command without appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 4: Import block with Import ID and appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 5: Import block with Import ID and no appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 6: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRuleGroup_Identity_ExistingResource_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_rule_group.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: v6.0 Identity set on refresh + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic_v6.0.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, t, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + + // Step 3: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New(names.AttrARN)), + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRuleGroup_Identity_ExistingResource_noRefreshNoChange(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_rule_group.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx, t), + AdditionalCLIOptions: &resource.AdditionalCLIOptions{ + Plan: resource.PlanOptions{ + NoRefresh: true, + }, + }, + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRuleGroupExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + }, + }, + }) +} diff --git a/internal/service/networkfirewall/proxy_rule_group_test.go b/internal/service/networkfirewall/proxy_rule_group_test.go index 30b6b92f5ab4..1ba71b0ca5a1 100644 --- a/internal/service/networkfirewall/proxy_rule_group_test.go +++ b/internal/service/networkfirewall/proxy_rule_group_test.go @@ -10,7 +10,6 @@ import ( "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/service/networkfirewall" - sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -25,7 +24,7 @@ func testAccNetworkFirewallProxyRuleGroup_basic(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" acctest.Test(ctx, t, resource.TestCase{ @@ -59,7 +58,7 @@ func testAccNetworkFirewallProxyRuleGroup_disappears(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" acctest.Test(ctx, t, resource.TestCase{ @@ -90,7 +89,7 @@ func testAccNetworkFirewallProxyRuleGroup_tags(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rule_group.test" acctest.Test(ctx, t, resource.TestCase{ @@ -164,7 +163,7 @@ func testAccCheckProxyRuleGroupDestroy(ctx context.Context, t *testing.T) resour } } -func testAccCheckProxyRuleGroupExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { +func testAccCheckProxyRuleGroupExists(ctx context.Context, t *testing.T, n string, v ...*networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -179,7 +178,9 @@ func testAccCheckProxyRuleGroupExists(ctx context.Context, t *testing.T, n strin return err } - *v = *output + if len(v) > 0 { + *v[0] = *output + } return nil } diff --git a/internal/service/networkfirewall/proxy_rules_exclusive.go b/internal/service/networkfirewall/proxy_rules_exclusive.go index b216d552bde3..5bb78df7d9cf 100644 --- a/internal/service/networkfirewall/proxy_rules_exclusive.go +++ b/internal/service/networkfirewall/proxy_rules_exclusive.go @@ -70,7 +70,7 @@ func proxyRuleSchemaBlock(ctx context.Context) schema.Block { CustomType: fwtypes.NewListNestedObjectTypeOf[proxyRuleModel](ctx), NestedObject: schema.NestedBlockObject{ Attributes: map[string]schema.Attribute{ - "action": schema.StringAttribute{ + names.AttrAction: schema.StringAttribute{ CustomType: fwtypes.StringEnumType[awstypes.ProxyRulePhaseAction](), Required: true, }, @@ -151,7 +151,7 @@ func (r *resourceProxyRulesExclusive) Create(ctx context.Context, req resource.C return } - smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, readOut, &plan)) + smerr.AddEnrich(ctx, &resp.Diagnostics, setProxyRulesState(ctx, readOut, &plan)) if resp.Diagnostics.HasError() { return } @@ -179,7 +179,7 @@ func (r *resourceProxyRulesExclusive) Read(ctx context.Context, req resource.Rea return } - smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, out, &state)) + smerr.AddEnrich(ctx, &resp.Diagnostics, setProxyRulesState(ctx, out, &state)) if resp.Diagnostics.HasError() { return } @@ -339,13 +339,11 @@ func (r *resourceProxyRulesExclusive) Update(ctx context.Context, req resource.U return } - out, err := conn.UpdateProxyRule(ctx, &updateInput) + _, err = conn.UpdateProxyRule(ctx, &updateInput) if err != nil { smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, plan.ID.String()) return } - - updateToken = out.UpdateToken } // Step 3: Create/recreate rules @@ -392,7 +390,7 @@ func (r *resourceProxyRulesExclusive) Update(ctx context.Context, req resource.U return } - smerr.AddEnrich(ctx, &resp.Diagnostics, flattenProxyRules(ctx, readOut, &plan)) + smerr.AddEnrich(ctx, &resp.Diagnostics, setProxyRulesState(ctx, readOut, &plan)) if resp.Diagnostics.HasError() { return } @@ -469,7 +467,7 @@ func findProxyRulesByGroupARN(ctx context.Context, conn *networkfirewall.Client, return out, nil } -func flattenProxyRules(ctx context.Context, out *networkfirewall.DescribeProxyRuleGroupOutput, model *resourceProxyRulesExclusiveModel) diag.Diagnostics { +func setProxyRulesState(ctx context.Context, out *networkfirewall.DescribeProxyRuleGroupOutput, model *resourceProxyRulesExclusiveModel) diag.Diagnostics { var diags diag.Diagnostics if out.ProxyRuleGroup == nil || out.ProxyRuleGroup.Rules == nil { diff --git a/internal/service/networkfirewall/proxy_rules_exclusive_identity_gen_test.go b/internal/service/networkfirewall/proxy_rules_exclusive_identity_gen_test.go new file mode 100644 index 000000000000..0748d5d0fd52 --- /dev/null +++ b/internal/service/networkfirewall/proxy_rules_exclusive_identity_gen_test.go @@ -0,0 +1,353 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by internal/generate/identitytests/main.go; DO NOT EDIT. + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkFirewallProxyRulesExclusive_Identity_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectRegionalARNFormat(resourceName, tfjsonpath.New("proxy_rule_group_arn"), "network-firewall", "proxy-rule-group/{name}"), + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New("proxy_rule_group_arn"), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + "proxy_rule_group_arn": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("proxy_rule_group_arn")), + }, + }, + + // Step 2: Import command + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import block with Import ID + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_rule_group_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + + // Step 4: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_rule_group_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.Region())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRulesExclusive_Identity_regionOverride(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: acctest.CheckDestroyNoop, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectRegionalARNAlternateRegionFormat(resourceName, tfjsonpath.New("proxy_rule_group_arn"), "network-firewall", "proxy-rule-group/{name}"), + statecheck.CompareValuePairs(resourceName, tfjsonpath.New(names.AttrID), resourceName, tfjsonpath.New("proxy_rule_group_arn"), compare.ValuesSame()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + "proxy_rule_group_arn": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("proxy_rule_group_arn")), + }, + }, + + // Step 2: Import command with appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 3: Import command without appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ImportStateKind: resource.ImportCommandWithID, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + + // Step 4: Import block with Import ID and appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportStateIdFunc: acctest.CrossRegionImportStateIdFunc(resourceName), + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_rule_group_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 5: Import block with Import ID and no appended "@" + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_rule_group_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + + // Step 6: Import block with Resource Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ResourceName: resourceName, + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New("proxy_rule_group_arn"), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrID), knownvalue.NotNull()), + plancheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrRegion), knownvalue.StringExact(acctest.AlternateRegion())), + }, + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRulesExclusive_Identity_ExistingResource_basic(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: v6.0 Identity set on refresh + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic_v6.0.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + "proxy_rule_group_arn": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("proxy_rule_group_arn")), + }, + }, + + // Step 3: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionNoop), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectIdentity(resourceName, map[string]knownvalue.Check{ + "proxy_rule_group_arn": knownvalue.NotNull(), + }), + statecheck.ExpectIdentityValueMatchesState(resourceName, tfjsonpath.New("proxy_rule_group_arn")), + }, + }, + }, + }) +} + +func TestAccNetworkFirewallProxyRulesExclusive_Identity_ExistingResource_noRefreshNoChange(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_12_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + CheckDestroy: testAccCheckProxyRulesExclusiveDestroy(ctx, t), + AdditionalCLIOptions: &resource.AdditionalCLIOptions{ + Plan: resource.PlanOptions{ + NoRefresh: true, + }, + }, + Steps: []resource.TestStep{ + // Step 1: Create pre-Identity + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic_v5.100.0/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProxyRulesExclusiveExists(ctx, t, resourceName), + ), + ConfigStateChecks: []statecheck.StateCheck{ + tfstatecheck.ExpectNoIdentity(resourceName), + }, + }, + + // Step 2: Current version + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/ProxyRulesExclusive/basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + }, + }, + }) +} diff --git a/internal/service/networkfirewall/proxy_rules_exclusive_test.go b/internal/service/networkfirewall/proxy_rules_exclusive_test.go index b61962200328..6c6a9500fa92 100644 --- a/internal/service/networkfirewall/proxy_rules_exclusive_test.go +++ b/internal/service/networkfirewall/proxy_rules_exclusive_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/aws/aws-sdk-go-v2/service/networkfirewall" - sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -24,7 +23,7 @@ func testAccNetworkFirewallProxyRulesExclusive_basic(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" ruleGroupResourceName := "aws_networkfirewall_proxy_rule_group.test" @@ -82,7 +81,7 @@ func testAccNetworkFirewallProxyRulesExclusive_disappears(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" acctest.Test(ctx, t, resource.TestCase{ @@ -113,7 +112,7 @@ func testAccNetworkFirewallProxyRulesExclusive_updateAdd(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" acctest.Test(ctx, t, resource.TestCase{ @@ -156,7 +155,7 @@ func testAccNetworkFirewallProxyRulesExclusive_updateModify(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" acctest.Test(ctx, t, resource.TestCase{ @@ -198,7 +197,7 @@ func testAccNetworkFirewallProxyRulesExclusive_updateRemove(t *testing.T) { ctx := acctest.Context(t) var v1, v2 networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" acctest.Test(ctx, t, resource.TestCase{ @@ -239,7 +238,7 @@ func testAccNetworkFirewallProxyRulesExclusive_multipleRulesPerPhase(t *testing. ctx := acctest.Context(t) var v networkfirewall.DescribeProxyRuleGroupOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy_rules_exclusive.test" acctest.Test(ctx, t, resource.TestCase{ @@ -321,7 +320,7 @@ func testAccCheckProxyRulesExclusiveDestroy(ctx context.Context, t *testing.T) r } } -func testAccCheckProxyRulesExclusiveExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { +func testAccCheckProxyRulesExclusiveExists(ctx context.Context, t *testing.T, n string, v ...*networkfirewall.DescribeProxyRuleGroupOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -336,7 +335,9 @@ func testAccCheckProxyRulesExclusiveExists(ctx context.Context, t *testing.T, n return err } - *v = *output + if len(v) > 0 { + *v[0] = *output + } return nil } @@ -354,7 +355,7 @@ resource "aws_networkfirewall_proxy_rules_exclusive" "test" { pre_dns { proxy_rule_name = "%[1]s-predns" action = "ALLOW" - description = "%[1]s-predns-description" + description = "%[1]s-predns-description" conditions { condition_key = "request:DestinationDomain" @@ -378,7 +379,7 @@ resource "aws_networkfirewall_proxy_rules_exclusive" "test" { post_response { proxy_rule_name = "%[1]s-postresponse" action = "ALERT" - description = "%[1]s-postresponse-description" + description = "%[1]s-postresponse-description" conditions { condition_key = "response:Http:StatusCode" @@ -425,7 +426,7 @@ resource "aws_networkfirewall_proxy_rules_exclusive" "test" { pre_dns { proxy_rule_name = "%[1]s-predns" action = "ALLOW" - description = "%[1]s-predns-description" + description = "%[1]s-predns-description" conditions { condition_key = "request:DestinationDomain" @@ -448,7 +449,7 @@ resource "aws_networkfirewall_proxy_rules_exclusive" "test" { post_response { proxy_rule_name = "%[1]s-postresponse" action = "ALERT" - description = "%[1]s-postresponse-description" + description = "%[1]s-postresponse-description" conditions { condition_key = "response:Http:StatusCode" diff --git a/internal/service/networkfirewall/proxy_test.go b/internal/service/networkfirewall/proxy_test.go index 4ac211577f58..fe3211290308 100644 --- a/internal/service/networkfirewall/proxy_test.go +++ b/internal/service/networkfirewall/proxy_test.go @@ -10,7 +10,6 @@ import ( "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/service/networkfirewall" - sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -25,7 +24,7 @@ func testAccNetworkFirewallProxy_basic(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy.test" acctest.Test(ctx, t, resource.TestCase{ @@ -67,7 +66,7 @@ func testAccNetworkFirewallProxy_disappears(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy.test" acctest.Test(ctx, t, resource.TestCase{ @@ -98,7 +97,7 @@ func testAccNetworkFirewallProxy_tlsInterceptEnabled(t *testing.T) { ctx := acctest.Context(t) var v networkfirewall.DescribeProxyOutput - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_networkfirewall_proxy.test" pcaResourceName := "aws_acmpca_certificate_authority.test" @@ -142,7 +141,7 @@ func testAccNetworkFirewallProxy_logging(t *testing.T) { t.Helper() ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) acctest.Test(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, @@ -207,7 +206,7 @@ func testAccCheckProxyDestroy(ctx context.Context, t *testing.T) resource.TestCh } } -func testAccCheckProxyExists(ctx context.Context, t *testing.T, n string, v *networkfirewall.DescribeProxyOutput) resource.TestCheckFunc { +func testAccCheckProxyExists(ctx context.Context, t *testing.T, n string, v ...*networkfirewall.DescribeProxyOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -222,7 +221,9 @@ func testAccCheckProxyExists(ctx context.Context, t *testing.T, n string, v *net return err } - *v = *output + if len(v) > 0 { + *v[0] = *output + } return nil } @@ -358,12 +359,12 @@ func testAccProxyConfig_basic(rName string) string { testAccProxyConfig_baseProxyConfiguration(rName), fmt.Sprintf(` resource "aws_networkfirewall_proxy" "test" { - name = %[1]q - nat_gateway_id = aws_nat_gateway.test.id + name = %[1]q + nat_gateway_id = aws_nat_gateway.test.id proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn tls_intercept_properties { - tls_intercept_mode = "DISABLED" + tls_intercept_mode = "DISABLED" } listener_properties { @@ -471,7 +472,7 @@ data "aws_region" "current" {} # Create a root CA for TLS interception resource "aws_acmpca_certificate_authority" "test" { - type = "ROOT" + type = "ROOT" certificate_authority_configuration { key_algorithm = "RSA_2048" @@ -509,7 +510,6 @@ resource "aws_acmpca_certificate_authority_certificate" "test" { certificate_chain = aws_acmpca_certificate.test.certificate_chain } - # Grant Network Firewall proxy permission to use the PCA resource "aws_acmpca_policy" "test" { resource_arn = aws_acmpca_certificate_authority.test.arn @@ -564,13 +564,13 @@ resource "aws_acmpca_policy" "test" { resource "aws_ram_resource_share" "test" { name = %[1]q allow_external_principals = true - permission_arns = ["arn:aws:ram::aws:permission/AWSRAMSubordinateCACertificatePathLen0IssuanceCertificateAuthority"] + permission_arns = ["arn:${data.aws_partition.current.partition}:ram::aws:permission/AWSRAMSubordinateCACertificatePathLen0IssuanceCertificateAuthority"] tags = { Name = %[1]q } } - + # Associate the PCA with the RAM share resource "aws_ram_resource_share_associations_exclusive" "test" { principals = ["proxy.network-firewall.amazonaws.com"] @@ -580,17 +580,17 @@ resource "aws_ram_resource_share_associations_exclusive" "test" { lifecycle { ignore_changes = [ - resource_arns + resource_arns ] replace_triggered_by = [ - aws_acmpca_certificate_authority.test + aws_acmpca_certificate_authority.test ] } } resource "aws_networkfirewall_proxy" "test" { - name = %[1]q - nat_gateway_id = aws_nat_gateway.test.id + name = %[1]q + nat_gateway_id = aws_nat_gateway.test.id proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn tls_intercept_properties { diff --git a/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown b/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown index 93125da6c730..5ec59b64ede0 100644 --- a/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown +++ b/website/docs/r/networkfirewall_proxy_rules_exclusive.html.markdown @@ -143,8 +143,8 @@ Each rule block (`post_response`, `pre_dns`, `pre_request`) supports the followi Each `conditions` block supports the following: * `condition_key` - (Required) Attribute to evaluate. Valid values include: - - Request-based: `request:SourceAccount`, `request:SourceVpc`, `request:SourceVpce`, `request:Time`, `request:SourceIp`, `request:DestinationIp`, `request:SourcePort`, `request:DestinationPort`, `request:Protocol`, `request:DestinationDomain`, `request:Http:Uri`, `request:Http:Method`, `request:Http:UserAgent`, `request:Http:ContentType`, `request:Http:Header/` - - Response-based: `response:Http:StatusCode`, `response:Http:ContentType`, `response:Http:Header/` + - Request-based: `request:SourceAccount`, `request:SourceVpc`, `request:SourceVpce`, `request:Time`, `request:SourceIp`, `request:DestinationIp`, `request:SourcePort`, `request:DestinationPort`, `request:Protocol`, `request:DestinationDomain`, `request:Http:Uri`, `request:Http:Method`, `request:Http:UserAgent`, `request:Http:ContentType`, `request:Http:Header/` + - Response-based: `response:Http:StatusCode`, `response:Http:ContentType`, `response:Http:Header/` ~> **NOTE:** HTTP field matching for HTTPS requests requires TLS decryption to be enabled. Without TLS decryption, only IP-based filtering is available in the pre-request phase. * `condition_operator` - (Required) Comparison operator. Valid values: `StringEquals`, `NumericGreaterThan`, `NumericGreaterThanEquals`. From 6c7b341f6bc6ec6bc1c78d6eccf2d9563fbb925a Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Tue, 24 Mar 2026 20:59:42 +1100 Subject: [PATCH 17/19] added list resources --- .../service/networkfirewall/firewall_test.go | 8 + .../proxy_configuration_list.go | 113 +++++++++++ .../proxy_configuration_list_test.go | 178 +++++++++++++++++ .../service/networkfirewall/proxy_list.go | 113 +++++++++++ .../networkfirewall/proxy_list_test.go | 183 ++++++++++++++++++ .../networkfirewall/proxy_rule_group_list.go | 113 +++++++++++ .../proxy_rule_group_list_test.go | 178 +++++++++++++++++ .../networkfirewall/proxy_serial_test.go | 15 ++ .../networkfirewall/service_package_gen.go | 37 ++++ .../testdata/Proxy/list_basic/main.tf | 109 +++++++++++ .../Proxy/list_basic/query.tfquery.hcl | 6 + .../Proxy/list_include_resource/main.tf | 109 +++++++++++ .../list_include_resource/main.tfquery.hcl | 8 + .../Proxy/list_region_override/main.tf | 127 ++++++++++++ .../list_region_override/main.tfquery.hcl | 10 + .../ProxyConfiguration/list_basic/main.tf | 26 +++ .../list_basic/query.tfquery.hcl | 6 + .../list_include_resource/main.tf | 26 +++ .../list_include_resource/main.tfquery.hcl | 8 + .../list_region_override/main.tf | 33 ++++ .../list_region_override/main.tfquery.hcl | 10 + .../ProxyRuleGroup/list_basic/main.tf | 20 ++ .../list_basic/query.tfquery.hcl | 6 + .../list_include_resource/main.tf | 20 ++ .../list_include_resource/main.tfquery.hcl | 8 + .../list_region_override/main.tf | 27 +++ .../list_region_override/main.tfquery.hcl | 10 + 27 files changed, 1507 insertions(+) create mode 100644 internal/service/networkfirewall/proxy_configuration_list.go create mode 100644 internal/service/networkfirewall/proxy_configuration_list_test.go create mode 100644 internal/service/networkfirewall/proxy_list.go create mode 100644 internal/service/networkfirewall/proxy_list_test.go create mode 100644 internal/service/networkfirewall/proxy_rule_group_list.go create mode 100644 internal/service/networkfirewall/proxy_rule_group_list_test.go create mode 100644 internal/service/networkfirewall/testdata/Proxy/list_basic/main.tf create mode 100644 internal/service/networkfirewall/testdata/Proxy/list_basic/query.tfquery.hcl create mode 100644 internal/service/networkfirewall/testdata/Proxy/list_include_resource/main.tf create mode 100644 internal/service/networkfirewall/testdata/Proxy/list_include_resource/main.tfquery.hcl create mode 100644 internal/service/networkfirewall/testdata/Proxy/list_region_override/main.tf create mode 100644 internal/service/networkfirewall/testdata/Proxy/list_region_override/main.tfquery.hcl create mode 100644 internal/service/networkfirewall/testdata/ProxyConfiguration/list_basic/main.tf create mode 100644 internal/service/networkfirewall/testdata/ProxyConfiguration/list_basic/query.tfquery.hcl create mode 100644 internal/service/networkfirewall/testdata/ProxyConfiguration/list_include_resource/main.tf create mode 100644 internal/service/networkfirewall/testdata/ProxyConfiguration/list_include_resource/main.tfquery.hcl create mode 100644 internal/service/networkfirewall/testdata/ProxyConfiguration/list_region_override/main.tf create mode 100644 internal/service/networkfirewall/testdata/ProxyConfiguration/list_region_override/main.tfquery.hcl create mode 100644 internal/service/networkfirewall/testdata/ProxyRuleGroup/list_basic/main.tf create mode 100644 internal/service/networkfirewall/testdata/ProxyRuleGroup/list_basic/query.tfquery.hcl create mode 100644 internal/service/networkfirewall/testdata/ProxyRuleGroup/list_include_resource/main.tf create mode 100644 internal/service/networkfirewall/testdata/ProxyRuleGroup/list_include_resource/main.tfquery.hcl create mode 100644 internal/service/networkfirewall/testdata/ProxyRuleGroup/list_region_override/main.tf create mode 100644 internal/service/networkfirewall/testdata/ProxyRuleGroup/list_region_override/main.tfquery.hcl diff --git a/internal/service/networkfirewall/firewall_test.go b/internal/service/networkfirewall/firewall_test.go index a236eaff0d3c..b6d42b0ff53a 100644 --- a/internal/service/networkfirewall/firewall_test.go +++ b/internal/service/networkfirewall/firewall_test.go @@ -638,6 +638,14 @@ func testAccPreCheck(ctx context.Context, t *testing.T) { } } +// testAccPreCheckProxyGA skips tests that require the Network Firewall Proxy service +// to be Generally Available (GA). During the preview period, only one proxy can be +// created at a time. Set TF_AWS_NETWORKFIREWALL_PROXY_GA=1 to run these tests once +// the service is GA. +func testAccPreCheckProxyGA(t *testing.T) { + acctest.SkipIfEnvVarNotSet(t, "TF_AWS_NETWORKFIREWALL_PROXY_GA") +} + func testAccFirewallConfig_baseVPC(rName string) string { return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 1), fmt.Sprintf(` resource "aws_networkfirewall_firewall_policy" "test" { diff --git a/internal/service/networkfirewall/proxy_configuration_list.go b/internal/service/networkfirewall/proxy_configuration_list.go new file mode 100644 index 000000000000..e99e68e0b0a3 --- /dev/null +++ b/internal/service/networkfirewall/proxy_configuration_list.go @@ -0,0 +1,113 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + "fmt" + "iter" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/hashicorp/terraform-plugin-framework/list" + "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" + "github.com/hashicorp/terraform-provider-aws/internal/retry" +) + +// Function annotations are used for list resource registration to the Provider. DO NOT EDIT. +// @FrameworkListResource("aws_networkfirewall_proxy_configuration") +func newProxyConfigurationResourceAsListResource() list.ListResourceWithConfigure { + return &proxyConfigurationListResource{} +} + +var _ list.ListResource = &proxyConfigurationListResource{} + +type proxyConfigurationListResource struct { + resourceProxyConfiguration + framework.WithList +} + +func (r *proxyConfigurationListResource) List(ctx context.Context, request list.ListRequest, stream *list.ListResultsStream) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var query listProxyConfigurationModel + if request.Config.Raw.IsKnown() && !request.Config.Raw.IsNull() { + if diags := request.Config.Get(ctx, &query); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + } + + stream.Results = func(yield func(list.ListResult) bool) { + result := request.NewListResult(ctx) + var input networkfirewall.ListProxyConfigurationsInput + for summary, err := range listProxyConfigurations(ctx, conn, &input) { + if err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + arn := aws.ToString(summary.Arn) + out, err := findProxyConfigurationByARN(ctx, conn, arn) + if retry.NotFound(err) { + continue + } + if err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + var data resourceProxyConfigurationModel + + r.SetResult(ctx, r.Meta(), request.IncludeResource, &data, &result, func() { + if diags := fwflex.Flatten(ctx, out.ProxyConfiguration, &data); diags.HasError() { + result.Diagnostics.Append(diags...) + yield(result) + return + } + + data.UpdateToken = fwflex.StringToFramework(ctx, out.UpdateToken) + result.DisplayName = aws.ToString(summary.Name) + }) + + if result.Diagnostics.HasError() { + result = list.ListResult{Diagnostics: result.Diagnostics} + yield(result) + return + } + + if !yield(result) { + return + } + } + } +} + +type listProxyConfigurationModel struct { + framework.WithRegionModel +} + +func listProxyConfigurations(ctx context.Context, conn *networkfirewall.Client, input *networkfirewall.ListProxyConfigurationsInput) iter.Seq2[awstypes.ProxyConfigurationMetadata, error] { + return func(yield func(awstypes.ProxyConfigurationMetadata, error) bool) { + pages := networkfirewall.NewListProxyConfigurationsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if err != nil { + yield(awstypes.ProxyConfigurationMetadata{}, fmt.Errorf("listing NetworkFirewall Proxy Configuration resources: %w", err)) + return + } + + for _, item := range page.ProxyConfigurations { + if !yield(item, nil) { + return + } + } + } + } +} diff --git a/internal/service/networkfirewall/proxy_configuration_list_test.go b/internal/service/networkfirewall/proxy_configuration_list_test.go new file mode 100644 index 000000000000..dfbf8c674493 --- /dev/null +++ b/internal/service/networkfirewall/proxy_configuration_list_test.go @@ -0,0 +1,178 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfquerycheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/querycheck" + tfqueryfilter "github.com/hashicorp/terraform-provider-aws/internal/acctest/queryfilter" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccNetworkFirewallProxyConfiguration_List_basic(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy_configuration.test[0]" + resourceName2 := "aws_networkfirewall_proxy_configuration.test[1]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + statecheck.ExpectKnownValue(resourceName2, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity("aws_networkfirewall_proxy_configuration.test", map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + querycheck.ExpectIdentity("aws_networkfirewall_proxy_configuration.test", map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + }, + }, + }, + }) +} + +func testAccNetworkFirewallProxyConfiguration_List_includeResource(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy_configuration.test[0]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + identity1 := tfstatecheck.Identity() + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyConfigurationDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/list_include_resource/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(1), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/list_include_resource/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(1), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy_configuration.test", identity1.Checks()), + querycheck.ExpectResourceKnownValues("aws_networkfirewall_proxy_configuration.test", tfqueryfilter.ByResourceIdentityFunc(identity1.Checks()), []querycheck.KnownValueCheck{ + tfquerycheck.KnownValueCheck(tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + tfquerycheck.KnownValueCheck(tfjsonpath.New(names.AttrName), knownvalue.StringExact(rName+"-0")), + }), + }, + }, + }, + }) +} + +func testAccNetworkFirewallProxyConfiguration_List_regionOverride(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy_configuration.test[0]" + resourceName2 := "aws_networkfirewall_proxy_configuration.test[1]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + identity1 := tfstatecheck.Identity() + identity2 := tfstatecheck.Identity() + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckMultipleRegion(t, 2); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/list_region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + identity2.GetIdentity(resourceName2), + statecheck.ExpectKnownValue(resourceName2, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/ProxyConfiguration/list_region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy_configuration.test", identity1.Checks()), + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy_configuration.test", identity2.Checks()), + }, + }, + }, + }) +} diff --git a/internal/service/networkfirewall/proxy_list.go b/internal/service/networkfirewall/proxy_list.go new file mode 100644 index 000000000000..432b55c921d1 --- /dev/null +++ b/internal/service/networkfirewall/proxy_list.go @@ -0,0 +1,113 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + "fmt" + "iter" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/hashicorp/terraform-plugin-framework/list" + "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" + "github.com/hashicorp/terraform-provider-aws/internal/retry" +) + +// Function annotations are used for list resource registration to the Provider. DO NOT EDIT. +// @FrameworkListResource("aws_networkfirewall_proxy") +func newProxyResourceAsListResource() list.ListResourceWithConfigure { + return &proxyListResource{} +} + +var _ list.ListResource = &proxyListResource{} + +type proxyListResource struct { + resourceProxy + framework.WithList +} + +func (r *proxyListResource) List(ctx context.Context, request list.ListRequest, stream *list.ListResultsStream) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var query listProxyModel + if request.Config.Raw.IsKnown() && !request.Config.Raw.IsNull() { + if diags := request.Config.Get(ctx, &query); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + } + + stream.Results = func(yield func(list.ListResult) bool) { + result := request.NewListResult(ctx) + var input networkfirewall.ListProxiesInput + for proxySummary, err := range listProxies(ctx, conn, &input) { + if err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + arn := aws.ToString(proxySummary.Arn) + out, err := findProxyByARN(ctx, conn, arn) + if retry.NotFound(err) { + continue + } + if err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + var data resourceProxyModel + + r.SetResult(ctx, r.Meta(), request.IncludeResource, &data, &result, func() { + if diags := fwflex.Flatten(ctx, out.Proxy, &data); diags.HasError() { + result.Diagnostics.Append(diags...) + yield(result) + return + } + + data.UpdateToken = fwflex.StringToFramework(ctx, out.UpdateToken) + result.DisplayName = aws.ToString(proxySummary.Name) + }) + + if result.Diagnostics.HasError() { + result = list.ListResult{Diagnostics: result.Diagnostics} + yield(result) + return + } + + if !yield(result) { + return + } + } + } +} + +type listProxyModel struct { + framework.WithRegionModel +} + +func listProxies(ctx context.Context, conn *networkfirewall.Client, input *networkfirewall.ListProxiesInput) iter.Seq2[awstypes.ProxyMetadata, error] { + return func(yield func(awstypes.ProxyMetadata, error) bool) { + pages := networkfirewall.NewListProxiesPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if err != nil { + yield(awstypes.ProxyMetadata{}, fmt.Errorf("listing NetworkFirewall Proxy resources: %w", err)) + return + } + + for _, item := range page.Proxies { + if !yield(item, nil) { + return + } + } + } + } +} diff --git a/internal/service/networkfirewall/proxy_list_test.go b/internal/service/networkfirewall/proxy_list_test.go new file mode 100644 index 000000000000..82902a731159 --- /dev/null +++ b/internal/service/networkfirewall/proxy_list_test.go @@ -0,0 +1,183 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfquerycheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/querycheck" + tfqueryfilter "github.com/hashicorp/terraform-provider-aws/internal/acctest/queryfilter" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccNetworkFirewallProxy_List_basic(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy.test[0]" + resourceName2 := "aws_networkfirewall_proxy.test[1]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t); testAccPreCheckProxyGA(t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + statecheck.ExpectKnownValue(resourceName2, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/Proxy/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity("aws_networkfirewall_proxy.test", map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + querycheck.ExpectIdentity("aws_networkfirewall_proxy.test", map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + }, + }, + }, + }) +} + +func testAccNetworkFirewallProxy_List_includeResource(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy.test[0]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + identity1 := tfstatecheck.Identity() + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/list_include_resource/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(1), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/Proxy/list_include_resource/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(1), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy.test", identity1.Checks()), + querycheck.ExpectResourceKnownValues("aws_networkfirewall_proxy.test", tfqueryfilter.ByResourceIdentityFunc(identity1.Checks()), []querycheck.KnownValueCheck{ + tfquerycheck.KnownValueCheck(tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + tfquerycheck.KnownValueCheck(tfjsonpath.New(names.AttrName), knownvalue.StringExact(rName+"-0")), + }), + }, + }, + }, + }) +} + +func testAccNetworkFirewallProxy_List_regionOverride(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy.test[0]" + resourceName2 := "aws_networkfirewall_proxy.test[1]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + identity1 := tfstatecheck.Identity() + identity2 := tfstatecheck.Identity() + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckMultipleRegion(t, 2) + testAccPreCheck(ctx, t) + testAccPreCheckProxyGA(t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/Proxy/list_region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + identity2.GetIdentity(resourceName2), + statecheck.ExpectKnownValue(resourceName2, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/Proxy/list_region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy.test", identity1.Checks()), + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy.test", identity2.Checks()), + }, + }, + }, + }) +} diff --git a/internal/service/networkfirewall/proxy_rule_group_list.go b/internal/service/networkfirewall/proxy_rule_group_list.go new file mode 100644 index 000000000000..f9431f6386f9 --- /dev/null +++ b/internal/service/networkfirewall/proxy_rule_group_list.go @@ -0,0 +1,113 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall + +import ( + "context" + "fmt" + "iter" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkfirewall/types" + "github.com/hashicorp/terraform-plugin-framework/list" + "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" + "github.com/hashicorp/terraform-provider-aws/internal/retry" +) + +// Function annotations are used for list resource registration to the Provider. DO NOT EDIT. +// @FrameworkListResource("aws_networkfirewall_proxy_rule_group") +func newProxyRuleGroupResourceAsListResource() list.ListResourceWithConfigure { + return &proxyRuleGroupListResource{} +} + +var _ list.ListResource = &proxyRuleGroupListResource{} + +type proxyRuleGroupListResource struct { + resourceProxyRuleGroup + framework.WithList +} + +func (r *proxyRuleGroupListResource) List(ctx context.Context, request list.ListRequest, stream *list.ListResultsStream) { + conn := r.Meta().NetworkFirewallClient(ctx) + + var query listProxyRuleGroupModel + if request.Config.Raw.IsKnown() && !request.Config.Raw.IsNull() { + if diags := request.Config.Get(ctx, &query); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + } + + stream.Results = func(yield func(list.ListResult) bool) { + result := request.NewListResult(ctx) + var input networkfirewall.ListProxyRuleGroupsInput + for summary, err := range listProxyRuleGroups(ctx, conn, &input) { + if err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + arn := aws.ToString(summary.Arn) + out, err := findProxyRuleGroupByARN(ctx, conn, arn) + if retry.NotFound(err) { + continue + } + if err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + var data resourceProxyRuleGroupModel + + r.SetResult(ctx, r.Meta(), request.IncludeResource, &data, &result, func() { + if diags := fwflex.Flatten(ctx, out.ProxyRuleGroup, &data); diags.HasError() { + result.Diagnostics.Append(diags...) + yield(result) + return + } + + data.UpdateToken = fwflex.StringToFramework(ctx, out.UpdateToken) + result.DisplayName = aws.ToString(summary.Name) + }) + + if result.Diagnostics.HasError() { + result = list.ListResult{Diagnostics: result.Diagnostics} + yield(result) + return + } + + if !yield(result) { + return + } + } + } +} + +type listProxyRuleGroupModel struct { + framework.WithRegionModel +} + +func listProxyRuleGroups(ctx context.Context, conn *networkfirewall.Client, input *networkfirewall.ListProxyRuleGroupsInput) iter.Seq2[awstypes.ProxyRuleGroupMetadata, error] { + return func(yield func(awstypes.ProxyRuleGroupMetadata, error) bool) { + pages := networkfirewall.NewListProxyRuleGroupsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if err != nil { + yield(awstypes.ProxyRuleGroupMetadata{}, fmt.Errorf("listing NetworkFirewall Proxy Rule Group resources: %w", err)) + return + } + + for _, item := range page.ProxyRuleGroups { + if !yield(item, nil) { + return + } + } + } + } +} diff --git a/internal/service/networkfirewall/proxy_rule_group_list_test.go b/internal/service/networkfirewall/proxy_rule_group_list_test.go new file mode 100644 index 000000000000..ba180ae597fd --- /dev/null +++ b/internal/service/networkfirewall/proxy_rule_group_list_test.go @@ -0,0 +1,178 @@ +// Copyright IBM Corp. 2014, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package networkfirewall_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfquerycheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/querycheck" + tfqueryfilter "github.com/hashicorp/terraform-provider-aws/internal/acctest/queryfilter" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccNetworkFirewallProxyRuleGroup_List_basic(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy_rule_group.test[0]" + resourceName2 := "aws_networkfirewall_proxy_rule_group.test[1]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + statecheck.ExpectKnownValue(resourceName2, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectIdentity("aws_networkfirewall_proxy_rule_group.test", map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + querycheck.ExpectIdentity("aws_networkfirewall_proxy_rule_group.test", map[string]knownvalue.Check{ + names.AttrARN: knownvalue.NotNull(), + }), + }, + }, + }, + }) +} + +func testAccNetworkFirewallProxyRuleGroup_List_includeResource(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy_rule_group.test[0]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + identity1 := tfstatecheck.Identity() + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProxyRuleGroupDestroy(ctx, t), + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/list_include_resource/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(1), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/list_include_resource/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(1), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy_rule_group.test", identity1.Checks()), + querycheck.ExpectResourceKnownValues("aws_networkfirewall_proxy_rule_group.test", tfqueryfilter.ByResourceIdentityFunc(identity1.Checks()), []querycheck.KnownValueCheck{ + tfquerycheck.KnownValueCheck(tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + tfquerycheck.KnownValueCheck(tfjsonpath.New(names.AttrName), knownvalue.StringExact(rName+"-0")), + }), + }, + }, + }, + }) +} + +func testAccNetworkFirewallProxyRuleGroup_List_regionOverride(t *testing.T) { + t.Helper() + + ctx := acctest.Context(t) + + resourceName1 := "aws_networkfirewall_proxy_rule_group.test[0]" + resourceName2 := "aws_networkfirewall_proxy_rule_group.test[1]" + rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) + identity1 := tfstatecheck.Identity() + identity2 := tfstatecheck.Identity() + + acctest.Test(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckMultipleRegion(t, 2); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFirewallServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: acctest.CheckDestroyNoop, + Steps: []resource.TestStep{ + // Step 1: Setup + { + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/list_region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + identity2.GetIdentity(resourceName2), + statecheck.ExpectKnownValue(resourceName2, tfjsonpath.New(names.AttrARN), knownvalue.NotNull()), + }, + }, + + // Step 2: Query + { + Query: true, + ConfigDirectory: config.StaticDirectory("testdata/ProxyRuleGroup/list_region_override/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + "resource_count": config.IntegerVariable(2), + "region": config.StringVariable(acctest.AlternateRegion()), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy_rule_group.test", identity1.Checks()), + tfquerycheck.ExpectIdentityFunc("aws_networkfirewall_proxy_rule_group.test", identity2.Checks()), + }, + }, + }, + }) +} diff --git a/internal/service/networkfirewall/proxy_serial_test.go b/internal/service/networkfirewall/proxy_serial_test.go index 967a7c7e5eff..8a3689d8ab66 100644 --- a/internal/service/networkfirewall/proxy_serial_test.go +++ b/internal/service/networkfirewall/proxy_serial_test.go @@ -21,16 +21,31 @@ func TestAccNetworkFirewallProxy_serial(t *testing.T) { "tlsInterceptEnabled": testAccNetworkFirewallProxy_tlsInterceptEnabled, "logging": testAccNetworkFirewallProxy_logging, }, + "ProxyList": { + "basic": testAccNetworkFirewallProxy_List_basic, + "includeResource": testAccNetworkFirewallProxy_List_includeResource, + "regionOverride": testAccNetworkFirewallProxy_List_regionOverride, + }, "ProxyConfiguration": { acctest.CtBasic: testAccNetworkFirewallProxyConfiguration_basic, acctest.CtDisappears: testAccNetworkFirewallProxyConfiguration_disappears, "tags": testAccNetworkFirewallProxyConfiguration_tags, }, + "ProxyConfigurationList": { + "basic": testAccNetworkFirewallProxyConfiguration_List_basic, + "includeResource": testAccNetworkFirewallProxyConfiguration_List_includeResource, + "regionOverride": testAccNetworkFirewallProxyConfiguration_List_regionOverride, + }, "ProxyRuleGroup": { acctest.CtBasic: testAccNetworkFirewallProxyRuleGroup_basic, acctest.CtDisappears: testAccNetworkFirewallProxyRuleGroup_disappears, "tags": testAccNetworkFirewallProxyRuleGroup_tags, }, + "ProxyRuleGroupList": { + "basic": testAccNetworkFirewallProxyRuleGroup_List_basic, + "includeResource": testAccNetworkFirewallProxyRuleGroup_List_includeResource, + "regionOverride": testAccNetworkFirewallProxyRuleGroup_List_regionOverride, + }, "ProxyRulesExclusive": { acctest.CtBasic: testAccNetworkFirewallProxyRulesExclusive_basic, acctest.CtDisappears: testAccNetworkFirewallProxyRulesExclusive_disappears, diff --git a/internal/service/networkfirewall/service_package_gen.go b/internal/service/networkfirewall/service_package_gen.go index 99483b09e2fb..d5b679c642d6 100644 --- a/internal/service/networkfirewall/service_package_gen.go +++ b/internal/service/networkfirewall/service_package_gen.go @@ -7,6 +7,8 @@ package networkfirewall import ( "context" + "iter" + "slices" "unique" "github.com/aws/aws-sdk-go-v2/aws" @@ -116,6 +118,41 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser } } +func (p *servicePackage) FrameworkListResources(ctx context.Context) iter.Seq[*inttypes.ServicePackageFrameworkListResource] { + return slices.Values([]*inttypes.ServicePackageFrameworkListResource{ + { + Factory: newProxyResourceAsListResource, + TypeName: "aws_networkfirewall_proxy", + Name: "Proxy", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }), + Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalARNIdentity(inttypes.WithIdentityDuplicateAttrs(names.AttrID)), + }, + { + Factory: newProxyConfigurationResourceAsListResource, + TypeName: "aws_networkfirewall_proxy_configuration", + Name: "Proxy Configuration", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }), + Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalARNIdentity(inttypes.WithIdentityDuplicateAttrs(names.AttrID)), + }, + { + Factory: newProxyRuleGroupResourceAsListResource, + TypeName: "aws_networkfirewall_proxy_rule_group", + Name: "Proxy Rule Group", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }), + Region: unique.Make(inttypes.ResourceRegionDefault()), + Identity: inttypes.RegionalARNIdentity(inttypes.WithIdentityDuplicateAttrs(names.AttrID)), + }, + }) +} + func (p *servicePackage) SDKDataSources(ctx context.Context) []*inttypes.ServicePackageSDKDataSource { return []*inttypes.ServicePackageSDKDataSource{ { diff --git a/internal/service/networkfirewall/testdata/Proxy/list_basic/main.tf b/internal/service/networkfirewall/testdata/Proxy/list_basic/main.tf new file mode 100644 index 000000000000..df36f29123f4 --- /dev/null +++ b/internal/service/networkfirewall/testdata/Proxy/list_basic/main.tf @@ -0,0 +1,109 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_subnet" "public" { + vpc_id = aws_vpc.test.id + cidr_block = "10.0.1.0/24" + availability_zone = data.aws_availability_zones.available.names[0] + map_public_ip_on_launch = true + + tags = { + Name = "${var.rName}-public" + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = var.rName + } +} + +resource "aws_eip" "test" { + count = var.resource_count + + domain = "vpc" + + tags = { + Name = "${var.rName}-${count.index}" + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_nat_gateway" "test" { + count = var.resource_count + + allocation_id = aws_eip.test[count.index].id + subnet_id = aws_subnet.public.id + + tags = { + Name = "${var.rName}-${count.index}" + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_networkfirewall_proxy_configuration" "test" { + name = var.rName + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +resource "aws_networkfirewall_proxy" "test" { + count = var.resource_count + + name = "${var.rName}-${count.index}" + nat_gateway_id = aws_nat_gateway.test[count.index].id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + tls_intercept_properties { + tls_intercept_mode = "DISABLED" + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/Proxy/list_basic/query.tfquery.hcl b/internal/service/networkfirewall/testdata/Proxy/list_basic/query.tfquery.hcl new file mode 100644 index 000000000000..207e9741c725 --- /dev/null +++ b/internal/service/networkfirewall/testdata/Proxy/list_basic/query.tfquery.hcl @@ -0,0 +1,6 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy" "test" { + provider = aws +} diff --git a/internal/service/networkfirewall/testdata/Proxy/list_include_resource/main.tf b/internal/service/networkfirewall/testdata/Proxy/list_include_resource/main.tf new file mode 100644 index 000000000000..df36f29123f4 --- /dev/null +++ b/internal/service/networkfirewall/testdata/Proxy/list_include_resource/main.tf @@ -0,0 +1,109 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_subnet" "public" { + vpc_id = aws_vpc.test.id + cidr_block = "10.0.1.0/24" + availability_zone = data.aws_availability_zones.available.names[0] + map_public_ip_on_launch = true + + tags = { + Name = "${var.rName}-public" + } +} + +resource "aws_internet_gateway" "test" { + vpc_id = aws_vpc.test.id + + tags = { + Name = var.rName + } +} + +resource "aws_eip" "test" { + count = var.resource_count + + domain = "vpc" + + tags = { + Name = "${var.rName}-${count.index}" + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_nat_gateway" "test" { + count = var.resource_count + + allocation_id = aws_eip.test[count.index].id + subnet_id = aws_subnet.public.id + + tags = { + Name = "${var.rName}-${count.index}" + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_networkfirewall_proxy_configuration" "test" { + name = var.rName + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +resource "aws_networkfirewall_proxy" "test" { + count = var.resource_count + + name = "${var.rName}-${count.index}" + nat_gateway_id = aws_nat_gateway.test[count.index].id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + tls_intercept_properties { + tls_intercept_mode = "DISABLED" + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/Proxy/list_include_resource/main.tfquery.hcl b/internal/service/networkfirewall/testdata/Proxy/list_include_resource/main.tfquery.hcl new file mode 100644 index 000000000000..f71313cc9857 --- /dev/null +++ b/internal/service/networkfirewall/testdata/Proxy/list_include_resource/main.tfquery.hcl @@ -0,0 +1,8 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy" "test" { + provider = aws + + include_resource = true +} diff --git a/internal/service/networkfirewall/testdata/Proxy/list_region_override/main.tf b/internal/service/networkfirewall/testdata/Proxy/list_region_override/main.tf new file mode 100644 index 000000000000..7c2455fd0801 --- /dev/null +++ b/internal/service/networkfirewall/testdata/Proxy/list_region_override/main.tf @@ -0,0 +1,127 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +data "aws_availability_zones" "available" { + region = var.region + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_vpc" "test" { + region = var.region + + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = var.rName + } +} + +resource "aws_subnet" "public" { + region = var.region + + vpc_id = aws_vpc.test.id + cidr_block = "10.0.1.0/24" + availability_zone = data.aws_availability_zones.available.names[0] + map_public_ip_on_launch = true + + tags = { + Name = "${var.rName}-public" + } +} + +resource "aws_internet_gateway" "test" { + region = var.region + + vpc_id = aws_vpc.test.id + + tags = { + Name = var.rName + } +} + +resource "aws_eip" "test" { + count = var.resource_count + region = var.region + + domain = "vpc" + + tags = { + Name = "${var.rName}-${count.index}" + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_nat_gateway" "test" { + count = var.resource_count + region = var.region + + allocation_id = aws_eip.test[count.index].id + subnet_id = aws_subnet.public.id + + tags = { + Name = "${var.rName}-${count.index}" + } + + depends_on = [aws_internet_gateway.test] +} + +resource "aws_networkfirewall_proxy_configuration" "test" { + region = var.region + + name = var.rName + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +resource "aws_networkfirewall_proxy" "test" { + count = var.resource_count + region = var.region + + name = "${var.rName}-${count.index}" + nat_gateway_id = aws_nat_gateway.test[count.index].id + proxy_configuration_arn = aws_networkfirewall_proxy_configuration.test.arn + + tls_intercept_properties { + tls_intercept_mode = "DISABLED" + } + + listener_properties { + port = 8080 + type = "HTTP" + } + + listener_properties { + port = 443 + type = "HTTPS" + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} + +variable "region" { + description = "Region to deploy resource in" + type = string + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/Proxy/list_region_override/main.tfquery.hcl b/internal/service/networkfirewall/testdata/Proxy/list_region_override/main.tfquery.hcl new file mode 100644 index 000000000000..2928e18044e7 --- /dev/null +++ b/internal/service/networkfirewall/testdata/Proxy/list_region_override/main.tfquery.hcl @@ -0,0 +1,10 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy" "test" { + provider = aws + + config { + region = var.region + } +} diff --git a/internal/service/networkfirewall/testdata/ProxyConfiguration/list_basic/main.tf b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_basic/main.tf new file mode 100644 index 000000000000..7843b92714d5 --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_basic/main.tf @@ -0,0 +1,26 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_networkfirewall_proxy_configuration" "test" { + count = var.resource_count + + name = "${var.rName}-${count.index}" + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/ProxyConfiguration/list_basic/query.tfquery.hcl b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_basic/query.tfquery.hcl new file mode 100644 index 000000000000..b79d94160903 --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_basic/query.tfquery.hcl @@ -0,0 +1,6 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy_configuration" "test" { + provider = aws +} diff --git a/internal/service/networkfirewall/testdata/ProxyConfiguration/list_include_resource/main.tf b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_include_resource/main.tf new file mode 100644 index 000000000000..7843b92714d5 --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_include_resource/main.tf @@ -0,0 +1,26 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_networkfirewall_proxy_configuration" "test" { + count = var.resource_count + + name = "${var.rName}-${count.index}" + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/ProxyConfiguration/list_include_resource/main.tfquery.hcl b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_include_resource/main.tfquery.hcl new file mode 100644 index 000000000000..bfc6d60e3d5a --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_include_resource/main.tfquery.hcl @@ -0,0 +1,8 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy_configuration" "test" { + provider = aws + + include_resource = true +} diff --git a/internal/service/networkfirewall/testdata/ProxyConfiguration/list_region_override/main.tf b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_region_override/main.tf new file mode 100644 index 000000000000..4f5fbb0abdd8 --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_region_override/main.tf @@ -0,0 +1,33 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_networkfirewall_proxy_configuration" "test" { + region = var.region + count = var.resource_count + + name = "${var.rName}-${count.index}" + + default_rule_phase_actions { + post_response = "ALLOW" + pre_dns = "ALLOW" + pre_request = "ALLOW" + } +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} + +variable "region" { + description = "Region to deploy resource in" + type = string + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/ProxyConfiguration/list_region_override/main.tfquery.hcl b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_region_override/main.tfquery.hcl new file mode 100644 index 000000000000..968af900f58d --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyConfiguration/list_region_override/main.tfquery.hcl @@ -0,0 +1,10 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy_configuration" "test" { + provider = aws + + config { + region = var.region + } +} diff --git a/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_basic/main.tf b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_basic/main.tf new file mode 100644 index 000000000000..22e61dcd85fe --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_basic/main.tf @@ -0,0 +1,20 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_networkfirewall_proxy_rule_group" "test" { + count = var.resource_count + + name = "${var.rName}-${count.index}" +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_basic/query.tfquery.hcl b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_basic/query.tfquery.hcl new file mode 100644 index 000000000000..6ff80916e705 --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_basic/query.tfquery.hcl @@ -0,0 +1,6 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy_rule_group" "test" { + provider = aws +} diff --git a/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_include_resource/main.tf b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_include_resource/main.tf new file mode 100644 index 000000000000..22e61dcd85fe --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_include_resource/main.tf @@ -0,0 +1,20 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_networkfirewall_proxy_rule_group" "test" { + count = var.resource_count + + name = "${var.rName}-${count.index}" +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_include_resource/main.tfquery.hcl b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_include_resource/main.tfquery.hcl new file mode 100644 index 000000000000..f63c0b143f68 --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_include_resource/main.tfquery.hcl @@ -0,0 +1,8 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy_rule_group" "test" { + provider = aws + + include_resource = true +} diff --git a/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_region_override/main.tf b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_region_override/main.tf new file mode 100644 index 000000000000..63f9688e044e --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_region_override/main.tf @@ -0,0 +1,27 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_networkfirewall_proxy_rule_group" "test" { + region = var.region + count = var.resource_count + + name = "${var.rName}-${count.index}" +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} + +variable "resource_count" { + description = "Number of resources to create" + type = number + nullable = false +} + +variable "region" { + description = "Region to deploy resource in" + type = string + nullable = false +} diff --git a/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_region_override/main.tfquery.hcl b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_region_override/main.tfquery.hcl new file mode 100644 index 000000000000..b48864ca17b7 --- /dev/null +++ b/internal/service/networkfirewall/testdata/ProxyRuleGroup/list_region_override/main.tfquery.hcl @@ -0,0 +1,10 @@ +# Copyright IBM Corp. 2014, 2026 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_networkfirewall_proxy_rule_group" "test" { + provider = aws + + config { + region = var.region + } +} From 778ac0e904e79238f58740718bedf3717e24e69a Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Tue, 24 Mar 2026 21:04:08 +1100 Subject: [PATCH 18/19] added list resources changelog --- .changelog/46939.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.changelog/46939.txt b/.changelog/46939.txt index b15737a1bfb7..a84c1e560f80 100644 --- a/.changelog/46939.txt +++ b/.changelog/46939.txt @@ -17,3 +17,15 @@ aws_networkfirewall_proxy_rules_exclusive ```release-note:new-resource aws_networkfirewall_proxy_configuration_rule_group_attachments_exclusive ``` + +```release-note:new-list-resource +aws_networkfirewall_proxy +``` + +```release-note:new-list-resource +aws_networkfirewall_proxy_configuration +``` + +```release-note:new-list-resource +aws_networkfirewall_proxy_rule_group +``` From b060f8ab18c86e928328d62d73ecf3126ff2767b Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Tue, 24 Mar 2026 21:31:08 +1100 Subject: [PATCH 19/19] fixed semgrep --- internal/service/networkfirewall/proxy_serial_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/service/networkfirewall/proxy_serial_test.go b/internal/service/networkfirewall/proxy_serial_test.go index 8a3689d8ab66..e60f78f0ede7 100644 --- a/internal/service/networkfirewall/proxy_serial_test.go +++ b/internal/service/networkfirewall/proxy_serial_test.go @@ -22,7 +22,7 @@ func TestAccNetworkFirewallProxy_serial(t *testing.T) { "logging": testAccNetworkFirewallProxy_logging, }, "ProxyList": { - "basic": testAccNetworkFirewallProxy_List_basic, + acctest.CtBasic: testAccNetworkFirewallProxy_List_basic, "includeResource": testAccNetworkFirewallProxy_List_includeResource, "regionOverride": testAccNetworkFirewallProxy_List_regionOverride, }, @@ -32,7 +32,7 @@ func TestAccNetworkFirewallProxy_serial(t *testing.T) { "tags": testAccNetworkFirewallProxyConfiguration_tags, }, "ProxyConfigurationList": { - "basic": testAccNetworkFirewallProxyConfiguration_List_basic, + acctest.CtBasic: testAccNetworkFirewallProxyConfiguration_List_basic, "includeResource": testAccNetworkFirewallProxyConfiguration_List_includeResource, "regionOverride": testAccNetworkFirewallProxyConfiguration_List_regionOverride, }, @@ -42,7 +42,7 @@ func TestAccNetworkFirewallProxy_serial(t *testing.T) { "tags": testAccNetworkFirewallProxyRuleGroup_tags, }, "ProxyRuleGroupList": { - "basic": testAccNetworkFirewallProxyRuleGroup_List_basic, + acctest.CtBasic: testAccNetworkFirewallProxyRuleGroup_List_basic, "includeResource": testAccNetworkFirewallProxyRuleGroup_List_includeResource, "regionOverride": testAccNetworkFirewallProxyRuleGroup_List_regionOverride, },