diff --git a/.changelog/44782.txt b/.changelog/44782.txt new file mode 100644 index 000000000000..9095a9c704ff --- /dev/null +++ b/.changelog/44782.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_networkflowmonitor_monitor +``` + +```release-note:new-resource +aws_networkflowmonitor_scope +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 489a3d36f2f0..2094417d2a98 100644 --- a/go.mod +++ b/go.mod @@ -289,6 +289,7 @@ require ( github.com/hashicorp/go-cty v1.5.0 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/go-set/v3 v3.0.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/hcl/v2 v2.24.0 diff --git a/go.sum b/go.sum index f239954cf487..499e94115c97 100644 --- a/go.sum +++ b/go.sum @@ -658,6 +658,8 @@ github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshf github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-set/v3 v3.0.1 h1:ZwO15ZYmIrFYL9zSm2wBuwcRiHxVdp46m/XA/MUlM6I= +github.com/hashicorp/go-set/v3 v3.0.1/go.mod h1:0oPQqhtitglZeT2ZiWnRIfUG6gJAHnn7LzrS7SbgNY4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -763,6 +765,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shoenig/test v1.12.1 h1:mLHfnMv7gmhhP44WrvT+nKSxKkPDiNkIuHGdIGI9RLU= +github.com/shoenig/test v1.12.1/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= diff --git a/internal/service/networkflowmonitor/exports_test.go b/internal/service/networkflowmonitor/exports_test.go new file mode 100644 index 000000000000..458b6ede9dfa --- /dev/null +++ b/internal/service/networkflowmonitor/exports_test.go @@ -0,0 +1,13 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkflowmonitor + +// Exports for use in tests only. +var ( + ResourceMonitor = newMonitorResource + ResourceScope = newScopeResource + + FindMonitorByName = findMonitorByName + FindScopeByID = findScopeByID +) diff --git a/internal/service/networkflowmonitor/generate.go b/internal/service/networkflowmonitor/generate.go index ef406938b6b1..4bf50c2009ed 100644 --- a/internal/service/networkflowmonitor/generate.go +++ b/internal/service/networkflowmonitor/generate.go @@ -3,7 +3,6 @@ //go:generate go run ../../generate/servicepackage/main.go //go:generate go run ../../generate/tags/main.go -KVTValues -ServiceTagsMap -ListTags -UpdateTags -//go:generate go run ../../generate/tagstests/main.go // ONLY generate directives and package declaration! Do not add anything else to this file. package networkflowmonitor diff --git a/internal/service/networkflowmonitor/monitor.go b/internal/service/networkflowmonitor/monitor.go new file mode 100644 index 000000000000..2190ea80191f --- /dev/null +++ b/internal/service/networkflowmonitor/monitor.go @@ -0,0 +1,418 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkflowmonitor + +import ( + "context" + "fmt" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkflowmonitor" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkflowmonitor/types" + set "github.com/hashicorp/go-set/v3" + uuid "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "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" + "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" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + 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_networkflowmonitor_monitor", name="Monitor") +// @Tags(identifierAttribute="monitor_arn") +func newMonitorResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &monitorResource{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +type monitorResource struct { + framework.ResourceWithModel[monitorResourceModel] + framework.WithTimeouts +} + +func (r *monitorResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "monitor_arn": framework.ARNAttributeComputedOnly(), + "monitor_name": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 255), + stringvalidator.RegexMatches(regexache.MustCompile(`[a-zA-Z0-9_.-]+`), ""), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "scope_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + }, + Blocks: map[string]schema.Block{ + "local_resource": schema.SetNestedBlock{ + CustomType: fwtypes.NewSetNestedObjectTypeOf[monitorLocalResourceModel](ctx), + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrIdentifier: schema.StringAttribute{ + Required: true, + }, + names.AttrType: schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.MonitorLocalResourceType](), + Required: true, + }, + }, + }, + }, + "remote_resource": schema.SetNestedBlock{ + CustomType: fwtypes.NewSetNestedObjectTypeOf[monitorRemoteResourceModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrIdentifier: schema.StringAttribute{ + Required: true, + }, + names.AttrType: schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.MonitorRemoteResourceType](), + Required: true, + }, + }, + }, + }, + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *monitorResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data monitorResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkFlowMonitorClient(ctx) + + var input networkflowmonitor.CreateMonitorInput + response.Diagnostics.Append(fwflex.Expand(ctx, data, &input)...) + if response.Diagnostics.HasError() { + return + } + + // Additional fields. + uuid, _ := uuid.GenerateUUID() + input.ClientToken = aws.String(uuid) + input.Tags = getTagsIn(ctx) + + output, err := conn.CreateMonitor(ctx, &input) + + if err != nil { + response.Diagnostics.AddError("creating Network Flow Monitor Monitor", err.Error()) + return + } + + // Set values for unknowns. + data.MonitorARN = fwflex.StringToFramework(ctx, output.MonitorArn) + + monitorName := fwflex.StringValueFromFramework(ctx, data.MonitorName) + if _, err := waitMonitorCreated(ctx, conn, monitorName, r.CreateTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Network Flow Monitor Monitor (%s) create", monitorName), err.Error()) + return + } + + response.Diagnostics.Append(response.State.Set(ctx, data)...) +} + +func (r *monitorResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data monitorResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkFlowMonitorClient(ctx) + + monitorName := fwflex.StringValueFromFramework(ctx, data.MonitorName) + output, err := findMonitorByName(ctx, conn, monitorName) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading Network Flow Monitor Monitor (%s)", monitorName), err.Error()) + return + } + + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { + return + } + + setTagsOut(ctx, output.Tags) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *monitorResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new monitorResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkFlowMonitorClient(ctx) + + diff, d := fwflex.Diff(ctx, new, old) + response.Diagnostics.Append(d...) + if response.Diagnostics.HasError() { + return + } + + if diff.HasChanges() { + var oldLocalResources, newLocalResources []awstypes.MonitorLocalResource + response.Diagnostics.Append(fwflex.Expand(ctx, old.LocalResources, &oldLocalResources)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(fwflex.Expand(ctx, new.LocalResources, &newLocalResources)...) + if response.Diagnostics.HasError() { + return + } + + hashLocalResource := func(v awstypes.MonitorLocalResource) string { + return string(v.Type) + ":" + aws.ToString(v.Identifier) + } + osLocalResource, nsLocalResource := set.HashSetFromFunc(oldLocalResources, hashLocalResource), set.HashSetFromFunc(newLocalResources, hashLocalResource) + + var oldRemoteResources, newRemoteResources []awstypes.MonitorRemoteResource + response.Diagnostics.Append(fwflex.Expand(ctx, old.RemoteResources, &oldRemoteResources)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(fwflex.Expand(ctx, new.RemoteResources, &newRemoteResources)...) + if response.Diagnostics.HasError() { + return + } + + hashRemoteResource := func(v awstypes.MonitorRemoteResource) string { + return string(v.Type) + ":" + aws.ToString(v.Identifier) + } + osRemoteResource, nsRemoteResource := set.HashSetFromFunc(oldRemoteResources, hashRemoteResource), set.HashSetFromFunc(newRemoteResources, hashRemoteResource) + + monitorName := fwflex.StringValueFromFramework(ctx, new.MonitorName) + input := networkflowmonitor.UpdateMonitorInput{ + LocalResourcesToAdd: nsLocalResource.Difference(osLocalResource).Slice(), + LocalResourcesToRemove: osLocalResource.Difference(nsLocalResource).Slice(), + MonitorName: aws.String(monitorName), + RemoteResourcesToAdd: nsRemoteResource.Difference(osRemoteResource).Slice(), + RemoteResourcesToRemove: osRemoteResource.Difference(nsRemoteResource).Slice(), + } + + _, err := conn.UpdateMonitor(ctx, &input) + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating Network Flow Monitor Monitor (%s)", monitorName), err.Error()) + return + } + + if _, err := waitMonitorUpdated(ctx, conn, monitorName, r.UpdateTimeout(ctx, new.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Network Flow Monitor Monitor (%s) update", monitorName), err.Error()) + return + } + } + + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *monitorResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data monitorResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkFlowMonitorClient(ctx) + + monitorName := fwflex.StringValueFromFramework(ctx, data.MonitorName) + input := networkflowmonitor.DeleteMonitorInput{ + MonitorName: aws.String(monitorName), + } + _, err := conn.DeleteMonitor(ctx, &input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting Network Flow Monitor Monitor (%s)", monitorName), err.Error()) + return + } + + if _, err := waitMonitorDeleted(ctx, conn, monitorName, r.DeleteTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Network Flow Monitor Monitor (%s) delete", monitorName), err.Error()) + return + } +} + +func (r *monitorResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("monitor_name"), request, response) +} + +func findMonitorByName(ctx context.Context, conn *networkflowmonitor.Client, name string) (*networkflowmonitor.GetMonitorOutput, error) { + input := networkflowmonitor.GetMonitorInput{ + MonitorName: aws.String(name), + } + + return findMonitor(ctx, conn, &input) +} + +func findMonitor(ctx context.Context, conn *networkflowmonitor.Client, input *networkflowmonitor.GetMonitorInput) (*networkflowmonitor.GetMonitorOutput, error) { + output, err := conn.GetMonitor(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func statusMonitor(ctx context.Context, conn *networkflowmonitor.Client, name string) retry.StateRefreshFunc { + return func() (any, string, error) { + output, err := findMonitorByName(ctx, conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.MonitorStatus), nil + } +} + +func waitMonitorCreated(ctx context.Context, conn *networkflowmonitor.Client, name string, timeout time.Duration) (*networkflowmonitor.GetMonitorOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.MonitorStatusPending), + Target: enum.Slice(awstypes.MonitorStatusActive), + Refresh: statusMonitor(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkflowmonitor.GetMonitorOutput); ok { + return output, err + } + + return nil, err +} + +func waitMonitorUpdated(ctx context.Context, conn *networkflowmonitor.Client, name string, timeout time.Duration) (*networkflowmonitor.GetMonitorOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.MonitorStatusPending), + Target: enum.Slice(awstypes.MonitorStatusActive), + Refresh: statusMonitor(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkflowmonitor.GetMonitorOutput); ok { + return output, err + } + + return nil, err +} + +func waitMonitorDeleted(ctx context.Context, conn *networkflowmonitor.Client, name string, timeout time.Duration) (*networkflowmonitor.GetMonitorOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.MonitorStatusDeleting), + Target: []string{}, + Refresh: statusMonitor(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkflowmonitor.GetMonitorOutput); ok { + return output, err + } + + return nil, err +} + +type monitorResourceModel struct { + framework.WithRegionModel + LocalResources fwtypes.SetNestedObjectValueOf[monitorLocalResourceModel] `tfsdk:"local_resource"` + MonitorARN types.String `tfsdk:"monitor_arn"` + MonitorName types.String `tfsdk:"monitor_name"` + RemoteResources fwtypes.SetNestedObjectValueOf[monitorRemoteResourceModel] `tfsdk:"remote_resource"` + ScopeARN fwtypes.ARN `tfsdk:"scope_arn"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type monitorLocalResourceModel struct { + Identifier types.String `tfsdk:"identifier"` + Type fwtypes.StringEnum[awstypes.MonitorLocalResourceType] `tfsdk:"type"` +} + +type monitorRemoteResourceModel struct { + Identifier types.String `tfsdk:"identifier"` + Type fwtypes.StringEnum[awstypes.MonitorRemoteResourceType] `tfsdk:"type"` +} diff --git a/internal/service/networkflowmonitor/monitor_test.go b/internal/service/networkflowmonitor/monitor_test.go new file mode 100644 index 000000000000..cddc4e7454d2 --- /dev/null +++ b/internal/service/networkflowmonitor/monitor_test.go @@ -0,0 +1,502 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkflowmonitor_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/networkflowmonitor" + "github.com/hashicorp/aws-sdk-go-base/v2/endpoints" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "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/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfknownvalue "github.com/hashicorp/terraform-provider-aws/internal/acctest/knownvalue" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkflowmonitor "github.com/hashicorp/terraform-provider-aws/internal/service/networkflowmonitor" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccMonitor_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkflowmonitor_monitor.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartition(t, endpoints.AwsPartitionID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFlowMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckMonitorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccMonitorConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("monitor_arn"), tfknownvalue.RegionalARNExact("networkflowmonitor", `monitor/`+rName)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.Null()), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "monitor_name"), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "monitor_name", + ImportStateVerifyIgnore: []string{"scope_arn"}, + }, + }, + }) +} + +func testAccMonitor_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkflowmonitor_monitor.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartition(t, endpoints.AwsPartitionID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFlowMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckMonitorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccMonitorConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkflowmonitor.ResourceMonitor, resourceName), + ), + ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + }, + }, + }) +} + +func testAccMonitor_tags(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkflowmonitor_monitor.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFlowMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckMonitorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccMonitorConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1), + })), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "monitor_name"), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "monitor_name", + ImportStateVerifyIgnore: []string{"scope_arn"}, + }, + { + Config: testAccMonitorConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1Updated), + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, + }, + { + Config: testAccMonitorConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, + }, + }, + }) +} + +func testAccMonitor_update(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_networkflowmonitor_monitor.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFlowMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckMonitorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccMonitorConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("local_resource"), knownvalue.SetSizeExact(1)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("remote_resource"), knownvalue.SetSizeExact(0)), + }, + }, + { + Config: testAccMonitorConfig_updated1(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("local_resource"), knownvalue.SetSizeExact(2)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("remote_resource"), knownvalue.SetSizeExact(1)), + }, + }, + { + Config: testAccMonitorConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("local_resource"), knownvalue.SetSizeExact(1)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("remote_resource"), knownvalue.SetSizeExact(0)), + }, + }, + { + Config: testAccMonitorConfig_updated2(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("local_resource"), knownvalue.SetSizeExact(2)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("remote_resource"), knownvalue.SetSizeExact(2)), + }, + }, + }, + }) +} + +func testAccCheckMonitorExists(ctx context.Context, n string) 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).NetworkFlowMonitorClient(ctx) + + _, err := tfnetworkflowmonitor.FindMonitorByName(ctx, conn, rs.Primary.Attributes["monitor_name"]) + + return err + } +} + +func testAccCheckMonitorDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFlowMonitorClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkflowmonitor_monitor" { + continue + } + + _, err := tfnetworkflowmonitor.FindMonitorByName(ctx, conn, rs.Primary.Attributes["monitor_name"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Flow Monitor Monitor %s still exists", rs.Primary.Attributes["monitor_name"]) + } + + return nil + } +} + +const testAccMonitorConfig_base = ` +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +resource "aws_networkflowmonitor_scope" "test" { + target { + region = data.aws_region.current.name + target_identifier { + target_type = "ACCOUNT" + target_id { + account_id = data.aws_caller_identity.current.account_id + } + } + } +} +` + +func testAccMonitorConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccMonitorConfig_base, fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_networkflowmonitor_monitor" "test" { + monitor_name = %[1]q + scope_arn = aws_networkflowmonitor_scope.test.scope_arn + + local_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } +} +`, rName)) +} + +func testAccMonitorConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccMonitorConfig_base, fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_networkflowmonitor_monitor" "test" { + monitor_name = %[1]q + scope_arn = aws_networkflowmonitor_scope.test.scope_arn + + local_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } + + remote_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1)) +} + +func testAccMonitorConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccMonitorConfig_base, fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_networkflowmonitor_monitor" "test" { + monitor_name = %[1]q + scope_arn = aws_networkflowmonitor_scope.test.scope_arn + + local_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } + + remote_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} + +func testAccMonitorConfig_updated1(rName string) string { + return acctest.ConfigCompose(testAccMonitorConfig_base, fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + vpc_id = aws_vpc.test.id + cidr_block = "10.0.1.0/24" + + tags = { + Name = %[1]q + } +} + +resource "aws_networkflowmonitor_monitor" "test" { + monitor_name = %[1]q + scope_arn = aws_networkflowmonitor_scope.test.scope_arn + + local_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } + + local_resource { + type = "AWS::EC2::Subnet" + identifier = aws_subnet.test.arn + } + + remote_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } +} +`, rName)) +} + +func testAccMonitorConfig_updated2(rName string) string { + return acctest.ConfigCompose(testAccMonitorConfig_base, fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + vpc_id = aws_vpc.test.id + cidr_block = "10.0.1.0/24" + + tags = { + Name = %[1]q + } +} + +resource "aws_networkflowmonitor_monitor" "test" { + monitor_name = %[1]q + scope_arn = aws_networkflowmonitor_scope.test.scope_arn + + local_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } + + local_resource { + type = "AWS::EC2::Subnet" + identifier = aws_subnet.test.arn + } + + remote_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.test.arn + } + + remote_resource { + type = "AWS::EC2::Subnet" + identifier = aws_subnet.test.arn + } +} +`, rName)) +} + +func testAccPreCheck(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFlowMonitorClient(ctx) + + input := networkflowmonitor.ListMonitorsInput{} + + _, err := conn.ListMonitors(ctx, &input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} diff --git a/internal/service/networkflowmonitor/networkflowmonitor_test.go b/internal/service/networkflowmonitor/networkflowmonitor_test.go new file mode 100644 index 000000000000..deb1828a42c9 --- /dev/null +++ b/internal/service/networkflowmonitor/networkflowmonitor_test.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkflowmonitor_test + +import ( + "testing" + + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkFlowMonitor_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]map[string]func(t *testing.T){ + "Monitor": { + acctest.CtBasic: testAccMonitor_basic, + acctest.CtDisappears: testAccMonitor_disappears, + "tags": testAccMonitor_tags, + "update": testAccMonitor_update, + }, + "Scope": { + acctest.CtBasic: testAccScope_basic, + acctest.CtDisappears: testAccScope_disappears, + "tags": testAccScope_tags, + }, + } + + acctest.RunSerialTests2Levels(t, testCases, 0) +} diff --git a/internal/service/networkflowmonitor/scope.go b/internal/service/networkflowmonitor/scope.go new file mode 100644 index 000000000000..cf6f05113e4d --- /dev/null +++ b/internal/service/networkflowmonitor/scope.go @@ -0,0 +1,440 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkflowmonitor + +import ( + "context" + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkflowmonitor" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkflowmonitor/types" + set "github.com/hashicorp/go-set/v3" + uuid "github.com/hashicorp/go-uuid" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "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" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + fwvalidators "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" + 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_networkflowmonitor_scope", name="Scope") +// @Tags(identifierAttribute="scope_arn") +func newScopeResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &scopeResource{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +type scopeResource struct { + framework.ResourceWithModel[scopeResourceModel] + framework.WithTimeouts +} + +func (r *scopeResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "scope_arn": framework.ARNAttributeComputedOnly(), + "scope_id": framework.IDAttribute(), + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + }, + Blocks: map[string]schema.Block{ + names.AttrTarget: schema.SetNestedBlock{ + CustomType: fwtypes.NewSetNestedObjectTypeOf[targetResourceModel](ctx), + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrRegion: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + fwvalidators.AWSRegion(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "target_identifier": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[targetIdentifierModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "target_type": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.TargetType](), + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "target_id": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[targetIdModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrAccountID: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + fwvalidators.AWSAccountID(), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *scopeResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data scopeResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkFlowMonitorClient(ctx) + + var input networkflowmonitor.CreateScopeInput + response.Diagnostics.Append(fwflex.Expand(ctx, data, &input)...) + if response.Diagnostics.HasError() { + return + } + + // Additional fields. + uuid, _ := uuid.GenerateUUID() + input.ClientToken = aws.String(uuid) + input.Tags = getTagsIn(ctx) + + output, err := conn.CreateScope(ctx, &input) + + if err != nil { + response.Diagnostics.AddError("creating Network Flow Monitor Scope", err.Error()) + return + } + + // Set values for unknowns. + data.ScopeARN = fwflex.StringToFramework(ctx, output.ScopeArn) + data.ScopeID = fwflex.StringToFramework(ctx, output.ScopeId) + + scopeID := fwflex.StringValueFromFramework(ctx, data.ScopeID) + if _, err := waitScopeCreated(ctx, conn, scopeID, r.CreateTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Network Flow Monitor Scope (%s) create", scopeID), err.Error()) + return + } + + response.Diagnostics.Append(response.State.Set(ctx, data)...) +} + +func (r *scopeResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data scopeResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkFlowMonitorClient(ctx) + + scopeID := fwflex.StringValueFromFramework(ctx, data.ScopeID) + output, err := findScopeByID(ctx, conn, scopeID) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading Network Flow Monitor Scope (%s)", scopeID), err.Error()) + return + } + + response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...) + if response.Diagnostics.HasError() { + return + } + + setTagsOut(ctx, output.Tags) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *scopeResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new scopeResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkFlowMonitorClient(ctx) + + diff, d := fwflex.Diff(ctx, new, old) + response.Diagnostics.Append(d...) + if response.Diagnostics.HasError() { + return + } + + if diff.HasChanges() { + var oldTargets, newTargets []awstypes.TargetResource + response.Diagnostics.Append(fwflex.Expand(ctx, old.Targets, &oldTargets)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(fwflex.Expand(ctx, new.Targets, &newTargets)...) + if response.Diagnostics.HasError() { + return + } + + hash := func(v awstypes.TargetResource) string { + accountID := any(v.TargetIdentifier.TargetId).(*awstypes.TargetIdMemberAccountId).Value + return aws.ToString(v.Region) + ":" + string(v.TargetIdentifier.TargetType) + ":" + accountID + } + os, ns := set.HashSetFromFunc(oldTargets, hash), set.HashSetFromFunc(newTargets, hash) + + scopeID := fwflex.StringValueFromFramework(ctx, new.ScopeID) + input := networkflowmonitor.UpdateScopeInput{ + ResourcesToAdd: ns.Difference(os).Slice(), + ResourcesToDelete: os.Difference(ns).Slice(), + ScopeId: aws.String(scopeID), + } + + _, err := conn.UpdateScope(ctx, &input) + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating Network Flow Monitor Scope (%s) targets", scopeID), err.Error()) + return + } + + if _, err := waitScopeUpdated(ctx, conn, scopeID, r.UpdateTimeout(ctx, new.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Network Flow Monitor Scope (%s) update", scopeID), err.Error()) + return + } + } + + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *scopeResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data scopeResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkFlowMonitorClient(ctx) + + scopeID := fwflex.StringValueFromFramework(ctx, data.ScopeID) + input := networkflowmonitor.DeleteScopeInput{ + ScopeId: aws.String(scopeID), + } + _, err := conn.DeleteScope(ctx, &input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting Network Flow Monitor Scope (%s)", scopeID), err.Error()) + return + } + + if _, err := waitScopeDeleted(ctx, conn, scopeID, r.DeleteTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Network Flow Monitor Scope (%s) delete", scopeID), err.Error()) + return + } +} + +func (r *scopeResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("scope_id"), request, response) +} + +func findScopeByID(ctx context.Context, conn *networkflowmonitor.Client, id string) (*networkflowmonitor.GetScopeOutput, error) { + input := networkflowmonitor.GetScopeInput{ + ScopeId: aws.String(id), + } + + return findScope(ctx, conn, &input) +} + +func findScope(ctx context.Context, conn *networkflowmonitor.Client, input *networkflowmonitor.GetScopeInput) (*networkflowmonitor.GetScopeOutput, error) { + output, err := conn.GetScope(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func statusScope(ctx context.Context, conn *networkflowmonitor.Client, id string) retry.StateRefreshFunc { + return func() (any, string, error) { + output, err := findScopeByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.Status), nil + } +} + +func waitScopeCreated(ctx context.Context, conn *networkflowmonitor.Client, id string, timeout time.Duration) (*networkflowmonitor.GetScopeOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ScopeStatusInProgress), + Target: enum.Slice(awstypes.ScopeStatusSucceeded), + Refresh: statusScope(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkflowmonitor.GetScopeOutput); ok { + return output, err + } + + return nil, err +} + +func waitScopeUpdated(ctx context.Context, conn *networkflowmonitor.Client, id string, timeout time.Duration) (*networkflowmonitor.GetScopeOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ScopeStatusInProgress), + Target: enum.Slice(awstypes.ScopeStatusSucceeded), + Refresh: statusScope(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkflowmonitor.GetScopeOutput); ok { + return output, err + } + + return nil, err +} + +func waitScopeDeleted(ctx context.Context, conn *networkflowmonitor.Client, id string, timeout time.Duration) (*networkflowmonitor.GetScopeOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ScopeStatusDeactivating), + Target: []string{}, + Refresh: statusScope(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkflowmonitor.GetScopeOutput); ok { + return output, err + } + + return nil, err +} + +type scopeResourceModel struct { + framework.WithRegionModel + ScopeARN types.String `tfsdk:"scope_arn"` + ScopeID types.String `tfsdk:"scope_id"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` + Targets fwtypes.SetNestedObjectValueOf[targetResourceModel] `tfsdk:"target"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type targetResourceModel struct { + Region types.String `tfsdk:"region"` + TargetIdentifier fwtypes.ListNestedObjectValueOf[targetIdentifierModel] `tfsdk:"target_identifier"` +} + +type targetIdentifierModel struct { + TargetID fwtypes.ListNestedObjectValueOf[targetIdModel] `tfsdk:"target_id"` + TargetType fwtypes.StringEnum[awstypes.TargetType] `tfsdk:"target_type"` +} + +type targetIdModel struct { + AccountID types.String `tfsdk:"account_id"` +} + +var ( + _ fwflex.Expander = targetIdModel{} + _ fwflex.Flattener = &targetIdModel{} +) + +func (m targetIdModel) Expand(ctx context.Context) (any, diag.Diagnostics) { + var diags diag.Diagnostics + switch { + case !m.AccountID.IsNull(): + var r awstypes.TargetIdMemberAccountId + r.Value = fwflex.StringValueFromFramework(ctx, m.AccountID) + return &r, diags + } + return nil, diags +} + +func (m *targetIdModel) Flatten(ctx context.Context, v any) diag.Diagnostics { + var diags diag.Diagnostics + switch t := v.(type) { + case awstypes.TargetIdMemberAccountId: + m.AccountID = fwflex.StringValueToFramework(ctx, t.Value) + default: + diags.AddError( + "Unsupported Type", + fmt.Sprintf("target ID flatten: %T", v), + ) + } + return diags +} diff --git a/internal/service/networkflowmonitor/scope_test.go b/internal/service/networkflowmonitor/scope_test.go new file mode 100644 index 000000000000..6c8d5f604f03 --- /dev/null +++ b/internal/service/networkflowmonitor/scope_test.go @@ -0,0 +1,284 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkflowmonitor_test + +import ( + "context" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/hashicorp/aws-sdk-go-base/v2/endpoints" + "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/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfknownvalue "github.com/hashicorp/terraform-provider-aws/internal/acctest/knownvalue" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkflowmonitor "github.com/hashicorp/terraform-provider-aws/internal/service/networkflowmonitor" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccScope_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_networkflowmonitor_scope.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartition(t, endpoints.AwsPartitionID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFlowMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckScopeDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccScopeConfig_basic(acctest.Region()), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckScopeExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("scope_arn"), tfknownvalue.RegionalARNRegexp("networkflowmonitor", regexache.MustCompile(`scope/.+`))), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("scope_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.Null()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTarget), knownvalue.SetSizeExact(1)), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "scope_id"), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "scope_id", + }, + }, + }) +} + +func testAccScope_disappears(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_networkflowmonitor_scope.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartition(t, endpoints.AwsPartitionID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFlowMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckScopeDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccScopeConfig_basic(endpoints.UsWest2RegionID), + Check: resource.ComposeTestCheckFunc( + testAccCheckScopeExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkflowmonitor.ResourceScope, resourceName), + ), + ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + }, + }, + }) +} + +func testAccScope_tags(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_networkflowmonitor_scope.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartition(t, endpoints.AwsPartitionID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkFlowMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckScopeDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccScopeConfig_tags1(acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckScopeExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1), + })), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, "scope_id"), + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "scope_id", + }, + { + Config: testAccScopeConfig_tags2(acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckScopeExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1Updated), + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, + }, + { + Config: testAccScopeConfig_tags1(acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckScopeExists(ctx, resourceName), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, + }, + }, + }) +} + +func testAccCheckScopeExists(ctx context.Context, n string) 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).NetworkFlowMonitorClient(ctx) + + _, err := tfnetworkflowmonitor.FindScopeByID(ctx, conn, rs.Primary.Attributes["scope_id"]) + + return err + } +} + +func testAccCheckScopeDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkFlowMonitorClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkflowmonitor_scope" { + continue + } + + _, err := tfnetworkflowmonitor.FindScopeByID(ctx, conn, rs.Primary.Attributes["scope_id"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Flow Monitor Scope %s still exists", rs.Primary.Attributes["scope_id"]) + } + + return nil + } +} + +func testAccScopeConfig_basic(regions ...string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_networkflowmonitor_scope" "test" { + dynamic "target" { + for_each = [%[1]s] + content { + region = target.value + target_identifier { + target_type = "ACCOUNT" + target_id { + account_id = data.aws_caller_identity.current.account_id + } + } + } + } +} +`, acctest.ListOfStrings(regions...)) +} + +func testAccScopeConfig_tags1(tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +resource "aws_networkflowmonitor_scope" "test" { + target { + region = data.aws_region.current.name + target_identifier { + target_type = "ACCOUNT" + target_id { + account_id = data.aws_caller_identity.current.account_id + } + } + } + + tags = { + %[1]q = %[2]q + } +} +`, tagKey1, tagValue1) +} + +func testAccScopeConfig_tags2(tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +resource "aws_networkflowmonitor_scope" "test" { + target { + region = data.aws_region.current.name + target_identifier { + target_type = "ACCOUNT" + target_id { + account_id = data.aws_caller_identity.current.account_id + } + } + } + + tags = { + %[1]q = %[2]q + %[3]q = %[4]q + } +} +`, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/networkflowmonitor/service_package_gen.go b/internal/service/networkflowmonitor/service_package_gen.go index f0c06407b5b7..b2b7102fc458 100644 --- a/internal/service/networkflowmonitor/service_package_gen.go +++ b/internal/service/networkflowmonitor/service_package_gen.go @@ -4,6 +4,7 @@ package networkflowmonitor import ( "context" + "unique" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/networkflowmonitor" @@ -21,7 +22,26 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*inttypes.S } func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.ServicePackageFrameworkResource { - return []*inttypes.ServicePackageFrameworkResource{} + return []*inttypes.ServicePackageFrameworkResource{ + { + Factory: newMonitorResource, + TypeName: "aws_networkflowmonitor_monitor", + Name: "Monitor", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: "monitor_arn", + }), + Region: unique.Make(inttypes.ResourceRegionDefault()), + }, + { + Factory: newScopeResource, + TypeName: "aws_networkflowmonitor_scope", + Name: "Scope", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: "scope_arn", + }), + Region: unique.Make(inttypes.ResourceRegionDefault()), + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*inttypes.ServicePackageSDKDataSource { diff --git a/internal/service/networkflowmonitor/sweep.go b/internal/service/networkflowmonitor/sweep.go new file mode 100644 index 000000000000..9ec84c749821 --- /dev/null +++ b/internal/service/networkflowmonitor/sweep.go @@ -0,0 +1,114 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkflowmonitor + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkflowmonitor" + "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" + "github.com/hashicorp/terraform-provider-aws/internal/sweep/framework" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func RegisterSweepers() { + resource.AddTestSweepers("aws_networkflowmonitor_monitor", &resource.Sweeper{ + Name: "aws_networkflowmonitor_monitor", + F: sweepMonitors, + }) + + resource.AddTestSweepers("aws_networkflowmonitor_scope", &resource.Sweeper{ + Name: "aws_networkflowmonitor_scope", + F: sweepScopes, + }) +} + +func sweepMonitors(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.NetworkFlowMonitorClient(ctx) + input := networkflowmonitor.ListMonitorsInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := networkflowmonitor.NewListMonitorsPaginator(conn, &input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Network Flow Monitor Monitor sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Network Flow Monitor Monitors (%s): %w", region, err) + } + + for _, v := range page.Monitors { + arn := aws.ToString(v.MonitorArn) + name := aws.ToString(v.MonitorName) + + sweepResources = append(sweepResources, framework.NewSweepResource(newMonitorResource, client, + framework.NewAttribute(names.AttrID, arn), + framework.NewAttribute("monitor_name", name), + )) + } + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Network Flow Monitor Monitors (%s): %w", region, err) + } + + return nil +} +func sweepScopes(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + conn := client.NetworkFlowMonitorClient(ctx) + input := networkflowmonitor.ListScopesInput{} + sweepResources := make([]sweep.Sweepable, 0) + + pages := networkflowmonitor.NewListScopesPaginator(conn, &input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Network Flow Monitor Scope sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Network Flow Monitor Scopes (%s): %w", region, err) + } + + for _, v := range page.Scopes { + scopeId := aws.ToString(v.ScopeId) + scopeArn := aws.ToString(v.ScopeArn) + + sweepResources = append(sweepResources, framework.NewSweepResource(newScopeResource, client, + framework.NewAttribute(names.AttrID, scopeArn), + framework.NewAttribute("scope_id", scopeId), + )) + } + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Network Flow Monitor Scopes (%s): %w", region, err) + } + + return nil +} diff --git a/internal/service/networkflowmonitor/tags_gen_test.go b/internal/service/networkflowmonitor/tags_gen_test.go new file mode 100644 index 000000000000..88465be11f11 --- /dev/null +++ b/internal/service/networkflowmonitor/tags_gen_test.go @@ -0,0 +1,16 @@ +// Code generated by internal/generate/tagstests/main.go; DO NOT EDIT. + +package networkflowmonitor_test + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + tfnetworkflowmonitor "github.com/hashicorp/terraform-provider-aws/internal/service/networkflowmonitor" +) + +func expectFullResourceTags(ctx context.Context, resourceAddress string, knownValue knownvalue.Check) statecheck.StateCheck { + return tfstatecheck.ExpectFullResourceTags(tfnetworkflowmonitor.ServicePackage(ctx), resourceAddress, knownValue) +} diff --git a/internal/sweep/register_gen_test.go b/internal/sweep/register_gen_test.go index 9d948704d3b5..3ea4eda45708 100644 --- a/internal/sweep/register_gen_test.go +++ b/internal/sweep/register_gen_test.go @@ -119,6 +119,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/neptune" "github.com/hashicorp/terraform-provider-aws/internal/service/neptunegraph" "github.com/hashicorp/terraform-provider-aws/internal/service/networkfirewall" + "github.com/hashicorp/terraform-provider-aws/internal/service/networkflowmonitor" "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" "github.com/hashicorp/terraform-provider-aws/internal/service/notifications" "github.com/hashicorp/terraform-provider-aws/internal/service/notificationscontacts" @@ -300,6 +301,7 @@ func registerSweepers() { neptune.RegisterSweepers() neptunegraph.RegisterSweepers() networkfirewall.RegisterSweepers() + networkflowmonitor.RegisterSweepers() networkmanager.RegisterSweepers() notifications.RegisterSweepers() notificationscontacts.RegisterSweepers() diff --git a/tools/tfsdk2fw/go.mod b/tools/tfsdk2fw/go.mod index 1deb51424e4c..fdea9962df2c 100644 --- a/tools/tfsdk2fw/go.mod +++ b/tools/tfsdk2fw/go.mod @@ -317,6 +317,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.7.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-set/v3 v3.0.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hc-install v0.9.2 // indirect diff --git a/tools/tfsdk2fw/go.sum b/tools/tfsdk2fw/go.sum index 52b52f214391..394055368c1c 100644 --- a/tools/tfsdk2fw/go.sum +++ b/tools/tfsdk2fw/go.sum @@ -656,6 +656,8 @@ github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshf github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-set/v3 v3.0.1 h1:ZwO15ZYmIrFYL9zSm2wBuwcRiHxVdp46m/XA/MUlM6I= +github.com/hashicorp/go-set/v3 v3.0.1/go.mod h1:0oPQqhtitglZeT2ZiWnRIfUG6gJAHnn7LzrS7SbgNY4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -758,6 +760,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shoenig/test v1.12.1 h1:mLHfnMv7gmhhP44WrvT+nKSxKkPDiNkIuHGdIGI9RLU= +github.com/shoenig/test v1.12.1/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= diff --git a/website/docs/r/networkflowmonitor_monitor.html.markdown b/website/docs/r/networkflowmonitor_monitor.html.markdown new file mode 100644 index 000000000000..e2a46feaa553 --- /dev/null +++ b/website/docs/r/networkflowmonitor_monitor.html.markdown @@ -0,0 +1,97 @@ +--- +subcategory: "CloudWatch NetworkFlow Monitor" +layout: "aws" +page_title: "AWS: aws_networkflowmonitor_monitor" +description: |- + Manages a Network Flow Monitor Monitor. +--- + +# Resource: aws_networkflowmonitor_monitor + +Manages a Network Flow Monitor Monitor. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_vpc" "example" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "example" + } +} + +resource "aws_networkflowmonitor_monitor" "example" { + monitor_name = "example-monitor" + scope_arn = aws_networkflowmonitor_scope.example.scope_arn + + local_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.example.arn + } + + remote_resource { + type = "AWS::EC2::VPC" + identifier = aws_vpc.example.arn + } + + tags = { + Name = "example" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `monitor_name` - (Required) The name of the monitor. Cannot be changed after creation. +* `scope_arn` - (Required) The Amazon Resource Name (ARN) of the scope for the monitor. Cannot be changed after creation. + +The following arguments are optional: + +* `local_resource` - (Optional) The local resources to monitor. A local resource in a workload is the location of the hosts where the Network Flow Monitor agent is installed. +* `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). +* `remote_resource` - (Optional) The remote resources to monitor. A remote resource is the other endpoint specified for the network flow of a workload, with a local resource. +* `tags` - (Optional) A 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. + +### local_resource and remote_resource + +The `local_resource` and `remote_resource` blocks support the following: + +* `type` - (Required) The type of the resource. Valid values are `AWS::EC2::VPC`, `AWS::EC2::Subnet`, `AWS::EC2::AvailabilityZone`, `AWS::EC2::Region`. +* `identifier` - (Required) The identifier of the resource. For VPC resources, this is the VPC ARN. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `monitor_arn` - The Amazon Resource Name (ARN) of the monitor. +* `tags_all` - A 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). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Flow Monitor Monitor using the monitor name. For example: + +```terraform +import { + to = aws_networkflowmonitor_monitor.example + id = "example-monitor" +} +``` + +Using `terraform import`, import Network Flow Monitor Monitor using the monitor name. For example: + +```console +% terraform import aws_networkflowmonitor_monitor.example example-monitor +``` diff --git a/website/docs/r/networkflowmonitor_scope.html.markdown b/website/docs/r/networkflowmonitor_scope.html.markdown new file mode 100644 index 000000000000..42ee8453edba --- /dev/null +++ b/website/docs/r/networkflowmonitor_scope.html.markdown @@ -0,0 +1,99 @@ +--- +subcategory: "CloudWatch NetworkFlow Monitor" +layout: "aws" +page_title: "AWS: aws_networkflowmonitor_scope" +description: |- + Manages a Network Flow Monitor Scope. +--- + +# Resource: aws_networkflowmonitor_scope + +Manages a Network Flow Monitor Scope. + +## Example Usage + +### Basic Usage + +```terraform +data "aws_caller_identity" "current" {} + +resource "aws_networkflowmonitor_scope" "example" { + target { + region = "us-east-1" + target_identifier { + target_type = "ACCOUNT" + target_id { + account_id = data.aws_caller_identity.current.account_id + } + } + } + + tags = { + Name = "example" + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `target` - (Required) The targets to define the scope to be monitored. A target is an array of target resources, which are currently Region-account pairs. + +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). +* `tags` - (Optional) A 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. + +### targets + +The `targets` block supports the following: + +* `region` - (Required) The AWS Region for the scope. +* `target_identifier` - (Required) A target identifier is a pair of identifying information for a scope. + +### target_identifier + +The `target_identifier` block supports the following: + +* `target_id` - (Required) The identifier for a target, which is currently always an account ID. +* `target_type` - (Required) The type of a target. A target type is currently always `ACCOUNT`. + +### target_id + +The `target_id` block supports the following: + +* `account_id` - (Required) AWS account ID. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `scope_arn` - The Amazon Resource Name (ARN) of the scope. +* `scope_id` - The identifier for the scope that includes the resources you want to get data results for. +* `tags_all` - A 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). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Network Flow Monitor Scope using the scope ID. For example: + +```terraform +import { + to = aws_networkflowmonitor_scope.example + id = "example-scope-id" +} +``` + +Using `terraform import`, import Network Flow Monitor Scope using the scope ID. For example: + +```console +% terraform import aws_networkflowmonitor_scope.example example-scope-id +```