diff --git a/.changelog/13251.txt b/.changelog/13251.txt new file mode 100644 index 00000000000..d76418807ed --- /dev/null +++ b/.changelog/13251.txt @@ -0,0 +1,83 @@ +```release-note:new-resource +aws_networkmanager_global_network +``` + +```release-note:new-resource +aws_networkmanager_site +``` + +```release-note:new-resource +aws_networkmanager_device +``` + +```release-note:new-resource +aws_networkmanager_transit_gateway_registration +``` + +```release-note:new-resource +aws_networkmanager_link +``` + +```release-note:new-resource +aws_networkmanager_link_association +``` + +```release-note:new-resource +aws_networkmanager_customer_gateway_association +``` + +```release-note:new-resource +aws_networkmanager_transit_gateway_connect_peer_association +``` + +```release-note:new-resource +aws_networkmanager_connection +``` + +```release-note:new-data-source +aws_networkmanager_global_network +``` + +```release-note:new-data-source +aws_networkmanager_global_networks +``` + +```release-note:new-data-source +aws_networkmanager_site +``` + +```release-note:new-data-source +aws_networkmanager_sites +``` + +```release-note:new-data-source +aws_networkmanager_device +``` + +```release-note:new-data-source +aws_networkmanager_devices +``` + +```release-note:new-data-source +aws_networkmanager_link +``` + +```release-note:new-data-source +aws_networkmanager_links +``` + +```release-note:enhancement +resource/aws_ec2_transit_gateway_connect_peer: Add `arn` attribute +``` + +```release-note:enhancement +data-source/aws_ec2_transit_gateway_connect_peer: Add `arn` attribute +``` + +```release-note:new-data-source +aws_networkmanager_connection +``` + +```release-note:new-data-source +aws_networkmanager_connections +``` \ No newline at end of file diff --git a/.changelog/23440.txt b/.changelog/23440.txt new file mode 100644 index 00000000000..25692d56289 --- /dev/null +++ b/.changelog/23440.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_iot_topic_rule: Add `cloudwatch_logs` and `error_action.cloudwatch_logs` arguments +``` \ No newline at end of file diff --git a/.changelog/23517.txt b/.changelog/23517.txt new file mode 100644 index 00000000000..9ab4adb7e08 --- /dev/null +++ b/.changelog/23517.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_amplify_app: Allow `repository` to be updated in-place +``` \ No newline at end of file diff --git a/.changelog/23533.txt b/.changelog/23533.txt new file mode 100644 index 00000000000..8a2219f72ff --- /dev/null +++ b/.changelog/23533.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_rds_cluster: Add Multi-AZ support for MySQL and PostgreSQL RDS clusters +``` diff --git a/.changelog/23535.txt b/.changelog/23535.txt new file mode 100644 index 00000000000..f4d7b48820b --- /dev/null +++ b/.changelog/23535.txt @@ -0,0 +1,3 @@ +```new-data-source +aws_ecrpublic_authorization_token +``` diff --git a/.changelog/23560.txt b/.changelog/23560.txt new file mode 100644 index 00000000000..5787a897152 --- /dev/null +++ b/.changelog/23560.txt @@ -0,0 +1,15 @@ +```release-note:bug +resource/aws_rds_event_subscription: Fix issue where `enabled` was sometimes not updated +``` + +```release-note:bug +resource/aws_db_instance: Fix issues where configured update timeout was not respected, and update would fail if instance were in the process of being configured. +``` + +```release-note:bug +resource/aws_rds_global_cluster: Fix ability to perform cluster version upgrades, including of clusters in distinct regions, such as previously got error: "Invalid database cluster identifier" +``` + +```release-note:enhancement +resource/aws_rds_global_cluster: Add configurable timeouts +``` diff --git a/.changelog/23600.txt b/.changelog/23600.txt new file mode 100644 index 00000000000..1fe84f981b3 --- /dev/null +++ b/.changelog/23600.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_ecs_service: `enable_ecs_managed_tags`, `load_balancer`, `propagate_tags` and `service_registries` can now be updated in-place +``` \ No newline at end of file diff --git a/.changelog/23606.txt b/.changelog/23606.txt new file mode 100644 index 00000000000..7827744e5db --- /dev/null +++ b/.changelog/23606.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_gamelift_game_server_group +``` diff --git a/.changelog/23631.txt b/.changelog/23631.txt new file mode 100644 index 00000000000..4878b643f0e --- /dev/null +++ b/.changelog/23631.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_transfer_server: Add `pre_authentication_login_banner` and `post_authentication_login_banner` arguments +``` \ No newline at end of file diff --git a/.changelog/23662.txt b/.changelog/23662.txt new file mode 100644 index 00000000000..10aba5e828c --- /dev/null +++ b/.changelog/23662.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_msk_configuration: Correctly set `latest_revision` as Computed when `server_properties` changes +``` \ No newline at end of file diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index 2cedd00cdcc..83c52d7ea69 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -218,6 +218,8 @@ service/kafka: - '((\*|-) ?`?|(data|resource) "?)aws_msk_' service/kafkaconnect: - '((\*|-) ?`?|(data|resource) "?)aws_mskconnect_' +service/keyspaces: + - '((\*|-) ?`?|(data|resource) "?)aws_keyspaces_' service/kinesis: - '((\*|-) ?`?|(data|resource) "?)aws_kinesis_stream' service/kinesisanalytics: diff --git a/.github/labeler-pr-triage.yml b/.github/labeler-pr-triage.yml index c9846bee6b2..8f5d6c19b2e 100644 --- a/.github/labeler-pr-triage.yml +++ b/.github/labeler-pr-triage.yml @@ -376,6 +376,9 @@ service/kafka: service/kafkaconnect: - 'internal/service/kafkaconnect/**/*' - 'website/**/mskconnect_*' +service/keyspaces: + - 'internal/service/keyspaces/**/*' + - 'website/**/keyspaces_*' service/kinesis: - 'internal/service/kinesis/**/*' - '*_aws_kinesis_stream*' diff --git a/.github/workflows/terraform_provider.yml b/.github/workflows/terraform_provider.yml index 45a750bd956..abc9482b96f 100644 --- a/.github/workflows/terraform_provider.yml +++ b/.github/workflows/terraform_provider.yml @@ -218,7 +218,7 @@ jobs: key: ${{ runner.os }}-go-pkg-mod-${{ hashFiles('go.sum') }} - name: Get all changed files id: changed-files - uses: tj-actions/changed-files@v17.2 + uses: tj-actions/changed-files@v18 - name: Get changed packages run: | touch /tmp/dirs_changed_all diff --git a/CHANGELOG.md b/CHANGELOG.md index f65f87c005e..9308fd5e57d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,46 @@ ## 4.6.0 (Unreleased) +FEATURES: + +* **New Data Source:** `aws_networkmanager_connection` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_connections` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_device` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_devices` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_global_network` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_global_networks` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_link` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_links` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_site` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Data Source:** `aws_networkmanager_sites` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_gamelift_game_server_group` ([#23606](https://github.com/hashicorp/terraform-provider-aws/issues/23606)) +* **New Resource:** `aws_networkmanager_connection` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_networkmanager_customer_gateway_association` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_networkmanager_device` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_networkmanager_global_network` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_networkmanager_link` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_networkmanager_link_association` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_networkmanager_site` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_networkmanager_transit_gateway_connect_peer_association` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* **New Resource:** `aws_networkmanager_transit_gateway_registration` ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) + ENHANCEMENTS: +* data-source/aws_ec2_transit_gateway_connect_peer: Add `arn` attribute ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) * data_source/aws_redshift_cluster: Add `availability_zone_relocation_enabled` attribute. ([#20812](https://github.com/hashicorp/terraform-provider-aws/issues/20812)) +* resource/aws_ec2_transit_gateway_connect_peer: Add `arn` attribute ([#13251](https://github.com/hashicorp/terraform-provider-aws/issues/13251)) +* resource/aws_ecs_service: `enable_ecs_managed_tags`, `load_balancer`, `propagate_tags` and `service_registries` can now be updated in-place ([#23600](https://github.com/hashicorp/terraform-provider-aws/issues/23600)) +* resource/aws_iot_topic_rule: Add `cloudwatch_logs` and `error_action.cloudwatch_logs` arguments ([#23440](https://github.com/hashicorp/terraform-provider-aws/issues/23440)) +* resource/aws_msk_configuration: Correctly set `latest_revision` as Computed when `server_properties` changes ([#23662](https://github.com/hashicorp/terraform-provider-aws/issues/23662)) +* resource/aws_rds_global_cluster: Add configurable timeouts ([#23560](https://github.com/hashicorp/terraform-provider-aws/issues/23560)) * resource/aws_redshift_cluster: Add `availability_zone_relocation_enabled` attribute and allow `availability_zone` to be changed in-place. ([#20812](https://github.com/hashicorp/terraform-provider-aws/issues/20812)) +* resource/aws_transfer_server: Add `pre_authentication_login_banner` and `post_authentication_login_banner` arguments ([#23631](https://github.com/hashicorp/terraform-provider-aws/issues/23631)) + +BUG FIXES: + +* resource/aws_amplify_app: Allow `repository` to be updated in-place ([#23517](https://github.com/hashicorp/terraform-provider-aws/issues/23517)) +* resource/aws_db_instance: Fix issues where configured update timeout was not respected, and update would fail if instance were in the process of being configured. ([#23560](https://github.com/hashicorp/terraform-provider-aws/issues/23560)) +* resource/aws_rds_event_subscription: Fix issue where `enabled` was sometimes not updated ([#23560](https://github.com/hashicorp/terraform-provider-aws/issues/23560)) +* resource/aws_rds_global_cluster: Fix ability to perform cluster version upgrades, including of clusters in distinct regions, such as previously got error: "Invalid database cluster identifier" ([#23560](https://github.com/hashicorp/terraform-provider-aws/issues/23560)) ## 4.5.0 (March 11, 2022) diff --git a/docs/contributing/contribution-checklists.md b/docs/contributing/contribution-checklists.md index 40af5794e1d..b198cfc9f3a 100644 --- a/docs/contributing/contribution-checklists.md +++ b/docs/contributing/contribution-checklists.md @@ -332,7 +332,7 @@ More details about this code generation, including fixes for potential error mes input := &eks.CreateClusterInput{ /* ... other configuration ... */ - Tags: Tags(tags.IgnoreAws()), + Tags: Tags(tags.IgnoreAWS()), } ``` @@ -348,7 +348,7 @@ More details about this code generation, including fixes for potential error mes } if len(tags) > 0 { - input.Tags = Tags(tags.IgnoreAws()) + input.Tags = Tags(tags.IgnoreAWS()) } ``` @@ -388,7 +388,7 @@ More details about this code generation, including fixes for potential error mes /* ... other d.Set(...) logic ... */ - tags := keyvaluetags.EksKeyValueTags(cluster.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig) + tags := keyvaluetags.EksKeyValueTags(cluster.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { return fmt.Errorf("error setting tags: %w", err) @@ -414,7 +414,7 @@ More details about this code generation, including fixes for potential error mes return fmt.Errorf("error listing tags for resource (%s): %w", arn, err) } - tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { return fmt.Errorf("error setting tags: %w", err) @@ -595,7 +595,7 @@ filters := namevaluesfilters.New(map[string]string{ "internet-gateway-id": d.Get("internet_gateway_id").(string), }) // Add filters based on keyvalue tags (N.B. Not applicable to all AWS services that support filtering) -filters.Add(namevaluesfilters.Ec2Tags(keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map())) +filters.Add(namevaluesfilters.Ec2Tags(keyvaluetags.New(d.Get("tags").(map[string]interface{})).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map())) // Add filters based on the custom filtering "filter" attribute. filters.Add(d.Get("filter").(*schema.Set)) diff --git a/go.mod b/go.mod index 358151c24bb..28ad6bad9a6 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.17 require ( github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 - github.com/aws/aws-sdk-go v1.43.9 + github.com/aws/aws-sdk-go v1.43.17 github.com/aws/aws-sdk-go-v2 v1.15.0 - github.com/aws/aws-sdk-go-v2/service/route53domains v1.11.0 + github.com/aws/aws-sdk-go-v2/service/route53domains v1.12.0 github.com/beevik/etree v1.1.0 github.com/google/go-cmp v0.5.7 github.com/hashicorp/aws-cloudformation-resource-schema-sdk-go v0.16.0 diff --git a/go.sum b/go.sum index faaf8423f10..784511f3661 100644 --- a/go.sum +++ b/go.sum @@ -65,9 +65,8 @@ github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3A github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.42.18/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.42.52/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= -github.com/aws/aws-sdk-go v1.43.9 h1:k1S/29Bp2QD5ZopnGzIn0Sp63yyt3WH1JRE2OOU3Aig= -github.com/aws/aws-sdk-go v1.43.9/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go-v2 v1.14.0/go.mod h1:ZA3Y8V0LrlWj63MQAnRHgKf/5QB//LSZCPNWlWrNGLU= +github.com/aws/aws-sdk-go v1.43.17 h1:jDPBz1UuTxmyRo0eLgaRiro0fiI1zL7lkscqYxoEDLM= +github.com/aws/aws-sdk-go v1.43.17/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v1.15.0 h1:f9kWLNfyCzCB43eupDAk3/XgJ2EpgktiySD6leqs0js= github.com/aws/aws-sdk-go-v2 v1.15.0/go.mod h1:lJYcuZZEHWNIb6ugJjbQY1fykdoobWbOS7kJYb4APoI= github.com/aws/aws-sdk-go-v2/config v1.15.0 h1:cibCYF2c2uq0lsbu0Ggbg8RuGeiHCmXwUlTMS77CiK4= @@ -76,10 +75,8 @@ github.com/aws/aws-sdk-go-v2/credentials v1.10.0 h1:M/FFpf2w31F7xqJqJLgiM0mFpLOt github.com/aws/aws-sdk-go-v2/credentials v1.10.0/go.mod h1:HWJMr4ut5X+Lt/7epc7I6Llg5QIcoFHKAeIzw32t6EE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.0 h1:gUlb+I7NwDtqJUIRcFYDiheYa97PdVHG/5Iz+SwdoHE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.0/go.mod h1:prX26x9rmLwkEE1VVCelQOQgRN9sOVIssgowIJ270SE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.5/go.mod h1:2hXc8ooJqF2nAznsbJQIn+7h851/bu8GVC80OVTTqf8= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6 h1:xiGjGVQsem2cxoIX61uRGy+Jux2s9C/kKbTrWLdrU54= github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.6/go.mod h1:SSPEdf9spsFgJyhjrXvawfpyzrXHBCUe+2eQ1CjC1Ak= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.3.0/go.mod h1:miRSv9l093jX/t/j+mBCaLqFHo9xKYzJ7DGm1BsGoJM= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0 h1:bt3zw79tm209glISdMRCIVRCwvSDXxgAxh5KWe2qHkY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.0/go.mod h1:viTrxhAuejD+LszDahzAE2x40YjYWhMqzHxv2ZiWaME= github.com/aws/aws-sdk-go-v2/internal/ini v1.3.7 h1:QOMEP8jnO8sm0SX/4G7dbaIq2eEP2wcWEsF0jzrXLJc= @@ -88,13 +85,12 @@ github.com/aws/aws-sdk-go-v2/service/iam v1.18.0 h1:ZYpP40/QE7/R0zDxdrZyGGUijX26 github.com/aws/aws-sdk-go-v2/service/iam v1.18.0/go.mod h1:9wRsXAkRJ7qBWIDTFYa66Cx+oQJsPEnBYCPrinanpS8= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.0 h1:YQ3fTXACo7xeAqg0NiqcCmBOXJruUfh+4+O2qxF2EjQ= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.0/go.mod h1:R31ot6BgESRCIoxwfKtIHzZMo/vsZn2un81g9BJ4nmo= -github.com/aws/aws-sdk-go-v2/service/route53domains v1.11.0 h1:2zgdxEAnHQhwwqcZ7TioK2p4yVehUqvCiLKJghjn7s4= -github.com/aws/aws-sdk-go-v2/service/route53domains v1.11.0/go.mod h1:tq0XRh3gkEy4hGD5ssu1T5zSIxq8Ey5b+5JUv8SiiEs= +github.com/aws/aws-sdk-go-v2/service/route53domains v1.12.0 h1:PN0LQirFrjh9esAO80iZXo+asiTtLpjNCXpzZ+1BKCw= +github.com/aws/aws-sdk-go-v2/service/route53domains v1.12.0/go.mod h1:xzqCQW+Y6wn/4+9WVo3IPmnRTsN8Nwlw6cNUd6HVzqI= github.com/aws/aws-sdk-go-v2/service/sso v1.11.0 h1:gZLEXLH6NiU8Y52nRhK1jA+9oz7LZzBK242fi/ziXa4= github.com/aws/aws-sdk-go-v2/service/sso v1.11.0/go.mod h1:d1WcT0OjggjQCAdOkph8ijkr5sUwk1IH/VenOn7W1PU= github.com/aws/aws-sdk-go-v2/service/sts v1.16.0 h1:0+X/rJ2+DTBKWbUsn7WtF0JvNk/fRf928vkFsXkbbZs= github.com/aws/aws-sdk-go-v2/service/sts v1.16.0/go.mod h1:+8k4H2ASUZZXmjx/s3DFLo9tGBb44lkz3XcgfypJY7s= -github.com/aws/smithy-go v1.11.0/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/aws/smithy-go v1.11.1 h1:IQ+lPZVkSM3FRtyaDox41R8YS6iwPMYIreejOgPW49g= github.com/aws/smithy-go v1.11.1/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= diff --git a/infrastructure/repository/labels-service.tf b/infrastructure/repository/labels-service.tf index 5f12bfd0f12..58f273bf9a5 100644 --- a/infrastructure/repository/labels-service.tf +++ b/infrastructure/repository/labels-service.tf @@ -121,6 +121,7 @@ variable "service_labels" { "kafka", "kafkaconnect", "kendra", + "keyspaces", "kinesis", "kinesisanalytics", "kinesisanalyticsv2", diff --git a/infrastructure/repository/main.tf b/infrastructure/repository/main.tf index 1833a12650c..2f9b000b5f7 100644 --- a/infrastructure/repository/main.tf +++ b/infrastructure/repository/main.tf @@ -10,7 +10,7 @@ terraform { required_providers { github = { source = "integrations/github" - version = "4.20.0" + version = "4.21.0" } } diff --git a/internal/conns/conns.go b/internal/conns/conns.go index e0658523a0b..b4fd53d3e2a 100644 --- a/internal/conns/conns.go +++ b/internal/conns/conns.go @@ -148,6 +148,7 @@ import ( "github.com/aws/aws-sdk-go/service/kafka" "github.com/aws/aws-sdk-go/service/kafkaconnect" "github.com/aws/aws-sdk-go/service/kendra" + "github.com/aws/aws-sdk-go/service/keyspaces" "github.com/aws/aws-sdk-go/service/kinesis" "github.com/aws/aws-sdk-go/service/kinesisanalytics" "github.com/aws/aws-sdk-go/service/kinesisanalyticsv2" @@ -433,6 +434,7 @@ const ( Kafka = "kafka" KafkaConnect = "kafkaconnect" Kendra = "kendra" + Keyspaces = "keyspaces" Kinesis = "kinesis" KinesisAnalytics = "kinesisanalytics" KinesisAnalyticsV2 = "kinesisanalyticsv2" @@ -729,6 +731,7 @@ func init() { serviceData[Kafka] = &ServiceDatum{AWSClientName: "Kafka", AWSServiceName: kafka.ServiceName, AWSEndpointsID: kafka.EndpointsID, AWSServiceID: kafka.ServiceID, ProviderNameUpper: "Kafka", HCLKeys: []string{"kafka"}} serviceData[KafkaConnect] = &ServiceDatum{AWSClientName: "KafkaConnect", AWSServiceName: kafkaconnect.ServiceName, AWSEndpointsID: kafkaconnect.EndpointsID, AWSServiceID: kafkaconnect.ServiceID, ProviderNameUpper: "KafkaConnect", HCLKeys: []string{"kafkaconnect"}} serviceData[Kendra] = &ServiceDatum{AWSClientName: "Kendra", AWSServiceName: kendra.ServiceName, AWSEndpointsID: kendra.EndpointsID, AWSServiceID: kendra.ServiceID, ProviderNameUpper: "Kendra", HCLKeys: []string{"kendra"}} + serviceData[Keyspaces] = &ServiceDatum{AWSClientName: "Keyspaces", AWSServiceName: keyspaces.ServiceName, AWSEndpointsID: keyspaces.EndpointsID, AWSServiceID: keyspaces.ServiceID, ProviderNameUpper: "Keyspaces", HCLKeys: []string{"keyspaces"}} serviceData[Kinesis] = &ServiceDatum{AWSClientName: "Kinesis", AWSServiceName: kinesis.ServiceName, AWSEndpointsID: kinesis.EndpointsID, AWSServiceID: kinesis.ServiceID, ProviderNameUpper: "Kinesis", HCLKeys: []string{"kinesis"}} serviceData[KinesisAnalytics] = &ServiceDatum{AWSClientName: "KinesisAnalytics", AWSServiceName: kinesisanalytics.ServiceName, AWSEndpointsID: kinesisanalytics.EndpointsID, AWSServiceID: kinesisanalytics.ServiceID, ProviderNameUpper: "KinesisAnalytics", HCLKeys: []string{"kinesisanalytics"}} serviceData[KinesisAnalyticsV2] = &ServiceDatum{AWSClientName: "KinesisAnalyticsV2", AWSServiceName: kinesisanalyticsv2.ServiceName, AWSEndpointsID: kinesisanalyticsv2.EndpointsID, AWSServiceID: kinesisanalyticsv2.ServiceID, ProviderNameUpper: "KinesisAnalyticsV2", HCLKeys: []string{"kinesisanalyticsv2"}} @@ -1040,6 +1043,7 @@ type AWSClient struct { KafkaConn *kafka.Kafka KafkaConnectConn *kafkaconnect.KafkaConnect KendraConn *kendra.Kendra + KeyspacesConn *keyspaces.Keyspaces KinesisAnalyticsConn *kinesisanalytics.KinesisAnalytics KinesisAnalyticsV2Conn *kinesisanalyticsv2.KinesisAnalyticsV2 KinesisConn *kinesis.Kinesis @@ -1141,6 +1145,7 @@ type AWSClient struct { ServiceDiscoveryConn *servicediscovery.ServiceDiscovery ServiceQuotasConn *servicequotas.ServiceQuotas SESConn *ses.SES + Session *session.Session SESV2Conn *sesv2.SESV2 SFNConn *sfn.SFN ShieldConn *shield.Shield @@ -1438,6 +1443,7 @@ func (c *Config) Client(ctx context.Context) (interface{}, diag.Diagnostics) { KafkaConn: kafka.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Kafka])})), KafkaConnectConn: kafkaconnect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KafkaConnect])})), KendraConn: kendra.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Kendra])})), + KeyspacesConn: keyspaces.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Keyspaces])})), KinesisAnalyticsConn: kinesisanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KinesisAnalytics])})), KinesisAnalyticsV2Conn: kinesisanalyticsv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KinesisAnalyticsV2])})), KinesisConn: kinesis.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Kinesis])})), @@ -1540,6 +1546,7 @@ func (c *Config) Client(ctx context.Context) (interface{}, diag.Diagnostics) { ServiceQuotasConn: servicequotas.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ServiceQuotas])})), SESConn: ses.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SES])})), SESV2Conn: sesv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SESV2])})), + Session: sess, SFNConn: sfn.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SFN])})), SignerConn: signer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Signer])})), SimpleDBConn: simpledb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SimpleDB])})), diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 740144a03e3..fabfe3404f4 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -118,6 +118,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/mwaa" "github.com/hashicorp/terraform-provider-aws/internal/service/neptune" "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/opsworks" "github.com/hashicorp/terraform-provider-aws/internal/service/organizations" "github.com/hashicorp/terraform-provider-aws/internal/service/outposts" @@ -580,6 +581,8 @@ func Provider() *schema.Provider { "aws_ecr_image": ecr.DataSourceImage(), "aws_ecr_repository": ecr.DataSourceRepository(), + "aws_ecrpublic_authorization_token": ecrpublic.DataSourceAuthorizationToken(), + "aws_ecs_cluster": ecs.DataSourceCluster(), "aws_ecs_container_definition": ecs.DataSourceContainerDefinition(), "aws_ecs_service": ecs.DataSourceService(), @@ -714,6 +717,17 @@ func Provider() *schema.Provider { "aws_neptune_engine_version": neptune.DataSourceEngineVersion(), "aws_neptune_orderable_db_instance": neptune.DataSourceOrderableDBInstance(), + "aws_networkmanager_connection": networkmanager.DataSourceConnection(), + "aws_networkmanager_connections": networkmanager.DataSourceConnections(), + "aws_networkmanager_device": networkmanager.DataSourceDevice(), + "aws_networkmanager_devices": networkmanager.DataSourceDevices(), + "aws_networkmanager_global_network": networkmanager.DataSourceGlobalNetwork(), + "aws_networkmanager_global_networks": networkmanager.DataSourceGlobalNetworks(), + "aws_networkmanager_link": networkmanager.DataSourceLink(), + "aws_networkmanager_links": networkmanager.DataSourceLinks(), + "aws_networkmanager_site": networkmanager.DataSourceSite(), + "aws_networkmanager_sites": networkmanager.DataSourceSites(), + "aws_organizations_delegated_administrators": organizations.DataSourceDelegatedAdministrators(), "aws_organizations_delegated_services": organizations.DataSourceDelegatedServices(), "aws_organizations_organization": organizations.DataSourceOrganization(), @@ -1390,6 +1404,7 @@ func Provider() *schema.Provider { "aws_gamelift_alias": gamelift.ResourceAlias(), "aws_gamelift_build": gamelift.ResourceBuild(), "aws_gamelift_fleet": gamelift.ResourceFleet(), + "aws_gamelift_game_server_group": gamelift.ResourceGameServerGroup(), "aws_gamelift_game_session_queue": gamelift.ResourceGameSessionQueue(), "aws_gamelift_script": gamelift.ResourceScript(), @@ -1582,6 +1597,16 @@ func Provider() *schema.Provider { "aws_networkfirewall_resource_policy": networkfirewall.ResourceResourcePolicy(), "aws_networkfirewall_rule_group": networkfirewall.ResourceRuleGroup(), + "aws_networkmanager_connection": networkmanager.ResourceConnection(), + "aws_networkmanager_customer_gateway_association": networkmanager.ResourceCustomerGatewayAssociation(), + "aws_networkmanager_device": networkmanager.ResourceDevice(), + "aws_networkmanager_global_network": networkmanager.ResourceGlobalNetwork(), + "aws_networkmanager_link": networkmanager.ResourceLink(), + "aws_networkmanager_link_association": networkmanager.ResourceLinkAssociation(), + "aws_networkmanager_site": networkmanager.ResourceSite(), + "aws_networkmanager_transit_gateway_connect_peer_association": networkmanager.ResourceTransitGatewayConnectPeerAssociation(), + "aws_networkmanager_transit_gateway_registration": networkmanager.ResourceTransitGatewayRegistration(), + "aws_opsworks_application": opsworks.ResourceApplication(), "aws_opsworks_custom_layer": opsworks.ResourceCustomLayer(), "aws_opsworks_ecs_cluster_layer": opsworks.ResourceECSClusterLayer(), diff --git a/internal/service/amplify/app.go b/internal/service/amplify/app.go index f869b302e63..ea6be42f0f9 100644 --- a/internal/service/amplify/app.go +++ b/internal/service/amplify/app.go @@ -306,7 +306,6 @@ func ResourceApp() *schema.Resource { "repository": { Type: schema.TypeString, Optional: true, - ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 1000), }, diff --git a/internal/service/amplify/app_test.go b/internal/service/amplify/app_test.go index ced02e1051f..8777fe97d85 100644 --- a/internal/service/amplify/app_test.go +++ b/internal/service/amplify/app_test.go @@ -23,7 +23,7 @@ func testAccApp_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -70,7 +70,7 @@ func testAccApp_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -93,7 +93,7 @@ func testAccApp_Tags(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -140,7 +140,7 @@ func testAccApp_AutoBranchCreationConfig(t *testing.T) { credentials := base64.StdEncoding.EncodeToString([]byte("username1:password1")) - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -236,7 +236,7 @@ func testAccApp_BasicAuthCredentials(t *testing.T) { credentials1 := base64.StdEncoding.EncodeToString([]byte("username1:password1")) credentials2 := base64.StdEncoding.EncodeToString([]byte("username2:password2")) - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -281,7 +281,7 @@ func testAccApp_BuildSpec(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -323,7 +323,7 @@ func testAccApp_CustomRules(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -373,7 +373,7 @@ func testAccApp_Description(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -416,7 +416,7 @@ func testAccApp_EnvironmentVariables(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -462,7 +462,7 @@ func testAccApp_IAMServiceRole(t *testing.T) { iamRole1ResourceName := "aws_iam_role.test1" iamRole2ResourceName := "aws_iam_role.test2" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -505,7 +505,7 @@ func testAccApp_Name(t *testing.T) { rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -551,7 +551,7 @@ func testAccApp_Repository(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_app.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, diff --git a/internal/service/amplify/backend_environment_test.go b/internal/service/amplify/backend_environment_test.go index ba8549348e2..2c1d0caac38 100644 --- a/internal/service/amplify/backend_environment_test.go +++ b/internal/service/amplify/backend_environment_test.go @@ -22,7 +22,7 @@ func testAccBackendEnvironment_basic(t *testing.T) { environmentName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlpha) - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -54,7 +54,7 @@ func testAccBackendEnvironment_disappears(t *testing.T) { environmentName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlpha) - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -79,7 +79,7 @@ func testAccBackendEnvironment_DeploymentArtifacts_StackName(t *testing.T) { environmentName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlpha) - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, diff --git a/internal/service/amplify/branch_test.go b/internal/service/amplify/branch_test.go index b87afc9ce73..fcf1486877d 100644 --- a/internal/service/amplify/branch_test.go +++ b/internal/service/amplify/branch_test.go @@ -21,7 +21,7 @@ func testAccBranch_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_branch.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -68,7 +68,7 @@ func testAccBranch_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_branch.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -91,7 +91,7 @@ func testAccBranch_Tags(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_branch.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -139,7 +139,7 @@ func testAccBranch_BasicAuthCredentials(t *testing.T) { credentials1 := base64.StdEncoding.EncodeToString([]byte("username1:password1")) credentials2 := base64.StdEncoding.EncodeToString([]byte("username2:password2")) - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -184,7 +184,7 @@ func testAccBranch_EnvironmentVariables(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_branch.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -231,7 +231,7 @@ func testAccBranch_OptionalArguments(t *testing.T) { backendEnvironment1ResourceName := "aws_amplify_backend_environment.test1" backendEnvironment2ResourceName := "aws_amplify_backend_environment.test2" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, diff --git a/internal/service/amplify/domain_association_test.go b/internal/service/amplify/domain_association_test.go index 7cdd1bc327d..daa4f74f8ef 100644 --- a/internal/service/amplify/domain_association_test.go +++ b/internal/service/amplify/domain_association_test.go @@ -27,7 +27,7 @@ func testAccDomainAssociation_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_domain_association.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -68,7 +68,7 @@ func testAccDomainAssociation_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_domain_association.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -97,7 +97,7 @@ func testAccDomainAssociation_update(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_domain_association.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, diff --git a/internal/service/amplify/webhook_test.go b/internal/service/amplify/webhook_test.go index 6e157fd5e8e..ee721049f29 100644 --- a/internal/service/amplify/webhook_test.go +++ b/internal/service/amplify/webhook_test.go @@ -20,7 +20,7 @@ func testAccWebhook_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_webhook.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -50,7 +50,7 @@ func testAccWebhook_disappears(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_webhook.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, @@ -73,7 +73,7 @@ func testAccWebhook_update(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_amplify_webhook.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, amplify.EndpointsID), Providers: acctest.Providers, diff --git a/internal/service/ec2/transit_gateway_connect_peer.go b/internal/service/ec2/transit_gateway_connect_peer.go index e911f553f44..f99d6fba347 100644 --- a/internal/service/ec2/transit_gateway_connect_peer.go +++ b/internal/service/ec2/transit_gateway_connect_peer.go @@ -2,12 +2,14 @@ package ec2 import ( "context" + "fmt" "log" "regexp" "strconv" "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -39,6 +41,10 @@ func ResourceTransitGatewayConnectPeer() *schema.Resource { CustomizeDiff: verify.SetTagsDiff, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "bgp_asn": { Type: schema.TypeString, Optional: true, @@ -152,6 +158,14 @@ func resourceTransitGatewayConnectPeerRead(ctx context.Context, d *schema.Resour return diag.Errorf("error reading EC2 Transit Gateway Connect Peer (%s): %s", d.Id(), err) } + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("transit-gateway-connect-peer/%s", d.Id()), + }.String() + d.Set("arn", arn) d.Set("bgp_asn", strconv.FormatInt(aws.Int64Value(transitGatewayConnectPeer.ConnectPeerConfiguration.BgpConfigurations[0].PeerAsn), 10)) d.Set("inside_cidr_blocks", aws.StringValueSlice(transitGatewayConnectPeer.ConnectPeerConfiguration.InsideCidrBlocks)) d.Set("peer_address", transitGatewayConnectPeer.ConnectPeerConfiguration.PeerAddress) diff --git a/internal/service/ec2/transit_gateway_connect_peer_data_source.go b/internal/service/ec2/transit_gateway_connect_peer_data_source.go index 89ae6a8a75f..ce937fddc3f 100644 --- a/internal/service/ec2/transit_gateway_connect_peer_data_source.go +++ b/internal/service/ec2/transit_gateway_connect_peer_data_source.go @@ -2,9 +2,11 @@ package ec2 import ( "context" + "fmt" "strconv" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -71,6 +73,15 @@ func dataSourceTransitGatewayConnectPeerRead(ctx context.Context, d *schema.Reso } d.SetId(aws.StringValue(transitGatewayConnectPeer.TransitGatewayConnectPeerId)) + + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: ec2.ServiceName, + Region: meta.(*conns.AWSClient).Region, + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("transit-gateway-connect-peer/%s", d.Id()), + }.String() + d.Set("arn", arn) d.Set("bgp_asn", strconv.FormatInt(aws.Int64Value(transitGatewayConnectPeer.ConnectPeerConfiguration.BgpConfigurations[0].PeerAsn), 10)) d.Set("inside_cidr_blocks", aws.StringValueSlice(transitGatewayConnectPeer.ConnectPeerConfiguration.InsideCidrBlocks)) d.Set("peer_address", transitGatewayConnectPeer.ConnectPeerConfiguration.PeerAddress) diff --git a/internal/service/ec2/transit_gateway_connect_peer_data_source_test.go b/internal/service/ec2/transit_gateway_connect_peer_data_source_test.go index bed5fdbe73f..1c6a802101e 100644 --- a/internal/service/ec2/transit_gateway_connect_peer_data_source_test.go +++ b/internal/service/ec2/transit_gateway_connect_peer_data_source_test.go @@ -24,6 +24,7 @@ func testAccTransitGatewayConnectPeerDataSource_Filter(t *testing.T) { { Config: testAccTransitGatewayConnectPeerFilterDataSourceConfig(rName), Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(dataSourceName, "bgp_asn", resourceName, "bgp_asn"), resource.TestCheckResourceAttrPair(dataSourceName, "inside_cidr_blocks.#", resourceName, "inside_cidr_blocks.#"), resource.TestCheckResourceAttrPair(dataSourceName, "peer_address", resourceName, "peer_address"), @@ -51,6 +52,7 @@ func testAccTransitGatewayConnectPeerDataSource_ID(t *testing.T) { { Config: testAccTransitGatewayConnectPeerIDDataSourceConfig(rName), Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), resource.TestCheckResourceAttrPair(dataSourceName, "bgp_asn", resourceName, "bgp_asn"), resource.TestCheckResourceAttrPair(dataSourceName, "inside_cidr_blocks.#", resourceName, "inside_cidr_blocks.#"), resource.TestCheckResourceAttrPair(dataSourceName, "peer_address", resourceName, "peer_address"), diff --git a/internal/service/ecrpublic/authorization_token_data_source.go b/internal/service/ecrpublic/authorization_token_data_source.go new file mode 100644 index 00000000000..39399de74f7 --- /dev/null +++ b/internal/service/ecrpublic/authorization_token_data_source.go @@ -0,0 +1,74 @@ +package ecrpublic + +import ( + "encoding/base64" + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecrpublic" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" +) + +func DataSourceAuthorizationToken() *schema.Resource { + return &schema.Resource{ + Read: dataSourceAuthorizationTokenRead, + + Schema: map[string]*schema.Schema{ + "authorization_token": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "expires_at": { + Type: schema.TypeString, + Computed: true, + }, + "password": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "user_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceAuthorizationTokenRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).ECRPublicConn + params := &ecrpublic.GetAuthorizationTokenInput{} + + out, err := conn.GetAuthorizationToken(params) + + if err != nil { + return fmt.Errorf("error getting Public ECR authorization token: %w", err) + } + + authorizationData := out.AuthorizationData + authorizationToken := aws.StringValue(authorizationData.AuthorizationToken) + expiresAt := aws.TimeValue(authorizationData.ExpiresAt).Format(time.RFC3339) + authBytes, err := base64.URLEncoding.DecodeString(authorizationToken) + if err != nil { + return fmt.Errorf("error decoding Public ECR authorization token: %w", err) + } + + basicAuthorization := strings.Split(string(authBytes), ":") + if len(basicAuthorization) != 2 { + return fmt.Errorf("unknown Public ECR authorization token format") + } + + userName := basicAuthorization[0] + password := basicAuthorization[1] + d.SetId(meta.(*conns.AWSClient).Region) + d.Set("authorization_token", authorizationToken) + d.Set("expires_at", expiresAt) + d.Set("user_name", userName) + d.Set("password", password) + + return nil +} diff --git a/internal/service/ecrpublic/authorization_token_data_source_test.go b/internal/service/ecrpublic/authorization_token_data_source_test.go new file mode 100644 index 00000000000..41cad8fd6b2 --- /dev/null +++ b/internal/service/ecrpublic/authorization_token_data_source_test.go @@ -0,0 +1,36 @@ +package ecrpublic_test + +import ( + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccECRPublicAuthorizationTokenDataSource_basic(t *testing.T) { + dataSourceName := "data.aws_ecrpublic_authorization_token.repo" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ecr.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccCheckAuthorizationTokenBasicDataSourceConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dataSourceName, "authorization_token"), + resource.TestCheckResourceAttrSet(dataSourceName, "expires_at"), + resource.TestCheckResourceAttrSet(dataSourceName, "user_name"), + resource.TestMatchResourceAttr(dataSourceName, "user_name", regexp.MustCompile(`AWS`)), + resource.TestCheckResourceAttrSet(dataSourceName, "password"), + ), + }, + }, + }) +} + +var testAccCheckAuthorizationTokenBasicDataSourceConfig = ` +data "aws_ecrpublic_authorization_token" "repo" {} +` diff --git a/internal/service/ecs/service.go b/internal/service/ecs/service.go index 674fd98771f..a1abb886e6c 100644 --- a/internal/service/ecs/service.go +++ b/internal/service/ecs/service.go @@ -107,15 +107,11 @@ func ResourceService() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": { - Type: schema.TypeString, - ForceNew: true, - Optional: true, - Default: ecs.DeploymentControllerTypeEcs, - ValidateFunc: validation.StringInSlice([]string{ - ecs.DeploymentControllerTypeCodeDeploy, - ecs.DeploymentControllerTypeEcs, - ecs.DeploymentControllerTypeExternal, - }, false), + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Default: ecs.DeploymentControllerTypeEcs, + ValidateFunc: validation.StringInSlice(ecs.DeploymentControllerType_Values(), false), }, }, }, @@ -153,7 +149,6 @@ func ResourceService() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: false, - ForceNew: true, }, "enable_execute_command": { Type: schema.TypeBool, @@ -185,7 +180,6 @@ func ResourceService() *schema.Resource { "load_balancer": { Type: schema.TypeSet, Optional: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "elb_name": { @@ -255,13 +249,9 @@ func ResourceService() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.PlacementStrategyTypeBinpack, - ecs.PlacementStrategyTypeRandom, - ecs.PlacementStrategyTypeSpread, - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(ecs.PlacementStrategyType_Values(), false), }, "field": { Type: schema.TypeString, @@ -291,12 +281,9 @@ func ResourceService() *schema.Resource { Optional: true, }, "type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - ecs.PlacementConstraintTypeDistinctInstance, - ecs.PlacementConstraintTypeMemberOf, - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(ecs.PlacementConstraintType_Values(), false), }, }, }, @@ -309,33 +296,24 @@ func ResourceService() *schema.Resource { "propagate_tags": { Type: schema.TypeString, Optional: true, - ForceNew: true, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { if old == "NONE" && new == "" { return true } return false }, - ValidateFunc: validation.StringInSlice([]string{ - ecs.PropagateTagsService, - ecs.PropagateTagsTaskDefinition, - "", - }, false), + ValidateFunc: validation.StringInSlice(ecs.PropagateTags_Values(), false), }, "scheduling_strategy": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: ecs.SchedulingStrategyReplica, - ValidateFunc: validation.StringInSlice([]string{ - ecs.SchedulingStrategyDaemon, - ecs.SchedulingStrategyReplica, - }, false), + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: ecs.SchedulingStrategyReplica, + ValidateFunc: validation.StringInSlice(ecs.SchedulingStrategy_Values(), false), }, "service_registries": { Type: schema.TypeList, Optional: true, - ForceNew: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -1000,122 +978,124 @@ func flattenServiceRegistries(srs []*ecs.ServiceRegistry) []map[string]interface func resourceServiceUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).ECSConn - updateService := false - input := ecs.UpdateServiceInput{ - Cluster: aws.String(d.Get("cluster").(string)), - ForceNewDeployment: aws.Bool(d.Get("force_new_deployment").(bool)), - Service: aws.String(d.Id()), - } + if d.HasChangesExcept("tags", "tags_all") { + input := &ecs.UpdateServiceInput{ + Cluster: aws.String(d.Get("cluster").(string)), + ForceNewDeployment: aws.Bool(d.Get("force_new_deployment").(bool)), + Service: aws.String(d.Id()), + } - schedulingStrategy := d.Get("scheduling_strategy").(string) + schedulingStrategy := d.Get("scheduling_strategy").(string) - if schedulingStrategy == ecs.SchedulingStrategyDaemon { - if d.HasChange("deployment_minimum_healthy_percent") { - updateService = true - input.DeploymentConfiguration = &ecs.DeploymentConfiguration{ - MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))), + if schedulingStrategy == ecs.SchedulingStrategyDaemon { + if d.HasChange("deployment_minimum_healthy_percent") { + input.DeploymentConfiguration = &ecs.DeploymentConfiguration{ + MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))), + } + } + } else if schedulingStrategy == ecs.SchedulingStrategyReplica { + if d.HasChange("desired_count") { + input.DesiredCount = aws.Int64(int64(d.Get("desired_count").(int))) } - } - } else if schedulingStrategy == ecs.SchedulingStrategyReplica { - if d.HasChange("desired_count") { - updateService = true - input.DesiredCount = aws.Int64(int64(d.Get("desired_count").(int))) - } - if d.HasChanges("deployment_maximum_percent", "deployment_minimum_healthy_percent") { - updateService = true - input.DeploymentConfiguration = &ecs.DeploymentConfiguration{ - MaximumPercent: aws.Int64(int64(d.Get("deployment_maximum_percent").(int))), - MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))), + if d.HasChanges("deployment_maximum_percent", "deployment_minimum_healthy_percent") { + input.DeploymentConfiguration = &ecs.DeploymentConfiguration{ + MaximumPercent: aws.Int64(int64(d.Get("deployment_maximum_percent").(int))), + MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))), + } } } - } - if d.HasChange("deployment_circuit_breaker") { - updateService = true + if d.HasChange("deployment_circuit_breaker") { + if input.DeploymentConfiguration == nil { + input.DeploymentConfiguration = &ecs.DeploymentConfiguration{} + } + + // To remove an existing deployment circuit breaker, specify an empty object. + input.DeploymentConfiguration.DeploymentCircuitBreaker = &ecs.DeploymentCircuitBreaker{} - if input.DeploymentConfiguration == nil { - input.DeploymentConfiguration = &ecs.DeploymentConfiguration{} + if v, ok := d.GetOk("deployment_circuit_breaker"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.DeploymentConfiguration.DeploymentCircuitBreaker = expandDeploymentCircuitBreaker(v.([]interface{})[0].(map[string]interface{})) + } } - // To remove an existing deployment circuit breaker, specify an empty object. - input.DeploymentConfiguration.DeploymentCircuitBreaker = &ecs.DeploymentCircuitBreaker{} + if d.HasChange("ordered_placement_strategy") { + // Reference: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_UpdateService.html#ECS-UpdateService-request-placementStrategy + // To remove an existing placement strategy, specify an empty object. + input.PlacementStrategy = []*ecs.PlacementStrategy{} + + if v, ok := d.GetOk("ordered_placement_strategy"); ok && len(v.([]interface{})) > 0 { + ps, err := expandPlacementStrategy(v.([]interface{})) - if v, ok := d.GetOk("deployment_circuit_breaker"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - input.DeploymentConfiguration.DeploymentCircuitBreaker = expandDeploymentCircuitBreaker(v.([]interface{})[0].(map[string]interface{})) + if err != nil { + return err + } + + input.PlacementStrategy = ps + } } - } - if d.HasChange("ordered_placement_strategy") { - updateService = true - // Reference: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_UpdateService.html#ECS-UpdateService-request-placementStrategy - // To remove an existing placement strategy, specify an empty object. - input.PlacementStrategy = []*ecs.PlacementStrategy{} + if d.HasChange("placement_constraints") { + // Reference: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_UpdateService.html#ECS-UpdateService-request-placementConstraints + // To remove all existing placement constraints, specify an empty array. + input.PlacementConstraints = []*ecs.PlacementConstraint{} - if v, ok := d.GetOk("ordered_placement_strategy"); ok && len(v.([]interface{})) > 0 { - ps, err := expandPlacementStrategy(v.([]interface{})) + if v, ok := d.Get("placement_constraints").(*schema.Set); ok && v.Len() > 0 { + pc, err := expandPlacementConstraints(v.List()) - if err != nil { - return err - } + if err != nil { + return err + } - input.PlacementStrategy = ps + input.PlacementConstraints = pc + } } - } - if d.HasChange("placement_constraints") { - updateService = true - // Reference: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_UpdateService.html#ECS-UpdateService-request-placementConstraints - // To remove all existing placement constraints, specify an empty array. - input.PlacementConstraints = []*ecs.PlacementConstraint{} + if d.HasChange("platform_version") { + input.PlatformVersion = aws.String(d.Get("platform_version").(string)) + } - if v, ok := d.Get("placement_constraints").(*schema.Set); ok && v.Len() > 0 { - pc, err := expandPlacementConstraints(v.List()) + if d.HasChange("health_check_grace_period_seconds") { + input.HealthCheckGracePeriodSeconds = aws.Int64(int64(d.Get("health_check_grace_period_seconds").(int))) + } - if err != nil { - return err - } + if d.HasChange("task_definition") { + input.TaskDefinition = aws.String(d.Get("task_definition").(string)) + } - input.PlacementConstraints = pc + if d.HasChange("network_configuration") { + input.NetworkConfiguration = expandNetworkConfiguration(d.Get("network_configuration").([]interface{})) } - } - if d.HasChange("platform_version") { - updateService = true - input.PlatformVersion = aws.String(d.Get("platform_version").(string)) - } + if d.HasChange("capacity_provider_strategy") { + input.CapacityProviderStrategy = expandCapacityProviderStrategy(d.Get("capacity_provider_strategy").(*schema.Set)) + } - if d.HasChange("health_check_grace_period_seconds") { - updateService = true - input.HealthCheckGracePeriodSeconds = aws.Int64(int64(d.Get("health_check_grace_period_seconds").(int))) - } + if d.HasChange("enable_execute_command") { + input.EnableExecuteCommand = aws.Bool(d.Get("enable_execute_command").(bool)) + } - if d.HasChange("task_definition") { - updateService = true - input.TaskDefinition = aws.String(d.Get("task_definition").(string)) - } + if d.HasChange("enable_ecs_managed_tags") { + input.EnableECSManagedTags = aws.Bool(d.Get("enable_ecs_managed_tags").(bool)) + } - if d.HasChange("network_configuration") { - updateService = true - input.NetworkConfiguration = expandNetworkConfiguration(d.Get("network_configuration").([]interface{})) - } + if d.HasChange("load_balancer") { + input.LoadBalancers = expandLoadBalancers(d.Get("load_balancer").([]interface{})) + } - if d.HasChange("capacity_provider_strategy") { - updateService = true - input.CapacityProviderStrategy = expandCapacityProviderStrategy(d.Get("capacity_provider_strategy").(*schema.Set)) - } + if d.HasChange("propagate_tags") { + input.PropagateTags = aws.String(d.Get("propagate_tags").(string)) + } - if d.HasChange("enable_execute_command") { - updateService = true - input.EnableExecuteCommand = aws.Bool(d.Get("enable_execute_command").(bool)) - } + if d.HasChange("service_registries") { + input.ServiceRegistries = expandServiceRegistries(d.Get("service_registries").([]interface{})) + } - if updateService { log.Printf("[DEBUG] Updating ECS Service (%s): %s", d.Id(), input) // Retry due to IAM eventual consistency err := resource.Retry(tfiam.PropagationTimeout+serviceUpdateTimeout, func() *resource.RetryError { - _, err := conn.UpdateService(&input) + _, err := conn.UpdateService(input) if err != nil { if tfawserr.ErrMessageContains(err, ecs.ErrCodeInvalidParameterException, "verify that the ECS service role being passed has the proper permissions") { @@ -1132,22 +1112,22 @@ func resourceServiceUpdate(d *schema.ResourceData, meta interface{}) error { }) if tfresource.TimedOut(err) { - _, err = conn.UpdateService(&input) + _, err = conn.UpdateService(input) } if err != nil { return fmt.Errorf("error updating ECS Service (%s): %w", d.Id(), err) } - } - if d.Get("wait_for_steady_state").(bool) { - cluster := "" - if v, ok := d.GetOk("cluster"); ok { - cluster = v.(string) - } + if d.Get("wait_for_steady_state").(bool) { + cluster := "" + if v, ok := d.GetOk("cluster"); ok { + cluster = v.(string) + } - if err := waitServiceStable(conn, d.Id(), cluster); err != nil { - return fmt.Errorf("error waiting for ECS service (%s) to become ready: %w", d.Id(), err) + if err := waitServiceStable(conn, d.Id(), cluster); err != nil { + return fmt.Errorf("error waiting for ECS service (%s) to become ready: %w", d.Id(), err) + } } } diff --git a/internal/service/ecs/service_test.go b/internal/service/ecs/service_test.go index 76ebdadc59d..8e7846a073a 100644 --- a/internal/service/ecs/service_test.go +++ b/internal/service/ecs/service_test.go @@ -1233,10 +1233,10 @@ func TestAccECSService_Tags_propagate(t *testing.T) { ), }, { - Config: testAccServiceManagedTagsConfig(rName), + Config: testAccServicePropagateTagsConfig(rName, "NONE"), Check: resource.ComposeTestCheckFunc( testAccCheckServiceExists(resourceName, &third), - resource.TestCheckResourceAttr(resourceName, "propagate_tags", "NONE"), + resource.TestCheckResourceAttr(resourceName, "propagate_tags", ecs.PropagateTagsNone), ), }, }, diff --git a/internal/service/gamelift/find.go b/internal/service/gamelift/find.go index 7adf6b37fc2..05acc1d2524 100644 --- a/internal/service/gamelift/find.go +++ b/internal/service/gamelift/find.go @@ -68,6 +68,31 @@ func FindFleetByID(conn *gamelift.GameLift, id string) (*gamelift.FleetAttribute return fleet, nil } +func FindGameServerGroupByName(conn *gamelift.GameLift, name string) (*gamelift.GameServerGroup, error) { + input := &gamelift.DescribeGameServerGroupInput{ + GameServerGroupName: aws.String(name), + } + + output, err := conn.DescribeGameServerGroup(input) + + if tfawserr.ErrCodeEquals(err, gamelift.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.GameServerGroup == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.GameServerGroup, nil +} + func FindScriptByID(conn *gamelift.GameLift, id string) (*gamelift.Script, error) { input := &gamelift.DescribeScriptInput{ ScriptId: aws.String(id), diff --git a/internal/service/gamelift/game_server_group.go b/internal/service/gamelift/game_server_group.go new file mode 100644 index 00000000000..9a44a50e59f --- /dev/null +++ b/internal/service/gamelift/game_server_group.go @@ -0,0 +1,588 @@ +package gamelift + +import ( // nosemgrep: aws-sdk-go-multiple-service-imports + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/gamelift" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +const ( + gameServerGroupCreatedDefaultTimeout = 10 * time.Minute + gameServerGroupDeletedDefaultTimeout = 30 * time.Minute +) + +func ResourceGameServerGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceGameServerGroupCreate, + Read: resourceGameServerGroupRead, + Update: resourceGameServerGroupUpdate, + Delete: resourceGameServerGroupDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(gameServerGroupCreatedDefaultTimeout), + Delete: schema.DefaultTimeout(gameServerGroupDeletedDefaultTimeout), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "auto_scaling_group_arn": { + Type: schema.TypeString, + Computed: true, + }, + "auto_scaling_policy": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "estimated_instance_warmup": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "target_tracking_configuration": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "target_value": { + Type: schema.TypeFloat, + Required: true, + ForceNew: true, + ValidateFunc: validation.FloatAtLeast(0), + }, + }, + }, + }, + }, + }, + }, + "balancing_strategy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(gamelift.BalancingStrategy_Values(), false), + }, + "game_server_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "game_server_protection_policy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(gamelift.GameServerProtectionPolicy_Values(), false), + }, + "instance_definition": { + Type: schema.TypeSet, + Required: true, + MinItems: 2, + MaxItems: 20, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "instance_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(gamelift.GameServerGroupInstanceType_Values(), false), + }, + "weighted_capacity": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 3), + }, + }, + }, + }, + "launch_template": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"launch_template.0.name"}, + ValidateFunc: verify.ValidLaunchTemplateID, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"launch_template.0.id"}, + ValidateFunc: verify.ValidLaunchTemplateName, + }, + "version": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + }, + }, + }, + "max_size": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "min_size": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(0), + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "vpc_subnets": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + MaxItems: 20, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringLenBetween(15, 24), + }, + }, + }, + } +} + +func resourceGameServerGroupCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).GameLiftConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + input := &gamelift.CreateGameServerGroupInput{ + GameServerGroupName: aws.String(d.Get("game_server_group_name").(string)), + InstanceDefinitions: expandGameliftInstanceDefinitions(d.Get("instance_definition").(*schema.Set).List()), + LaunchTemplate: expandGameliftLaunchTemplateSpecification(d.Get("launch_template").([]interface{})[0].(map[string]interface{})), + MaxSize: aws.Int64(int64(d.Get("max_size").(int))), + MinSize: aws.Int64(int64(d.Get("min_size").(int))), + RoleArn: aws.String(d.Get("role_arn").(string)), + Tags: Tags(tags.IgnoreAWS()), + } + + if v, ok := d.GetOk("auto_scaling_policy"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.AutoScalingPolicy = expandGameliftGameServerGroupAutoScalingPolicy(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("balancing_strategy"); ok { + input.BalancingStrategy = aws.String(v.(string)) + } + + if v, ok := d.GetOk("game_server_protection_policy"); ok { + input.GameServerProtectionPolicy = aws.String(v.(string)) + } + + if v, ok := d.GetOk("vpc_subnets"); ok && v.(*schema.Set).Len() > 0 { + input.VpcSubnets = flex.ExpandStringSet(v.(*schema.Set)) + } + + log.Printf("[INFO] Creating Gamelift Game Server Group: %s", input) + var out *gamelift.CreateGameServerGroupOutput + err := resource.Retry(tfiam.PropagationTimeout, func() *resource.RetryError { + var err error + out, err = conn.CreateGameServerGroup(input) + + if tfawserr.ErrMessageContains(err, gamelift.ErrCodeInvalidRequestException, "GameLift is not authorized to perform") { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + + if tfresource.TimedOut(err) { + out, err = conn.CreateGameServerGroup(input) + } + + if err != nil { + return fmt.Errorf("error creating GameLift Game Server Group (%s): %w", d.Get("name").(string), err) + } + + d.SetId(aws.StringValue(out.GameServerGroup.GameServerGroupName)) + + if output, err := waitGameServerGroupActive(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return fmt.Errorf("error waiting for GameLift Game Server Group (%s) to become active (%s): %w", d.Id(), *output.StatusReason, err) + } + + return resourceGameServerGroupRead(d, meta) +} + +func resourceGameServerGroupRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).GameLiftConn + autoscalingConn := meta.(*conns.AWSClient).AutoScalingConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + gameServerGroupName := d.Id() + + log.Printf("[INFO] Describing Gamelift Game Server Group: %s", gameServerGroupName) + gameServerGroup, err := FindGameServerGroupByName(conn, gameServerGroupName) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] GameLift Game Server Group (%s) not found, removing from state", gameServerGroupName) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading GameLift Game Server Group (%s): %w", gameServerGroupName, err) + } + + autoScalingGroupName := strings.Split(aws.StringValue(gameServerGroup.AutoScalingGroupArn), "/")[1] + autoScalingGroupOutput, err := autoscalingConn.DescribeAutoScalingGroups(&autoscaling.DescribeAutoScalingGroupsInput{ + AutoScalingGroupNames: []*string{aws.String(autoScalingGroupName)}, + }) + if err != nil { + return err + } + if autoScalingGroupOutput == nil || len(autoScalingGroupOutput.AutoScalingGroups) == 0 { + return fmt.Errorf("error describing Auto Scaling Group (%s): not found", autoScalingGroupName) + } + autoScalingGroup := autoScalingGroupOutput.AutoScalingGroups[0] + + describePoliciesOutput, err := autoscalingConn.DescribePolicies(&autoscaling.DescribePoliciesInput{ + AutoScalingGroupName: aws.String(autoScalingGroupName), + PolicyNames: []*string{aws.String(gameServerGroupName)}, + }) + + if err != nil { + return fmt.Errorf("error describing Auto Scaling Group Policies (%s): %s", autoScalingGroupName, err) + } + + arn := aws.StringValue(gameServerGroup.GameServerGroupArn) + d.Set("arn", arn) + d.Set("auto_scaling_group_arn", gameServerGroup.AutoScalingGroupArn) + d.Set("balancing_strategy", gameServerGroup.BalancingStrategy) + d.Set("game_server_group_name", gameServerGroupName) + d.Set("game_server_protection_policy", gameServerGroup.GameServerProtectionPolicy) + d.Set("max_size", autoScalingGroup.MaxSize) + d.Set("min_size", autoScalingGroup.MinSize) + d.Set("role_arn", gameServerGroup.RoleArn) + + if len(describePoliciesOutput.ScalingPolicies) == 1 { + if err := d.Set("auto_scaling_policy", []interface{}{flattenGameliftGameServerGroupAutoScalingPolicy(describePoliciesOutput.ScalingPolicies[0])}); err != nil { + return fmt.Errorf("error setting auto_scaling_policy: %w", err) + } + } else { + d.Set("auto_scaling_policy", nil) + } + + if err := d.Set("instance_definition", flattenGameliftInstanceDefinitions(gameServerGroup.InstanceDefinitions)); err != nil { + return fmt.Errorf("error setting instance_definition: %s", err) + } + + if err := d.Set("launch_template", flattenAutoscalingLaunchTemplateSpecification(autoScalingGroup.MixedInstancesPolicy.LaunchTemplate.LaunchTemplateSpecification)); err != nil { + return fmt.Errorf("error setting launch_template: %s", err) + } + + tags, err := ListTags(conn, arn) + + if tfawserr.ErrMessageContains(err, gamelift.ErrCodeInvalidRequestException, fmt.Sprintf("Resource %s is not in a taggable state", d.Id())) { + return nil + } + + if err != nil { + return fmt.Errorf("error listing tags for Game Lift Game Server Group (%s): %s", arn, err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourceGameServerGroupUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).GameLiftConn + + log.Printf("[INFO] Updating Gamelift Game Server Group: %s", d.Id()) + + if d.HasChanges("balancing_strategy", "game_server_protection_policy", "instance_definition", "role_arn") { + input := gamelift.UpdateGameServerGroupInput{ + GameServerGroupName: aws.String(d.Id()), + InstanceDefinitions: expandGameliftInstanceDefinitions(d.Get("instance_definition").(*schema.Set).List()), + RoleArn: aws.String(d.Get("role_arn").(string)), + } + + if v, ok := d.GetOk("balancing_strategy"); ok { + input.BalancingStrategy = aws.String(v.(string)) + } + + if v, ok := d.GetOk("game_server_protection_policy"); ok { + input.GameServerProtectionPolicy = aws.String(v.(string)) + } + + _, err := conn.UpdateGameServerGroup(&input) + if err != nil { + return fmt.Errorf("error updating GameLift Game Server Group (%s): %w", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + arn := d.Get("arn").(string) + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, arn, o, n); err != nil { + return fmt.Errorf("error updating Game Lift Game Server Group (%s) tags: %w", arn, err) + } + } + + return resourceGameServerGroupRead(d, meta) +} + +func resourceGameServerGroupDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).GameLiftConn + + log.Printf("[INFO] Deleting Gamelift Game Server Group: %s", d.Id()) + input := &gamelift.DeleteGameServerGroupInput{ + GameServerGroupName: aws.String(d.Id()), + } + err := resource.Retry(gameServerGroupDeletedDefaultTimeout, func() *resource.RetryError { + _, err := conn.DeleteGameServerGroup(input) + if err != nil { + msg := fmt.Sprintf("Cannot delete game server group %s: %s", d.Id(), err) + if tfawserr.ErrMessageContains(err, gamelift.ErrCodeInvalidRequestException, msg) { + return resource.RetryableError(err) + } + return resource.NonRetryableError(err) + } + return nil + }) + if tfresource.TimedOut(err) { + _, err = conn.DeleteGameServerGroup(input) + } + if err != nil { + if tfawserr.ErrCodeEquals(err, gamelift.ErrCodeNotFoundException) { + return nil + } + return fmt.Errorf("Error deleting Gamelift game server group: %w", err) + } + + if err := waitGameServerGroupTerminated(conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("error waiting for GameLift Game Server Group (%s) to be deleted: %w", d.Id(), err) + } + + return nil +} + +func expandGameliftGameServerGroupAutoScalingPolicy(tfMap map[string]interface{}) *gamelift.GameServerGroupAutoScalingPolicy { + if tfMap == nil { + return nil + } + + apiObject := &gamelift.GameServerGroupAutoScalingPolicy{ + TargetTrackingConfiguration: expandGameliftTargetTrackingConfiguration(tfMap["target_tracking_configuration"].([]interface{})[0].(map[string]interface{})), + } + + if v, ok := tfMap["estimated_instance_warmup"].(int); ok && v != 0 { + apiObject.EstimatedInstanceWarmup = aws.Int64(int64(v)) + } + + return apiObject +} + +func expandGameliftInstanceDefinition(tfMap map[string]interface{}) *gamelift.InstanceDefinition { + if tfMap == nil { + return nil + } + + apiObject := &gamelift.InstanceDefinition{ + InstanceType: aws.String(tfMap["instance_type"].(string)), + } + + if v, ok := tfMap["weighted_capacity"].(string); ok && v != "" { + apiObject.WeightedCapacity = aws.String(v) + } + + return apiObject +} + +func expandGameliftInstanceDefinitions(tfList []interface{}) []*gamelift.InstanceDefinition { + if len(tfList) == 0 { + return nil + } + + var apiObjects []*gamelift.InstanceDefinition + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + + if !ok { + continue + } + + apiObject := expandGameliftInstanceDefinition(tfMap) + + if apiObject == nil { + continue + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandGameliftLaunchTemplateSpecification(tfMap map[string]interface{}) *gamelift.LaunchTemplateSpecification { + if tfMap == nil { + return nil + } + + apiObject := &gamelift.LaunchTemplateSpecification{} + + if v, ok := tfMap["id"].(string); ok && v != "" { + apiObject.LaunchTemplateId = aws.String(v) + } + + if v, ok := tfMap["name"].(string); ok && v != "" { + apiObject.LaunchTemplateName = aws.String(v) + } + + if v, ok := tfMap["version"].(string); ok && v != "" { + apiObject.Version = aws.String(v) + } + + return apiObject +} + +func expandGameliftTargetTrackingConfiguration(tfMap map[string]interface{}) *gamelift.TargetTrackingConfiguration { + if tfMap == nil { + return nil + } + + apiObject := &gamelift.TargetTrackingConfiguration{ + TargetValue: aws.Float64(tfMap["target_value"].(float64)), + } + + return apiObject +} + +func flattenGameliftGameServerGroupAutoScalingPolicy(apiObject *autoscaling.ScalingPolicy) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "target_tracking_configuration": []interface{}{flattenGameliftTargetTrackingConfiguration(apiObject.TargetTrackingConfiguration)}, + } + + if v := apiObject.EstimatedInstanceWarmup; v != nil { + tfMap["estimated_instance_warmup"] = aws.Int64Value(v) + } + + return tfMap +} + +func flattenGameliftInstanceDefinition(apiObject *gamelift.InstanceDefinition) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "instance_type": aws.StringValue(apiObject.InstanceType), + } + + if v := apiObject.WeightedCapacity; v != nil { + tfMap["weighted_capacity"] = aws.StringValue(v) + } + + return tfMap +} + +func flattenAutoscalingLaunchTemplateSpecification(apiObject *autoscaling.LaunchTemplateSpecification) []map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "id": aws.StringValue(apiObject.LaunchTemplateId), + "name": aws.StringValue(apiObject.LaunchTemplateName), + } + + // version is returned only if it was previously set + if apiObject.Version != nil { + tfMap["version"] = aws.StringValue(apiObject.Version) + } else { + tfMap["version"] = nil + } + + return []map[string]interface{}{tfMap} +} + +func flattenGameliftInstanceDefinitions(apiObjects []*gamelift.InstanceDefinition) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + tfList = append(tfList, flattenGameliftInstanceDefinition(apiObject)) + } + + return tfList +} + +func flattenGameliftTargetTrackingConfiguration(apiObject *autoscaling.TargetTrackingConfiguration) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "target_value": aws.Float64Value(apiObject.TargetValue), + } + + return tfMap +} diff --git a/internal/service/gamelift/game_server_group_test.go b/internal/service/gamelift/game_server_group_test.go new file mode 100644 index 00000000000..216922683bc --- /dev/null +++ b/internal/service/gamelift/game_server_group_test.go @@ -0,0 +1,1140 @@ +package gamelift_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/gamelift" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" +) + +func TestAccGameliftGameServerGroup_basic(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "gamelift", regexp.MustCompile(`gameservergroup/.+`)), + acctest.MatchResourceAttrRegionalARN(resourceName, "auto_scaling_group_arn", "autoscaling", regexp.MustCompile(`autoScalingGroup:.+`)), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_policy.#", "0"), + resource.TestCheckResourceAttr(resourceName, "balancing_strategy", gamelift.BalancingStrategySpotPreferred), + resource.TestCheckResourceAttr(resourceName, "game_server_protection_policy", gamelift.GameServerProtectionPolicyNoProtection), + resource.TestCheckResourceAttr(resourceName, "launch_template.0.version", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_AutoScalingPolicy(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigAutoScalingPolicy(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_policy.0.estimated_instance_warmup", "60"), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_policy.0.target_tracking_configuration.0.target_value", "77.7"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_AutoScalingPolicy_EstimatedInstanceWarmup(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigAutoScalingPolicyEstimatedInstanceWarmup(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_policy.0.estimated_instance_warmup", "66"), + resource.TestCheckResourceAttr(resourceName, "auto_scaling_policy.0.target_tracking_configuration.0.target_value", "77.7"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_BalancingStrategy(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigBalancingStrategy(rName, gamelift.BalancingStrategySpotOnly), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "balancing_strategy", gamelift.BalancingStrategySpotOnly), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_GameServerGroupName(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigGameServerGroupName(rName, rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "game_server_group_name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + { + Config: testAccGameliftGameServerGroupConfigGameServerGroupName(rName, rName+"-new"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "game_server_group_name", rName+"-new"), + ), + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_InstanceDefinition(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigInstanceDefinition(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_definition.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + { + Config: testAccGameliftGameServerGroupConfigInstanceDefinition(rName, 3), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_definition.#", "3"), + ), + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_InstanceDefinition_WeightedCapacity(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigInstanceDefinitionWeightedCapacity(rName, "1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_definition.#", "2"), + resource.TestCheckResourceAttr(resourceName, "instance_definition.0.weighted_capacity", "1"), + resource.TestCheckResourceAttr(resourceName, "instance_definition.1.weighted_capacity", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + { + Config: testAccGameliftGameServerGroupConfigInstanceDefinitionWeightedCapacity(rName, "2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_definition.#", "2"), + resource.TestCheckResourceAttr(resourceName, "instance_definition.0.weighted_capacity", "2"), + resource.TestCheckResourceAttr(resourceName, "instance_definition.1.weighted_capacity", "2"), + ), + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_LaunchTemplate_Id(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigLaunchTemplateId(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "launch_template.0.id", "aws_launch_template.test", "id"), + resource.TestCheckResourceAttr(resourceName, "launch_template.0.name", rName), + resource.TestCheckResourceAttr(resourceName, "launch_template.0.version", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_LaunchTemplate_Name(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigLaunchTemplateName(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "launch_template.0.id", "aws_launch_template.test", "id"), + resource.TestCheckResourceAttr(resourceName, "launch_template.0.name", rName), + resource.TestCheckResourceAttr(resourceName, "launch_template.0.version", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_LaunchTemplate_Version(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigLaunchTemplateVersion(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "launch_template.0.id", "aws_launch_template.test", "id"), + resource.TestCheckResourceAttr(resourceName, "launch_template.0.name", rName), + resource.TestCheckResourceAttr(resourceName, "launch_template.0.version", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_GameServerProtectionPolicy(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigGameServerProtectionPolicy(rName, gamelift.GameServerProtectionPolicyFullProtection), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "game_server_protection_policy", gamelift.GameServerProtectionPolicyFullProtection), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_MaxSize(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigMaxSize(rName, "1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "max_size", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + { + Config: testAccGameliftGameServerGroupConfigMaxSize(rName, "2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "max_size", "2"), + ), + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_MinSize(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigMinSize(rName, "1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "min_size", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + { + Config: testAccGameliftGameServerGroupConfigMinSize(rName, "2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "min_size", "2"), + ), + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_RoleArn(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigRoleArn(rName, "test1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + acctest.CheckResourceAttrGlobalARN(resourceName, "role_arn", "iam", fmt.Sprintf(`role/%s-test1`, rName)), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test1", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + { + Config: testAccGameliftGameServerGroupConfigRoleArn(rName, "test2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + acctest.CheckResourceAttrGlobalARN(resourceName, "role_arn", "iam", fmt.Sprintf(`role/%s-test2`, rName)), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test2", "arn"), + ), + }, + }, + }) +} + +func TestAccGameliftGameServerGroup_VpcSubnets(t *testing.T) { + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_gamelift_game_server_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(gamelift.EndpointsID, t) + testAccPreCheck(t) + }, + ErrorCheck: acctest.ErrorCheck(t, gamelift.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGameliftGameServerGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGameliftGameServerGroupConfigVpcSubnets(rName, 1), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vpc_subnets"}, + }, + { + Config: testAccGameliftGameServerGroupConfigVpcSubnets(rName, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckGameliftGameServerGroupExists(resourceName), + ), + }, + }, + }) +} + +func testAccCheckGameliftGameServerGroupDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).GameLiftConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_gamelift_game_server_group" { + continue + } + + input := gamelift.DescribeGameServerGroupInput{ + GameServerGroupName: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeGameServerGroup(&input) + + if tfawserr.ErrCodeEquals(err, gamelift.ErrCodeNotFoundException) { + continue + } + + if err != nil { + return err + } + + if output != nil { + return fmt.Errorf("Gamelift Game Server Group (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckGameliftGameServerGroupExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource %s not found", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("resource %s has not set its id", resourceName) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).GameLiftConn + + input := gamelift.DescribeGameServerGroupInput{ + GameServerGroupName: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeGameServerGroup(&input) + + if err != nil { + return fmt.Errorf("error reading Gamelift Game Server Group (%s): %w", rs.Primary.ID, err) + } + + if output == nil { + return fmt.Errorf("Gamelift Game Server Group (%s) not found", rs.Primary.ID) + } + + return nil + } +} + +func testAccGameliftGameServerGroupIamConfig(rName string, name string) string { + return fmt.Sprintf(` +data "aws_partition" %[2]q {} + +resource "aws_iam_role" %[2]q { + assume_role_policy = <<-EOF + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "autoscaling.amazonaws.com", + "gamelift.amazonaws.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + EOF + name = "%[1]s-%[2]s" +} + +resource "aws_iam_role_policy_attachment" %[2]q { + policy_arn = "arn:${data.aws_partition.%[2]s.partition}:iam::aws:policy/GameLiftGameServerGroupPolicy" + role = aws_iam_role.%[2]s.name +} +`, rName, name) +} + +func testAccGameliftGameServerGroupLaunchTemplateConfig(rName string) string { + return fmt.Sprintf(` +data "aws_vpc" "test" { + default = true +} + +data "aws_subnets" "test" { + filter { + name = "vpc-id" + values = [data.aws_vpc.test.id] + } +} + +resource "aws_launch_template" "test" { + image_id = data.aws_ami.amzn-ami-minimal-hvm-ebs.id + name = %[1]q + + network_interfaces { + subnet_id = data.aws_subnets.test.ids[0] + } +} +`, rName) +} + +func testAccGameliftGameServerGroupInstanceTypeOfferingsConfig() string { + return ` +data "aws_ec2_instance_type_offerings" "available" { + filter { + name = "instance-type" + values = ["c5a.large", "c5a.2xlarge", "c5.large", "c5.2xlarge", "m4.large", "m4.2xlarge", "m5a.large", "m5a.2xlarge", "m5.large", "m5.2xlarge"] + } +} +` +} + +func testAccGameliftGameServerGroupConfig(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName)) +} + +func testAccGameliftGameServerGroupConfigAutoScalingPolicy(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + auto_scaling_policy { + target_tracking_configuration { + target_value = 77.7 + } + } + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName)) +} + +func testAccGameliftGameServerGroupConfigAutoScalingPolicyEstimatedInstanceWarmup(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + auto_scaling_policy { + estimated_instance_warmup = 66 + target_tracking_configuration { + target_value = 77.7 + } + } + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName)) +} + +func testAccGameliftGameServerGroupConfigBalancingStrategy(rName string, balancingStrategy string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + balancing_strategy = %[2]q + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName, balancingStrategy)) +} + +func testAccGameliftGameServerGroupConfigGameServerGroupName(rName string, gameServerGroupName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, gameServerGroupName)) +} + +func testAccGameliftGameServerGroupConfigInstanceDefinition(rName string, count int) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = slice(sort(tolist(data.aws_ec2_instance_type_offerings.available.instance_types)), 0, %[2]d) + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName, count)) +} + +func testAccGameliftGameServerGroupConfigInstanceDefinitionWeightedCapacity(rName string, weightedCapacity string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = slice(sort(tolist(data.aws_ec2_instance_type_offerings.available.instance_types)), 0, 2) + content { + instance_type = instance_definition.value + weighted_capacity = %[2]q + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName, weightedCapacity)) +} + +func testAccGameliftGameServerGroupConfigLaunchTemplateId(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName)) +} + +func testAccGameliftGameServerGroupConfigLaunchTemplateName(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + name = aws_launch_template.test.name + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName)) +} + +func testAccGameliftGameServerGroupConfigLaunchTemplateVersion(rName string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + version = 1 + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName)) +} + +func testAccGameliftGameServerGroupConfigMaxSize(rName string, maxSize string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = %[2]s + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName, maxSize)) +} + +func testAccGameliftGameServerGroupConfigMinSize(rName string, minSize string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 2 + min_size = %[2]s + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName, minSize)) +} + +func testAccGameliftGameServerGroupConfigGameServerProtectionPolicy(rName string, gameServerProtectionPolicy string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + game_server_protection_policy = %[2]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName, gameServerProtectionPolicy)) +} + +func testAccGameliftGameServerGroupConfigRoleArn(rName string, roleArn string) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, roleArn), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.%[2]s.arn + + vpc_subnets = [data.aws_subnets.test.ids[0]] + + depends_on = [aws_iam_role_policy_attachment.%[2]s] +} +`, rName, roleArn)) +} + +func testAccGameliftGameServerGroupConfigVpcSubnets(rName string, count int) string { + return acctest.ConfigCompose( + acctest.ConfigLatestAmazonLinuxHvmEbsAmi(), + testAccGameliftGameServerGroupIamConfig(rName, "test"), + testAccGameliftGameServerGroupInstanceTypeOfferingsConfig(), + testAccGameliftGameServerGroupLaunchTemplateConfig(rName), + fmt.Sprintf(` +resource "aws_gamelift_game_server_group" "test" { + game_server_group_name = %[1]q + dynamic "instance_definition" { + for_each = data.aws_ec2_instance_type_offerings.available.instance_types + content { + instance_type = instance_definition.value + } + } + launch_template { + id = aws_launch_template.test.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.test.arn + vpc_subnets = slice(data.aws_subnets.test.ids, 0, %[2]d) + + depends_on = [aws_iam_role_policy_attachment.test] +} +`, rName, count)) +} diff --git a/internal/service/gamelift/status.go b/internal/service/gamelift/status.go index bd4dd11888e..352e9487f2e 100644 --- a/internal/service/gamelift/status.go +++ b/internal/service/gamelift/status.go @@ -38,3 +38,19 @@ func statusFleet(conn *gamelift.GameLift, id string) resource.StateRefreshFunc { return output, aws.StringValue(output.Status), nil } } + +func statusGameServerGroup(conn *gamelift.GameLift, name string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindGameServerGroupByName(conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} diff --git a/internal/service/gamelift/sweep.go b/internal/service/gamelift/sweep.go index e8b432441e2..0d5f311b08d 100644 --- a/internal/service/gamelift/sweep.go +++ b/internal/service/gamelift/sweep.go @@ -42,6 +42,11 @@ func init() { F: sweepFleets, }) + resource.AddTestSweepers("aws_gamelift_game_server_group", &resource.Sweeper{ + Name: "aws_gamelift_game_server_group", + F: sweepGameServerGroups, + }) + resource.AddTestSweepers("aws_gamelift_game_session_queue", &resource.Sweeper{ Name: "aws_gamelift_game_session_queue", F: sweepGameSessionQueue, @@ -214,6 +219,60 @@ func sweepFleets(region string) error { return errs.ErrorOrNil() } +func sweepGameServerGroups(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).GameLiftConn + sweepResources := make([]*sweep.SweepResource, 0) + var errs *multierror.Error + + input := &gamelift.ListGameServerGroupsInput{} + + for { + output, err := conn.ListGameServerGroups(input) + + for _, gameServerGroup := range output.GameServerGroups { + r := ResourceGameServerGroup() + d := r.Data(nil) + + id := aws.StringValue(gameServerGroup.GameServerGroupName) + d.SetId(id) + + if err != nil { + err := fmt.Errorf("error reading GameLift Game Server Group (%s): %w", id, err) + log.Printf("[ERROR] %s", err) + errs = multierror.Append(errs, err) + continue + } + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + if aws.StringValue(output.NextToken) == "" { + break + } + + input.NextToken = output.NextToken + } + + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("error listing GameLift Game Server Group for %s: %w", region, err)) + } + + if err := sweep.SweepOrchestrator(sweepResources); err != nil { + errs = multierror.Append(errs, fmt.Errorf("error sweeping GameLift Game Server Group for %s: %w", region, err)) + } + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping GameLift Game Server Group sweep for %s: %s", region, errs) + return nil + } + + return errs.ErrorOrNil() +} + func sweepGameSessionQueue(region string) error { client, err := sweep.SharedRegionalSweepClient(region) if err != nil { diff --git a/internal/service/gamelift/wait.go b/internal/service/gamelift/wait.go index aa56ea61a76..3b16dc226c0 100644 --- a/internal/service/gamelift/wait.go +++ b/internal/service/gamelift/wait.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/gamelift" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -144,3 +145,47 @@ func isGameliftEventFailure(event *gamelift.Event) bool { } return false } + +func waitGameServerGroupActive(conn *gamelift.GameLift, name string, timeout time.Duration) (*gamelift.GameServerGroup, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + gamelift.GameServerGroupStatusNew, + gamelift.GameServerGroupStatusActivating, + }, + Target: []string{gamelift.GameServerGroupStatusActive}, + Refresh: statusGameServerGroup(conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*gamelift.GameServerGroup); ok { + return output, err + } + + return nil, err +} + +func waitGameServerGroupTerminated(conn *gamelift.GameLift, name string, timeout time.Duration) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + gamelift.GameServerGroupStatusDeleteScheduled, + gamelift.GameServerGroupStatusDeleting, + }, + Target: []string{}, + Refresh: statusGameServerGroup(conn, name), + Timeout: timeout, + } + + _, err := stateConf.WaitForState() + + if tfawserr.ErrCodeEquals(err, gamelift.ErrCodeNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting GameLift Game Server Group (%s): %w", name, err) + } + + return nil +} diff --git a/internal/service/iot/topic_rule.go b/internal/service/iot/topic_rule.go index ecc0a43de41..78cfce7af8e 100644 --- a/internal/service/iot/topic_rule.go +++ b/internal/service/iot/topic_rule.go @@ -58,6 +58,23 @@ func ResourceTopicRule() *schema.Resource { }, }, }, + "cloudwatch_logs": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "log_group_name": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + }, "cloudwatch_metric": { Type: schema.TypeSet, Optional: true, @@ -463,6 +480,43 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", + "error_action.0.cloudwatch_metric", + "error_action.0.dynamodb", + "error_action.0.dynamodbv2", + "error_action.0.elasticsearch", + "error_action.0.firehose", + "error_action.0.iot_analytics", + "error_action.0.iot_events", + "error_action.0.kinesis", + "error_action.0.lambda", + "error_action.0.republish", + "error_action.0.s3", + "error_action.0.step_functions", + "error_action.0.sns", + "error_action.0.sqs", + }, + }, + "cloudwatch_logs": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "log_group_name": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + }, + }, + ExactlyOneOf: []string{ + "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -515,6 +569,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -587,6 +642,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -631,6 +687,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -679,6 +736,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -719,6 +777,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -754,6 +813,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -793,6 +853,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -832,6 +893,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -863,6 +925,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -904,6 +967,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -943,6 +1007,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -982,6 +1047,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -1023,6 +1089,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -1062,6 +1129,7 @@ func ResourceTopicRule() *schema.Resource { }, ExactlyOneOf: []string{ "error_action.0.cloudwatch_alarm", + "error_action.0.cloudwatch_logs", "error_action.0.cloudwatch_metric", "error_action.0.dynamodb", "error_action.0.dynamodbv2", @@ -1175,6 +1243,10 @@ func resourceTopicRuleRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting cloudwatch_alarm: %w", err) } + if err := d.Set("cloudwatch_logs", flattenIotCloudWatchLogsActions(out.Rule.Actions)); err != nil { + return fmt.Errorf("error setting cloudwatch_logs: %w", err) + } + if err := d.Set("cloudwatch_metric", flattenIotCloudwatchMetricActions(out.Rule.Actions)); err != nil { return fmt.Errorf("error setting cloudwatch_metric: %w", err) } @@ -1243,6 +1315,7 @@ func resourceTopicRuleUpdate(d *schema.ResourceData, meta interface{}) error { if d.HasChanges( "cloudwatch_alarm", + "cloudwatch_logs", "cloudwatch_metric", "description", "dynamodb", @@ -1344,6 +1417,25 @@ func expandIotCloudwatchAlarmAction(tfList []interface{}) *iot.CloudwatchAlarmAc return apiObject } +func expandIotCloudwatchLogsAction(tfList []interface{}) *iot.CloudwatchLogsAction { + if len(tfList) == 0 || tfList[0] == nil { + return nil + } + + apiObject := &iot.CloudwatchLogsAction{} + tfMap := tfList[0].(map[string]interface{}) + + if v, ok := tfMap["log_group_name"].(string); ok && v != "" { + apiObject.LogGroupName = aws.String(v) + } + + if v, ok := tfMap["role_arn"].(string); ok && v != "" { + apiObject.RoleArn = aws.String(v) + } + + return apiObject +} + func expandIotCloudwatchMetricAction(tfList []interface{}) *iot.CloudwatchMetricAction { if len(tfList) == 0 || tfList[0] == nil { return nil @@ -1712,6 +1804,17 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { actions = append(actions, &iot.Action{CloudwatchAlarm: action}) } + // Legacy root attribute handling + for _, tfMapRaw := range d.Get("cloudwatch_logs").(*schema.Set).List() { + action := expandIotCloudwatchLogsAction([]interface{}{tfMapRaw}) + + if action == nil { + continue + } + + actions = append(actions, &iot.Action{CloudwatchLogs: action}) + } + // Legacy root attribute handling for _, tfMapRaw := range d.Get("cloudwatch_metric").(*schema.Set).List() { action := expandIotCloudwatchMetricAction([]interface{}{tfMapRaw}) @@ -1887,6 +1990,16 @@ func expandIotTopicRulePayload(d *schema.ResourceData) *iot.TopicRulePayload { iotErrorAction = &iot.Action{CloudwatchAlarm: action} } + case "cloudwatch_logs": + for _, tfMapRaw := range v.([]interface{}) { + action := expandIotCloudwatchLogsAction([]interface{}{tfMapRaw}) + + if action == nil { + continue + } + + iotErrorAction = &iot.Action{CloudwatchLogs: action} + } case "cloudwatch_metric": for _, tfMapRaw := range v.([]interface{}) { action := expandIotCloudwatchMetricAction([]interface{}{tfMapRaw}) @@ -2084,6 +2197,41 @@ func flattenIotCloudWatchAlarmActions(actions []*iot.Action) []interface{} { return results } +func flattenIotCloudwatchLogsAction(apiObject *iot.CloudwatchLogsAction) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := make(map[string]interface{}) + + if v := apiObject.LogGroupName; v != nil { + tfMap["log_group_name"] = aws.StringValue(v) + } + + if v := apiObject.RoleArn; v != nil { + tfMap["role_arn"] = aws.StringValue(v) + } + + return []interface{}{tfMap} +} + +// Legacy root attribute handling +func flattenIotCloudWatchLogsActions(actions []*iot.Action) []interface{} { + results := make([]interface{}, 0) + + for _, action := range actions { + if action == nil { + continue + } + + if v := action.CloudwatchLogs; v != nil { + results = append(results, flattenIotCloudwatchLogsAction(v)...) + } + } + + return results +} + // Legacy root attribute handling func flattenIotCloudwatchMetricActions(actions []*iot.Action) []interface{} { results := make([]interface{}, 0) @@ -2665,6 +2813,10 @@ func flattenIotErrorAction(errorAction *iot.Action) []map[string]interface{} { results = append(results, map[string]interface{}{"cloudwatch_alarm": flattenIotCloudWatchAlarmActions(input)}) return results } + if errorAction.CloudwatchLogs != nil { + results = append(results, map[string]interface{}{"cloudwatch_logs": flattenIotCloudWatchLogsActions(input)}) + return results + } if errorAction.CloudwatchMetric != nil { results = append(results, map[string]interface{}{"cloudwatch_metric": flattenIotCloudwatchMetricActions(input)}) return results diff --git a/internal/service/iot/topic_rule_test.go b/internal/service/iot/topic_rule_test.go index 3705b3f72e9..b2f3833312f 100644 --- a/internal/service/iot/topic_rule_test.go +++ b/internal/service/iot/topic_rule_test.go @@ -69,6 +69,31 @@ func TestAccIoTTopicRule_cloudWatchAlarm(t *testing.T) { }) } +func TestAccIoTTopicRule_cloudWatchLogs(t *testing.T) { + rName := sdkacctest.RandString(5) + resourceName := "aws_iot_topic_rule.rule" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iot.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTopicRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccIoTTopicRule_cloudWatchLogs(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTopicRuleExists("aws_iot_topic_rule.rule"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccIoTTopicRule_cloudWatchMetric(t *testing.T) { rName := sdkacctest.RandString(5) resourceName := "aws_iot_topic_rule.rule" @@ -705,6 +730,25 @@ resource "aws_iot_topic_rule" "rule" { `, rName)) } +func testAccIoTTopicRule_cloudWatchLogs(rName string) string { + return acctest.ConfigCompose( + testAccTopicRuleRole(rName), + fmt.Sprintf(` +resource "aws_iot_topic_rule" "rule" { + name = "test_rule_%[1]s" + description = "Example rule" + enabled = true + sql = "SELECT * FROM 'topic/test'" + sql_version = "2015-10-08" + + cloudwatch_logs { + log_group_name = "mylogs" + role_arn = aws_iam_role.iot_role.arn + } +} +`, rName)) +} + func testAccTopicRule_cloudWatchmetric(rName string) string { return acctest.ConfigCompose( testAccTopicRuleRole(rName), diff --git a/internal/service/kafka/configuration.go b/internal/service/kafka/configuration.go index 9cfc3f816b0..4d918e060bb 100644 --- a/internal/service/kafka/configuration.go +++ b/internal/service/kafka/configuration.go @@ -1,12 +1,14 @@ package kafka import ( + "context" "fmt" "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/kafka" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/flex" @@ -23,6 +25,12 @@ func ResourceConfiguration() *schema.Resource { State: schema.ImportStatePassthrough, }, + CustomizeDiff: customdiff.Sequence( + customdiff.ComputedIf("latest_revision", func(_ context.Context, diff *schema.ResourceDiff, meta interface{}) bool { + return diff.HasChange("server_properties") + }), + ), + Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, diff --git a/internal/service/networkmanager/connection.go b/internal/service/networkmanager/connection.go new file mode 100644 index 00000000000..bf8f1853a10 --- /dev/null +++ b/internal/service/networkmanager/connection.go @@ -0,0 +1,385 @@ +package networkmanager + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceConnection() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceConnectionCreate, + ReadWithoutTimeout: resourceConnectionRead, + UpdateWithoutTimeout: resourceConnectionUpdate, + DeleteWithoutTimeout: resourceConnectionDelete, + + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parsedARN, err := arn.Parse(d.Id()) + + if err != nil { + return nil, fmt.Errorf("error parsing ARN (%s): %w", d.Id(), err) + } + + // See https://docs.aws.amazon.com/service-authorization/latest/reference/list_networkmanager.html#networkmanager-resources-for-iam-policies. + resourceParts := strings.Split(parsedARN.Resource, "/") + + if actual, expected := len(resourceParts), 3; actual < expected { + return nil, fmt.Errorf("expected at least %d resource parts in ARN (%s), got: %d", expected, d.Id(), actual) + } + + d.SetId(resourceParts[2]) + d.Set("global_network_id", resourceParts[1]) + + return []*schema.ResourceData{d}, nil + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "connected_device_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "connected_link_id": { + Type: schema.TypeString, + Optional: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "device_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "link_id": { + Type: schema.TypeString, + Optional: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + } +} + +func resourceConnectionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + globalNetworkID := d.Get("global_network_id").(string) + + input := &networkmanager.CreateConnectionInput{ + ConnectedDeviceId: aws.String(d.Get("connected_device_id").(string)), + DeviceId: aws.String(d.Get("device_id").(string)), + GlobalNetworkId: aws.String(globalNetworkID), + } + + if v, ok := d.GetOk("connected_link_id"); ok { + input.ConnectedLinkId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("link_id"); ok { + input.LinkId = aws.String(v.(string)) + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + log.Printf("[DEBUG] Creating Network Manager Connection: %s", input) + output, err := conn.CreateConnectionWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating Network Manager Connection: %s", err) + } + + d.SetId(aws.StringValue(output.Connection.ConnectionId)) + + if _, err := waitConnectionCreated(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Connection (%s) create: %s", d.Id(), err) + } + + return resourceConnectionRead(ctx, d, meta) +} + +func resourceConnectionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + connection, err := FindConnectionByTwoPartKey(ctx, conn, globalNetworkID, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Connection %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Connection (%s): %s", d.Id(), err) + } + + d.Set("arn", connection.ConnectionArn) + d.Set("connected_device_id", connection.ConnectedDeviceId) + d.Set("connected_link_id", connection.ConnectedLinkId) + d.Set("description", connection.Description) + d.Set("device_id", connection.DeviceId) + d.Set("global_network_id", connection.GlobalNetworkId) + d.Set("link_id", connection.LinkId) + + tags := KeyValueTags(connection.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("error setting tags_all: %s", err) + } + + return nil +} + +func resourceConnectionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + if d.HasChangesExcept("tags", "tags_all") { + globalNetworkID := d.Get("global_network_id").(string) + input := &networkmanager.UpdateConnectionInput{ + ConnectedLinkId: aws.String(d.Get("connected_link_id").(string)), + ConnectionId: aws.String(d.Id()), + Description: aws.String(d.Get("description").(string)), + GlobalNetworkId: aws.String(globalNetworkID), + LinkId: aws.String(d.Get("link_id").(string)), + } + + log.Printf("[DEBUG] Updating Network Manager Connection: %s", input) + _, err := conn.UpdateConnectionWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error updating Network Manager Connection (%s): %s", d.Id(), err) + } + + if _, err := waitConnectionUpdated(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("error waiting for Network Manager Connection (%s) update: %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("error updating Network Manager Connection (%s) tags: %s", d.Id(), err) + } + } + + return resourceConnectionRead(ctx, d, meta) +} + +func resourceConnectionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID := d.Get("global_network_id").(string) + + log.Printf("[DEBUG] Deleting Network Manager Connection: %s", d.Id()) + _, err := conn.DeleteConnectionWithContext(ctx, &networkmanager.DeleteConnectionInput{ + ConnectionId: aws.String(d.Id()), + GlobalNetworkId: aws.String(globalNetworkID), + }) + + if globalNetworkIDNotFoundError(err) || tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting Network Manager Connection (%s): %s", d.Id(), err) + } + + if _, err := waitConnectionDeleted(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("error waiting for Network Manager Connection (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindConnection(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetConnectionsInput) (*networkmanager.Connection, error) { + output, err := FindConnections(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindConnections(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetConnectionsInput) ([]*networkmanager.Connection, error) { + var output []*networkmanager.Connection + + err := conn.GetConnectionsPagesWithContext(ctx, input, func(page *networkmanager.GetConnectionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Connections { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if globalNetworkIDNotFoundError(err) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindConnectionByTwoPartKey(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectionID string) (*networkmanager.Connection, error) { + input := &networkmanager.GetConnectionsInput{ + ConnectionIds: aws.StringSlice([]string{connectionID}), + GlobalNetworkId: aws.String(globalNetworkID), + } + + output, err := FindConnection(ctx, conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != globalNetworkID || aws.StringValue(output.ConnectionId) != connectionID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusConnectionState(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectionID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindConnectionByTwoPartKey(ctx, conn, globalNetworkID, connectionID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func waitConnectionCreated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectionID string, timeout time.Duration) (*networkmanager.Connection, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.ConnectionStatePending}, + Target: []string{networkmanager.ConnectionStateAvailable}, + Timeout: timeout, + Refresh: statusConnectionState(ctx, conn, globalNetworkID, connectionID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Connection); ok { + return output, err + } + + return nil, err +} + +func waitConnectionDeleted(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectionID string, timeout time.Duration) (*networkmanager.Connection, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.ConnectionStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusConnectionState(ctx, conn, globalNetworkID, connectionID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Connection); ok { + return output, err + } + + return nil, err +} + +func waitConnectionUpdated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectionID string, timeout time.Duration) (*networkmanager.Connection, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.ConnectionStateUpdating}, + Target: []string{networkmanager.ConnectionStateAvailable}, + Timeout: timeout, + Refresh: statusConnectionState(ctx, conn, globalNetworkID, connectionID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Connection); ok { + return output, err + } + + return nil, err +} diff --git a/internal/service/networkmanager/connection_data_source.go b/internal/service/networkmanager/connection_data_source.go new file mode 100644 index 00000000000..743df865b65 --- /dev/null +++ b/internal/service/networkmanager/connection_data_source.go @@ -0,0 +1,81 @@ +package networkmanager + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceConnection() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceConnectionRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "connected_device_id": { + Type: schema.TypeString, + Computed: true, + }, + "connected_link_id": { + Type: schema.TypeString, + Computed: true, + }, + "connection_id": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "device_id": { + Type: schema.TypeString, + Computed: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "link_id": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +func dataSourceConnectionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + connectionID := d.Get("connection_id").(string) + connection, err := FindConnectionByTwoPartKey(ctx, conn, globalNetworkID, connectionID) + + if err != nil { + return diag.Errorf("error reading Network Manager Connection (%s): %s", connectionID, err) + } + + d.SetId(connectionID) + d.Set("arn", connection.ConnectionArn) + d.Set("connected_device_id", connection.ConnectedDeviceId) + d.Set("connected_link_id", connection.ConnectedLinkId) + d.Set("connection_id", connection.ConnectionId) + d.Set("description", connection.Description) + d.Set("device_id", connection.DeviceId) + d.Set("global_network_id", connection.GlobalNetworkId) + d.Set("link_id", connection.LinkId) + + if err := d.Set("tags", KeyValueTags(connection.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + return nil +} diff --git a/internal/service/networkmanager/connection_data_source_test.go b/internal/service/networkmanager/connection_data_source_test.go new file mode 100644 index 00000000000..287f7111f5c --- /dev/null +++ b/internal/service/networkmanager/connection_data_source_test.go @@ -0,0 +1,46 @@ +package networkmanager_test + +import ( + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerConnectionDataSource_basic(t *testing.T) { + dataSourceName := "data.aws_networkmanager_connection.test" + resourceName := "aws_networkmanager_connection.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccConnectionDataSourceConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "connected_device_id", resourceName, "connected_device_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "connected_link_id", resourceName, "connected_link_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "connection_id", resourceName, "id"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "global_network_id", resourceName, "global_network_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "link_id", resourceName, "link_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + ), + }, + }, + }) +} + +func testAccConnectionDataSourceConfig(rName string) string { + return acctest.ConfigCompose(testAccConnectionDescriptionAndLinksConfig(rName), ` +data "aws_networkmanager_connection" "test" { + global_network_id = aws_networkmanager_global_network.test.id + connection_id = aws_networkmanager_connection.test.id +} +`) +} diff --git a/internal/service/networkmanager/connection_test.go b/internal/service/networkmanager/connection_test.go new file mode 100644 index 00000000000..8ad62fd6726 --- /dev/null +++ b/internal/service/networkmanager/connection_test.go @@ -0,0 +1,408 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerConnection_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccNetworkManagerConnection_basic, + "disappears": testAccNetworkManagerConnection_disappears, + "tags": testAccNetworkManagerConnection_tags, + "descriptionAndLinks": testAccNetworkManagerConnection_descriptionAndLinks, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccNetworkManagerConnection_basic(t *testing.T) { + resourceName := "aws_networkmanager_connection.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConnectionConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckConnectionExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "connected_link_id", ""), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "link_id", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccConnectionImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNetworkManagerConnection_disappears(t *testing.T) { + resourceName := "aws_networkmanager_connection.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConnectionConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckConnectionExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceConnection(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccNetworkManagerConnection_tags(t *testing.T) { + resourceName := "aws_networkmanager_connection.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConnectionConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConnectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccConnectionImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccConnectionConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConnectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccConnectionConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConnectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccNetworkManagerConnection_descriptionAndLinks(t *testing.T) { + resourceName := "aws_networkmanager_connection.test" + link1ResourceName := "aws_networkmanager_link.test1" + link2ResourceName := "aws_networkmanager_link.test2" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccConnectionDescriptionAndLinksConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckConnectionExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "connected_link_id", ""), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + resource.TestCheckResourceAttrPair(resourceName, "link_id", link1ResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccConnectionImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccConnectionDescriptionAndLinksUpdatedConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckConnectionExists(resourceName), + resource.TestCheckResourceAttrPair(resourceName, "connected_link_id", link2ResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + resource.TestCheckResourceAttrPair(resourceName, "link_id", link1ResourceName, "id"), + ), + }, + }, + }) +} + +func testAccCheckConnectionDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_device" { + continue + } + + _, err := tfnetworkmanager.FindConnectionByTwoPartKey(context.TODO(), conn, rs.Primary.Attributes["global_network_id"], rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Connection %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckConnectionExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Connection ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + _, err := tfnetworkmanager.FindConnectionByTwoPartKey(context.TODO(), conn, rs.Primary.Attributes["global_network_id"], rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccConnectionBaseConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + description = %[1]q + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + description = %[1]q + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "%[1]s-1" + + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test2" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "%[1]s-2" + + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } + + # Create one device at a time. + depends_on = [aws_networkmanager_device.test1] +} +`, rName) +} + +func testAccConnectionConfig(rName string) string { + return acctest.ConfigCompose(testAccConnectionBaseConfig(rName), ` +resource "aws_networkmanager_connection" "test" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test1.id + connected_device_id = aws_networkmanager_device.test2.id +} +`) +} + +func testAccConnectionConfigTags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccConnectionBaseConfig(rName), fmt.Sprintf(` +resource "aws_networkmanager_connection" "test" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test1.id + connected_device_id = aws_networkmanager_device.test2.id + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1)) +} + +func testAccConnectionConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccConnectionBaseConfig(rName), fmt.Sprintf(` +resource "aws_networkmanager_connection" "test" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test1.id + connected_device_id = aws_networkmanager_device.test2.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} + +func testAccConnectionDescriptionAndLinksBaseConfig(rName string) string { + return acctest.ConfigCompose(testAccConnectionBaseConfig(rName), fmt.Sprintf(` +resource "aws_networkmanager_link" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + description = "%[1]s-1" + + bandwidth { + download_speed = 50 + upload_speed = 10 + } + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test2" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + description = "%[1]s-2" + + bandwidth { + download_speed = 100 + upload_speed = 20 + } + + tags = { + Name = %[1]q + } + + # Create one link at a time. + depends_on = [aws_networkmanager_link.test1] +} + +resource "aws_networkmanager_link_association" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + link_id = aws_networkmanager_link.test1.id + device_id = aws_networkmanager_device.test1.id +} + +resource "aws_networkmanager_link_association" "test2" { + global_network_id = aws_networkmanager_global_network.test.id + link_id = aws_networkmanager_link.test2.id + device_id = aws_networkmanager_device.test2.id +} +`, rName)) +} + +func testAccConnectionDescriptionAndLinksConfig(rName string) string { + return acctest.ConfigCompose(testAccConnectionDescriptionAndLinksBaseConfig(rName), fmt.Sprintf(` +resource "aws_networkmanager_connection" "test" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test1.id + connected_device_id = aws_networkmanager_device.test2.id + + description = "description1" + + link_id = aws_networkmanager_link.test1.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_networkmanager_link_association.test1, aws_networkmanager_link_association.test2] +} +`, rName)) +} + +func testAccConnectionDescriptionAndLinksUpdatedConfig(rName string) string { + return acctest.ConfigCompose(testAccConnectionDescriptionAndLinksBaseConfig(rName), fmt.Sprintf(` +resource "aws_networkmanager_connection" "test" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test1.id + connected_device_id = aws_networkmanager_device.test2.id + + description = "description2" + + link_id = aws_networkmanager_link.test1.id + connected_link_id = aws_networkmanager_link.test2.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_networkmanager_link_association.test1, aws_networkmanager_link_association.test2] +} +`, rName)) +} + +func testAccConnectionImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + + return rs.Primary.Attributes["arn"], nil + } +} diff --git a/internal/service/networkmanager/connections_data_source.go b/internal/service/networkmanager/connections_data_source.go new file mode 100644 index 00000000000..24c425d1221 --- /dev/null +++ b/internal/service/networkmanager/connections_data_source.go @@ -0,0 +1,72 @@ +package networkmanager + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceConnections() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceConnectionsRead, + + Schema: map[string]*schema.Schema{ + "device_id": { + Type: schema.TypeString, + Optional: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": tftags.TagsSchema(), + }, + } +} + +func dataSourceConnectionsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tagsToMatch := tftags.New(d.Get("tags").(map[string]interface{})).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + input := &networkmanager.GetConnectionsInput{ + GlobalNetworkId: aws.String(d.Get("global_network_id").(string)), + } + + if v, ok := d.GetOk("device_id"); ok { + input.DeviceId = aws.String(v.(string)) + } + + output, err := FindConnections(ctx, conn, input) + + if err != nil { + return diag.Errorf("error listing Network Manager Connections: %s", err) + } + + var connectionIDs []string + + for _, v := range output { + if len(tagsToMatch) > 0 { + if !KeyValueTags(v.Tags).ContainsAll(tagsToMatch) { + continue + } + } + + connectionIDs = append(connectionIDs, aws.StringValue(v.ConnectionId)) + } + + d.SetId(meta.(*conns.AWSClient).Region) + d.Set("ids", connectionIDs) + + return nil +} diff --git a/internal/service/networkmanager/connections_data_source_test.go b/internal/service/networkmanager/connections_data_source_test.go new file mode 100644 index 00000000000..dbd716664e9 --- /dev/null +++ b/internal/service/networkmanager/connections_data_source_test.go @@ -0,0 +1,150 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerConnectionsDataSource_basic(t *testing.T) { + dataSourceAllName := "data.aws_networkmanager_connections.all" + dataSourceByTagsName := "data.aws_networkmanager_connections.by_tags" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccConnectionsDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue(dataSourceAllName, "ids.#", "1"), + resource.TestCheckResourceAttr(dataSourceByTagsName, "ids.#", "1"), + ), + }, + }, + }) +} + +func testAccConnectionsDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + description = %[1]q + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + description = %[1]q + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "%[1]s-1" + + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test2" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "%[1]s-2" + + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } + + # Create one device at a time. + depends_on = [aws_networkmanager_device.test1] +} + +resource "aws_networkmanager_device" "test3" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "%[1]s-3" + + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } + + # Create one device at a time. + depends_on = [aws_networkmanager_device.test2] +} + +resource "aws_networkmanager_device" "test4" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "%[1]s-4" + + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } + + # Create one device at a time. + depends_on = [aws_networkmanager_device.test3] +} + +resource "aws_networkmanager_connection" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test1.id + connected_device_id = aws_networkmanager_device.test2.id + + description = "%[1]s-1" +} + +resource "aws_networkmanager_connection" "test2" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test3.id + connected_device_id = aws_networkmanager_device.test4.id + + description = "%[1]s-2" + + tags = { + Name = %[1]q + } + + # Create one connection at a time. + depends_on = [aws_networkmanager_connection.test1] +} + +data "aws_networkmanager_connections" "all" { + global_network_id = aws_networkmanager_global_network.test.id + + depends_on = [aws_networkmanager_connection.test1, aws_networkmanager_connection.test2] +} + +data "aws_networkmanager_connections" "by_tags" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_networkmanager_connection.test1, aws_networkmanager_connection.test2] +} +`, rName) +} diff --git a/internal/service/networkmanager/customer_gateway_association.go b/internal/service/networkmanager/customer_gateway_association.go new file mode 100644 index 00000000000..3412e99ed3f --- /dev/null +++ b/internal/service/networkmanager/customer_gateway_association.go @@ -0,0 +1,339 @@ +package networkmanager + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceCustomerGatewayAssociation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceCustomerGatewayAssociationCreate, + ReadWithoutTimeout: resourceCustomerGatewayAssociationRead, + DeleteWithoutTimeout: resourceCustomerGatewayAssociationDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "customer_gateway_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + "device_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "link_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceCustomerGatewayAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID := d.Get("global_network_id").(string) + customerGatewayARN := d.Get("customer_gateway_arn").(string) + id := CustomerGatewayAssociationCreateResourceID(globalNetworkID, customerGatewayARN) + input := &networkmanager.AssociateCustomerGatewayInput{ + CustomerGatewayArn: aws.String(customerGatewayARN), + DeviceId: aws.String(d.Get("device_id").(string)), + GlobalNetworkId: aws.String(globalNetworkID), + } + + if v, ok := d.GetOk("link_id"); ok { + input.LinkId = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating Network Manager Customer Gateway Association: %s", input) + _, err := tfresource.RetryWhenContext(ctx, customerGatewayAssociationResourceNotFoundExceptionTimeout, + func() (interface{}, error) { + return conn.AssociateCustomerGatewayWithContext(ctx, input) + }, + func(err error) (bool, error) { + // Wait out eventual consistency errors like: + // + // ResourceNotFoundException: Resource not found. + // { + // RespMetadata: { + // StatusCode: 404, + // RequestID: "530d124c-2af8-4adf-be73-cee3793042f3" + // }, + // Message_: "Resource not found.", + // ResourceId: "arn:aws:ec2:us-west-2:123456789012:customer-gateway/cgw-07c83f17516ae28fd", + // ResourceType: "customer-gateway" + // } + if resourceNotFoundExceptionResourceIDEquals(err, customerGatewayARN) { + return true, err + } + + return false, err + }, + ) + + if err != nil { + return diag.Errorf("error creating Network Manager Customer Gateway Association (%s): %s", id, err) + } + + d.SetId(id) + + if _, err := waitCustomerGatewayAssociationCreated(ctx, conn, globalNetworkID, customerGatewayARN, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Customer Gateway Association (%s) create: %s", d.Id(), err) + } + + return resourceCustomerGatewayAssociationRead(ctx, d, meta) +} + +func resourceCustomerGatewayAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, customerGatewayARN, err := CustomerGatewayAssociationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + output, err := FindCustomerGatewayAssociationByTwoPartKey(ctx, conn, globalNetworkID, customerGatewayARN) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Customer Gateway Association %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Customer Gateway Association (%s): %s", d.Id(), err) + } + + d.Set("customer_gateway_arn", output.CustomerGatewayArn) + d.Set("device_id", output.DeviceId) + d.Set("global_network_id", output.GlobalNetworkId) + d.Set("link_id", output.LinkId) + + return nil +} + +func resourceCustomerGatewayAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, customerGatewayARN, err := CustomerGatewayAssociationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + err = disassociateCustomerGateway(ctx, conn, globalNetworkID, customerGatewayARN, d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func disassociateCustomerGateway(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, customerGatewayARN string, timeout time.Duration) error { + id := CustomerGatewayAssociationCreateResourceID(globalNetworkID, customerGatewayARN) + + log.Printf("[DEBUG] Deleting Network Manager Customer Gateway Association: %s", id) + _, err := conn.DisassociateCustomerGatewayWithContext(ctx, &networkmanager.DisassociateCustomerGatewayInput{ + CustomerGatewayArn: aws.String(customerGatewayARN), + GlobalNetworkId: aws.String(globalNetworkID), + }) + + if globalNetworkIDNotFoundError(err) || tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Network Manager Customer Gateway Association (%s): %w", id, err) + } + + if _, err := waitCustomerGatewayAssociationDeleted(ctx, conn, globalNetworkID, customerGatewayARN, timeout); err != nil { + return fmt.Errorf("error waiting for Network Manager Customer Gateway Association (%s) delete: %w", id, err) + } + + return nil +} + +func FindCustomerGatewayAssociation(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetCustomerGatewayAssociationsInput) (*networkmanager.CustomerGatewayAssociation, error) { + output, err := FindCustomerGatewayAssociations(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].State == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindCustomerGatewayAssociations(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetCustomerGatewayAssociationsInput) ([]*networkmanager.CustomerGatewayAssociation, error) { + var output []*networkmanager.CustomerGatewayAssociation + + err := conn.GetCustomerGatewayAssociationsPagesWithContext(ctx, input, func(page *networkmanager.GetCustomerGatewayAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.CustomerGatewayAssociations { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if globalNetworkIDNotFoundError(err) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindCustomerGatewayAssociationByTwoPartKey(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, customerGatewayARN string) (*networkmanager.CustomerGatewayAssociation, error) { + input := &networkmanager.GetCustomerGatewayAssociationsInput{ + CustomerGatewayArns: aws.StringSlice([]string{customerGatewayARN}), + GlobalNetworkId: aws.String(globalNetworkID), + } + + output, err := FindCustomerGatewayAssociation(ctx, conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == networkmanager.CustomerGatewayAssociationStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != globalNetworkID || aws.StringValue(output.CustomerGatewayArn) != customerGatewayARN { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusCustomerGatewayAssociationState(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, customerGatewayARN string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindCustomerGatewayAssociationByTwoPartKey(ctx, conn, globalNetworkID, customerGatewayARN) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func waitCustomerGatewayAssociationCreated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, customerGatewayARN string, timeout time.Duration) (*networkmanager.CustomerGatewayAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.CustomerGatewayAssociationStatePending}, + Target: []string{networkmanager.CustomerGatewayAssociationStateAvailable}, + Timeout: timeout, + Refresh: statusCustomerGatewayAssociationState(ctx, conn, globalNetworkID, customerGatewayARN), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.CustomerGatewayAssociation); ok { + return output, err + } + + return nil, err +} + +func waitCustomerGatewayAssociationDeleted(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, customerGatewayARN string, timeout time.Duration) (*networkmanager.CustomerGatewayAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.CustomerGatewayAssociationStateAvailable, networkmanager.CustomerGatewayAssociationStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusCustomerGatewayAssociationState(ctx, conn, globalNetworkID, customerGatewayARN), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.CustomerGatewayAssociation); ok { + return output, err + } + + return nil, err +} + +const customerGatewayAssociationIDSeparator = "," + +func CustomerGatewayAssociationCreateResourceID(globalNetworkID, customerGatewayARN string) string { + parts := []string{globalNetworkID, customerGatewayARN} + id := strings.Join(parts, customerGatewayAssociationIDSeparator) + + return id +} + +func CustomerGatewayAssociationParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, customerGatewayAssociationIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected GLOBAL-NETWORK-ID%[2]sCUSTOMER-GATEWAY-ARN", id, customerGatewayAssociationIDSeparator) +} + +const ( + customerGatewayAssociationResourceNotFoundExceptionTimeout = 1 * time.Minute +) diff --git a/internal/service/networkmanager/customer_gateway_association_test.go b/internal/service/networkmanager/customer_gateway_association_test.go new file mode 100644 index 00000000000..4468e0e0df4 --- /dev/null +++ b/internal/service/networkmanager/customer_gateway_association_test.go @@ -0,0 +1,232 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/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" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerCustomerGatewayAssociation_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccNetworkManagerCustomerGatewayAssociation_basic, + "disappears": testAccNetworkManagerCustomerGatewayAssociation_disappears, + "disappears_CustomerGateway": testAccNetworkManagerCustomerGatewayAssociation_disappears_CustomerGateway, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccNetworkManagerCustomerGatewayAssociation_basic(t *testing.T) { + resourceName := "aws_networkmanager_customer_gateway_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckCustomerGatewayAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomerGatewayAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomerGatewayAssociationExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNetworkManagerCustomerGatewayAssociation_disappears(t *testing.T) { + resourceName := "aws_networkmanager_customer_gateway_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckCustomerGatewayAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomerGatewayAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomerGatewayAssociationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceCustomerGatewayAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccNetworkManagerCustomerGatewayAssociation_disappears_CustomerGateway(t *testing.T) { + resourceName := "aws_networkmanager_customer_gateway_association.test" + vpnConnectionResourceName := "aws_vpn_connection.test" + customerGatewayResourceName := "aws_customer_gateway.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckCustomerGatewayAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCustomerGatewayAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCustomerGatewayAssociationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceVPNConnection(), vpnConnectionResourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceCustomerGateway(), customerGatewayResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckCustomerGatewayAssociationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_customer_gateway_association" { + continue + } + + globalNetworkID, customerGatewayARN, err := tfnetworkmanager.CustomerGatewayAssociationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfnetworkmanager.FindCustomerGatewayAssociationByTwoPartKey(context.TODO(), conn, globalNetworkID, customerGatewayARN) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Customer Gateway Association %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckCustomerGatewayAssociationExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Customer Gateway Association ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, customerGatewayARN, err := tfnetworkmanager.CustomerGatewayAssociationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfnetworkmanager.FindCustomerGatewayAssociationByTwoPartKey(context.TODO(), conn, globalNetworkID, customerGatewayARN) + + if err != nil { + return err + } + + return nil + } +} + +func testAccCustomerGatewayAssociationConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_customer_gateway" "test" { + bgp_asn = 65534 + ip_address = "12.1.2.3" + type = "ipsec.1" + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_vpn_connection" "test" { + customer_gateway_id = aws_customer_gateway.test.id + transit_gateway_id = aws_ec2_transit_gateway.test.id + type = aws_customer_gateway.test.type + static_routes_only = true + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_transit_gateway_registration" "test" { + global_network_id = aws_networkmanager_global_network.test.id + transit_gateway_arn = aws_ec2_transit_gateway.test.arn + + depends_on = [aws_vpn_connection.test] +} + +resource "aws_networkmanager_customer_gateway_association" "test" { + global_network_id = aws_networkmanager_global_network.test.id + customer_gateway_arn = aws_customer_gateway.test.arn + device_id = aws_networkmanager_device.test.id + + depends_on = [aws_networkmanager_transit_gateway_registration.test] +} +`, rName) +} diff --git a/internal/service/networkmanager/device.go b/internal/service/networkmanager/device.go new file mode 100644 index 00000000000..f767fdd45e1 --- /dev/null +++ b/internal/service/networkmanager/device.go @@ -0,0 +1,515 @@ +package networkmanager + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceDevice() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceDeviceCreate, + ReadWithoutTimeout: resourceDeviceRead, + UpdateWithoutTimeout: resourceDeviceUpdate, + DeleteWithoutTimeout: resourceDeviceDelete, + + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parsedARN, err := arn.Parse(d.Id()) + + if err != nil { + return nil, fmt.Errorf("error parsing ARN (%s): %w", d.Id(), err) + } + + // See https://docs.aws.amazon.com/service-authorization/latest/reference/list_networkmanager.html#networkmanager-resources-for-iam-policies. + resourceParts := strings.Split(parsedARN.Resource, "/") + + if actual, expected := len(resourceParts), 3; actual < expected { + return nil, fmt.Errorf("expected at least %d resource parts in ARN (%s), got: %d", expected, d.Id(), actual) + } + + d.SetId(resourceParts[2]) + d.Set("global_network_id", resourceParts[1]) + + return []*schema.ResourceData{d}, nil + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "aws_location": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidARN, + ConflictsWith: []string{"aws_location.0.zone"}, + }, + "zone": { + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"aws_location.0.subnet_arn"}, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "location": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "latitude": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "longitude": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + }, + }, + }, + "model": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 128), + }, + "serial_number": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 128), + }, + "site_id": { + Type: schema.TypeString, + Optional: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "vendor": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 128), + }, + }, + } +} + +func resourceDeviceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + globalNetworkID := d.Get("global_network_id").(string) + + input := &networkmanager.CreateDeviceInput{ + GlobalNetworkId: aws.String(globalNetworkID), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("aws_location"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.AWSLocation = expandAWSLocation(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("location"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Location = expandLocation(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("model"); ok { + input.Model = aws.String(v.(string)) + } + + if v, ok := d.GetOk("serial_number"); ok { + input.SerialNumber = aws.String(v.(string)) + } + + if v, ok := d.GetOk("site_id"); ok { + input.SiteId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("type"); ok { + input.Type = aws.String(v.(string)) + } + + if v, ok := d.GetOk("vendor"); ok { + input.Vendor = aws.String(v.(string)) + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + log.Printf("[DEBUG] Creating Network Manager Device: %s", input) + output, err := conn.CreateDeviceWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating Network Manager Device: %s", err) + } + + d.SetId(aws.StringValue(output.Device.DeviceId)) + + if _, err := waitDeviceCreated(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Device (%s) create: %s", d.Id(), err) + } + + return resourceDeviceRead(ctx, d, meta) +} + +func resourceDeviceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + device, err := FindDeviceByTwoPartKey(ctx, conn, globalNetworkID, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Device %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Device (%s): %s", d.Id(), err) + } + + d.Set("arn", device.DeviceArn) + if device.AWSLocation != nil { + if err := d.Set("aws_location", []interface{}{flattenAWSLocation(device.AWSLocation)}); err != nil { + return diag.Errorf("error setting aws_location: %s", err) + } + } else { + d.Set("aws_location", nil) + } + d.Set("description", device.Description) + d.Set("global_network_id", device.GlobalNetworkId) + if device.Location != nil { + if err := d.Set("location", []interface{}{flattenLocation(device.Location)}); err != nil { + return diag.Errorf("error setting location: %s", err) + } + } else { + d.Set("location", nil) + } + d.Set("model", device.Model) + d.Set("serial_number", device.SerialNumber) + d.Set("site_id", device.SiteId) + d.Set("type", device.Type) + d.Set("vendor", device.Vendor) + + tags := KeyValueTags(device.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("error setting tags_all: %s", err) + } + + return nil +} + +func resourceDeviceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + if d.HasChangesExcept("tags", "tags_all") { + globalNetworkID := d.Get("global_network_id").(string) + input := &networkmanager.UpdateDeviceInput{ + Description: aws.String(d.Get("description").(string)), + DeviceId: aws.String(d.Id()), + GlobalNetworkId: aws.String(globalNetworkID), + Model: aws.String(d.Get("model").(string)), + SerialNumber: aws.String(d.Get("serial_number").(string)), + SiteId: aws.String(d.Get("site_id").(string)), + Type: aws.String(d.Get("type").(string)), + Vendor: aws.String(d.Get("vendor").(string)), + } + + if v, ok := d.GetOk("aws_location"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.AWSLocation = expandAWSLocation(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("location"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Location = expandLocation(v.([]interface{})[0].(map[string]interface{})) + } + + log.Printf("[DEBUG] Updating Network Manager Device: %s", input) + _, err := conn.UpdateDeviceWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error updating Network Manager Device (%s): %s", d.Id(), err) + } + + if _, err := waitDeviceUpdated(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("error waiting for Network Manager Device (%s) update: %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("error updating Network Manager Device (%s) tags: %s", d.Id(), err) + } + } + + return resourceDeviceRead(ctx, d, meta) +} + +func resourceDeviceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID := d.Get("global_network_id").(string) + + log.Printf("[DEBUG] Deleting Network Manager Device: %s", d.Id()) + _, err := conn.DeleteDeviceWithContext(ctx, &networkmanager.DeleteDeviceInput{ + GlobalNetworkId: aws.String(globalNetworkID), + DeviceId: aws.String(d.Id()), + }) + + if globalNetworkIDNotFoundError(err) || tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting Network Manager Device (%s): %s", d.Id(), err) + } + + if _, err := waitDeviceDeleted(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("error waiting for Network Manager Device (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindDevice(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetDevicesInput) (*networkmanager.Device, error) { + output, err := FindDevices(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindDevices(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetDevicesInput) ([]*networkmanager.Device, error) { + var output []*networkmanager.Device + + err := conn.GetDevicesPagesWithContext(ctx, input, func(page *networkmanager.GetDevicesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Devices { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if globalNetworkIDNotFoundError(err) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindDeviceByTwoPartKey(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, deviceID string) (*networkmanager.Device, error) { + input := &networkmanager.GetDevicesInput{ + DeviceIds: aws.StringSlice([]string{deviceID}), + GlobalNetworkId: aws.String(globalNetworkID), + } + + output, err := FindDevice(ctx, conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != globalNetworkID || aws.StringValue(output.DeviceId) != deviceID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusDeviceState(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, deviceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindDeviceByTwoPartKey(ctx, conn, globalNetworkID, deviceID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func waitDeviceCreated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, deviceID string, timeout time.Duration) (*networkmanager.Device, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.DeviceStatePending}, + Target: []string{networkmanager.DeviceStateAvailable}, + Timeout: timeout, + Refresh: statusDeviceState(ctx, conn, globalNetworkID, deviceID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Device); ok { + return output, err + } + + return nil, err +} + +func waitDeviceDeleted(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, deviceID string, timeout time.Duration) (*networkmanager.Device, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.DeviceStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusDeviceState(ctx, conn, globalNetworkID, deviceID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Device); ok { + return output, err + } + + return nil, err +} + +func waitDeviceUpdated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, deviceID string, timeout time.Duration) (*networkmanager.Device, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.DeviceStateUpdating}, + Target: []string{networkmanager.DeviceStateAvailable}, + Timeout: timeout, + Refresh: statusDeviceState(ctx, conn, globalNetworkID, deviceID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Device); ok { + return output, err + } + + return nil, err +} + +func expandAWSLocation(tfMap map[string]interface{}) *networkmanager.AWSLocation { + if tfMap == nil { + return nil + } + + apiObject := &networkmanager.AWSLocation{} + + if v, ok := tfMap["subnet_arn"].(string); ok { + apiObject.SubnetArn = aws.String(v) + } + + if v, ok := tfMap["zone"].(string); ok { + apiObject.Zone = aws.String(v) + } + + return apiObject +} + +func flattenAWSLocation(apiObject *networkmanager.AWSLocation) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.SubnetArn; v != nil { + tfMap["subnet_arn"] = aws.StringValue(v) + } + + if v := apiObject.Zone; v != nil { + tfMap["zone"] = aws.StringValue(v) + } + + return tfMap +} diff --git a/internal/service/networkmanager/device_data_source.go b/internal/service/networkmanager/device_data_source.go new file mode 100644 index 00000000000..2c3f6011848 --- /dev/null +++ b/internal/service/networkmanager/device_data_source.go @@ -0,0 +1,135 @@ +package networkmanager + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceDevice() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceDeviceRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "aws_location": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet_arn": { + Type: schema.TypeString, + Computed: true, + }, + "zone": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "device_id": { + Type: schema.TypeString, + Required: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "location": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Computed: true, + }, + "latitude": { + Type: schema.TypeString, + Computed: true, + }, + "longitude": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "model": { + Type: schema.TypeString, + Computed: true, + }, + "serial_number": { + Type: schema.TypeString, + Computed: true, + }, + "site_id": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), + "type": { + Type: schema.TypeString, + Computed: true, + }, + "vendor": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceDeviceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + deviceID := d.Get("device_id").(string) + device, err := FindDeviceByTwoPartKey(ctx, conn, globalNetworkID, deviceID) + + if err != nil { + return diag.Errorf("error reading Network Manager Device (%s): %s", deviceID, err) + } + + d.SetId(deviceID) + d.Set("arn", device.DeviceArn) + if device.AWSLocation != nil { + if err := d.Set("aws_location", []interface{}{flattenAWSLocation(device.AWSLocation)}); err != nil { + return diag.Errorf("error setting aws_location: %s", err) + } + } else { + d.Set("aws_location", nil) + } + d.Set("description", device.Description) + d.Set("device_id", device.DeviceId) + if device.Location != nil { + if err := d.Set("location", []interface{}{flattenLocation(device.Location)}); err != nil { + return diag.Errorf("error setting location: %s", err) + } + } else { + d.Set("location", nil) + } + d.Set("model", device.Model) + d.Set("serial_number", device.SerialNumber) + d.Set("site_id", device.SiteId) + d.Set("type", device.Type) + d.Set("vendor", device.Vendor) + + if err := d.Set("tags", KeyValueTags(device.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + return nil +} diff --git a/internal/service/networkmanager/device_data_source_test.go b/internal/service/networkmanager/device_data_source_test.go new file mode 100644 index 00000000000..50785ab5b80 --- /dev/null +++ b/internal/service/networkmanager/device_data_source_test.go @@ -0,0 +1,76 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerDeviceDataSource_basic(t *testing.T) { + dataSourceName := "data.aws_networkmanager_device.test" + resourceName := "aws_networkmanager_device.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccDeviceDataSourceConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "aws_location.#", resourceName, "aws_location.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "device_id", resourceName, "id"), + resource.TestCheckResourceAttrPair(dataSourceName, "global_network_id", resourceName, "global_network_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "location.#", resourceName, "location.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "model", resourceName, "model"), + resource.TestCheckResourceAttrPair(dataSourceName, "site_id", resourceName, "site_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + resource.TestCheckResourceAttrPair(dataSourceName, "type", resourceName, "type"), + resource.TestCheckResourceAttrPair(dataSourceName, "vendor", resourceName, "vendor"), + ), + }, + }, + }) +} + +func testAccDeviceDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "description1" + model = "model1" + serial_number = "sn1" + type = "type1" + vendor = "vendor1" + + location { + address = "Address 1" + latitude = "1.1" + longitude = "-1.1" + } + + tags = { + Name = %[1]q + } +} + +data "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test.id +} +`, rName) +} diff --git a/internal/service/networkmanager/device_test.go b/internal/service/networkmanager/device_test.go new file mode 100644 index 00000000000..e0f867d668e --- /dev/null +++ b/internal/service/networkmanager/device_test.go @@ -0,0 +1,495 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerDevice_basic(t *testing.T) { + resourceName := "aws_networkmanager_device.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckDeviceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDeviceConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDeviceExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "aws_location.#", "0"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "location.#", "0"), + resource.TestCheckResourceAttr(resourceName, "model", ""), + resource.TestCheckResourceAttr(resourceName, "serial_number", ""), + resource.TestCheckResourceAttr(resourceName, "site_id", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "type", ""), + resource.TestCheckResourceAttr(resourceName, "vendor", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccDeviceImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkManagerDevice_disappears(t *testing.T) { + resourceName := "aws_networkmanager_device.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckDeviceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDeviceConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDeviceExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceDevice(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccNetworkManagerDevice_tags(t *testing.T) { + resourceName := "aws_networkmanager_device.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckDeviceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDeviceConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDeviceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccDeviceImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccDeviceConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDeviceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccDeviceConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckDeviceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccNetworkManagerDevice_allAttributes(t *testing.T) { + resourceName := "aws_networkmanager_device.test" + site1ResourceName := "aws_networkmanager_site.test1" + site2ResourceName := "aws_networkmanager_site.test2" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckDeviceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDeviceAllAttributesConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDeviceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + resource.TestCheckResourceAttr(resourceName, "location.#", "1"), + resource.TestCheckResourceAttr(resourceName, "location.0.address", "Address 1"), + resource.TestCheckResourceAttr(resourceName, "location.0.latitude", "1.1"), + resource.TestCheckResourceAttr(resourceName, "location.0.longitude", "-1.1"), + resource.TestCheckResourceAttr(resourceName, "model", "model1"), + resource.TestCheckResourceAttr(resourceName, "serial_number", "sn1"), + resource.TestCheckResourceAttrPair(resourceName, "site_id", site1ResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "type", "type1"), + resource.TestCheckResourceAttr(resourceName, "vendor", "vendor1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccDeviceImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccDeviceAllAttributesUpdatedConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDeviceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + resource.TestCheckResourceAttr(resourceName, "location.#", "1"), + resource.TestCheckResourceAttr(resourceName, "location.0.address", "Address 2"), + resource.TestCheckResourceAttr(resourceName, "location.0.latitude", "22"), + resource.TestCheckResourceAttr(resourceName, "location.0.longitude", "-22"), + resource.TestCheckResourceAttr(resourceName, "model", "model2"), + resource.TestCheckResourceAttr(resourceName, "serial_number", "sn2"), + resource.TestCheckResourceAttrPair(resourceName, "site_id", site2ResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "type", "type2"), + resource.TestCheckResourceAttr(resourceName, "vendor", "vendor2"), + ), + }, + }, + }) +} + +func TestAccNetworkManagerDevice_awsLocation(t *testing.T) { + resourceName := "aws_networkmanager_device.test" + subnetResourceName := "aws_subnet.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckDeviceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDeviceAWSLocationConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDeviceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "aws_location.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "aws_location.0.subnet_arn", subnetResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "aws_location.0.zone", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccDeviceImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccDeviceAWSLocationUpdatedConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDeviceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "aws_location.#", "1"), + resource.TestCheckResourceAttr(resourceName, "aws_location.0.subnet_arn", ""), + resource.TestCheckResourceAttrPair(resourceName, "aws_location.0.zone", subnetResourceName, "availability_zone"), + ), + }, + }, + }) +} + +func testAccCheckDeviceDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_device" { + continue + } + + _, err := tfnetworkmanager.FindDeviceByTwoPartKey(context.TODO(), conn, rs.Primary.Attributes["global_network_id"], rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Device %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckDeviceExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Device ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + _, err := tfnetworkmanager.FindDeviceByTwoPartKey(context.TODO(), conn, rs.Primary.Attributes["global_network_id"], rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccDeviceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id +} +`, rName) +} + +func testAccDeviceConfigTags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccDeviceConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccDeviceAllAttributesConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test2" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "description1" + model = "model1" + serial_number = "sn1" + site_id = aws_networkmanager_site.test1.id + type = "type1" + vendor = "vendor1" + + location { + address = "Address 1" + latitude = "1.1" + longitude = "-1.1" + } + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccDeviceAllAttributesUpdatedConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test2" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + description = "description2" + model = "model2" + serial_number = "sn2" + site_id = aws_networkmanager_site.test2.id + type = "type2" + vendor = "vendor2" + + location { + address = "Address 2" + latitude = "22" + longitude = "-22" + } + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccDeviceAWSLocationConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + aws_location { + subnet_arn = aws_subnet.test.arn + } + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccDeviceAWSLocationUpdatedConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + aws_location { + zone = aws_subnet.test.availability_zone + } + + tags = { + Name = %[1]q + } +} +`, rName)) +} + +func testAccDeviceImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + + return rs.Primary.Attributes["arn"], nil + } +} diff --git a/internal/service/networkmanager/devices_data_source.go b/internal/service/networkmanager/devices_data_source.go new file mode 100644 index 00000000000..0e65de24e41 --- /dev/null +++ b/internal/service/networkmanager/devices_data_source.go @@ -0,0 +1,72 @@ +package networkmanager + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceDevices() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceDevicesRead, + + Schema: map[string]*schema.Schema{ + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "site_id": { + Type: schema.TypeString, + Optional: true, + }, + "tags": tftags.TagsSchema(), + }, + } +} + +func dataSourceDevicesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tagsToMatch := tftags.New(d.Get("tags").(map[string]interface{})).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + input := &networkmanager.GetDevicesInput{ + GlobalNetworkId: aws.String(d.Get("global_network_id").(string)), + } + + if v, ok := d.GetOk("site_id"); ok { + input.SiteId = aws.String(v.(string)) + } + + output, err := FindDevices(ctx, conn, input) + + if err != nil { + return diag.Errorf("error listing Network Manager Devices: %s", err) + } + + var deviceIDs []string + + for _, v := range output { + if len(tagsToMatch) > 0 { + if !KeyValueTags(v.Tags).ContainsAll(tagsToMatch) { + continue + } + } + + deviceIDs = append(deviceIDs, aws.StringValue(v.DeviceId)) + } + + d.SetId(meta.(*conns.AWSClient).Region) + d.Set("ids", deviceIDs) + + return nil +} diff --git a/internal/service/networkmanager/devices_data_source_test.go b/internal/service/networkmanager/devices_data_source_test.go new file mode 100644 index 00000000000..6d53ba0ef9b --- /dev/null +++ b/internal/service/networkmanager/devices_data_source_test.go @@ -0,0 +1,70 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerDevicesDataSource_basic(t *testing.T) { + dataSourceAllName := "data.aws_networkmanager_devices.all" + dataSourceByTagsName := "data.aws_networkmanager_devices.by_tags" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccDevicesDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue(dataSourceAllName, "ids.#", "1"), + resource.TestCheckResourceAttr(dataSourceByTagsName, "ids.#", "1"), + ), + }, + }, + }) +} + +func testAccDevicesDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test2" { + global_network_id = aws_networkmanager_global_network.test.id +} + +data "aws_networkmanager_devices" "all" { + global_network_id = aws_networkmanager_global_network.test.id + + depends_on = [aws_networkmanager_device.test1, aws_networkmanager_device.test2] +} + +data "aws_networkmanager_devices" "by_tags" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_networkmanager_device.test1, aws_networkmanager_device.test2] +} +`, rName) +} diff --git a/internal/service/networkmanager/errors.go b/internal/service/networkmanager/errors.go new file mode 100644 index 00000000000..a8ee8e1a4eb --- /dev/null +++ b/internal/service/networkmanager/errors.go @@ -0,0 +1,40 @@ +package networkmanager + +import ( + "errors" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" +) + +// resourceNotFoundExceptionResourceIDEquals returns true if the error matches all these conditions: +// * err is of type networkmanager.ResourceNotFoundException +// * ResourceNotFoundException.ResourceId equals resourceID +func resourceNotFoundExceptionResourceIDEquals(err error, resourceID string) bool { + var resourceNotFoundException *networkmanager.ResourceNotFoundException + + if errors.As(err, &resourceNotFoundException) && aws.StringValue(resourceNotFoundException.ResourceId) == resourceID { + return true + } + + return false +} + +// validationExceptionMessageContains returns true if the error matches all these conditions: +// * err is of type networkmanager.ValidationException +// * ValidationException.Reason equals reason +// * ValidationException.Fields.Message contains message +func validationExceptionMessageContains(err error, reason string, message string) bool { + var validationException *networkmanager.ValidationException + + if errors.As(err, &validationException) && aws.StringValue(validationException.Reason) == reason { + for _, v := range validationException.Fields { + if strings.Contains(aws.StringValue(v.Message), message) { + return true + } + } + } + + return false +} diff --git a/internal/service/networkmanager/global_network.go b/internal/service/networkmanager/global_network.go new file mode 100644 index 00000000000..37d88fea130 --- /dev/null +++ b/internal/service/networkmanager/global_network.go @@ -0,0 +1,428 @@ +package networkmanager + +import ( + "context" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceGlobalNetwork() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceGlobalNetworkCreate, + ReadWithoutTimeout: resourceGlobalNetworkRead, + UpdateWithoutTimeout: resourceGlobalNetworkUpdate, + DeleteWithoutTimeout: resourceGlobalNetworkDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + } +} + +func resourceGlobalNetworkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + input := &networkmanager.CreateGlobalNetworkInput{} + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + log.Printf("[DEBUG] Creating Network Manager Global Network: %s", input) + output, err := conn.CreateGlobalNetworkWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating Network Manager Global Network: %s", err) + } + + d.SetId(aws.StringValue(output.GlobalNetwork.GlobalNetworkId)) + + if _, err := waitGlobalNetworkCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Global Network (%s) create: %s", d.Id(), err) + } + + return resourceGlobalNetworkRead(ctx, d, meta) +} + +func resourceGlobalNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetwork, err := FindGlobalNetworkByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Global Network %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Global Network (%s): %s", d.Id(), err) + } + + d.Set("arn", globalNetwork.GlobalNetworkArn) + d.Set("description", globalNetwork.Description) + + tags := KeyValueTags(globalNetwork.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("error setting tags_all: %s", err) + } + + return nil +} + +func resourceGlobalNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + if d.HasChangesExcept("tags", "tags_all") { + input := &networkmanager.UpdateGlobalNetworkInput{ + Description: aws.String(d.Get("description").(string)), + GlobalNetworkId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Updating Network Manager Global Network: %s", input) + _, err := conn.UpdateGlobalNetworkWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error updating Network Manager Global Network (%s): %s", d.Id(), err) + } + + if _, err := waitGlobalNetworkUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("error waiting for Network Manager Global Network (%s) update: %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("error updating Network Manager Global Network (%s) tags: %s", d.Id(), err) + } + } + + return resourceGlobalNetworkRead(ctx, d, meta) +} + +func resourceGlobalNetworkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + if diags := disassociateCustomerGateways(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); diags.HasError() { + return diags + } + + if diags := disassociateTransitGatewayConnectPeers(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); diags.HasError() { + return diags + } + + if diags := deregisterTransitGateways(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); diags.HasError() { + return diags + } + + log.Printf("[DEBUG] Deleting Network Manager Global Network: %s", d.Id()) + _, err := tfresource.RetryWhenContext(ctx, globalNetworkValidationExceptionTimeout, + func() (interface{}, error) { + return conn.DeleteGlobalNetworkWithContext(ctx, &networkmanager.DeleteGlobalNetworkInput{ + GlobalNetworkId: aws.String(d.Id()), + }) + }, + func(err error) (bool, error) { + if tfawserr.ErrMessageContains(err, networkmanager.ErrCodeValidationException, "cannot be deleted due to existing devices, sites, or links") { + return true, err + } + + return false, err + }, + ) + + if tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting Network Manager Global Network (%s): %s", d.Id(), err) + } + + if _, err := waitGlobalNetworkDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("error waiting for Network Manager Global Network (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func deregisterTransitGateways(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID string, timeout time.Duration) diag.Diagnostics { + output, err := FindTransitGatewayRegistrations(ctx, conn, &networkmanager.GetTransitGatewayRegistrationsInput{ + GlobalNetworkId: aws.String(globalNetworkID), + }) + + if tfresource.NotFound(err) { + err = nil + } + + if err != nil { + return diag.Errorf("error listing Network Manager Transit Gateway Registrations (%s): %s", globalNetworkID, err) + } + + var diags diag.Diagnostics + + for _, v := range output { + err := deregisterTransitGateway(ctx, conn, globalNetworkID, aws.StringValue(v.TransitGatewayArn), timeout) + + if err != nil { + diags = append(diags, diag.FromErr(err)...) + } + } + + if diags.HasError() { + return diags + } + + return nil +} + +func disassociateCustomerGateways(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID string, timeout time.Duration) diag.Diagnostics { + output, err := FindCustomerGatewayAssociations(ctx, conn, &networkmanager.GetCustomerGatewayAssociationsInput{ + GlobalNetworkId: aws.String(globalNetworkID), + }) + + if tfresource.NotFound(err) { + err = nil + } + + if err != nil { + return diag.Errorf("error listing Network Manager Customer Gateway Associations (%s): %s", globalNetworkID, err) + } + + var diags diag.Diagnostics + + for _, v := range output { + err := disassociateCustomerGateway(ctx, conn, globalNetworkID, aws.StringValue(v.CustomerGatewayArn), timeout) + + if err != nil { + diags = append(diags, diag.FromErr(err)...) + } + } + + if diags.HasError() { + return diags + } + + return nil +} + +func disassociateTransitGatewayConnectPeers(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID string, timeout time.Duration) diag.Diagnostics { + output, err := FindTransitGatewayConnectPeerAssociations(ctx, conn, &networkmanager.GetTransitGatewayConnectPeerAssociationsInput{ + GlobalNetworkId: aws.String(globalNetworkID), + }) + + if tfresource.NotFound(err) { + err = nil + } + + if err != nil { + return diag.Errorf("error listing Network Manager Transit Gateway Connect Peer Associations (%s): %s", globalNetworkID, err) + } + + var diags diag.Diagnostics + + for _, v := range output { + err := disassociateTransitGatewayConnectPeer(ctx, conn, globalNetworkID, aws.StringValue(v.TransitGatewayConnectPeerArn), timeout) + + if err != nil { + diags = append(diags, diag.FromErr(err)...) + } + } + + if diags.HasError() { + return diags + } + + return nil +} + +func globalNetworkIDNotFoundError(err error) bool { + return validationExceptionMessageContains(err, networkmanager.ValidationExceptionReasonFieldValidationFailed, "Global network not found") +} + +func FindGlobalNetwork(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.DescribeGlobalNetworksInput) (*networkmanager.GlobalNetwork, error) { + output, err := FindGlobalNetworks(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindGlobalNetworks(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.DescribeGlobalNetworksInput) ([]*networkmanager.GlobalNetwork, error) { + var output []*networkmanager.GlobalNetwork + + err := conn.DescribeGlobalNetworksPagesWithContext(ctx, input, func(page *networkmanager.DescribeGlobalNetworksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.GlobalNetworks { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindGlobalNetworkByID(ctx context.Context, conn *networkmanager.NetworkManager, id string) (*networkmanager.GlobalNetwork, error) { + input := &networkmanager.DescribeGlobalNetworksInput{ + GlobalNetworkIds: aws.StringSlice([]string{id}), + } + + output, err := FindGlobalNetwork(ctx, conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusGlobalNetworkState(ctx context.Context, conn *networkmanager.NetworkManager, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindGlobalNetworkByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func waitGlobalNetworkCreated(ctx context.Context, conn *networkmanager.NetworkManager, id string, timeout time.Duration) (*networkmanager.GlobalNetwork, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.GlobalNetworkStatePending}, + Target: []string{networkmanager.GlobalNetworkStateAvailable}, + Timeout: timeout, + Refresh: statusGlobalNetworkState(ctx, conn, id), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.GlobalNetwork); ok { + return output, err + } + + return nil, err +} + +func waitGlobalNetworkDeleted(ctx context.Context, conn *networkmanager.NetworkManager, id string, timeout time.Duration) (*networkmanager.GlobalNetwork, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.GlobalNetworkStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusGlobalNetworkState(ctx, conn, id), + NotFoundChecks: 1, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.GlobalNetwork); ok { + return output, err + } + + return nil, err +} + +func waitGlobalNetworkUpdated(ctx context.Context, conn *networkmanager.NetworkManager, id string, timeout time.Duration) (*networkmanager.GlobalNetwork, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.GlobalNetworkStateUpdating}, + Target: []string{networkmanager.GlobalNetworkStateAvailable}, + Timeout: timeout, + Refresh: statusGlobalNetworkState(ctx, conn, id), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.GlobalNetwork); ok { + return output, err + } + + return nil, err +} + +const ( + globalNetworkValidationExceptionTimeout = 2 * time.Minute +) diff --git a/internal/service/networkmanager/global_network_data_source.go b/internal/service/networkmanager/global_network_data_source.go new file mode 100644 index 00000000000..8bdee16dbea --- /dev/null +++ b/internal/service/networkmanager/global_network_data_source.go @@ -0,0 +1,55 @@ +package networkmanager + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceGlobalNetwork() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceGlobalNetworkRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +func dataSourceGlobalNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + globalNetwork, err := FindGlobalNetworkByID(ctx, conn, globalNetworkID) + + if err != nil { + return diag.Errorf("error reading Network Manager Global Network (%s): %s", globalNetworkID, err) + } + + d.SetId(globalNetworkID) + d.Set("arn", globalNetwork.GlobalNetworkArn) + d.Set("description", globalNetwork.Description) + d.Set("global_network_id", globalNetwork.GlobalNetworkId) + + if err := d.Set("tags", KeyValueTags(globalNetwork.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + return nil +} diff --git a/internal/service/networkmanager/global_network_data_source_test.go b/internal/service/networkmanager/global_network_data_source_test.go new file mode 100644 index 00000000000..f9b46911d78 --- /dev/null +++ b/internal/service/networkmanager/global_network_data_source_test.go @@ -0,0 +1,50 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerGlobalNetworkDataSource_basic(t *testing.T) { + dataSourceName := "data.aws_networkmanager_global_network.test" + resourceName := "aws_networkmanager_global_network.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccGlobalNetworkDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "global_network_id", resourceName, "id"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + ), + }, + }, + }) +} + +func testAccGlobalNetworkDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + description = "test" + + tags = { + Name = %[1]q + } +} + +data "aws_networkmanager_global_network" "test" { + global_network_id = aws_networkmanager_global_network.test.id +} +`, rName) +} diff --git a/internal/service/networkmanager/global_network_test.go b/internal/service/networkmanager/global_network_test.go new file mode 100644 index 00000000000..03491edb683 --- /dev/null +++ b/internal/service/networkmanager/global_network_test.go @@ -0,0 +1,221 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerGlobalNetwork_basic(t *testing.T) { + resourceName := "aws_networkmanager_global_network.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGlobalNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGlobalNetworkConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalNetworkExists(resourceName), + acctest.MatchResourceAttrGlobalARN(resourceName, "arn", "networkmanager", regexp.MustCompile(`global-network/global-network-.+`)), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkManagerGlobalNetwork_disappears(t *testing.T) { + resourceName := "aws_networkmanager_global_network.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGlobalNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGlobalNetworkConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalNetworkExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceGlobalNetwork(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccNetworkManagerGlobalNetwork_tags(t *testing.T) { + resourceName := "aws_networkmanager_global_network.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGlobalNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGlobalNetworkConfigTags1("key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalNetworkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccGlobalNetworkConfigTags2("key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalNetworkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccGlobalNetworkConfigTags1("key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalNetworkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccNetworkManagerGlobalNetwork_description(t *testing.T) { + resourceName := "aws_networkmanager_global_network.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckGlobalNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGlobalNetworkDescriptionConfig("description1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalNetworkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccGlobalNetworkDescriptionConfig("description2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalNetworkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + ), + }, + }, + }) +} + +func testAccCheckGlobalNetworkDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_global_network" { + continue + } + + _, err := tfnetworkmanager.FindGlobalNetworkByID(context.TODO(), conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Global Network %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckGlobalNetworkExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Global Network ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + _, err := tfnetworkmanager.FindGlobalNetworkByID(context.TODO(), conn, rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccGlobalNetworkConfig() string { + return ` +resource "aws_networkmanager_global_network" "test" {} +` +} + +func testAccGlobalNetworkConfigTags1(tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + %[1]q = %[2]q + } +} +`, tagKey1, tagValue1) +} + +func testAccGlobalNetworkConfigTags2(tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + %[1]q = %[2]q + %[3]q = %[4]q + } +} +`, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccGlobalNetworkDescriptionConfig(description string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + description = %[1]q +} +`, description) +} diff --git a/internal/service/networkmanager/global_networks_data_source.go b/internal/service/networkmanager/global_networks_data_source.go new file mode 100644 index 00000000000..21651d3cc04 --- /dev/null +++ b/internal/service/networkmanager/global_networks_data_source.go @@ -0,0 +1,56 @@ +package networkmanager + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceGlobalNetworks() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceGlobalNetworksRead, + + Schema: map[string]*schema.Schema{ + "ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": tftags.TagsSchema(), + }, + } +} + +func dataSourceGlobalNetworksRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tagsToMatch := tftags.New(d.Get("tags").(map[string]interface{})).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + output, err := FindGlobalNetworks(ctx, conn, &networkmanager.DescribeGlobalNetworksInput{}) + + if err != nil { + return diag.Errorf("error listing Network Manager Global Networks: %s", err) + } + + var globalNetworkIDs []string + + for _, v := range output { + if len(tagsToMatch) > 0 { + if !KeyValueTags(v.Tags).ContainsAll(tagsToMatch) { + continue + } + } + + globalNetworkIDs = append(globalNetworkIDs, aws.StringValue(v.GlobalNetworkId)) + } + + d.SetId(meta.(*conns.AWSClient).Region) + d.Set("ids", globalNetworkIDs) + + return nil +} diff --git a/internal/service/networkmanager/global_networks_data_source_test.go b/internal/service/networkmanager/global_networks_data_source_test.go new file mode 100644 index 00000000000..50622e24266 --- /dev/null +++ b/internal/service/networkmanager/global_networks_data_source_test.go @@ -0,0 +1,60 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerGlobalNetworksDataSource_basic(t *testing.T) { + dataSourceAllName := "data.aws_networkmanager_global_networks.all" + dataSourceByTagsName := "data.aws_networkmanager_global_networks.by_tags" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccGlobalNetworksDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue(dataSourceAllName, "ids.#", "1"), + resource.TestCheckResourceAttr(dataSourceByTagsName, "ids.#", "1"), + ), + }, + }, + }) +} + +func testAccGlobalNetworksDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test1" { + description = "test1" + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_global_network" "test2" { + description = "test2" +} + +data "aws_networkmanager_global_networks" "all" { + depends_on = [aws_networkmanager_global_network.test1, aws_networkmanager_global_network.test2] +} + +data "aws_networkmanager_global_networks" "by_tags" { + tags = { + Name = %[1]q + } + + depends_on = [aws_networkmanager_global_network.test1, aws_networkmanager_global_network.test2] +} +`, rName) +} diff --git a/internal/service/networkmanager/link.go b/internal/service/networkmanager/link.go new file mode 100644 index 00000000000..c633262e4b1 --- /dev/null +++ b/internal/service/networkmanager/link.go @@ -0,0 +1,447 @@ +package networkmanager + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceLink() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceLinkCreate, + ReadWithoutTimeout: resourceLinkRead, + UpdateWithoutTimeout: resourceLinkUpdate, + DeleteWithoutTimeout: resourceLinkDelete, + + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parsedARN, err := arn.Parse(d.Id()) + + if err != nil { + return nil, fmt.Errorf("error parsing ARN (%s): %w", d.Id(), err) + } + + // See https://docs.aws.amazon.com/service-authorization/latest/reference/list_networkmanager.html#networkmanager-resources-for-iam-policies. + resourceParts := strings.Split(parsedARN.Resource, "/") + + if actual, expected := len(resourceParts), 3; actual < expected { + return nil, fmt.Errorf("expected at least %d resource parts in ARN (%s), got: %d", expected, d.Id(), actual) + } + + d.SetId(resourceParts[2]) + d.Set("global_network_id", resourceParts[1]) + + return []*schema.ResourceData{d}, nil + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "bandwidth": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "download_speed": { + Type: schema.TypeInt, + Optional: true, + }, + "upload_speed": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "provider_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 128), + }, + "site_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + "type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 128), + }, + }, + } +} + +func resourceLinkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + globalNetworkID := d.Get("global_network_id").(string) + input := &networkmanager.CreateLinkInput{ + GlobalNetworkId: aws.String(globalNetworkID), + SiteId: aws.String(d.Get("site_id").(string)), + } + + if v, ok := d.GetOk("bandwidth"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Bandwidth = expandBandwidth(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("provider_name"); ok { + input.Provider = aws.String(v.(string)) + } + + if v, ok := d.GetOk("type"); ok { + input.Type = aws.String(v.(string)) + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + log.Printf("[DEBUG] Creating Network Manager Link: %s", input) + output, err := conn.CreateLinkWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating Network Manager Link: %s", err) + } + + d.SetId(aws.StringValue(output.Link.LinkId)) + + if _, err := waitLinkCreated(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Link (%s) create: %s", d.Id(), err) + } + + return resourceLinkRead(ctx, d, meta) +} + +func resourceLinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + link, err := FindLinkByTwoPartKey(ctx, conn, globalNetworkID, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Link %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Link (%s): %s", d.Id(), err) + } + + d.Set("arn", link.LinkArn) + if link.Bandwidth != nil { + if err := d.Set("bandwidth", []interface{}{flattenBandwidth(link.Bandwidth)}); err != nil { + return diag.Errorf("error setting bandwidth: %s", err) + } + } else { + d.Set("bandwidth", nil) + } + d.Set("description", link.Description) + d.Set("global_network_id", link.GlobalNetworkId) + d.Set("provider_name", link.Provider) + d.Set("site_id", link.SiteId) + d.Set("type", link.Type) + + tags := KeyValueTags(link.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("error setting tags_all: %s", err) + } + + return nil +} + +func resourceLinkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + if d.HasChangesExcept("tags", "tags_all") { + globalNetworkID := d.Get("global_network_id").(string) + input := &networkmanager.UpdateLinkInput{ + Description: aws.String(d.Get("description").(string)), + GlobalNetworkId: aws.String(globalNetworkID), + LinkId: aws.String(d.Id()), + Provider: aws.String(d.Get("provider_name").(string)), + Type: aws.String(d.Get("type").(string)), + } + + if v, ok := d.GetOk("bandwidth"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Bandwidth = expandBandwidth(v.([]interface{})[0].(map[string]interface{})) + } + + log.Printf("[DEBUG] Updating Network Manager Link: %s", input) + _, err := conn.UpdateLinkWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error updating Network Manager Link (%s): %s", d.Id(), err) + } + + if _, err := waitLinkUpdated(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("error waiting for Network Manager Link (%s) update: %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("error updating Network Manager Link (%s) tags: %s", d.Id(), err) + } + } + + return resourceLinkRead(ctx, d, meta) +} + +func resourceLinkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID := d.Get("global_network_id").(string) + + log.Printf("[DEBUG] Deleting Network Manager Link: %s", d.Id()) + _, err := conn.DeleteLinkWithContext(ctx, &networkmanager.DeleteLinkInput{ + GlobalNetworkId: aws.String(globalNetworkID), + LinkId: aws.String(d.Id()), + }) + + if globalNetworkIDNotFoundError(err) || tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting Network Manager Link (%s): %s", d.Id(), err) + } + + if _, err := waitLinkDeleted(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("error waiting for Network Manager Link (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindLink(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetLinksInput) (*networkmanager.Link, error) { + output, err := FindLinks(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindLinks(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetLinksInput) ([]*networkmanager.Link, error) { + var output []*networkmanager.Link + + err := conn.GetLinksPagesWithContext(ctx, input, func(page *networkmanager.GetLinksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Links { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if globalNetworkIDNotFoundError(err) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindLinkByTwoPartKey(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID string) (*networkmanager.Link, error) { + input := &networkmanager.GetLinksInput{ + GlobalNetworkId: aws.String(globalNetworkID), + LinkIds: aws.StringSlice([]string{linkID}), + } + + output, err := FindLink(ctx, conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != globalNetworkID || aws.StringValue(output.LinkId) != linkID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusLinkState(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindLinkByTwoPartKey(ctx, conn, globalNetworkID, linkID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func waitLinkCreated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID string, timeout time.Duration) (*networkmanager.Link, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.LinkStatePending}, + Target: []string{networkmanager.LinkStateAvailable}, + Timeout: timeout, + Refresh: statusLinkState(ctx, conn, globalNetworkID, linkID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Link); ok { + return output, err + } + + return nil, err +} + +func waitLinkDeleted(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID string, timeout time.Duration) (*networkmanager.Link, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.LinkStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusLinkState(ctx, conn, globalNetworkID, linkID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Link); ok { + return output, err + } + + return nil, err +} + +func waitLinkUpdated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID string, timeout time.Duration) (*networkmanager.Link, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.LinkStateUpdating}, + Target: []string{networkmanager.LinkStateAvailable}, + Timeout: timeout, + Refresh: statusLinkState(ctx, conn, globalNetworkID, linkID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Link); ok { + return output, err + } + + return nil, err +} + +func expandBandwidth(tfMap map[string]interface{}) *networkmanager.Bandwidth { + if tfMap == nil { + return nil + } + + apiObject := &networkmanager.Bandwidth{} + + if v, ok := tfMap["download_speed"].(int); ok && v != 0 { + apiObject.DownloadSpeed = aws.Int64(int64(v)) + } + + if v, ok := tfMap["upload_speed"].(int); ok && v != 0 { + apiObject.UploadSpeed = aws.Int64(int64(v)) + } + + return apiObject +} + +func flattenBandwidth(apiObject *networkmanager.Bandwidth) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.DownloadSpeed; v != nil { + tfMap["download_speed"] = aws.Int64Value(v) + } + + if v := apiObject.UploadSpeed; v != nil { + tfMap["upload_speed"] = aws.Int64Value(v) + } + + return tfMap +} diff --git a/internal/service/networkmanager/link_association.go b/internal/service/networkmanager/link_association.go new file mode 100644 index 00000000000..57adcc8541a --- /dev/null +++ b/internal/service/networkmanager/link_association.go @@ -0,0 +1,291 @@ +package networkmanager + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func ResourceLinkAssociation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceLinkAssociationCreate, + ReadWithoutTimeout: resourceLinkAssociationRead, + DeleteWithoutTimeout: resourceLinkAssociationDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "device_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "link_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceLinkAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID := d.Get("global_network_id").(string) + linkID := d.Get("link_id").(string) + deviceID := d.Get("device_id").(string) + id := LinkAssociationCreateResourceID(globalNetworkID, linkID, deviceID) + input := &networkmanager.AssociateLinkInput{ + DeviceId: aws.String(deviceID), + GlobalNetworkId: aws.String(globalNetworkID), + LinkId: aws.String(linkID), + } + + log.Printf("[DEBUG] Creating Network Manager Link Association: %s", input) + _, err := conn.AssociateLinkWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating Network Manager Link Association (%s): %s", id, err) + } + + d.SetId(id) + + if _, err := waitLinkAssociationCreated(ctx, conn, globalNetworkID, linkID, deviceID, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Link Association (%s) create: %s", d.Id(), err) + } + + return resourceLinkAssociationRead(ctx, d, meta) +} + +func resourceLinkAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, linkID, deviceID, err := LinkAssociationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + output, err := FindLinkAssociationByThreePartKey(ctx, conn, globalNetworkID, linkID, deviceID) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Link Association %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Link Association (%s): %s", d.Id(), err) + } + + d.Set("device_id", output.DeviceId) + d.Set("global_network_id", output.GlobalNetworkId) + d.Set("link_id", output.LinkId) + + return nil +} + +func resourceLinkAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, linkID, deviceID, err := LinkAssociationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + log.Printf("[DEBUG] Deleting Network Manager Link Association: %s", d.Id()) + _, err = conn.DisassociateLinkWithContext(ctx, &networkmanager.DisassociateLinkInput{ + DeviceId: aws.String(deviceID), + GlobalNetworkId: aws.String(globalNetworkID), + LinkId: aws.String(linkID), + }) + + if globalNetworkIDNotFoundError(err) || tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting Network Manager Link Association (%s): %s", d.Id(), err) + } + + if _, err := waitLinkAssociationDeleted(ctx, conn, globalNetworkID, linkID, deviceID, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Link Association (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindLinkAssociation(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetLinkAssociationsInput) (*networkmanager.LinkAssociation, error) { + output, err := FindLinkAssociations(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindLinkAssociations(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetLinkAssociationsInput) ([]*networkmanager.LinkAssociation, error) { + var output []*networkmanager.LinkAssociation + + err := conn.GetLinkAssociationsPagesWithContext(ctx, input, func(page *networkmanager.GetLinkAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.LinkAssociations { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if globalNetworkIDNotFoundError(err) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindLinkAssociationByThreePartKey(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID, deviceID string) (*networkmanager.LinkAssociation, error) { + input := &networkmanager.GetLinkAssociationsInput{ + DeviceId: aws.String(deviceID), + GlobalNetworkId: aws.String(globalNetworkID), + LinkId: aws.String(linkID), + } + + output, err := FindLinkAssociation(ctx, conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.LinkAssociationState); state == networkmanager.LinkAssociationStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != globalNetworkID || aws.StringValue(output.LinkId) != linkID || aws.StringValue(output.DeviceId) != deviceID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusLinkAssociationState(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID, deviceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindLinkAssociationByThreePartKey(ctx, conn, globalNetworkID, linkID, deviceID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.LinkAssociationState), nil + } +} + +func waitLinkAssociationCreated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID, deviceID string, timeout time.Duration) (*networkmanager.LinkAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.LinkAssociationStatePending}, + Target: []string{networkmanager.LinkAssociationStateAvailable}, + Timeout: timeout, + Refresh: statusLinkAssociationState(ctx, conn, globalNetworkID, linkID, deviceID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.LinkAssociation); ok { + return output, err + } + + return nil, err +} + +func waitLinkAssociationDeleted(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, linkID, deviceID string, timeout time.Duration) (*networkmanager.LinkAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.LinkAssociationStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusLinkAssociationState(ctx, conn, globalNetworkID, linkID, deviceID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.LinkAssociation); ok { + return output, err + } + + return nil, err +} + +const linkAssociationIDSeparator = "," + +func LinkAssociationCreateResourceID(globalNetworkID, linkID, deviceID string) string { + parts := []string{globalNetworkID, linkID, deviceID} + id := strings.Join(parts, linkAssociationIDSeparator) + + return id +} + +func LinkAssociationParseResourceID(id string) (string, string, string, error) { + parts := strings.Split(id, linkAssociationIDSeparator) + + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + return parts[0], parts[1], parts[2], nil + } + + return "", "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected GLOBAL-NETWORK-ID%[2]sLINK-ID%[2]sDEVICE-ID", id, linkAssociationIDSeparator) +} diff --git a/internal/service/networkmanager/link_association_test.go b/internal/service/networkmanager/link_association_test.go new file mode 100644 index 00000000000..e61c0d73533 --- /dev/null +++ b/internal/service/networkmanager/link_association_test.go @@ -0,0 +1,169 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerLinkAssociation_basic(t *testing.T) { + resourceName := "aws_networkmanager_link_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLinkAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkAssociationExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkManagerLinkAssociation_disappears(t *testing.T) { + resourceName := "aws_networkmanager_link_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLinkAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkAssociationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceLinkAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckLinkAssociationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_link_association" { + continue + } + + globalNetworkID, linkID, deviceID, err := tfnetworkmanager.LinkAssociationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfnetworkmanager.FindLinkAssociationByThreePartKey(context.TODO(), conn, globalNetworkID, linkID, deviceID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Link Association %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckLinkAssociationExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Link Association ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, linkID, deviceID, err := tfnetworkmanager.LinkAssociationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfnetworkmanager.FindLinkAssociationByThreePartKey(context.TODO(), conn, globalNetworkID, linkID, deviceID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccLinkAssociationConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 50 + upload_speed = 10 + } + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link_association" "test" { + global_network_id = aws_networkmanager_global_network.test.id + link_id = aws_networkmanager_link.test.id + device_id = aws_networkmanager_device.test.id +} +`, rName) +} diff --git a/internal/service/networkmanager/link_data_source.go b/internal/service/networkmanager/link_data_source.go new file mode 100644 index 00000000000..06d254cfe06 --- /dev/null +++ b/internal/service/networkmanager/link_data_source.go @@ -0,0 +1,99 @@ +package networkmanager + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceLink() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceLinkRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "bandwidth": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "download_speed": { + Type: schema.TypeInt, + Computed: true, + }, + "upload_speed": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "link_id": { + Type: schema.TypeString, + Required: true, + }, + "provider_name": { + Type: schema.TypeString, + Computed: true, + }, + "site_id": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchemaComputed(), + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceLinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + linkID := d.Get("link_id").(string) + link, err := FindLinkByTwoPartKey(ctx, conn, globalNetworkID, linkID) + + if err != nil { + return diag.Errorf("error reading Network Manager Link (%s): %s", linkID, err) + } + + d.SetId(linkID) + d.Set("arn", link.LinkArn) + if link.Bandwidth != nil { + if err := d.Set("bandwidth", []interface{}{flattenBandwidth(link.Bandwidth)}); err != nil { + return diag.Errorf("error setting bandwidth: %s", err) + } + } else { + d.Set("bandwidth", nil) + } + d.Set("description", link.Description) + d.Set("global_network_id", link.GlobalNetworkId) + d.Set("link_id", link.LinkId) + d.Set("provider_name", link.Provider) + d.Set("site_id", link.SiteId) + d.Set("type", link.Type) + + if err := d.Set("tags", KeyValueTags(link.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + return nil +} diff --git a/internal/service/networkmanager/link_data_source_test.go b/internal/service/networkmanager/link_data_source_test.go new file mode 100644 index 00000000000..7b9bc3ba0bd --- /dev/null +++ b/internal/service/networkmanager/link_data_source_test.go @@ -0,0 +1,80 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerLinkDataSource_basic(t *testing.T) { + dataSourceName := "data.aws_networkmanager_link.test" + resourceName := "aws_networkmanager_link.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccLinkDataSourceConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "bandwidth.#", resourceName, "bandwidth.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "global_network_id", resourceName, "global_network_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "link_id", resourceName, "id"), + resource.TestCheckResourceAttrPair(dataSourceName, "provider_name", resourceName, "provider_name"), + resource.TestCheckResourceAttrPair(dataSourceName, "site_id", resourceName, "site_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + resource.TestCheckResourceAttrPair(dataSourceName, "type", resourceName, "type"), + ), + }, + }, + }) +} + +func testAccLinkDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 50 + upload_speed = 10 + } + + description = "description1" + provider_name = "provider1" + type = "type1" + + tags = { + Name = %[1]q + } +} + +data "aws_networkmanager_link" "test" { + global_network_id = aws_networkmanager_global_network.test.id + link_id = aws_networkmanager_link.test.id +} +`, rName) +} diff --git a/internal/service/networkmanager/link_test.go b/internal/service/networkmanager/link_test.go new file mode 100644 index 00000000000..6d736f75c2f --- /dev/null +++ b/internal/service/networkmanager/link_test.go @@ -0,0 +1,384 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerLink_basic(t *testing.T) { + resourceName := "aws_networkmanager_link.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "bandwidth.#", "1"), + resource.TestCheckResourceAttr(resourceName, "bandwidth.0.download_speed", "50"), + resource.TestCheckResourceAttr(resourceName, "bandwidth.0.upload_speed", "10"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "provider_name", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "type", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccLinkImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkManagerLink_disappears(t *testing.T) { + resourceName := "aws_networkmanager_link.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceLink(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccNetworkManagerLink_tags(t *testing.T) { + resourceName := "aws_networkmanager_link.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccLinkImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccLinkConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccLinkConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckLinkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccNetworkManagerLink_allAttributes(t *testing.T) { + resourceName := "aws_networkmanager_link.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckLinkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLinkAllAttributesConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLinkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "bandwidth.#", "1"), + resource.TestCheckResourceAttr(resourceName, "bandwidth.0.download_speed", "50"), + resource.TestCheckResourceAttr(resourceName, "bandwidth.0.upload_speed", "10"), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + resource.TestCheckResourceAttr(resourceName, "provider_name", "provider1"), + resource.TestCheckResourceAttr(resourceName, "type", "type1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccLinkImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccLinkAllAttributesUpdatedConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckLinkExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "bandwidth.#", "1"), + resource.TestCheckResourceAttr(resourceName, "bandwidth.0.download_speed", "75"), + resource.TestCheckResourceAttr(resourceName, "bandwidth.0.upload_speed", "20"), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + resource.TestCheckResourceAttr(resourceName, "provider_name", "provider2"), + resource.TestCheckResourceAttr(resourceName, "type", "type2"), + ), + }, + }, + }) +} + +func testAccCheckLinkDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_link" { + continue + } + + _, err := tfnetworkmanager.FindLinkByTwoPartKey(context.TODO(), conn, rs.Primary.Attributes["global_network_id"], rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Link %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckLinkExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Link ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + _, err := tfnetworkmanager.FindLinkByTwoPartKey(context.TODO(), conn, rs.Primary.Attributes["global_network_id"], rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccLinkConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 50 + upload_speed = 10 + } +} +`, rName) +} + +func testAccLinkConfigTags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 50 + upload_speed = 10 + } + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccLinkConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 50 + upload_speed = 10 + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccLinkAllAttributesConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 50 + upload_speed = 10 + } + + description = "description1" + provider_name = "provider1" + type = "type1" + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccLinkAllAttributesUpdatedConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 75 + upload_speed = 20 + } + + description = "description2" + provider_name = "provider2" + type = "type2" + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccLinkImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + + return rs.Primary.Attributes["arn"], nil + } +} diff --git a/internal/service/networkmanager/links_data_source.go b/internal/service/networkmanager/links_data_source.go new file mode 100644 index 00000000000..c8c9502e1ad --- /dev/null +++ b/internal/service/networkmanager/links_data_source.go @@ -0,0 +1,88 @@ +package networkmanager + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceLinks() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceLinksRead, + + Schema: map[string]*schema.Schema{ + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "provider_name": { + Type: schema.TypeString, + Optional: true, + }, + "site_id": { + Type: schema.TypeString, + Optional: true, + }, + "tags": tftags.TagsSchema(), + "type": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +} + +func dataSourceLinksRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tagsToMatch := tftags.New(d.Get("tags").(map[string]interface{})).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + input := &networkmanager.GetLinksInput{ + GlobalNetworkId: aws.String(d.Get("global_network_id").(string)), + } + + if v, ok := d.GetOk("provider_name"); ok { + input.Provider = aws.String(v.(string)) + } + + if v, ok := d.GetOk("site_id"); ok { + input.SiteId = aws.String(v.(string)) + } + + if v, ok := d.GetOk("type"); ok { + input.Type = aws.String(v.(string)) + } + + output, err := FindLinks(ctx, conn, input) + + if err != nil { + return diag.Errorf("error listing Network Manager Links: %s", err) + } + + var linkIDs []string + + for _, v := range output { + if len(tagsToMatch) > 0 { + if !KeyValueTags(v.Tags).ContainsAll(tagsToMatch) { + continue + } + } + + linkIDs = append(linkIDs, aws.StringValue(v.LinkId)) + } + + d.SetId(meta.(*conns.AWSClient).Region) + d.Set("ids", linkIDs) + + return nil +} diff --git a/internal/service/networkmanager/links_data_source_test.go b/internal/service/networkmanager/links_data_source_test.go new file mode 100644 index 00000000000..a507d4bef9b --- /dev/null +++ b/internal/service/networkmanager/links_data_source_test.go @@ -0,0 +1,90 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerLinksDataSource_basic(t *testing.T) { + dataSourceAllName := "data.aws_networkmanager_links.all" + dataSourceByTagsName := "data.aws_networkmanager_links.by_tags" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccLinksDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue(dataSourceAllName, "ids.#", "1"), + resource.TestCheckResourceAttr(dataSourceByTagsName, "ids.#", "1"), + ), + }, + }, + }) +} + +func testAccLinksDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_link" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 50 + upload_speed = 10 + } +} + +resource "aws_networkmanager_link" "test2" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + bandwidth { + download_speed = 75 + upload_speed = 25 + } + + tags = { + Name = %[1]q + } +} + +data "aws_networkmanager_links" "all" { + global_network_id = aws_networkmanager_global_network.test.id + + depends_on = [aws_networkmanager_link.test1, aws_networkmanager_link.test2] +} + +data "aws_networkmanager_links" "by_tags" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_networkmanager_link.test1, aws_networkmanager_link.test2] +} +`, rName) +} diff --git a/internal/service/networkmanager/site.go b/internal/service/networkmanager/site.go new file mode 100644 index 00000000000..edcfa3647d0 --- /dev/null +++ b/internal/service/networkmanager/site.go @@ -0,0 +1,448 @@ +package networkmanager + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceSite() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceSiteCreate, + ReadWithoutTimeout: resourceSiteRead, + UpdateWithoutTimeout: resourceSiteUpdate, + DeleteWithoutTimeout: resourceSiteDelete, + + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parsedARN, err := arn.Parse(d.Id()) + + if err != nil { + return nil, fmt.Errorf("error parsing ARN (%s): %w", d.Id(), err) + } + + // See https://docs.aws.amazon.com/service-authorization/latest/reference/list_networkmanager.html#networkmanager-resources-for-iam-policies. + resourceParts := strings.Split(parsedARN.Resource, "/") + + if actual, expected := len(resourceParts), 3; actual < expected { + return nil, fmt.Errorf("expected at least %d resource parts in ARN (%s), got: %d", expected, d.Id(), actual) + } + + d.SetId(resourceParts[2]) + d.Set("global_network_id", resourceParts[1]) + + return []*schema.ResourceData{d}, nil + }, + }, + + CustomizeDiff: verify.SetTagsDiff, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "location": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "latitude": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + "longitude": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 256), + }, + }, + }, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + } +} + +func resourceSiteCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + globalNetworkID := d.Get("global_network_id").(string) + input := &networkmanager.CreateSiteInput{ + GlobalNetworkId: aws.String(globalNetworkID), + } + + if v, ok := d.GetOk("description"); ok { + input.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("location"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Location = expandLocation(v.([]interface{})[0].(map[string]interface{})) + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + log.Printf("[DEBUG] Creating Network Manager Site: %s", input) + output, err := conn.CreateSiteWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating Network Manager Site: %s", err) + } + + d.SetId(aws.StringValue(output.Site.SiteId)) + + if _, err := waitSiteCreated(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Site (%s) create: %s", d.Id(), err) + } + + return resourceSiteRead(ctx, d, meta) +} + +func resourceSiteRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + site, err := FindSiteByTwoPartKey(ctx, conn, globalNetworkID, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Site %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Site (%s): %s", d.Id(), err) + } + + d.Set("arn", site.SiteArn) + d.Set("description", site.Description) + d.Set("global_network_id", site.GlobalNetworkId) + if site.Location != nil { + if err := d.Set("location", []interface{}{flattenLocation(site.Location)}); err != nil { + return diag.Errorf("error setting location: %s", err) + } + } else { + d.Set("location", nil) + } + + tags := KeyValueTags(site.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.Errorf("error setting tags_all: %s", err) + } + + return nil +} + +func resourceSiteUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + if d.HasChangesExcept("tags", "tags_all") { + globalNetworkID := d.Get("global_network_id").(string) + input := &networkmanager.UpdateSiteInput{ + Description: aws.String(d.Get("description").(string)), + GlobalNetworkId: aws.String(globalNetworkID), + SiteId: aws.String(d.Id()), + } + + if v, ok := d.GetOk("location"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.Location = expandLocation(v.([]interface{})[0].(map[string]interface{})) + } + + log.Printf("[DEBUG] Updating Network Manager Site: %s", input) + _, err := conn.UpdateSiteWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error updating Network Manager Site (%s): %s", d.Id(), err) + } + + if _, err := waitSiteUpdated(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return diag.Errorf("error waiting for Network Manager Site (%s) update: %s", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("error updating Network Manager Site (%s) tags: %s", d.Id(), err) + } + } + + return resourceSiteRead(ctx, d, meta) +} + +func resourceSiteDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID := d.Get("global_network_id").(string) + + log.Printf("[DEBUG] Deleting Network Manager Site: %s", d.Id()) + _, err := tfresource.RetryWhenContext(ctx, siteValidationExceptionTimeout, + func() (interface{}, error) { + return conn.DeleteSiteWithContext(ctx, &networkmanager.DeleteSiteInput{ + GlobalNetworkId: aws.String(globalNetworkID), + SiteId: aws.String(d.Id()), + }) + }, + func(err error) (bool, error) { + if tfawserr.ErrMessageContains(err, networkmanager.ErrCodeValidationException, "cannot be deleted due to existing association") { + return true, err + } + + return false, err + }, + ) + + if globalNetworkIDNotFoundError(err) || tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return diag.Errorf("error deleting Network Manager Site (%s): %s", d.Id(), err) + } + + if _, err := waitSiteDeleted(ctx, conn, globalNetworkID, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return diag.Errorf("error waiting for Network Manager Site (%s) delete: %s", d.Id(), err) + } + + return nil +} + +func FindSite(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetSitesInput) (*networkmanager.Site, error) { + output, err := FindSites(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindSites(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetSitesInput) ([]*networkmanager.Site, error) { + var output []*networkmanager.Site + + err := conn.GetSitesPagesWithContext(ctx, input, func(page *networkmanager.GetSitesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Sites { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if globalNetworkIDNotFoundError(err) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindSiteByTwoPartKey(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, siteID string) (*networkmanager.Site, error) { + input := &networkmanager.GetSitesInput{ + GlobalNetworkId: aws.String(globalNetworkID), + SiteIds: aws.StringSlice([]string{siteID}), + } + + output, err := FindSite(ctx, conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != globalNetworkID || aws.StringValue(output.SiteId) != siteID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusSiteState(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, siteID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindSiteByTwoPartKey(ctx, conn, globalNetworkID, siteID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func waitSiteCreated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, siteID string, timeout time.Duration) (*networkmanager.Site, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.SiteStatePending}, + Target: []string{networkmanager.SiteStateAvailable}, + Timeout: timeout, + Refresh: statusSiteState(ctx, conn, globalNetworkID, siteID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Site); ok { + return output, err + } + + return nil, err +} + +func waitSiteDeleted(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, siteID string, timeout time.Duration) (*networkmanager.Site, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.SiteStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusSiteState(ctx, conn, globalNetworkID, siteID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Site); ok { + return output, err + } + + return nil, err +} + +func waitSiteUpdated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, siteID string, timeout time.Duration) (*networkmanager.Site, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.SiteStateUpdating}, + Target: []string{networkmanager.SiteStateAvailable}, + Timeout: timeout, + Refresh: statusSiteState(ctx, conn, globalNetworkID, siteID), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.Site); ok { + return output, err + } + + return nil, err +} + +const ( + siteValidationExceptionTimeout = 2 * time.Minute +) + +func expandLocation(tfMap map[string]interface{}) *networkmanager.Location { + if tfMap == nil { + return nil + } + + apiObject := &networkmanager.Location{} + + if v, ok := tfMap["address"].(string); ok { + apiObject.Address = aws.String(v) + } + + if v, ok := tfMap["latitude"].(string); ok { + apiObject.Latitude = aws.String(v) + } + + if v, ok := tfMap["longitude"].(string); ok { + apiObject.Longitude = aws.String(v) + } + + return apiObject +} + +func flattenLocation(apiObject *networkmanager.Location) map[string]interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if v := apiObject.Address; v != nil { + tfMap["address"] = aws.StringValue(v) + } + + if v := apiObject.Latitude; v != nil { + tfMap["latitude"] = aws.StringValue(v) + } + + if v := apiObject.Longitude; v != nil { + tfMap["longitude"] = aws.StringValue(v) + } + + return tfMap +} diff --git a/internal/service/networkmanager/site_data_source.go b/internal/service/networkmanager/site_data_source.go new file mode 100644 index 00000000000..5e6eea2538a --- /dev/null +++ b/internal/service/networkmanager/site_data_source.go @@ -0,0 +1,88 @@ +package networkmanager + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceSite() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceSiteRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "location": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Computed: true, + }, + "latitude": { + Type: schema.TypeString, + Computed: true, + }, + "longitude": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "site_id": { + Type: schema.TypeString, + Required: true, + }, + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +func dataSourceSiteRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + globalNetworkID := d.Get("global_network_id").(string) + siteID := d.Get("site_id").(string) + site, err := FindSiteByTwoPartKey(ctx, conn, globalNetworkID, siteID) + + if err != nil { + return diag.Errorf("error reading Network Manager Site (%s): %s", siteID, err) + } + + d.SetId(siteID) + d.Set("arn", site.SiteArn) + d.Set("description", site.Description) + d.Set("global_network_id", site.GlobalNetworkId) + if site.Location != nil { + if err := d.Set("location", []interface{}{flattenLocation(site.Location)}); err != nil { + return diag.Errorf("error setting location: %s", err) + } + } else { + d.Set("location", nil) + } + d.Set("site_id", site.SiteId) + + if err := d.Set("tags", KeyValueTags(site.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return diag.Errorf("error setting tags: %s", err) + } + + return nil +} diff --git a/internal/service/networkmanager/site_data_source_test.go b/internal/service/networkmanager/site_data_source_test.go new file mode 100644 index 00000000000..86e958fceaf --- /dev/null +++ b/internal/service/networkmanager/site_data_source_test.go @@ -0,0 +1,65 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerSiteDataSource_basic(t *testing.T) { + dataSourceName := "data.aws_networkmanager_site.test" + resourceName := "aws_networkmanager_site.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccSiteDataSourceConfig(rName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "description", resourceName, "description"), + resource.TestCheckResourceAttrPair(dataSourceName, "global_network_id", resourceName, "global_network_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "location.#", resourceName, "location.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "site_id", resourceName, "id"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + ), + }, + }, + }) +} + +func testAccSiteDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + description = "test" + global_network_id = aws_networkmanager_global_network.test.id + + location { + latitude = "18.0029784" + longitude = "-76.7897987" + } + + tags = { + Name = %[1]q + } +} + +data "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id +} +`, rName) +} diff --git a/internal/service/networkmanager/site_test.go b/internal/service/networkmanager/site_test.go new file mode 100644 index 00000000000..28dedc915b1 --- /dev/null +++ b/internal/service/networkmanager/site_test.go @@ -0,0 +1,363 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerSite_basic(t *testing.T) { + resourceName := "aws_networkmanager_site.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckSiteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSiteConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "location.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSiteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkManagerSite_disappears(t *testing.T) { + resourceName := "aws_networkmanager_site.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckSiteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSiteConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceSite(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccNetworkManagerSite_tags(t *testing.T) { + resourceName := "aws_networkmanager_site.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckSiteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSiteConfigTags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSiteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccSiteConfigTags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccSiteConfigTags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func TestAccNetworkManagerSite_description(t *testing.T) { + resourceName := "aws_networkmanager_site.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckSiteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSiteDescriptionConfig(rName, "description1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSiteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccSiteDescriptionConfig(rName, "description2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + ), + }, + }, + }) +} + +func TestAccNetworkManagerSite_location(t *testing.T) { + resourceName := "aws_networkmanager_site.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckSiteDestroy, + Steps: []resource.TestStep{ + { + Config: testAccSiteLocationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "location.#", "1"), + resource.TestCheckResourceAttr(resourceName, "location.0.address", "Stuart, FL"), + resource.TestCheckResourceAttr(resourceName, "location.0.latitude", "27.198"), + resource.TestCheckResourceAttr(resourceName, "location.0.longitude", "-80.253"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccSiteImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + { + Config: testAccSiteLocationUpdatedConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSiteExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "location.#", "1"), + resource.TestCheckResourceAttr(resourceName, "location.0.address", "Brisbane, QLD"), + resource.TestCheckResourceAttr(resourceName, "location.0.latitude", "-27.470"), + resource.TestCheckResourceAttr(resourceName, "location.0.longitude", "153.026"), + ), + }, + }, + }) +} + +func testAccCheckSiteDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_site" { + continue + } + + _, err := tfnetworkmanager.FindSiteByTwoPartKey(context.TODO(), conn, rs.Primary.Attributes["global_network_id"], rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Site %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckSiteExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Site ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + _, err := tfnetworkmanager.FindSiteByTwoPartKey(context.TODO(), conn, rs.Primary.Attributes["global_network_id"], rs.Primary.ID) + + if err != nil { + return err + } + + return nil + } +} + +func testAccSiteConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id +} +`, rName) +} + +func testAccSiteConfigTags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccSiteConfigTags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccSiteDescriptionConfig(rName, description string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + description = %[2]q + + tags = { + Name = %[1]q + } +} +`, rName, description) +} + +func testAccSiteLocationConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + location { + address = "Stuart, FL" + latitude = "27.198" + longitude = "-80.253" + } + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccSiteLocationUpdatedConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + location { + address = "Brisbane, QLD" + latitude = "-27.470" + longitude = "153.026" + } + + tags = { + Name = %[1]q + } +} +`, rName) +} + +func testAccSiteImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + + return rs.Primary.Attributes["arn"], nil + } +} diff --git a/internal/service/networkmanager/sites_data_source.go b/internal/service/networkmanager/sites_data_source.go new file mode 100644 index 00000000000..9f2b1f5ed91 --- /dev/null +++ b/internal/service/networkmanager/sites_data_source.go @@ -0,0 +1,62 @@ +package networkmanager + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +func DataSourceSites() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceSitesRead, + + Schema: map[string]*schema.Schema{ + "global_network_id": { + Type: schema.TypeString, + Required: true, + }, + "ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "tags": tftags.TagsSchema(), + }, + } +} + +func dataSourceSitesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tagsToMatch := tftags.New(d.Get("tags").(map[string]interface{})).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + output, err := FindSites(ctx, conn, &networkmanager.GetSitesInput{ + GlobalNetworkId: aws.String(d.Get("global_network_id").(string)), + }) + + if err != nil { + return diag.Errorf("error listing Network Manager Sites: %s", err) + } + + var siteIDs []string + + for _, v := range output { + if len(tagsToMatch) > 0 { + if !KeyValueTags(v.Tags).ContainsAll(tagsToMatch) { + continue + } + } + + siteIDs = append(siteIDs, aws.StringValue(v.SiteId)) + } + + d.SetId(meta.(*conns.AWSClient).Region) + d.Set("ids", siteIDs) + + return nil +} diff --git a/internal/service/networkmanager/sites_data_source_test.go b/internal/service/networkmanager/sites_data_source_test.go new file mode 100644 index 00000000000..c3b8c52ea9f --- /dev/null +++ b/internal/service/networkmanager/sites_data_source_test.go @@ -0,0 +1,70 @@ +package networkmanager_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" +) + +func TestAccNetworkManagerSitesDataSource_basic(t *testing.T) { + dataSourceAllName := "data.aws_networkmanager_sites.all" + dataSourceByTagsName := "data.aws_networkmanager_sites.by_tags" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccSitesDataSourceConfig(rName), + Check: resource.ComposeTestCheckFunc( + acctest.CheckResourceAttrGreaterThanValue(dataSourceAllName, "ids.#", "1"), + resource.TestCheckResourceAttr(dataSourceByTagsName, "ids.#", "1"), + ), + }, + }, + }) +} + +func testAccSitesDataSourceConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test1" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test2" { + global_network_id = aws_networkmanager_global_network.test.id +} + +data "aws_networkmanager_sites" "all" { + global_network_id = aws_networkmanager_global_network.test.id + + depends_on = [aws_networkmanager_site.test1, aws_networkmanager_site.test2] +} + +data "aws_networkmanager_sites" "by_tags" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } + + depends_on = [aws_networkmanager_site.test1, aws_networkmanager_site.test2] +} +`, rName) +} diff --git a/internal/service/networkmanager/sweep.go b/internal/service/networkmanager/sweep.go new file mode 100644 index 00000000000..791e662f8e9 --- /dev/null +++ b/internal/service/networkmanager/sweep.go @@ -0,0 +1,441 @@ +//go:build sweep +// +build sweep + +package networkmanager + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/sweep" +) + +func init() { + resource.AddTestSweepers("aws_networkmanager_global_network", &resource.Sweeper{ + Name: "aws_networkmanager_global_network", + F: sweepGlobalNetworks, + Dependencies: []string{ + "aws_networkmanager_site", + }, + }) + + resource.AddTestSweepers("aws_networkmanager_site", &resource.Sweeper{ + Name: "aws_networkmanager_site", + F: sweepSites, + Dependencies: []string{ + "aws_networkmanager_device", + "aws_networkmanager_link", + }, + }) + + resource.AddTestSweepers("aws_networkmanager_device", &resource.Sweeper{ + Name: "aws_networkmanager_device", + F: sweepDevices, + Dependencies: []string{ + "aws_networkmanager_link_association", + }, + }) + + resource.AddTestSweepers("aws_networkmanager_link", &resource.Sweeper{ + Name: "aws_networkmanager_link", + F: sweepLinks, + Dependencies: []string{ + "aws_networkmanager_link_association", + }, + }) + + resource.AddTestSweepers("aws_networkmanager_link_association", &resource.Sweeper{ + Name: "aws_networkmanager_link_association", + F: sweepLinkAssociations, + Dependencies: []string{ + "aws_networkmanager_connection", + }, + }) + + resource.AddTestSweepers("aws_networkmanager_connection", &resource.Sweeper{ + Name: "aws_networkmanager_connection", + F: sweepConnections, + }) +} + +func sweepGlobalNetworks(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).NetworkManagerConn + input := &networkmanager.DescribeGlobalNetworksInput{} + sweepResources := make([]*sweep.SweepResource, 0) + + err = conn.DescribeGlobalNetworksPages(input, func(page *networkmanager.DescribeGlobalNetworksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.GlobalNetworks { + r := ResourceGlobalNetwork() + d := r.Data(nil) + d.SetId(aws.StringValue(v.GlobalNetworkId)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Network Manager Global Network sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Network Manager Global Networks (%s): %w", region, err) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Network Manager Global Networks (%s): %w", region, err) + } + + return nil +} + +func sweepSites(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).NetworkManagerConn + input := &networkmanager.DescribeGlobalNetworksInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*sweep.SweepResource, 0) + + err = conn.DescribeGlobalNetworksPages(input, func(page *networkmanager.DescribeGlobalNetworksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.GlobalNetworks { + input := &networkmanager.GetSitesInput{ + GlobalNetworkId: v.GlobalNetworkId, + } + + err := conn.GetSitesPages(input, func(page *networkmanager.GetSitesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Sites { + r := ResourceSite() + d := r.Data(nil) + d.SetId(aws.StringValue(v.SiteId)) + d.Set("global_network_id", v.GlobalNetworkId) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + continue + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Sites (%s): %w", region, err)) + } + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Network Manager Site sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Global Networks (%s): %w", region, err)) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Network Manager Sites (%s): %w", region, err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func sweepDevices(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).NetworkManagerConn + input := &networkmanager.DescribeGlobalNetworksInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*sweep.SweepResource, 0) + + err = conn.DescribeGlobalNetworksPages(input, func(page *networkmanager.DescribeGlobalNetworksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.GlobalNetworks { + input := &networkmanager.GetDevicesInput{ + GlobalNetworkId: v.GlobalNetworkId, + } + + err := conn.GetDevicesPages(input, func(page *networkmanager.GetDevicesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Devices { + r := ResourceDevice() + d := r.Data(nil) + d.SetId(aws.StringValue(v.DeviceId)) + d.Set("global_network_id", v.GlobalNetworkId) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + continue + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Devices (%s): %w", region, err)) + } + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Network Manager Device sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Global Networks (%s): %w", region, err)) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Network Manager Devices (%s): %w", region, err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func sweepLinks(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).NetworkManagerConn + input := &networkmanager.DescribeGlobalNetworksInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*sweep.SweepResource, 0) + + err = conn.DescribeGlobalNetworksPages(input, func(page *networkmanager.DescribeGlobalNetworksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.GlobalNetworks { + input := &networkmanager.GetLinksInput{ + GlobalNetworkId: v.GlobalNetworkId, + } + + err := conn.GetLinksPages(input, func(page *networkmanager.GetLinksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Links { + r := ResourceLink() + d := r.Data(nil) + d.SetId(aws.StringValue(v.LinkId)) + d.Set("global_network_id", v.GlobalNetworkId) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + continue + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Links (%s): %w", region, err)) + } + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Network Manager Link sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Global Networks (%s): %w", region, err)) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Network Manager Links (%s): %w", region, err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func sweepLinkAssociations(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).NetworkManagerConn + input := &networkmanager.DescribeGlobalNetworksInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*sweep.SweepResource, 0) + + err = conn.DescribeGlobalNetworksPages(input, func(page *networkmanager.DescribeGlobalNetworksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.GlobalNetworks { + input := &networkmanager.GetLinkAssociationsInput{ + GlobalNetworkId: v.GlobalNetworkId, + } + + err := conn.GetLinkAssociationsPages(input, func(page *networkmanager.GetLinkAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.LinkAssociations { + r := ResourceLinkAssociation() + d := r.Data(nil) + d.SetId(LinkAssociationCreateResourceID(aws.StringValue(v.GlobalNetworkId), aws.StringValue(v.LinkId), aws.StringValue(v.DeviceId))) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + continue + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Link Associations (%s): %w", region, err)) + } + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Network Manager Link Association sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Global Networks (%s): %w", region, err)) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Network Manager Link Associations (%s): %w", region, err)) + } + + return sweeperErrs.ErrorOrNil() +} + +func sweepConnections(region string) error { + client, err := sweep.SharedRegionalSweepClient(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + conn := client.(*conns.AWSClient).NetworkManagerConn + input := &networkmanager.DescribeGlobalNetworksInput{} + var sweeperErrs *multierror.Error + sweepResources := make([]*sweep.SweepResource, 0) + + err = conn.DescribeGlobalNetworksPages(input, func(page *networkmanager.DescribeGlobalNetworksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.GlobalNetworks { + input := &networkmanager.GetConnectionsInput{ + GlobalNetworkId: v.GlobalNetworkId, + } + + err := conn.GetConnectionsPages(input, func(page *networkmanager.GetConnectionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Connections { + r := ResourceConnection() + d := r.Data(nil) + d.SetId(aws.StringValue(v.ConnectionId)) + d.Set("global_network_id", v.GlobalNetworkId) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + continue + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Connections (%s): %w", region, err)) + } + } + + return !lastPage + }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping Network Manager Connection sweep for %s: %s", region, err) + return sweeperErrs.ErrorOrNil() // In case we have completed some pages, but had errors + } + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error listing Network Manager Global Networks (%s): %w", region, err)) + } + + err = sweep.SweepOrchestrator(sweepResources) + + if err != nil { + sweeperErrs = multierror.Append(sweeperErrs, fmt.Errorf("error sweeping Network Manager Connections (%s): %w", region, err)) + } + + return sweeperErrs.ErrorOrNil() +} diff --git a/internal/service/networkmanager/transit_gateway_connect_peer_association.go b/internal/service/networkmanager/transit_gateway_connect_peer_association.go new file mode 100644 index 00000000000..94ae13d29c7 --- /dev/null +++ b/internal/service/networkmanager/transit_gateway_connect_peer_association.go @@ -0,0 +1,312 @@ +package networkmanager + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceTransitGatewayConnectPeerAssociation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTransitGatewayConnectPeerAssociationCreate, + ReadWithoutTimeout: resourceTransitGatewayConnectPeerAssociationRead, + DeleteWithoutTimeout: resourceTransitGatewayConnectPeerAssociationDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "device_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "global_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "link_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "transit_gateway_connect_peer_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + }, + } +} + +func resourceTransitGatewayConnectPeerAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID := d.Get("global_network_id").(string) + connectPeerARN := d.Get("transit_gateway_connect_peer_arn").(string) + id := TransitGatewayConnectPeerAssociationCreateResourceID(globalNetworkID, connectPeerARN) + input := &networkmanager.AssociateTransitGatewayConnectPeerInput{ + DeviceId: aws.String(d.Get("device_id").(string)), + GlobalNetworkId: aws.String(globalNetworkID), + TransitGatewayConnectPeerArn: aws.String(connectPeerARN), + } + + if v, ok := d.GetOk("link_id"); ok { + input.LinkId = aws.String(v.(string)) + } + + log.Printf("[DEBUG] Creating Network Manager Transit Gateway Connect Peer Association: %s", input) + _, err := conn.AssociateTransitGatewayConnectPeerWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating Network Manager Transit Gateway Connect Peer Association (%s): %s", id, err) + } + + d.SetId(id) + + if _, err := waitTransitGatewayConnectPeerAssociationCreated(ctx, conn, globalNetworkID, connectPeerARN, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Transit Gateway Connect Peer Association (%s) create: %s", d.Id(), err) + } + + return resourceTransitGatewayConnectPeerAssociationRead(ctx, d, meta) +} + +func resourceTransitGatewayConnectPeerAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, connectPeerARN, err := TransitGatewayConnectPeerAssociationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + output, err := FindTransitGatewayConnectPeerAssociationByTwoPartKey(ctx, conn, globalNetworkID, connectPeerARN) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Transit Gateway Connect Peer Association %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Transit Gateway Connect Peer Association (%s): %s", d.Id(), err) + } + + d.Set("device_id", output.DeviceId) + d.Set("global_network_id", output.GlobalNetworkId) + d.Set("link_id", output.LinkId) + d.Set("transit_gateway_connect_peer_arn", output.TransitGatewayConnectPeerArn) + + return nil +} + +func resourceTransitGatewayConnectPeerAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, connectPeerARN, err := TransitGatewayConnectPeerAssociationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + err = disassociateTransitGatewayConnectPeer(ctx, conn, globalNetworkID, connectPeerARN, d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func disassociateTransitGatewayConnectPeer(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectPeerARN string, timeout time.Duration) error { + id := TransitGatewayConnectPeerAssociationCreateResourceID(globalNetworkID, connectPeerARN) + + log.Printf("[DEBUG] Deleting Network Manager Transit Gateway Connect Peer Association: %s", id) + _, err := conn.DisassociateTransitGatewayConnectPeerWithContext(ctx, &networkmanager.DisassociateTransitGatewayConnectPeerInput{ + GlobalNetworkId: aws.String(globalNetworkID), + TransitGatewayConnectPeerArn: aws.String(connectPeerARN), + }) + + if globalNetworkIDNotFoundError(err) || tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Network Manager Transit Gateway Connect Peer Association (%s): %w", id, err) + } + + if _, err := waitTransitGatewayConnectPeerAssociationDeleted(ctx, conn, globalNetworkID, connectPeerARN, timeout); err != nil { + return fmt.Errorf("error waiting for Network Manager Transit Gateway Connect Peer Association (%s) delete: %w", id, err) + } + + return nil +} + +func FindTransitGatewayConnectPeerAssociation(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetTransitGatewayConnectPeerAssociationsInput) (*networkmanager.TransitGatewayConnectPeerAssociation, error) { + output, err := FindTransitGatewayConnectPeerAssociations(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].State == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayConnectPeerAssociations(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetTransitGatewayConnectPeerAssociationsInput) ([]*networkmanager.TransitGatewayConnectPeerAssociation, error) { + var output []*networkmanager.TransitGatewayConnectPeerAssociation + + err := conn.GetTransitGatewayConnectPeerAssociationsPagesWithContext(ctx, input, func(page *networkmanager.GetTransitGatewayConnectPeerAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayConnectPeerAssociations { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if globalNetworkIDNotFoundError(err) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayConnectPeerAssociationByTwoPartKey(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectPeerARN string) (*networkmanager.TransitGatewayConnectPeerAssociation, error) { + input := &networkmanager.GetTransitGatewayConnectPeerAssociationsInput{ + GlobalNetworkId: aws.String(globalNetworkID), + TransitGatewayConnectPeerArns: aws.StringSlice([]string{connectPeerARN}), + } + + output, err := FindTransitGatewayConnectPeerAssociation(ctx, conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == networkmanager.TransitGatewayConnectPeerAssociationStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != globalNetworkID || aws.StringValue(output.TransitGatewayConnectPeerArn) != connectPeerARN { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusTransitGatewayConnectPeerAssociationState(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectPeerARN string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindTransitGatewayConnectPeerAssociationByTwoPartKey(ctx, conn, globalNetworkID, connectPeerARN) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func waitTransitGatewayConnectPeerAssociationCreated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectPeerARN string, timeout time.Duration) (*networkmanager.TransitGatewayConnectPeerAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.TransitGatewayConnectPeerAssociationStatePending}, + Target: []string{networkmanager.TransitGatewayConnectPeerAssociationStateAvailable}, + Timeout: timeout, + Refresh: statusTransitGatewayConnectPeerAssociationState(ctx, conn, globalNetworkID, connectPeerARN), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.TransitGatewayConnectPeerAssociation); ok { + return output, err + } + + return nil, err +} + +func waitTransitGatewayConnectPeerAssociationDeleted(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, connectPeerARN string, timeout time.Duration) (*networkmanager.TransitGatewayConnectPeerAssociation, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.TransitGatewayConnectPeerAssociationStateAvailable, networkmanager.TransitGatewayConnectPeerAssociationStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusTransitGatewayConnectPeerAssociationState(ctx, conn, globalNetworkID, connectPeerARN), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.TransitGatewayConnectPeerAssociation); ok { + return output, err + } + + return nil, err +} + +const transitGatewayConnectPeerAssociationIDSeparator = "," + +func TransitGatewayConnectPeerAssociationCreateResourceID(globalNetworkID, connectPeerARN string) string { + parts := []string{globalNetworkID, connectPeerARN} + id := strings.Join(parts, transitGatewayConnectPeerAssociationIDSeparator) + + return id +} + +func TransitGatewayConnectPeerAssociationParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, transitGatewayConnectPeerAssociationIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected GLOBAL-NETWORK-ID%[2]sCONNECT-PEER-ARN", id, transitGatewayConnectPeerAssociationIDSeparator) +} diff --git a/internal/service/networkmanager/transit_gateway_connect_peer_association_test.go b/internal/service/networkmanager/transit_gateway_connect_peer_association_test.go new file mode 100644 index 00000000000..7c49c03e71e --- /dev/null +++ b/internal/service/networkmanager/transit_gateway_connect_peer_association_test.go @@ -0,0 +1,259 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/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" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerTransitGatewayConnectPeerAssociation_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccNetworkManagerTransitGatewayConnectPeerAssociation_basic, + "disappears": testAccNetworkManagerTransitGatewayConnectPeerAssociation_disappears, + "disappears_ConnectPeer": testAccNetworkManagerTransitGatewayConnectPeerAssociation_disappears_ConnectPeer, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccNetworkManagerTransitGatewayConnectPeerAssociation_basic(t *testing.T) { + resourceName := "aws_networkmanager_transit_gateway_connect_peer_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayConnectPeerAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayConnectPeerAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayConnectPeerAssociationExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNetworkManagerTransitGatewayConnectPeerAssociation_disappears(t *testing.T) { + resourceName := "aws_networkmanager_transit_gateway_connect_peer_association.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayConnectPeerAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayConnectPeerAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayConnectPeerAssociationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceTransitGatewayConnectPeerAssociation(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccNetworkManagerTransitGatewayConnectPeerAssociation_disappears_ConnectPeer(t *testing.T) { + resourceName := "aws_networkmanager_transit_gateway_connect_peer_association.test" + connetPeerResourceName := "aws_ec2_transit_gateway_connect_peer.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayConnectPeerAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayConnectPeerAssociationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayConnectPeerAssociationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGatewayConnectPeer(), connetPeerResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckTransitGatewayConnectPeerAssociationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_customer_gateway_association" { + continue + } + + globalNetworkID, connectPeerARN, err := tfnetworkmanager.TransitGatewayConnectPeerAssociationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfnetworkmanager.FindTransitGatewayConnectPeerAssociationByTwoPartKey(context.TODO(), conn, globalNetworkID, connectPeerARN) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Transit Gateway Connect Peer Association %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckTransitGatewayConnectPeerAssociationExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Transit Gateway Connect Peer Association ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, connectPeerARN, err := tfnetworkmanager.TransitGatewayConnectPeerAssociationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfnetworkmanager.FindTransitGatewayConnectPeerAssociationByTwoPartKey(context.TODO(), conn, globalNetworkID, connectPeerARN) + + if err != nil { + return err + } + + return nil + } +} + +func testAccTransitGatewayConnectPeerAssociationConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } +} + +resource "aws_subnet" "test" { + availability_zone = data.aws_availability_zones.available.names[0] + cidr_block = "10.0.0.0/24" + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + transit_gateway_cidr_blocks = ["10.20.30.0/24"] + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_vpc_attachment" "test" { + subnet_ids = [aws_subnet.test.id] + transit_gateway_id = aws_ec2_transit_gateway.test.id + vpc_id = aws_vpc.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_connect" "test" { + transit_gateway_id = aws_ec2_transit_gateway.test.id + transport_attachment_id = aws_ec2_transit_gateway_vpc_attachment.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway_connect_peer" "test" { + inside_cidr_blocks = ["169.254.200.0/29"] + peer_address = "1.1.1.1" + transit_gateway_attachment_id = aws_ec2_transit_gateway_connect.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_site" "test" { + global_network_id = aws_networkmanager_global_network.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_device" "test" { + global_network_id = aws_networkmanager_global_network.test.id + site_id = aws_networkmanager_site.test.id + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_transit_gateway_registration" "test" { + global_network_id = aws_networkmanager_global_network.test.id + transit_gateway_arn = aws_ec2_transit_gateway.test.arn + + depends_on = [aws_ec2_transit_gateway_connect_peer.test] +} + +resource "aws_networkmanager_transit_gateway_connect_peer_association" "test" { + global_network_id = aws_networkmanager_global_network.test.id + device_id = aws_networkmanager_device.test.id + + transit_gateway_connect_peer_arn = aws_ec2_transit_gateway_connect_peer.test.arn + + depends_on = [aws_networkmanager_transit_gateway_registration.test] +} +`, rName)) +} diff --git a/internal/service/networkmanager/transit_gateway_registration.go b/internal/service/networkmanager/transit_gateway_registration.go new file mode 100644 index 00000000000..5a5f10103b9 --- /dev/null +++ b/internal/service/networkmanager/transit_gateway_registration.go @@ -0,0 +1,304 @@ +package networkmanager + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +func ResourceTransitGatewayRegistration() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTransitGatewayRegistrationCreate, + ReadWithoutTimeout: resourceTransitGatewayRegistrationRead, + DeleteWithoutTimeout: resourceTransitGatewayRegistrationDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "global_network_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "transit_gateway_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + }, + } +} + +func resourceTransitGatewayRegistrationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID := d.Get("global_network_id").(string) + transitGatewayARN := d.Get("transit_gateway_arn").(string) + id := TransitGatewayRegistrationCreateResourceID(globalNetworkID, transitGatewayARN) + input := &networkmanager.RegisterTransitGatewayInput{ + GlobalNetworkId: aws.String(globalNetworkID), + TransitGatewayArn: aws.String(transitGatewayARN), + } + + log.Printf("[DEBUG] Creating Network Manager Transit Gateway Registration: %s", input) + _, err := conn.RegisterTransitGatewayWithContext(ctx, input) + + if err != nil { + return diag.Errorf("error creating Network Manager Transit Gateway Registration (%s): %s", id, err) + } + + d.SetId(id) + + if _, err := waitTransitGatewayRegistrationCreated(ctx, conn, globalNetworkID, transitGatewayARN, d.Timeout(schema.TimeoutCreate)); err != nil { + return diag.Errorf("error waiting for Network Manager Transit Gateway Attachment (%s) create: %s", d.Id(), err) + } + + return resourceTransitGatewayRegistrationRead(ctx, d, meta) +} + +func resourceTransitGatewayRegistrationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, transitGatewayARN, err := TransitGatewayRegistrationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + transitGatewayRegistration, err := FindTransitGatewayRegistrationByTwoPartKey(ctx, conn, globalNetworkID, transitGatewayARN) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Network Manager Transit Gateway Registration %s not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return diag.Errorf("error reading Network Manager Transit Gateway Registration (%s): %s", d.Id(), err) + } + + d.Set("global_network_id", transitGatewayRegistration.GlobalNetworkId) + d.Set("transit_gateway_arn", transitGatewayRegistration.TransitGatewayArn) + + return nil +} + +func resourceTransitGatewayRegistrationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, transitGatewayARN, err := TransitGatewayRegistrationParseResourceID(d.Id()) + + if err != nil { + return diag.FromErr(err) + } + + err = deregisterTransitGateway(ctx, conn, globalNetworkID, transitGatewayARN, d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func deregisterTransitGateway(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, transitGatewayARN string, timeout time.Duration) error { + id := TransitGatewayRegistrationCreateResourceID(globalNetworkID, transitGatewayARN) + + log.Printf("[DEBUG] Deleting Network Manager Transit Gateway Registration: %s", id) + _, err := conn.DeregisterTransitGatewayWithContext(ctx, &networkmanager.DeregisterTransitGatewayInput{ + GlobalNetworkId: aws.String(globalNetworkID), + TransitGatewayArn: aws.String(transitGatewayARN), + }) + + if globalNetworkIDNotFoundError(err) || tfawserr.ErrCodeEquals(err, networkmanager.ErrCodeResourceNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Network Manager Transit Gateway Registration (%s): %w", id, err) + } + + if _, err := waitTransitGatewayRegistrationDeleted(ctx, conn, globalNetworkID, transitGatewayARN, timeout); err != nil { + return fmt.Errorf("error waiting for Network Manager Transit Gateway Registration (%s) delete: %w", id, err) + } + + return nil +} + +func FindTransitGatewayRegistration(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetTransitGatewayRegistrationsInput) (*networkmanager.TransitGatewayRegistration, error) { + output, err := FindTransitGatewayRegistrations(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].State == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayRegistrations(ctx context.Context, conn *networkmanager.NetworkManager, input *networkmanager.GetTransitGatewayRegistrationsInput) ([]*networkmanager.TransitGatewayRegistration, error) { + var output []*networkmanager.TransitGatewayRegistration + + err := conn.GetTransitGatewayRegistrationsPagesWithContext(ctx, input, func(page *networkmanager.GetTransitGatewayRegistrationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayRegistrations { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if globalNetworkIDNotFoundError(err) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayRegistrationByTwoPartKey(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, transitGatewayARN string) (*networkmanager.TransitGatewayRegistration, error) { + input := &networkmanager.GetTransitGatewayRegistrationsInput{ + GlobalNetworkId: aws.String(globalNetworkID), + TransitGatewayArns: aws.StringSlice([]string{transitGatewayARN}), + } + + output, err := FindTransitGatewayRegistration(ctx, conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State.Code); state == networkmanager.TransitGatewayRegistrationStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.GlobalNetworkId) != globalNetworkID || aws.StringValue(output.TransitGatewayArn) != transitGatewayARN { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func statusTransitGatewayRegistrationState(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, transitGatewayARN string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindTransitGatewayRegistrationByTwoPartKey(ctx, conn, globalNetworkID, transitGatewayARN) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State.Code), nil + } +} + +func waitTransitGatewayRegistrationCreated(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, transitGatewayARN string, timeout time.Duration) (*networkmanager.TransitGatewayRegistration, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.TransitGatewayRegistrationStatePending}, + Target: []string{networkmanager.TransitGatewayRegistrationStateAvailable}, + Timeout: timeout, + Refresh: statusTransitGatewayRegistrationState(ctx, conn, globalNetworkID, transitGatewayARN), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.TransitGatewayRegistration); ok { + if state := aws.StringValue(output.State.Code); state == networkmanager.TransitGatewayRegistrationStateFailed { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.State.Message))) + } + + return output, err + } + + return nil, err +} + +func waitTransitGatewayRegistrationDeleted(ctx context.Context, conn *networkmanager.NetworkManager, globalNetworkID, transitGatewayARN string, timeout time.Duration) (*networkmanager.TransitGatewayRegistration, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{networkmanager.TransitGatewayRegistrationStateAvailable, networkmanager.TransitGatewayRegistrationStateDeleting}, + Target: []string{}, + Timeout: timeout, + Refresh: statusTransitGatewayRegistrationState(ctx, conn, globalNetworkID, transitGatewayARN), + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*networkmanager.TransitGatewayRegistration); ok { + if state := aws.StringValue(output.State.Code); state == networkmanager.TransitGatewayRegistrationStateFailed { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.State.Message))) + } + + return output, err + } + + return nil, err +} + +const transitGatewayRegistrationIDSeparator = "," + +func TransitGatewayRegistrationCreateResourceID(globalNetworkID, transitGatewayARN string) string { + parts := []string{globalNetworkID, transitGatewayARN} + id := strings.Join(parts, transitGatewayRegistrationIDSeparator) + + return id +} + +func TransitGatewayRegistrationParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, transitGatewayRegistrationIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected GLOBAL-NETWORK-ID%[2]sTRANSIT-GATEWAY-ARN", id, transitGatewayRegistrationIDSeparator) +} diff --git a/internal/service/networkmanager/transit_gateway_registration_test.go b/internal/service/networkmanager/transit_gateway_registration_test.go new file mode 100644 index 00000000000..a0713612118 --- /dev/null +++ b/internal/service/networkmanager/transit_gateway_registration_test.go @@ -0,0 +1,232 @@ +package networkmanager_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/networkmanager" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/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" + tfnetworkmanager "github.com/hashicorp/terraform-provider-aws/internal/service/networkmanager" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func TestAccNetworkManagerTransitGatewayRegistration_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccNetworkManagerTransitGatewayRegistration_basic, + "disappears": testAccNetworkManagerTransitGatewayRegistration_disappears, + "disappears_TransitGateway": testAccNetworkManagerTransitGatewayRegistration_disappears_TransitGateway, + "crossRegion": testAccNetworkManagerTransitGatewayRegistration_crossRegion, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } +} + +func testAccNetworkManagerTransitGatewayRegistration_basic(t *testing.T) { + resourceName := "aws_networkmanager_transit_gateway_registration.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayRegistrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayRegistrationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayRegistrationExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNetworkManagerTransitGatewayRegistration_disappears(t *testing.T) { + resourceName := "aws_networkmanager_transit_gateway_registration.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayRegistrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayRegistrationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayRegistrationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfnetworkmanager.ResourceTransitGatewayRegistration(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccNetworkManagerTransitGatewayRegistration_disappears_TransitGateway(t *testing.T) { + resourceName := "aws_networkmanager_transit_gateway_registration.test" + transitGatewayResourceName := "aws_ec2_transit_gateway.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckTransitGatewayRegistrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayRegistrationConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayRegistrationExists(resourceName), + acctest.CheckResourceDisappears(acctest.Provider, tfec2.ResourceTransitGateway(), transitGatewayResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccNetworkManagerTransitGatewayRegistration_crossRegion(t *testing.T) { + resourceName := "aws_networkmanager_transit_gateway_registration.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + var providers []*schema.Provider + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckMultipleRegion(t, 2) }, + ErrorCheck: acctest.ErrorCheck(t, networkmanager.EndpointsID), + ProviderFactories: acctest.FactoriesAlternate(&providers), + CheckDestroy: testAccCheckTransitGatewayRegistrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccTransitGatewayRegistrationCrossRegionConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTransitGatewayRegistrationExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckTransitGatewayRegistrationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_networkmanager_transit_gateway_registration" { + continue + } + + globalNetworkID, transitGatewayARN, err := tfnetworkmanager.TransitGatewayRegistrationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfnetworkmanager.FindTransitGatewayRegistrationByTwoPartKey(context.TODO(), conn, globalNetworkID, transitGatewayARN) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Network Manager Transit Gateway Registration %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckTransitGatewayRegistrationExists(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) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Network Manager Transit Gateway Registration ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).NetworkManagerConn + + globalNetworkID, transitGatewayARN, err := tfnetworkmanager.TransitGatewayRegistrationParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfnetworkmanager.FindTransitGatewayRegistrationByTwoPartKey(context.TODO(), conn, globalNetworkID, transitGatewayARN) + + if err != nil { + return err + } + + return nil + } +} +func testAccTransitGatewayRegistrationConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_transit_gateway_registration" "test" { + global_network_id = aws_networkmanager_global_network.test.id + transit_gateway_arn = aws_ec2_transit_gateway.test.arn +} +`, rName) +} + +func testAccTransitGatewayRegistrationCrossRegionConfig(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAlternateRegionProvider(), fmt.Sprintf(` +resource "aws_networkmanager_global_network" "test" { + tags = { + Name = %[1]q + } +} + +resource "aws_ec2_transit_gateway" "test" { + provider = "awsalternate" + + tags = { + Name = %[1]q + } +} + +resource "aws_networkmanager_transit_gateway_registration" "test" { + global_network_id = aws_networkmanager_global_network.test.id + transit_gateway_arn = aws_ec2_transit_gateway.test.arn +} +`, rName)) +} diff --git a/internal/service/rds/cluster.go b/internal/service/rds/cluster.go index 16cfd9d0736..35d34477e2f 100644 --- a/internal/service/rds/cluster.go +++ b/internal/service/rds/cluster.go @@ -23,9 +23,9 @@ import ( ) const ( - rdsClusterScalingConfiguration_DefaultMinCapacity = 1 - rdsClusterScalingConfiguration_DefaultMaxCapacity = 16 - rdsClusterTimeoutDelete = 2 * time.Minute + clusterScalingConfiguration_DefaultMinCapacity = 1 + clusterScalingConfiguration_DefaultMaxCapacity = 16 + clusterTimeoutDelete = 2 * time.Minute ) func ResourceCluster() *schema.Resource { @@ -157,6 +157,11 @@ func ResourceCluster() *schema.Resource { Computed: true, }, + "db_cluster_instance_class": { + Type: schema.TypeString, + Optional: true, + }, + "engine": { Type: schema.TypeString, Optional: true, @@ -205,12 +210,12 @@ func ResourceCluster() *schema.Resource { "max_capacity": { Type: schema.TypeInt, Optional: true, - Default: rdsClusterScalingConfiguration_DefaultMaxCapacity, + Default: clusterScalingConfiguration_DefaultMaxCapacity, }, "min_capacity": { Type: schema.TypeInt, Optional: true, - Default: rdsClusterScalingConfiguration_DefaultMinCapacity, + Default: clusterScalingConfiguration_DefaultMinCapacity, }, "seconds_until_auto_pause": { Type: schema.TypeInt, @@ -231,6 +236,22 @@ func ResourceCluster() *schema.Resource { }, }, + "allocated_storage": { + Type: schema.TypeInt, + Optional: true, + }, + + "storage_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "iops": { + Type: schema.TypeInt, + Optional: true, + }, + "storage_encrypted": { Type: schema.TypeBool, Optional: true, @@ -870,6 +891,10 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { createOpts.DBClusterParameterGroupName = aws.String(attr.(string)) } + if attr, ok := d.GetOk("db_cluster_instance_class"); ok { + createOpts.DBClusterInstanceClass = aws.String(attr.(string)) + } + if attr, ok := d.GetOk("engine_version"); ok { createOpts.EngineVersion = aws.String(attr.(string)) } @@ -922,6 +947,18 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { createOpts.ReplicationSourceIdentifier = aws.String(attr.(string)) } + if attr, ok := d.GetOkExists("allocated_storage"); ok { + createOpts.AllocatedStorage = aws.Int64(int64(attr.(int))) + } + + if attr, ok := d.GetOkExists("storage_type"); ok { + createOpts.StorageType = aws.String(attr.(string)) + } + + if attr, ok := d.GetOkExists("iops"); ok { + createOpts.Iops = aws.Int64(int64(attr.(int))) + } + if attr, ok := d.GetOkExists("storage_encrypted"); ok { createOpts.StorageEncrypted = aws.Bool(attr.(bool)) } @@ -953,8 +990,7 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { log.Printf("[INFO] RDS Cluster ID: %s", d.Id()) - log.Println( - "[INFO] Waiting for RDS Cluster to be available") + log.Println("[INFO] Waiting for RDS Cluster to be available") stateConf := &resource.StateChangeConf{ Pending: resourceClusterCreatePendingStates, @@ -990,7 +1026,7 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error { } log.Printf("[INFO] Waiting for RDS Cluster (%s) to be available", d.Id()) - err = waitForRDSClusterUpdate(conn, d.Id(), d.Timeout(schema.TimeoutCreate)) + err = waitForClusterUpdate(conn, d.Id(), d.Timeout(schema.TimeoutCreate)) if err != nil { return fmt.Errorf("error waiting for RDS Cluster (%s) to be available: %s", d.Id(), err) } @@ -1076,12 +1112,13 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { } d.Set("endpoint", dbc.Endpoint) + d.Set("db_cluster_instance_class", dbc.DBClusterInstanceClass) d.Set("engine_mode", dbc.EngineMode) d.Set("engine", dbc.Engine) d.Set("hosted_zone_id", dbc.HostedZoneId) d.Set("iam_database_authentication_enabled", dbc.IAMDatabaseAuthenticationEnabled) - rdsClusterSetResourceDataEngineVersionFromCluster(d, dbc) + clusterSetResourceDataEngineVersionFromCluster(d, dbc) var roles []string for _, r := range dbc.AssociatedRoles { @@ -1103,7 +1140,11 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("error setting scaling_configuration: %s", err) } + d.Set("allocated_storage", dbc.AllocatedStorage) + d.Set("storage_type", dbc.StorageType) + d.Set("iops", dbc.Iops) d.Set("storage_encrypted", dbc.StorageEncrypted) + d.Set("enable_http_endpoint", dbc.HttpEndpointEnabled) var vpcg []string @@ -1182,6 +1223,11 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { requestUpdate = true } + if d.HasChange("db_cluster_instance_class") { + req.EngineVersion = aws.String(d.Get("db_cluster_instance_class").(string)) + requestUpdate = true + } + if d.HasChange("engine_version") { req.EngineVersion = aws.String(d.Get("engine_version").(string)) requestUpdate = true @@ -1201,6 +1247,21 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { requestUpdate = true } + if d.HasChange("storage_type") { + req.StorageType = aws.String(d.Get("storage_type").(string)) + requestUpdate = true + } + + if d.HasChange("allocated_storage") { + req.AllocatedStorage = aws.Int64(int64(d.Get("allocated_storage").(int))) + requestUpdate = true + } + + if d.HasChange("iops") { + req.Iops = aws.Int64(int64(d.Get("iops").(int))) + requestUpdate = true + } + if d.HasChange("preferred_backup_window") { req.PreferredBackupWindow = aws.String(d.Get("preferred_backup_window").(string)) requestUpdate = true @@ -1288,7 +1349,7 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error { } log.Printf("[INFO] Waiting for RDS Cluster (%s) to be available", d.Id()) - err = waitForRDSClusterUpdate(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) + err = waitForClusterUpdate(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) if err != nil { return fmt.Errorf("error waiting for RDS Cluster (%s) to be available: %s", d.Id(), err) } @@ -1397,7 +1458,7 @@ func resourceClusterDelete(d *schema.ResourceData, meta interface{}) error { log.Printf("[DEBUG] RDS Cluster delete options: %s", deleteOpts) - err := resource.Retry(rdsClusterTimeoutDelete, func() *resource.RetryError { + err := resource.Retry(clusterTimeoutDelete, func() *resource.RetryError { _, err := conn.DeleteDBCluster(&deleteOpts) if err != nil { if tfawserr.ErrMessageContains(err, rds.ErrCodeInvalidDBClusterStateFault, "is not currently in the available state") { @@ -1506,7 +1567,7 @@ var resourceClusterUpdatePendingStates = []string{ "upgrading", } -func waitForRDSClusterUpdate(conn *rds.RDS, id string, timeout time.Duration) error { +func waitForClusterUpdate(conn *rds.RDS, id string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: resourceClusterUpdatePendingStates, Target: []string{"available"}, @@ -1515,6 +1576,7 @@ func waitForRDSClusterUpdate(conn *rds.RDS, id string, timeout time.Duration) er MinTimeout: 10 * time.Second, Delay: 30 * time.Second, // Wait 30 secs before starting } + _, err := stateConf.WaitForState() return err } @@ -1534,22 +1596,8 @@ func WaitForClusterDeletion(conn *rds.RDS, id string, timeout time.Duration) err return err } -func rdsClusterSetResourceDataEngineVersionFromCluster(d *schema.ResourceData, c *rds.DBCluster) { +func clusterSetResourceDataEngineVersionFromCluster(d *schema.ResourceData, c *rds.DBCluster) { oldVersion := d.Get("engine_version").(string) newVersion := aws.StringValue(c.EngineVersion) compareActualEngineVersion(d, oldVersion, newVersion) } - -func compareActualEngineVersion(d *schema.ResourceData, oldVersion string, newVersion string) { - newVersionSubstr := newVersion - - if len(newVersion) > len(oldVersion) { - newVersionSubstr = string([]byte(newVersion)[0 : len(oldVersion)+1]) - } - - if oldVersion != newVersion && string(append([]byte(oldVersion), []byte(".")...)) != newVersionSubstr { - d.Set("engine_version", newVersion) - } - - d.Set("engine_version_actual", newVersion) -} diff --git a/internal/service/rds/cluster_endpoint.go b/internal/service/rds/cluster_endpoint.go index 805303b1b1f..bdf9beed532 100644 --- a/internal/service/rds/cluster_endpoint.go +++ b/internal/service/rds/cluster_endpoint.go @@ -18,9 +18,9 @@ import ( ) const ( - AWSRDSClusterEndpointCreateTimeout = 30 * time.Minute - AWSRDSClusterEndpointRetryDelay = 5 * time.Second - ClusterEndpointRetryMinTimeout = 3 * time.Second + clusterEndpointCreateTimeout = 30 * time.Minute + clusterEndpointRetryDelay = 5 * time.Second + ClusterEndpointRetryMinTimeout = 3 * time.Second ) func ResourceClusterEndpoint() *schema.Resource { @@ -114,7 +114,7 @@ func resourceClusterEndpointCreate(d *schema.ResourceData, meta interface{}) err d.SetId(endpointId) - err = resourceClusterEndpointWaitForAvailable(AWSRDSClusterEndpointCreateTimeout, d.Id(), conn) + err = resourceClusterEndpointWaitForAvailable(clusterEndpointCreateTimeout, d.Id(), conn) if err != nil { return err } @@ -252,7 +252,7 @@ func resourceClusterEndpointWaitForDestroy(timeout time.Duration, id string, con Target: []string{"destroyed"}, Refresh: DBClusterEndpointStateRefreshFunc(conn, id), Timeout: timeout, - Delay: AWSRDSClusterEndpointRetryDelay, + Delay: clusterEndpointRetryDelay, MinTimeout: ClusterEndpointRetryMinTimeout, } _, err := stateConf.WaitForState() @@ -270,7 +270,7 @@ func resourceClusterEndpointWaitForAvailable(timeout time.Duration, id string, c Target: []string{"available"}, Refresh: DBClusterEndpointStateRefreshFunc(conn, id), Timeout: timeout, - Delay: AWSRDSClusterEndpointRetryDelay, + Delay: clusterEndpointRetryDelay, MinTimeout: ClusterEndpointRetryMinTimeout, } diff --git a/internal/service/rds/cluster_endpoint_test.go b/internal/service/rds/cluster_endpoint_test.go index 02e74881f79..754bad93244 100644 --- a/internal/service/rds/cluster_endpoint_test.go +++ b/internal/service/rds/cluster_endpoint_test.go @@ -18,6 +18,10 @@ import ( ) func TestAccRDSClusterEndpoint_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + rInt := sdkacctest.RandInt() var customReaderEndpoint rds.DBClusterEndpoint var customEndpoint rds.DBClusterEndpoint @@ -61,6 +65,10 @@ func TestAccRDSClusterEndpoint_basic(t *testing.T) { } func TestAccRDSClusterEndpoint_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + rInt := sdkacctest.RandInt() var customReaderEndpoint rds.DBClusterEndpoint resourceName := "aws_rds_cluster_endpoint.reader" @@ -211,7 +219,9 @@ func testAccCheckClusterEndpointExistsWithProvider(resourceName string, endpoint } func testAccClusterEndpointBaseConfig(n int) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` + return acctest.ConfigCompose( + acctest.ConfigAvailableAZsNoOptIn(), + fmt.Sprintf(` data "aws_rds_orderable_db_instance" "test" { engine = aws_rds_cluster.default.engine engine_version = aws_rds_cluster.default.engine_version @@ -249,7 +259,9 @@ resource "aws_rds_cluster_instance" "test2" { } func testAccClusterEndpointConfig(n int) string { - return testAccClusterEndpointBaseConfig(n) + fmt.Sprintf(` + return acctest.ConfigCompose( + testAccClusterEndpointBaseConfig(n), + fmt.Sprintf(` resource "aws_rds_cluster_endpoint" "reader" { cluster_identifier = aws_rds_cluster.default.id cluster_endpoint_identifier = "reader-%[1]d" @@ -265,11 +277,13 @@ resource "aws_rds_cluster_endpoint" "default" { excluded_members = [aws_rds_cluster_instance.test2.id] } -`, n) +`, n)) } func testAccClusterEndpointTags1Config(n int, tagKey1, tagValue1 string) string { - return testAccClusterEndpointBaseConfig(n) + fmt.Sprintf(` + return acctest.ConfigCompose( + testAccClusterEndpointBaseConfig(n), + fmt.Sprintf(` resource "aws_rds_cluster_endpoint" "reader" { cluster_identifier = aws_rds_cluster.default.id cluster_endpoint_identifier = "reader-%[1]d" @@ -281,11 +295,13 @@ resource "aws_rds_cluster_endpoint" "reader" { %[2]q = %[3]q } } -`, n, tagKey1, tagValue1) +`, n, tagKey1, tagValue1)) } func testAccClusterEndpointTags2Config(n int, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return testAccClusterEndpointBaseConfig(n) + fmt.Sprintf(` + return acctest.ConfigCompose( + testAccClusterEndpointBaseConfig(n), + fmt.Sprintf(` resource "aws_rds_cluster_endpoint" "reader" { cluster_identifier = aws_rds_cluster.default.id cluster_endpoint_identifier = "reader-%[1]d" @@ -298,5 +314,5 @@ resource "aws_rds_cluster_endpoint" "reader" { %[4]q = %[5]q } } -`, n, tagKey1, tagValue1, tagKey2, tagValue2) +`, n, tagKey1, tagValue1, tagKey2, tagValue2)) } diff --git a/internal/service/rds/cluster_instance_test.go b/internal/service/rds/cluster_instance_test.go index 649138ddf61..a2a18595f16 100644 --- a/internal/service/rds/cluster_instance_test.go +++ b/internal/service/rds/cluster_instance_test.go @@ -19,6 +19,10 @@ import ( ) func TestAccRDSClusterInstance_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance resourceName := "aws_rds_cluster_instance.cluster_instances" @@ -66,6 +70,10 @@ func TestAccRDSClusterInstance_basic(t *testing.T) { } func TestAccRDSClusterInstance_isAlreadyBeingDeleted(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance resourceName := "aws_rds_cluster_instance.cluster_instances" rInt := sdkacctest.RandInt() @@ -103,6 +111,10 @@ func TestAccRDSClusterInstance_isAlreadyBeingDeleted(t *testing.T) { } func TestAccRDSClusterInstance_az(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance resourceName := "aws_rds_cluster_instance.cluster_instances" availabilityZonesDataSourceName := "data.aws_availability_zones.available" @@ -135,6 +147,10 @@ func TestAccRDSClusterInstance_az(t *testing.T) { } func TestAccRDSClusterInstance_namePrefix(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance rInt := sdkacctest.RandInt() resourceName := "aws_rds_cluster_instance.test" @@ -168,6 +184,10 @@ func TestAccRDSClusterInstance_namePrefix(t *testing.T) { } func TestAccRDSClusterInstance_generatedName(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance resourceName := "aws_rds_cluster_instance.test" @@ -199,6 +219,10 @@ func TestAccRDSClusterInstance_generatedName(t *testing.T) { } func TestAccRDSClusterInstance_kmsKey(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance kmsKeyResourceName := "aws_kms_key.foo" resourceName := "aws_rds_cluster_instance.cluster_instances" @@ -231,6 +255,10 @@ func TestAccRDSClusterInstance_kmsKey(t *testing.T) { // https://github.com/hashicorp/terraform/issues/5350 func TestAccRDSClusterInstance_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance resourceName := "aws_rds_cluster_instance.cluster_instances" @@ -253,6 +281,10 @@ func TestAccRDSClusterInstance_disappears(t *testing.T) { } func TestAccRDSClusterInstance_publiclyAccessible(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_cluster_instance.test" @@ -291,6 +323,10 @@ func TestAccRDSClusterInstance_publiclyAccessible(t *testing.T) { } func TestAccRDSClusterInstance_copyTagsToSnapshot(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rNameSuffix := sdkacctest.RandInt() resourceName := "aws_rds_cluster_instance.cluster_instances" @@ -329,6 +365,10 @@ func TestAccRDSClusterInstance_copyTagsToSnapshot(t *testing.T) { } func TestAccRDSClusterInstance_monitoringInterval(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_rds_cluster_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -381,6 +421,10 @@ func TestAccRDSClusterInstance_monitoringInterval(t *testing.T) { } func TestAccRDSClusterInstance_MonitoringRoleARN_enabledToDisabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance iamRoleResourceName := "aws_iam_role.test" resourceName := "aws_rds_cluster_instance.test" @@ -420,6 +464,10 @@ func TestAccRDSClusterInstance_MonitoringRoleARN_enabledToDisabled(t *testing.T) } func TestAccRDSClusterInstance_MonitoringRoleARN_enabledToRemoved(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance iamRoleResourceName := "aws_iam_role.test" resourceName := "aws_rds_cluster_instance.test" @@ -458,6 +506,10 @@ func TestAccRDSClusterInstance_MonitoringRoleARN_enabledToRemoved(t *testing.T) } func TestAccRDSClusterInstance_MonitoringRoleARN_removedToEnabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance iamRoleResourceName := "aws_iam_role.test" resourceName := "aws_rds_cluster_instance.test" @@ -496,6 +548,10 @@ func TestAccRDSClusterInstance_MonitoringRoleARN_removedToEnabled(t *testing.T) } func TestAccRDSClusterInstance_PerformanceInsightsEnabled_auroraMySQL1(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_rds_cluster_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -528,6 +584,10 @@ func TestAccRDSClusterInstance_PerformanceInsightsEnabled_auroraMySQL1(t *testin } func TestAccRDSClusterInstance_PerformanceInsightsEnabled_auroraMySQL2(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_rds_cluster_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -561,6 +621,10 @@ func TestAccRDSClusterInstance_PerformanceInsightsEnabled_auroraMySQL2(t *testin } func TestAccRDSClusterInstance_PerformanceInsightsEnabled_auroraPostgresql(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_rds_cluster_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -593,6 +657,10 @@ func TestAccRDSClusterInstance_PerformanceInsightsEnabled_auroraPostgresql(t *te } func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyID_auroraMySQL1(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance kmsKeyResourceName := "aws_kms_key.test" resourceName := "aws_rds_cluster_instance.test" @@ -627,6 +695,10 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyID_auroraMySQL1(t *testi } func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraMySQL1_defaultKeyToCustomKey(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_rds_cluster_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -656,13 +728,17 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraMySQL1_defaultKe }, { Config: testAccClusterInstancePerformanceInsightsKMSKeyIdAuroraMySQL1Config(rName, engine), - ExpectError: regexp.MustCompile(`InvalidParameterCombination: You cannot change your Performance Insights KMS key`), + ExpectError: regexp.MustCompile(`InvalidParameterCombination: You .* change your Performance Insights KMS key`), }, }, }) } func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyID_auroraMySQL2(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance kmsKeyResourceName := "aws_kms_key.test" resourceName := "aws_rds_cluster_instance.test" @@ -698,6 +774,10 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyID_auroraMySQL2(t *testi } func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraMySQL2_defaultKeyToCustomKey(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_rds_cluster_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -728,13 +808,17 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraMySQL2_defaultKe }, { Config: testAccClusterInstancePerformanceInsightsKMSKeyIdAuroraMySQL2Config(rName, engine, engineVersion), - ExpectError: regexp.MustCompile(`InvalidParameterCombination: You cannot change your Performance Insights KMS key`), + ExpectError: regexp.MustCompile(`InvalidParameterCombination: You .* change your Performance Insights KMS key`), }, }, }) } func TestAccRDSClusterInstance_performanceInsightsRetentionPeriod(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_rds_cluster_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -775,6 +859,10 @@ func TestAccRDSClusterInstance_performanceInsightsRetentionPeriod(t *testing.T) } func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyID_auroraPostgresql(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance kmsKeyResourceName := "aws_kms_key.test" resourceName := "aws_rds_cluster_instance.test" @@ -809,6 +897,10 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyID_auroraPostgresql(t *t } func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraPostgresql_defaultKeyToCustomKey(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_rds_cluster_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -838,13 +930,17 @@ func TestAccRDSClusterInstance_PerformanceInsightsKMSKeyIDAuroraPostgresql_defau }, { Config: testAccClusterInstancePerformanceInsightsKMSKeyIdAuroraPostgresqlConfig(rName, engine), - ExpectError: regexp.MustCompile(`InvalidParameterCombination: You cannot change your Performance Insights KMS key`), + ExpectError: regexp.MustCompile(`InvalidParameterCombination: You .* change your Performance Insights KMS key`), }, }, }) } func TestAccRDSClusterInstance_caCertificateIdentifier(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_cluster_instance.test" diff --git a/internal/service/rds/cluster_test.go b/internal/service/rds/cluster_test.go index 401660d83e7..ec76b8a9337 100644 --- a/internal/service/rds/cluster_test.go +++ b/internal/service/rds/cluster_test.go @@ -84,6 +84,10 @@ func TestAccRDSCluster_basic(t *testing.T) { } func TestAccRDSCluster_allowMajorVersionUpgrade(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster1, dbCluster2 rds.DBCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_cluster.test" @@ -91,8 +95,8 @@ func TestAccRDSCluster_allowMajorVersionUpgrade(t *testing.T) { // either by having a new data source created or implementing the testing similar // to TestAccAWSDmsReplicationInstance_EngineVersion engine := "aurora-postgresql" - engineVersion1 := "10.11" - engineVersion2 := "11.7" + engineVersion1 := "12.9" + engineVersion2 := "13.5" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -137,6 +141,10 @@ func TestAccRDSCluster_allowMajorVersionUpgrade(t *testing.T) { } func TestAccRDSCluster_allowMajorVersionUpgradeWithCustomParametersApplyImm(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster1, dbCluster2 rds.DBCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_cluster.test" @@ -144,8 +152,8 @@ func TestAccRDSCluster_allowMajorVersionUpgradeWithCustomParametersApplyImm(t *t // either by having a new data source created or implementing the testing similar // to TestAccAWSDmsReplicationInstance_EngineVersion engine := "aurora-postgresql" - engineVersion1 := "11.9" - engineVersion2 := "12.4" + engineVersion1 := "12.9" + engineVersion2 := "13.5" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -176,6 +184,10 @@ func TestAccRDSCluster_allowMajorVersionUpgradeWithCustomParametersApplyImm(t *t } func TestAccRDSCluster_allowMajorVersionUpgradeWithCustomParameters(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster1, dbCluster2 rds.DBCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_cluster.test" @@ -183,8 +195,8 @@ func TestAccRDSCluster_allowMajorVersionUpgradeWithCustomParameters(t *testing.T // either by having a new data source created or implementing the testing similar // to TestAccAWSDmsReplicationInstance_EngineVersion engine := "aurora-postgresql" - engineVersion1 := "10.11" - engineVersion2 := "11.7" + engineVersion1 := "12.9" + engineVersion2 := "13.5" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -229,6 +241,10 @@ func TestAccRDSCluster_allowMajorVersionUpgradeWithCustomParameters(t *testing.T } func TestAccRDSCluster_onlyMajorVersion(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster1 rds.DBCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_cluster.test" @@ -292,6 +308,94 @@ func TestAccRDSCluster_availabilityZones(t *testing.T) { }) } +func TestAccRDSCluster_storageType(t *testing.T) { + var dbCluster rds.DBCluster + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_StorageType(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(resourceName, &dbCluster), + resource.TestCheckResourceAttr(resourceName, "storage_type", "io1"), + ), + }, + }, + }) +} + +func TestAccRDSCluster_allocatedStorage(t *testing.T) { + var dbCluster rds.DBCluster + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_AllocatedStorage(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(resourceName, &dbCluster), + resource.TestCheckResourceAttr(resourceName, "allocated_storage", "100"), + ), + }, + }, + }) +} + +func TestAccRDSCluster_iops(t *testing.T) { + var dbCluster rds.DBCluster + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_Iops(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(resourceName, &dbCluster), + resource.TestCheckResourceAttr(resourceName, "iops", "300"), + ), + }, + }, + }) +} + +func TestAccRDSCluster_dbClusterInstanceClass(t *testing.T) { + var dbCluster rds.DBCluster + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccClusterConfig_DbClusterInstanceClass(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterExists(resourceName, &dbCluster), + resource.TestCheckResourceAttr(resourceName, "db_cluster_instance_class", "db.r6gd.xlarge"), + ), + }, + }, + }) +} + func TestAccRDSCluster_backtrackWindow(t *testing.T) { var dbCluster rds.DBCluster resourceName := "aws_rds_cluster.test" @@ -364,6 +468,10 @@ func TestAccRDSCluster_dbSubnetGroupName(t *testing.T) { } func TestAccRDSCluster_s3Restore(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBCluster resourceName := "aws_rds_cluster.test" bucket := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -388,6 +496,10 @@ func TestAccRDSCluster_s3Restore(t *testing.T) { } func TestAccRDSCluster_pointInTimeRestore(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBCluster var c rds.DBCluster @@ -414,6 +526,10 @@ func TestAccRDSCluster_pointInTimeRestore(t *testing.T) { } func TestAccRDSCluster_PointInTimeRestore_enabledCloudWatchLogsExports(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBCluster var c rds.DBCluster @@ -725,6 +841,10 @@ func TestAccRDSCluster_copyTagsToSnapshot(t *testing.T) { } func TestAccRDSCluster_ReplicationSourceIdentifier_kmsKeyID(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var primaryCluster rds.DBCluster var replicaCluster rds.DBCluster resourceName := "aws_rds_cluster.test" @@ -846,6 +966,10 @@ func TestAccRDSCluster_deletionProtection(t *testing.T) { } func TestAccRDSCluster_engineMode(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster1, dbCluster2 rds.DBCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -948,6 +1072,10 @@ func TestAccRDSCluster_EngineMode_parallelQuery(t *testing.T) { } func TestAccRDSCluster_engineVersion(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster rds.DBCluster rInt := sdkacctest.RandInt() resourceName := "aws_rds_cluster.test" @@ -976,6 +1104,10 @@ func TestAccRDSCluster_engineVersion(t *testing.T) { } func TestAccRDSCluster_engineVersionWithPrimaryInstance(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster rds.DBCluster rInt := sdkacctest.RandInt() resourceName := "aws_rds_cluster.test" @@ -1145,6 +1277,10 @@ func TestAccRDSCluster_GlobalClusterIdentifierEngineMode_provisioned(t *testing. // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/13126 func TestAccRDSCluster_GlobalClusterIdentifier_primarySecondaryClusters(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var providers []*schema.Provider var primaryDbCluster, secondaryDbCluster rds.DBCluster @@ -1178,6 +1314,10 @@ func TestAccRDSCluster_GlobalClusterIdentifier_primarySecondaryClusters(t *testi // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/13715 func TestAccRDSCluster_GlobalClusterIdentifier_replicationSourceIdentifier(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var providers []*schema.Provider var primaryDbCluster, secondaryDbCluster rds.DBCluster @@ -1208,6 +1348,10 @@ func TestAccRDSCluster_GlobalClusterIdentifier_replicationSourceIdentifier(t *te // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/14457 func TestAccRDSCluster_GlobalClusterIdentifier_secondaryClustersWriteForwarding(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var providers []*schema.Provider var primaryDbCluster, secondaryDbCluster rds.DBCluster @@ -1242,7 +1386,7 @@ func TestAccRDSCluster_GlobalClusterIdentifier_secondaryClustersWriteForwarding( func TestAccRDSCluster_port(t *testing.T) { var dbCluster1, dbCluster2 rds.DBCluster - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_cluster.test" resource.ParallelTest(t, resource.TestCase{ @@ -1252,14 +1396,14 @@ func TestAccRDSCluster_port(t *testing.T) { CheckDestroy: testAccCheckClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccClusterConfig_Port(rInt, 5432), + Config: testAccClusterConfig_Port(rName, 5432), Check: resource.ComposeTestCheckFunc( testAccCheckClusterExists(resourceName, &dbCluster1), resource.TestCheckResourceAttr(resourceName, "port", "5432"), ), }, { - Config: testAccClusterConfig_Port(rInt, 2345), + Config: testAccClusterConfig_Port(rName, 2345), Check: resource.ComposeTestCheckFunc( testAccCheckClusterExists(resourceName, &dbCluster2), resource.TestCheckResourceAttr(resourceName, "port", "2345"), @@ -1270,6 +1414,10 @@ func TestAccRDSCluster_port(t *testing.T) { } func TestAccRDSCluster_scaling(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster rds.DBCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1339,6 +1487,10 @@ func TestAccRDSCluster_Scaling_defaultMinCapacity(t *testing.T) { } func TestAccRDSCluster_snapshotIdentifier(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1366,6 +1518,10 @@ func TestAccRDSCluster_snapshotIdentifier(t *testing.T) { } func TestAccRDSCluster_SnapshotIdentifier_deletionProtection(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1404,6 +1560,10 @@ func TestAccRDSCluster_SnapshotIdentifier_deletionProtection(t *testing.T) { } func TestAccRDSCluster_SnapshotIdentifierEngineMode_parallelQuery(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1432,6 +1592,10 @@ func TestAccRDSCluster_SnapshotIdentifierEngineMode_parallelQuery(t *testing.T) } func TestAccRDSCluster_SnapshotIdentifierEngineMode_provisioned(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1493,6 +1657,10 @@ func TestAccRDSCluster_SnapshotIdentifierEngineMode_serverless(t *testing.T) { // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/6157 func TestAccRDSCluster_SnapshotIdentifierEngineVersion_different(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1523,6 +1691,10 @@ func TestAccRDSCluster_SnapshotIdentifierEngineVersion_different(t *testing.T) { // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/6157 func TestAccRDSCluster_SnapshotIdentifierEngineVersion_equal(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1552,6 +1724,10 @@ func TestAccRDSCluster_SnapshotIdentifierEngineVersion_equal(t *testing.T) { } func TestAccRDSCluster_SnapshotIdentifier_kmsKeyID(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1581,6 +1757,10 @@ func TestAccRDSCluster_SnapshotIdentifier_kmsKeyID(t *testing.T) { } func TestAccRDSCluster_SnapshotIdentifier_masterPassword(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1609,6 +1789,10 @@ func TestAccRDSCluster_SnapshotIdentifier_masterPassword(t *testing.T) { } func TestAccRDSCluster_SnapshotIdentifier_masterUsername(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1639,6 +1823,10 @@ func TestAccRDSCluster_SnapshotIdentifier_masterUsername(t *testing.T) { } func TestAccRDSCluster_SnapshotIdentifier_preferredBackupWindow(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1667,6 +1855,10 @@ func TestAccRDSCluster_SnapshotIdentifier_preferredBackupWindow(t *testing.T) { } func TestAccRDSCluster_SnapshotIdentifier_preferredMaintenanceWindow(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1695,6 +1887,10 @@ func TestAccRDSCluster_SnapshotIdentifier_preferredMaintenanceWindow(t *testing. } func TestAccRDSCluster_SnapshotIdentifier_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1724,6 +1920,10 @@ func TestAccRDSCluster_SnapshotIdentifier_tags(t *testing.T) { } func TestAccRDSCluster_SnapshotIdentifier_vpcSecurityGroupIDs(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1755,6 +1955,10 @@ func TestAccRDSCluster_SnapshotIdentifier_vpcSecurityGroupIDs(t *testing.T) { // vpc_security_group_ids is set (which triggered the resource update function), // and tags is set which was missing its ARN used for tagging func TestAccRDSCluster_SnapshotIdentifierVPCSecurityGroupIDs_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1784,6 +1988,10 @@ func TestAccRDSCluster_SnapshotIdentifierVPCSecurityGroupIDs_tags(t *testing.T) } func TestAccRDSCluster_SnapshotIdentifier_encryptedRestore(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster, sourceDbCluster rds.DBCluster var dbClusterSnapshot rds.DBClusterSnapshot @@ -1814,6 +2022,10 @@ func TestAccRDSCluster_SnapshotIdentifier_encryptedRestore(t *testing.T) { } func TestAccRDSCluster_enableHTTPEndpoint(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbCluster rds.DBCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -2127,6 +2339,94 @@ resource "aws_rds_cluster" "test" { `, rName) } +func testAccClusterConfig_StorageType(rName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + state = "available" +} + +resource "aws_rds_cluster" "test" { + apply_immediately = true + availability_zones = [data.aws_availability_zones.available.names[0], data.aws_availability_zones.available.names[1], data.aws_availability_zones.available.names[2]] + cluster_identifier = %q + db_cluster_instance_class = "db.r6gd.xlarge" + engine = "mysql" + storage_type = "io1" + allocated_storage = 100 + iops = 1000 + master_password = "mustbeeightcharaters" + master_username = "test" + skip_final_snapshot = true +} +`, rName) +} + +func testAccClusterConfig_AllocatedStorage(rName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + state = "available" +} + +resource "aws_rds_cluster" "test" { + apply_immediately = true + availability_zones = [data.aws_availability_zones.available.names[0], data.aws_availability_zones.available.names[1], data.aws_availability_zones.available.names[2]] + cluster_identifier = %q + db_cluster_instance_class = "db.r6gd.xlarge" + engine = "mysql" + storage_type = "io1" + allocated_storage = 100 + iops = 1000 + master_password = "mustbeeightcharaters" + master_username = "test" + skip_final_snapshot = true +} +`, rName) +} + +func testAccClusterConfig_Iops(rName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + state = "available" +} + +resource "aws_rds_cluster" "test" { + apply_immediately = true + availability_zones = [data.aws_availability_zones.available.names[0], data.aws_availability_zones.available.names[1], data.aws_availability_zones.available.names[2]] + cluster_identifier = %q + db_cluster_instance_class = "db.r6gd.xlarge" + engine = "mysql" + storage_type = "io1" + allocated_storage = 100 + iops = 1000 + master_password = "mustbeeightcharaters" + master_username = "test" + skip_final_snapshot = true +} +`, rName) +} + +func testAccClusterConfig_DbClusterInstanceClass(rName string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + state = "available" +} + +resource "aws_rds_cluster" "test" { + apply_immediately = true + availability_zones = [data.aws_availability_zones.available.names[0], data.aws_availability_zones.available.names[1], data.aws_availability_zones.available.names[2]] + cluster_identifier = %q + db_cluster_instance_class = "db.r6gd.xlarge" + engine = "mysql" + storage_type = "io1" + allocated_storage = 100 + iops = 1000 + master_password = "mustbeeightcharaters" + master_username = "test" + skip_final_snapshot = true +} +`, rName) +} + func testAccClusterConfig_BacktrackWindow(backtrackWindow int) string { return fmt.Sprintf(` resource "aws_rds_cluster" "test" { @@ -2595,7 +2895,7 @@ func testAccClusterConfig_EngineVersion(upgrade bool, rInt int) string { return fmt.Sprintf(` data "aws_rds_engine_version" "test" { engine = "aurora-postgresql" - preferred_versions = ["9.6.3", "9.6.6", "9.6.8"] + preferred_versions = ["11.6", "11.7", "11.9"] } data "aws_rds_engine_version" "upgrade" { @@ -2666,19 +2966,19 @@ resource "aws_rds_cluster_instance" "test" { `, upgrade, rInt) } -func testAccClusterConfig_Port(rInt, port int) string { +func testAccClusterConfig_Port(rName string, port int) string { return fmt.Sprintf(` resource "aws_rds_cluster" "test" { - cluster_identifier = "tf-acc-test-%d" + cluster_identifier = %[1]q database_name = "mydb" - db_cluster_parameter_group_name = "default.aurora-postgresql11" + db_cluster_parameter_group_name = "default.aurora-postgresql13" engine = "aurora-postgresql" master_password = "mustbeeightcharaters" master_username = "foo" - port = %d + port = %[2]d skip_final_snapshot = true } -`, rInt, port) +`, rName, port) } func testAccClusterIncludingIAMRolesConfig(n int) string { @@ -3182,7 +3482,7 @@ func testAccClusterConfig_GlobalClusterIdentifier_EngineMode_Provisioned(rName s return fmt.Sprintf(` resource "aws_rds_global_cluster" "test" { engine = "aurora-postgresql" - engine_version = "10.11" + engine_version = "12.9" global_cluster_identifier = %[1]q } @@ -3409,10 +3709,20 @@ data "aws_availability_zones" "alternate" { } } +data "aws_rds_engine_version" "default" { + engine = "aurora-postgresql" +} + +data "aws_rds_orderable_db_instance" "test" { + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version + preferred_instance_classes = ["db.r5.large", "db.r5.xlarge", "db.r6g.large"] # Aurora global db may be limited to rx +} + resource "aws_rds_global_cluster" "test" { global_cluster_identifier = %[1]q - engine = "aurora-postgresql" - engine_version = "10.11" + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version } resource "aws_rds_cluster" "primary" { @@ -3431,7 +3741,7 @@ resource "aws_rds_cluster_instance" "primary" { engine = aws_rds_cluster.primary.engine engine_version = aws_rds_cluster.primary.engine_version identifier = "%[1]s-primary" - instance_class = "db.r4.large" # only db.r4 or db.r5 are valid for Aurora global db + instance_class = data.aws_rds_orderable_db_instance.test.instance_class } resource "aws_vpc" "alternate" { @@ -3599,7 +3909,7 @@ func testAccClusterConfig_SnapshotIdentifier_EngineVersion(rName string, same bo return fmt.Sprintf(` data "aws_rds_engine_version" "test" { engine = "aurora-postgresql" - preferred_versions = ["11.4", "10.4", "9.6.6"] + preferred_versions = ["13.3", "12.9", "11.14"] } data "aws_rds_engine_version" "upgrade" { diff --git a/internal/service/rds/enum.go b/internal/service/rds/consts.go similarity index 100% rename from internal/service/rds/enum.go rename to internal/service/rds/consts.go diff --git a/internal/service/rds/consts_test.go b/internal/service/rds/consts_test.go new file mode 100644 index 00000000000..b2e22376478 --- /dev/null +++ b/internal/service/rds/consts_test.go @@ -0,0 +1,12 @@ +package rds_test + +const ( + // Please make sure GovCloud and commercial support these since they vary + postgresPreferredInstanceClasses = `"db.t3.micro", "db.t3.small", "db.t2.small", "db.t2.medium"` + mySQLPreferredInstanceClasses = `"db.t3.micro", "db.t3.small", "db.t2.small", "db.t2.medium"` + mariaDBPreferredInstanceClasses = `"db.t3.micro", "db.t3.small", "db.t2.small", "db.t2.medium"` + oraclePreferredInstanceClasses = `"db.t3.medium", "db.t2.medium", "db.t3.large", "db.t2.large"` // Oracle requires at least a medium instance as a replica source + sqlServerPreferredInstanceClasses = `"db.t2.small", "db.t3.small"` + sqlServerSEPreferredInstanceClasses = `"db.m5.large", "db.m4.large", "db.r4.large"` + oracleSE2PreferredInstanceClasses = `"db.m5.large", "db.m4.large", "db.r4.large"` +) diff --git a/internal/service/rds/engine_version_data_source_test.go b/internal/service/rds/engine_version_data_source_test.go index 83189f55c7c..26d76153ca6 100644 --- a/internal/service/rds/engine_version_data_source_test.go +++ b/internal/service/rds/engine_version_data_source_test.go @@ -81,7 +81,7 @@ func TestAccRDSEngineVersionDataSource_preferred(t *testing.T) { { Config: testAccEngineVersionPreferredDataSourceConfig(), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, "version", "5.7.19"), + resource.TestCheckResourceAttr(dataSourceName, "version", "8.0.27"), ), }, }, @@ -129,9 +129,9 @@ func testAccEngineVersionPreCheck(t *testing.T) { func testAccEngineVersionBasicDataSourceConfig(engine, version, paramGroup string) string { return fmt.Sprintf(` data "aws_rds_engine_version" "test" { - engine = %q - version = %q - parameter_group_family = %q + engine = %[1]q + version = %[2]q + parameter_group_family = %[3]q } `, engine, version, paramGroup) } @@ -140,7 +140,7 @@ func testAccEngineVersionUpgradeTargetsDataSourceConfig() string { return ` data "aws_rds_engine_version" "test" { engine = "mysql" - version = "5.7.17" + version = "8.0.27" } ` } @@ -149,7 +149,7 @@ func testAccEngineVersionPreferredDataSourceConfig() string { return ` data "aws_rds_engine_version" "test" { engine = "mysql" - preferred_versions = ["85.9.12", "5.7.19", "5.7.17"] + preferred_versions = ["85.9.12", "8.0.27", "8.0.26"] } ` } diff --git a/internal/service/rds/event_subscription.go b/internal/service/rds/event_subscription.go index 4b45d79ee36..5b5711d5b5c 100644 --- a/internal/service/rds/event_subscription.go +++ b/internal/service/rds/event_subscription.go @@ -193,9 +193,7 @@ func resourceEventSubscriptionUpdate(d *schema.ResourceData, meta interface{}) e SubscriptionName: aws.String(d.Id()), } - if d.HasChange("enabled") { - input.Enabled = aws.Bool(d.Get("enabled").(bool)) - } + input.Enabled = aws.Bool(d.Get("enabled").(bool)) if d.HasChange("event_categories") { input.EventCategories = flex.ExpandStringSet(d.Get("event_categories").(*schema.Set)) diff --git a/internal/service/rds/global_cluster.go b/internal/service/rds/global_cluster.go index 8d5806da3da..a2d98929268 100644 --- a/internal/service/rds/global_cluster.go +++ b/internal/service/rds/global_cluster.go @@ -3,9 +3,12 @@ package rds import ( "fmt" "log" + "strings" "time" + "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/service/rds" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -16,7 +19,9 @@ import ( ) const ( - rdsGlobalClusterRemovalTimeout = 2 * time.Minute + GlobalClusterRemovalTimeout = 30 * time.Minute + globalClusterCreateTimeout = 30 * time.Minute + globalClusterUpdateTimeout = 90 * time.Minute ) func ResourceGlobalCluster() *schema.Resource { @@ -29,6 +34,12 @@ func ResourceGlobalCluster() *schema.Resource { State: schema.ImportStatePassthrough, }, + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(globalClusterCreateTimeout), + Update: schema.DefaultTimeout(globalClusterUpdateTimeout), + Delete: schema.DefaultTimeout(GlobalClusterRemovalTimeout), + }, + Schema: map[string]*schema.Schema{ "arn": { Type: schema.TypeString, @@ -153,7 +164,7 @@ func resourceGlobalClusterCreate(d *schema.ResourceData, meta interface{}) error d.SetId(aws.StringValue(output.GlobalCluster.GlobalClusterIdentifier)) - if err := waitForGlobalClusterCreation(conn, d.Id()); err != nil { + if err := waitForGlobalClusterCreation(conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { return fmt.Errorf("error waiting for RDS Global Cluster (%s) availability: %s", d.Id(), err) } @@ -213,7 +224,7 @@ func resourceGlobalClusterUpdate(d *schema.ResourceData, meta interface{}) error } if d.HasChange("engine_version") { - if err := resourceGlobalClusterUpgradeEngineVersion(d, conn); err != nil { + if err := globalClusterUpgradeEngineVersion(d, meta, d.Timeout(schema.TimeoutUpdate)); err != nil { return err } } @@ -229,26 +240,13 @@ func resourceGlobalClusterUpdate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("error deleting RDS Global Cluster: %s", err) } - if err := waitForGlobalClusterUpdate(conn, d.Id()); err != nil { + if err := waitForGlobalClusterUpdate(conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { return fmt.Errorf("error waiting for RDS Global Cluster (%s) update: %s", d.Id(), err) } return resourceGlobalClusterRead(d, meta) } -func resourceGlobalClusterGetIdByARN(conn *rds.RDS, arn string) string { - result, err := conn.DescribeDBClusters(&rds.DescribeDBClustersInput{}) - if err != nil { - return "" - } - for _, cluster := range result.DBClusters { - if aws.StringValue(cluster.DBClusterArn) == arn { - return aws.StringValue(cluster.DBClusterIdentifier) - } - } - return "" -} - func resourceGlobalClusterDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*conns.AWSClient).RDSConn @@ -281,7 +279,7 @@ func resourceGlobalClusterDelete(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("error removing RDS DB Cluster (%s) from Global Cluster (%s): %w", dbClusterArn, d.Id(), err) } - if err := waitForGlobalClusterRemoval(conn, dbClusterArn); err != nil { + if err := waitForGlobalClusterRemoval(conn, dbClusterArn, d.Timeout(schema.TimeoutDelete)); err != nil { return fmt.Errorf("error waiting for RDS DB Cluster (%s) removal from RDS Global Cluster (%s): %w", dbClusterArn, d.Id(), err) } } @@ -295,7 +293,7 @@ func resourceGlobalClusterDelete(d *schema.ResourceData, meta interface{}) error // Allow for eventual consistency // InvalidGlobalClusterStateFault: Global Cluster arn:aws:rds::123456789012:global-cluster:tf-acc-test-5618525093076697001-0 is not empty - err := resource.Retry(1*time.Minute, func() *resource.RetryError { + err := resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { _, err := conn.DeleteGlobalCluster(input) if tfawserr.ErrMessageContains(err, rds.ErrCodeInvalidGlobalClusterStateFault, "is not empty") { @@ -321,7 +319,7 @@ func resourceGlobalClusterDelete(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("error deleting RDS Global Cluster: %s", err) } - if err := WaitForGlobalClusterDeletion(conn, d.Id()); err != nil { + if err := WaitForGlobalClusterDeletion(conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { return fmt.Errorf("error waiting for RDS Global Cluster (%s) deletion: %s", d.Id(), err) } @@ -414,7 +412,7 @@ func DescribeGlobalClusterFromClusterARN(conn *rds.RDS, dbClusterARN string) (*r return globalCluster, err } -func rdsGlobalClusterRefreshFunc(conn *rds.RDS, globalClusterID string) resource.StateRefreshFunc { +func globalClusterRefreshFunc(conn *rds.RDS, globalClusterID string) resource.StateRefreshFunc { return func() (interface{}, string, error) { globalCluster, err := DescribeGlobalCluster(conn, globalClusterID) @@ -434,12 +432,12 @@ func rdsGlobalClusterRefreshFunc(conn *rds.RDS, globalClusterID string) resource } } -func waitForGlobalClusterCreation(conn *rds.RDS, globalClusterID string) error { +func waitForGlobalClusterCreation(conn *rds.RDS, globalClusterID string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{"creating"}, Target: []string{"available"}, - Refresh: rdsGlobalClusterRefreshFunc(conn, globalClusterID), - Timeout: 10 * time.Minute, + Refresh: globalClusterRefreshFunc(conn, globalClusterID), + Timeout: timeout, } log.Printf("[DEBUG] Waiting for RDS Global Cluster (%s) availability", globalClusterID) @@ -448,12 +446,12 @@ func waitForGlobalClusterCreation(conn *rds.RDS, globalClusterID string) error { return err } -func waitForGlobalClusterUpdate(conn *rds.RDS, globalClusterID string) error { +func waitForGlobalClusterUpdate(conn *rds.RDS, globalClusterID string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{"modifying", "upgrading"}, Target: []string{"available"}, - Refresh: rdsGlobalClusterRefreshFunc(conn, globalClusterID), - Timeout: 10 * time.Minute, + Refresh: globalClusterRefreshFunc(conn, globalClusterID), + Timeout: timeout, Delay: 30 * time.Second, } @@ -463,15 +461,15 @@ func waitForGlobalClusterUpdate(conn *rds.RDS, globalClusterID string) error { return err } -func WaitForGlobalClusterDeletion(conn *rds.RDS, globalClusterID string) error { +func WaitForGlobalClusterDeletion(conn *rds.RDS, globalClusterID string, timeout time.Duration) error { stateConf := &resource.StateChangeConf{ Pending: []string{ "available", "deleting", }, Target: []string{"deleted"}, - Refresh: rdsGlobalClusterRefreshFunc(conn, globalClusterID), - Timeout: 10 * time.Minute, + Refresh: globalClusterRefreshFunc(conn, globalClusterID), + Timeout: timeout, NotFoundChecks: 1, } @@ -485,11 +483,11 @@ func WaitForGlobalClusterDeletion(conn *rds.RDS, globalClusterID string) error { return err } -func waitForGlobalClusterRemoval(conn *rds.RDS, dbClusterIdentifier string) error { +func waitForGlobalClusterRemoval(conn *rds.RDS, dbClusterIdentifier string, timeout time.Duration) error { var globalCluster *rds.GlobalCluster stillExistsErr := fmt.Errorf("RDS DB Cluster still exists in RDS Global Cluster") - err := resource.Retry(rdsGlobalClusterRemovalTimeout, func() *resource.RetryError { + err := resource.Retry(timeout, func() *resource.RetryError { var err error globalCluster, err = DescribeGlobalClusterFromClusterARN(conn, dbClusterIdentifier) @@ -520,19 +518,25 @@ func waitForGlobalClusterRemoval(conn *rds.RDS, dbClusterIdentifier string) erro return nil } -func resourceGlobalClusterUpgradeMajorEngineVersion(clusterId string, engineVersion string, conn *rds.RDS) error { +func globalClusterUpgradeMajorEngineVersion(meta interface{}, clusterID string, engineVersion string, timeout time.Duration) error { + conn := meta.(*conns.AWSClient).RDSConn + input := &rds.ModifyGlobalClusterInput{ - GlobalClusterIdentifier: aws.String(clusterId), + GlobalClusterIdentifier: aws.String(clusterID), } + input.AllowMajorVersionUpgrade = aws.Bool(true) input.EngineVersion = aws.String(engineVersion) - err := resource.Retry(rdsClusterInitiateUpgradeTimeout, func() *resource.RetryError { + + err := resource.Retry(timeout, func() *resource.RetryError { _, err := conn.ModifyGlobalCluster(input) + if err != nil { if tfawserr.ErrCodeEquals(err, rds.ErrCodeGlobalClusterNotFoundFault) { return resource.NonRetryableError(err) } - if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "ModifyGlobalCluster only supports Major Version Upgrades. To patch the members of your global cluster to a newer minor version you need to call ModifyDbCluster in each one of them.") { + + if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "only supports Major Version Upgrades") { return resource.NonRetryableError(err) } @@ -541,73 +545,197 @@ func resourceGlobalClusterUpgradeMajorEngineVersion(clusterId string, engineVers return nil }) + if tfresource.TimedOut(err) { _, err = conn.ModifyGlobalCluster(input) } + + if err != nil { + return fmt.Errorf("while upgrading major version of RDS Global Cluster (%s): %w", clusterID, err) + } + + globalCluster, err := DescribeGlobalCluster(conn, clusterID) + + if err != nil { + return fmt.Errorf("while upgrading major version of RDS Global Cluster (%s): %w", clusterID, err) + } + + for _, clusterMember := range globalCluster.GlobalClusterMembers { + arnID := aws.StringValue(clusterMember.DBClusterArn) + + if arnID == "" { + continue + } + + dbi, clusterRegion, err := ClusterIDRegionFromARN(arnID) + + if err != nil { + return fmt.Errorf("while upgrading RDS Global Cluster Cluster minor engine version: %w", err) + } + + if dbi == "" { + continue + } + + useConn := conn // clusters may not all be in the same region + + if clusterRegion != meta.(*conns.AWSClient).Region { + useConn = rds.New(meta.(*conns.AWSClient).Session, aws.NewConfig().WithRegion(clusterRegion)) + } + + if err := waitForClusterUpdate(useConn, dbi, timeout); err != nil { + return fmt.Errorf("failed to update engine_version, waiting for RDS Global Cluster (%s) to update: %s", dbi, err) + } + } + return err } -func resourceGlobalClusterUpgradeMinorEngineVersion(clusterMembers *schema.Set, engineVersion string, conn *rds.RDS) error { +func ClusterIDRegionFromARN(arnID string) (string, string, error) { + parsedARN, err := arn.Parse(arnID) + + if err != nil { + return "", "", fmt.Errorf("could not parse ARN (%s): %w", arnID, err) + } + + dbi := "" + + if parsedARN.Resource != "" { + parts := strings.Split(parsedARN.Resource, ":") + + if len(parts) < 2 { + return "", "", fmt.Errorf("could not get DB Cluster ID from parsing ARN (%s): %w", arnID, err) + } + + if parsedARN.Service != endpoints.RdsServiceID || parts[0] != "cluster" { + return "", "", fmt.Errorf("wrong ARN (%s) for a DB Cluster", arnID) + } + + dbi = parts[1] + } + + return dbi, parsedARN.Region, nil +} + +func globalClusterUpgradeMinorEngineVersion(meta interface{}, clusterMembers *schema.Set, clusterID, engineVersion string, timeout time.Duration) error { + conn := meta.(*conns.AWSClient).RDSConn + + log.Printf("[INFO] Performing RDS Global Cluster (%s) minor version (%s) upgrade", clusterID, engineVersion) + for _, clusterMemberRaw := range clusterMembers.List() { clusterMember := clusterMemberRaw.(map[string]interface{}) - if clusterMemberArn, ok := clusterMember["db_cluster_arn"]; ok && clusterMemberArn.(string) != "" { - modInput := &rds.ModifyDBClusterInput{ - ApplyImmediately: aws.Bool(true), - DBClusterIdentifier: aws.String(clusterMemberArn.(string)), - EngineVersion: aws.String(engineVersion), - } - err := resource.Retry(rdsClusterInitiateUpgradeTimeout, func() *resource.RetryError { - _, err := conn.ModifyDBCluster(modInput) - if err != nil { - if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "IAM role ARN value is invalid or does not include the required permissions") { - return resource.RetryableError(err) - } - - if tfawserr.ErrMessageContains(err, rds.ErrCodeInvalidDBClusterStateFault, "Cannot modify engine version without a primary instance in DB cluster") { - return resource.NonRetryableError(err) - } - - if tfawserr.ErrCodeEquals(err, rds.ErrCodeInvalidDBClusterStateFault) { - return resource.RetryableError(err) - } + + // DBClusterIdentifier supposedly can be either ARN or ID, and both used to work, + // but as of now, only ID works + if clusterMemberArn, ok := clusterMember["db_cluster_arn"]; !ok || clusterMemberArn.(string) == "" { + continue + } + + arnID := clusterMember["db_cluster_arn"].(string) + + dbi, clusterRegion, err := ClusterIDRegionFromARN(arnID) + + if err != nil { + return fmt.Errorf("while upgrading RDS Global Cluster Cluster minor engine version: %w", err) + } + + if dbi == "" { + continue + } + + useConn := conn + + if clusterRegion != meta.(*conns.AWSClient).Region { + useConn = rds.New(meta.(*conns.AWSClient).Session, aws.NewConfig().WithRegion(clusterRegion)) + } + + modInput := &rds.ModifyDBClusterInput{ + ApplyImmediately: aws.Bool(true), + DBClusterIdentifier: aws.String(dbi), + EngineVersion: aws.String(engineVersion), + } + + log.Printf("[INFO] Performing RDS Global Cluster (%s) Cluster (%s) minor version (%s) upgrade", clusterID, dbi, engineVersion) + + err = resource.Retry(timeout, func() *resource.RetryError { + _, err := useConn.ModifyDBCluster(modInput) + + if err != nil { + if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "IAM role ARN value is invalid or does not include the required permissions") { + return resource.RetryableError(err) + } + + if tfawserr.ErrMessageContains(err, rds.ErrCodeInvalidDBClusterStateFault, "Cannot modify engine version without a primary instance in DB cluster") { return resource.NonRetryableError(err) } - return nil - }) - if tfresource.TimedOut(err) { - _, err := conn.ModifyDBCluster(modInput) - if err != nil { - return err + + if tfawserr.ErrCodeEquals(err, rds.ErrCodeInvalidDBClusterStateFault) { + return resource.RetryableError(err) } + + return resource.NonRetryableError(err) } + return nil + }) + + if tfresource.TimedOut(err) { + _, err := useConn.ModifyDBCluster(modInput) + if err != nil { - return fmt.Errorf("Failed to update engine_version on global cluster member (%s): %s", clusterMemberArn, err) + return err } } + + if err != nil { + return fmt.Errorf("failed to update engine_version on RDS Global Cluster Cluster (%s): %s", dbi, err) + } + + log.Printf("[INFO] Waiting for RDS Global Cluster (%s) Cluster (%s) minor version (%s) upgrade", clusterID, dbi, engineVersion) + if err := waitForClusterUpdate(useConn, dbi, timeout); err != nil { + return fmt.Errorf("failed to update engine_version, waiting for RDS Global Cluster Cluster (%s) to update: %s", dbi, err) + } } + + globalCluster, err := DescribeGlobalCluster(conn, clusterID) + + if tfawserr.ErrCodeEquals(err, rds.ErrCodeGlobalClusterNotFoundFault) { + return fmt.Errorf("after upgrading engine_version, could not find RDS Global Cluster (%s): %s", clusterID, err) + } + + if err != nil { + return fmt.Errorf("after minor engine_version upgrade to RDS Global Cluster (%s): %s", clusterID, err) + } + + if globalCluster == nil { + return fmt.Errorf("after minor engine_version upgrade to RDS Global Cluster (%s): empty response", clusterID) + } + + if aws.StringValue(globalCluster.EngineVersion) != engineVersion { + log.Printf("[DEBUG] RDS Global Cluster (%s) upgrade did not take effect, trying again", clusterID) + return globalClusterUpgradeMinorEngineVersion(meta, clusterMembers, clusterID, engineVersion, timeout) + } + return nil } -func resourceGlobalClusterUpgradeEngineVersion(d *schema.ResourceData, conn *rds.RDS) error { +func globalClusterUpgradeEngineVersion(d *schema.ResourceData, meta interface{}, timeout time.Duration) error { log.Printf("[DEBUG] Upgrading RDS Global Cluster (%s) engine version: %s", d.Id(), d.Get("engine_version")) - err := resourceGlobalClusterUpgradeMajorEngineVersion(d.Id(), d.Get("engine_version").(string), conn) - if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "ModifyGlobalCluster only supports Major Version Upgrades. To patch the members of your global cluster to a newer minor version you need to call ModifyDbCluster in each one of them.") { - err = resourceGlobalClusterUpgradeMinorEngineVersion(d.Get("global_cluster_members").(*schema.Set), d.Get("engine_version").(string), conn) + + err := globalClusterUpgradeMajorEngineVersion(meta, d.Id(), d.Get("engine_version").(string), timeout) + + if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "only supports Major Version Upgrades") { + err = globalClusterUpgradeMinorEngineVersion(meta, d.Get("global_cluster_members").(*schema.Set), d.Id(), d.Get("engine_version").(string), timeout) + if err != nil { - return err + return fmt.Errorf("while upgrading minor version of RDS Global Cluster (%s): %w", d.Id(), err) } - } else if err != nil { - return err + + return nil } - globalCluster, err := DescribeGlobalCluster(conn, d.Id()) + if err != nil { - return err - } - for _, clusterMember := range globalCluster.GlobalClusterMembers { - err := waitForRDSClusterUpdate(conn, resourceGlobalClusterGetIdByARN(conn, aws.StringValue(clusterMember.DBClusterArn)), d.Timeout(schema.TimeoutUpdate)) - if err != nil { - return err - } + return fmt.Errorf("while upgrading major version of RDS Global Cluster (%s): %w", d.Id(), err) } + return nil } diff --git a/internal/service/rds/global_cluster_test.go b/internal/service/rds/global_cluster_test.go index 893f88d74bd..dc2c4d15892 100644 --- a/internal/service/rds/global_cluster_test.go +++ b/internal/service/rds/global_cluster_test.go @@ -11,12 +11,84 @@ import ( "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfrds "github.com/hashicorp/terraform-provider-aws/internal/service/rds" ) +func TestClusterIDRegionFromARN(t *testing.T) { + testCases := []struct { + TestName string + Input string + ExpectedID string + ExpectedRegion string + ExpectedErr bool + }{ + { + TestName: "empty", + Input: "", + ExpectedID: "", + ExpectedRegion: "", + ExpectedErr: true, + }, + { + TestName: "normal ARN", + Input: "arn:aws:rds:us-west-2:012345678901:cluster:tf-acc-test-1467354933239945971", // lintignore:AWSAT003,AWSAT005 + ExpectedID: "tf-acc-test-1467354933239945971", + ExpectedRegion: "us-west-2", // lintignore:AWSAT003 + ExpectedErr: false, + }, + { + TestName: "another good ARN", + Input: "arn:aws:rds:us-east-1:012345678901:cluster:tf-acc-test-1467354933239945971", // lintignore:AWSAT003,AWSAT005 + ExpectedID: "tf-acc-test-1467354933239945971", + ExpectedRegion: "us-east-1", // lintignore:AWSAT003 + ExpectedErr: false, + }, + { + TestName: "no account", + Input: "arn:aws:rds:us-east-2::cluster:tf-acc-test-1467354933239945971", // lintignore:AWSAT003,AWSAT005 + ExpectedID: "tf-acc-test-1467354933239945971", + ExpectedRegion: "us-east-2", // lintignore:AWSAT003 + ExpectedErr: false, + }, + { + TestName: "wrong service", + Input: "arn:aws:connect:us-west-2:012345678901:instance/1032bdc4-d72c-5490-a9fa-3c9b4dba67bb", // lintignore:AWSAT003,AWSAT005 + ExpectedID: "", + ExpectedRegion: "", + ExpectedErr: true, + }, + { + TestName: "wrong resource", + Input: "arn:aws:rds:us-east-2::notacluster:tf-acc-test-1467354933239945971", // lintignore:AWSAT003,AWSAT005 + ExpectedID: "", + ExpectedRegion: "", + ExpectedErr: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + gotID, gotRegion, gotErr := tfrds.ClusterIDRegionFromARN(testCase.Input) + + if gotErr != nil && !testCase.ExpectedErr { + t.Errorf("got no error, expected one: %s", testCase.Input) + } + + if gotID != testCase.ExpectedID { + t.Errorf("got %s, expected %s", gotID, testCase.ExpectedID) + } + + if gotRegion != testCase.ExpectedRegion { + t.Errorf("got %s, expected %s", gotRegion, testCase.ExpectedRegion) + } + }) + } +} + func TestAccRDSGlobalCluster_basic(t *testing.T) { var globalCluster1 rds.GlobalCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -183,10 +255,10 @@ func TestAccRDSGlobalCluster_EngineVersion_aurora(t *testing.T) { CheckDestroy: testAccCheckGlobalClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccGlobalClusterEngineVersionConfig(rName, "aurora", "5.6.10a"), + Config: testAccGlobalClusterEngineVersionConfig(rName, "aurora"), Check: resource.ComposeTestCheckFunc( testAccCheckGlobalClusterExists(resourceName, &globalCluster1), - resource.TestCheckResourceAttr(resourceName, "engine_version", "5.6.10a"), + resource.TestCheckResourceAttrPair(resourceName, "engine_version", "data.aws_rds_engine_version.default", "version"), ), }, { @@ -198,7 +270,11 @@ func TestAccRDSGlobalCluster_EngineVersion_aurora(t *testing.T) { }) } -func TestAccRDSGlobalCluster_engineVersionUpdateMinor(t *testing.T) { +func TestAccRDSGlobalCluster_EngineVersion_updateMinor(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var globalCluster1, globalCluster2 rds.GlobalCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_global_cluster.test" @@ -210,17 +286,19 @@ func TestAccRDSGlobalCluster_engineVersionUpdateMinor(t *testing.T) { CheckDestroy: testAccCheckGlobalClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora", "5.6.mysql_aurora.1.22.2"), + //Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora", "5.6.mysql_aurora.1.22.2"), + Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora-postgresql", "13.4"), Check: resource.ComposeTestCheckFunc( testAccCheckGlobalClusterExists(resourceName, &globalCluster1), ), }, { - Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora", "5.6.mysql_aurora.1.23.2"), + //Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora", "5.6.mysql_aurora.1.23.2"), + Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora-postgresql", "13.5"), Check: resource.ComposeTestCheckFunc( testAccCheckGlobalClusterExists(resourceName, &globalCluster2), testAccCheckGlobalClusterNotRecreated(&globalCluster1, &globalCluster2), - resource.TestCheckResourceAttr(resourceName, "engine_version", "5.6.mysql_aurora.1.23.2"), + resource.TestCheckResourceAttr(resourceName, "engine_version", "13.5"), ), }, { @@ -232,7 +310,11 @@ func TestAccRDSGlobalCluster_engineVersionUpdateMinor(t *testing.T) { }) } -func TestAccRDSGlobalCluster_engineVersionUpdateMajor(t *testing.T) { +func TestAccRDSGlobalCluster_EngineVersion_updateMajor(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var globalCluster1, globalCluster2 rds.GlobalCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_global_cluster.test" @@ -244,18 +326,17 @@ func TestAccRDSGlobalCluster_engineVersionUpdateMajor(t *testing.T) { CheckDestroy: testAccCheckGlobalClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora", "5.6.mysql_aurora.1.22.2"), + Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora", "5.6.mysql_aurora.1.23.4"), Check: resource.ComposeTestCheckFunc( testAccCheckGlobalClusterExists(resourceName, &globalCluster1), ), }, { - Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora", "5.7.mysql_aurora.2.07.2"), - ExpectNonEmptyPlan: true, + Config: testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, "aurora-mysql", "5.7.mysql_aurora.2.10.2"), Check: resource.ComposeTestCheckFunc( testAccCheckGlobalClusterExists(resourceName, &globalCluster2), testAccCheckGlobalClusterNotRecreated(&globalCluster1, &globalCluster2), - resource.TestCheckResourceAttr(resourceName, "engine_version", "5.7.mysql_aurora.2.07.2"), + resource.TestCheckResourceAttr(resourceName, "engine_version", "5.7.mysql_aurora.2.10.2"), ), }, { @@ -267,6 +348,78 @@ func TestAccRDSGlobalCluster_engineVersionUpdateMajor(t *testing.T) { }) } +func TestAccRDSGlobalCluster_EngineVersion_updateMinorMultiRegion(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var providers []*schema.Provider + var globalCluster1, globalCluster2 rds.GlobalCluster + rNameGlobal := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) // don't need to be unique but makes debugging easier + rNamePrimary := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rNameSecondary := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckGlobalCluster(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProviderFactories: acctest.FactoriesAlternate(&providers), + CheckDestroy: testAccCheckGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGlobalClusterEngineVersionUpgradeMultiRegionConfig(rNameGlobal, rNamePrimary, rNameSecondary, "aurora-mysql", "5.7.mysql_aurora.2.07.5"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalClusterExists(resourceName, &globalCluster1), + ), + }, + { + Config: testAccGlobalClusterEngineVersionUpgradeMultiRegionConfig(rNameGlobal, rNamePrimary, rNameSecondary, "aurora-mysql", "5.7.mysql_aurora.2.07.7"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalClusterExists(resourceName, &globalCluster2), + testAccCheckGlobalClusterNotRecreated(&globalCluster1, &globalCluster2), + resource.TestCheckResourceAttr(resourceName, "engine_version", "5.7.mysql_aurora.2.07.7"), + ), + }, + }, + }) +} + +func TestAccRDSGlobalCluster_EngineVersion_updateMajorMultiRegion(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var providers []*schema.Provider + var globalCluster1, globalCluster2 rds.GlobalCluster + rNameGlobal := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) // don't need to be unique but makes debugging easier + rNamePrimary := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rNameSecondary := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_rds_global_cluster.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheckGlobalCluster(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProviderFactories: acctest.FactoriesAlternate(&providers), + CheckDestroy: testAccCheckGlobalClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGlobalClusterEngineVersionUpgradeMultiRegionConfig(rNameGlobal, rNamePrimary, rNameSecondary, "aurora", "5.6.mysql_aurora.1.23.4"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalClusterExists(resourceName, &globalCluster1), + ), + }, + { + Config: testAccGlobalClusterEngineVersionUpgradeMultiRegionConfig(rNameGlobal, rNamePrimary, rNameSecondary, "aurora-mysql", "5.7.mysql_aurora.2.10.2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckGlobalClusterExists(resourceName, &globalCluster2), + testAccCheckGlobalClusterNotRecreated(&globalCluster1, &globalCluster2), + resource.TestCheckResourceAttr(resourceName, "engine_version", "5.7.mysql_aurora.2.10.2"), + ), + }, + }, + }) +} + func TestAccRDSGlobalCluster_EngineVersion_auroraMySQL(t *testing.T) { var globalCluster1 rds.GlobalCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -279,10 +432,10 @@ func TestAccRDSGlobalCluster_EngineVersion_auroraMySQL(t *testing.T) { CheckDestroy: testAccCheckGlobalClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccGlobalClusterEngineVersionConfig(rName, "aurora-mysql", "5.7.mysql_aurora.2.07.1"), + Config: testAccGlobalClusterEngineVersionConfig(rName, "aurora-mysql"), Check: resource.ComposeTestCheckFunc( testAccCheckGlobalClusterExists(resourceName, &globalCluster1), - resource.TestCheckResourceAttr(resourceName, "engine_version", "5.7.mysql_aurora.2.07.1"), + resource.TestCheckResourceAttrPair(resourceName, "engine_version", "data.aws_rds_engine_version.default", "version"), ), }, { @@ -294,7 +447,7 @@ func TestAccRDSGlobalCluster_EngineVersion_auroraMySQL(t *testing.T) { }) } -func TestAccRDSGlobalCluster_EngineVersion_auroraPostgresql(t *testing.T) { +func TestAccRDSGlobalCluster_EngineVersion_auroraPostgreSQL(t *testing.T) { var globalCluster1 rds.GlobalCluster rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_global_cluster.test" @@ -306,10 +459,10 @@ func TestAccRDSGlobalCluster_EngineVersion_auroraPostgresql(t *testing.T) { CheckDestroy: testAccCheckGlobalClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccGlobalClusterEngineVersionConfig(rName, "aurora-postgresql", "10.11"), + Config: testAccGlobalClusterEngineVersionConfig(rName, "aurora-postgresql"), Check: resource.ComposeTestCheckFunc( testAccCheckGlobalClusterExists(resourceName, &globalCluster1), - resource.TestCheckResourceAttr(resourceName, "engine_version", "10.11"), + resource.TestCheckResourceAttrPair(resourceName, "engine_version", "data.aws_rds_engine_version.default", "version"), ), }, { @@ -489,7 +642,7 @@ func testAccCheckGlobalClusterDisappears(globalCluster *rds.GlobalCluster) resou return err } - return tfrds.WaitForGlobalClusterDeletion(conn, aws.StringValue(globalCluster.GlobalClusterIdentifier)) + return tfrds.WaitForGlobalClusterDeletion(conn, aws.StringValue(globalCluster.GlobalClusterIdentifier), tfrds.GlobalClusterRemovalTimeout) } } @@ -532,7 +685,7 @@ func testAccPreCheckGlobalCluster(t *testing.T) { func testAccGlobalClusterConfig(rName string) string { return fmt.Sprintf(` resource "aws_rds_global_cluster" "test" { - global_cluster_identifier = %q + global_cluster_identifier = %[1]q } `, rName) } @@ -540,8 +693,8 @@ resource "aws_rds_global_cluster" "test" { func testAccGlobalClusterDatabaseNameConfig(rName, databaseName string) string { return fmt.Sprintf(` resource "aws_rds_global_cluster" "test" { - database_name = %q - global_cluster_identifier = %q + database_name = %[1]q + global_cluster_identifier = %[2]q } `, databaseName, rName) } @@ -549,8 +702,8 @@ resource "aws_rds_global_cluster" "test" { func testAccGlobalClusterDeletionProtectionConfig(rName string, deletionProtection bool) string { return fmt.Sprintf(` resource "aws_rds_global_cluster" "test" { - deletion_protection = %t - global_cluster_identifier = %q + deletion_protection = %[1]t + global_cluster_identifier = %[2]q } `, deletionProtection, rName) } @@ -558,27 +711,37 @@ resource "aws_rds_global_cluster" "test" { func testAccGlobalClusterEngineConfig(rName, engine string) string { return fmt.Sprintf(` resource "aws_rds_global_cluster" "test" { - engine = %q - global_cluster_identifier = %q + engine = %[1]q + global_cluster_identifier = %[2]q } `, engine, rName) } -func testAccGlobalClusterEngineVersionConfig(rName, engine, engineVersion string) string { +func testAccGlobalClusterEngineVersionConfig(rName, engine string) string { return fmt.Sprintf(` +data "aws_rds_engine_version" "default" { + engine = %[1]q +} + resource "aws_rds_global_cluster" "test" { - engine = %q - engine_version = %q - global_cluster_identifier = %q + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version + global_cluster_identifier = %[2]q } -`, engine, engineVersion, rName) +`, engine, rName) } func testAccGlobalClusterWithPrimaryEngineVersionConfig(rName, engine, engineVersion string) string { return fmt.Sprintf(` +data "aws_rds_orderable_db_instance" "test" { + engine = %[1]q + engine_version = %[2]q + preferred_instance_classes = ["db.r5.large", "db.r5.xlarge", "db.r6g.large"] # Aurora global db may be limited to rx +} + resource "aws_rds_global_cluster" "test" { - engine = %[1]q - engine_version = %[2]q + engine = data.aws_rds_orderable_db_instance.test.engine + engine_version = data.aws_rds_orderable_db_instance.test.engine_version global_cluster_identifier = %[3]q } @@ -586,11 +749,13 @@ resource "aws_rds_cluster" "test" { apply_immediately = true allow_major_version_upgrade = true cluster_identifier = %[3]q + engine = data.aws_rds_orderable_db_instance.test.engine + engine_version = data.aws_rds_orderable_db_instance.test.engine_version master_password = "mustbeeightcharacters" master_username = "test" skip_final_snapshot = true - global_cluster_identifier = aws_rds_global_cluster.test.global_cluster_identifier + global_cluster_identifier = aws_rds_global_cluster.test.id lifecycle { ignore_changes = [global_cluster_identifier] @@ -600,8 +765,10 @@ resource "aws_rds_cluster" "test" { resource "aws_rds_cluster_instance" "test" { apply_immediately = true cluster_identifier = aws_rds_cluster.test.id + engine = data.aws_rds_orderable_db_instance.test.engine + engine_version = data.aws_rds_orderable_db_instance.test.engine_version identifier = %[3]q - instance_class = "db.r3.large" + instance_class = data.aws_rds_orderable_db_instance.test.instance_class lifecycle { ignore_changes = [engine_version] @@ -610,12 +777,123 @@ resource "aws_rds_cluster_instance" "test" { `, engine, engineVersion, rName) } +func testAccGlobalClusterEngineVersionUpgradeMultiRegionConfig(rNameGlobal, rNamePrimary, rNameSecondary, engine, engineVersion string) string { + return acctest.ConfigCompose( + acctest.ConfigMultipleRegionProvider(2), + fmt.Sprintf(` +data "aws_region" "current" {} + +data "aws_availability_zones" "alternate" { + provider = "awsalternate" + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_rds_global_cluster" "test" { + global_cluster_identifier = %[1]q + engine = %[2]q + engine_version = %[3]q +} + +resource "aws_rds_cluster" "primary" { + allow_major_version_upgrade = true + apply_immediately = true + cluster_identifier = %[4]q + database_name = "totoro" + engine = aws_rds_global_cluster.test.engine + engine_version = aws_rds_global_cluster.test.engine_version + global_cluster_identifier = aws_rds_global_cluster.test.id + master_password = "satsukimae" + master_username = "maesatsuki" + skip_final_snapshot = true + + lifecycle { + ignore_changes = [engine_version] + } +} + +resource "aws_rds_cluster_instance" "primary" { + apply_immediately = true + cluster_identifier = aws_rds_cluster.primary.id + engine = aws_rds_cluster.primary.engine + engine_version = aws_rds_cluster.primary.engine_version + identifier = %[4]q + instance_class = "db.r4.large" +} + +resource "aws_vpc" "alternate" { + provider = "awsalternate" + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[5]q + } +} + +resource "aws_subnet" "alternate" { + provider = "awsalternate" + count = 3 + vpc_id = aws_vpc.alternate.id + availability_zone = data.aws_availability_zones.alternate.names[count.index] + cidr_block = "10.0.${count.index}.0/24" + + tags = { + Name = %[5]q + } +} + +resource "aws_db_subnet_group" "alternate" { + provider = "awsalternate" + name = %[5]q + subnet_ids = aws_subnet.alternate[*].id +} + +resource "aws_rds_cluster" "secondary" { + provider = "awsalternate" + allow_major_version_upgrade = true + apply_immediately = true + cluster_identifier = %[5]q + engine = aws_rds_global_cluster.test.engine + engine_version = aws_rds_global_cluster.test.engine_version + global_cluster_identifier = aws_rds_global_cluster.test.id + skip_final_snapshot = true + + lifecycle { + ignore_changes = [ + replication_source_identifier, + engine_version, + ] + } + + depends_on = [aws_rds_cluster_instance.primary] +} + +resource "aws_rds_cluster_instance" "secondary" { + provider = "awsalternate" + apply_immediately = true + cluster_identifier = aws_rds_cluster.secondary.id + engine = aws_rds_cluster.secondary.engine + engine_version = aws_rds_cluster.secondary.engine_version + identifier = %[5]q + instance_class = "db.r4.large" +} +`, rNameGlobal, engine, engineVersion, rNamePrimary, rNameSecondary)) +} + func testAccGlobalClusterSourceClusterIdentifierConfig(rName string) string { return fmt.Sprintf(` +data "aws_rds_engine_version" "default" { + engine = "aurora-postgresql" +} + resource "aws_rds_cluster" "test" { cluster_identifier = %[1]q - engine = "aurora-postgresql" - engine_version = "10.11" # Minimum supported version for Global Clusters + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version master_password = "mustbeeightcharacters" master_username = "test" skip_final_snapshot = true @@ -637,10 +915,14 @@ resource "aws_rds_global_cluster" "test" { func testAccGlobalClusterSourceClusterIdentifierStorageEncryptedConfig(rName string) string { return fmt.Sprintf(` +data "aws_rds_engine_version" "default" { + engine = "aurora-postgresql" +} + resource "aws_rds_cluster" "test" { cluster_identifier = %[1]q - engine = "aurora-postgresql" - engine_version = "10.11" # Minimum supported version for Global Clusters + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version master_password = "mustbeeightcharacters" master_username = "test" skip_final_snapshot = true @@ -664,8 +946,8 @@ resource "aws_rds_global_cluster" "test" { func testAccGlobalClusterStorageEncryptedConfig(rName string, storageEncrypted bool) string { return fmt.Sprintf(` resource "aws_rds_global_cluster" "test" { - global_cluster_identifier = %q - storage_encrypted = %t + global_cluster_identifier = %[1]q + storage_encrypted = %[2]t } `, rName, storageEncrypted) } diff --git a/internal/service/rds/instance.go b/internal/service/rds/instance.go index c8cfbe783df..a4b4c4c20ed 100644 --- a/internal/service/rds/instance.go +++ b/internal/service/rds/instance.go @@ -1808,7 +1808,7 @@ func resourceInstanceUpdate(d *schema.ResourceData, meta interface{}) error { if requestUpdate { log.Printf("[DEBUG] DB Instance Modification request: %s", req) - err := resource.Retry(tfiam.PropagationTimeout, func() *resource.RetryError { + err := resource.Retry(d.Timeout(schema.TimeoutUpdate), func() *resource.RetryError { _, err := conn.ModifyDBInstance(req) // Retry for IAM eventual consistency @@ -1816,6 +1816,11 @@ func resourceInstanceUpdate(d *schema.ResourceData, meta interface{}) error { return resource.RetryableError(err) } + // InvalidDBInstanceState: RDS is configuring Enhanced Monitoring or Performance Insights for this DB instance. Try your request later. + if tfawserr.ErrMessageContains(err, rds.ErrCodeInvalidDBInstanceStateFault, "your request later") { + return resource.RetryableError(err) + } + if err != nil { return resource.NonRetryableError(err) } @@ -1828,7 +1833,7 @@ func resourceInstanceUpdate(d *schema.ResourceData, meta interface{}) error { } if err != nil { - return fmt.Errorf("Error modifying DB Instance %s: %w", d.Id(), err) + return fmt.Errorf("modifying DB Instance %s: %w", d.Id(), err) } log.Printf("[DEBUG] Waiting for DB Instance (%s) to be available", d.Id()) diff --git a/internal/service/rds/instance_data_source_test.go b/internal/service/rds/instance_data_source_test.go index 59164d71ac2..254aac723e1 100644 --- a/internal/service/rds/instance_data_source_test.go +++ b/internal/service/rds/instance_data_source_test.go @@ -11,32 +11,38 @@ import ( ) func TestAccRDSInstanceDataSource_basic(t *testing.T) { - rInt := sdkacctest.RandInt() + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_db_instance.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), Providers: acctest.Providers, Steps: []resource.TestStep{ { - Config: testAccInstanceDataSourceConfig(rInt), + Config: testAccInstanceDataSourceConfig(rName), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "address"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "allocated_storage"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "auto_minor_version_upgrade"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "db_instance_class"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "db_name"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "db_subnet_group"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "endpoint"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "engine"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "hosted_zone_id"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "master_username"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "port"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "multi_az"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "enabled_cloudwatch_logs_exports.0"), - resource.TestCheckResourceAttrSet("data.aws_db_instance.bar", "enabled_cloudwatch_logs_exports.1"), - resource.TestCheckResourceAttrPair("data.aws_db_instance.bar", "resource_id", "aws_db_instance.bar", "resource_id"), - resource.TestCheckResourceAttrPair("data.aws_db_instance.bar", "tags.%", "aws_db_instance.bar", "tags.%"), - resource.TestCheckResourceAttrPair("data.aws_db_instance.bar", "tags.Environment", "aws_db_instance.bar", "tags.Environment"), + resource.TestCheckResourceAttrSet(dataSourceName, "address"), + resource.TestCheckResourceAttrSet(dataSourceName, "allocated_storage"), + resource.TestCheckResourceAttrSet(dataSourceName, "auto_minor_version_upgrade"), + resource.TestCheckResourceAttrSet(dataSourceName, "db_instance_class"), + resource.TestCheckResourceAttrSet(dataSourceName, "db_name"), + resource.TestCheckResourceAttrSet(dataSourceName, "db_subnet_group"), + resource.TestCheckResourceAttrSet(dataSourceName, "endpoint"), + resource.TestCheckResourceAttrSet(dataSourceName, "engine"), + resource.TestCheckResourceAttrSet(dataSourceName, "hosted_zone_id"), + resource.TestCheckResourceAttrSet(dataSourceName, "master_username"), + resource.TestCheckResourceAttrSet(dataSourceName, "port"), + resource.TestCheckResourceAttrSet(dataSourceName, "multi_az"), + resource.TestCheckResourceAttrSet(dataSourceName, "enabled_cloudwatch_logs_exports.0"), + resource.TestCheckResourceAttrSet(dataSourceName, "enabled_cloudwatch_logs_exports.1"), + resource.TestCheckResourceAttrPair(dataSourceName, "resource_id", "aws_db_instance.test", "resource_id"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", "aws_db_instance.test", "tags.%"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.Environment", "aws_db_instance.test", "tags.Environment"), ), }, }, @@ -44,7 +50,12 @@ func TestAccRDSInstanceDataSource_basic(t *testing.T) { } func TestAccRDSInstanceDataSource_ec2Classic(t *testing.T) { - rInt := sdkacctest.RandInt() + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_db_instance.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckEC2Classic(t) }, @@ -52,34 +63,57 @@ func TestAccRDSInstanceDataSource_ec2Classic(t *testing.T) { ProviderFactories: acctest.ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccInstanceDataSourceConfig_ec2Classic(rInt), + Config: testAccInstanceDataSourceConfig_ec2Classic(rName), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.aws_db_instance.bar", "db_subnet_group", ""), + resource.TestCheckResourceAttr(dataSourceName, "db_subnet_group", ""), ), }, }, }) } -func testAccInstanceDataSourceConfig(rInt int) string { - return fmt.Sprintf(` -data "aws_rds_orderable_db_instance" "test" { - engine = "mariadb" - preferred_instance_classes = ["db.t3.micro", "db.t2.micro", "db.t3.small"] +func testAccInstanceDataSourceConfig(rName string) string { + return acctest.ConfigCompose( + testAccInstanceConfig_orderableClassMariadb(), + acctest.ConfigAvailableAZsNoOptIn(), + fmt.Sprintf(` +resource "aws_vpc" "test" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = %[1]q + } } -resource "aws_db_instance" "bar" { - identifier = "datasource-test-terraform-%d" +resource "aws_subnet" "test" { + count = 2 - allocated_storage = 10 - engine = data.aws_rds_orderable_db_instance.test.engine - instance_class = data.aws_rds_orderable_db_instance.test.instance_class - name = "baz" - password = "barbarbarbar" - username = "foo" + availability_zone = data.aws_availability_zones.available.names[count.index] + cidr_block = "10.0.${count.index}.0/24" + vpc_id = aws_vpc.test.id + tags = { + Name = %[1]q + } +} + +resource "aws_db_subnet_group" "test" { + name = %[1]q + subnet_ids = aws_subnet.test[*].id +} + +resource "aws_db_instance" "test" { + allocated_storage = 10 backup_retention_period = 0 + db_subnet_group_name = aws_db_subnet_group.test.name + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version + identifier = %[1]q + instance_class = data.aws_rds_orderable_db_instance.test.instance_class + name = "baz" + password = "barbarbarbar" skip_final_snapshot = true + username = "foo" enabled_cloudwatch_logs_exports = [ "audit", @@ -91,39 +125,45 @@ resource "aws_db_instance" "bar" { } } -data "aws_db_instance" "bar" { - db_instance_identifier = aws_db_instance.bar.identifier +data "aws_db_instance" "test" { + db_instance_identifier = aws_db_instance.test.identifier } -`, rInt) +`, rName)) } -func testAccInstanceDataSourceConfig_ec2Classic(rInt int) string { +func testAccInstanceDataSourceConfig_ec2Classic(rName string) string { return acctest.ConfigCompose( acctest.ConfigEC2ClassicRegionProvider(), fmt.Sprintf(` +data "aws_rds_engine_version" "default" { + engine = "mysql" +} + +# EC2-Classic specific data "aws_rds_orderable_db_instance" "test" { - engine = "mysql" - engine_version = "5.6.41" + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version preferred_instance_classes = ["db.m3.medium", "db.m3.large", "db.r3.large"] } -resource "aws_db_instance" "bar" { - identifier = "foobarbaz-test-terraform-%[1]d" +resource "aws_db_instance" "test" { + identifier = %[1]q allocated_storage = 10 engine = data.aws_rds_orderable_db_instance.test.engine engine_version = data.aws_rds_orderable_db_instance.test.engine_version instance_class = data.aws_rds_orderable_db_instance.test.instance_class - name = "baz" + storage_type = data.aws_rds_orderable_db_instance.test.storage_type + db_name = "baz" password = "barbarbarbar" username = "foo" publicly_accessible = true security_group_names = ["default"] - parameter_group_name = "default.mysql5.6" + parameter_group_name = "default.${data.aws_rds_engine_version.default.parameter_group_family}" skip_final_snapshot = true } -data "aws_db_instance" "bar" { - db_instance_identifier = aws_db_instance.bar.identifier +data "aws_db_instance" "test" { + db_instance_identifier = aws_db_instance.test.identifier } -`, rInt)) +`, rName)) } diff --git a/internal/service/rds/instance_role_association_test.go b/internal/service/rds/instance_role_association_test.go index 6fd567d89cd..bab6d99f05d 100644 --- a/internal/service/rds/instance_role_association_test.go +++ b/internal/service/rds/instance_role_association_test.go @@ -16,6 +16,10 @@ import ( ) func TestAccRDSInstanceRoleAssociation_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstanceRole1 rds.DBInstanceRole rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) dbInstanceResourceName := "aws_db_instance.test" @@ -47,6 +51,10 @@ func TestAccRDSInstanceRoleAssociation_basic(t *testing.T) { } func TestAccRDSInstanceRoleAssociation_disappears(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance1 rds.DBInstance var dbInstanceRole1 rds.DBInstanceRole rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) diff --git a/internal/service/rds/instance_test.go b/internal/service/rds/instance_test.go index 0b3f177b1d4..53c4aee5c0e 100644 --- a/internal/service/rds/instance_test.go +++ b/internal/service/rds/instance_test.go @@ -5,6 +5,7 @@ import ( "log" "os" "regexp" + "strings" "testing" "github.com/aws/aws-sdk-go/aws" @@ -22,6 +23,10 @@ import ( ) func TestAccRDSInstance_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance1 rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -62,8 +67,8 @@ func TestAccRDSInstance_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "license_model", "general-public-license"), resource.TestCheckResourceAttrSet(resourceName, "maintenance_window"), resource.TestCheckResourceAttr(resourceName, "max_allocated_storage", "0"), - resource.TestCheckResourceAttr(resourceName, "option_group_name", "default:mysql-8-0"), - resource.TestCheckResourceAttr(resourceName, "parameter_group_name", "default.mysql8.0"), + resource.TestMatchResourceAttr(resourceName, "option_group_name", regexp.MustCompile(`^default:mysql-\d`)), + resource.TestMatchResourceAttr(resourceName, "parameter_group_name", regexp.MustCompile(`^default\.mysql\d`)), resource.TestCheckResourceAttr(resourceName, "port", "3306"), resource.TestCheckResourceAttr(resourceName, "publicly_accessible", "false"), resource.TestCheckResourceAttrSet(resourceName, "resource_id"), @@ -91,6 +96,10 @@ func TestAccRDSInstance_basic(t *testing.T) { } func TestAccRDSInstance_NameDeprecated_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance1 rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -161,6 +170,10 @@ func TestAccRDSInstance_NameDeprecated_basic(t *testing.T) { } func TestAccRDSInstance_onlyMajorVersion(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance1 rds.DBInstance resourceName := "aws_db_instance.test" engine := "mysql" @@ -173,7 +186,7 @@ func TestAccRDSInstance_onlyMajorVersion(t *testing.T) { CheckDestroy: testAccCheckClusterDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_MajorVersionOnly(engine, engineVersion), + Config: testAccInstanceConfig_MajorVersionOnly(), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &dbInstance1), resource.TestCheckResourceAttr(resourceName, "engine", engine), @@ -194,6 +207,10 @@ func TestAccRDSInstance_onlyMajorVersion(t *testing.T) { } func TestAccRDSInstance_namePrefix(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance const identifierPrefix = "tf-acc-test-prefix-" @@ -226,6 +243,10 @@ func TestAccRDSInstance_namePrefix(t *testing.T) { } func TestAccRDSInstance_nameGenerated(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance const resourceName = "aws_db_instance.test" @@ -257,9 +278,14 @@ func TestAccRDSInstance_nameGenerated(t *testing.T) { } func TestAccRDSInstance_kmsKey(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance kmsKeyResourceName := "aws_kms_key.foo" resourceName := "aws_db_instance.bar" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -268,7 +294,7 @@ func TestAccRDSInstance_kmsKey(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_KMSKeyID(sdkacctest.RandInt()), + Config: testAccInstanceConfig_KMSKeyID(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), testAccCheckInstanceAttributes(&v), @@ -292,8 +318,13 @@ func TestAccRDSInstance_kmsKey(t *testing.T) { } func TestAccRDSInstance_subnetGroup(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance - rName := sdkacctest.RandString(10) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_db_instance.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -304,17 +335,15 @@ func TestAccRDSInstance_subnetGroup(t *testing.T) { { Config: testAccInstanceConfig_WithSubnetGroup(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists("aws_db_instance.bar", &v), - resource.TestCheckResourceAttr( - "aws_db_instance.bar", "db_subnet_group_name", "foo-"+rName), + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "db_subnet_group_name", rName), ), }, { Config: testAccInstanceConfig_WithSubnetGroupUpdated(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists("aws_db_instance.bar", &v), - resource.TestCheckResourceAttr( - "aws_db_instance.bar", "db_subnet_group_name", "bar-"+rName), + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "db_subnet_group_name", fmt.Sprintf("%s-2", rName)), ), }, }, @@ -322,9 +351,14 @@ func TestAccRDSInstance_subnetGroup(t *testing.T) { } func TestAccRDSInstance_optionGroup(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance - rName := fmt.Sprintf("tf-option-test-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_db_instance.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -335,10 +369,9 @@ func TestAccRDSInstance_optionGroup(t *testing.T) { { Config: testAccInstanceConfig_WithOptionGroup(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists("aws_db_instance.bar", &v), + testAccCheckInstanceExists(resourceName, &v), testAccCheckInstanceAttributes(&v), - resource.TestCheckResourceAttr( - "aws_db_instance.bar", "option_group_name", rName), + resource.TestCheckResourceAttr(resourceName, "option_group_name", rName), ), }, }, @@ -346,7 +379,12 @@ func TestAccRDSInstance_optionGroup(t *testing.T) { } func TestAccRDSInstance_iamAuth(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -355,7 +393,7 @@ func TestAccRDSInstance_iamAuth(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_IAMAuth(sdkacctest.RandInt()), + Config: testAccInstanceConfig_IAMAuth(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists("aws_db_instance.bar", &v), testAccCheckInstanceAttributes(&v), @@ -368,6 +406,10 @@ func TestAccRDSInstance_iamAuth(t *testing.T) { } func TestAccRDSInstance_allowMajorVersionUpgrade(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance1 rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -410,6 +452,10 @@ func TestAccRDSInstance_allowMajorVersionUpgrade(t *testing.T) { } func TestAccRDSInstance_dbSubnetGroupName(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance var dbSubnetGroup rds.DBSubnetGroup @@ -467,6 +513,10 @@ func TestAccRDSInstance_DBSubnetGroupName_ramShared(t *testing.T) { } func TestAccRDSInstance_DBSubnetGroupName_vpcSecurityGroupIDs(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance var dbSubnetGroup rds.DBSubnetGroup @@ -493,6 +543,10 @@ func TestAccRDSInstance_DBSubnetGroupName_vpcSecurityGroupIDs(t *testing.T) { } func TestAccRDSInstance_deletionProtection(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -535,8 +589,12 @@ func TestAccRDSInstance_deletionProtection(t *testing.T) { } func TestAccRDSInstance_finalSnapshotIdentifier(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var snap rds.DBInstance - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -547,7 +605,7 @@ func TestAccRDSInstance_finalSnapshotIdentifier(t *testing.T) { CheckDestroy: testAccCheckInstanceSnapshot, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_FinalSnapshotIdentifier(rInt), + Config: testAccInstanceConfig_FinalSnapshotIdentifier(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists("aws_db_instance.snapshot", &snap), ), @@ -557,7 +615,12 @@ func TestAccRDSInstance_finalSnapshotIdentifier(t *testing.T) { } func TestAccRDSInstance_FinalSnapshotIdentifier_skipFinalSnapshot(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var snap rds.DBInstance + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -566,7 +629,7 @@ func TestAccRDSInstance_FinalSnapshotIdentifier_skipFinalSnapshot(t *testing.T) CheckDestroy: testAccCheckInstanceNoSnapshot, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_FinalSnapshotIdentifier_SkipFinalSnapshot(), + Config: testAccInstanceConfig_FinalSnapshotIdentifier_SkipFinalSnapshot(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists("aws_db_instance.snapshot", &snap), ), @@ -576,6 +639,10 @@ func TestAccRDSInstance_FinalSnapshotIdentifier_skipFinalSnapshot(t *testing.T) } func TestAccRDSInstance_isAlreadyBeingDeleted(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -614,6 +681,10 @@ func TestAccRDSInstance_isAlreadyBeingDeleted(t *testing.T) { } func TestAccRDSInstance_maxAllocatedStorage(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -658,6 +729,10 @@ func TestAccRDSInstance_maxAllocatedStorage(t *testing.T) { } func TestAccRDSInstance_password(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -696,7 +771,11 @@ func TestAccRDSInstance_password(t *testing.T) { }) } -func TestAccRDSInstance_replicateSourceDB_basic(t *testing.T) { +func TestAccRDSInstance_ReplicateSourceDB_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -733,7 +812,11 @@ func TestAccRDSInstance_replicateSourceDB_basic(t *testing.T) { }) } -func TestAccRDSInstance_replicateSourceDB_namePrefix(t *testing.T) { +func TestAccRDSInstance_ReplicateSourceDB_namePrefix(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance sourceName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -766,7 +849,11 @@ func TestAccRDSInstance_replicateSourceDB_namePrefix(t *testing.T) { }) } -func TestAccRDSInstance_replicateSourceDB_nameGenerated(t *testing.T) { +func TestAccRDSInstance_ReplicateSourceDB_nameGenerated(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance sourceName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -798,7 +885,11 @@ func TestAccRDSInstance_replicateSourceDB_nameGenerated(t *testing.T) { }) } -func TestAccRDSInstance_replicateSourceDB_addLater(t *testing.T) { +func TestAccRDSInstance_ReplicateSourceDB_addLater(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -830,6 +921,10 @@ func TestAccRDSInstance_replicateSourceDB_addLater(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_allocatedStorage(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -856,6 +951,10 @@ func TestAccRDSInstance_ReplicateSourceDB_allocatedStorage(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_iops(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -882,6 +981,10 @@ func TestAccRDSInstance_ReplicateSourceDB_iops(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_allocatedStorageAndIops(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -909,6 +1012,10 @@ func TestAccRDSInstance_ReplicateSourceDB_allocatedStorageAndIops(t *testing.T) } func TestAccRDSInstance_ReplicateSourceDB_allowMajorVersionUpgrade(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -935,6 +1042,10 @@ func TestAccRDSInstance_ReplicateSourceDB_allowMajorVersionUpgrade(t *testing.T) } func TestAccRDSInstance_ReplicateSourceDB_autoMinorVersionUpgrade(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -961,6 +1072,10 @@ func TestAccRDSInstance_ReplicateSourceDB_autoMinorVersionUpgrade(t *testing.T) } func TestAccRDSInstance_ReplicateSourceDB_availabilityZone(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -986,6 +1101,10 @@ func TestAccRDSInstance_ReplicateSourceDB_availabilityZone(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_backupRetentionPeriod(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1012,6 +1131,10 @@ func TestAccRDSInstance_ReplicateSourceDB_backupRetentionPeriod(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_backupWindow(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1038,6 +1161,10 @@ func TestAccRDSInstance_ReplicateSourceDB_backupWindow(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_dbSubnetGroupName(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance var dbSubnetGroup rds.DBSubnetGroup var providers []*schema.Provider @@ -1068,6 +1195,10 @@ func TestAccRDSInstance_ReplicateSourceDB_dbSubnetGroupName(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDBDBSubnetGroupName_ramShared(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance var dbSubnetGroup rds.DBSubnetGroup var providers []*schema.Provider @@ -1100,6 +1231,10 @@ func TestAccRDSInstance_ReplicateSourceDBDBSubnetGroupName_ramShared(t *testing. } func TestAccRDSInstance_ReplicateSourceDBDBSubnetGroupName_vpcSecurityGroupIDs(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance var dbSubnetGroup rds.DBSubnetGroup var providers []*schema.Provider @@ -1178,6 +1313,10 @@ func TestAccRDSInstance_ReplicateSourceDB_deletionProtection(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_iamDatabaseAuthenticationEnabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1204,6 +1343,10 @@ func TestAccRDSInstance_ReplicateSourceDB_iamDatabaseAuthenticationEnabled(t *te } func TestAccRDSInstance_ReplicateSourceDB_maintenanceWindow(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1230,6 +1373,10 @@ func TestAccRDSInstance_ReplicateSourceDB_maintenanceWindow(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_maxAllocatedStorage(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1256,6 +1403,10 @@ func TestAccRDSInstance_ReplicateSourceDB_maxAllocatedStorage(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_monitoring(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1282,6 +1433,10 @@ func TestAccRDSInstance_ReplicateSourceDB_monitoring(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_multiAZ(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1307,7 +1462,11 @@ func TestAccRDSInstance_ReplicateSourceDB_multiAZ(t *testing.T) { }) } -func TestAccRDSInstance_ReplicateSourceDB_parameterGroupName_sameSetOnBoth(t *testing.T) { +func TestAccRDSInstance_ReplicateSourceDB_parameterGroupNameSameSetOnBoth(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1336,7 +1495,11 @@ func TestAccRDSInstance_ReplicateSourceDB_parameterGroupName_sameSetOnBoth(t *te }) } -func TestAccRDSInstance_ReplicateSourceDB_parameterGroupName_differentSetOnBoth(t *testing.T) { +func TestAccRDSInstance_ReplicateSourceDB_parameterGroupNameDifferentSetOnBoth(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1365,7 +1528,11 @@ func TestAccRDSInstance_ReplicateSourceDB_parameterGroupName_differentSetOnBoth( }) } -func TestAccRDSInstance_ReplicateSourceDB_parameterGroupName_replicaCopiesValue(t *testing.T) { +func TestAccRDSInstance_ReplicateSourceDB_parameterGroupNameReplicaCopiesValue(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1393,7 +1560,11 @@ func TestAccRDSInstance_ReplicateSourceDB_parameterGroupName_replicaCopiesValue( }) } -func TestAccRDSInstance_ReplicateSourceDB_parameterGroupName_setOnReplica(t *testing.T) { +func TestAccRDSInstance_ReplicateSourceDB_parameterGroupNameSetOnReplica(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1421,6 +1592,10 @@ func TestAccRDSInstance_ReplicateSourceDB_parameterGroupName_setOnReplica(t *tes } func TestAccRDSInstance_ReplicateSourceDB_port(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1447,6 +1622,10 @@ func TestAccRDSInstance_ReplicateSourceDB_port(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_vpcSecurityGroupIDs(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1473,6 +1652,10 @@ func TestAccRDSInstance_ReplicateSourceDB_vpcSecurityGroupIDs(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_caCertificateIdentifier(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1501,6 +1684,10 @@ func TestAccRDSInstance_ReplicateSourceDB_caCertificateIdentifier(t *testing.T) } func TestAccRDSInstance_ReplicateSourceDB_replicaMode(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1532,6 +1719,10 @@ func TestAccRDSInstance_ReplicateSourceDB_replicaMode(t *testing.T) { // InvalidDBInstanceState: Instance cannot currently reboot due to an in-progress management operation // https://github.com/hashicorp/terraform-provider-aws/issues/11905 func TestAccRDSInstance_ReplicateSourceDB_parameterGroupTwoStep(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1569,10 +1760,9 @@ func TestAccRDSInstance_ReplicateSourceDB_parameterGroupTwoStep(t *testing.T) { }) } -func TestAccRDSInstance_s3Import_basic(t *testing.T) { +func TestAccRDSInstance_S3Import_basic(t *testing.T) { var snap rds.DBInstance - bucket := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - uniqueId := sdkacctest.RandomWithPrefix("tf-acc-s3-import-test") + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketPrefix := sdkacctest.RandString(5) const resourceName = "aws_db_instance.test" @@ -1584,10 +1774,10 @@ func TestAccRDSInstance_s3Import_basic(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_S3Import_basic(bucket, bucketPrefix, uniqueId), + Config: testAccInstanceConfig_S3Import_basic(rName, bucketPrefix), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &snap), - resource.TestCheckResourceAttr(resourceName, "identifier", uniqueId), + resource.TestCheckResourceAttr(resourceName, "identifier", rName), resource.TestCheckResourceAttr(resourceName, "identifier_prefix", ""), ), }, @@ -1603,10 +1793,9 @@ func TestAccRDSInstance_s3Import_basic(t *testing.T) { }) } -func TestAccRDSInstance_s3Import_NameDeprecated_basic(t *testing.T) { +func TestAccRDSInstance_S3Import_nameDeprecated(t *testing.T) { var snap rds.DBInstance - bucket := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - uniqueId := sdkacctest.RandomWithPrefix("tf-acc-s3-import-test") + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketPrefix := sdkacctest.RandString(5) const resourceName = "aws_db_instance.test" @@ -1618,10 +1807,10 @@ func TestAccRDSInstance_s3Import_NameDeprecated_basic(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_S3Import_NameDeprecated_basic(bucket, bucketPrefix, uniqueId), + Config: testAccInstanceConfig_S3Import_NameDeprecated_basic(rName, bucketPrefix), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &snap), - resource.TestCheckResourceAttr(resourceName, "identifier", uniqueId), + resource.TestCheckResourceAttr(resourceName, "identifier", rName), resource.TestCheckResourceAttr(resourceName, "identifier_prefix", ""), ), }, @@ -1637,10 +1826,10 @@ func TestAccRDSInstance_s3Import_NameDeprecated_basic(t *testing.T) { }) } -func TestAccRDSInstance_s3Import_namePrefix(t *testing.T) { +func TestAccRDSInstance_S3Import_namePrefix(t *testing.T) { var snap rds.DBInstance const identifierPrefix = "tf-acc-test-prefix-" - bucket := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketPrefix := sdkacctest.RandString(5) const resourceName = "aws_db_instance.test" @@ -1652,7 +1841,7 @@ func TestAccRDSInstance_s3Import_namePrefix(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_S3Import_namePrefix(bucket, bucketPrefix, identifierPrefix), + Config: testAccInstanceConfig_S3Import_namePrefix(rName, bucketPrefix, identifierPrefix), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &snap), create.TestCheckResourceAttrNameFromPrefix(resourceName, "identifier", identifierPrefix), @@ -1671,9 +1860,9 @@ func TestAccRDSInstance_s3Import_namePrefix(t *testing.T) { }) } -func TestAccRDSInstance_s3Import_nameGenerated(t *testing.T) { +func TestAccRDSInstance_S3Import_nameGenerated(t *testing.T) { var snap rds.DBInstance - bucket := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketPrefix := sdkacctest.RandString(5) const resourceName = "aws_db_instance.test" @@ -1685,7 +1874,7 @@ func TestAccRDSInstance_s3Import_nameGenerated(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_S3Import_nameGenerated(bucket, bucketPrefix), + Config: testAccInstanceConfig_S3Import_nameGenerated(rName, bucketPrefix), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &snap), create.TestCheckResourceAttrNameGenerated(resourceName, "identifier"), @@ -1705,6 +1894,10 @@ func TestAccRDSInstance_s3Import_nameGenerated(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_basic(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -1744,6 +1937,10 @@ func TestAccRDSInstance_SnapshotIdentifier_basic(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_namePrefix(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance sourceName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1778,6 +1975,10 @@ func TestAccRDSInstance_SnapshotIdentifier_namePrefix(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_nameGenerated(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance sourceName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1811,6 +2012,10 @@ func TestAccRDSInstance_SnapshotIdentifier_nameGenerated(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_AssociationRemoved(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance1, dbInstance2 rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -1844,6 +2049,10 @@ func TestAccRDSInstance_SnapshotIdentifier_AssociationRemoved(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_allocatedStorage(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -1872,6 +2081,10 @@ func TestAccRDSInstance_SnapshotIdentifier_allocatedStorage(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_io1Storage(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -1900,6 +2113,10 @@ func TestAccRDSInstance_SnapshotIdentifier_io1Storage(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_allowMajorVersionUpgrade(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -1928,6 +2145,10 @@ func TestAccRDSInstance_SnapshotIdentifier_allowMajorVersionUpgrade(t *testing.T } func TestAccRDSInstance_SnapshotIdentifier_autoMinorVersionUpgrade(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -1956,6 +2177,10 @@ func TestAccRDSInstance_SnapshotIdentifier_autoMinorVersionUpgrade(t *testing.T) } func TestAccRDSInstance_SnapshotIdentifier_availabilityZone(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -1982,7 +2207,11 @@ func TestAccRDSInstance_SnapshotIdentifier_availabilityZone(t *testing.T) { }) } -func TestAccRDSInstance_SnapshotIdentifier_backupRetentionPeriod_override(t *testing.T) { +func TestAccRDSInstance_SnapshotIdentifier_backupRetentionPeriodOverride(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2010,7 +2239,11 @@ func TestAccRDSInstance_SnapshotIdentifier_backupRetentionPeriod_override(t *tes }) } -func TestAccRDSInstance_SnapshotIdentifier_backupRetentionPeriod_unset(t *testing.T) { +func TestAccRDSInstance_SnapshotIdentifier_backupRetentionPeriodUnset(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2039,6 +2272,10 @@ func TestAccRDSInstance_SnapshotIdentifier_backupRetentionPeriod_unset(t *testin } func TestAccRDSInstance_SnapshotIdentifier_backupWindow(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2067,6 +2304,10 @@ func TestAccRDSInstance_SnapshotIdentifier_backupWindow(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_dbSubnetGroupName(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot var dbSubnetGroup rds.DBSubnetGroup @@ -2097,7 +2338,11 @@ func TestAccRDSInstance_SnapshotIdentifier_dbSubnetGroupName(t *testing.T) { }) } -func TestAccRDSInstance_SnapshotIdentifier_DBSubnetGroupName_ramShared(t *testing.T) { +func TestAccRDSInstance_SnapshotIdentifier_dbSubnetGroupNameRAMShared(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot var dbSubnetGroup rds.DBSubnetGroup @@ -2133,7 +2378,11 @@ func TestAccRDSInstance_SnapshotIdentifier_DBSubnetGroupName_ramShared(t *testin }) } -func TestAccRDSInstance_SnapshotIdentifier_DBSubnetGroupName_vpcSecurityGroupIDs(t *testing.T) { +func TestAccRDSInstance_SnapshotIdentifier_dbSubnetGroupNameVPCSecurityGroupIDs(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot var dbSubnetGroup rds.DBSubnetGroup @@ -2165,6 +2414,10 @@ func TestAccRDSInstance_SnapshotIdentifier_DBSubnetGroupName_vpcSecurityGroupIDs } func TestAccRDSInstance_SnapshotIdentifier_deletionProtection(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2203,6 +2456,10 @@ func TestAccRDSInstance_SnapshotIdentifier_deletionProtection(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_iamDatabaseAuthenticationEnabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2231,6 +2488,10 @@ func TestAccRDSInstance_SnapshotIdentifier_iamDatabaseAuthenticationEnabled(t *t } func TestAccRDSInstance_SnapshotIdentifier_maintenanceWindow(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2259,6 +2520,10 @@ func TestAccRDSInstance_SnapshotIdentifier_maintenanceWindow(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_maxAllocatedStorage(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2287,6 +2552,10 @@ func TestAccRDSInstance_SnapshotIdentifier_maxAllocatedStorage(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_monitoring(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2315,6 +2584,10 @@ func TestAccRDSInstance_SnapshotIdentifier_monitoring(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_multiAZ(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2342,7 +2615,11 @@ func TestAccRDSInstance_SnapshotIdentifier_multiAZ(t *testing.T) { }) } -func TestAccRDSInstance_SnapshotIdentifier_multiAZ_sqlServer(t *testing.T) { +func TestAccRDSInstance_SnapshotIdentifier_multiAZSQLServer(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2371,6 +2648,10 @@ func TestAccRDSInstance_SnapshotIdentifier_multiAZ_sqlServer(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_parameterGroupName(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2400,6 +2681,10 @@ func TestAccRDSInstance_SnapshotIdentifier_parameterGroupName(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_port(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2428,6 +2713,10 @@ func TestAccRDSInstance_SnapshotIdentifier_port(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_tags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2456,7 +2745,7 @@ func TestAccRDSInstance_SnapshotIdentifier_tags(t *testing.T) { }) } -func TestAccRDSInstance_SnapshotIdentifier_Tags_Clear(t *testing.T) { +func TestAccRDSInstance_SnapshotIdentifier_tagsRemove(t *testing.T) { acctest.Skip(t, "To be fixed: https://github.com/hashicorp/terraform-provider-aws/issues/5959") // --- FAIL: TestAccRDSInstance_SnapshotIdentifierTags_unset (1086.15s) // testing.go:527: Step 0 error: Check failed: Check 4/4 error: aws_db_instance.test: Attribute 'tags.%' expected "0", got "1" @@ -2489,6 +2778,10 @@ func TestAccRDSInstance_SnapshotIdentifier_Tags_Clear(t *testing.T) { } func TestAccRDSInstance_SnapshotIdentifier_vpcSecurityGroupIDs(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2519,7 +2812,11 @@ func TestAccRDSInstance_SnapshotIdentifier_vpcSecurityGroupIDs(t *testing.T) { // This acceptance test explicitly tests when snapshot_identifier is set, // vpc_security_group_ids is set (which triggered the resource update function), // and tags is set which was missing its ARN used for tagging -func TestAccRDSInstance_SnapshotIdentifier_vpcSecurityGroupIDs_tags(t *testing.T) { +func TestAccRDSInstance_SnapshotIdentifier_vpcSecurityGroupIDsTags(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -2549,6 +2846,10 @@ func TestAccRDSInstance_SnapshotIdentifier_vpcSecurityGroupIDs_tags(t *testing.T } func TestAccRDSInstance_monitoringInterval(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_db_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -2603,6 +2904,10 @@ func TestAccRDSInstance_monitoringInterval(t *testing.T) { } func TestAccRDSInstance_MonitoringRoleARN_enabledToDisabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance iamRoleResourceName := "aws_iam_role.test" resourceName := "aws_db_instance.test" @@ -2644,6 +2949,10 @@ func TestAccRDSInstance_MonitoringRoleARN_enabledToDisabled(t *testing.T) { } func TestAccRDSInstance_MonitoringRoleARN_enabledToRemoved(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance iamRoleResourceName := "aws_iam_role.test" resourceName := "aws_db_instance.test" @@ -2684,6 +2993,10 @@ func TestAccRDSInstance_MonitoringRoleARN_enabledToRemoved(t *testing.T) { } func TestAccRDSInstance_MonitoringRoleARN_removedToEnabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance iamRoleResourceName := "aws_iam_role.test" resourceName := "aws_db_instance.test" @@ -2727,9 +3040,13 @@ func TestAccRDSInstance_MonitoringRoleARN_removedToEnabled(t *testing.T) { // We apply a plan, then change just the iops. If the apply succeeds, we // consider this a pass, as before in 3760 the request would fail func TestAccRDSInstance_separateIopsUpdate(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance - rName := sdkacctest.RandString(5) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -2757,9 +3074,14 @@ func TestAccRDSInstance_separateIopsUpdate(t *testing.T) { } func TestAccRDSInstance_portUpdate(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance - rName := sdkacctest.RandString(5) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_db_instance.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -2768,20 +3090,18 @@ func TestAccRDSInstance_portUpdate(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_SnapshotInstanceConfig_mysqlPort(rName), + Config: testAccInstanceConfig_SnapshotInstanceConfig_mySQLPort(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists("aws_db_instance.bar", &v), - resource.TestCheckResourceAttr( - "aws_db_instance.bar", "port", "3306"), + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "port", "3306"), ), }, { Config: testAccInstanceConfig_SnapshotInstanceConfig_updateMySQLPort(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists("aws_db_instance.bar", &v), - resource.TestCheckResourceAttr( - "aws_db_instance.bar", "port", "3305"), + testAccCheckInstanceExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "port", "3305"), ), }, }, @@ -2789,8 +3109,13 @@ func TestAccRDSInstance_portUpdate(t *testing.T) { } func TestAccRDSInstance_MSSQL_tz(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_db_instance.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -2799,26 +3124,22 @@ func TestAccRDSInstance_MSSQL_tz(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_MSSQL_timezone(rInt), + Config: testAccInstanceConfig_MSSQL_timezone(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists("aws_db_instance.mssql", &v), + testAccCheckInstanceExists(resourceName, &v), testAccCheckInstanceAttributes_MSSQL(&v, ""), - resource.TestCheckResourceAttr( - "aws_db_instance.mssql", "allocated_storage", "20"), - resource.TestCheckResourceAttr( - "aws_db_instance.mssql", "engine", "sqlserver-ex"), + resource.TestCheckResourceAttr(resourceName, "allocated_storage", "20"), + resource.TestCheckResourceAttr(resourceName, "engine", "sqlserver-ex"), ), }, { - Config: testAccInstanceConfig_MSSQL_timezone_AKST(rInt), + Config: testAccInstanceConfig_MSSQL_timezone_AKST(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists("aws_db_instance.mssql", &v), + testAccCheckInstanceExists(resourceName, &v), testAccCheckInstanceAttributes_MSSQL(&v, "Alaskan Standard Time"), - resource.TestCheckResourceAttr( - "aws_db_instance.mssql", "allocated_storage", "20"), - resource.TestCheckResourceAttr( - "aws_db_instance.mssql", "engine", "sqlserver-ex"), + resource.TestCheckResourceAttr(resourceName, "allocated_storage", "20"), + resource.TestCheckResourceAttr(resourceName, "engine", "sqlserver-ex"), ), }, }, @@ -2826,6 +3147,10 @@ func TestAccRDSInstance_MSSQL_tz(t *testing.T) { } func TestAccRDSInstance_MSSQL_domain(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var vBefore, vAfter rds.DBInstance resourceName := "aws_db_instance.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -2863,6 +3188,14 @@ func TestAccRDSInstance_MSSQL_domain(t *testing.T) { } func TestAccRDSInstance_MSSQL_domainSnapshotRestore(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v, vRestoredInstance rds.DBInstance resourceName := "aws_db_instance.test" originResourceName := "aws_db_instance.origin" @@ -2891,8 +3224,14 @@ func TestAccRDSInstance_MSSQL_domainSnapshotRestore(t *testing.T) { } func TestAccRDSInstance_MySQL_snapshotRestoreWithEngineVersion(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v, vRestoredInstance rds.DBInstance - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_db_instance.test" + restoreResourceName := "aws_db_instance.restore" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -2901,12 +3240,13 @@ func TestAccRDSInstance_MySQL_snapshotRestoreWithEngineVersion(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_MySQLSnapshotRestoreWithEngineVersion(rInt), + Config: testAccInstanceConfig_MySQLSnapshotRestoreWithEngineVersion(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceExists("aws_db_instance.mysql_restore", &vRestoredInstance), - testAccCheckInstanceExists("aws_db_instance.mysql", &v), - resource.TestCheckResourceAttr("aws_db_instance.mysql", "engine_version", "8.0.23"), - resource.TestCheckResourceAttr("aws_db_instance.mysql_restore", "engine_version", "8.0.25"), + testAccCheckInstanceExists(restoreResourceName, &vRestoredInstance), + testAccCheckInstanceExists(resourceName, &v), + // Hardcoded older version. Will to update when no longer compatible to upgrade from this to the default version. + resource.TestCheckResourceAttr(resourceName, "engine_version", "8.0.25"), + resource.TestCheckResourceAttrPair(restoreResourceName, "engine_version", "data.aws_rds_engine_version.default", "version"), ), }, }, @@ -2914,7 +3254,12 @@ func TestAccRDSInstance_MySQL_snapshotRestoreWithEngineVersion(t *testing.T) { } func TestAccRDSInstance_minorVersion(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -2923,7 +3268,7 @@ func TestAccRDSInstance_minorVersion(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_AutoMinorVersion(), + Config: testAccInstanceConfig_AutoMinorVersion(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists("aws_db_instance.bar", &v), ), @@ -2934,7 +3279,7 @@ func TestAccRDSInstance_minorVersion(t *testing.T) { func TestAccRDSInstance_ec2Classic(t *testing.T) { var v rds.DBInstance - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_db_instance.bar" resource.ParallelTest(t, resource.TestCase{ @@ -2944,7 +3289,7 @@ func TestAccRDSInstance_ec2Classic(t *testing.T) { CheckDestroy: testAccCheckInstanceEC2ClassicDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_EC2Classic(rInt), + Config: testAccInstanceConfig_EC2Classic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceEC2ClassicExists(resourceName, &v), ), @@ -2954,9 +3299,13 @@ func TestAccRDSInstance_ec2Classic(t *testing.T) { } func TestAccRDSInstance_cloudWatchLogsExport(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -2965,7 +3314,7 @@ func TestAccRDSInstance_cloudWatchLogsExport(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_CloudWatchLogsExportConfiguration(rInt), + Config: testAccInstanceConfig_CloudWatchLogsExportConfiguration(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists("aws_db_instance.bar", &v), ), @@ -2987,9 +3336,13 @@ func TestAccRDSInstance_cloudWatchLogsExport(t *testing.T) { } func TestAccRDSInstance_EnabledCloudWatchLogsExports_mySQL(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var v rds.DBInstance resourceName := "aws_db_instance.bar" - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -2998,7 +3351,7 @@ func TestAccRDSInstance_EnabledCloudWatchLogsExports_mySQL(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_CloudWatchLogsExportConfiguration(rInt), + Config: testAccInstanceConfig_CloudWatchLogsExportConfiguration(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "enabled_cloudwatch_logs_exports.#", "2"), @@ -3007,7 +3360,7 @@ func TestAccRDSInstance_EnabledCloudWatchLogsExports_mySQL(t *testing.T) { ), }, { - Config: testAccInstanceConfig_CloudWatchLogsExportConfigurationAdd(rInt), + Config: testAccInstanceConfig_CloudWatchLogsExportConfigurationAdd(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "enabled_cloudwatch_logs_exports.#", "3"), @@ -3017,7 +3370,7 @@ func TestAccRDSInstance_EnabledCloudWatchLogsExports_mySQL(t *testing.T) { ), }, { - Config: testAccInstanceConfig_CloudWatchLogsExportConfigurationModify(rInt), + Config: testAccInstanceConfig_CloudWatchLogsExportConfigurationModify(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "enabled_cloudwatch_logs_exports.#", "3"), @@ -3027,7 +3380,7 @@ func TestAccRDSInstance_EnabledCloudWatchLogsExports_mySQL(t *testing.T) { ), }, { - Config: testAccInstanceConfig_CloudWatchLogsExportConfigurationDelete(rInt), + Config: testAccInstanceConfig_CloudWatchLogsExportConfigurationDelete(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(resourceName, &v), resource.TestCheckResourceAttr(resourceName, "enabled_cloudwatch_logs_exports.#", "0"), @@ -3038,6 +3391,10 @@ func TestAccRDSInstance_EnabledCloudWatchLogsExports_mySQL(t *testing.T) { } func TestAccRDSInstance_EnabledCloudWatchLogsExports_msSQL(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -3072,6 +3429,10 @@ func TestAccRDSInstance_EnabledCloudWatchLogsExports_msSQL(t *testing.T) { } func TestAccRDSInstance_EnabledCloudWatchLogsExports_oracle(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -3107,6 +3468,10 @@ func TestAccRDSInstance_EnabledCloudWatchLogsExports_oracle(t *testing.T) { } func TestAccRDSInstance_EnabledCloudWatchLogsExports_postgresql(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -3142,9 +3507,13 @@ func TestAccRDSInstance_EnabledCloudWatchLogsExports_postgresql(t *testing.T) { } func TestAccRDSInstance_noDeleteAutomatedBackups(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance - rName := sdkacctest.RandomWithPrefix("tf-testacc-nodelautobak") + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_db_instance.test" resource.ParallelTest(t, resource.TestCase{ @@ -3335,12 +3704,12 @@ func testAccCheckInstanceSnapshot(s *terraform.State) error { var found bool for _, t := range listTagsOutput.TagList { - if *t.Key == "Name" && *t.Value == "tf-tags-db" { + if aws.StringValue(t.Key) == "Name" && strings.HasPrefix(aws.StringValue(t.Value), acctest.ResourcePrefix) { found = true } } if !found { - return fmt.Errorf("Expected to find tag Name (%s), but wasn't found. Tags: %s", "tf-tags-db", listTagsOutput.TagList) + return fmt.Errorf("Expected to find tag Name with prefix \"%s\", but wasn't found. Tags: %s", acctest.ResourcePrefix, listTagsOutput.TagList) } // end tag search @@ -3494,6 +3863,10 @@ func testAccCheckInstanceEC2ClassicExists(resourceName string, v *rds.DBInstance // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/8792 func TestAccRDSInstance_PerformanceInsightsEnabled_disabledToEnabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_db_instance.test" @@ -3533,6 +3906,10 @@ func TestAccRDSInstance_PerformanceInsightsEnabled_disabledToEnabled(t *testing. } func TestAccRDSInstance_PerformanceInsightsEnabled_enabledToDisabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_db_instance.test" @@ -3572,6 +3949,10 @@ func TestAccRDSInstance_PerformanceInsightsEnabled_enabledToDisabled(t *testing. } func TestAccRDSInstance_performanceInsightsKMSKeyID(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) kmsKeyResourceName := "aws_kms_key.test" @@ -3622,6 +4003,10 @@ func TestAccRDSInstance_performanceInsightsKMSKeyID(t *testing.T) { } func TestAccRDSInstance_performanceInsightsRetentionPeriod(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_db_instance.test" @@ -3663,6 +4048,10 @@ func TestAccRDSInstance_performanceInsightsRetentionPeriod(t *testing.T) { } func TestAccRDSInstance_ReplicateSourceDB_performanceInsightsEnabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -3692,6 +4081,10 @@ func TestAccRDSInstance_ReplicateSourceDB_performanceInsightsEnabled(t *testing. } func TestAccRDSInstance_SnapshotIdentifier_performanceInsightsEnabled(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance var dbSnapshot rds.DBSnapshot @@ -3723,6 +4116,10 @@ func TestAccRDSInstance_SnapshotIdentifier_performanceInsightsEnabled(t *testing } func TestAccRDSInstance_caCertificateIdentifier(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance resourceName := "aws_db_instance.bar" @@ -3746,9 +4143,14 @@ func TestAccRDSInstance_caCertificateIdentifier(t *testing.T) { } func TestAccRDSInstance_RestoreToPointInTime_sourceIdentifier(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance sourceName := "aws_db_instance.test" resourceName := "aws_db_instance.restore" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -3757,7 +4159,7 @@ func TestAccRDSInstance_RestoreToPointInTime_sourceIdentifier(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_RestoreToPointInTime_SourceIdentifier(), + Config: testAccInstanceConfig_RestoreToPointInTime_SourceIdentifier(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(sourceName, &sourceDbInstance), testAccCheckInstanceExists(resourceName, &dbInstance), @@ -3782,9 +4184,14 @@ func TestAccRDSInstance_RestoreToPointInTime_sourceIdentifier(t *testing.T) { } func TestAccRDSInstance_RestoreToPointInTime_sourceResourceID(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance, sourceDbInstance rds.DBInstance sourceName := "aws_db_instance.test" resourceName := "aws_db_instance.restore" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -3793,7 +4200,7 @@ func TestAccRDSInstance_RestoreToPointInTime_sourceResourceID(t *testing.T) { CheckDestroy: testAccCheckInstanceDestroy, Steps: []resource.TestStep{ { - Config: testAccInstanceConfig_RestoreToPointInTime_SourceResourceID(), + Config: testAccInstanceConfig_RestoreToPointInTime_SourceResourceID(rName), Check: resource.ComposeTestCheckFunc( testAccCheckInstanceExists(sourceName, &sourceDbInstance), testAccCheckInstanceExists(resourceName, &dbInstance), @@ -3818,7 +4225,11 @@ func TestAccRDSInstance_RestoreToPointInTime_sourceResourceID(t *testing.T) { } func TestAccRDSInstance_NationalCharacterSet_oracle(t *testing.T) { - var dbInstance rds.DBInstance + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_db_instance.test" @@ -3853,6 +4264,10 @@ func TestAccRDSInstance_NationalCharacterSet_oracle(t *testing.T) { } func TestAccRDSInstance_NoNationalCharacterSet_oracle(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -4056,6 +4471,10 @@ func TestAccRDSInstance_CoIPEnabled_snapshotIdentifier(t *testing.T) { } func TestAccRDSInstance_license(t *testing.T) { + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + var dbInstance1 rds.DBInstance rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_db_instance.test" @@ -4096,54 +4515,38 @@ func TestAccRDSInstance_license(t *testing.T) { }) } -func testAccInstanceConfig_orderableClass(engine, version, license string) string { +func testAccInstanceConfig_orderableClass(engine, license, storage, classes string) string { return fmt.Sprintf(` -data "aws_rds_orderable_db_instance" "test" { - engine = %[1]q - engine_version = %[2]q - license_model = %[3]q - storage_type = "standard" - - preferred_instance_classes = ["db.t3.micro", "db.t2.micro", "db.t2.medium"] -} -`, engine, version, license) +data "aws_rds_engine_version" "default" { + engine = %[1]q } -func testAccInstanceConfig_orderableClass_SQLServerEx(engine, version, license string) string { - return fmt.Sprintf(` data "aws_rds_orderable_db_instance" "test" { - engine = %[1]q - engine_version = %[2]q - license_model = %[3]q - storage_type = "standard" + engine = data.aws_rds_engine_version.default.engine + engine_version = data.aws_rds_engine_version.default.version + license_model = %[2]q + storage_type = %[3]q - preferred_instance_classes = ["db.t2.small", "db.t3.small"] + preferred_instance_classes = [%[4]s] } -`, engine, version, license) +`, engine, license, storage, classes) } func testAccInstanceConfig_orderableClassMySQL() string { - return testAccInstanceConfig_orderableClass("mysql", "8.0.23", "general-public-license") + return testAccInstanceConfig_orderableClass("mysql", "general-public-license", "standard", mySQLPreferredInstanceClasses) } func testAccInstanceConfig_orderableClassMariadb() string { - return testAccInstanceConfig_orderableClass("mariadb", "10.5.12", "general-public-license") + return testAccInstanceConfig_orderableClass("mariadb", "general-public-license", "standard", mariaDBPreferredInstanceClasses) } func testAccInstanceConfig_orderableClassSQLServerEx() string { - return testAccInstanceConfig_orderableClass_SQLServerEx("sqlserver-ex", "15.00.4073.23.v1", "license-included") + return testAccInstanceConfig_orderableClass("sqlserver-ex", "license-included", "standard", sqlServerPreferredInstanceClasses) } -const testAccInstanceConfig_orderableClassSQLServerSe = ` -data "aws_rds_orderable_db_instance" "test" { - engine = "sqlserver-se" - engine_version = "15.00.4073.23.v1" - license_model = "license-included" - storage_type = "standard" - - preferred_instance_classes = ["db.m5.large", "db.m4.large", "db.r4.large"] +func testAccInstanceConfig_orderableClassSQLServerSe() string { + return testAccInstanceConfig_orderableClass("sqlserver-se", "license-included", "standard", sqlServerSEPreferredInstanceClasses) } -` func testAccInstanceBasicConfig(rName string) string { return acctest.ConfigCompose( @@ -4156,8 +4559,8 @@ resource "aws_db_instance" "test" { engine = data.aws_rds_orderable_db_instance.test.engine engine_version = data.aws_rds_orderable_db_instance.test.engine_version instance_class = data.aws_rds_orderable_db_instance.test.instance_class - name = "baz" - parameter_group_name = "default.mysql8.0" + db_name = "baz" + parameter_group_name = "default.${data.aws_rds_engine_version.default.parameter_group_family}" password = "barbarbarbar" skip_final_snapshot = true username = "test" @@ -4182,7 +4585,7 @@ resource "aws_db_instance" "test" { engine_version = data.aws_rds_orderable_db_instance.test.engine_version instance_class = data.aws_rds_orderable_db_instance.test.instance_class name = "baz" # deprecated - parameter_group_name = "default.mysql8.0" + parameter_group_name = "default.${data.aws_rds_engine_version.default.parameter_group_family}" password = "barbarbarbar" skip_final_snapshot = true username = "test" @@ -4195,18 +4598,18 @@ resource "aws_db_instance" "test" { `, rName)) } -func testAccInstanceConfig_MajorVersionOnly(engine, engineVersion string) string { +func testAccInstanceConfig_MajorVersionOnly() string { return acctest.ConfigCompose( testAccInstanceConfig_orderableClassMySQL(), - fmt.Sprintf(` + ` resource "aws_db_instance" "test" { allocated_storage = 10 backup_retention_period = 0 - engine = data.aws_rds_orderable_db_instance.test.engine - engine_version = %[1]q + engine = data.aws_rds_engine_version.default.engine + engine_version = regex("^\\d+\\.\\d+", data.aws_rds_engine_version.default.version) instance_class = data.aws_rds_orderable_db_instance.test.instance_class - name = "baz" - parameter_group_name = "default.%[2]s%[1]s" + db_name = "baz" + parameter_group_name = "default.${data.aws_rds_engine_version.default.parameter_group_family}" password = "barbarbarbar" skip_final_snapshot = true username = "foo" @@ -4216,7 +4619,7 @@ resource "aws_db_instance" "test" { # validation error). maintenance_window = "Fri:09:00-Fri:09:30" } -`, engineVersion, engine)) +`) } func testAccInstanceConfig_namePrefix(identifierPrefix string) string { @@ -4251,10 +4654,10 @@ resource "aws_db_instance" "test" { `) } -func testAccInstanceConfig_KMSKeyID(rInt int) string { +func testAccInstanceConfig_KMSKeyID(rName string) string { return fmt.Sprintf(` resource "aws_kms_key" "foo" { - description = "Terraform acc test %d" + description = %[1]q policy = < len(oldVersion) { + newVersionSubstr = string([]byte(newVersion)[0 : len(oldVersion)+1]) + } + + if oldVersion != newVersion && string(append([]byte(oldVersion), []byte(".")...)) != newVersionSubstr { + d.Set("engine_version", newVersion) + } + + d.Set("engine_version_actual", newVersion) +} diff --git a/internal/service/rds/wait.go b/internal/service/rds/wait.go index a3917df1d14..6751a4d048d 100644 --- a/internal/service/rds/wait.go +++ b/internal/service/rds/wait.go @@ -8,8 +8,6 @@ import ( ) const ( - rdsClusterInitiateUpgradeTimeout = 5 * time.Minute - dbClusterRoleAssociationCreatedTimeout = 5 * time.Minute dbClusterRoleAssociationDeletedTimeout = 5 * time.Minute ) @@ -54,12 +52,13 @@ func waitEventSubscriptionDeleted(conn *rds.RDS, id string, timeout time.Duratio func waitEventSubscriptionUpdated(conn *rds.RDS, id string, timeout time.Duration) (*rds.EventSubscription, error) { stateConf := &resource.StateChangeConf{ - Pending: []string{EventSubscriptionStatusModifying}, - Target: []string{EventSubscriptionStatusActive}, - Refresh: statusEventSubscription(conn, id), - Timeout: timeout, - MinTimeout: 10 * time.Second, - Delay: 30 * time.Second, + Pending: []string{EventSubscriptionStatusModifying}, + Target: []string{EventSubscriptionStatusActive}, + Refresh: statusEventSubscription(conn, id), + Timeout: timeout, + MinTimeout: 10 * time.Second, + Delay: 30 * time.Second, + ContinuousTargetOccurence: 2, } outputRaw, err := stateConf.WaitForState() @@ -161,11 +160,12 @@ func waitDBInstanceDeleted(conn *rds.RDS, id string, timeout time.Duration) (*rd InstanceStatusStorageFull, InstanceStatusStorageOptimization, }, - Target: []string{}, - Refresh: statusDBInstance(conn, id), - Timeout: timeout, - MinTimeout: 10 * time.Second, - Delay: 30 * time.Second, + Target: []string{}, + Refresh: statusDBInstance(conn, id), + Timeout: timeout, + MinTimeout: 10 * time.Second, + Delay: 30 * time.Second, + ContinuousTargetOccurence: 3, } outputRaw, err := stateConf.WaitForState() diff --git a/internal/service/transfer/server.go b/internal/service/transfer/server.go index f21a650fa2e..8460b36de22 100644 --- a/internal/service/transfer/server.go +++ b/internal/service/transfer/server.go @@ -50,18 +50,15 @@ func ResourceServer() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "certificate": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - "directory_id": { Type: schema.TypeString, Optional: true, }, - "domain": { Type: schema.TypeString, Optional: true, @@ -69,12 +66,10 @@ func ResourceServer() *schema.Resource { Default: transfer.DomainS3, ValidateFunc: validation.StringInSlice(transfer.Domain_Values(), false), }, - "endpoint": { Type: schema.TypeString, Computed: true, }, - "endpoint_details": { Type: schema.TypeList, Optional: true, @@ -115,38 +110,32 @@ func ResourceServer() *schema.Resource { }, }, }, - "endpoint_type": { Type: schema.TypeString, Optional: true, Default: transfer.EndpointTypePublic, ValidateFunc: validation.StringInSlice(transfer.EndpointType_Values(), false), }, - "force_destroy": { Type: schema.TypeBool, Optional: true, Default: false, }, - "function": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - "host_key": { Type: schema.TypeString, Optional: true, Sensitive: true, ValidateFunc: validation.StringLenBetween(0, 4096), }, - "host_key_fingerprint": { Type: schema.TypeString, Computed: true, }, - "identity_provider_type": { Type: schema.TypeString, Optional: true, @@ -154,19 +143,28 @@ func ResourceServer() *schema.Resource { Default: transfer.IdentityProviderTypeServiceManaged, ValidateFunc: validation.StringInSlice(transfer.IdentityProviderType_Values(), false), }, - "invocation_role": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - "logging_role": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - + "post_authentication_login_banner": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(0, 512), + }, + "pre_authentication_login_banner": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(0, 512), + }, "protocols": { Type: schema.TypeSet, MinItems: 1, @@ -178,17 +176,14 @@ func ResourceServer() *schema.Resource { ValidateFunc: validation.StringInSlice(transfer.Protocol_Values(), false), }, }, - "security_policy_name": { Type: schema.TypeString, Optional: true, Default: SecurityPolicyName2018_11, ValidateFunc: validation.StringInSlice(SecurityPolicyName_Values(), false), }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), - "url": { Type: schema.TypeString, Optional: true, @@ -263,6 +258,14 @@ func resourceServerCreate(d *schema.ResourceData, meta interface{}) error { input.LoggingRole = aws.String(v.(string)) } + if v, ok := d.GetOk("post_authentication_login_banner"); ok { + input.PostAuthenticationLoginBanner = aws.String(v.(string)) + } + + if v, ok := d.GetOk("pre_authentication_login_banner"); ok { + input.PreAuthenticationLoginBanner = aws.String(v.(string)) + } + if v, ok := d.GetOk("protocols"); ok && v.(*schema.Set).Len() > 0 { input.Protocols = flex.ExpandStringSet(v.(*schema.Set)) } @@ -386,6 +389,8 @@ func resourceServerRead(d *schema.ResourceData, meta interface{}) error { d.Set("invocation_role", "") } d.Set("logging_role", output.LoggingRole) + d.Set("post_authentication_login_banner", output.PostAuthenticationLoginBanner) + d.Set("pre_authentication_login_banner", output.PreAuthenticationLoginBanner) d.Set("protocols", aws.StringValueSlice(output.Protocols)) d.Set("security_policy_name", output.SecurityPolicyName) if output.IdentityProviderDetails != nil { @@ -553,6 +558,14 @@ func resourceServerUpdate(d *schema.ResourceData, meta interface{}) error { input.LoggingRole = aws.String(d.Get("logging_role").(string)) } + if d.HasChange("post_authentication_login_banner") { + input.PostAuthenticationLoginBanner = aws.String(d.Get("post_authentication_login_banner").(string)) + } + + if d.HasChange("pre_authentication_login_banner") { + input.PreAuthenticationLoginBanner = aws.String(d.Get("pre_authentication_login_banner").(string)) + } + if d.HasChange("protocols") { input.Protocols = flex.ExpandStringSet(d.Get("protocols").(*schema.Set)) } diff --git a/internal/service/transfer/server_test.go b/internal/service/transfer/server_test.go index 8cdae5c377a..3e5a1e2e379 100644 --- a/internal/service/transfer/server_test.go +++ b/internal/service/transfer/server_test.go @@ -56,6 +56,8 @@ func testAccServer_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "identity_provider_type", "SERVICE_MANAGED"), resource.TestCheckResourceAttr(resourceName, "invocation_role", ""), resource.TestCheckResourceAttr(resourceName, "logging_role", ""), + resource.TestCheckResourceAttr(resourceName, "post_authentication_login_banner", ""), + resource.TestCheckResourceAttr(resourceName, "pre_authentication_login_banner", ""), resource.TestCheckResourceAttr(resourceName, "protocols.#", "1"), resource.TestCheckTypeSetElemAttr(resourceName, "protocols.*", "SFTP"), resource.TestCheckResourceAttr(resourceName, "security_policy_name", "TransferSecurityPolicy-2018-11"), @@ -941,6 +943,34 @@ func testAccServer_lambdaFunction(t *testing.T) { }) } +func testAccTransferServer_AuthenticationLoginBanners(t *testing.T) { + var conf transfer.DescribedServer + resourceName := "aws_transfer_server.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); testAccPreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckServerDestroy, + Steps: []resource.TestStep{ + { + Config: testAccServerDisplayBannersConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckServerExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "post_authentication_login_banner", "This system is for the use of authorized users only - post"), + resource.TestCheckResourceAttr(resourceName, "pre_authentication_login_banner", "This system is for the use of authorized users only - pre"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"force_destroy"}, + }, + }, + }) +} + func testAccCheckServerExists(n string, v *transfer.DescribedServer) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -1194,6 +1224,15 @@ resource "aws_transfer_server" "test" {} ` } +func testAccServerDisplayBannersConfig() string { + return ` +resource "aws_transfer_server" "test" { + pre_authentication_login_banner = "This system is for the use of authorized users only - pre" + post_authentication_login_banner = "This system is for the use of authorized users only - post" +} +` +} + func testAccServerDomainConfig() string { return ` resource "aws_transfer_server" "test" { diff --git a/internal/service/transfer/transfer_test.go b/internal/service/transfer/transfer_test.go index 30b1a3ea6fd..a4a6b1f400b 100644 --- a/internal/service/transfer/transfer_test.go +++ b/internal/service/transfer/transfer_test.go @@ -17,6 +17,7 @@ func TestAccTransfer_serial(t *testing.T) { "disappears": testAccServer_disappears, "APIGateway": testAccServer_apiGateway, "APIGatewayForceDestroy": testAccServer_apiGateway_forceDestroy, + "AuthenticationLoginBanners": testAccTransferServer_AuthenticationLoginBanners, "DirectoryService": testAccServer_directoryService, "Domain": testAccServer_domain, "ForceDestroy": testAccServer_forceDestroy, diff --git a/internal/sweep/sweep_test.go b/internal/sweep/sweep_test.go index ea81c32edb2..2204d46303b 100644 --- a/internal/sweep/sweep_test.go +++ b/internal/sweep/sweep_test.go @@ -82,6 +82,7 @@ import ( _ "github.com/hashicorp/terraform-provider-aws/internal/service/mwaa" _ "github.com/hashicorp/terraform-provider-aws/internal/service/neptune" _ "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/opsworks" _ "github.com/hashicorp/terraform-provider-aws/internal/service/pinpoint" _ "github.com/hashicorp/terraform-provider-aws/internal/service/qldb" diff --git a/providerlint/go.mod b/providerlint/go.mod index 6b1f6ae90c4..17c95f1af08 100644 --- a/providerlint/go.mod +++ b/providerlint/go.mod @@ -3,7 +3,7 @@ module github.com/hashicorp/terraform-provider-aws/providerlint go 1.16 require ( - github.com/aws/aws-sdk-go v1.43.9 + github.com/aws/aws-sdk-go v1.43.17 github.com/bflad/tfproviderlint v0.28.1 github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1 golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb diff --git a/providerlint/go.sum b/providerlint/go.sum index 2b8b853c769..61971994936 100644 --- a/providerlint/go.sum +++ b/providerlint/go.sum @@ -70,8 +70,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.43.9 h1:k1S/29Bp2QD5ZopnGzIn0Sp63yyt3WH1JRE2OOU3Aig= -github.com/aws/aws-sdk-go v1.43.9/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.43.17 h1:jDPBz1UuTxmyRo0eLgaRiro0fiI1zL7lkscqYxoEDLM= +github.com/aws/aws-sdk-go v1.43.17/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/bflad/gopaniccheck v0.1.0 h1:tJftp+bv42ouERmUMWLoUn/5bi/iQZjHPznM00cP/bU= github.com/bflad/gopaniccheck v0.1.0/go.mod h1:ZCj2vSr7EqVeDaqVsWN4n2MwdROx1YL+LFo47TSWtsA= github.com/bflad/tfproviderlint v0.28.1 h1:7f54/ynV6/lK5/1EyG7tHtc4sMdjJSEFGjZNRJKwBs8= diff --git a/providerlint/vendor/github.com/aws/aws-sdk-go/aws/endpoints/defaults.go b/providerlint/vendor/github.com/aws/aws-sdk-go/aws/endpoints/defaults.go index c565a135563..95f5502d768 100644 --- a/providerlint/vendor/github.com/aws/aws-sdk-go/aws/endpoints/defaults.go +++ b/providerlint/vendor/github.com/aws/aws-sdk-go/aws/endpoints/defaults.go @@ -2293,15 +2293,60 @@ var awsPartition = partition{ endpointKey{ Region: "eu-west-1", }: endpoint{}, + endpointKey{ + Region: "fips-us-east-1", + }: endpoint{ + Hostname: "apprunner-fips.us-east-1.amazonaws.com", + CredentialScope: credentialScope{ + Region: "us-east-1", + }, + Deprecated: boxedTrue, + }, + endpointKey{ + Region: "fips-us-east-2", + }: endpoint{ + Hostname: "apprunner-fips.us-east-2.amazonaws.com", + CredentialScope: credentialScope{ + Region: "us-east-2", + }, + Deprecated: boxedTrue, + }, + endpointKey{ + Region: "fips-us-west-2", + }: endpoint{ + Hostname: "apprunner-fips.us-west-2.amazonaws.com", + CredentialScope: credentialScope{ + Region: "us-west-2", + }, + Deprecated: boxedTrue, + }, endpointKey{ Region: "us-east-1", }: endpoint{}, + endpointKey{ + Region: "us-east-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "apprunner-fips.us-east-1.amazonaws.com", + }, endpointKey{ Region: "us-east-2", }: endpoint{}, + endpointKey{ + Region: "us-east-2", + Variant: fipsVariant, + }: endpoint{ + Hostname: "apprunner-fips.us-east-2.amazonaws.com", + }, endpointKey{ Region: "us-west-2", }: endpoint{}, + endpointKey{ + Region: "us-west-2", + Variant: fipsVariant, + }: endpoint{ + Hostname: "apprunner-fips.us-west-2.amazonaws.com", + }, }, }, "appstream2": service{ @@ -2828,6 +2873,9 @@ var awsPartition = partition{ endpointKey{ Region: "ap-southeast-2", }: endpoint{}, + endpointKey{ + Region: "ap-southeast-3", + }: endpoint{}, endpointKey{ Region: "ca-central-1", }: endpoint{}, @@ -11021,6 +11069,18 @@ var awsPartition = partition{ }, "ivs": service{ Endpoints: serviceEndpoints{ + endpointKey{ + Region: "ap-northeast-1", + }: endpoint{}, + endpointKey{ + Region: "ap-northeast-2", + }: endpoint{}, + endpointKey{ + Region: "ap-south-1", + }: endpoint{}, + endpointKey{ + Region: "eu-central-1", + }: endpoint{}, endpointKey{ Region: "eu-west-1", }: endpoint{}, @@ -24172,6 +24232,14 @@ var awsusgovPartition = partition{ }, }, "autoscaling": service{ + Defaults: endpointDefaults{ + defaultKey{}: endpoint{}, + defaultKey{ + Variant: fipsVariant, + }: endpoint{ + Hostname: "autoscaling.{region}.{dnsSuffix}", + }, + }, Endpoints: serviceEndpoints{ endpointKey{ Region: "us-gov-east-1", @@ -24356,20 +24424,40 @@ var awsusgovPartition = partition{ "cloudtrail": service{ Endpoints: serviceEndpoints{ endpointKey{ - Region: "us-gov-east-1", + Region: "fips-us-gov-east-1", }: endpoint{ Hostname: "cloudtrail.us-gov-east-1.amazonaws.com", CredentialScope: credentialScope{ Region: "us-gov-east-1", }, + Deprecated: boxedTrue, }, endpointKey{ - Region: "us-gov-west-1", + Region: "fips-us-gov-west-1", }: endpoint{ Hostname: "cloudtrail.us-gov-west-1.amazonaws.com", CredentialScope: credentialScope{ Region: "us-gov-west-1", }, + Deprecated: boxedTrue, + }, + endpointKey{ + Region: "us-gov-east-1", + }: endpoint{}, + endpointKey{ + Region: "us-gov-east-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "cloudtrail.us-gov-east-1.amazonaws.com", + }, + endpointKey{ + Region: "us-gov-west-1", + }: endpoint{}, + endpointKey{ + Region: "us-gov-west-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "cloudtrail.us-gov-west-1.amazonaws.com", }, }, }, @@ -25361,20 +25449,40 @@ var awsusgovPartition = partition{ "events": service{ Endpoints: serviceEndpoints{ endpointKey{ - Region: "us-gov-east-1", + Region: "fips-us-gov-east-1", }: endpoint{ Hostname: "events.us-gov-east-1.amazonaws.com", CredentialScope: credentialScope{ Region: "us-gov-east-1", }, + Deprecated: boxedTrue, }, endpointKey{ - Region: "us-gov-west-1", + Region: "fips-us-gov-west-1", }: endpoint{ Hostname: "events.us-gov-west-1.amazonaws.com", CredentialScope: credentialScope{ Region: "us-gov-west-1", }, + Deprecated: boxedTrue, + }, + endpointKey{ + Region: "us-gov-east-1", + }: endpoint{}, + endpointKey{ + Region: "us-gov-east-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "events.us-gov-east-1.amazonaws.com", + }, + endpointKey{ + Region: "us-gov-west-1", + }: endpoint{}, + endpointKey{ + Region: "us-gov-west-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "events.us-gov-west-1.amazonaws.com", }, }, }, @@ -26245,20 +26353,40 @@ var awsusgovPartition = partition{ "logs": service{ Endpoints: serviceEndpoints{ endpointKey{ - Region: "us-gov-east-1", + Region: "fips-us-gov-east-1", }: endpoint{ Hostname: "logs.us-gov-east-1.amazonaws.com", CredentialScope: credentialScope{ Region: "us-gov-east-1", }, + Deprecated: boxedTrue, }, endpointKey{ - Region: "us-gov-west-1", + Region: "fips-us-gov-west-1", }: endpoint{ Hostname: "logs.us-gov-west-1.amazonaws.com", CredentialScope: credentialScope{ Region: "us-gov-west-1", }, + Deprecated: boxedTrue, + }, + endpointKey{ + Region: "us-gov-east-1", + }: endpoint{}, + endpointKey{ + Region: "us-gov-east-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "logs.us-gov-east-1.amazonaws.com", + }, + endpointKey{ + Region: "us-gov-west-1", + }: endpoint{}, + endpointKey{ + Region: "us-gov-west-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "logs.us-gov-west-1.amazonaws.com", }, }, }, @@ -27594,25 +27722,55 @@ var awsusgovPartition = partition{ "sns": service{ Endpoints: serviceEndpoints{ endpointKey{ - Region: "us-gov-east-1", + Region: "fips-us-gov-east-1", }: endpoint{ Hostname: "sns.us-gov-east-1.amazonaws.com", CredentialScope: credentialScope{ Region: "us-gov-east-1", }, + Deprecated: boxedTrue, }, endpointKey{ - Region: "us-gov-west-1", + Region: "fips-us-gov-west-1", }: endpoint{ - Hostname: "sns.us-gov-west-1.amazonaws.com", - Protocols: []string{"http", "https"}, + Hostname: "sns.us-gov-west-1.amazonaws.com", CredentialScope: credentialScope{ Region: "us-gov-west-1", }, + Deprecated: boxedTrue, + }, + endpointKey{ + Region: "us-gov-east-1", + }: endpoint{}, + endpointKey{ + Region: "us-gov-east-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "sns.us-gov-east-1.amazonaws.com", + }, + endpointKey{ + Region: "us-gov-west-1", + }: endpoint{ + Protocols: []string{"http", "https"}, + }, + endpointKey{ + Region: "us-gov-west-1", + Variant: fipsVariant, + }: endpoint{ + Hostname: "sns.us-gov-west-1.amazonaws.com", + Protocols: []string{"http", "https"}, }, }, }, "sqs": service{ + Defaults: endpointDefaults{ + defaultKey{}: endpoint{}, + defaultKey{ + Variant: fipsVariant, + }: endpoint{ + Hostname: "sqs.{region}.{dnsSuffix}", + }, + }, Endpoints: serviceEndpoints{ endpointKey{ Region: "us-gov-east-1", diff --git a/providerlint/vendor/modules.txt b/providerlint/vendor/modules.txt index aa426fbe1e5..8c0f6602c02 100644 --- a/providerlint/vendor/modules.txt +++ b/providerlint/vendor/modules.txt @@ -4,7 +4,7 @@ github.com/agext/levenshtein github.com/apparentlymart/go-textseg/v12/textseg # github.com/apparentlymart/go-textseg/v13 v13.0.0 github.com/apparentlymart/go-textseg/v13/textseg -# github.com/aws/aws-sdk-go v1.43.9 +# github.com/aws/aws-sdk-go v1.43.17 ## explicit github.com/aws/aws-sdk-go/aws/awserr github.com/aws/aws-sdk-go/aws/endpoints diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index 8b3f2980836..2d6bffb2d45 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -82,12 +82,13 @@ Identity Store Image Builder Inspector IoT -KMS +Keyspaces Kinesis Kinesis Data Analytics (SQL Applications) Kinesis Data Analytics v2 (SQL and Flink Applications) Kinesis Firehose Kinesis Video +KMS Lake Formation Lambda Lex @@ -106,6 +107,7 @@ MemoryDB Managed Workflows for Apache Airflow (MWAA) Neptune Network Firewall +Network Manager OpsWorks Organizations Outposts @@ -147,7 +149,6 @@ Storage Gateway Synthetics Timestream Write Transfer -Transit Gateway Network Manager VPC WAF Regional WAF diff --git a/website/docs/d/ec2_transit_gateway_connect_peer.html.markdown b/website/docs/d/ec2_transit_gateway_connect_peer.html.markdown index e8c68bc5288..3c58f0799ac 100644 --- a/website/docs/d/ec2_transit_gateway_connect_peer.html.markdown +++ b/website/docs/d/ec2_transit_gateway_connect_peer.html.markdown @@ -47,6 +47,7 @@ The following arguments are supported: In addition to all arguments above, the following attributes are exported: +* `arn` - EC2 Transit Gateway Connect Peer ARN * `bgp_asn` - The BGP ASN number assigned customer device * `inside_cidr_blocks` - The CIDR blocks that will be used for addressing within the tunnel. * `peer_address` - The IP addressed assigned to customer device, which is used as tunnel endpoint diff --git a/website/docs/d/ecrpublic_authorization_token.html.markdown b/website/docs/d/ecrpublic_authorization_token.html.markdown new file mode 100644 index 00000000000..755fc5d71ec --- /dev/null +++ b/website/docs/d/ecrpublic_authorization_token.html.markdown @@ -0,0 +1,28 @@ +--- +subcategory: "ECR" +layout: "aws" +page_title: "AWS: aws_ecrpublic_authorization_token" +description: |- + Provides details about a Public ECR Authorization Token +--- + +# Data Source: aws_ecrpublic_authorization_token + +The Public ECR Authorization Token data source allows the authorization token, token expiration date, user name and password to be retrieved for a Public ECR repository. + +## Example Usage + +```terraform +data "aws_ecrpublic_authorization_token" "token" { +} +``` + +## Attributes Reference + +The following attributes are exported: + +* `authorization_token` - Temporary IAM authentication credentials to access the ECR repository encoded in base64 in the form of `user_name:password`. +* `expires_at` - The time in UTC RFC3339 format when the authorization token expires. +* `id` - Region of the authorization token. +* `password` - Password decoded from the authorization token. +* `user_name` - User name decoded from the authorization token. diff --git a/website/docs/d/networkmanager_connection.html.markdown b/website/docs/d/networkmanager_connection.html.markdown new file mode 100644 index 00000000000..151628d965d --- /dev/null +++ b/website/docs/d/networkmanager_connection.html.markdown @@ -0,0 +1,37 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_connection" +description: |- + Retrieve information about a connection. +--- + +# Data Source: aws_networkmanager_connection + +Retrieve information about a connection. + +## Example Usage + +```terraform +data "aws_networkmanager_connection" "example" { + global_network_id = var.global_network_id + connection_id = var.connection_id +} +``` + +## Argument Reference + +* `global_network_id` - (Required) The ID of the Global Network of the connection to retrieve. +* `connection_id` - (Required) The id of the specific connection to retrieve. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The ARN of the connection. +* `connected_device_id` - The ID of the second device in the connection. +* `connected_link_id` - The ID of the link for the second device. +* `description` - A description of the connection. +* `device_id` - The ID of the first device in the connection. +* `link_id` - The ID of the link for the first device. +* `tags` - Key-value tags for the connection. diff --git a/website/docs/d/networkmanager_connections.html.markdown b/website/docs/d/networkmanager_connections.html.markdown new file mode 100644 index 00000000000..1191aa3c781 --- /dev/null +++ b/website/docs/d/networkmanager_connections.html.markdown @@ -0,0 +1,35 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_connections" +description: |- + Retrieve information about connections. +--- + +# Data Source: aws_networkmanager_connections + +Retrieve information about connections. + +## Example Usage + +```terraform +data "aws_networkmanager_connections" "example" { + global_network_id = var.global_network_id + + tags = { + Env = "test" + } +} +``` + +## Argument Reference + +* `device_id` - (Optional) The ID of the device of the connections to retrieve. +* `global_network_id` - (Required) The ID of the Global Network of the connections to retrieve. +* `tags` - (Optional) Restricts the list to the connections with these tags. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `ids` - The IDs of the connections. diff --git a/website/docs/d/networkmanager_device.html.markdown b/website/docs/d/networkmanager_device.html.markdown new file mode 100644 index 00000000000..3cf9e6c448a --- /dev/null +++ b/website/docs/d/networkmanager_device.html.markdown @@ -0,0 +1,51 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_device" +description: |- + Retrieve information about a device. +--- + +# Data Source: aws_networkmanager_device + +Retrieve information about a device. + +## Example Usage + +```terraform +data "aws_networkmanager_device" "example" { + global_network_id_id = var.global_network_id + device_id = var.device_id +} +``` + +## Argument Reference + +* `device_id` - (Required) The ID of the device. +* `global_network_id` - (Required) The ID of the global network. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) of the device. +* `aws_location` - The AWS location of the device. Documented below. +* `description` - A description of the device. +* `location` - The location of the device. Documented below. +* `model` - The model of device. +* `serial_number` - The serial number of the device. +* `site_id` - The ID of the site. +* `tags` - Key-value tags for the device. +* `type` - The type of device. +* `vendor` - The vendor of the device. + +The `aws_location` object supports the following: + +* `subnet_arn` - The Amazon Resource Name (ARN) of the subnet that the device is located in. +* `zone` - The Zone that the device is located in. + +The `location` object supports the following: + +* `address` - The physical address. +* `latitude` - The latitude. +* `longitude` - The longitude. diff --git a/website/docs/d/networkmanager_devices.html.markdown b/website/docs/d/networkmanager_devices.html.markdown new file mode 100644 index 00000000000..61aa42218d7 --- /dev/null +++ b/website/docs/d/networkmanager_devices.html.markdown @@ -0,0 +1,35 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_devices" +description: |- + Retrieve information about devices. +--- + +# Data Source: aws_networkmanager_devices + +Retrieve information about devices. + +## Example Usage + +```terraform +data "aws_networkmanager_devices" "example" { + global_network_id = var.global_network_id + + tags = { + Env = "test" + } +} +``` + +## Argument Reference + +* `global_network_id` - (Required) The ID of the Global Network of the devices to retrieve. +* `site_id` - (Optional) The ID of the site of the devices to retrieve. +* `tags` - (Optional) Restricts the list to the devices with these tags. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `ids` - The IDs of the devices. diff --git a/website/docs/d/networkmanager_global_network.html.markdown b/website/docs/d/networkmanager_global_network.html.markdown new file mode 100644 index 00000000000..65f90a98596 --- /dev/null +++ b/website/docs/d/networkmanager_global_network.html.markdown @@ -0,0 +1,31 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_global_network" +description: |- + Retrieve information about a global network. +--- + +# Data Source: aws_networkmanager_global_network + +Retrieve information about a global network. + +## Example Usage + +```terraform +data "aws_networkmanager_global_network" "example" { + global_network_id = var.global_network_id +} +``` + +## Argument Reference + +* `global_network_id` - (Required) The id of the specific global network to retrieve. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The ARN of the global network. +* `description` - The description of the global network. +* `tags` - A map of resource tags. \ No newline at end of file diff --git a/website/docs/d/networkmanager_global_networks.html.markdown b/website/docs/d/networkmanager_global_networks.html.markdown new file mode 100644 index 00000000000..e2d8f5c01c4 --- /dev/null +++ b/website/docs/d/networkmanager_global_networks.html.markdown @@ -0,0 +1,31 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_global_networks" +description: |- + Retrieve information about global networks. +--- + +# Data Source: aws_networkmanager_global_networks + +Retrieve information about global networks. + +## Example Usage + +```terraform +data "aws_networkmanager_global_networks" "example" { + tags = { + Env = "test" + } +} +``` + +## Argument Reference + +* `tags` - (Optional) Restricts the list to the global networks with these tags. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `ids` - The IDs of the global networks. diff --git a/website/docs/d/networkmanager_link.html.markdown b/website/docs/d/networkmanager_link.html.markdown new file mode 100644 index 00000000000..bf9800def42 --- /dev/null +++ b/website/docs/d/networkmanager_link.html.markdown @@ -0,0 +1,42 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_link" +description: |- + Retrieve information about a link. +--- + +# Data Source: aws_networkmanager_link + +Retrieve information about a link. + +## Example Usage + +```terraform +data "aws_networkmanager_link" "example" { + global_network_id = var.global_network_id + link_id = var.link_id +} +``` + +## Argument Reference + +* `global_network_id` - (Required) The ID of the Global Network of the link to retrieve. +* `link_id` - (Required) The id of the specific link to retrieve. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The ARN of the link. +* `bandwidth` - The upload speed and download speed of the link as documented below +* `description` - The description of the link. +* `provider_name` - The provider of the link. +* `site_id` - The ID of the site. +* `tags` - Key-value tags for the link. +* `type` - The type of the link. + +The `bandwidth` object supports the following: + +* `download_speed` - Download speed in Mbps. +* `upload_speed` - Upload speed in Mbps. diff --git a/website/docs/d/networkmanager_links.html.markdown b/website/docs/d/networkmanager_links.html.markdown new file mode 100644 index 00000000000..49c362da3ac --- /dev/null +++ b/website/docs/d/networkmanager_links.html.markdown @@ -0,0 +1,37 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_links" +description: |- + Retrieve information about links. +--- + +# Data Source: aws_networkmanager_links + +Retrieve information about link. + +## Example Usage + +```terraform +data "aws_networkmanager_links" "example" { + global_network_id = var.global_network_id + + tags = { + Env = "test" + } +} +``` + +## Argument Reference + +* `global_network_id` - (Required) The ID of the Global Network of the links to retrieve. +* `provider_name` - (Optional) The link provider to retrieve. +* `site_id` - (Optional) The ID of the site of the links to retrieve. +* `tags` - (Optional) Restricts the list to the links with these tags. +* `type` - (Optional) The link type to retrieve. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `ids` - The IDs of the links. diff --git a/website/docs/d/networkmanager_site.html.markdown b/website/docs/d/networkmanager_site.html.markdown new file mode 100644 index 00000000000..51bb1f03faf --- /dev/null +++ b/website/docs/d/networkmanager_site.html.markdown @@ -0,0 +1,40 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_site" +description: |- + Retrieve information about a site. +--- + +# Data Source: aws_networkmanager_site + +Retrieve information about a site. + +## Example Usage + +```terraform +data "aws_networkmanager_site" "example" { + global_network_id = var.global_network_id + site_id = var.site_id +} +``` + +## Argument Reference + +* `global_network_id` - (Required) The ID of the Global Network of the site to retrieve. +* `site_id` - (Required) The id of the specific site to retrieve. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The ARN of the site. +* `description` - The description of the site. +* `location` - The site location as documented below. +* `tags` - Key-value tags for the Site. + +The `location` object supports the following: + +* `address` - Address of the location. +* `latitude` - Latitude of the location. +* `longitude` - Longitude of the location. diff --git a/website/docs/d/networkmanager_sites.html.markdown b/website/docs/d/networkmanager_sites.html.markdown new file mode 100644 index 00000000000..04854fe1ebc --- /dev/null +++ b/website/docs/d/networkmanager_sites.html.markdown @@ -0,0 +1,34 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_sites" +description: |- + Retrieve information about sites. +--- + +# Data Source: aws_networkmanager_sites + +Retrieve information about sites. + +## Example Usage + +```terraform +data "aws_networkmanager_sites" "example" { + global_network_id = var.global_network_id + + tags = { + Env = "test" + } +} +``` + +## Argument Reference + +* `global_network_id` - (Required) The ID of the Global Network of the sites to retrieve. +* `tags` - (Optional) Restricts the list to the sites with these tags. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `ids` - The IDs of the sites. diff --git a/website/docs/guides/custom-service-endpoints.html.md b/website/docs/guides/custom-service-endpoints.html.md index 7d7e4336c69..b122c9b23b4 100644 --- a/website/docs/guides/custom-service-endpoints.html.md +++ b/website/docs/guides/custom-service-endpoints.html.md @@ -206,6 +206,7 @@ provider "aws" {
  • kafka
  • kafkaconnect
  • kendra
  • +
  • keyspaces
  • kinesis
  • kinesisanalytics
  • kinesisanalyticsv2
  • diff --git a/website/docs/r/api_gateway_vpc_link.html.markdown b/website/docs/r/api_gateway_vpc_link.html.markdown index 032f7ed9c9b..f797482c1b7 100644 --- a/website/docs/r/api_gateway_vpc_link.html.markdown +++ b/website/docs/r/api_gateway_vpc_link.html.markdown @@ -54,5 +54,5 @@ In addition to all arguments above, the following attributes are exported: API Gateway VPC Link can be imported using the `id`, e.g., ``` -$ terraform import aws_api_gateway_vpc_link.example +$ terraform import aws_api_gateway_vpc_link.example 12345abcde ``` diff --git a/website/docs/r/ec2_transit_gateway_connect_peer.html.markdown b/website/docs/r/ec2_transit_gateway_connect_peer.html.markdown index 3a523ee1ba8..0ab8f4566f1 100644 --- a/website/docs/r/ec2_transit_gateway_connect_peer.html.markdown +++ b/website/docs/r/ec2_transit_gateway_connect_peer.html.markdown @@ -40,7 +40,8 @@ The following arguments are supported: In addition to all arguments above, the following attributes are exported: -* `id` - EC2 Transit Gateway Attachment identifier +* `id` - EC2 Transit Gateway Connect Peer identifier +* `arn` - EC2 Transit Gateway Connect Peer ARN * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block). ## Timeouts diff --git a/website/docs/r/gamelift_game_server_group.markdown b/website/docs/r/gamelift_game_server_group.markdown new file mode 100644 index 00000000000..61c32e31ec3 --- /dev/null +++ b/website/docs/r/gamelift_game_server_group.markdown @@ -0,0 +1,204 @@ +--- +subcategory: "Gamelift" +layout: "aws" +page_title: "AWS: aws_gamelift_game_server_group" +description: |- + Provides a Gamelift Game Server Group resource. +--- + +# Resource: aws_gamelift_game_server_group + +Provides an Gamelift Game Server Group resource. + +## Example Usage + +```terraform +resource "aws_gamelift_game_server_group" "example" { + game_server_group_name = "example" + + instance_definition { + instance_type = "c5.large" + } + + instance_definition { + instance_type = "c5a.large" + } + + launch_template { + id = aws_launch_template.example.id + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.example.arn + + depends_on = [ + aws_iam_role_policy_attachment.example + ] +} +``` + +Full usage: + +```terraform +resource "aws_gamelift_game_server_group" "example" { + auto_scaling_policy { + estimated_instance_warmup = 60 + target_tracking_configuration { + target_value = 75 + } + } + + balancing_strategy = "SPOT_ONLY" + game_server_group_name = "example" + game_server_protection_policy = "FULL_PROTECTION" + + instance_definition { + instance_type = "c5.large" + weighted_capacity = "1" + } + + instance_definition { + instance_type = "c5.2xlarge" + weighted_capacity = "2" + } + + launch_template { + id = aws_launch_template.example.id + version = "1" + } + + max_size = 1 + min_size = 1 + role_arn = aws_iam_role.example.arn + + tags = { + Name = "example" + } + + vpc_subnets = [ + "subnet-12345678", + "subnet-23456789" + ] + + depends_on = [ + aws_iam_role_policy_attachment.example + ] +} +``` + +### Example IAM Role for Gamelift Game Server Group + +```terraform +data "aws_partition" "current" {} +resource "aws_iam_role" "example" { + assume_role_policy = <<-EOF + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "autoscaling.amazonaws.com", + "gamelift.amazonaws.com" + ] + }, + "Action": "sts:AssumeRole" + } + ] + } + EOF + name = "gamelift-game-server-group-example" +} +resource "aws_iam_role_policy_attachment" "example" { + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/GameLiftGameServerGroupPolicy" + role = aws_iam_role.example.name +} +``` + +## Argument Reference + +The following arguments are supported: + +* `balancing_strategy` - (Optional) Indicates how GameLift FleetIQ balances the use of Spot Instances and On-Demand Instances. + Valid values: `SPOT_ONLY`, `SPOT_PREFERRED`, `ON_DEMAND_ONLY`. Defaults to `SPOT_PREFERRED`. +* `game_server_group_name` - (Required) Name of the game server group. + This value is used to generate unique ARN identifiers for the EC2 Auto Scaling group and the GameLift FleetIQ game server group. +* `game_server_protection_policy` - (Optional) Indicates whether instances in the game server group are protected from early termination. + Unprotected instances that have active game servers running might be terminated during a scale-down event, + causing players to be dropped from the game. + Protected instances cannot be terminated while there are active game servers running except in the event + of a forced game server group deletion. + Valid values: `NO_PROTECTION`, `FULL_PROTECTION`. Defaults to `NO_PROTECTION`. +* `max_size` - (Required) The maximum number of instances allowed in the EC2 Auto Scaling group. + During automatic scaling events, GameLift FleetIQ and EC2 do not scale up the group above this maximum. +* `min_size` - (Required) The minimum number of instances allowed in the EC2 Auto Scaling group. + During automatic scaling events, GameLift FleetIQ and EC2 do not scale down the group below this minimum. +* `role_arn` - (Required) ARN for an IAM role that allows Amazon GameLift to access your EC2 Auto Scaling groups. +* `tags` - (Optional) Key-value map of resource tags +* `vpc_subnets` - (Optional) A list of VPC subnets to use with instances in the game server group. + By default, all GameLift FleetIQ-supported Availability Zones are used. + +### `auto_scaling_policy` + +Configuration settings to define a scaling policy for the Auto Scaling group that is optimized for game hosting. +The scaling policy uses the metric `PercentUtilizedGameServers` to maintain a buffer of idle game servers that +can immediately accommodate new games and players. + +* `estimated_instance_warmup` - (Optional) Length of time, in seconds, it takes for a new instance to start + new game server processes and register with GameLift FleetIQ. + Specifying a warm-up time can be useful, particularly with game servers that take a long time to start up, + because it avoids prematurely starting new instances. Defaults to `60`. + +#### `target_tracking_configuration` + +Settings for a target-based scaling policy applied to Auto Scaling group. +These settings are used to create a target-based policy that tracks the GameLift FleetIQ metric `PercentUtilizedGameServers` +and specifies a target value for the metric. + +* `target_value` - (Required) Desired value to use with a game server group target-based scaling policy. + +### `instance_definition` + +The EC2 instance types and sizes to use in the Auto Scaling group. +The instance definitions must specify at least two different instance types that are supported by GameLift FleetIQ. + +* `instance_type` - (Required) An EC2 instance type. +* `weighted_capacity` - (Optional) Instance weighting that indicates how much this instance type contributes + to the total capacity of a game server group. + Instance weights are used by GameLift FleetIQ to calculate the instance type's cost per unit hour and better identify + the most cost-effective options. + +### `launch_template` + +The EC2 launch template that contains configuration settings and game server code to be deployed to all instances in the game server group. +You can specify the template using either the template name or ID. + +* `id` - (Optional) A unique identifier for an existing EC2 launch template. +* `name` - (Optional) A readable identifier for an existing EC2 launch template. +* `version` - (Optional) The version of the EC2 launch template to use. If none is set, the default is the first version created. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The name of the Gamelift Game Server Group. +* `arn` - The ARN of the Gamelift Game Server Group. +* `auto_scaling_group_arn` - The ARN of the created EC2 Auto Scaling group. + +## Timeouts + +`aws_gamelift_game_server_group` provides the following +[Timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) configuration options: + +* `create` - (Default `10 minutes`) How long to wait for the Gamelift Game Server Group to be created. +* `delete` - (Default `30 minutes`) How long to wait for the Gamelift Game Server Group to be deleted. + +## Import + +Gamelift Game Server Group can be imported using the `name`, e.g. + +``` +$ terraform import aws_gamelift_game_server_group.example example +``` diff --git a/website/docs/r/iot_topic_rule.html.markdown b/website/docs/r/iot_topic_rule.html.markdown index fbe52a1f0ef..ac31f87fbdd 100644 --- a/website/docs/r/iot_topic_rule.html.markdown +++ b/website/docs/r/iot_topic_rule.html.markdown @@ -88,7 +88,7 @@ EOF * `enabled` - (Required) Specifies whether the rule is enabled. * `sql` - (Required) The SQL statement used to query the topic. For more information, see AWS IoT SQL Reference (http://docs.aws.amazon.com/iot/latest/developerguide/iot-rules.html#aws-iot-sql-reference) in the AWS IoT Developer Guide. * `sql_version` - (Required) The version of the SQL rules engine to use when evaluating the rule. -* `error_action` - (Optional) Configuration block with error action to be associated with the rule. See the documentation for `cloudwatch_alarm`, `cloudwatch_metric`, `dynamodb`, `dynamodbv2`, `elasticsearch`, `firehose`, `iot_analytics`, `iot_events`, `kinesis`, `lambda`, `republish`, `s3`, `step_functions`, `sns`, `sqs` configuration blocks for further configuration details. +* `error_action` - (Optional) Configuration block with error action to be associated with the rule. See the documentation for `cloudwatch_alarm`, `cloudwatch_logs`, `cloudwatch_metric`, `dynamodb`, `dynamodbv2`, `elasticsearch`, `firehose`, `iot_analytics`, `iot_events`, `kinesis`, `lambda`, `republish`, `s3`, `step_functions`, `sns`, `sqs` configuration blocks for further configuration details. * `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. The `cloudwatch_alarm` object takes the following arguments: @@ -98,6 +98,11 @@ The `cloudwatch_alarm` object takes the following arguments: * `state_reason` - (Required) The reason for the alarm change. * `state_value` - (Required) The value of the alarm state. Acceptable values are: OK, ALARM, INSUFFICIENT_DATA. +The `cloudwatch_logs` object takes the following arguments: + +* `log_group_name` - (Required) The CloudWatch log group name. +* `role_arn` - (Required) The IAM role ARN that allows access to the CloudWatch alarm. + The `cloudwatch_metric` object takes the following arguments: * `metric_name` - (Required) The CloudWatch metric name. diff --git a/website/docs/r/msk_cluster.html.markdown b/website/docs/r/msk_cluster.html.markdown index 30b9ae9f445..14d7c31f4aa 100644 --- a/website/docs/r/msk_cluster.html.markdown +++ b/website/docs/r/msk_cluster.html.markdown @@ -266,8 +266,8 @@ In addition to all arguments above, the following attributes are exported: * `current_version` - Current version of the MSK Cluster used for updates, e.g., `K13V1IB3VIYZZH` * `encryption_info.0.encryption_at_rest_kms_key_arn` - The ARN of the KMS key used for encryption at rest of the broker data volumes. * `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). -* `zookeeper_connect_string` - A comma separated list of one or more hostname:port pairs to use to connect to the Apache Zookeeper cluster. The returned values are sorted alphbetically. The AWS API may not return all endpoints, so this value is not guaranteed to be stable across applies. -* `zookeeper_connect_string_tls` - A comma separated list of one or more hostname:port pairs to use to connect to the Apache Zookeeper cluster via TLS. The returned values are sorted alphbetically. The AWS API may not return all endpoints, so this value is not guaranteed to be stable across applies. +* `zookeeper_connect_string` - A comma separated list of one or more hostname:port pairs to use to connect to the Apache Zookeeper cluster. The returned values are sorted alphabetically. The AWS API may not return all endpoints, so this value is not guaranteed to be stable across applies. +* `zookeeper_connect_string_tls` - A comma separated list of one or more hostname:port pairs to use to connect to the Apache Zookeeper cluster via TLS. The returned values are sorted alphabetically. The AWS API may not return all endpoints, so this value is not guaranteed to be stable across applies. ## Timeouts diff --git a/website/docs/r/networkmanager_connection.html.markdown b/website/docs/r/networkmanager_connection.html.markdown new file mode 100644 index 00000000000..d46ab005151 --- /dev/null +++ b/website/docs/r/networkmanager_connection.html.markdown @@ -0,0 +1,49 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_connection" +description: |- + Creates a connection between two devices. +--- + +# Resource: aws_networkmanager_connection + +Creates a connection between two devices. +The devices can be a physical or virtual appliance that connects to a third-party appliance in a VPC, or a physical appliance that connects to another physical appliance in an on-premises network. + +## Example Usage + +```terraform +resource "aws_networkmanager_connection" "example" { + global_network_id = aws_networkmanager_global_network.example.id + device_id = aws_networkmanager_device.example1.id + connected_device_id = aws_networkmanager_device.example2.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `connected_device_id` - (Required) The ID of the second device in the connection. +* `connected_link_id` - (Optional) The ID of the link for the second device. +* `description` - (Optional) A description of the connection. +* `device_id` - (Required) The ID of the first device in the connection. +* `global_network_id` - (Required) The ID of the global network. +* `link_id` - (Optional) The ID of the link for the first device. +* `tags` - (Optional) Key-value tags for the connection. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) of the connection. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +`aws_networkmanager_connection` can be imported using the connection ARN, e.g. + +``` +$ terraform import aws_networkmanager_connection.example arn:aws:networkmanager::123456789012:device/global-network-0d47f6t230mz46dy4/connection-07f6fd08867abc123 +``` diff --git a/website/docs/r/networkmanager_customer_gateway_association.html.markdown b/website/docs/r/networkmanager_customer_gateway_association.html.markdown new file mode 100644 index 00000000000..a3dbc642d02 --- /dev/null +++ b/website/docs/r/networkmanager_customer_gateway_association.html.markdown @@ -0,0 +1,80 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_customer_gateway_association" +description: |- + Associates a customer gateway with a device and optionally, with a link. +--- + +# Resource: aws_networkmanager_customer_gateway_association + +Associates a customer gateway with a device and optionally, with a link. +If you specify a link, it must be associated with the specified device. + +## Example Usage + +```terraform +resource "aws_networkmanager_global_network" "example" { + description = "example" +} + +resource "aws_networkmanager_site" "example" { + global_network_id = aws_networkmanager_global_network.example.id +} + +resource "aws_networkmanager_device" "example" { + global_network_id = aws_networkmanager_global_network.example.id + site_id = aws_networkmanager_site.example.id +} + +resource "aws_customer_gateway" "example" { + bgp_asn = 65000 + ip_address = "172.83.124.10" + type = "ipsec.1" +} + +resource "aws_ec2_transit_gateway" "example" {} + +resource "aws_vpn_connection" "example" { + customer_gateway_id = aws_customer_gateway.example.id + transit_gateway_id = aws_ec2_transit_gateway.example.id + type = aws_customer_gateway.example.type + static_routes_only = true +} + +resource "aws_networkmanager_transit_gateway_registration" "example" { + global_network_id = aws_networkmanager_global_network.example.id + transit_gateway_arn = aws_ec2_transit_gateway.example.arn + + depends_on = [aws_vpn_connection.example] +} + +resource "aws_networkmanager_customer_gateway_association" "example" { + global_network_id = aws_networkmanager_global_network.example.id + customer_gateway_arn = aws_customer_gateway.example.arn + device_id = aws_networkmanager_device.example.id + + depends_on = [aws_networkmanager_transit_gateway_registration.example] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `customer_gateway_arn` - (Required) The Amazon Resource Name (ARN) of the customer gateway. +* `device_id` - (Required) The ID of the device. +* `global_network_id` - (Required) The ID of the global network. +* `link_id` - (Optional) The ID of the link. + +## Attributes Reference + +No additional attributes are exported. + +## Import + +`aws_networkmanager_customer_gateway_association` can be imported using the global network ID and customer gateway ARN, e.g. + +``` +$ terraform import aws_networkmanager_customer_gateway_association.example global-network-0d47f6t230mz46dy4,arn:aws:ec2:us-west-2:123456789012:customer-gateway/cgw-123abc05e04123abc +``` diff --git a/website/docs/r/networkmanager_device.html.markdown b/website/docs/r/networkmanager_device.html.markdown new file mode 100644 index 00000000000..2214250cc00 --- /dev/null +++ b/website/docs/r/networkmanager_device.html.markdown @@ -0,0 +1,62 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_device" +description: |- + Creates a device in a global network. +--- + +# Resource: aws_networkmanager_device + +Creates a device in a global network. If you specify both a site ID and a location, +the location of the site is used for visualization in the Network Manager console. + +## Example Usage + +```terraform +resource "aws_networkmanager_device" "example" { + global_network_id = aws_networkmanager_global_network.example.id + site_id = aws_networkmanager_site.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `aws_location` - (Optional) The AWS location of the device. Documented below. +* `description` - (Optional) A description of the device. +* `global_network_id` - (Required) The ID of the global network. +* `location` - (Optional) The location of the device. Documented below. +* `model` - (Optional) The model of device. +* `serial_number` - (Optional) The serial number of the device. +* `site_id` - (Optional) The ID of the site. +* `tags` - (Optional) Key-value tags for the device. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `type` - (Optional) The type of device. +* `vendor` - (Optional) The vendor of the device. + +The `aws_location` object supports the following: + +* `subnet_arn` - (Optional) The Amazon Resource Name (ARN) of the subnet that the device is located in. +* `zone` - (Optional) The Zone that the device is located in. Specify the ID of an Availability Zone, Local Zone, Wavelength Zone, or an Outpost. + +The `location` object supports the following: + +* `address` - (Optional) The physical address. +* `latitude` - (Optional) The latitude. +* `longitude` - (Optional) The longitude. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - The Amazon Resource Name (ARN) of the device. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +`aws_networkmanager_device` can be imported using the device ARN, e.g. + +``` +$ terraform import aws_networkmanager_device.example arn:aws:networkmanager::123456789012:device/global-network-0d47f6t230mz46dy4/device-07f6fd08867abc123 +``` diff --git a/website/docs/r/networkmanager_global_network.html.markdown b/website/docs/r/networkmanager_global_network.html.markdown new file mode 100644 index 00000000000..4323aefaa92 --- /dev/null +++ b/website/docs/r/networkmanager_global_network.html.markdown @@ -0,0 +1,41 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_global_network" +description: |- + Provides a global network resource. +--- + +# Resource: aws_networkmanager_global_network + +Provides a global network resource. + +## Example Usage + +```terraform +resource "aws_networkmanager_global_network" "example" { + description = "example" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `description` - (Optional) Description of the Global Network. +* `tags` - (Optional) Key-value tags for the Global Network. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Global Network Amazon Resource Name (ARN) +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +`aws_networkmanager_global_network` can be imported using the global network ID, e.g. + +``` +$ terraform import aws_networkmanager_global_network.example global-network-0d47f6t230mz46dy4 +``` diff --git a/website/docs/r/networkmanager_link.html.markdown b/website/docs/r/networkmanager_link.html.markdown new file mode 100644 index 00000000000..2c9717a0c00 --- /dev/null +++ b/website/docs/r/networkmanager_link.html.markdown @@ -0,0 +1,59 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_link" +description: |- + Creates a link for a site. +--- + +# Resource: aws_networkmanager_link + +Creates a link for a site. + +## Example Usage + +```terraform +resource "aws_networkmanager_link" "example" { + global_network_id = aws_networkmanager_global_network.example.id + site_id = aws_networkmanager_global_site.example.id + + bandwidth { + upload_speed = 10 + download_speed = 50 + } + + provider_name = "MegaCorp" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `bandwidth` - (Required) The upload speed and download speed in Mbps. Documented below. +* `description` - (Optional) A description of the link. +* `global_network_id` - (Required) The ID of the global network. +* `provider_name` - (Optional) The provider of the link. +* `site_id` - (Required) The ID of the site. +* `tags` - (Optional) Key-value tags for the link. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `type` - (Optional) The type of the link. + +The `bandwidth` object supports the following: + +* `download_speed` - (Optional) Download speed in Mbps. +* `upload_speed` - (Optional) Upload speed in Mbps. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Link Amazon Resource Name (ARN). +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +`aws_networkmanager_link` can be imported using the link ARN, e.g. + +``` +$ terraform import aws_networkmanager_link.example arn:aws:networkmanager::123456789012:link/global-network-0d47f6t230mz46dy4/link-444555aaabbb11223 +``` diff --git a/website/docs/r/networkmanager_link_association.html.markdown b/website/docs/r/networkmanager_link_association.html.markdown new file mode 100644 index 00000000000..dfdd2db2c0c --- /dev/null +++ b/website/docs/r/networkmanager_link_association.html.markdown @@ -0,0 +1,43 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_link_association" +description: |- + Associates a link to a device. +--- + +# Resource: aws_networkmanager_link_association + +Associates a link to a device. +A device can be associated to multiple links and a link can be associated to multiple devices. +The device and link must be in the same global network and the same site. + +## Example Usage + +```terraform +resource "aws_networkmanager_link_association" "example" { + global_network_id = aws_networkmanager_global_network.example.id + link_id = aws_networkmanager_link.example.id + device_id = aws_networkmanager_device.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `device_id` - (Required) The ID of the device. +* `global_network_id` - (Required) The ID of the global network. +* `link_id` - (Required) The ID of the link. + +## Attributes Reference + +No additional attributes are exported. + +## Import + +`aws_networkmanager_link_association` can be imported using the global network ID, link ID and device ID, e.g. + +``` +$ terraform import aws_networkmanager_link_association.example global-network-0d47f6t230mz46dy4,link-444555aaabbb11223,device-07f6fd08867abc123 +``` diff --git a/website/docs/r/networkmanager_site.html.markdown b/website/docs/r/networkmanager_site.html.markdown new file mode 100644 index 00000000000..848913058cd --- /dev/null +++ b/website/docs/r/networkmanager_site.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_site" +description: |- + Creates a site in a global network. +--- + +# Resource: aws_networkmanager_site + +Creates a site in a global network. + +## Example Usage + +```terraform +resource "aws_networkmanager_global_network" "example" { +} + +resource "aws_networkmanager_site" "example" { + global_network_id = aws_networkmanager_global_network.example.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `global_network_id` - (Required) The ID of the Global Network to create the site in. +* `description` - (Optional) Description of the Site. +* `location` - (Optional) The site location as documented below. +* `tags` - (Optional) Key-value tags for the Site. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +The `location` object supports the following: + +* `address` - (Optional) Address of the location. +* `latitude` - (Optional) Latitude of the location. +* `longitude` - (Optional) Longitude of the location. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Site Amazon Resource Name (ARN) +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +`aws_networkmanager_site` can be imported using the site ARN, e.g. + +``` +$ terraform import aws_networkmanager_site.example arn:aws:networkmanager::123456789012:site/global-network-0d47f6t230mz46dy4/site-444555aaabbb11223 +``` diff --git a/website/docs/r/networkmanager_transit_gateway_connect_peer_association.html.markdown b/website/docs/r/networkmanager_transit_gateway_connect_peer_association.html.markdown new file mode 100644 index 00000000000..403439f73ba --- /dev/null +++ b/website/docs/r/networkmanager_transit_gateway_connect_peer_association.html.markdown @@ -0,0 +1,43 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_transit_gateway_connect_peer_association" +description: |- + Associates a transit gateway Connect peer with a device, and optionally, with a link. +--- + +# Resource: aws_networkmanager_transit_gateway_connect_peer_association + +Associates a transit gateway Connect peer with a device, and optionally, with a link. +If you specify a link, it must be associated with the specified device. + +## Example Usage + +```terraform +resource "aws_networkmanager_transit_gateway_connect_peer_association" "example" { + global_network_id = aws_networkmanager_global_network.example.id + device_id = aws_networkmanager_device.example.id + transit_gateway_connect_peer_arn = aws_ec2_transit_gateway_connect_peer.example.arn +} +``` + +## Argument Reference + +The following arguments are supported: + +* `device_id` - (Required) The ID of the device. +* `global_network_id` - (Required) The ID of the global network. +* `link_id` - (Optional) The ID of the link. +* `transit_gateway_connect_peer_arn` - (Required) The Amazon Resource Name (ARN) of the Connect peer. + +## Attributes Reference + +No additional attributes are exported. + +## Import + +`aws_networkmanager_transit_gateway_connect_peer_association` can be imported using the global network ID and customer gateway ARN, e.g. + +``` +$ terraform import aws_networkmanager_transit_gateway_connect_peer_association.example global-network-0d47f6t230mz46dy4,arn:aws:ec2:us-west-2:123456789012:transit-gateway-connect-peer/tgw-connect-peer-12345678 +``` diff --git a/website/docs/r/networkmanager_transit_gateway_registration.html.markdown b/website/docs/r/networkmanager_transit_gateway_registration.html.markdown new file mode 100644 index 00000000000..a8eed7cb2d0 --- /dev/null +++ b/website/docs/r/networkmanager_transit_gateway_registration.html.markdown @@ -0,0 +1,47 @@ +--- +subcategory: "Network Manager" +layout: "aws" +page_title: "AWS: aws_networkmanager_transit_gateway_registration" +description: |- + Registers a transit gateway to a global network. +--- + +# Resource: aws_networkmanager_transit_gateway_registration + +Registers a transit gateway to a global network. The transit gateway can be in any AWS Region, +but it must be owned by the same AWS account that owns the global network. +You cannot register a transit gateway in more than one global network. + +## Example Usage + +```terraform +resource "aws_networkmanager_global_network" "example" { + description = "example" +} + +resource "aws_ec2_transit_gateway" "example" {} + +resource "aws_networkmanager_transit_gateway_registration" "example" { + global_network_id = aws_networkmanager_global_network.example.id + transit_gateway_arn = aws_ec2_transit_gateway.example.arn +} +``` + +## Argument Reference + +The following arguments are supported: + +* `global_network_id` - (Required) The ID of the Global Network to register to. +* `transit_gateway_arn` - (Required) The ARN of the Transit Gateway to register. + +## Attributes Reference + +No additional attributes are exported. + +## Import + +`aws_networkmanager_transit_gateway_registration` can be imported using the global network ID and transit gateway ARN, e.g. + +``` +$ terraform import aws_networkmanager_transit_gateway_registration.example global-network-0d47f6t230mz46dy4,arn:aws:ec2:us-west-2:123456789012:transit-gateway/tgw-123abc05e04123abc +``` diff --git a/website/docs/r/rds_global_cluster.html.markdown b/website/docs/r/rds_global_cluster.html.markdown index 48aa664fd2e..bf2cfd3c6a8 100644 --- a/website/docs/r/rds_global_cluster.html.markdown +++ b/website/docs/r/rds_global_cluster.html.markdown @@ -162,16 +162,53 @@ resource "aws_rds_global_cluster" "example" { } ``` +### Upgrading Engine Versions + +When you upgrade the version of an `aws_rds_global_cluster`, Terraform will attempt to in-place upgrade the engine versions of all associated clusters. Since the `aws_rds_cluster` resource is being updated through the `aws_rds_global_cluster`, you are likely to get an error (`Provider produced inconsistent final plan`). To avoid this, use the `lifecycle` `ignore_changes` meta argument as shown below on the `aws_rds_cluster`. + +```terraform +resource "aws_rds_global_cluster" "example" { + global_cluster_identifier = "kyivkharkiv" + engine = "aurora-mysql" + engine_version = "5.7.mysql_aurora.2.07.5" +} + +resource "aws_rds_cluster" "primary" { + allow_major_version_upgrade = true + apply_immediately = true + cluster_identifier = "odessadnipro" + database_name = "totoro" + engine = aws_rds_global_cluster.example.engine + engine_version = aws_rds_global_cluster.example.engine_version + global_cluster_identifier = aws_rds_global_cluster.example.id + master_password = "satsukimae" + master_username = "maesatsuki" + skip_final_snapshot = true + + lifecycle { + ignore_changes = [engine_version] + } +} + +resource "aws_rds_cluster_instance" "primary" { + apply_immediately = true + cluster_identifier = aws_rds_cluster.primary.id + engine = aws_rds_cluster.primary.engine + engine_version = aws_rds_cluster.primary.engine_version + identifier = "donetsklviv" + instance_class = "db.r4.large" +} +``` + ## Argument Reference The following arguments are supported: -* `global_cluster_identifier` - (Required, Forces new resources) The global cluster identifier. +* `global_cluster_identifier` - (Required, Forces new resources) Global cluster identifier. * `database_name` - (Optional, Forces new resources) Name for an automatically created database on cluster creation. * `deletion_protection` - (Optional) If the Global Cluster should have deletion protection enabled. The database can't be deleted when this value is set to `true`. The default is `false`. * `engine` - (Optional, Forces new resources) Name of the database engine to be used for this DB cluster. Terraform will only perform drift detection if a configuration value is provided. Valid values: `aurora`, `aurora-mysql`, `aurora-postgresql`. Defaults to `aurora`. Conflicts with `source_db_cluster_identifier`. -* `engine_version` - (Optional) Engine version of the Aurora global database. Upgrading the engine version will result in all cluster members being immediately updated. - * **NOTE:** When the engine is set to `aurora-mysql`, an engine version compatible with global database is required. The earliest available version is `5.7.mysql_aurora.2.06.0`. +* `engine_version` - (Optional) Engine version of the Aurora global database. The `engine`, `engine_version`, and `instance_class` (on the `aws_rds_cluster_instance`) must together support global databases. See [Using Amazon Aurora global databases](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora-global-database.html) for more information. By upgrading the engine version, Terraform will upgrade cluster members. **NOTE:** To avoid an `inconsistent final plan` error while upgrading, use the `lifecycle` `ignore_changes` for `engine_version` meta argument on the associated `aws_rds_cluster` resource as shown above in [Upgrading Engine Versions](#upgrading-engine-versions) example. * `force_destroy` - (Optional) Enable to remove DB Cluster members from Global Cluster on destroy. Required with `source_db_cluster_identifier`. * `source_db_cluster_identifier` - (Optional) Amazon Resource Name (ARN) to use as the primary DB Cluster of the Global Cluster on creation. Terraform cannot perform drift detection of this value. * `storage_encrypted` - (Optional, Forces new resources) Specifies whether the DB cluster is encrypted. The default is `false` unless `source_db_cluster_identifier` is specified and encrypted. Terraform will only perform drift detection if a configuration value is provided. @@ -187,6 +224,15 @@ In addition to all arguments above, the following attributes are exported: * `global_cluster_resource_id` - AWS Region-unique, immutable identifier for the global database cluster. This identifier is found in AWS CloudTrail log entries whenever the AWS KMS key for the DB cluster is accessed * `id` - RDS Global Cluster identifier +## Timeouts + +`aws_rds_global_cluster` provides the following +[Timeouts](https://www.terraform.io/docs/configuration/blocks/resources/syntax.html#operation-timeouts) configuration options: + +- `create` - (Default `30 minutes`) +- `update` - (Default `90 minutes`) +- `delete` - (Default `30 minutes`) + ## Import `aws_rds_global_cluster` can be imported by using the RDS Global Cluster identifier, e.g., diff --git a/website/docs/r/transfer_server.html.markdown b/website/docs/r/transfer_server.html.markdown index 004b76a4890..dbdd8e63fa4 100644 --- a/website/docs/r/transfer_server.html.markdown +++ b/website/docs/r/transfer_server.html.markdown @@ -103,6 +103,8 @@ The following arguments are supported: * `function` - (Optional) The ARN for a lambda function to use for the Identity provider. * `logging_role` - (Optional) Amazon Resource Name (ARN) of an IAM role that allows the service to write your SFTP users’ activity to your Amazon CloudWatch logs for monitoring and auditing purposes. * `force_destroy` - (Optional) A boolean that indicates all users associated with the server should be deleted so that the Server can be destroyed without error. The default value is `false`. This option only applies to servers configured with a `SERVICE_MANAGED` `identity_provider_type`. +* `post_authentication_login_banner`- (Optional) Specify a string to display when users connect to a server. This string is displayed after the user authenticates. The SFTP protocol does not support post-authentication display banners. +* `pre_authentication_login_banner`- (Optional) Specify a string to display when users connect to a server. This string is displayed before the user authenticates. * `security_policy_name` - (Optional) Specifies the name of the security policy that is attached to the server. Possible values are `TransferSecurityPolicy-2018-11`, `TransferSecurityPolicy-2020-06`, and `TransferSecurityPolicy-FIPS-2020-06`. Default value is: `TransferSecurityPolicy-2018-11`. * `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://www.terraform.io/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.