diff --git a/.changelog/35722.txt b/.changelog/35722.txt new file mode 100644 index 000000000000..68bffb9b8296 --- /dev/null +++ b/.changelog/35722.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +aws_networkmonitor_monitor +``` + +```release-note:new-resource +aws_networkmonitor_probe +``` \ No newline at end of file diff --git a/.ci/.semgrep-service-name0.yml b/.ci/.semgrep-service-name0.yml index 785a5fa8f672..d6b2e404f9d1 100644 --- a/.ci/.semgrep-service-name0.yml +++ b/.ci/.semgrep-service-name0.yml @@ -4174,3 +4174,21 @@ rules: patterns: - pattern-regex: "(?i)ComputeOptimizer" severity: WARNING + - id: configservice-in-func-name + languages: + - go + message: Do not use "ConfigService" in func name inside configservice package + paths: + include: + - internal/service/configservice + exclude: + - internal/service/configservice/list_pages_gen.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)ConfigService" + - focus-metavariable: $NAME + - pattern-not: func $NAME($T *testing.T) + severity: WARNING diff --git a/.ci/.semgrep-service-name1.yml b/.ci/.semgrep-service-name1.yml index 89182455900f..b21d93dd2dfe 100644 --- a/.ci/.semgrep-service-name1.yml +++ b/.ci/.semgrep-service-name1.yml @@ -1,23 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: configservice-in-func-name - languages: - - go - message: Do not use "ConfigService" in func name inside configservice package - paths: - include: - - internal/service/configservice - exclude: - - internal/service/configservice/list_pages_gen.go - patterns: - - pattern: func $NAME( ... ) - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)ConfigService" - - focus-metavariable: $NAME - - pattern-not: func $NAME($T *testing.T) - severity: WARNING - id: configservice-in-test-name languages: - go @@ -4187,3 +4169,32 @@ rules: - focus-metavariable: $NAME - pattern-not: func $NAME($T *testing.T) severity: WARNING + - id: iot-in-test-name + languages: + - go + message: Include "IoT" in test name + paths: + include: + - internal/service/iot/*_test.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccIoT" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: iot-in-const-name + languages: + - go + message: Do not use "IoT" in const name inside iot package + paths: + include: + - internal/service/iot + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)IoT" + severity: WARNING diff --git a/.ci/.semgrep-service-name2.yml b/.ci/.semgrep-service-name2.yml index 3eee1703dfce..a71b08ba566c 100644 --- a/.ci/.semgrep-service-name2.yml +++ b/.ci/.semgrep-service-name2.yml @@ -1,34 +1,5 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: - - id: iot-in-test-name - languages: - - go - message: Include "IoT" in test name - paths: - include: - - internal/service/iot/*_test.go - patterns: - - pattern: func $NAME( ... ) - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-not-regex: "^TestAccIoT" - - pattern-regex: ^TestAcc.* - severity: WARNING - - id: iot-in-const-name - languages: - - go - message: Do not use "IoT" in const name inside iot package - paths: - include: - - internal/service/iot - patterns: - - pattern: const $NAME = ... - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)IoT" - severity: WARNING - id: iot-in-var-name languages: - go @@ -2759,6 +2730,67 @@ rules: patterns: - pattern-regex: "(?i)NetworkManager" severity: WARNING + - id: networkmonitor-in-func-name + languages: + - go + message: Do not use "NetworkMonitor" in func name inside networkmonitor package + paths: + include: + - internal/service/networkmonitor + exclude: + - internal/service/networkmonitor/list_pages_gen.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)NetworkMonitor" + - focus-metavariable: $NAME + - pattern-not: func $NAME($T *testing.T) + severity: WARNING + - id: networkmonitor-in-test-name + languages: + - go + message: Include "NetworkMonitor" in test name + paths: + include: + - internal/service/networkmonitor/*_test.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-not-regex: "^TestAccNetworkMonitor" + - pattern-regex: ^TestAcc.* + severity: WARNING + - id: networkmonitor-in-const-name + languages: + - go + message: Do not use "NetworkMonitor" in const name inside networkmonitor package + paths: + include: + - internal/service/networkmonitor + patterns: + - pattern: const $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)NetworkMonitor" + severity: WARNING + - id: networkmonitor-in-var-name + languages: + - go + message: Do not use "NetworkMonitor" in var name inside networkmonitor package + paths: + include: + - internal/service/networkmonitor + patterns: + - pattern: var $NAME = ... + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)NetworkMonitor" + severity: WARNING - id: oam-in-func-name languages: - go @@ -4154,21 +4186,3 @@ rules: patterns: - pattern-regex: "(?i)recyclebin" severity: WARNING - - id: redshift-in-func-name - languages: - - go - message: Do not use "Redshift" in func name inside redshift package - paths: - include: - - internal/service/redshift - exclude: - - internal/service/redshift/list_pages_gen.go - patterns: - - pattern: func $NAME( ... ) - - metavariable-pattern: - metavariable: $NAME - patterns: - - pattern-regex: "(?i)Redshift" - - focus-metavariable: $NAME - - pattern-not: func $NAME($T *testing.T) - severity: WARNING diff --git a/.ci/.semgrep-service-name3.yml b/.ci/.semgrep-service-name3.yml index d02b95019cfd..186f67e5da96 100644 --- a/.ci/.semgrep-service-name3.yml +++ b/.ci/.semgrep-service-name3.yml @@ -1,5 +1,23 @@ # Generated by internal/generate/servicesemgrep/main.go; DO NOT EDIT. rules: + - id: redshift-in-func-name + languages: + - go + message: Do not use "Redshift" in func name inside redshift package + paths: + include: + - internal/service/redshift + exclude: + - internal/service/redshift/list_pages_gen.go + patterns: + - pattern: func $NAME( ... ) + - metavariable-pattern: + metavariable: $NAME + patterns: + - pattern-regex: "(?i)Redshift" + - focus-metavariable: $NAME + - pattern-not: func $NAME($T *testing.T) + severity: WARNING - id: redshift-in-test-name languages: - go diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index c4e1591383d9..dd34122729f3 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -487,6 +487,8 @@ service/networkfirewall: - '((\*|-)\s*`?|(data|resource)\s+"?)aws_networkfirewall_' service/networkmanager: - '((\*|-)\s*`?|(data|resource)\s+"?)aws_networkmanager_' +service/networkmonitor: + - '((\*|-)\s*`?|(data|resource)\s+"?)aws_networkmonitor_' service/nimble: - '((\*|-)\s*`?|(data|resource)\s+"?)aws_nimble_' service/oam: diff --git a/.github/labeler-pr-triage.yml b/.github/labeler-pr-triage.yml index 03ff5f041972..9b7bed4fdc38 100644 --- a/.github/labeler-pr-triage.yml +++ b/.github/labeler-pr-triage.yml @@ -1538,6 +1538,12 @@ service/networkmanager: - any-glob-to-any-file: - 'internal/service/networkmanager/**/*' - 'website/**/networkmanager_*' +service/networkmonitor: + - any: + - changed-files: + - any-glob-to-any-file: + - 'internal/service/networkmonitor/**/*' + - 'website/**/networkmonitor_*' service/nimble: - any: - changed-files: diff --git a/.teamcity/components/generated/services_all.kt b/.teamcity/components/generated/services_all.kt index c09b03445561..31dc9623b5f4 100644 --- a/.teamcity/components/generated/services_all.kt +++ b/.teamcity/components/generated/services_all.kt @@ -158,6 +158,7 @@ val services = mapOf( "neptunegraph" to ServiceSpec("Neptune Analytics"), "networkfirewall" to ServiceSpec("Network Firewall", vpcLock = true), "networkmanager" to ServiceSpec("Network Manager", vpcLock = true), + "networkmonitor" to ServiceSpec("CloudWatch Network Monitor"), "oam" to ServiceSpec("CloudWatch Observability Access Manager"), "opensearch" to ServiceSpec("OpenSearch", vpcLock = true), "opensearchserverless" to ServiceSpec("OpenSearch Serverless"), diff --git a/go.mod b/go.mod index 14b30cabb56c..673163aa58fa 100644 --- a/go.mod +++ b/go.mod @@ -133,6 +133,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/mwaa v1.28.1 github.com/aws/aws-sdk-go-v2/service/neptunegraph v1.9.1 github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.39.1 + github.com/aws/aws-sdk-go-v2/service/networkmonitor v1.4.1 github.com/aws/aws-sdk-go-v2/service/oam v1.12.1 github.com/aws/aws-sdk-go-v2/service/opensearchserverless v1.12.1 github.com/aws/aws-sdk-go-v2/service/organizations v1.28.1 diff --git a/go.sum b/go.sum index 981ec8fbe8af..77bacfd88193 100644 --- a/go.sum +++ b/go.sum @@ -296,6 +296,8 @@ github.com/aws/aws-sdk-go-v2/service/neptunegraph v1.9.1 h1:UjByGYRBlhjY4l8Lun62 github.com/aws/aws-sdk-go-v2/service/neptunegraph v1.9.1/go.mod h1:5q3YTQennpO1/KB7rU71vW/9PjLC4PuosEi2xDEw5OY= github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.39.1 h1:f2TcduRAvOs8ltPaAnjSP64WHRmM/B5bsDSqXRYBYGs= github.com/aws/aws-sdk-go-v2/service/networkfirewall v1.39.1/go.mod h1:23qyfghRkv9qOMRIL9KdUHiKyhARU/0FddRMtvMSVV0= +github.com/aws/aws-sdk-go-v2/service/networkmonitor v1.4.1 h1:ehZAcRu5cnQRYOA/JXs0wAcEgVsPhSXXiZwmDpEi5FI= +github.com/aws/aws-sdk-go-v2/service/networkmonitor v1.4.1/go.mod h1:AN15OEzh1YVoFSTlWZxMxVfSAqJCFpzVUgphuxJFjr8= github.com/aws/aws-sdk-go-v2/service/oam v1.12.1 h1:LZrULRkfrmZVE8OHqwI8tKFEFxpjZl6ll7Bn2MCCVwg= github.com/aws/aws-sdk-go-v2/service/oam v1.12.1/go.mod h1:yiUaEYA1zVxtz/EGgf8NE7rT56sLKGqQwQrWg/GhGu8= github.com/aws/aws-sdk-go-v2/service/opensearchserverless v1.12.1 h1:BRAM7tTwHJojSOhiyUkPh2Z/hOco7OkayTf6MYFOF5w= diff --git a/infrastructure/repository/labels-service.tf b/infrastructure/repository/labels-service.tf index 6d67d781d0b3..4b9bcb2b474f 100644 --- a/infrastructure/repository/labels-service.tf +++ b/infrastructure/repository/labels-service.tf @@ -230,6 +230,7 @@ variable "service_labels" { "neptunegraph", "networkfirewall", "networkmanager", + "networkmonitor", "nimble", "oam", "opensearch", diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go index c7a3894ffd22..f5f1a480c4e7 100644 --- a/internal/conns/awsclient_gen.go +++ b/internal/conns/awsclient_gen.go @@ -125,6 +125,7 @@ import ( mwaa_sdkv2 "github.com/aws/aws-sdk-go-v2/service/mwaa" neptunegraph_sdkv2 "github.com/aws/aws-sdk-go-v2/service/neptunegraph" networkfirewall_sdkv2 "github.com/aws/aws-sdk-go-v2/service/networkfirewall" + networkmonitor_sdkv2 "github.com/aws/aws-sdk-go-v2/service/networkmonitor" oam_sdkv2 "github.com/aws/aws-sdk-go-v2/service/oam" opensearchserverless_sdkv2 "github.com/aws/aws-sdk-go-v2/service/opensearchserverless" organizations_sdkv2 "github.com/aws/aws-sdk-go-v2/service/organizations" @@ -916,6 +917,10 @@ func (c *AWSClient) NetworkManagerConn(ctx context.Context) *networkmanager_sdkv return errs.Must(conn[*networkmanager_sdkv1.NetworkManager](ctx, c, names.NetworkManager, make(map[string]any))) } +func (c *AWSClient) NetworkMonitorClient(ctx context.Context) *networkmonitor_sdkv2.Client { + return errs.Must(client[*networkmonitor_sdkv2.Client](ctx, c, names.NetworkMonitor, make(map[string]any))) +} + func (c *AWSClient) ObservabilityAccessManagerClient(ctx context.Context) *oam_sdkv2.Client { return errs.Must(client[*oam_sdkv2.Client](ctx, c, names.ObservabilityAccessManager, make(map[string]any))) } diff --git a/internal/provider/service_packages_gen.go b/internal/provider/service_packages_gen.go index 772d87186328..339afa8d6c42 100644 --- a/internal/provider/service_packages_gen.go +++ b/internal/provider/service_packages_gen.go @@ -165,6 +165,7 @@ import ( "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/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/service/networkmonitor" "github.com/hashicorp/terraform-provider-aws/internal/service/oam" "github.com/hashicorp/terraform-provider-aws/internal/service/opensearch" "github.com/hashicorp/terraform-provider-aws/internal/service/opensearchserverless" @@ -407,6 +408,7 @@ func servicePackages(ctx context.Context) []conns.ServicePackage { neptunegraph.ServicePackage(ctx), networkfirewall.ServicePackage(ctx), networkmanager.ServicePackage(ctx), + networkmonitor.ServicePackage(ctx), oam.ServicePackage(ctx), opensearch.ServicePackage(ctx), opensearchserverless.ServicePackage(ctx), diff --git a/internal/service/ec2/exports.go b/internal/service/ec2/exports.go index 78067a8dabe8..681dc9a99184 100644 --- a/internal/service/ec2/exports.go +++ b/internal/service/ec2/exports.go @@ -12,13 +12,16 @@ var ( FindInstanceByID = findInstanceByID FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription = findNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription FindNetworkInterfacesV2 = findNetworkInterfaces + FindSecurityGroupByDescriptionAndVPCID = findSecurityGroupByDescriptionAndVPCID FindSecurityGroupByNameAndVPCID = findSecurityGroupByNameAndVPCID + FindSecurityGroupByNameAndVPCIDAndOwnerID = findSecurityGroupByNameAndVPCIDAndOwnerID FindVPCByIDV2 = findVPCByID FindVPCEndpointByID = findVPCEndpointByID NewCustomFilterListFrameworkV2 = newCustomFilterListFrameworkV2 NewFilter = newFilter NewFilterV2 = newFilterV2 ResourceAMI = resourceAMI + ResourceSecurityGroup = resourceSecurityGroup ResourceTransitGateway = resourceTransitGateway ResourceTransitGatewayConnectPeer = resourceTransitGatewayConnectPeer VPCEndpointCreationTimeout = vpcEndpointCreationTimeout diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index fd9d21fbb909..cd155b2c759f 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -435,8 +435,19 @@ func FindSecurityGroupByID(ctx context.Context, conn *ec2.EC2, id string) (*ec2. return output, nil } -// FindSecurityGroupByNameAndVPCIDAndOwnerID looks up a security group by name, VPC ID and owner ID. Returns a retry.NotFoundError if not found. -func FindSecurityGroupByNameAndVPCIDAndOwnerID(ctx context.Context, conn *ec2.EC2, name, vpcID, ownerID string) (*ec2.SecurityGroup, error) { +func findSecurityGroupByDescriptionAndVPCID(ctx context.Context, conn *ec2.EC2, description, vpcID string) (*ec2.SecurityGroup, error) { + input := &ec2.DescribeSecurityGroupsInput{ + Filters: newAttributeFilterList( + map[string]string{ + names.AttrDescription: description, + "vpc-id": vpcID, + }, + ), + } + return FindSecurityGroup(ctx, conn, input) +} + +func findSecurityGroupByNameAndVPCIDAndOwnerID(ctx context.Context, conn *ec2.EC2, name, vpcID, ownerID string) (*ec2.SecurityGroup, error) { input := &ec2.DescribeSecurityGroupsInput{ Filters: newAttributeFilterList( map[string]string{ diff --git a/internal/service/ec2/service_package_gen.go b/internal/service/ec2/service_package_gen.go index 42565197ff7b..9edd65786e77 100644 --- a/internal/service/ec2/service_package_gen.go +++ b/internal/service/ec2/service_package_gen.go @@ -1033,7 +1033,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka TypeName: "aws_route_table_association", }, { - Factory: ResourceSecurityGroup, + Factory: resourceSecurityGroup, TypeName: "aws_security_group", Name: "Security Group", Tags: &types.ServicePackageResourceTags{ diff --git a/internal/service/ec2/vpc_security_group.go b/internal/service/ec2/vpc_security_group.go index 0cf7358bf14f..b47de6df567c 100644 --- a/internal/service/ec2/vpc_security_group.go +++ b/internal/service/ec2/vpc_security_group.go @@ -37,7 +37,7 @@ import ( // @Tags(identifierAttribute="id") // @Testing(existsType="github.com/aws/aws-sdk-go/service/ec2;ec2.SecurityGroup") // @Testing(importIgnore="revoke_rules_on_delete") -func ResourceSecurityGroup() *schema.Resource { +func resourceSecurityGroup() *schema.Resource { //lintignore:R011 return &schema.Resource{ CreateWithoutTimeout: resourceSecurityGroupCreate, diff --git a/internal/service/networkmonitor/exports_test.go b/internal/service/networkmonitor/exports_test.go new file mode 100644 index 000000000000..c7e8a69c051b --- /dev/null +++ b/internal/service/networkmonitor/exports_test.go @@ -0,0 +1,13 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkmonitor + +// Exports for use in tests only. +var ( + ResourceMonitor = newMonitorResource + ResourceProbe = newProbeResource + + FindMonitorByName = findMonitorByName + FindProbeByTwoPartKey = findProbeByTwoPartKey +) diff --git a/internal/service/networkmonitor/generate.go b/internal/service/networkmonitor/generate.go new file mode 100644 index 000000000000..5c92af04d476 --- /dev/null +++ b/internal/service/networkmonitor/generate.go @@ -0,0 +1,8 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:generate go run ../../generate/servicepackage/main.go +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -KVTValues -SkipTypesImp -ServiceTagsMap -ListTags -UpdateTags +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package networkmonitor diff --git a/internal/service/networkmonitor/monitor.go b/internal/service/networkmonitor/monitor.go new file mode 100644 index 000000000000..c47a78368654 --- /dev/null +++ b/internal/service/networkmonitor/monitor.go @@ -0,0 +1,330 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkmonitor + +import ( + "context" + "fmt" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkmonitor" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkmonitor/types" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "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/id" + "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" + 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(name="Monitor") +// @Tags(identifierAttribute="arn") +func newMonitorResource(context.Context) (resource.ResourceWithConfigure, error) { + return &monitorResource{}, nil +} + +type monitorResource struct { + framework.ResourceWithConfigure + framework.WithImportByID +} + +func (*monitorResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_networkmonitor_monitor" +} + +func (r *monitorResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "aggregation_period": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + int64validator.OneOf(30, 60), + }, + }, + names.AttrARN: framework.ARNAttributeComputedOnly(), + names.AttrID: framework.IDAttribute(), + "monitor_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexache.MustCompile("[a-zA-Z0-9_-]+"), "Must match [a-zA-Z0-9_-]+"), + stringvalidator.LengthBetween(1, 255), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + }, + } +} + +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().NetworkMonitorClient(ctx) + + name := data.MonitorName.ValueString() + input := &networkmonitor.CreateMonitorInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) + if response.Diagnostics.HasError() { + return + } + + input.ClientToken = aws.String(id.UniqueId()) + input.Tags = getTagsIn(ctx) + + output, err := conn.CreateMonitor(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("creating CloudWatch Network Monitor Monitor (%s)", name), err.Error()) + + return + } + + // Set values for unknowns. + data.MonitorARN = fwflex.StringToFramework(ctx, output.MonitorArn) + data.setID() + + if _, err := waitMonitorReady(ctx, conn, data.ID.ValueString()); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for CloudWatch Network Monitor Monitor (%s) create", data.ID.ValueString()), 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 + } + + if err := data.InitFromID(); err != nil { + response.Diagnostics.AddError("parsing resource ID", err.Error()) + + return + } + + conn := r.Meta().NetworkMonitorClient(ctx) + + output, err := findMonitorByName(ctx, conn, data.ID.ValueString()) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading CloudWatch Network Monitor Monitor (%s)", data.ID.ValueString()), err.Error()) + + return + } + + // Set attributes for import. + 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.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkMonitorClient(ctx) + + if !new.AggregationPeriod.Equal(old.AggregationPeriod) { + input := &networkmonitor.UpdateMonitorInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, new, input)...) + if response.Diagnostics.HasError() { + return + } + + _, err := conn.UpdateMonitor(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating CloudWatch Network Monitor Monitor (%s)", new.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitMonitorReady(ctx, conn, new.ID.ValueString()); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for CloudWatch Network Monitor Monitor (%s) update", new.ID.ValueString()), 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().NetworkMonitorClient(ctx) + + _, err := conn.DeleteMonitor(ctx, &networkmonitor.DeleteMonitorInput{ + MonitorName: fwflex.StringFromFramework(ctx, data.ID), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting CloudWatch Network Monitor Monitor (%s)", data.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitMonitorDeleted(ctx, conn, data.ID.ValueString()); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for CloudWatch Network Monitor Monitor (%s) delete", data.ID.ValueString()), err.Error()) + + return + } +} + +func (r *monitorResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +func findMonitorByName(ctx context.Context, conn *networkmonitor.Client, name string) (*networkmonitor.GetMonitorOutput, error) { + input := &networkmonitor.GetMonitorInput{ + MonitorName: aws.String(name), + } + + 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 *networkmonitor.Client, name string) retry.StateRefreshFunc { + return func() (interface{}, 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.State), nil + } +} + +func waitMonitorReady(ctx context.Context, conn *networkmonitor.Client, name string) (*networkmonitor.GetMonitorOutput, error) { //nolint:unparam + const ( + timeout = time.Minute * 10 + ) + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.MonitorStatePending), + Target: enum.Slice(awstypes.MonitorStateActive, awstypes.MonitorStateInactive), + Refresh: statusMonitor(ctx, conn, name), + Timeout: timeout, + MinTimeout: 10 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmonitor.GetMonitorOutput); ok { + return output, err + } + + return nil, err +} + +func waitMonitorDeleted(ctx context.Context, conn *networkmonitor.Client, name string) (*networkmonitor.GetMonitorOutput, error) { + const ( + timeout = time.Minute * 10 + ) + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.MonitorStateDeleting, awstypes.MonitorStateActive, awstypes.MonitorStateInactive), + Target: []string{}, + Refresh: statusMonitor(ctx, conn, name), + Timeout: timeout, + MinTimeout: 10 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmonitor.GetMonitorOutput); ok { + return output, err + } + + return nil, err +} + +type monitorResourceModel struct { + AggregationPeriod types.Int64 `tfsdk:"aggregation_period"` + ID types.String `tfsdk:"id"` + MonitorARN types.String `tfsdk:"arn"` + MonitorName types.String `tfsdk:"monitor_name"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` +} + +func (model *monitorResourceModel) InitFromID() error { + model.MonitorName = model.ID + + return nil +} + +func (model *monitorResourceModel) setID() { + model.ID = model.MonitorName +} diff --git a/internal/service/networkmonitor/monitor_test.go b/internal/service/networkmonitor/monitor_test.go new file mode 100644 index 000000000000..17be63198f20 --- /dev/null +++ b/internal/service/networkmonitor/monitor_test.go @@ -0,0 +1,200 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkmonitor_test + +import ( + "context" + "fmt" + "testing" + + 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" + tfnetworkmonitor "github.com/hashicorp/terraform-provider-aws/internal/service/networkmonitor" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkMonitorMonitor_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_networkmonitor_monitor.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckMonitorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccMonitorConfig_basic(rName, 30), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "aggregation_period", "30"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccMonitorConfig_basic(rName, 60), + Check: resource.ComposeTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "aggregation_period", "60"), + ), + }, + }, + }) +} + +func TestAccNetworkMonitorMonitor_tags(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_networkmonitor_monitor.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckMonitorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccMonitorConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccMonitorConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccMonitorConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + +func TestAccNetworkMonitorMonitor_disappears(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_networkmonitor_monitor.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckMonitorDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccMonitorConfig_basic(rName, 30), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMonitorExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkmonitor.ResourceMonitor, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckMonitorDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkMonitorClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmonitor_monitor" { + continue + } + + _, err := tfnetworkmonitor.FindMonitorByName(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CloudWatch Network Monitor Monitor %s still exists", rs.Primary.ID) + } + + return nil + } +} + +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).NetworkMonitorClient(ctx) + + _, err := tfnetworkmonitor.FindMonitorByName(ctx, conn, rs.Primary.ID) + + return err + } +} + +func testAccMonitorConfig_basic(rName string, aggregation int) string { + return fmt.Sprintf(` +resource "aws_networkmonitor_monitor" "test" { + aggregation_period = %[2]d + monitor_name = %[1]q +} +`, rName, aggregation) +} + +func testAccMonitorConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_networkmonitor_monitor" "test" { + aggregation_period = 30 + monitor_name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccMonitorConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_networkmonitor_monitor" "test" { + aggregation_period = 30 + monitor_name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/networkmonitor/probe.go b/internal/service/networkmonitor/probe.go new file mode 100644 index 000000000000..7d11f8383863 --- /dev/null +++ b/internal/service/networkmonitor/probe.go @@ -0,0 +1,432 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkmonitor + +import ( + "context" + "fmt" + "time" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkmonitor" + awstypes "github.com/aws/aws-sdk-go-v2/service/networkmonitor/types" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "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/id" + "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/flex" + "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(name="Probe") +// @Tags(identifierAttribute="arn") +func newProbeResource(context.Context) (resource.ResourceWithConfigure, error) { + return &probeResource{}, nil +} + +type probeResource struct { + framework.ResourceWithConfigure + framework.WithImportByID +} + +func (*probeResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_networkmonitor_probe" +} + +func (r *probeResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "address_family": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.AddressFamily](), + Computed: true, + }, + names.AttrARN: framework.ARNAttributeComputedOnly(), + names.AttrDestination: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 255), + }, + }, + "destination_port": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(0, 65536), + }, + }, + names.AttrID: framework.IDAttribute(), + "monitor_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexache.MustCompile("[a-zA-Z0-9_-]+"), "Must match [a-zA-Z0-9_-]+"), + stringvalidator.LengthBetween(1, 255), + }, + }, + "packet_size": schema.Int64Attribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + Validators: []validator.Int64{ + int64validator.Between(56, 8500), + }, + }, + "probe_id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + names.AttrProtocol: schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.Protocol](), + Required: true, + }, + "source_arn": schema.StringAttribute{ + CustomType: fwtypes.ARNType, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + names.AttrVPCID: schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *probeResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data probeResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkMonitorClient(ctx) + + probeInput := &awstypes.ProbeInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, probeInput)...) + if response.Diagnostics.HasError() { + return + } + + input := &networkmonitor.CreateProbeInput{ + ClientToken: aws.String(id.UniqueId()), + MonitorName: fwflex.StringFromFramework(ctx, data.MonitorName), + Probe: probeInput, + Tags: getTagsIn(ctx), + } + + outputCP, err := conn.CreateProbe(ctx, input) + + if err != nil { + response.Diagnostics.AddError("creating CloudWatch Network Monitor Probe (%s)", err.Error()) + + return + } + + // Set values for unknowns. + data.ProbeARN = fwflex.StringToFramework(ctx, outputCP.ProbeArn) + data.ProbeID = fwflex.StringToFramework(ctx, outputCP.ProbeId) + data.setID() + + outputGP, err := waitProbeReady(ctx, conn, data.MonitorName.ValueString(), data.ProbeID.ValueString()) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for CloudWatch Network Monitor Probe (%s) create", data.ID.ValueString()), err.Error()) + + return + } + + // Set values for unknowns. + data.AddressFamily = fwtypes.StringEnumValue(outputGP.AddressFamily) + if data.PacketSize.IsUnknown() { + data.PacketSize = fwflex.Int32ToFramework(ctx, outputGP.PacketSize) + } + data.VpcID = fwflex.StringToFramework(ctx, outputGP.VpcId) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *probeResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data probeResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + if err := data.InitFromID(); err != nil { + response.Diagnostics.AddError("parsing resource ID", err.Error()) + + return + } + + conn := r.Meta().NetworkMonitorClient(ctx) + + output, err := findProbeByTwoPartKey(ctx, conn, data.MonitorName.ValueString(), data.ProbeID.ValueString()) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading CloudWatch Network Monitor Probe (%s)", data.ID.String()), err.Error()) + + return + } + + // Set attributes for import. + 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 *probeResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new probeResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkMonitorClient(ctx) + + if !new.Destination.Equal(old.Destination) || + !new.DestinationPort.Equal(old.DestinationPort) || + !new.PacketSize.Equal(old.PacketSize) || + !new.Protocol.Equal(old.Protocol) { + input := &networkmonitor.UpdateProbeInput{ + MonitorName: fwflex.StringFromFramework(ctx, new.MonitorName), + ProbeId: fwflex.StringFromFramework(ctx, new.ProbeID), + } + + if !new.Destination.Equal(old.Destination) { + input.Destination = fwflex.StringFromFramework(ctx, new.Destination) + } + if !new.DestinationPort.Equal(old.DestinationPort) { + input.DestinationPort = fwflex.Int32FromFramework(ctx, new.DestinationPort) + } + if !new.PacketSize.Equal(old.PacketSize) { + input.PacketSize = fwflex.Int32FromFramework(ctx, new.PacketSize) + } + if !new.Protocol.Equal(old.Protocol) { + input.Protocol = new.Protocol.ValueEnum() + } + + _, err := conn.UpdateProbe(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating CloudWatch Network Monitor Probe (%s)", new.ID.String()), err.Error()) + + return + } + + outputGP, err := waitProbeReady(ctx, conn, new.MonitorName.ValueString(), new.ProbeID.ValueString()) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for CloudWatch Network Monitor Probe (%s) update", new.ID.ValueString()), err.Error()) + + return + } + + // Set values for unknowns. + new.AddressFamily = fwtypes.StringEnumValue(outputGP.AddressFamily) + } else { + new.AddressFamily = old.AddressFamily + } + + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *probeResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data probeResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().NetworkMonitorClient(ctx) + + _, err := conn.DeleteProbe(ctx, &networkmonitor.DeleteProbeInput{ + MonitorName: fwflex.StringFromFramework(ctx, data.MonitorName), + ProbeId: fwflex.StringFromFramework(ctx, data.ProbeID), + }) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting CloudWatch Network Monitor Probe (%s)", data.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitProbeDeleted(ctx, conn, data.MonitorName.ValueString(), data.ProbeID.ValueString()); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for CloudWatch Network Monitor Probe (%s) delete", data.ID.ValueString()), err.Error()) + + return + } +} + +func (r *probeResource) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +func findProbeByTwoPartKey(ctx context.Context, conn *networkmonitor.Client, monitorName, probeID string) (*networkmonitor.GetProbeOutput, error) { + input := &networkmonitor.GetProbeInput{ + MonitorName: aws.String(monitorName), + ProbeId: aws.String(probeID), + } + + output, err := conn.GetProbe(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 statusProbe(ctx context.Context, conn *networkmonitor.Client, monitorName, probeID string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findProbeByTwoPartKey(ctx, conn, monitorName, probeID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.State), nil + } +} + +func waitProbeReady(ctx context.Context, conn *networkmonitor.Client, monitorName, probeID string) (*networkmonitor.GetProbeOutput, error) { + const ( + timeout = time.Minute * 15 + ) + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ProbeStatePending), + Target: enum.Slice(awstypes.ProbeStateActive, awstypes.ProbeStateInactive), + Refresh: statusProbe(ctx, conn, monitorName, probeID), + Timeout: timeout, + MinTimeout: 10 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmonitor.GetProbeOutput); ok { + return output, err + } + + return nil, err +} + +func waitProbeDeleted(ctx context.Context, conn *networkmonitor.Client, monitorName, probeID string) (*networkmonitor.GetProbeOutput, error) { + const ( + timeout = time.Minute * 15 + ) + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(awstypes.ProbeStateActive, awstypes.ProbeStateInactive, awstypes.ProbeStateDeleting), + Target: []string{}, + Refresh: statusProbe(ctx, conn, monitorName, probeID), + Timeout: timeout, + MinTimeout: 10 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmonitor.GetProbeOutput); ok { + return output, err + } + + return nil, err +} + +type probeResourceModel struct { + AddressFamily fwtypes.StringEnum[awstypes.AddressFamily] `tfsdk:"address_family"` + Destination types.String `tfsdk:"destination"` + DestinationPort types.Int64 `tfsdk:"destination_port"` + ID types.String `tfsdk:"id"` + MonitorName types.String `tfsdk:"monitor_name"` + PacketSize types.Int64 `tfsdk:"packet_size"` + ProbeARN types.String `tfsdk:"arn"` + ProbeID types.String `tfsdk:"probe_id"` + Protocol fwtypes.StringEnum[awstypes.Protocol] `tfsdk:"protocol"` + SourceARN fwtypes.ARN `tfsdk:"source_arn"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + VpcID types.String `tfsdk:"vpc_id"` +} + +const ( + probeResourceIDPartCount = 2 +) + +func (m *probeResourceModel) InitFromID() error { + id := m.ID.ValueString() + parts, err := flex.ExpandResourceId(id, probeResourceIDPartCount, false) + + if err != nil { + return err + } + + m.MonitorName = types.StringValue(parts[0]) + m.ProbeID = types.StringValue(parts[1]) + + return nil +} + +func (m *probeResourceModel) setID() { + m.ID = types.StringValue(errs.Must(flex.FlattenResourceId([]string{m.MonitorName.ValueString(), m.ProbeID.ValueString()}, probeResourceIDPartCount, false))) +} diff --git a/internal/service/networkmonitor/probe_test.go b/internal/service/networkmonitor/probe_test.go new file mode 100644 index 000000000000..1bbf0a6b2531 --- /dev/null +++ b/internal/service/networkmonitor/probe_test.go @@ -0,0 +1,348 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package networkmonitor_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go/service/ec2" + 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" + tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2" + tfnetworkmonitor "github.com/hashicorp/terraform-provider-aws/internal/service/networkmonitor" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccNetworkMonitorProbe_basic(t *testing.T) { + ctx := acctest.Context(t) + var vpc ec2.Vpc + resourceName := "aws_networkmonitor_probe.test" + vpcResourceName := "aws_vpc.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProbeDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProbeConfig_basic(rName, "10.0.0.1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProbeExists(ctx, resourceName), + resource.TestCheckResourceAttrSet(resourceName, "address_family"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrDestination, "10.0.0.1"), + resource.TestCheckNoResourceAttr(resourceName, "destination_port"), + resource.TestCheckResourceAttrSet(resourceName, "packet_size"), + resource.TestCheckResourceAttrSet(resourceName, "probe_id"), + resource.TestCheckResourceAttr(resourceName, names.AttrProtocol, "ICMP"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttrSet(resourceName, names.AttrVPCID), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { // nosemgrep:ci.test-config-funcs-correct-form + Config: acctest.ConfigVPCWithSubnets(rName, 1), + Check: resource.ComposeTestCheckFunc( + acctest.CheckVPCExists(ctx, vpcResourceName, &vpc), + testAccCheckProbeDeleteSecurityGroup(ctx, rName, &vpc), + ), + }, + }, + }) +} + +func TestAccNetworkMonitorProbe_disappears(t *testing.T) { + ctx := acctest.Context(t) + var vpc ec2.Vpc + resourceName := "aws_networkmonitor_probe.test" + vpcResourceName := "aws_vpc.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProbeDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProbeConfig_basic(rName, "10.0.0.1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckProbeExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfnetworkmonitor.ResourceProbe, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + { // nosemgrep:ci.test-config-funcs-correct-form + Config: acctest.ConfigVPCWithSubnets(rName, 1), + Check: resource.ComposeTestCheckFunc( + acctest.CheckVPCExists(ctx, vpcResourceName, &vpc), + testAccCheckProbeDeleteSecurityGroup(ctx, rName, &vpc), + ), + }, + }, + }) +} + +func TestAccNetworkMonitorProbe_tags(t *testing.T) { + ctx := acctest.Context(t) + var vpc ec2.Vpc + resourceName := "aws_networkmonitor_probe.test" + vpcResourceName := "aws_vpc.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProbeDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProbeConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckProbeExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccProbeConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckProbeExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccProbeConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckProbeExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { // nosemgrep:ci.test-config-funcs-correct-form + Config: acctest.ConfigVPCWithSubnets(rName, 1), + Check: resource.ComposeTestCheckFunc( + acctest.CheckVPCExists(ctx, vpcResourceName, &vpc), + testAccCheckProbeDeleteSecurityGroup(ctx, rName, &vpc), + ), + }, + }, + }) +} + +func TestAccNetworkMonitorProbe_update(t *testing.T) { + ctx := acctest.Context(t) + var vpc ec2.Vpc + resourceName := "aws_networkmonitor_probe.test" + vpcResourceName := "aws_vpc.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.NetworkMonitorServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProbeDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProbeConfig_full(rName, "10.0.0.1", 8080, 256), + Check: resource.ComposeTestCheckFunc( + testAccCheckProbeExists(ctx, resourceName), + resource.TestCheckResourceAttrSet(resourceName, "address_family"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrDestination, "10.0.0.1"), + resource.TestCheckResourceAttr(resourceName, "destination_port", "8080"), + resource.TestCheckResourceAttr(resourceName, "packet_size", "256"), + resource.TestCheckResourceAttrSet(resourceName, "probe_id"), + resource.TestCheckResourceAttr(resourceName, names.AttrProtocol, "TCP"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttrSet(resourceName, names.AttrVPCID), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccProbeConfig_full(rName, "10.0.0.2", 8443, 512), + Check: resource.ComposeTestCheckFunc( + testAccCheckProbeExists(ctx, resourceName), + resource.TestCheckResourceAttrSet(resourceName, "address_family"), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, names.AttrDestination, "10.0.0.2"), + resource.TestCheckResourceAttr(resourceName, "destination_port", "8443"), + resource.TestCheckResourceAttr(resourceName, "packet_size", "512"), + resource.TestCheckResourceAttrSet(resourceName, "probe_id"), + resource.TestCheckResourceAttr(resourceName, names.AttrProtocol, "TCP"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct0), + resource.TestCheckResourceAttrSet(resourceName, names.AttrVPCID), + ), + }, + { // nosemgrep:ci.test-config-funcs-correct-form + Config: acctest.ConfigVPCWithSubnets(rName, 1), + Check: resource.ComposeTestCheckFunc( + acctest.CheckVPCExists(ctx, vpcResourceName, &vpc), + testAccCheckProbeDeleteSecurityGroup(ctx, rName, &vpc), + ), + }, + }, + }) +} + +func testAccCheckProbeDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkMonitorClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmonitor_probe" { + continue + } + + _, err := tfnetworkmonitor.FindProbeByTwoPartKey(ctx, conn, rs.Primary.Attributes["monitor_name"], rs.Primary.Attributes["probe_id"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CloudWatch Network Monitor Probe %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckProbeExists(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).NetworkMonitorClient(ctx) + + _, err := tfnetworkmonitor.FindProbeByTwoPartKey(ctx, conn, rs.Primary.Attributes["monitor_name"], rs.Primary.Attributes["probe_id"]) + + return err + } +} + +func testAccCheckProbeDeleteSecurityGroup(ctx context.Context, rName string, vpc *ec2.Vpc) resource.TestCheckFunc { + return func(s *terraform.State) error { + meta := acctest.Provider.Meta() + conn := meta.(*conns.AWSClient).EC2Conn(ctx) + + description := "Created By Amazon CloudWatch Network Monitor for " + rName + v, err := tfec2.FindSecurityGroupByDescriptionAndVPCID(ctx, conn, description, aws.ToString(vpc.VpcId)) + + if tfresource.NotFound(err) { + // Already gone. + return nil + } + + if err != nil { + return err + } + + r := tfec2.ResourceSecurityGroup() + d := r.Data(nil) + d.SetId(aws.ToString(v.GroupId)) + d.Set("revoke_rules_on_delete", true) + + err = acctest.DeleteResource(ctx, r, d, meta) + + return err + } +} + +func testAccProbeConfig_base(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 1), fmt.Sprintf(` +resource "aws_networkmonitor_monitor" "test" { + aggregation_period = 30 + monitor_name = %[1]q + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccProbeConfig_basic(rName, destination string) string { + return acctest.ConfigCompose(testAccProbeConfig_base(rName), fmt.Sprintf(` +resource "aws_networkmonitor_probe" "test" { + monitor_name = aws_networkmonitor_monitor.test.monitor_name + destination = %[2]q + protocol = "ICMP" + source_arn = aws_subnet.test[0].arn +} +`, rName, destination)) +} + +func testAccProbeConfig_full(rName, destination string, port, packetSize int) string { + return acctest.ConfigCompose(testAccProbeConfig_base(rName), fmt.Sprintf(` +resource "aws_networkmonitor_probe" "test" { + monitor_name = aws_networkmonitor_monitor.test.monitor_name + destination = %[2]q + destination_port = %[3]d + protocol = "TCP" + source_arn = aws_subnet.test[0].arn + packet_size = %[4]d +} +`, rName, destination, port, packetSize)) +} + +func testAccProbeConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccProbeConfig_base(rName), fmt.Sprintf(` +resource "aws_networkmonitor_probe" "test" { + monitor_name = aws_networkmonitor_monitor.test.monitor_name + destination = "10.0.0.1" + protocol = "ICMP" + source_arn = aws_subnet.test[0].arn + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1)) +} + +func testAccProbeConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccProbeConfig_base(rName), fmt.Sprintf(` +resource "aws_networkmonitor_probe" "test" { + monitor_name = aws_networkmonitor_monitor.test.monitor_name + destination = "10.0.0.1" + protocol = "ICMP" + source_arn = aws_subnet.test[0].arn + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} diff --git a/internal/service/networkmonitor/service_endpoint_resolver_gen.go b/internal/service/networkmonitor/service_endpoint_resolver_gen.go new file mode 100644 index 000000000000..c4505310f2d4 --- /dev/null +++ b/internal/service/networkmonitor/service_endpoint_resolver_gen.go @@ -0,0 +1,82 @@ +// Code generated by internal/generate/servicepackage/main.go; DO NOT EDIT. + +package networkmonitor + +import ( + "context" + "fmt" + "net" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + networkmonitor_sdkv2 "github.com/aws/aws-sdk-go-v2/service/networkmonitor" + smithyendpoints "github.com/aws/smithy-go/endpoints" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-aws/internal/errs" +) + +var _ networkmonitor_sdkv2.EndpointResolverV2 = resolverSDKv2{} + +type resolverSDKv2 struct { + defaultResolver networkmonitor_sdkv2.EndpointResolverV2 +} + +func newEndpointResolverSDKv2() resolverSDKv2 { + return resolverSDKv2{ + defaultResolver: networkmonitor_sdkv2.NewDefaultEndpointResolverV2(), + } +} + +func (r resolverSDKv2) ResolveEndpoint(ctx context.Context, params networkmonitor_sdkv2.EndpointParameters) (endpoint smithyendpoints.Endpoint, err error) { + params = params.WithDefaults() + useFIPS := aws_sdkv2.ToBool(params.UseFIPS) + + if eps := params.Endpoint; aws_sdkv2.ToString(eps) != "" { + tflog.Debug(ctx, "setting endpoint", map[string]any{ + "tf_aws.endpoint": endpoint, + }) + + if useFIPS { + tflog.Debug(ctx, "endpoint set, ignoring UseFIPSEndpoint setting") + params.UseFIPS = aws_sdkv2.Bool(false) + } + + return r.defaultResolver.ResolveEndpoint(ctx, params) + } else if useFIPS { + ctx = tflog.SetField(ctx, "tf_aws.use_fips", useFIPS) + + endpoint, err = r.defaultResolver.ResolveEndpoint(ctx, params) + if err != nil { + return endpoint, err + } + + tflog.Debug(ctx, "endpoint resolved", map[string]any{ + "tf_aws.endpoint": endpoint.URI.String(), + }) + + hostname := endpoint.URI.Hostname() + _, err = net.LookupHost(hostname) + if err != nil { + if dnsErr, ok := errs.As[*net.DNSError](err); ok && dnsErr.IsNotFound { + tflog.Debug(ctx, "default endpoint host not found, disabling FIPS", map[string]any{ + "tf_aws.hostname": hostname, + }) + params.UseFIPS = aws_sdkv2.Bool(false) + } else { + err = fmt.Errorf("looking up networkmonitor endpoint %q: %s", hostname, err) + return + } + } else { + return endpoint, err + } + } + + return r.defaultResolver.ResolveEndpoint(ctx, params) +} + +func withBaseEndpoint(endpoint string) func(*networkmonitor_sdkv2.Options) { + return func(o *networkmonitor_sdkv2.Options) { + if endpoint != "" { + o.BaseEndpoint = aws_sdkv2.String(endpoint) + } + } +} diff --git a/internal/service/networkmonitor/service_endpoints_gen_test.go b/internal/service/networkmonitor/service_endpoints_gen_test.go new file mode 100644 index 000000000000..3af81e1d308a --- /dev/null +++ b/internal/service/networkmonitor/service_endpoints_gen_test.go @@ -0,0 +1,610 @@ +// Code generated by internal/generate/serviceendpointtests/main.go; DO NOT EDIT. + +package networkmonitor_test + +import ( + "context" + "errors" + "fmt" + "maps" + "net" + "net/url" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" + networkmonitor_sdkv2 "github.com/aws/aws-sdk-go-v2/service/networkmonitor" + "github.com/aws/smithy-go/middleware" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + terraformsdk "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + "github.com/hashicorp/terraform-provider-aws/names" +) + +type endpointTestCase struct { + with []setupFunc + expected caseExpectations +} + +type caseSetup struct { + config map[string]any + configFile configFile + environmentVariables map[string]string +} + +type configFile struct { + baseUrl string + serviceUrl string +} + +type caseExpectations struct { + diags diag.Diagnostics + endpoint string + region string +} + +type apiCallParams struct { + endpoint string + region string +} + +type setupFunc func(setup *caseSetup) + +type callFunc func(ctx context.Context, t *testing.T, meta *conns.AWSClient) apiCallParams + +const ( + packageNameConfigEndpoint = "https://packagename-config.endpoint.test/" + awsServiceEnvvarEndpoint = "https://service-envvar.endpoint.test/" + baseEnvvarEndpoint = "https://base-envvar.endpoint.test/" + serviceConfigFileEndpoint = "https://service-configfile.endpoint.test/" + baseConfigFileEndpoint = "https://base-configfile.endpoint.test/" +) + +const ( + packageName = "networkmonitor" + awsEnvVar = "AWS_ENDPOINT_URL_NETWORKMONITOR" + baseEnvVar = "AWS_ENDPOINT_URL" + configParam = "networkmonitor" +) + +const ( + expectedCallRegion = "us-west-2" //lintignore:AWSAT003 +) + +func TestEndpointConfiguration(t *testing.T) { //nolint:paralleltest // uses t.Setenv + const providerRegion = "us-west-2" //lintignore:AWSAT003 + const expectedEndpointRegion = providerRegion + + testcases := map[string]endpointTestCase{ + "no config": { + with: []setupFunc{withNoConfig}, + expected: expectDefaultEndpoint(t, expectedEndpointRegion), + }, + + // Package name endpoint on Config + + "package name endpoint config": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides aws service envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withAwsEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base envvar": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEnvVar, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides service config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withServiceEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + "package name endpoint config overrides base config file": { + with: []setupFunc{ + withPackageNameEndpointInConfig, + withBaseEndpointInConfigFile, + }, + expected: expectPackageNameConfigEndpoint(), + }, + + // Service endpoint in AWS envvar + + "service aws envvar": { + with: []setupFunc{ + withAwsEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base envvar": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEnvVar, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides service config file": { + with: []setupFunc{ + withAwsEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + "service aws envvar overrides base config file": { + with: []setupFunc{ + withAwsEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectAwsEnvVarEndpoint(), + }, + + // Base endpoint in envvar + + "base endpoint envvar": { + with: []setupFunc{ + withBaseEnvVar, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides service config file": { + with: []setupFunc{ + withBaseEnvVar, + withServiceEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + "base endpoint envvar overrides base config file": { + with: []setupFunc{ + withBaseEnvVar, + withBaseEndpointInConfigFile, + }, + expected: expectBaseEnvVarEndpoint(), + }, + + // Service endpoint in config file + + "service config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + "service config file overrides base config file": { + with: []setupFunc{ + withServiceEndpointInConfigFile, + withBaseEndpointInConfigFile, + }, + expected: expectServiceConfigFileEndpoint(), + }, + + // Base endpoint in config file + + "base endpoint config file": { + with: []setupFunc{ + withBaseEndpointInConfigFile, + }, + expected: expectBaseConfigFileEndpoint(), + }, + + // Use FIPS endpoint on Config + + "use fips config": { + with: []setupFunc{ + withUseFIPSInConfig, + }, + expected: expectDefaultFIPSEndpoint(t, expectedEndpointRegion), + }, + + "use fips config with package name endpoint config": { + with: []setupFunc{ + withUseFIPSInConfig, + withPackageNameEndpointInConfig, + }, + expected: expectPackageNameConfigEndpoint(), + }, + } + + for name, testcase := range testcases { //nolint:paralleltest // uses t.Setenv + testcase := testcase + + t.Run(name, func(t *testing.T) { + testEndpointCase(t, providerRegion, testcase, callService) + }) + } +} + +func defaultEndpoint(region string) (url.URL, error) { + r := networkmonitor_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.Background(), networkmonitor_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + }) + if err != nil { + return url.URL{}, err + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI, nil +} + +func defaultFIPSEndpoint(region string) (url.URL, error) { + r := networkmonitor_sdkv2.NewDefaultEndpointResolverV2() + + ep, err := r.ResolveEndpoint(context.Background(), networkmonitor_sdkv2.EndpointParameters{ + Region: aws_sdkv2.String(region), + UseFIPS: aws_sdkv2.Bool(true), + }) + if err != nil { + return url.URL{}, err + } + + if ep.URI.Path == "" { + ep.URI.Path = "/" + } + + return ep.URI, nil +} + +func callService(ctx context.Context, t *testing.T, meta *conns.AWSClient) apiCallParams { + t.Helper() + + client := meta.NetworkMonitorClient(ctx) + + var result apiCallParams + + _, err := client.ListMonitors(ctx, &networkmonitor_sdkv2.ListMonitorsInput{}, + func(opts *networkmonitor_sdkv2.Options) { + opts.APIOptions = append(opts.APIOptions, + addRetrieveEndpointURLMiddleware(t, &result.endpoint), + addRetrieveRegionMiddleware(&result.region), + addCancelRequestMiddleware(), + ) + }, + ) + if err == nil { + t.Fatal("Expected an error, got none") + } else if !errors.Is(err, errCancelOperation) { + t.Fatalf("Unexpected error: %s", err) + } + + return result +} + +func withNoConfig(_ *caseSetup) { + // no-op +} + +func withPackageNameEndpointInConfig(setup *caseSetup) { + if _, ok := setup.config[names.AttrEndpoints]; !ok { + setup.config[names.AttrEndpoints] = []any{ + map[string]any{}, + } + } + endpoints := setup.config[names.AttrEndpoints].([]any)[0].(map[string]any) + endpoints[packageName] = packageNameConfigEndpoint +} + +func withAwsEnvVar(setup *caseSetup) { + setup.environmentVariables[awsEnvVar] = awsServiceEnvvarEndpoint +} + +func withBaseEnvVar(setup *caseSetup) { + setup.environmentVariables[baseEnvVar] = baseEnvvarEndpoint +} + +func withServiceEndpointInConfigFile(setup *caseSetup) { + setup.configFile.serviceUrl = serviceConfigFileEndpoint +} + +func withBaseEndpointInConfigFile(setup *caseSetup) { + setup.configFile.baseUrl = baseConfigFileEndpoint +} + +func withUseFIPSInConfig(setup *caseSetup) { + setup.config["use_fips_endpoint"] = true +} + +func expectDefaultEndpoint(t *testing.T, region string) caseExpectations { + t.Helper() + + endpoint, err := defaultEndpoint(region) + if err != nil { + t.Fatalf("resolving accessanalyzer default endpoint: %s", err) + } + + return caseExpectations{ + endpoint: endpoint.String(), + region: expectedCallRegion, + } +} + +func expectDefaultFIPSEndpoint(t *testing.T, region string) caseExpectations { + t.Helper() + + endpoint, err := defaultFIPSEndpoint(region) + if err != nil { + t.Fatalf("resolving accessanalyzer FIPS endpoint: %s", err) + } + + hostname := endpoint.Hostname() + _, err = net.LookupHost(hostname) + if dnsErr, ok := errs.As[*net.DNSError](err); ok && dnsErr.IsNotFound { + return expectDefaultEndpoint(t, region) + } else if err != nil { + t.Fatalf("looking up accessanalyzer endpoint %q: %s", hostname, err) + } + + return caseExpectations{ + endpoint: endpoint.String(), + region: expectedCallRegion, + } +} + +func expectPackageNameConfigEndpoint() caseExpectations { + return caseExpectations{ + endpoint: packageNameConfigEndpoint, + region: expectedCallRegion, + } +} + +func expectAwsEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: awsServiceEnvvarEndpoint, + region: expectedCallRegion, + } +} + +func expectBaseEnvVarEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseEnvvarEndpoint, + region: expectedCallRegion, + } +} + +func expectServiceConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: serviceConfigFileEndpoint, + region: expectedCallRegion, + } +} + +func expectBaseConfigFileEndpoint() caseExpectations { + return caseExpectations{ + endpoint: baseConfigFileEndpoint, + region: expectedCallRegion, + } +} + +func testEndpointCase(t *testing.T, region string, testcase endpointTestCase, callF callFunc) { + t.Helper() + + ctx := context.Background() + + setup := caseSetup{ + config: map[string]any{}, + environmentVariables: map[string]string{}, + } + + for _, f := range testcase.with { + f(&setup) + } + + config := map[string]any{ + names.AttrAccessKey: servicemocks.MockStaticAccessKey, + names.AttrSecretKey: servicemocks.MockStaticSecretKey, + names.AttrRegion: region, + names.AttrSkipCredentialsValidation: true, + names.AttrSkipRequestingAccountID: true, + } + + maps.Copy(config, setup.config) + + if setup.configFile.baseUrl != "" || setup.configFile.serviceUrl != "" { + config[names.AttrProfile] = "default" + tempDir := t.TempDir() + writeSharedConfigFile(t, &config, tempDir, generateSharedConfigFile(setup.configFile)) + } + + for k, v := range setup.environmentVariables { + t.Setenv(k, v) + } + + p, err := provider.New(ctx) + if err != nil { + t.Fatal(err) + } + + expectedDiags := testcase.expected.diags + expectedDiags = append( + expectedDiags, + errs.NewWarningDiagnostic( + "AWS account ID not found for provider", + "See https://registry.terraform.io/providers/hashicorp/aws/latest/docs#skip_requesting_account_id for implications.", + ), + ) + + diags := p.Configure(ctx, terraformsdk.NewResourceConfigRaw(config)) + + if diff := cmp.Diff(diags, expectedDiags, cmp.Comparer(sdkdiag.Comparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diags.HasError() { + return + } + + meta := p.Meta().(*conns.AWSClient) + + callParams := callF(ctx, t, meta) + + if e, a := testcase.expected.endpoint, callParams.endpoint; e != a { + t.Errorf("expected endpoint %q, got %q", e, a) + } + + if e, a := testcase.expected.region, callParams.region; e != a { + t.Errorf("expected region %q, got %q", e, a) + } +} + +func addRetrieveEndpointURLMiddleware(t *testing.T, endpoint *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + retrieveEndpointURLMiddleware(t, endpoint), + middleware.After, + ) + } +} + +func retrieveEndpointURLMiddleware(t *testing.T, endpoint *string) middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Retrieve Endpoint", + func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + t.Helper() + + request, ok := in.Request.(*smithyhttp.Request) + if !ok { + t.Fatalf("Expected *github.com/aws/smithy-go/transport/http.Request, got %s", fullTypeName(in.Request)) + } + + url := request.URL + url.RawQuery = "" + url.Path = "/" + + *endpoint = url.String() + + return next.HandleFinalize(ctx, in) + }) +} + +func addRetrieveRegionMiddleware(region *string) func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Serialize.Add( + retrieveRegionMiddleware(region), + middleware.After, + ) + } +} + +func retrieveRegionMiddleware(region *string) middleware.SerializeMiddleware { + return middleware.SerializeMiddlewareFunc( + "Test: Retrieve Region", + func(ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler) (middleware.SerializeOutput, middleware.Metadata, error) { + *region = awsmiddleware.GetRegion(ctx) + + return next.HandleSerialize(ctx, in) + }, + ) +} + +var errCancelOperation = fmt.Errorf("Test: Canceling request") + +func addCancelRequestMiddleware() func(*middleware.Stack) error { + return func(stack *middleware.Stack) error { + return stack.Finalize.Add( + cancelRequestMiddleware(), + middleware.After, + ) + } +} + +// cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it +func cancelRequestMiddleware() middleware.FinalizeMiddleware { + return middleware.FinalizeMiddlewareFunc( + "Test: Cancel Requests", + func(_ context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (middleware.FinalizeOutput, middleware.Metadata, error) { + return middleware.FinalizeOutput{}, middleware.Metadata{}, errCancelOperation + }) +} + +func fullTypeName(i interface{}) string { + return fullValueTypeName(reflect.ValueOf(i)) +} + +func fullValueTypeName(v reflect.Value) string { + if v.Kind() == reflect.Ptr { + return "*" + fullValueTypeName(reflect.Indirect(v)) + } + + requestType := v.Type() + return fmt.Sprintf("%s.%s", requestType.PkgPath(), requestType.Name()) +} + +func generateSharedConfigFile(config configFile) string { + var buf strings.Builder + + buf.WriteString(` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey +`) + if config.baseUrl != "" { + buf.WriteString(fmt.Sprintf("endpoint_url = %s\n", config.baseUrl)) + } + + if config.serviceUrl != "" { + buf.WriteString(fmt.Sprintf(` +services = endpoint-test + +[services endpoint-test] +%[1]s = + endpoint_url = %[2]s +`, configParam, serviceConfigFileEndpoint)) + } + + return buf.String() +} + +func writeSharedConfigFile(t *testing.T, config *map[string]any, tempDir, content string) string { + t.Helper() + + file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file")) + if err != nil { + t.Fatalf("creating shared configuration file: %s", err) + } + + _, err = file.WriteString(content) + if err != nil { + t.Fatalf(" writing shared configuration file: %s", err) + } + + if v, ok := (*config)[names.AttrSharedConfigFiles]; !ok { + (*config)[names.AttrSharedConfigFiles] = []any{file.Name()} + } else { + (*config)[names.AttrSharedConfigFiles] = append(v.([]any), file.Name()) + } + + return file.Name() +} diff --git a/internal/service/networkmonitor/service_package_gen.go b/internal/service/networkmonitor/service_package_gen.go new file mode 100644 index 000000000000..de51b85ce97d --- /dev/null +++ b/internal/service/networkmonitor/service_package_gen.go @@ -0,0 +1,64 @@ +// Code generated by internal/generate/servicepackage/main.go; DO NOT EDIT. + +package networkmonitor + +import ( + "context" + + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" + networkmonitor_sdkv2 "github.com/aws/aws-sdk-go-v2/service/networkmonitor" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/types" + "github.com/hashicorp/terraform-provider-aws/names" +) + +type servicePackage struct{} + +func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.ServicePackageFrameworkDataSource { + return []*types.ServicePackageFrameworkDataSource{} +} + +func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { + return []*types.ServicePackageFrameworkResource{ + { + Factory: newMonitorResource, + Name: "Monitor", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }, + }, + { + Factory: newProbeResource, + Name: "Probe", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }, + }, + } +} + +func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { + return []*types.ServicePackageSDKDataSource{} +} + +func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { + return []*types.ServicePackageSDKResource{} +} + +func (p *servicePackage) ServicePackageName() string { + return names.NetworkMonitor +} + +// NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. +func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*networkmonitor_sdkv2.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) + + return networkmonitor_sdkv2.NewFromConfig(cfg, + networkmonitor_sdkv2.WithEndpointResolverV2(newEndpointResolverSDKv2()), + withBaseEndpoint(config[names.AttrEndpoint].(string)), + ), nil +} + +func ServicePackage(ctx context.Context) conns.ServicePackage { + return &servicePackage{} +} diff --git a/internal/service/networkmonitor/tags_gen.go b/internal/service/networkmonitor/tags_gen.go new file mode 100644 index 000000000000..40ab57a4ca80 --- /dev/null +++ b/internal/service/networkmonitor/tags_gen.go @@ -0,0 +1,128 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. +package networkmonitor + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/networkmonitor" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/logging" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/types/option" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// listTags lists networkmonitor service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func listTags(ctx context.Context, conn *networkmonitor.Client, identifier string, optFns ...func(*networkmonitor.Options)) (tftags.KeyValueTags, error) { + input := &networkmonitor.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(ctx, input, optFns...) + + if err != nil { + return tftags.New(ctx, nil), err + } + + return KeyValueTags(ctx, output.Tags), nil +} + +// ListTags lists networkmonitor service tags and set them in Context. +// It is called from outside this package. +func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier string) error { + tags, err := listTags(ctx, meta.(*conns.AWSClient).NetworkMonitorClient(ctx), identifier) + + if err != nil { + return err + } + + if inContext, ok := tftags.FromContext(ctx); ok { + inContext.TagsOut = option.Some(tags) + } + + return nil +} + +// map[string]string handling + +// Tags returns networkmonitor service tags. +func Tags(tags tftags.KeyValueTags) map[string]string { + return tags.Map() +} + +// KeyValueTags creates tftags.KeyValueTags from networkmonitor service tags. +func KeyValueTags(ctx context.Context, tags map[string]string) tftags.KeyValueTags { + return tftags.New(ctx, tags) +} + +// getTagsIn returns networkmonitor service tags from Context. +// nil is returned if there are no input tags. +func getTagsIn(ctx context.Context) map[string]string { + if inContext, ok := tftags.FromContext(ctx); ok { + if tags := Tags(inContext.TagsIn.UnwrapOrDefault()); len(tags) > 0 { + return tags + } + } + + return nil +} + +// setTagsOut sets networkmonitor service tags in Context. +func setTagsOut(ctx context.Context, tags map[string]string) { + if inContext, ok := tftags.FromContext(ctx); ok { + inContext.TagsOut = option.Some(KeyValueTags(ctx, tags)) + } +} + +// updateTags updates networkmonitor service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func updateTags(ctx context.Context, conn *networkmonitor.Client, identifier string, oldTagsMap, newTagsMap any, optFns ...func(*networkmonitor.Options)) error { + oldTags := tftags.New(ctx, oldTagsMap) + newTags := tftags.New(ctx, newTagsMap) + + ctx = tflog.SetField(ctx, logging.KeyResourceId, identifier) + + removedTags := oldTags.Removed(newTags) + removedTags = removedTags.IgnoreSystem(names.NetworkMonitor) + if len(removedTags) > 0 { + input := &networkmonitor.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: removedTags.Keys(), + } + + _, err := conn.UntagResource(ctx, input, optFns...) + + if err != nil { + return fmt.Errorf("untagging resource (%s): %w", identifier, err) + } + } + + updatedTags := oldTags.Updated(newTags) + updatedTags = updatedTags.IgnoreSystem(names.NetworkMonitor) + if len(updatedTags) > 0 { + input := &networkmonitor.TagResourceInput{ + ResourceArn: aws.String(identifier), + Tags: Tags(updatedTags), + } + + _, err := conn.TagResource(ctx, input, optFns...) + + if err != nil { + return fmt.Errorf("tagging resource (%s): %w", identifier, err) + } + } + + return nil +} + +// UpdateTags updates networkmonitor service tags. +// It is called from outside this package. +func (p *servicePackage) UpdateTags(ctx context.Context, meta any, identifier string, oldTags, newTags any) error { + return updateTags(ctx, meta.(*conns.AWSClient).NetworkMonitorClient(ctx), identifier, oldTags, newTags) +} diff --git a/internal/sweep/service_packages_gen_test.go b/internal/sweep/service_packages_gen_test.go index ea020e67cdfe..8b34242ed0f6 100644 --- a/internal/sweep/service_packages_gen_test.go +++ b/internal/sweep/service_packages_gen_test.go @@ -165,6 +165,7 @@ import ( "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/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/service/networkmonitor" "github.com/hashicorp/terraform-provider-aws/internal/service/oam" "github.com/hashicorp/terraform-provider-aws/internal/service/opensearch" "github.com/hashicorp/terraform-provider-aws/internal/service/opensearchserverless" @@ -407,6 +408,7 @@ func servicePackages(ctx context.Context) []conns.ServicePackage { neptunegraph.ServicePackage(ctx), networkfirewall.ServicePackage(ctx), networkmanager.ServicePackage(ctx), + networkmonitor.ServicePackage(ctx), oam.ServicePackage(ctx), opensearch.ServicePackage(ctx), opensearchserverless.ServicePackage(ctx), diff --git a/names/consts_gen.go b/names/consts_gen.go index 1741294bd73b..87d4fec2085c 100644 --- a/names/consts_gen.go +++ b/names/consts_gen.go @@ -159,6 +159,7 @@ const ( NeptuneGraph = "neptunegraph" NetworkFirewall = "networkfirewall" NetworkManager = "networkmanager" + NetworkMonitor = "networkmonitor" ObservabilityAccessManager = "oam" OpenSearch = "opensearch" OpenSearchIngestion = "osis" @@ -401,6 +402,7 @@ const ( NeptuneGraphServiceID = "Neptune Graph" NetworkFirewallServiceID = "Network Firewall" NetworkManagerServiceID = "NetworkManager" + NetworkMonitorServiceID = "NetworkMonitor" ObservabilityAccessManagerServiceID = "OAM" OpenSearchServiceID = "OpenSearch" OpenSearchIngestionServiceID = "OSIS" diff --git a/names/data/names_data.hcl b/names/data/names_data.hcl index 9979ce2b085e..6ff6e1fd771d 100644 --- a/names/data/names_data.hcl +++ b/names/data/names_data.hcl @@ -1780,6 +1780,31 @@ service "logs" { brand = "AWS" } +service "networkmonitor" { + + sdk { + id = "NetworkMonitor" + client_version = [2] + } + + names { + provider_name_upper = "NetworkMonitor" + human_friendly = "CloudWatch Network Monitor" + } + + endpoint_info { + endpoint_api_call = "ListMonitors" + } + + resource_prefix { + correct = "aws_networkmonitor_" + } + + provider_package_correct = "networkmonitor" + doc_prefix = ["networkmonitor_"] + brand = "Amazon" +} + service "rum" { go_packages { diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index f537ece977d0..a0e4599ececb 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -46,6 +46,7 @@ CloudWatch Application Insights CloudWatch Evidently CloudWatch Internet Monitor CloudWatch Logs +CloudWatch Network Monitor CloudWatch Observability Access Manager CloudWatch RUM CloudWatch Synthetics diff --git a/website/docs/guides/custom-service-endpoints.html.markdown b/website/docs/guides/custom-service-endpoints.html.markdown index a95c088d677c..d87066172150 100644 --- a/website/docs/guides/custom-service-endpoints.html.markdown +++ b/website/docs/guides/custom-service-endpoints.html.markdown @@ -227,6 +227,7 @@ provider "aws" {
neptunegraph
networkfirewall
networkmanager
networkmonitor
oam
(or cloudwatchobservabilityaccessmanager
)opensearch
(or opensearchservice
)opensearchserverless