From 7c19dd5b7c2d088f2c04a2bd5ebae8b2681052d8 Mon Sep 17 00:00:00 2001 From: gdavison Date: Thu, 6 Jul 2023 19:56:21 +0000 Subject: [PATCH] Deployed 185b328 with MkDocs version: 1.4.2 --- .nojekyll | 0 404.html | 793 ++ acc-test-environment-variables/index.html | 1187 +++ add-a-new-datasource/index.html | 1093 +++ add-a-new-region/index.html | 993 +++ add-a-new-resource/index.html | 1093 +++ add-a-new-service/index.html | 1018 +++ add-import-support/index.html | 837 ++ adding-a-tag-resource/index.html | 1004 +++ assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.51d95adb.min.js | 29 + assets/javascripts/bundle.51d95adb.min.js.map | 8 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.e5c33ebb.min.js | 42 + .../workers/search.e5c33ebb.min.js.map | 8 + assets/stylesheets/main.558e4712.min.css | 1 + assets/stylesheets/main.558e4712.min.css.map | 1 + assets/stylesheets/palette.2505c338.min.css | 1 + .../stylesheets/palette.2505c338.min.css.map | 1 + aws-go-sdk-base/index.html | 831 ++ aws-go-sdk-versions/index.html | 936 +++ bugs-and-enhancements/index.html | 855 +++ changelog-process/index.html | 1151 +++ core-services/index.html | 850 +++ data-handling-and-conversion/index.html | 2273 ++++++ debugging/index.html | 1369 ++++ dependency-updates/index.html | 1030 +++ development-environment/index.html | 957 +++ documentation-changes/index.html | 845 +++ error-handling/index.html | 1459 ++++ faq/index.html | 1053 +++ images/breakpoints.png | Bin 0 -> 109768 bytes images/debug-button.png | Bin 0 -> 2244 bytes images/debug-controls.png | Bin 0 -> 47101 bytes images/debug-gui.png | Bin 0 -> 206714 bytes images/hashicorp-black.png | Bin 0 -> 6857 bytes images/hashicorp.png | Bin 0 -> 10852 bytes images/launch-json.png | Bin 0 -> 28571 bytes index.html | 1063 +++ issue-reporting-and-lifecycle/index.html | 1023 +++ naming/index.html | 1475 ++++ prioritization/index.html | 1083 +++ provider-design/index.html | 1207 +++ raising-a-pull-request/index.html | 1046 +++ resource-filtering/index.html | 969 +++ resource-name-generation/index.html | 1060 +++ resource-tagging/index.html | 1591 ++++ retries-and-waiters/index.html | 1603 ++++ .../index.html | 3073 ++++++++ search/search_index.json | 1 + sitemap.xml | 163 + sitemap.xml.gz | Bin 0 -> 216 bytes skaff/index.html | 1057 +++ stylesheets/extra.css | 8 + terraform-plugin-versions/index.html | 835 ++ 82 files changed, 46170 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 acc-test-environment-variables/index.html create mode 100644 add-a-new-datasource/index.html create mode 100644 add-a-new-region/index.html create mode 100644 add-a-new-resource/index.html create mode 100644 add-a-new-service/index.html create mode 100644 add-import-support/index.html create mode 100644 adding-a-tag-resource/index.html create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.51d95adb.min.js create mode 100644 assets/javascripts/bundle.51d95adb.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.e5c33ebb.min.js create mode 100644 assets/javascripts/workers/search.e5c33ebb.min.js.map create mode 100644 assets/stylesheets/main.558e4712.min.css create mode 100644 assets/stylesheets/main.558e4712.min.css.map create mode 100644 assets/stylesheets/palette.2505c338.min.css create mode 100644 assets/stylesheets/palette.2505c338.min.css.map create mode 100644 aws-go-sdk-base/index.html create mode 100644 aws-go-sdk-versions/index.html create mode 100644 bugs-and-enhancements/index.html create mode 100644 changelog-process/index.html create mode 100644 core-services/index.html create mode 100644 data-handling-and-conversion/index.html create mode 100644 debugging/index.html create mode 100644 dependency-updates/index.html create mode 100644 development-environment/index.html create mode 100644 documentation-changes/index.html create mode 100644 error-handling/index.html create mode 100644 faq/index.html create mode 100644 images/breakpoints.png create mode 100644 images/debug-button.png create mode 100644 images/debug-controls.png create mode 100644 images/debug-gui.png create mode 100644 images/hashicorp-black.png create mode 100644 images/hashicorp.png create mode 100644 images/launch-json.png create mode 100644 index.html create mode 100644 issue-reporting-and-lifecycle/index.html create mode 100644 naming/index.html create mode 100644 prioritization/index.html create mode 100644 provider-design/index.html create mode 100644 raising-a-pull-request/index.html create mode 100644 resource-filtering/index.html create mode 100644 resource-name-generation/index.html create mode 100644 resource-tagging/index.html create mode 100644 retries-and-waiters/index.html create mode 100644 running-and-writing-acceptance-tests/index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 skaff/index.html create mode 100644 stylesheets/extra.css create mode 100644 terraform-plugin-versions/index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/404.html b/404.html new file mode 100644 index 000000000000..d46b3a6f86fd --- /dev/null +++ b/404.html @@ -0,0 +1,793 @@ + + + + + + + + + + + + + + + + + + + + Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/acc-test-environment-variables/index.html b/acc-test-environment-variables/index.html new file mode 100644 index 000000000000..a36f18a60001 --- /dev/null +++ b/acc-test-environment-variables/index.html @@ -0,0 +1,1187 @@ + + + + + + + + + + + + + + + + + + + + + + + + Acceptance Test Environment Variables - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Acceptance Testing Environment Variable Dictionary#

+

Environment variables (beyond standard AWS Go SDK ones) used by acceptance testing. See also the internal/acctest package.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableDescription
ACM_CERTIFICATE_ROOT_DOMAINRoot domain name to use with ACM Certificate testing.
ACM_CERTIFICATE_MULTIPLE_ISSUED_DOMAINDomain name of ACM Certificate with a multiple issued certificates. DEPRECATED: Should be replaced with aws_acm_certficate resource usage in tests.
ACM_CERTIFICATE_MULTIPLE_ISSUED_MOST_RECENT_ARNAmazon Resource Name of most recent ACM Certificate with a multiple issued certificates. DEPRECATED: Should be replaced with aws_acm_certficate resource usage in tests.
ACM_CERTIFICATE_SINGLE_ISSUED_DOMAINDomain name of ACM Certificate with a single issued certificate. DEPRECATED: Should be replaced with aws_acm_certficate resource usage in tests.
ACM_CERTIFICATE_SINGLE_ISSUED_MOST_RECENT_ARNAmazon Resource Name of most recent ACM Certificate with a single issued certificate. DEPRECATED: Should be replaced with aws_acm_certficate resource usage in tests.
ADM_CLIENT_IDIdentifier for Amazon Device Manager Client in Pinpoint testing.
AMPLIFY_DOMAIN_NAMEDomain name to use for Amplify domain association testing.
AMPLIFY_GITHUB_ACCESS_TOKENGitHub access token used for AWS Amplify testing.
AMPLIFY_GITHUB_REPOSITORYGitHub repository used for AWS Amplify testing.
ADM_CLIENT_SECRETSecret for Amazon Device Manager Client in Pinpoint testing.
APNS_BUNDLE_IDIdentifier for Apple Push Notification Service Bundle in Pinpoint testing.
APNS_CERTIFICATECertificate (PEM format) for Apple Push Notification Service in Pinpoint testing.
APNS_CERTIFICATE_PRIVATE_KEYPrivate key for Apple Push Notification Service in Pinpoint testing.
APNS_SANDBOX_BUNDLE_IDIdentifier for Sandbox Apple Push Notification Service Bundle in Pinpoint testing.
APNS_SANDBOX_CERTIFICATECertificate (PEM format) for Sandbox Apple Push Notification Service in Pinpoint testing.
APNS_SANDBOX_CERTIFICATE_PRIVATE_KEYPrivate key for Sandbox Apple Push Notification Service in Pinpoint testing.
APNS_SANDBOX_CREDENTIALCredential contents for Sandbox Apple Push Notification Service in SNS Application Platform testing. Conflicts with APNS_SANDBOX_CREDENTIAL_PATH.
APNS_SANDBOX_CREDENTIAL_PATHPath to credential for Sandbox Apple Push Notification Service in SNS Application Platform testing. Conflicts with APNS_SANDBOX_CREDENTIAL.
APNS_SANDBOX_PRINCIPALPrincipal contents for Sandbox Apple Push Notification Service in SNS Application Platform testing. Conflicts with APNS_SANDBOX_PRINCIPAL_PATH.
APNS_SANDBOX_PRINCIPAL_PATHPath to principal for Sandbox Apple Push Notification Service in SNS Application Platform testing. Conflicts with APNS_SANDBOX_PRINCIPAL.
APNS_SANDBOX_TEAM_IDIdentifier for Sandbox Apple Push Notification Service Team in Pinpoint testing.
APNS_SANDBOX_TOKEN_KEYToken key file content (.p8 format) for Sandbox Apple Push Notification Service in Pinpoint testing.
APNS_SANDBOX_TOKEN_KEY_IDIdentifier for Sandbox Apple Push Notification Service Token Key in Pinpoint testing.
APNS_TEAM_IDIdentifier for Apple Push Notification Service Team in Pinpoint testing.
APNS_TOKEN_KEYToken key file content (.p8 format) for Apple Push Notification Service in Pinpoint testing.
APNS_TOKEN_KEY_IDIdentifier for Apple Push Notification Service Token Key in Pinpoint testing.
APNS_VOIP_BUNDLE_IDIdentifier for VOIP Apple Push Notification Service Bundle in Pinpoint testing.
APNS_VOIP_CERTIFICATECertificate (PEM format) for VOIP Apple Push Notification Service in Pinpoint testing.
APNS_VOIP_CERTIFICATE_PRIVATE_KEYPrivate key for VOIP Apple Push Notification Service in Pinpoint testing.
APNS_VOIP_TEAM_IDIdentifier for VOIP Apple Push Notification Service Team in Pinpoint testing.
APNS_VOIP_TOKEN_KEYToken key file content (.p8 format) for VOIP Apple Push Notification Service in Pinpoint testing.
APNS_VOIP_TOKEN_KEY_IDIdentifier for VOIP Apple Push Notification Service Token Key in Pinpoint testing.
APPRUNNER_CUSTOM_DOMAINA custom domain endpoint (root domain, subdomain, or wildcard) for AppRunner Custom Domain Association testing.
AUDITMANAGER_DEREGISTER_ACCOUNT_ON_DESTROYFlag to execute tests that will disable AuditManager in the account upon destruction.
AUDITMANAGER_ORGANIZATION_ADMIN_ACCOUNT_IDOrganization admin account identifier for use in AuditManager testing.
AWS_ALTERNATE_ACCESS_KEY_IDAWS access key ID with access to a secondary AWS account for tests requiring multiple accounts. Requires AWS_ALTERNATE_SECRET_ACCESS_KEY. Conflicts with AWS_ALTERNATE_PROFILE.
AWS_ALTERNATE_SECRET_ACCESS_KEYAWS secret access key with access to a secondary AWS account for tests requiring multiple accounts. Requires AWS_ALTERNATE_ACCESS_KEY_ID. Conflicts with AWS_ALTERNATE_PROFILE.
AWS_ALTERNATE_PROFILEAWS profile with access to a secondary AWS account for tests requiring multiple accounts. Conflicts with AWS_ALTERNATE_ACCESS_KEY_ID and AWS_ALTERNATE_SECRET_ACCESS_KEY.
AWS_ALTERNATE_REGIONSecondary AWS region for tests requiring multiple regions. Defaults to us-east-1.
AWS_API_GATEWAY_DOMAIN_NAME_CERTIFICATE_BODYCertificate body of publicly trusted certificate for API Gateway Domain Name testing.
AWS_API_GATEWAY_DOMAIN_NAME_CERTIFICATE_CHAINCertificate chain of publicly trusted certificate for API Gateway Domain Name testing.
AWS_API_GATEWAY_DOMAIN_NAME_CERTIFICATE_PRIVATE_KEYPrivate key of publicly trusted certificate for API Gateway Domain Name testing.
AWS_API_GATEWAY_DOMAIN_NAME_REGIONAL_CERTIFICATE_NAME_ENABLEDFlag to enable API Gateway Domain Name regional certificate upload testing.
AWS_CODEBUILD_BITBUCKET_SOURCE_LOCATIONBitBucket source URL for CodeBuild testing. CodeBuild must have access to this repository via OAuth or Source Credentials. Defaults to https://terraform@bitbucket.org/terraform/aws-test.git.
AWS_CODEBUILD_GITHUB_SOURCE_LOCATIONGitHub source URL for CodeBuild testing. CodeBuild must have access to this repository via OAuth or Source Credentials. Defaults to https://github.com/hashibot-test/aws-test.git.
AWS_DEFAULT_REGIONPrimary AWS region for tests. Defaults to us-west-2.
AWS_DETECTIVE_MEMBER_EMAILEmail address for Detective Member testing. A valid email address associated with an AWS root account is required for tests to pass.
AWS_EC2_CLIENT_VPN_LIMITConcurrency limit for Client VPN acceptance tests. Default is 5 if not specified.
AWS_EC2_EIP_PUBLIC_IPV4_POOLIdentifier for EC2 Public IPv4 Pool for EC2 EIP testing.
AWS_GUARDDUTY_MEMBER_ACCOUNT_IDIdentifier of AWS Account for GuardDuty Member testing. DEPRECATED: Should be replaced with standard alternate account handling for tests.
AWS_GUARDDUTY_MEMBER_EMAILEmail address for GuardDuty Member testing. DEPRECATED: It may be possible to use a placeholder email address instead.
AWS_LAMBDA_IMAGE_LATEST_IDECR repository image URI (tagged as latest) for Lambda container image acceptance tests.
AWS_LAMBDA_IMAGE_V1_IDECR repository image URI (tagged as v1) for Lambda container image acceptance tests.
AWS_LAMBDA_IMAGE_V2_IDECR repository image URI (tagged as v2) for Lambda container image acceptance tests.
AWS_THIRD_ACCESS_KEY_IDAWS access key ID with access to a third AWS account for tests requiring multiple accounts. Requires AWS_THIRD_SECRET_ACCESS_KEY. Conflicts with AWS_THIRD_PROFILE.
AWS_THIRD_SECRET_ACCESS_KEYAWS secret access key with access to a third AWS account for tests requiring multiple accounts. Requires AWS_THIRD_ACCESS_KEY_ID. Conflicts with AWS_THIRD_PROFILE.
AWS_THIRD_PROFILEAWS profile with access to a third AWS account for tests requiring multiple accounts. Conflicts with AWS_THIRD_ACCESS_KEY_ID and AWS_THIRD_SECRET_ACCESS_KEY.
AWS_THIRD_REGIONThird AWS region for tests requiring multiple regions. Defaults to us-east-2.
DX_CONNECTION_IDIdentifier for Direct Connect Connection testing.
DX_VIRTUAL_INTERFACE_IDIdentifier for Direct Connect Virtual Interface testing.
EC2_SECURITY_GROUP_RULES_PER_GROUP_LIMITEC2 Quota for Rules per Security Group. Defaults to 50. DEPRECATED: Can be augmented or replaced with Service Quotas lookup.
EVENT_BRIDGE_PARTNER_EVENT_BUS_NAMEAmazon EventBridge partner event bus name.
EVENT_BRIDGE_PARTNER_EVENT_SOURCE_NAMEAmazon EventBridge partner event source name.
GCM_API_KEYAPI Key for Google Cloud Messaging in Pinpoint and SNS Platform Application testing.
GITHUB_TOKENGitHub token for CodePipeline testing.
GLOBALACCERATOR_BYOIP_IPV4_ADDRESSIPv4 address from a BYOIP CIDR of AWS Account used for testing Global Accelerator's BYOIP accelerator.
GRAFANA_SSO_GROUP_IDAWS SSO group ID for Grafana testing.
GRAFANA_SSO_USER_IDAWS SSO user ID for Grafana testing.
MACIE_MEMBER_ACCOUNT_IDIdentifier of AWS Account for Macie Member testing. DEPRECATED: Should be replaced with standard alternate account handling for tests.
QUICKSIGHT_NAMESPACEQuickSight namespace name for testing.
QUICKSIGHT_ATHENA_TESTING_ENABLEDEnable QuickSight tests dependent on Amazon Athena resources.
ROUTE53DOMAINS_DOMAIN_NAMERegistered domain for Route 53 Domains testing.
SAGEMAKER_IMAGE_VERSION_BASE_IMAGESageMaker base image to use for tests.
SERVICEQUOTAS_INCREASE_ON_CREATE_QUOTA_CODEQuota Code for Service Quotas testing (submits support case).
SERVICEQUOTAS_INCREASE_ON_CREATE_SERVICE_CODEService Code for Service Quotas testing (submits support case).
SERVICEQUOTAS_INCREASE_ON_CREATE_VALUEValue of quota increase for Service Quotas testing (submits support case).
SES_DOMAIN_IDENTITY_ROOT_DOMAINRoot domain name of publicly accessible and Route 53 configurable domain for SES Domain Identity testing.
SES_DEDICATED_IPDedicated IP address for testing IP assignment with a "Standard" (non-managed) SES dedicated IP pool.
SWF_DOMAIN_TESTING_ENABLEDEnables SWF Domain testing (API does not support deletions).
TEST_AWS_ORGANIZATION_ACCOUNT_EMAIL_DOMAINEmail address for Organizations Account testing.
TEST_AWS_SES_VERIFIED_EMAIL_ARNVerified SES Email Identity for use in Cognito User Pool testing.
TF_ACCEnables Go tests containing resource.Test() and resource.ParallelTest().
TF_ACC_ASSUME_ROLE_ARNAmazon Resource Name of existing IAM Role to use for limited permissions acceptance testing.
TF_AWS_LICENSE_MANAGER_GRANT_HOME_REGIONRegion where a License Manager license is imported.
TF_AWS_LICENSE_MANAGER_GRANT_LICENSE_ARNARN for a License Manager license imported into the current account.
TF_AWS_LICENSE_MANAGER_GRANT_PRINCIPALARN of a principal to share the License Manager license with. Either a root user, Organization, or Organizational Unit.
TF_TEST_CLOUDFRONT_RETAINFlag to disable but dangle CloudFront Distributions during testing to reduce feedback time (must be manually destroyed afterwards)
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/add-a-new-datasource/index.html b/add-a-new-datasource/index.html new file mode 100644 index 000000000000..14d0007d00a0 --- /dev/null +++ b/add-a-new-datasource/index.html @@ -0,0 +1,1093 @@ + + + + + + + + + + + + + + + + + + + + + + + + Data source - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Adding a New Data Source#

+

New data sources are required when AWS adds a new service, or adds new features within an existing service which would require a new data source to allow practitioners to query existing resources of that type for use in their configurations. Anything with a Describe or Get endpoint could make a data source, but some are more useful than others.

+

Each data source should be submitted for review in isolation, pull requests containing multiple data sources and/or resources are harder to review and the maintainers will normally ask for them to be broken apart.

+

Prerequisites#

+

If this is the first addition of a data source for a new service, please ensure the Service Client for the new service has been added and merged. See Adding a new Service for details.

+

Determine which version of the AWS SDK for Go the resource will be built upon. For more information and instructions on how to determine this choice, please read AWS SDK for Go Versions

+

Steps to Add a Data Source#

+

Fork the Provider and Create a Feature Branch#

+

For a new data source use a branch named f-{datasource name} for example: f-ec2-vpc. See Raising a Pull Request for more details.

+

Create and Name the Data Source#

+

See the Naming Guide for details on how to name the new resource and the resource file. Not following the naming standards will cause extra delay as maintainers request that you make changes.

+

Use the skaff provider scaffolding tool to generate new resource and test templates using your chosen name ensuring you provide the v1 flag if you are targeting version 1 of the aws-go-sdk. Doing so will ensure that any boilerplate code, structural best practices and repetitive naming is done for you and always represents our most current standards.

+

Fill out the Data Source Schema#

+

In the internal/service/<service>/<service>_data_source.go file you will see a Schema property which exists as a map of Schema objects. This relates the AWS API data model with the Terraform resource itself. For each property you want to make available in Terraform, you will need to add it as an attribute, and choose the correct data type.

+

Attribute names are to specified in snake_case as opposed to the AWS API which is CamelCase

+

Implement Read Handler#

+

These will map the AWS API response to the data source schema. You will also need to handle different response types (including errors correctly). For complex attributes you will need to implement Flattener or Expander functions. The Data Handling and Conversion Guide covers everything you need to know for mapping AWS API responses to Terraform State and vice-versa. The Error Handling Guide covers everything you need to know about handling AWS API responses consistently.

+

Register Data Source to the provider#

+

Data Sources use a self registration process that adds them to the provider using the @SDKDataSource() annotation in the datasource's comments. Run make gen to register the datasource. This will add an entry to the service_package_gen.go file located in the service package folder.

+
package something
+
+import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+// @SDKDataSource("aws_something_example", name="Example")
+func DataSourceExample() *schema.Resource {
+    return &schema.Resource{
+        // some configuration
+    }
+}
+
+

Write Passing Acceptance Tests#

+

In order to adequately test the data source we will need to write a complete set of Acceptance Tests. You will need an AWS account for this which allows the provider to read to state of the associated resource. See Writing Acceptance Tests for a detailed guide on how to approach these.

+

You will need at minimum:

+
    +
  • Basic Test - Tests full lifecycle (CRUD + Import) of a minimal configuration (all required fields, no optional).
  • +
  • Disappears Test - Tests what Terraform does if a resource it is tracking can no longer be found.
  • +
  • Per Attribute Tests - For each attribute a test should exists which tests that particular attribute in isolation alongside any required fields.
  • +
+

Create Documentation for the Data Source#

+

Add a file covering the use of the new data source in website/docs/d/<service>_<name>.md. You may want to also add examples of the data source in use particularly if its use is complex, or relies on resources in another service. This documentation will appear on the Terraform Registry when the data source is made available in a provider release. It is fine to link out to AWS Documentation where appropriate, particularly for values which are likely to change.

+

Ensure Format and Lint Checks are Passing Locally#

+

Run go fmt to format your code, and install and run all linters to detect and resolve any structural issues with the implementation or documentation.

+
make fmt
+make tools        # install linters and dependencies
+make lint         # run provider linters
+make docs-lint    # run documentation linters
+make website-lint # run website documentation linters
+
+

Raise a Pull Request#

+

See Raising a Pull Request.

+

Wait for Prioritization#

+

In general, pull requests are triaged within a few days of creation and are prioritized based on community reactions. Please view our prioritization guide for full details of the process.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/add-a-new-region/index.html b/add-a-new-region/index.html new file mode 100644 index 000000000000..e02a54987805 --- /dev/null +++ b/add-a-new-region/index.html @@ -0,0 +1,993 @@ + + + + + + + + + + + + + + + + + + + + + + + + AWS Region - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Adding a Newly Released AWS Region#

+

New regions can typically be used immediately with the provider, with two important caveats:

+
    +
  • Regions often need to be explicitly enabled via the AWS console. See ap-east-1 launch blog for an example of how to enable a new region for use.
  • +
  • Until the provider is aware of the new region, automatic region validation will fail. In order to use the region before validation support is added to the provider you will need to disable region validation by doing the following:
  • +
+
provider "aws" {
+  # ... potentially other configuration ...
+
+  region                 = "me-south-1"
+  skip_region_validation = true
+}
+
+

Enabling Region Validation#

+

Support for region validation requires that the provider has an updated AWS Go SDK dependency that includes the new region. These are added to the AWS Go SDK aws/endpoints/defaults.go file and generally noted in the AWS Go SDK CHANGELOG as aws/endpoints: Updated Regions. This also needs to be done in the core Terraform binary itself to enable it for the S3 backend. The provider currently takes a dependency on both v1 AND v2 of the AWS Go SDK, as we start to base new (and migrate) resources on v2. Many of the authentication and provider level configuration interactions are also located in the aws-go-sdk-base library. As all of these things take direct dependencies and as a result there ends up being quite a few places these dependency updates need to be made.

+

Update aws-go-sdk-base#

+

aws-go-sdk-base

+ +

Update Terraform AWS Provider#

+

provider

+ +

Update Terraform Core (S3 Backend)#

+

core

+ +
go get github.com/aws/aws-sdk-go@v#.#.#
+go mod tidy
+
+

See the Changelog Process document for example changelog format.

+

Update Region Specific values in static Data Sources#

+

Some data sources include static values specific to regions that are not available via a standard AWS API call. These will need to be manually updated. AWS employees can code search previous region values to find new region values in internal packages like RIPStaticConfig if they are not documented yet.

+ + + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/add-a-new-resource/index.html b/add-a-new-resource/index.html new file mode 100644 index 000000000000..46d69b2d9f60 --- /dev/null +++ b/add-a-new-resource/index.html @@ -0,0 +1,1093 @@ + + + + + + + + + + + + + + + + + + + + + + + + Resource - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Adding a New Resource#

+

New resources are required when AWS adds a new service, or adds new features within an existing service which would require a new resource to manage in Terraform. Typically anything with a new set of CRUD API endpoints is a great candidate for a new resource.

+

Each resource should be submitted for review in isolation. Pull requests containing multiple resources are harder to review and the maintainers will normally ask for them to be broken apart.

+

Prerequisites#

+

If this is the first resource for a new service, please ensure the Service Client for the new service has been added and merged. See Adding a new Service for details.

+

Determine which version of the AWS SDK for Go the resource will be built upon. For more information and instructions on how to determine this choice, please read AWS SDK for Go Versions

+

Steps to Add a Resource#

+

Fork the provider and create a feature branch#

+

For a new resources use a branch named f-{resource name} for example: f-ec2-vpc. See Raising a Pull Request for more details.

+

Create and Name the Resource#

+

See the Naming Guide for details on how to name the new resource and the resource file. Not following the naming standards will cause extra delay as maintainers request that you make changes.

+

Use the skaff provider scaffolding tool to generate new resource and test templates using your chosen name ensuring you provide the v1 flag if you are targeting version 1 of the aws-go-sdk. Doing so will ensure that any boilerplate code, structural best practices and repetitive naming is done for you and always represents our most current standards.

+

Fill out the Resource Schema#

+

In the internal/service/<service>/<service>.go file you will see a Schema property which exists as a map of Schema objects. This relates the AWS API data model with the Terraform resource itself. For each property you want to make available in Terraform, you will need to add it as an attribute, choose the correct data type and supply the correct Schema Behaviors in order to ensure Terraform knows how to correctly handle the value.

+

Typically you will add arguments to represent the values that are under control by Terraform, and attributes in order to supply read-only values as references for Terraform. These are distinguished by Schema Behavior.

+

Attribute names are to specified in camel_case as opposed to the AWS API which is CamelCase

+

Implement CRUD handlers#

+

These will map planned Terraform state to the AWS API call, or an AWS API response to an applied Terraform state. You will also need to handle different response types (including errors correctly). For complex attributes you will need to implement Flattener or Expander functions. The Data Handling and Conversion Guide covers everything you need to know for mapping AWS API responses to Terraform State and vice-versa. The Error Handling Guide covers everything you need to know about handling AWS API responses consistently.

+

Register Resource to the provider#

+

Resources use a self registration process that adds them to the provider using the @SDKResource() annotation in the resource's comments. Run make gen to register the resource. This will add an entry to the service_package_gen.go file located in the service package folder.

+
package something
+
+import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+// @SDKResource("aws_something_example", name="Example)
+func ResourceExample() *schema.Resource {
+    return &schema.Resource{
+        // some configuration
+    }
+}
+
+

Write passing Acceptance Tests#

+

In order to adequately test the resource we will need to write a complete set of Acceptance Tests. You will need an AWS account for this which allows the creation of that resource. See Writing Acceptance Tests for a detailed guide on how to approach these.

+

You will need at minimum:

+
    +
  • Basic Test - Tests full lifecycle (CRUD + Import) of a minimal configuration (all required fields, no optional).
  • +
  • Disappears Test - Tests what Terraform does if a resource it is tracking can no longer be found.
  • +
  • Argument Tests - All arguments should be tested in a pragmatic way. Ensure that each argument can be initially set, updated, and cleared, as applicable. Depending on the logic and interaction of arguments, this may take one to several separate tests.
  • +
+

Create documentation for the resource#

+

Add a file covering the use of the new resource in website/docs/r/<service>_<name>.md. Add more examples if it is complex or relies on resources in another service. This documentation will appear on the Terraform Registry when the resource is made available in a provider release. Link to AWS Documentation where appropriate, particularly for values which are likely to change.

+

Ensure format and lint checks are passing locally#

+

Format your code and check linters to detect various issues.

+
make fmt
+make tools     # install linters and dependencies
+make lint      # run provider linters
+make docs-lint # run documentation linters
+
+

Raise a Pull Request#

+

See Raising a Pull Request.

+

Wait for Prioritization#

+

In general, pull requests are triaged within a few days of creation and are prioritized based on community reactions. Please view our Prioritization Guide for full details of the process.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/add-a-new-service/index.html b/add-a-new-service/index.html new file mode 100644 index 000000000000..432bf88f0417 --- /dev/null +++ b/add-a-new-service/index.html @@ -0,0 +1,1018 @@ + + + + + + + + + + + + + + + + + + + + + + + + Service - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Adding a New AWS Service#

+

AWS frequently launches new services, and Terraform support is frequently desired by the community shortly after launch. Depending on the API surface area of the new service, this could be a major undertaking. The following steps should be followed to prepare for adding the resources that allow for Terraform management of that service.

+

Perform Service Design#

+

Before adding a new service to the provider its a good idea to familiarize yourself with the primary workflows practitioners are likely to want to accomplish with the provider to ensure the provider design can solve for this. Its not always necessary to cover 100% of the AWS service offering to unblock most workflows.

+

You should have an idea of what resources and data sources should be added, their dependencies and relative importance in relation to the workflow. This should give you an idea of the order in which resources to be added. It's important to note that generally, we like to review and merge resources in isolation, and avoid combining multiple new resources in one Pull Request.

+

Using the AWS API documentation as a reference, identify the various API's which correspond to the CRUD operations which consist of the management surface for that resource. These will be the set of API's called from the new resource. The API's model attributes will correspond to your resource schema.

+

From there begin to map out the list of resources you would like to implement, and note your plan on the GitHub issue relating to the service (or create one if one does not exist) for the community and maintainers to feedback.

+

Add a Service Client#

+

Before new resources are submitted, please raise a separate pull request containing just the new AWS SDK for Go service client.

+

To add an AWS SDK for Go service client:

+
    +
  1. +

    Check the file names/names_data.csv for the service. + If it is already there, you are ready to implement the first resource or data source.

    +
  2. +
  3. +

    Otherwise, determine the service identifier using the rule described in the Naming Guide.

    +
  4. +
  5. +

    In names/names_data.csv, add a new line with all the requested information for the service following the guidance in the names README. + Be very careful when adding or changing data in names_data.csv! + The Provider and generators depend on the file being correct. + We strongly recommend using an editor with CSV support.

    +
  6. +
  7. +

    Run the following then submit the pull request:

    +
  8. +
+
make gen
+make test
+go mod tidy
+
+

Once the service client has been added, implement the first resource or data source in a separate PR.

+

Adding a Custom Service Client#

+

If an AWS service must be created in a non-standard way, for example the service API's endpoint must be accessed via a single AWS Region, then:

+
    +
  1. +

    Add an x in the SkipClientGenerate column for the service in names/names_data.csv

    +
  2. +
  3. +

    Run make gen

    +
  4. +
  5. +

    Add a file internal/<service>/service_package.go that contains an API client factory function, for example:

    +
  6. +
+
package globalaccelerator
+
+import (
+    "context"
+
+    aws_sdkv1 "github.com/aws/aws-sdk-go/aws"
+    endpoints_sdkv1 "github.com/aws/aws-sdk-go/aws/endpoints"
+    session_sdkv1 "github.com/aws/aws-sdk-go/aws/session"
+    globalaccelerator_sdkv1 "github.com/aws/aws-sdk-go/service/globalaccelerator"
+)
+
+// NewConn returns a new AWS SDK for Go v1 client for this service package's AWS API.
+func (p *servicePackage) NewConn(ctx context.Context) (*globalaccelerator_sdkv1.GlobalAccelerator, error) {
+    sess := p.config["session"].(*session_sdkv1.Session)
+    config := &aws_sdkv1.Config{Endpoint: aws_sdkv1.String(p.config["endpoint"].(string))}
+
+    // Force "global" services to correct Regions.
+    if p.config["partition"].(string) == endpoints_sdkv1.AwsPartitionID {
+        config.Region = aws_sdkv1.String(endpoints_sdkv1.UsWest2RegionID)
+    }
+
+    return globalaccelerator_sdkv1.New(sess.Copy(config)), nil
+}
+
+

Customizing a new Service Client#

+

If an AWS service must be customized after creation, for example retry handling must be changed, then:

+
    +
  1. Add a file internal/<service>/service_package.go that contains an API client customization function, for example:
  2. +
+
package chime
+
+import (
+    "context"
+
+    aws_sdkv1 "github.com/aws/aws-sdk-go/aws"
+    request_sdkv1 "github.com/aws/aws-sdk-go/aws/request"
+    chime_sdkv1 "github.com/aws/aws-sdk-go/service/chime"
+    "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
+)
+
+// CustomizeConn customizes a new AWS SDK for Go v1 client for this service package's AWS API.
+func (p *servicePackage) CustomizeConn(ctx context.Context, conn *chime_sdkv1.Chime) (*chime_sdkv1.Chime, error) {
+    conn.Handlers.Retry.PushBack(func(r *request_sdkv1.Request) {
+        // When calling CreateVoiceConnector across multiple resources,
+        // the API can randomly return a BadRequestException without explanation
+        if r.Operation.Name == "CreateVoiceConnector" {
+            if tfawserr.ErrMessageContains(r.Error, chime_sdkv1.ErrCodeBadRequestException, "Service received a bad request") {
+                r.Retryable = aws_sdkv1.Bool(true)
+            }
+        }
+    })
+
+    return conn, nil
+}
+
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/add-import-support/index.html b/add-import-support/index.html new file mode 100644 index 000000000000..36ca3fa01957 --- /dev/null +++ b/add-import-support/index.html @@ -0,0 +1,837 @@ + + + + + + + + + + + + + + + + + + + + + + + + Import Support - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Adding Resource Import Support#

+

Adding import support for Terraform resources will allow existing infrastructure to be managed within Terraform. This type of enhancement generally requires a small to moderate amount of code changes.

+

Comprehensive code examples and information about resource import support can be found in the Terraform Plugin SDK v2 documentation.

+
    +
  • Resource Code Implementation: In the resource code (e.g., internal/service/{service}/{thing}.go), implementation of Importer State function. When possible, prefer using schema.ImportStatePassthroughContext as the Importer State function
  • +
  • Resource Acceptance Testing Implementation: In the resource acceptance testing (e.g., internal/service/{service}/{thing}_test.go), implementation of TestSteps with ImportState: true
  • +
  • Resource Documentation Implementation: In the resource documentation (e.g., website/docs/r/service_thing.html.markdown), addition of Import documentation section at the bottom of the page
  • +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/adding-a-tag-resource/index.html b/adding-a-tag-resource/index.html new file mode 100644 index 000000000000..adfb4eedbe65 --- /dev/null +++ b/adding-a-tag-resource/index.html @@ -0,0 +1,1004 @@ + + + + + + + + + + + + + + + + + + + + + + + + Tag Resource - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Adding a New Tag Resource#

+

Adding a tag resource, similar to the aws_ecs_tag resource, has its own implementation procedure since the resource code and initial acceptance testing functions are automatically generated. The rest of the resource acceptance testing and resource documentation must still be manually created.

+
    +
  • In internal/generate: Ensure the service is supported by all generators. Run make gen after any modifications.
  • +
  • In internal/service/{service}/generate.go: Add the new //go:generate call with the correct generator directives. Run make gen after any modifications.
  • +
  • In internal/provider/provider.go: Add the new resource.
  • +
  • Run make test and ensure there are no failures.
  • +
  • Create internal/service/{service}/tag_gen_test.go with initial acceptance testing similar to the following (where the parent resource is simple to provision):
  • +
+
import (
+    "fmt"
+    "testing"
+
+    "github.com/aws/aws-sdk-go/service/{Service}"
+    "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+    "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAcc{Service}Tag_basic(t *testing.T) {
+    ctx := acctest.Context(t)
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+    resourceName := "aws_{service}_tag.test"
+
+    resource.ParallelTest(t, resource.TestCase{
+        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+        ErrorCheck:               acctest.ErrorCheck(t, {Service}.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+        CheckDestroy:             testAccCheck{Service}TagDestroy(ctx),
+        Steps: []resource.TestStep{
+            {
+                Config: testAcc{Service}TagConfig(rName, "key1", "value1"),
+                Check: resource.ComposeTestCheckFunc(
+                    testAccCheck{Service}TagExists(ctx, resourceName),
+                    resource.TestCheckResourceAttr(resourceName, "key", "key1"),
+                    resource.TestCheckResourceAttr(resourceName, "value", "value1"),
+                ),
+            },
+            {
+                ResourceName:      resourceName,
+                ImportState:       true,
+                ImportStateVerify: true,
+            },
+        },
+    })
+}
+
+func TestAcc{Service}Tag_disappears(t *testing.T) {
+    ctx := acctest.Context(t)
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+    resourceName := "aws_{service}_tag.test"
+
+    resource.ParallelTest(t, resource.TestCase{
+        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+        ErrorCheck:               acctest.ErrorCheck(t, {Service}.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+        CheckDestroy:             testAccCheck{Service}TagDestroy(ctx),
+        Steps: []resource.TestStep{
+            {
+                Config: testAcc{Service}TagConfig(rName, "key1", "value1"),
+                Check: resource.ComposeTestCheckFunc(
+                    testAccCheck{Service}TagExists(ctx, resourceName),
+                    acctest.CheckResourceDisappears(ctx, acctest.Provider, resourceAws{Service}Tag(), resourceName),
+                ),
+                ExpectNonEmptyPlan: true,
+            },
+        },
+    })
+}
+
+func TestAcc{Service}Tag_Value(t *testing.T) {
+    ctx := acctest.Context(t)
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+    resourceName := "aws_{service}_tag.test"
+
+    resource.ParallelTest(t, resource.TestCase{
+        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+        ErrorCheck:               acctest.ErrorCheck(t, {Service}.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+        CheckDestroy:             testAccCheck{Service}TagDestroy(ctx),
+        Steps: []resource.TestStep{
+            {
+                Config: testAcc{Service}TagConfig(rName, "key1", "value1"),
+                Check: resource.ComposeTestCheckFunc(
+                    testAccCheck{Service}TagExists(ctx, resourceName),
+                    resource.TestCheckResourceAttr(resourceName, "key", "key1"),
+                    resource.TestCheckResourceAttr(resourceName, "value", "value1"),
+                ),
+            },
+            {
+                ResourceName:      resourceName,
+                ImportState:       true,
+                ImportStateVerify: true,
+            },
+            {
+                Config: testAcc{Service}TagConfig(rName, "key1", "value1updated"),
+                Check: resource.ComposeTestCheckFunc(
+                    testAccCheck{Service}TagExists(ctx, resourceName),
+                    resource.TestCheckResourceAttr(resourceName, "key", "key1"),
+                    resource.TestCheckResourceAttr(resourceName, "value", "value1updated"),
+                ),
+            },
+        },
+    })
+}
+
+func testAcc{Service}TagConfig(rName string, key string, value string) string {
+    return fmt.Sprintf(`
+resource "aws_{service}_{thing}" "test" {
+  name = %[1]q
+
+  lifecycle {
+    ignore_changes = [tags]
+  }
+}
+
+resource "aws_{service}_tag" "test" {
+  resource_arn = aws_{service}_{thing}.test.arn
+  key          = %[2]q
+  value        = %[3]q
+}
+`, rName, key, value)
+}
+
+
    +
  • Run make testacc TESTS=TestAcc{Service}Tags_ PKG={Service} and ensure there are no failures.
  • +
  • Create website/docs/r/{service}_tag.html.markdown with initial documentation similar to the following:
  • +
+
---
+subcategory: "{SERVICE}"
+layout: "aws"
+page_title: "AWS: aws_{service}_tag"
+description: |-
+  Manages an individual {SERVICE} resource tag
+---
+
+# Resource: aws_{service}_tag
+
+Manages an individual {SERVICE} resource tag. This resource should only be used in cases where {SERVICE} resources are created outside Terraform (e.g., {SERVICE} {THING}s implicitly created by {OTHER SERVICE THING}).
+
+~> **NOTE:** This tagging resource should not be combined with the Terraform resource for managing the parent resource. For example, using `aws_{service}_{thing}` and `aws_{service}_tag` to manage tags of the same {SERVICE} {THING} will cause a perpetual difference where the `aws_{service}_{thing}` resource will try to remove the tag being added by the `aws_{service}_tag` resource.
+
+~> **NOTE:** This tagging resource does not use the [provider `ignore_tags` configuration](/docs/providers/aws/index.html#ignore_tags).
+
+## Example Usage
+
+```terraform
+resource "aws_{service}_tag" "example" {
+  resource_arn = "..."
+  key          = "Name"
+  value        = "Hello World"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `resource_arn` - (Required) ARN of the {SERVICE} resource to tag.
+* `key` - (Required) Tag name.
+* `value` - (Required) Tag value.
+
+## Attributes Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `id` - {SERVICE} resource identifier and key, separated by a comma (`,`)
+
+## Import
+
+`aws_{service}_tag` can be imported by using the {SERVICE} resource identifier and key, separated by a comma (`,`), e.g.
+
+```console
+$ terraform import aws_{service}_tag.example arn:aws:{service}:us-east-1:123456789012:{thing}/example,Name
+```
+
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/javascripts/bundle.51d95adb.min.js b/assets/javascripts/bundle.51d95adb.min.js new file mode 100644 index 000000000000..b20ec6835bb7 --- /dev/null +++ b/assets/javascripts/bundle.51d95adb.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Hi=Object.create;var xr=Object.defineProperty;var Pi=Object.getOwnPropertyDescriptor;var $i=Object.getOwnPropertyNames,kt=Object.getOwnPropertySymbols,Ii=Object.getPrototypeOf,Er=Object.prototype.hasOwnProperty,an=Object.prototype.propertyIsEnumerable;var on=(e,t,r)=>t in e?xr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))Er.call(t,r)&&on(e,r,t[r]);if(kt)for(var r of kt(t))an.call(t,r)&&on(e,r,t[r]);return e};var sn=(e,t)=>{var r={};for(var n in e)Er.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&kt)for(var n of kt(e))t.indexOf(n)<0&&an.call(e,n)&&(r[n]=e[n]);return r};var Ht=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Fi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of $i(t))!Er.call(e,o)&&o!==r&&xr(e,o,{get:()=>t[o],enumerable:!(n=Pi(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Hi(Ii(e)):{},Fi(t||!e||!e.__esModule?xr(r,"default",{value:e,enumerable:!0}):r,e));var fn=Ht((wr,cn)=>{(function(e,t){typeof wr=="object"&&typeof cn!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(wr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(T){return!!(T&&T!==document&&T.nodeName!=="HTML"&&T.nodeName!=="BODY"&&"classList"in T&&"contains"in T.classList)}function f(T){var Ke=T.type,We=T.tagName;return!!(We==="INPUT"&&a[Ke]&&!T.readOnly||We==="TEXTAREA"&&!T.readOnly||T.isContentEditable)}function c(T){T.classList.contains("focus-visible")||(T.classList.add("focus-visible"),T.setAttribute("data-focus-visible-added",""))}function u(T){T.hasAttribute("data-focus-visible-added")&&(T.classList.remove("focus-visible"),T.removeAttribute("data-focus-visible-added"))}function p(T){T.metaKey||T.altKey||T.ctrlKey||(s(r.activeElement)&&c(r.activeElement),n=!0)}function m(T){n=!1}function d(T){s(T.target)&&(n||f(T.target))&&c(T.target)}function h(T){s(T.target)&&(T.target.classList.contains("focus-visible")||T.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(T.target))}function v(T){document.visibilityState==="hidden"&&(o&&(n=!0),B())}function B(){document.addEventListener("mousemove",z),document.addEventListener("mousedown",z),document.addEventListener("mouseup",z),document.addEventListener("pointermove",z),document.addEventListener("pointerdown",z),document.addEventListener("pointerup",z),document.addEventListener("touchmove",z),document.addEventListener("touchstart",z),document.addEventListener("touchend",z)}function re(){document.removeEventListener("mousemove",z),document.removeEventListener("mousedown",z),document.removeEventListener("mouseup",z),document.removeEventListener("pointermove",z),document.removeEventListener("pointerdown",z),document.removeEventListener("pointerup",z),document.removeEventListener("touchmove",z),document.removeEventListener("touchstart",z),document.removeEventListener("touchend",z)}function z(T){T.target.nodeName&&T.target.nodeName.toLowerCase()==="html"||(n=!1,re())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),B(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var un=Ht(Sr=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},a=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(re,z){d.append(z,re)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(T){throw new Error("URL unable to set base "+c+" due to "+T)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,B=!0,re=this;["append","delete","set"].forEach(function(T){var Ke=h[T];h[T]=function(){Ke.apply(h,arguments),v&&(B=!1,re.search=h.toString(),B=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var z=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==z&&(z=this.search,B&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},a=i.prototype,s=function(f){Object.defineProperty(a,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){s(f)}),Object.defineProperty(a,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(a,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Sr)});var Qr=Ht((Lt,Kr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Lt=="object"&&typeof Kr=="object"?Kr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Lt=="object"?Lt.ClipboardJS=r():t.ClipboardJS=r()})(Lt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return ki}});var a=i(279),s=i.n(a),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(O){return!1}}var d=function(O){var w=p()(O);return m("cut"),w},h=d;function v(j){var O=document.documentElement.getAttribute("dir")==="rtl",w=document.createElement("textarea");w.style.fontSize="12pt",w.style.border="0",w.style.padding="0",w.style.margin="0",w.style.position="absolute",w.style[O?"right":"left"]="-9999px";var k=window.pageYOffset||document.documentElement.scrollTop;return w.style.top="".concat(k,"px"),w.setAttribute("readonly",""),w.value=j,w}var B=function(O,w){var k=v(O);w.container.appendChild(k);var F=p()(k);return m("copy"),k.remove(),F},re=function(O){var w=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},k="";return typeof O=="string"?k=B(O,w):O instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(O==null?void 0:O.type)?k=B(O.value,w):(k=p()(O),m("copy")),k},z=re;function T(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?T=function(w){return typeof w}:T=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},T(j)}var Ke=function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},w=O.action,k=w===void 0?"copy":w,F=O.container,q=O.target,Le=O.text;if(k!=="copy"&&k!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&T(q)==="object"&&q.nodeType===1){if(k==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(k==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Le)return z(Le,{container:F});if(q)return k==="cut"?h(q):z(q,{container:F})},We=Ke;function Ie(j){return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Ie=function(w){return typeof w}:Ie=function(w){return w&&typeof Symbol=="function"&&w.constructor===Symbol&&w!==Symbol.prototype?"symbol":typeof w},Ie(j)}function Ti(j,O){if(!(j instanceof O))throw new TypeError("Cannot call a class as a function")}function nn(j,O){for(var w=0;w0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof F.action=="function"?F.action:this.defaultAction,this.target=typeof F.target=="function"?F.target:this.defaultTarget,this.text=typeof F.text=="function"?F.text:this.defaultText,this.container=Ie(F.container)==="object"?F.container:document.body}},{key:"listenClick",value:function(F){var q=this;this.listener=c()(F,"click",function(Le){return q.onClick(Le)})}},{key:"onClick",value:function(F){var q=F.delegateTarget||F.currentTarget,Le=this.action(q)||"copy",Rt=We({action:Le,container:this.container,target:this.target(q),text:this.text(q)});this.emit(Rt?"success":"error",{action:Le,text:Rt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(F){return yr("action",F)}},{key:"defaultTarget",value:function(F){var q=yr("target",F);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(F){return yr("text",F)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(F){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return z(F,q)}},{key:"cut",value:function(F){return h(F)}},{key:"isSupported",value:function(){var F=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof F=="string"?[F]:F,Le=!!document.queryCommandSupported;return q.forEach(function(Rt){Le=Le&&!!document.queryCommandSupported(Rt)}),Le}}]),w}(s()),ki=Ri},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,f){for(;s&&s.nodeType!==o;){if(typeof s.matches=="function"&&s.matches(f))return s;s=s.parentNode}}n.exports=a},438:function(n,o,i){var a=i(828);function s(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?s.apply(null,arguments):typeof m=="function"?s.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return s(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=a(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(n,o,i){var a=i(879),s=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(h))throw new TypeError("Third argument must be a Function");if(a.node(m))return c(m,d,h);if(a.nodeList(m))return u(m,d,h);if(a.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return s(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),a=f.toString()}return a}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,a,s){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var f=this;function c(){f.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=s.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var is=/["'&<>]/;Jo.exports=as;function as(e){var t=""+e,r=is.exec(t);if(!r)return t;var n,o="",i=0,a=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],a;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(s){a={error:s}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(a)throw a.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||s(m,d)})})}function s(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof Xe?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){s("next",m)}function u(m){s("throw",m)}function p(m,d){m(d),i.shift(),i.length&&s(i[0][0],i[0][1])}}function mn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof xe=="function"?xe(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(a){return new Promise(function(s,f){a=e[i](a),o(s,f,a.done,a.value)})}}function o(i,a,s,f){Promise.resolve(f).then(function(c){i({value:c,done:s})},a)}}function A(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var $t=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function De(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Fe=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=xe(a),f=s.next();!f.done;f=s.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var u=this.initialTeardown;if(A(u))try{u()}catch(v){i=v instanceof $t?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=xe(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{dn(h)}catch(v){i=i!=null?i:[],v instanceof $t?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new $t(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)dn(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&De(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&De(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Or=Fe.EMPTY;function It(e){return e instanceof Fe||e&&"closed"in e&&A(e.remove)&&A(e.add)&&A(e.unsubscribe)}function dn(e){A(e)?e():e.unsubscribe()}var Ae={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,a=o.isStopped,s=o.observers;return i||a?Or:(this.currentObservers=null,s.push(r),new Fe(function(){n.currentObservers=null,De(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,a=n.isStopped;o?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new U;return r.source=this,r},t.create=function(r,n){return new wn(r,n)},t}(U);var wn=function(e){ne(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Or},t}(E);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ne(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,a=n._infiniteTimeWindow,s=n._timestampProvider,f=n._windowTime;o||(i.push(r),!a&&i.push(s.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,a=o._buffer,s=a.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var a=r.actions;n!=null&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Ut);var On=function(e){ne(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Wt);var we=new On(Tn);var R=new U(function(e){return e.complete()});function Dt(e){return e&&A(e.schedule)}function kr(e){return e[e.length-1]}function Qe(e){return A(kr(e))?e.pop():void 0}function Se(e){return Dt(kr(e))?e.pop():void 0}function Vt(e,t){return typeof kr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function zt(e){return A(e==null?void 0:e.then)}function Nt(e){return A(e[ft])}function qt(e){return Symbol.asyncIterator&&A(e==null?void 0:e[Symbol.asyncIterator])}function Kt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Ki(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Qt=Ki();function Yt(e){return A(e==null?void 0:e[Qt])}function Gt(e){return ln(this,arguments,function(){var r,n,o,i;return Pt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,Xe(r.read())];case 3:return n=a.sent(),o=n.value,i=n.done,i?[4,Xe(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,Xe(o)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Bt(e){return A(e==null?void 0:e.getReader)}function $(e){if(e instanceof U)return e;if(e!=null){if(Nt(e))return Qi(e);if(pt(e))return Yi(e);if(zt(e))return Gi(e);if(qt(e))return _n(e);if(Yt(e))return Bi(e);if(Bt(e))return Ji(e)}throw Kt(e)}function Qi(e){return new U(function(t){var r=e[ft]();if(A(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function Yi(e){return new U(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?_(function(o,i){return e(o,i,n)}):me,Oe(1),r?He(t):zn(function(){return new Xt}))}}function Nn(){for(var e=[],t=0;t=2,!0))}function fe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new E}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,f=s===void 0?!0:s;return function(c){var u,p,m,d=0,h=!1,v=!1,B=function(){p==null||p.unsubscribe(),p=void 0},re=function(){B(),u=m=void 0,h=v=!1},z=function(){var T=u;re(),T==null||T.unsubscribe()};return g(function(T,Ke){d++,!v&&!h&&B();var We=m=m!=null?m:r();Ke.add(function(){d--,d===0&&!v&&!h&&(p=jr(z,f))}),We.subscribe(Ke),!u&&d>0&&(u=new et({next:function(Ie){return We.next(Ie)},error:function(Ie){v=!0,B(),p=jr(re,o,Ie),We.error(Ie)},complete:function(){h=!0,B(),p=jr(re,a),We.complete()}}),$(T).subscribe(u))})(c)}}function jr(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function V(e,t=document){let r=se(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function se(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),N(e===_e()),Y())}function Be(e){return{x:e.offsetLeft,y:e.offsetTop}}function Yn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,we),l(()=>Be(e)),N(Be(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,we),l(()=>rr(e)),N(rr(e)))}var Bn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!zr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),xa?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!zr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ya.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Jn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Zn=typeof WeakMap!="undefined"?new WeakMap:new Bn,eo=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=Ea.getInstance(),n=new Ra(t,r,this);Zn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){eo.prototype[e]=function(){var t;return(t=Zn.get(this))[e].apply(t,arguments)}});var ka=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:eo}(),to=ka;var ro=new E,Ha=I(()=>H(new to(e=>{for(let t of e)ro.next(t)}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function de(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){return Ha.pipe(S(t=>t.observe(e)),x(t=>ro.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(()=>de(e)))),N(de(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var no=new E,Pa=I(()=>H(new IntersectionObserver(e=>{for(let t of e)no.next(t)},{threshold:0}))).pipe(x(e=>L(Te,H(e)).pipe(C(()=>e.disconnect()))),J(1));function sr(e){return Pa.pipe(S(t=>t.observe(e)),x(t=>no.pipe(_(({target:r})=>r===e),C(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function oo(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=de(e),o=bt(e);return r>=o.height-n.height-t}),Y())}var cr={drawer:V("[data-md-toggle=drawer]"),search:V("[data-md-toggle=search]")};function io(e){return cr[e].checked}function qe(e,t){cr[e].checked!==t&&cr[e].click()}function je(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),N(t.checked))}function $a(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ia(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(N(!1))}function ao(){let e=b(window,"keydown").pipe(_(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:io("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),_(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!$a(n,r)}return!0}),fe());return Ia().pipe(x(t=>t?R:e))}function Me(){return new URL(location.href)}function ot(e){location.href=e.href}function so(){return new E}function co(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)co(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)co(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function fo(){return location.hash.substring(1)}function uo(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Fa(){return b(window,"hashchange").pipe(l(fo),N(fo()),_(e=>e.length>0),J(1))}function po(){return Fa().pipe(l(e=>se(`[id="${e}"]`)),_(e=>typeof e!="undefined"))}function Nr(e){let t=matchMedia(e);return Zt(r=>t.addListener(()=>r(t.matches))).pipe(N(t.matches))}function lo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(N(e.matches))}function qr(e,t){return e.pipe(x(r=>r?t():R))}function ur(e,t={credentials:"same-origin"}){return ve(fetch(`${e}`,t)).pipe(ce(()=>R),x(r=>r.status!==200?Tt(()=>new Error(r.statusText)):H(r)))}function Ue(e,t){return ur(e,t).pipe(x(r=>r.json()),J(1))}function mo(e,t){let r=new DOMParser;return ur(e,t).pipe(x(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),J(1))}function pr(e){let t=M("script",{src:e});return I(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(x(()=>Tt(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),C(()=>document.head.removeChild(t)),Oe(1))))}function ho(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function bo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(ho),N(ho()))}function vo(){return{width:innerWidth,height:innerHeight}}function go(){return b(window,"resize",{passive:!0}).pipe(l(vo),N(vo()))}function yo(){return Q([bo(),go()]).pipe(l(([e,t])=>({offset:e,size:t})),J(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(X("size")),o=Q([n,r]).pipe(l(()=>Be(e)));return Q([r,t,o]).pipe(l(([{height:i},{offset:a,size:s},{x:f,y:c}])=>({offset:{x:a.x-f,y:a.y-c+i},size:s})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(a=>{let s=document.createElement("script");s.src=i,s.onload=a,document.body.appendChild(s)})),Promise.resolve())}var r=class{constructor(n){this.url=n,this.onerror=null,this.onmessage=null,this.onmessageerror=null,this.m=a=>{a.source===this.w&&(a.stopImmediatePropagation(),this.dispatchEvent(new MessageEvent("message",{data:a.data})),this.onmessage&&this.onmessage(a))},this.e=(a,s,f,c,u)=>{if(s===this.url.toString()){let p=new ErrorEvent("error",{message:a,filename:s,lineno:f,colno:c,error:u});this.dispatchEvent(p),this.onerror&&this.onerror(p)}};let o=new EventTarget;this.addEventListener=o.addEventListener.bind(o),this.removeEventListener=o.removeEventListener.bind(o),this.dispatchEvent=o.dispatchEvent.bind(o);let i=document.createElement("iframe");i.width=i.height=i.frameBorder="0",document.body.appendChild(this.iframe=i),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

aws-go-sdk-base#

+

https://github.com/hashicorp/aws-sdk-go-base

+

This is a base library used by the AWS Provider, AWSCC Provider and the Terraform S3 Backend to allow them to handle authentication and other non-service level AWS interactions consistently.

+

Typically this changes infrequently and changes are normally performed by HashiCorp maintainers. It should not be necessary to change this library for the majority of provider contributions.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/aws-go-sdk-versions/index.html b/aws-go-sdk-versions/index.html new file mode 100644 index 000000000000..2a155643c269 --- /dev/null +++ b/aws-go-sdk-versions/index.html @@ -0,0 +1,936 @@ + + + + + + + + + + + + + + + + + + + + + + + + AWS SDK for Go Versions - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

AWS Go SDK Versions#

+

The Terraform AWS Provider relies on the AWS SDK for Go which is maintained and published by AWS to allow us to safely and securely interact with AWS API's in a consistent fashion. +There are two versions of this API, both of which are considered Generally Available and fully supported by AWS at present.

+ +

While the vast majority of the provider is based on the AWS SDK for Go v1, +the provider also allows the use of the AWS SDK for Go v2.

+

Which SDK Version should I use?#

+

Each Terraform provider implementation for an AWS service relies on a service client which in turn is constructed based on a specific SDK version. +At present, we are slowly increasing our footprint on SDK v2, but are not actively migrating existing code to use v2. +The choice of SDK will be as follows:

+

For new services, you should use AWS SDK for Go v2. +AWS has a migration guide that details the differences between the versions of the SDK.

+

For existing services, use the version of the SDK that service currently uses. +You can determine this by looking at the import section in the service's Go files.

+

What does the SDK handle?#

+

The AWS SDKs handle calling the various web service interfaces for AWS services. +In addition to encoding and decoding the Go structures in the correct JSON or XML payloads, +the SDKs handle authentication, request logging, and retrying requests.

+

The various language SDKs and the AWS CLI share a consistent configuration interface, +using environment variables and shared configuration and credentials files.

+

The AWS SDKs also automatically retry several common failure cases, such as network errors.

+

How do the SDK versions differ?#

+

The AWS SDK for Go v1.0.0 was released in late 2015, when the current version of Go was v1.5. +The Go language has evolved significantly since then. +Many currently-recommended practices were not possible at that time, +including the use of the context package, introduced in Go v1.7, +and error wrapping, introduced in Go v1.13.

+

The AWS SDK for Go v2 uses a modern Go style +and has also been modularized, so that individual services are packaged and imported separately.

+

For details on the specific changes to the AWS SDK for Go v2, +see Migrating to the AWS SDK for Go v2, +especially the Service Clients section.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/bugs-and-enhancements/index.html b/bugs-and-enhancements/index.html new file mode 100644 index 000000000000..ee1ac12aad95 --- /dev/null +++ b/bugs-and-enhancements/index.html @@ -0,0 +1,855 @@ + + + + + + + + + + + + + + + + + + + + + + + + Bugs and Enhancements - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Making Small Changes to Existing Resources#

+

Most contributions to the provider will take the form of small additions or bug-fixes on existing resources/data sources. In this case the existing resource will give you the best guidance on how the change should be structured, but we require the following to allow the change to be merged:

+
    +
  • Acceptance test coverage of new behavior: Existing resources each + have a set of acceptance tests covering their functionality. + These tests should exercise all the behavior of the resource. Whether you are + adding something or fixing a bug, the idea is to have an acceptance test that + fails if your code were to be removed. Sometimes it is sufficient to + "enhance" an existing test by adding an assertion or tweaking the config + that is used, but it's often better to add a new test. You can copy/paste an + existing test and follow the conventions you see there, modifying the test + to exercise the behavior of your code.
  • +
  • Documentation updates: If your code makes any changes that need to + be documented, you should include those documentation changes + in the same PR. This includes things like new resource attributes or changes in default values.
  • +
  • Well-formed Code: Do your best to follow existing conventions you + see in the codebase, and ensure your code is formatted with go fmt. + The PR reviewers can help out on this front, and may provide comments with + suggestions on how to improve the code.
  • +
  • Dependency updates: Create a separate PR if you are updating dependencies. + This is to avoid conflicts as version updates tend to be fast- + moving targets. We will plan to merge the PR with this change first.
  • +
  • Changelog entry: Assuming the code change affects Terraform operators, + the relevant PR ought to include a user-facing changelog entry + describing the new behavior.
  • +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/changelog-process/index.html b/changelog-process/index.html new file mode 100644 index 000000000000..742b93af181e --- /dev/null +++ b/changelog-process/index.html @@ -0,0 +1,1151 @@ + + + + + + + + + + + + + + + + + + + + + + + + Changelog Process - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Changelog Process#

+

HashiCorp’s open-source projects have always maintained user-friendly, readable CHANGELOG.md that allow users to tell at a glance whether a release should have any effect on them, and to gauge the risk of an upgrade.

+

We use the go-changelog to generate and update the changelog from files created in the .changelog/ directory. It is important that when you raise your Pull Request, there is a changelog entry which describes the changes your contribution makes. Not all changes require an entry in the changelog, guidance follows on what changes do.

+

Changelog format#

+

The changelog format requires an entry in the following format, where HEADER corresponds to the changelog category, and the entry is the changelog entry itself. The entry should be included in a file in the .changelog directory with the naming convention {PR-NUMBER}.txt. For example, to create a changelog entry for pull request 1234, there should be a file named .changelog/1234.txt.

+
```release-note:{HEADER}
+{ENTRY}
+```
+
+

If a pull request should contain multiple changelog entries, then multiple blocks can be added to the same changelog file. For example:

+
```release-note:note
+resource/aws_example_thing: The `broken` attribute has been deprecated. All configurations using `broken` should be updated to use the new `not_broken` attribute instead.
+```
+
+```release-note:enhancement
+resource/aws_example_thing: Add `not_broken` attribute
+```
+
+

Pull request types to CHANGELOG#

+

The CHANGELOG is intended to show operator-impacting changes to the codebase for a particular version. If every change or commit to the code resulted in an entry, the CHANGELOG would become less useful for operators. The lists below are general guidelines and examples for when a decision needs to be made to decide whether a change should have an entry.

+

Changes that should have a CHANGELOG entry#

+

New resource#

+

A new resource entry should only contain the name of the resource, and use the release-note:new-resource header.

+
```release-note:new-resource
+aws_secretsmanager_secret_policy
+```
+
+

New data source#

+

A new data source entry should only contain the name of the data source, and use the release-note:new-data-source header.

+
```release-note:new-data-source
+aws_workspaces_workspace
+```
+
+

New full-length documentation guides (e.g., EKS Getting Started Guide, IAM Policy Documents with Terraform)#

+

A new full length documentation entry gives the title of the documentation added, using the release-note:new-guide header.

+
```release-note:new-guide
+Custom Service Endpoint Configuration
+```
+
+

Resource and provider bug fixes#

+

A new bug entry should use the release-note:bug header and have a prefix indicating the resource or data source it corresponds to, a colon, then followed by a brief summary. Use a provider prefix for provider level fixes.

+
```release-note:bug
+resource/aws_glue_classifier: Fix quote_symbol being optional
+```
+
+

Resource and provider enhancements#

+

A new enhancement entry should use the release-note:enhancement header and have a prefix indicating the resource or data source it corresponds to, a colon, then followed by a brief summary. Use a provider prefix for provider level enhancements.

+
```release-note:enhancement
+resource/aws_eip: Add network_border_group argument
+```
+
+

Deprecations#

+

A deprecation entry should use the release-note:note header and have a prefix indicating the resource or data source it corresponds to, a colon, then followed by a brief summary. Use a provider prefix for provider level changes.

+
```release-note:note
+resource/aws_dx_gateway_association: The vpn_gateway_id attribute is being deprecated in favor of the new associated_gateway_id attribute to support transit gateway associations
+```
+
+

Breaking changes and removals#

+

A breaking-change entry should use the release-note:breaking-change header and have a prefix indicating the resource or data source it corresponds to, a colon, then followed by a brief summary. Use a provider prefix for provider level changes.

+
```release-note:breaking-change
+resource/aws_lambda_alias: Resource import no longer converts Lambda Function name to ARN
+```
+
+

Region validation support#

+
```release-note:note
+provider: Region validation now automatically supports the new `XX-XXXXX-#` (Location) region. For AWS operations to work in the new region, the region must be explicitly enabled as outlined in the [AWS Documentation](https://docs.aws.amazon.com/general/latest/gr/rande-manage.html#rande-manage-enable). When the region is not enabled, the Terraform AWS Provider will return errors during credential validation (e.g., `error validating provider credentials: error calling sts:GetCallerIdentity: InvalidClientTokenId: The security token included in the request is invalid`) or AWS operations will throw their own errors (e.g., `data.aws_availability_zones.available: Error fetching Availability Zones: AuthFailure: AWS was not able to validate the provided access credentials`). [GH-####]
+```
+
+```release-note:enhancement
+* provider: Support automatic region validation for `XX-XXXXX-#` [GH-####]
+```
+
+

Changes that may have a CHANGELOG entry#

+

Dependency updates: If the update contains relevant bug fixes or enhancements that affect operators, those should be called out. +Any changes which do not fit into the above categories but warrant highlighting. +Use resource/data source/provider prefixes where appropriate.

+
```release-note:note
+resource/aws_lambda_alias: Resource import no longer converts Lambda Function name to ARN
+```
+
+

Changes that should not have a CHANGELOG entry#

+
    +
  • Resource and provider documentation updates
  • +
  • Testing updates
  • +
  • Code refactoring
  • +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/core-services/index.html b/core-services/index.html new file mode 100644 index 000000000000..992036ba414b --- /dev/null +++ b/core-services/index.html @@ -0,0 +1,850 @@ + + + + + + + + + + + + + + + + + + + + Terraform AWS Provider Core Services - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Terraform AWS Provider Core Services#

+

Core Services are AWS services we have identified as critical for a large majority of our users. Our goal is to continually increase the depth of coverage for these services. We will work to prioritize features and enhancements to these services in each weekly release, even if they are not necessarily highlighted in our quarterly roadmap.

+

The core services we have identified are:

+
    +
  • +

    EC2

    +
  • +
  • +

    Lambda

    +
  • +
  • +

    EKS

    +
  • +
  • +

    ECS

    +
  • +
  • +

    VPC

    +
  • +
  • +

    S3

    +
  • +
  • +

    RDS

    +
  • +
  • +

    DynamoDB

    +
  • +
  • +

    IAM

    +
  • +
  • +

    Autoscaling (ASG)

    +
  • +
  • +

    ElastiCache

    +
  • +
+

We'll continue to evaluate the selected services as our user base grows and changes.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/data-handling-and-conversion/index.html b/data-handling-and-conversion/index.html new file mode 100644 index 000000000000..57fdf7daa359 --- /dev/null +++ b/data-handling-and-conversion/index.html @@ -0,0 +1,2273 @@ + + + + + + + + + + + + + + + + + + + + + + + + Data Handling and Conversion - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Data Handling and Conversion#

+

The Terraform AWS Provider codebase bridges the implementation of a Terraform Plugin and an AWS API client to support AWS operations and data types as Terraform Resources. Data handling and conversion is a large portion of resource implementation given the domain specific implementations of each side of the provider. The first where Terraform is a generic infrastructure as code tool with a generic data model and the other where the details are driven by AWS API data modeling concepts. This guide is intended to explain and show preferred Terraform AWS Provider code implementations required to successfully translate data between these two systems.

+

At the bottom of this documentation is a Glossary section, which may be a helpful reference while reading the other sections.

+

Data Conversions in Terraform Providers#

+

Before getting into highly specific documentation about the Terraform AWS Provider handling of data, it may be helpful to briefly highlight how Terraform Plugins (Terraform Providers in this case) interact with Terraform CLI and the Terraform State in general and where this documentation fits into the whole process.

+

There are two primary data flows that are typically handled by resources within a Terraform Provider. Data is either being converted from a planned new Terraform State into making a remote system request or a remote system response is being converted into a applied new Terraform State. The semantics of how the data of the planned new Terraform State is surfaced to the resource implementation is determined by where a resource is in its lifecycle and mainly handled by Terraform CLI. This concept can be explored further in the Terraform Resource Instance Change Lifecycle documentation, with the caveat that some additional behaviors occur within the Terraform Plugin SDK as well (if the Terraform Plugin uses that implementation detail).

+

As a generic walkthrough, the following data handling occurs when creating a Terraform Resource:

+
    +
  • An operator creates a Terraform configuration with a new resource defined and runs terraform apply
  • +
  • Terraform CLI merges an empty prior state for the resource, along with the given configuration state, to create a planned new state for the resource
  • +
  • Terraform CLI sends a Terraform Plugin Protocol request to create the new resource with its planned new state data
  • +
  • If the Terraform Plugin is using a higher level library, such as the Terraform Plugin SDK, that library receives the request and translates the Terraform Plugin Protocol data types into the expected library types
  • +
  • Terraform Plugin invokes the resource creation function with the planned new state data
      +
    • The planned new state data is converted into an remote system request (e.g., API creation request) that is invoked
    • +
    • The remote system response is received and the data is converted into an applied new state
    • +
    +
  • +
  • If the Terraform Plugin is using a higher level library, such as the Terraform Plugin SDK, that library translates the library types back into Terraform Plugin Protocol data types
  • +
  • Terraform Plugin responds to Terraform Plugin Protocol request with the new state data
  • +
  • Terraform CLI verifies and stores the new state
  • +
+

The highlighted lines are the focus of this documentation today. In the future however, the Terraform AWS Provider may replace certain functionality in the items mentioning the Terraform Plugin SDK above to workaround certain limitations of that particular library.

+

Implicit State Passthrough#

+

An important behavior to note with Terraform State handling is if the value of a particular root attribute or block is not refreshed during plan or apply operations, then the prior Terraform State is implicitly deep copied to the new Terraform State for that attribute or block.

+

Given a resource with a writeable root attribute named not_set_attr that never calls d.Set("not_set_attr", /* ... nil or value */), the following happens:

+
    +
  • If the Terraform configuration contains not_set_attr = "anything" on resource creation, the Terraform State contains not_set_attr equal to "anything" after apply.
  • +
  • If the Terraform configuration is updated to not_set_attr = "updated", the Terraform State contains not_set_attr equal to "updated" after apply.
  • +
  • If the attribute was meant to be associated with a remote system value, it will never update the Terraform State on plan or apply with the remote value. Effectively, it cannot perform drift detection with the remote value.
  • +
+

This however does not apply for nested attributes and blocks if the parent block is refreshed. Given a resource with a root block named parent, nested child attributes named set_attr and not_set_attr, and that calls d.Set("parent", /* ... only refreshes nested set_attr ... */), the Terraform State for the nested not_set_attr will not be copied.

+

There are valid use cases for passthrough attribute values such as these (see the Virtual Attributes section), however the behavior can be confusing or incorrect for operators if the drift detection is expected. Typically these types of drift detection issues can be discovered by implementing resource import testing with state verification.

+

Data Conversions in the Terraform AWS Provider#

+

To expand on the data handling that occurs specifically within the Terraform AWS Provider resource implementations, the above resource creation items become the below in practice given our current usage of the Terraform Plugin SDK:

+
    +
  • The Create/CreateWithoutTimeout function of a schema.Resource is invoked with *schema.ResourceData containing the planned new state data (conventionally named d) and an AWS API client (conventionally named meta).
      +
    • Note: Before reaching this point, the ResourceData was already translated from the Terraform Plugin Protocol data types by the Terraform Plugin SDK so values can be read by invoking d.Get() and d.GetOk() receiver methods with Attribute and Block names from the Schema of the schema.Resource.
    • +
    +
  • +
  • An AWS Go SDK operation input type (e.g., *ec2.CreateVpcInput) is initialized
  • +
  • For each necessary field to configure in the operation input type, the data is read from the ResourceData (e.g., d.Get(), d.GetOk()) and converted into the AWS Go SDK type for the field (e.g., *string)
  • +
  • The AWS Go SDK operation is invoked and the output type (e.g., *ec2.CreateVpcOutput) is initialized
  • +
  • For each necessary Attribute, Block, or resource identifier to be saved in the state, the data is read from the AWS Go SDK type for the field (*string), if necessary converted into a ResourceData compatible type, and saved into a mutated ResourceData (e.g., d.Set(), d.SetId())
  • +
  • Function is returned
  • +
+

Type Mapping#

+

To further understand the necessary data conversions used throughout the Terraform AWS Provider codebase between AWS Go SDK types and the Terraform Plugin SDK, the following table can be referenced for most scenarios:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AWS API ModelAWS Go SDKTerraform Plugin SDKTerraform Language/State
boolean*boolTypeBool (bool)bool
float*float64TypeFloat (float64)number
integer*int64TypeInt (int)number
list[]*TTypeList ([]interface{} of T)
TypeSet (*schema.Set of T)
list(any)
set(any)
mapmap[T1]*T2TypeMap (map[string]interface{})map(any)
string*stringTypeString (string)string
structurestructTypeList ([]interface{} of map[string]interface{}) with MaxItems: 1list(object(any))
timestamp*time.TimeTypeString (typically RFC3339 formatted)string
+ + +

You may notice there are type encoding differences the AWS Go SDK and Terraform Plugin SDK:

+
    +
  • AWS Go SDK types are all Go pointer types, while Terraform Plugin SDK types are not.
  • +
  • AWS Go SDK structures are the Go struct type, while there is no semantically equivalent Terraform Plugin SDK type. Instead they are represented as a slice of interfaces with an underlying map of interfaces.
  • +
  • AWS Go SDK types are all Go concrete types, while the Terraform Plugin SDK types for collections and maps are interfaces.
  • +
  • AWS Go SDK whole numeric type is always 64-bit, while the Terraform Plugin SDK type is implementation-specific.
  • +
+

Conceptually, the first and second items above the most problematic in the Terraform AWS Provider codebase. The first item because non-pointer types in Go cannot implement the concept of no value (nil). The Zero Value Mapping section will go into more details about the implications of this limitation. The second item because it can be confusing to always handle a structure ("object") type as a list.

+

There are efforts to replace the Terraform Plugin type system with one similar the underlying Terraform CLI type system. As these efforts materialize, this documentation will be updated.

+

Zero Value Mapping#

+

As mentioned in the Type Mapping section, there is a discrepancy with how the Terraform Plugin SDK represents values and the reality that a Terraform State may not configure an Attribute. These values will default to the matching underlying Go type "zero value" if not set:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Terraform Plugin SDKGo TypeZero Value
TypeBoolboolfalse
TypeFloatfloat640.0
TypeIntint0
TypeStringstring""
+

For Terraform resource logic this means that these special values must always be accounted for in implementation. The semantics of the API and its meaning of the zero value will determine whether:

+
    +
  • If it is not used/needed, then generally the zero value can safely be used to store an "unset" value and should be ignored when sending to the API.
  • +
  • If it is used/needed, whether:
      +
    • A value can always be set and it is safe to always send to the API. Generally, boolean values fall into this category.
    • +
    • A different default/sentinel value must be used as the "unset" value so it can either match the default of the API or be ignored when sending to the API.
    • +
    • A special type implementation is required within the schema to workaround the limitation.
    • +
    +
  • +
+

The maintainers can provide guidance on appropriate solutions for cases not mentioned in the Recommended Implementation section.

+

Root Attributes Versus Block Attributes#

+

All Attributes and Blocks at the top level of schema.Resource Schema are considered "root" attributes. These will always be handled with receiver methods on ResourceData, such as reading with d.Get(), d.GetOk(), etc. and writing with d.Set(). Any nested Attributes and Blocks inside those root Blocks will then be handled with standard Go types according to the table in the Type Mapping section.

+

By convention in the codebase, each level of Block handling beyond root attributes should be separated into "expand" functions that convert Terraform Plugin SDK data into the equivalent AWS Go SDK type (typically named expand{Service}{Type}) and "flatten" functions that convert an AWS Go SDK type into the equivalent Terraform Plugin SDK data (typically named flatten{Service}{Type}). The Recommended Implementations section will go into those details.

+

NOTE: While it is possible in certain type scenarios to deeply read and write ResourceData information for a Block Attribute, this practice is discouraged in preference of only handling root Attributes and Blocks.

+ +

Given the various complexities around the Terraform Plugin SDK type system, this section contains recommended implementations for Terraform AWS Provider resource code based on the Type Mapping section and the features of the Terraform Plugin SDK and AWS Go SDK. The eventual goal and styling for many of these recommendations is to ease static analysis of the codebase and future potential code generation efforts.

+

Some of these coding patterns may not be well represented in the codebase, as refactoring the many older styles over years of community development is a large task. However this is meant to represent the preferred implementations today. These will continue to evolve as this codebase and the Terraform Plugin ecosystem changes.

+

Where to Define Flex Functions#

+

Define FLatten and EXpand (i.e., flex) functions at the most local level possible. This table provides guidance on the preferred place to define flex functions based on usage.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Where UsedWhere to DefineInclude Service in Name
One resource (e.g., aws_instance)Resource file (e.g., internal/service/ec2/instance.go)No
Few resources in one service (e.g., EC2)Resource file or service flex file (e.g., internal/service/ec2/flex.go)No
Widely used in one service (e.g., EC2)Service flex file (e.g., internal/service/ec2/flex.go)No
Two services (e.g., EC2 and EKS)Define a copy in each serviceIf helpful
3+ servicesinternal/flex/flex.goYes
+

Expand Functions for Blocks#

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    if tfMap == nil {
+        return nil
+    }
+
+    apiObject := &service.Structure{}
+
+    // ... nested attribute handling ...
+
+    return apiObject
+}
+
+func expandStructures(tfList []interface{}) []*service.Structure {
+    if len(tfList) == 0 {
+        return nil
+    }
+
+    var apiObjects []*service.Structure
+
+    for _, tfMapRaw := range tfList {
+        tfMap, ok := tfMapRaw.(map[string]interface{})
+
+        if !ok {
+            continue
+        }
+
+        apiObject := expandStructure(tfMap)
+
+        if apiObject == nil {
+            continue
+        }
+
+        apiObjects = append(apiObjects, apiObject)
+    }
+
+    return apiObjects
+}
+
+

Flatten Functions for Blocks#

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    if apiObject == nil {
+        return nil
+    }
+
+    tfMap := map[string]interface{}{}
+
+    // ... nested attribute handling ...
+
+    return tfMap
+}
+
+func flattenStructures(apiObjects []*service.Structure) []interface{} {
+    if len(apiObjects) == 0 {
+        return nil
+    }
+
+    var tfList []interface{}
+
+    for _, apiObject := range apiObjects {
+        if apiObject == nil {
+            continue
+        }
+
+        tfList = append(tfList, flattenStructure(apiObject))
+    }
+
+    return tfList
+}
+
+

Root TypeBool and AWS Boolean#

+

To read, if always sending the attribute value is correct:

+
input := service.ExampleOperationInput{
+    AttributeName: aws.String(d.Get("attribute_name").(bool))
+}
+
+

Otherwise to read, if only sending the attribute value when true is preferred (!ok for opposite):

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok {
+    input.AttributeName = aws.Bool(v.(bool))
+}
+
+

To write:

+
d.Set("attribute_name", output.Thing.AttributeName)
+
+

Root TypeFloat and AWS Float#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok {
+    input.AttributeName = aws.Float64(v.(float64))
+}
+
+

To write:

+
d.Set("attribute_name", output.Thing.AttributeName)
+
+

Root TypeInt and AWS Integer#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok {
+    input.AttributeName = aws.Int64(int64(v.(int)))
+}
+
+

To write:

+
d.Set("attribute_name", output.Thing.AttributeName)
+
+

Root TypeList of Resource and AWS List of Structure#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok && len(v.([]interface{})) > 0 {
+    input.AttributeName = expandStructures(v.([]interface{}))
+}
+
+

To write:

+
if err := d.Set("attribute_name", flattenStructures(output.Thing.AttributeName)); err != nil {
+    return diag.Errorf("setting attribute_name: %s", err)
+}
+
+

Root TypeList of Resource and AWS Structure#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {
+    input.AttributeName = expandStructure(v.([]interface{})[0].(map[string]interface{}))
+}
+
+

To write (likely to have helper function introduced soon):

+
if output.Thing.AttributeName != nil {
+    if err := d.Set("attribute_name", []interface{}{flattenStructure(output.Thing.AttributeName)}); err != nil {
+        return diag.Errorf("setting attribute_name: %s", err)
+    }
+} else {
+    d.Set("attribute_name", nil)
+}
+
+

Root TypeList of TypeString and AWS List of String#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok && len(v.([]interface{})) > 0 {
+    input.AttributeName = flex.ExpandStringList(v.([]interface{}))
+}
+
+

To write:

+
d.Set("attribute_name", aws.StringValueSlice(output.Thing.AttributeName))
+
+

Root TypeMap of TypeString and AWS Map of String#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok && len(v.(map[string]interface{})) > 0 {
+    input.AttributeName = flex.ExpandStringMap(v.(map[string]interface{}))
+}
+
+

To write:

+
d.Set("attribute_name", aws.StringValueMap(output.Thing.AttributeName))
+
+

Root TypeSet of Resource and AWS List of Structure#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok && v.(*schema.Set).Len() > 0 {
+    input.AttributeName = expandStructures(v.(*schema.Set).List())
+}
+
+

To write:

+
if err := d.Set("attribute_name", flattenStructures(output.Thing.AttributeNames)); err != nil {
+    return diag.Errorf("setting attribute_name: %s", err)
+}
+
+

Root TypeSet of TypeString and AWS List of String#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok && v.(*schema.Set).Len() > 0 {
+    input.AttributeName = flex.ExpandStringSet(v.(*schema.Set))
+}
+
+

To write:

+
d.Set("attribute_name", aws.StringValueSlice(output.Thing.AttributeName))
+
+

Root TypeString and AWS String#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok {
+    input.AttributeName = aws.String(v.(string))
+}
+
+

To write:

+
d.Set("attribute_name", output.Thing.AttributeName)
+
+

Root TypeString and AWS Timestamp#

+

To ensure that parsing the read string value does not fail, define attribute_name's schema.Schema with an appropriate ValidateFunc:

+
"attribute_name": {
+    Type:         schema.TypeString,
+    // ...
+    ValidateFunc: validation.IsRFC3339Time,
+},
+
+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := d.GetOk("attribute_name"); ok {
+    v, _ := time.Parse(time.RFC3339, v.(string))
+
+    input.AttributeName = aws.Time(v)
+}
+
+

To write:

+
if output.Thing.AttributeName != nil {
+    d.Set("attribute_name", aws.TimeValue(output.Thing.AttributeName).Format(time.RFC3339))
+} else {
+    d.Set("attribute_name", nil)
+}
+
+

Nested TypeBool and AWS Boolean#

+

To read, if always sending the attribute value is correct:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].(bool); ok {
+        apiObject.NestedAttributeName = aws.Bool(v)
+    }
+
+    // ...
+}
+
+

To read, if only sending the attribute value when true is preferred (!v for opposite):

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].(bool); ok && v {
+        apiObject.NestedAttributeName = aws.Bool(v)
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = aws.BoolValue(v)
+    }
+
+    // ...
+}
+
+

Nested TypeFloat and AWS Float#

+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].(float64); ok && v != 0.0 {
+        apiObject.NestedAttributeName = aws.Float64(v)
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = aws.Float64Value(v)
+    }
+
+    // ...
+}
+
+

Nested TypeInt and AWS Integer#

+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].(int); ok && v != 0 {
+        apiObject.NestedAttributeName = aws.Int64(int64(v))
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = aws.Int64Value(v)
+    }
+
+    // ...
+}
+
+

Nested TypeList of Resource and AWS List of Structure#

+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].([]interface{}); ok && len(v) > 0 {
+        apiObject.NestedAttributeName = expandStructures(v)
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = flattenNestedStructures(v)
+    }
+
+    // ...
+}
+
+

Nested TypeList of Resource and AWS Structure#

+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].([]interface{}); ok && len(v) > 0 && v[0] != nil {
+        apiObject.NestedAttributeName = expandStructure(v[0].(map[string]interface{}))
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = []interface{}{flattenNestedStructure(v)}
+    }
+
+    // ...
+}
+
+

Nested TypeList of TypeString and AWS List of String#

+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].([]interface{}); ok && len(v) > 0 {
+        apiObject.NestedAttributeName = flex.ExpandStringList(v)
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = aws.StringValueSlice(v)
+    }
+
+    // ...
+}
+
+

Nested TypeMap of TypeString and AWS Map of String#

+

To read:

+
input := service.ExampleOperationInput{}
+
+if v, ok := tfMap["nested_attribute_name"].(map[string]interface{}); ok && len(v) > 0 {
+    apiObject.NestedAttributeName = flex.ExpandStringMap(v)
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = aws.StringValueMap(v)
+    }
+
+    // ...
+}
+
+

Nested TypeSet of Resource and AWS List of Structure#

+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].(*schema.Set); ok && v.Len() > 0 {
+        apiObject.NestedAttributeName = expandStructures(v.List())
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = flattenNestedStructures(v)
+    }
+
+    // ...
+}
+
+

Nested TypeSet of TypeString and AWS List of String#

+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].(*schema.Set); ok && v.Len() > 0 {
+        apiObject.NestedAttributeName = flex.ExpandStringSet(v)
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = aws.StringValueSlice(v)
+    }
+
+    // ...
+}
+
+

Nested TypeString and AWS String#

+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].(string); ok && v != "" {
+        apiObject.NestedAttributeName = aws.String(v)
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = aws.StringValue(v)
+    }
+
+    // ...
+}
+
+

Nested TypeString and AWS Timestamp#

+

To ensure that parsing the read string value does not fail, define nested_attribute_name's schema.Schema with an appropriate ValidateFunc:

+
"nested_attribute_name": {
+    Type:         schema.TypeString,
+    // ...
+    ValidateFunc: validation.IsRFC3339Time,
+},
+
+

To read:

+
func expandStructure(tfMap map[string]interface{}) *service.Structure {
+    // ...
+
+    if v, ok := tfMap["nested_attribute_name"].(string); ok && v != "" {
+        v, _ := time.Parse(time.RFC3339, v)
+
+        apiObject.NestedAttributeName = aws.Time(v)
+    }
+
+    // ...
+}
+
+

To write:

+
func flattenStructure(apiObject *service.Structure) map[string]interface{} {
+    // ...
+
+    if v := apiObject.NestedAttributeName; v != nil {
+        tfMap["nested_attribute_name"] = aws.TimeValue(v).Format(time.RFC3339)
+    }
+
+    // ...
+}
+
+

Further Guidelines#

+

This section includes additional topics related to data design and decision making from the Terraform AWS Provider maintainers.

+

Binary Values#

+

Certain resources may need to interact with binary (non UTF-8) data while the Terraform State only supports UTF-8 data. Configurations attempting to pass binary data to an attribute will receive an error from Terraform CLI. These attributes should expect and store the value as a Base64 string while performing any necessary encoding or decoding in the resource logic.

+

Destroy State Values#

+

During resource destroy operations, only previously applied Terraform State values are available to resource logic. Even if the configuration is updated in a manner where both the resource destroy is triggered (e.g., setting the resource meta-argument count = 0) and an attribute value is updated, the resource logic will only have the previously applied data values.

+

Any usage of attribute values during destroy should explicitly note in the resource documentation that the desired value must be applied into the Terraform State before any apply to destroy the resource.

+

Hashed Values#

+

Attribute values may be very lengthy or potentially contain Sensitive Values. A potential solution might be to use a hashing algorithm, such as MD5 or SHA256, to convert the value before saving in the Terraform State to reduce its relative size or attempt to obfuscate the value. However, there are a few reasons not to do so:

+
    +
  • Terraform expects any planned values to match applied values. Ensuring proper handling during the various Terraform operations such as difference planning and Terraform State storage can be a burden.
  • +
  • Hashed values are generally unusable in downstream attribute references. If a value is hashed, it cannot be successfully used in another resource or provider configuration that expects the real value.
  • +
  • Terraform plan differences are meant to be human readable. If a value is hashed, operators will only see the relatively unhelpful hash differences abc123 -> def456 in plans.
  • +
+

Any value hashing implementation will not be accepted. An exception to this guidance is if the remote system explicitly provides a separate hash value in responses, in which a resource can provide a separate attribute with that hashed value.

+

Sensitive Values#

+

Marking an Attribute in the Terraform Plugin SDK Schema with Sensitive has the following real world implications:

+
    +
  • All occurrences of the Attribute will have the value hidden in plan difference output. In the context of an Attribute within a Block, all Blocks will hide all values of the Attribute.
  • +
  • In Terraform CLI 0.14 (with the provider_sensitive_attrs experiment enabled) and later, any downstream references to the value in other configuration will hide the value in plan difference output.
  • +
+

The value is either always hidden or not as the Terraform Plugin SDK does not currently implement conditional support for this functionality. Since Terraform Configurations have no control over the behavior, hiding values from the plan difference can incur a potentially undesirable user experience cost for operators.

+

Given that and especially with the improvements in Terraform CLI 0.14, the Terraform AWS Provider maintainers guiding principles for determining whether an Attribute should be marked as Sensitive is if an Attribute value:

+
    +
  • Objectively will always contain a credential, password, or other secret material. Operators can have differing opinions on what constitutes secret material and the maintainers will make best effort determinations, if necessary consulting with the HashiCorp Security team.
  • +
  • If the Attribute is within a Block, that all occurrences of the Attribute value will objectively contain secret material. Some APIs (and therefore the Terraform AWS Provider resources) implement generic "setting" and "value" structures which likely will contain a mixture of secret and non-secret material. These will generally not be accepted for marking as Sensitive.
  • +
+

If you are unsatisfied with sensitive value handling, the maintainers can recommend ensuring there is a covering issue in the Terraform CLI and/or Terraform Plugin SDK projects explaining the use case. Ultimately, Terraform Plugins including the Terraform AWS Provider cannot implement their own sensitive value abilities if the upstream projects do not implement the appropriate functionality.

+

Virtual Attributes#

+

Attributes which only exist within Terraform and not the remote system are typically referred as virtual attributes. Especially in the case of Destroy State Values, these attributes rely on the Implicit State Passthrough behavior of values in Terraform to be available in resource logic. A fictitous example of one of these may be a resource attribute such as a skip_waiting flag, which is used only in the resource logic to skip the typical behavior of waiting for operations to complete.

+

If a virtual attribute has a default value that does not match the Zero Value Mapping for the type, it is recommended to explicitly call d.Set() with the default value in the schema.Resource Importer State function, for example:

+
&schema.Resource{
+    // ... other fields ...
+    Importer: &schema.ResourceImporter{
+        State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
+            d.Set("skip_waiting", true)
+
+            return []*schema.ResourceData{d}, nil
+        },
+    },
+}
+
+

This helps prevent an immediate plan difference after resource import unless the configuration has a non-default value.

+

Glossary#

+

Below is a listing of relevant terms and descriptions for data handling and conversion in the Terraform AWS Provider to establish common conventions throughout this documentation. This list is not exhaustive of all concepts of Terraform Plugins, the Terraform AWS Provider, or the data handling that occurs during Terraform runs, but these should generally provide enough context about the topics discussed here.

+
    +
  • AWS Go SDK: Library that converts Go code into AWS Service API compatible operations and data types. Currently refers to version 1 (v1) available since 2015, however version 2 (v2) will reach general availability status soon. Project.
  • +
  • AWS Go SDK Model: AWS Go SDK compatible format of AWS Service API Model.
  • +
  • AWS Go SDK Service: AWS Service API Go code generated from the AWS Go SDK Model. Generated by the AWS Go SDK code.
  • +
  • AWS Service API: Logical boundary of an AWS service by API endpoint. Some large AWS services may be marketed with many different product names under the same service API (e.g., VPC functionality is part of the EC2 API) and vice-versa where some services may be marketed with one product name but are split into multiple service APIs (e.g., Single Sign-On functionality is split into the Identity Store and SSO Admin APIs).
  • +
  • AWS Service API Model: Declarative description of the AWS Service API operations and data types. Generated by the AWS service teams. Used to operate the API and generate API clients such as the various AWS Software Development Kits (SDKs).
  • +
  • Terraform Language ("Configuration"): Configuration syntax interpreted by the Terraform CLI. An implementation of HCL. Full Documentation.
  • +
  • Terraform Plugin Protocol: Description of Terraform Plugin operations and data types. Currently based on the Remote Procedure Call (RPC) library gRPC.
  • +
  • Terraform Plugin Go: Low-level library that converts Go code into Terraform Plugin Protocol compatible operations and data types. Not currently implemented in the Terraform AWS Provider. Project.
  • +
  • Terraform Plugin SDK: High-level library that converts Go code into Terraform Plugin Protocol compatible operations and data types. Project.
  • +
  • Terraform Plugin SDK Schema: Declarative description of types and domain specific behaviors for a Terraform provider, including resources and attributes. Full Documentation.
  • +
  • Terraform State: Bindings between objects in a remote system (e.g., an EC2 VPC) and a Terraform configuration (e.g., an aws_vpc resource configuration). Full Documentation.
  • +
+

AWS Service API Models use specific terminology to describe data and types:

+
    +
  • Enumeration: Collection of valid values for a Shape.
  • +
  • Operation: An API call. Includes information about input, output, and error Shapes.
  • +
  • Shape: Type description.
      +
    • boolean: Boolean value.
    • +
    • float: Fractional numeric value. May contain value validation such as maximum or minimum.
    • +
    • integer: Whole numeric value. May contain value validation such as maximum or minimum.
    • +
    • list: Collection that contains member Shapes. May contain value validation such as maximum or minimum keys.
    • +
    • map: Grouping of key Shape to value Shape. May contain value validation such as maximum or minimum keys.
    • +
    • string: Sequence of characters. May contain value validation such as an enumeration, regular expression pattern, maximum length, or minimum length.
    • +
    • structure: Object that contains member Shapes. May represent an error.
    • +
    • timestamp: Date and time value.
    • +
    +
  • +
+

The Terraform Language uses the following terminology to describe data and types:

+
    +
  • Attribute ("Argument"): Assigns a name to a data value.
  • +
  • Block ("Configuration Block"): Container type for Attributes or Blocks.
  • +
  • null: Virtual value equivalent to the Attribute not being set.
  • +
  • Types: Full Documentation.
      +
    • any: Virtual type representing any concrete type in type declarations.
    • +
    • bool: Boolean value.
    • +
    • list ("tuple"): Ordered collection of values.
    • +
    • map ("object"): Grouping of string keys to values.
    • +
    • number: Numeric value. Can be either whole or fractional numbers.
    • +
    • set: Unordered collection of values.
    • +
    • string: Sequence of characters.
    • +
    +
  • +
+

Terraform Plugin SDK Schemas use the following terminology to describe data and types:

+
    +
  • Behaviors: Full Documentation.
      +
    • Sensitive: Whether the value should be hidden from user interface output.
    • +
    • StateFunc: Conversion function between the value set by the Terraform Plugin and the value seen by Terraform Plugin SDK (and ultimately the Terraform State).
    • +
    +
  • +
  • Element: Underylying value type for a collection or grouping Schema.
  • +
  • Resource Data: Data representation of a Resource Schema. Translation layer between the Schema and Go code of a Terraform Plugin. In the Terraform Plugin SDK, the ResourceData Go type.
  • +
  • Resource Schema: Grouping of Schema that represents a Terraform Resource.
  • +
  • Schema: Represents an Attribute or Block. Has a Type and Behavior(s).
  • +
  • Types: Full Documentation.
      +
    • TypeBool: Boolean value.
    • +
    • TypeFloat: Fractional numeric value.
    • +
    • TypeInt: Whole numeric value.
    • +
    • TypeList: Ordered collection of values or Blocks.
    • +
    • TypeMap: Grouping of key Type to value Type.
    • +
    • TypeSet: Unordered collection of values or Blocks.
    • +
    • TypeString: Sequence of characters value.
    • +
    +
  • +
+

Some other terms that may be used:

+
    +
  • Block Attribute ("Child Attribute", "Nested Attribute"): Block level Attribute.
  • +
  • Expand Function: Function that converts Terraform Plugin SDK data into the equivalent AWS Go SDK type.
  • +
  • Flatten Function: Function that converts an AWS Go SDK type into the equivalent Terraform Plugin SDK data.
  • +
  • NullableTypeBool: Workaround "schema type" created to accept a boolean value that is not configured in addition to true and false. Not implemented in the Terraform Plugin SDK, but uses TypeString (where "" represents not configured) and additional validation.
  • +
  • NullableTypeFloat: Workaround "schema type" created to accept a fractional numeric value that is not configured in addition to 0.0. Not implemented in the Terraform Plugin SDK, but uses TypeString (where "" represents not configured) and additional validation.
  • +
  • NullableTypeInt: Workaround "schema type" created to accept a whole numeric value that is not configured in addition to 0. Not implemented in the Terraform Plugin SDK, but uses TypeString (where "" represents not configured) and additional validation.
  • +
  • Root Attribute: Resource top level Attribute or Block.
  • +
+

For additional reference, the Terraform documentation also includes a full glossary of terminology.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/debugging/index.html b/debugging/index.html new file mode 100644 index 000000000000..9fa8616b13b4 --- /dev/null +++ b/debugging/index.html @@ -0,0 +1,1369 @@ + + + + + + + + + + + + + + + + + + + + Debugging - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Debugging#

+

This guide covers strategies we have found useful in finding runtime and logic errors in the AWS Provider. We do not cover syntax or compiler errors as these are well addressed by Go documentation and IDEs, such as Visual Studio Code ("VS Code").

+

If you have your own debugging tricks for the provider, open a pull request to add them here!

+

1. Reproduce#

+

One of the most crucial steps in the process of bug fixing is to reproduce the bug and create a minimal reproduction of it. In this section, we will discuss why it is so important to reproduce bugs.

+

TL;DR: for Repro

+

Perhaps the most important step in debugging is reproducing the bug.

+

What is a bug?#

+

Before we dive into the details, let's first define what a bug is. A bug is an error or flaw that produces an incorrect or unexpected result, or causes the AWS Provider to behave in an unintended manner. For the Provider, "bugs" generally refer to errors that happen at runtime due to logic problems or unexpected interactions with AWS.

+

Why is it important to reproduce bugs?#

+

Reproducing a bug is the process of intentionally triggering the error or unexpected behavior that occurs when the bug is present. It is important to reproduce bugs because it allows us to:

+
    +
  1. Verify that the bug exists: By reproducing the bug, we can confirm that the error or unexpected behavior is real and either always happens or happens intermittently and not just a one-time occurrence. This is important because if we can't reproduce the bug, we may end up wasting time trying to fix a non-existent issue.
  2. +
  3. Understand the cause of the bug: Reproducing the bug can help us understand what is causing the error or unexpected behavior. This is important because without understanding the root cause of the bug, we may end up fixing the symptoms of the bug rather than the underlying problem.
  4. +
  5. Test the fix: If we know how to reproduce the bug, we also know how to verify we've fixed it.
  6. +
+

2. Create a minimal reproduction#

+

TL;DR: A complete 10-line configuration that reproduces a bug is far more (perhaps 100× more) useful than a 1000-line configuration that reproduces the same bug.

+

Creating a minimal reproduction of a bug is the process of isolating the bug to its simplest form. It is very important to create a minimal reproduction of bugs, especially with Terraform configurations, because it allows us to:

+
    +
  1. Focus on the root cause of the bug: By eliminating any extraneous configuration or dependencies, we can focus on the specific configuration that is causing the bug. This makes it easier to understand and fix the root cause of the bug.
  2. +
  3. Save time: By creating a minimal reproduction of the bug, we can reduce the amount of time it takes to reproduce the bug and test the fix. This is because we don't have to navigate through a large configuration or deal with unnecessary dependencies. The minimal configuration becomes the basis of a new acceptance test that verifies the bug is fixed (and stays fixed in the future).
  4. +
  5. Make it easier for others to reproduce the bug: If we are working on a team, creating a minimal reproduction of the bug makes it easier for other team members to reproduce the bug and understand the root cause.
  6. +
+

How to create a minimal reproduction of a bug#

+

Creating a minimal reproduction of a bug can be a time-consuming process, but it is well worth the effort. Here are some tips for creating a minimal reproduction of a bug:

+
    +
  1. Start with a simple test case: Start with a simple configuration that causes the bug.
  2. +
  3. Remove any extraneous configuration: Remove as many resources, dependencies, and arguments as possible to simplify, while still maintaining test independence.
  4. +
  5. Verify that the configuration still reproduces the bug: After removing configuration, make sure that the configuration still causes the bug. If the bug no longer occurs, you may have removed too much. In this case, add back configuration until the bug reappears.
  6. +
+

A minimal configuration is worth its weight in gold. If you're only able to make it this far in the debugging process, create a new issue with your minimal configuration or add it to an existing issue. A minimal configuration is a great way to give some else a jump start in looking at the problem.

+

3. Create A New Acceptance Test#

+

Sometimes, we tend to immediately jump into the code without creating a test. However, creating an acceptance test is something we'll need to do eventually anyway but doing it first has the added benefit of allowing us to easily trigger the bug. That makes debugging easier and allows us to use debugging tools.

+

Use the Minimal Configuration as the Basis for the Test#

+

Adding a problematic minimal configuration test is just like creating an acceptance test except that when we run it, it has a problem. Starting with the end in mind focuses our efforts and gives great satisfaction when the code is fixed!

+

Erroring tests example#

+

For example, if we're looking at an error in the VPC flow log resource, this is how we might add a test with a minimal configuration to trigger the error.

+
func TestAccVPCFlowLog_destinationError(t *testing.T) {
+    ctx := acctest.Context(t)
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+
+    resource.ParallelTest(t, resource.TestCase{
+        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+        ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+        CheckDestroy:             testAccCheckFlowLogDestroy(ctx),
+        Steps: []resource.TestStep{
+            {
+                Config:      testAccVPCFlowLogConfig_destinationError(rName),
+            },
+        },
+    })
+}
+
+// ...
+
+func testAccVPCFlowLogConfig_destinationError(rName string) string {
+    return fmt.Sprintf(`
+resource "aws_flow_log" "test" {
+  # minimal configuration causing a bug
+}
+`, rName)
+}
+
+

If we were to run the test on the command line, this is how the erroring output might look:

+
% make testacc TESTS=TestAccVPCFlowLog_destinationError PKG=vpc
+==> Checking that code complies with gofmt requirements...
+TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run='TestAccVPCFlowLog_destinationError'  -timeout 180m
+=== RUN   TestAccVPCFlowLog_destinationError
+=== PAUSE TestAccVPCFlowLog_destinationError
+=== CONT  TestAccVPCFlowLog_destinationError
+    vpc_flow_log_test.go:297: Step 1/1 error: Error running apply: exit status 1
+
+        Error: creating Flow Log (vpc-0c2635533cef2be79): 1 error occurred:
+            * vpc-0c2635533cef2be79: 400: Access Denied for LogDestination: does-not-exist. Please check LogDestination permission
+
+          with aws_flow_log.test,
+          on terraform_plugin_test.tf line 34, in resource "aws_flow_log" "test":
+          34: resource "aws_flow_log" "test" {
+
+--- FAIL: TestAccVPCFlowLog_destinationError (13.79s)
+FAIL
+FAIL    github.com/hashicorp/terraform-provider-aws/internal/service/ec2    15.373s
+FAIL
+make: *** [testacc] Error 1
+
+

Wrong results example#

+

Of course, not all bugs throw errors. For example, perhaps the bug you are looking at involves a wrong value in the log_group_name attribute. This is an example of adding a test that will error because the value is wrong.

+
func TestAccVPCFlowLog_destinationError(t *testing.T) {
+    ctx := acctest.Context(t)
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+
+    resource.ParallelTest(t, resource.TestCase{
+        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+        ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+        CheckDestroy:             testAccCheckFlowLogDestroy(ctx),
+        Steps: []resource.TestStep{
+            {
+                Config:      testAccVPCFlowLogConfig_destinationError(rName),
+                Check: resource.ComposeTestCheckFunc(
+                    testAccCheckFlowLogExists(ctx, resourceName, &flowLog),
+                    resource.TestCheckResourceAttr(resourceName, "log_group_name", ""), // this should not be empty
+                ),
+            },
+        },
+    })
+}
+
+// ...
+
+func testAccVPCFlowLogConfig_destinationError(rName string) string {
+    return fmt.Sprintf(`
+resource "aws_flow_log" "test" {
+  # minimal configuration causing a bug
+}
+`, rName)
+}
+
+

If we were to run the test on the command line, this is how the erroring output might look:

+
% make testacc TESTS=TestAccVPCFlowLog_LogDestinationType_s3 PKG=vpc
+==> Checking that code complies with gofmt requirements...
+TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run='TestAccVPCFlowLog_LogDestinationType_s3'  -timeout 180m
+=== RUN   TestAccVPCFlowLog_LogDestinationType_s3
+=== PAUSE TestAccVPCFlowLog_LogDestinationType_s3
+=== CONT  TestAccVPCFlowLog_LogDestinationType_s3
+    vpc_flow_log_test.go:269: Step 1/2 error: Check failed: Check 3/4 error: aws_flow_log.test: Attribute 'log_group_name' expected "abc-123", got ""
+--- FAIL: TestAccVPCFlowLog_LogDestinationType_s3 (15.49s)
+FAIL
+FAIL    github.com/hashicorp/terraform-provider-aws/internal/service/ec2    17.358s
+FAIL
+make: *** [testacc] Error 1
+
+

Take Stock#

+

It may seem like we haven't accomplished much of anything at this point. However, we have! We've:

+
    +
  1. Reproduced the error
  2. +
  3. Created a minimal reproduction
  4. +
  5. Created a test to trigger the error
  6. +
+

This will make the rest of debugging a lot more straightforward.

+

Contributing a Failing Test#

+

If you aren't able to figure out a bug or delving into code is not your thing, open a pull request to contribute just the test that highlights the bug. This is a valuable starting point for future work!

+

We ask that you make the test "PASS," but use code comments and a GitHub issue to explain what is wrong. With nearly 7,000 acceptance tests, there are always a certain percentage that inexplicably fail. Since we know that the new test we're adding highlights a known bug, we don't want the failure to be lost in the tally of inexplicable failures.

+

Failing Test Contribution Example: Errors#

+

For example, if you want to contribute just a failing test, the example below shows how to use ExpectError and a code comment to reference an open GitHub issue addressing the bug. Here we're using ExpectError to allow the test to "pass," even though it should not. When the next person comes along to debug, they can simply remove ExpectError.

+
func TestAccVPCFlowLog_destinationError(t *testing.T) {
+    ctx := acctest.Context(t)
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+
+    resource.ParallelTest(t, resource.TestCase{
+        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+        ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+        CheckDestroy:             testAccCheckFlowLogDestroy(ctx),
+        Steps: []resource.TestStep{
+            {
+                Config:      testAccVPCFlowLogConfig_destinationError(rName),
+                // This error should not happen!
+                // See https://github.com/hashicorp/terraform-provider-aws/issues/45912
+                ExpectError: regexp.MustCompile(`invalid destination`),
+            },
+        },
+    })
+}
+
+// ...
+
+func testAccVPCFlowLogConfig_destinationError(rName string) string {
+    return fmt.Sprintf(`
+resource "aws_flow_log" "test" {
+  # minimal configuration causing a bug
+}
+`, rName)
+}
+
+

Failing Test Contribution Example: Wrong Results#

+

There are other types of bugs besides the error thrown in the previous example. For example, with a wrong results test, you would have resource.TestCheckResourceAttr() highlighting what is wrong, along with a link to the GitHub issue to explain why the attribute value is wrong.

+
func TestAccVPCFlowLog_destinationError(t *testing.T) {
+    ctx := acctest.Context(t)
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+
+    resource.ParallelTest(t, resource.TestCase{
+        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+        ErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+        CheckDestroy:             testAccCheckFlowLogDestroy(ctx),
+        Steps: []resource.TestStep{
+            {
+                Config:      testAccVPCFlowLogConfig_destinationError(rName),
+                Check: resource.ComposeTestCheckFunc(
+                    testAccCheckFlowLogExists(ctx, resourceName, &flowLog),
+                     // log_group_name should be "xyz-123"
+                     // See https://github.com/hashicorp/terraform-provider-aws/issues/45912
+                    resource.TestCheckResourceAttr(resourceName, "log_group_name", ""),
+                ),
+            },
+        },
+    })
+}
+
+// ...
+
+func testAccVPCFlowLogConfig_destinationError(rName string) string {
+    return fmt.Sprintf(`
+resource "aws_flow_log" "test" {
+  # minimal configuration causing a bug
+}
+`, rName)
+}
+
+

4. Find Out Why a Bug Happens#

+

Now that we can easily reproduce a bug with a test and we have a minimal configuration, we can look more closely at why the bug is happening.

+

There are several approaches to looking at what is happening in the AWS provider. You might find that some bugs lend themselves to one approach while others are more easily evaluated with another. Also, you might start with one approach and then find you need to move to another, e.g., starting by adding a few fmt.Printf() statements and then moving to an IDE debugger.

+

Use fmt.Printf()#

+

One quick and dirty approach that works for simple bugs is to have Go output information to the console (i.e., terminal). This approach is especially helpful if you've reviewed the code, found an error, and have a strong suspicion about what is going on. You can use fmt.Printf() to confirm what you think is happening.

+

The approach is also helpful to see if the code reaches a certain point or for seeing the value of a variable or two.

+

This approach does not work well for complex logic, examining many lines of code, or for looking at many different variables. Use more advanced debugging, as described below, for these situations.

+

This example shows using fmt.Printf() to output information to the console:

+
func resourceLogFlowCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+    // other code ...
+
+    outputRaw, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) {
+        return conn.CreateFlowLogsWithContext(ctx, input)
+    }, errCodeInvalidParameter, "Unable to assume given IAM role")
+
+    fmt.Printf("reached point %d, with input: %+v\n", 3, input)
+
+    if err == nil && outputRaw != nil {
+        fmt.Printf("reached point %d, with output: %+v\n", 4, outputRaw)
+        err = UnsuccessfulItemsError(outputRaw.(*ec2.CreateFlowLogsOutput).Unsuccessful)
+    }
+
+    // other code ...
+}
+
+

Running the test from the command line, we might see the following output:

+
% make testacc TESTS=TestAccVPCFlowLog_LogDestinationType_s3 PKG=vpc
+==> Checking that code complies with gofmt requirements...
+TF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run='TestAccVPCFlowLog_LogDestinationType_s3'  -timeout 180m
+=== RUN   TestAccVPCFlowLog_LogDestinationType_s3
+=== PAUSE TestAccVPCFlowLog_LogDestinationType_s3
+=== CONT  TestAccVPCFlowLog_LogDestinationType_s3
+reached point 3, with input: {
+  ClientToken: "terraform-20230403170214312900000001",
+  LogDestination: "arn:aws:s3:::tf-acc-test-4802362269206133111",
+  LogDestinationType: "s3",
+  MaxAggregationInterval: 600,
+  ResourceIds: ["vpc-093265898e824c24e"],
+  ResourceType: "VPC",
+  TagSpecifications: [{
+      ResourceType: "vpc-flow-log",
+      Tags: [{
+          Key: "Name",
+          Value: "tf-acc-test-4802362269206133111"
+        }]
+    }],
+  TrafficType: "ALL"
+}
+reached point 4, with output: {
+  ClientToken: "terraform-20230403170214312900000001",
+  FlowLogIds: ["fl-09861862b9f8bb3a3"]
+}
+--- PASS: TestAccVPCFlowLog_LogDestinationType_s3 (26.45s)
+
+

Use Visual Studio Code Debugging#

+

Using debugging from within VS Code provides extra benefits but also an extra challenge. The extra benefits include the ability to set break points, step over and into code, and seeing the values of variables. The extra challenge is getting your debug environment properly set up to include access to your AWS credentials and environment variables used for testing.

+

Special thanks to Drew Mullen for his work on debugging the AWS provider in VS Code.

+

Set up launch.json#

+

VS Code uses a hidden directory called .vscode in the root of your project for configuration files. This directory and files it contains are ignored by Git using the .gitignore file included with the AWS provider. This allows you to have your own local configuration without concerns that it will be uploaded to or overwritten by the AWS provider repository.

+

As shown below, use VS Code to create two files in the .vscode directory: launch.json and private.env.

+

Using launch.json in VS Code

+

In launch.json, add this configuration:

+
{
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Debug Selected Test",
+      "request": "launch",
+      "type": "go",
+      "args": [
+        "-test.v",
+        "-test.run",
+        "^${selectedText}$"
+      ],
+      "mode": "auto",
+      "program": "${fileDirname}",
+      "env": {"PKG_NAME": "${relativeFileDirname}"},
+      "envFile": "${workspaceFolder}/.vscode/private.env",
+      "showLog": true
+    }
+  ]
+}
+
+

In private.env, add environment variables to represent your AWS provider testing configuration, including replacing aws_provider_profile and aws_alternate_profile below with the names of profiles defined in your ~/.aws/config file:

+
TF_ACC=1
+TF_LOG=info
+GOFLAGS='-mod=readonly'
+AWS_PROFILE=aws_provider_profile
+AWS_DEFAULT_REGION=us-west-2
+AWS_ALTERNATE_PROFILE=aws_alternate_profile
+AWS_ALTERNATE_REGION=us-east-1
+AWS_THIRD_REGION=us-east-2
+ACM_CERTIFICATE_ROOT_DOMAIN=terraform-provider-aws-acctest-acm.com
+
+

Note that you can set TF_LOG to debug but, if you do, you can receive thousands of lines of additional information that can make it difficult to find useful information.

+

Viewing Run and Debug#

+

Open the Run and Debug panel in VS Code by pressing Shift-⌘-D or by clicking on the debug button icon:

+

Debug Button

+

To debug, you'll also want to open the debug console by pressing Shift-⌘-Y or selecting "Debug Console" from the View top menu.

+

Run and Debug#

+

With the environment and VS Code GUI set up, you are ready to run and debug.

+
    +
  1. As shown below, select the name of a test function from a *_test.go file by double-clicking on the name or click-dragging over the name to highlight the entire name. (Do not include the func at the beginning or (t *testing.T) { at the end.)
  2. +
  3. Make sure that "Debug Selected Test" is shown next to the debug play button.
  4. +
  5. Click on the play button in the Run and Debug panel. Depending on your configuration, there may also be a "Debug Selected Test" option at the bottom of the VS Code window. Clicking on that is equivalent to clicking the play button in the Run and Debug panel.
  6. +
+

Debug GUI

+

Breakpoints#

+

Depending on where you suspect the problems are occurring, you can set breakpoints on the resource's Create, Read, Update, Delete, or other functions. The debugger will stop at the first breakpoint it encounters.

+

Setting breakpoints is built into VS Code and easy. Hover to the left of a line number and you'll see a faded red dot. Click on the faded red dot to turn it into a breakpoint, as shown below.

+

You can see a list of breakpoints throughout the codebase at the bottom of the Run and Debug panel, as shown below.

+

Breakpoints

+

Stepping Out#

+

Once the debugger has stopped at a breakpoint, you can control how it continues from there. VS Code will display the debugger controls, which you can reposition using the grip (i.e., six dots), at the left of the controls.

+

Debug Controls

+

From left to right, the controls are as follows:

+
    +
  1. Pause (i.e., two vertical lines) pauses execution wherever it happens to be when you click pause
  2. +
  3. Step over (i.e., dot with a curved arrow above it) goes to the next statement but will not follow execution inside functions
  4. +
  5. Step into (i.e., dot with an arrow pointing down) goes to the next statement, following execution inside functions
  6. +
  7. Step out (i.e., dot with an arrow pointing up) goes to the next statement in the calling function skipping the rest of the execution in the current function
  8. +
  9. Restart (i.e., the circular arrow) stops the current run and starts at the beginning again
  10. +
  11. Stop (i.e., the square) stops the current run
  12. +
+

Use Delve#

+

Behind the scenes, VS Code and other IDEs use Delve to debug. You can also use Delve without an IDE if you prefer to work on the command line.

+

Here are some resources to get you started using Delve:

+ +

5. Verify the Fix with a Test#

+

Verify that bugs are fixed with one or more tests. The tests used to help debug, described above, verify that the bug is fixed after debugging. In addition, the tests ensure that future changes don't undo the fix.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/dependency-updates/index.html b/dependency-updates/index.html new file mode 100644 index 000000000000..1824818b34d1 --- /dev/null +++ b/dependency-updates/index.html @@ -0,0 +1,1030 @@ + + + + + + + + + + + + + + + + + + + + + + + + Dependency Updates - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Dependency Updates#

+

Generally dependency updates are handled by maintainers.

+

Go Default Version Update#

+

This project typically upgrades its Go version for development and testing shortly after release to get the latest and greatest Go functionality. Before beginning the update process, ensure that you review the new version release notes to look for any areas of possible friction when updating.

+

Create an issue to cover the update noting down any areas of particular interest or friction.

+

Ensure that the following steps are tracked within the issue and completed within the resulting pull request.

+
    +
  • Update go version in go.mod
  • +
  • Verify make test lint works as expected
  • +
  • Verify goreleaser build --snapshot succeeds for all currently supported architectures
  • +
  • Verify goenv support for the new version
  • +
  • Update docs/development-environment.md
  • +
  • Update .go-version
  • +
  • Update CHANGELOG.md detailing the update and mention any notes practitioners need to be aware of.
  • +
+

See #9992 / #10206 for a recent example.

+

AWS Go SDK Updates#

+

Almost exclusively, github.com/aws/aws-sdk-go updates are additive in nature. It is generally safe to only scan through them before approving and merging. If you have any concerns about any of the service client updates such as suspicious code removals in the update, or deprecations introduced, run the acceptance testing for potentially affected resources before merging.

+

Authentication changes#

+

Occasionally, there will be changes listed in the authentication pieces of the AWS Go SDK codebase, e.g., changes to aws/session. The AWS Go SDK CHANGELOG should include a relevant description of these changes under a heading such as SDK Enhancements or SDK Bug Fixes. If they seem worthy of a callout in the Terraform AWS Provider CHANGELOG, then upon merging we should include a similar message prefixed with the provider subsystem, e.g., * provider: ....

+

Additionally, if a CHANGELOG addition seemed appropriate, this dependency and version should also be updated in the Terraform S3 Backend, which currently lives in Terraform Core. An example of this can be found with https://github.com/hashicorp/terraform-provider-aws/pull/9305 and https://github.com/hashicorp/terraform/pull/22055.

+

CloudFront changes#

+

CloudFront service client updates have previously caused an issue when a new field introduced in the SDK was not included with Terraform and caused all requests to error (https://github.com/hashicorp/terraform-provider-aws/issues/4091). As a precaution, if you see CloudFront updates, run all the CloudFront resource acceptance testing before merging (TestAccCloudFront).

+

golangci-lint Updates#

+

Merge if CI passes.

+

Terraform Plugin SDK Updates#

+

Except for trivial changes, run the full acceptance testing suite against the pull request and verify there are no new or unexpected failures.

+

tfproviderdocs Updates#

+

Merge if CI passes.

+

tfproviderlint Updates#

+

Merge if CI passes.

+

yaml.v2 Updates#

+

Run the acceptance testing pattern, TestAccCloudFormationStack(_dataSource)?_yaml, and merge if passing.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/development-environment/index.html b/development-environment/index.html new file mode 100644 index 000000000000..b9eca006ad39 --- /dev/null +++ b/development-environment/index.html @@ -0,0 +1,957 @@ + + + + + + + + + + + + + + + + + + + + + + + + Development Environment - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Development Environment Setup#

+

Requirements#

+
    +
  • Terraform 0.12.26+ (to run acceptance tests)
  • +
  • Go 1.19.3+ (to build the provider plugin)
  • +
+

Quick Start#

+

If you wish to work on the provider, you'll first need Go installed on your machine (please check the requirements before proceeding).

+

Note: This project uses Go Modules making it safe to work with it outside of your existing GOPATH. The instructions that follow assume a directory in your home directory outside of the standard GOPATH (i.e $HOME/development/hashicorp/).

+

Clone repository to: $HOME/development/hashicorp/

+
$ mkdir -p $HOME/development/hashicorp/; cd $HOME/development/hashicorp/
+$ git clone git@github.com:hashicorp/terraform-provider-aws
+...
+
+

Enter the provider directory and run make tools. This will install the needed tools for the provider.

+
$ make tools
+
+

To compile the provider, run make build. This will build the provider and put the provider binary in the $GOPATH/bin directory.

+
$ make build
+...
+$ $GOPATH/bin/terraform-provider-aws
+...
+
+

Testing the Provider#

+

In order to test the provider, you can run make test.

+

Note: Make sure no AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY variables are set, and there's no [default] section in the AWS credentials file ~/.aws/credentials.

+
$ make test
+
+

In order to run the full suite of Acceptance tests, run make testacc.

+

Note: Acceptance tests create real resources, and often cost money to run. Please read Running and Writing Acceptance Tests in the contribution guidelines for more information on usage.

+
$ make testacc
+
+

Using the Provider#

+

With Terraform v0.14 and later, development overrides for provider developers can be leveraged in order to use the provider built from source.

+

To do this, populate a Terraform CLI configuration file (~/.terraformrc for all platforms other than Windows; terraform.rc in the %APPDATA% directory when using Windows) with at least the following options:

+
provider_installation {
+  dev_overrides {
+    "hashicorp/aws" = "[REPLACE WITH GOPATH]/bin"
+  }
+  direct {}
+}
+
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/documentation-changes/index.html b/documentation-changes/index.html new file mode 100644 index 000000000000..f757993e1888 --- /dev/null +++ b/documentation-changes/index.html @@ -0,0 +1,845 @@ + + + + + + + + + + + + + + + + + + + + + + + + Documentation Changes - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

End User Documentation Changes#

+

All practitioner focused documentation is found in the /website folder of the repository.

+
├── website/
+    ├── r/                     # Documentation for resources
+    ├── d/                     # Documentation for data sources
+    ├── guides/                # Long format guides for provider level configuration or provider upgrades.
+    └── index.html.markdown    # Home page and all provider level documentation.
+└── examples/                  # Large example configurations
+
+

For any documentation change please raise a pull request including and adhering to the following:

+
    +
  • Reasoning for Change: Documentation updates should include an explanation for why the update is needed. If the change is a correction which is an alignment to AWS behavior, please include a link to the AWS Documentation in the PR.
  • +
  • Prefer AWS Documentation: Documentation about AWS service features and valid argument values that are likely to update over time should link to AWS service user guides and API references where possible.
  • +
  • Large Example Configurations: Example Terraform configuration that includes multiple resource definitions should be added to the repository examples directory instead of an individual resource documentation page. Each directory under examples should be self-contained to call terraform apply without special configuration.
  • +
  • Avoid Terraform Configuration Language Features: Individual resource documentation pages and examples should refrain from highlighting particular Terraform configuration language syntax workarounds or features such as variable, local, count, and built-in functions.
  • +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/error-handling/index.html b/error-handling/index.html new file mode 100644 index 000000000000..33e5b1c9d113 --- /dev/null +++ b/error-handling/index.html @@ -0,0 +1,1459 @@ + + + + + + + + + + + + + + + + + + + + + + + + Error Handling - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Error Handling#

+

The Terraform AWS Provider codebase bridges the implementation of a Terraform Plugin and an AWS API client to support AWS operations and data types as Terraform Resources. An important aspect of performing resource and remote actions is properly handling those operations, but those operations are not guaranteed to succeed every time. Some common examples include where network connections are unreliable, necessary permissions are not properly setup, incorrect Terraform configurations, or the remote system responds unexpectedly. All these situations lead to an unexpected workflow action that must be surfaced to the Terraform user interface for operators to troubleshoot. This guide is intended to explain and show various Terraform AWS Provider code implementations that are considered best practice for surfacing these issues properly to operators and code maintainers.

+

For further details about how the AWS SDK for Go v1 and the Terraform AWS Provider resource logic handle retryable errors, see the Retries and Waiters documentation.

+

General Guidelines and Helpers#

+

Naming and Check Style#

+

Following typical Go conventions, error variables in the Terraform AWS Provider codebase should be named err, e.g.

+
result, err := strconv.Itoa("oh no!")
+
+

The code that then checks these errors should prefer if conditionals that usually return (or in the case of looping constructs, break/continue) early, especially in the case of multiple error checks, e.g.

+
if /* ... something checking err first ... */ {
+    // ... return, break, continue, etc. ...
+}
+
+if err != nil {
+    // ... return, break, continue, etc. ...
+}
+
+// all good!
+
+

This is in preference of some other styles of error checking, such as switch conditionals without a condition.

+

Wrap Errors#

+

Go implements error wrapping, which means that a deeply nested function call can return a particular error type, while each function up the stack can provide additional error message context without losing the ability to determine the original error. Additional information about this concept can be found on the Go blog entry titled Working with Errors in Go 1.13.

+

For most use cases in this codebase, this means if code is receiving an error and needs to return it, it should implement fmt.Errorf() and the %w verb, e.g.

+
return fmt.Errorf("adding some additional message: %w", err)
+
+

This type of error wrapping should be applied to all Terraform resource logic. It should also be applied to any nested functions that contains two or more error conditions (e.g., a function that calls an update API and waits for the update to finish) so practitioners and code maintainers have a clear idea which generated the error. When returning errors in those situations, it is important to only include necessary additional context. Resource logic will typically include the information such as the type of operation and resource identifier (e.g., updating Service Thing (%s): %w), so these messages can be more terse such as waiting for completion: %w.

+

AWS SDK for Go v1 Errors#

+

The AWS SDK for Go v1 documentation includes a section on handling errors, which is recommended reading.

+

For the purposes of this documentation, the most important concepts with handling these errors are:

+
    +
  • Each response error (which eventually implements awserr.Error) has a string error code (Code) and string error message (Message). When printed as a string, they format as: Code: Message, e.g., InvalidParameterValueException: IAM Role arn:aws:iam::123456789012:role/XXX cannot be assumed by AWS Backup.
  • +
  • Error handling is almost exclusively done via those string fields and not other response information, such as HTTP Status Codes.
  • +
  • When the error code is non-specific, the error message should also be checked. Unfortunately, AWS APIs generally do not provide documentation or API modeling with the contents of these messages and often the Terraform AWS Provider code must rely on substring matching.
  • +
  • Not all errors are returned in the response error from an AWS API operation. This is service- and sometimes API-call-specific. For example, the EC2 DeleteVpcEndpoints API call can return a "successful" response (in terms of no response error) but include information in an Unsuccessful field in the response body.
  • +
+

When working with AWS SDK for Go v1 errors, it is preferred to use the helpers outlined below and use the %w format verb. Code should generally avoid type assertions with the underlying awserr.Error type or calling its Code(), Error(), Message(), or String() receiver methods. Using the %v, %#v, or %+v format verbs generally provides extraneous information that is not helpful to operators or code maintainers.

+

AWS SDK for Go Error Helpers#

+

To simplify operations with AWS SDK for Go error types, the following helpers are available via the github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr Go package:

+
    +
  • tfawserr.ErrCodeEquals(err, "Code"): Preferred when the error code is specific enough for the check condition. For example, a ResourceNotFoundError code provides enough information that the requested API resource identifier/Amazon Resource Name does not exist.
  • +
  • tfawserr.ErrMessageContains(err, "Code", "MessageContains"): Does simple substring matching for the error message.
  • +
+

The recommendation for error message checking is to be just specific enough to capture the anticipated issue, but not include too much matching as the AWS API can change over time without notice. The maintainers have observed changes in wording and capitalization cause unexpected issues in the past.

+

For example, given this error code and message:

+
InvalidParameterValueException: IAM Role arn:aws:iam::123456789012:role/XXX cannot be assumed by AWS Backup
+
+

An error check for this might be:

+
if tfawserr.ErrMessageContains(err, backup.ErrCodeInvalidParameterValueException, "cannot be assumed") { /* ... */ }
+
+

The Amazon Resource Name in the error message will be different for every environment and does not add value to the check. The AWS Backup suffix is also extraneous and could change should the service ever rename.

+

Use AWS SDK for Go v1 Error Code Constants#

+

Each AWS SDK for Go v1 service API typically implements common error codes, which get exported as public constants in the SDK. In the AWS SDK for Go v1 API Reference, these can be found in each of the service packages under the Constants section (typically named ErrCode{ExceptionName}).

+

If an AWS SDK for Go service API is missing an error code constant, an AWS Support case should be submitted and a new constant can be added to internal/service/{SERVICE}/errors.go file (created if not present), e.g.

+
const(
+    ErrCodeInvalidParameterException = "InvalidParameterException"
+)
+
+

Then referencing code can use it via:

+
// imports
+tf{SERVICE} "github.com/hashicorp/terraform-provider-aws/internal/service/{SERVICE}"
+
+// logic
+tfawserr.ErrCodeEquals(err, tf{SERVICE}.ErrCodeInvalidParameterException)
+
+

e.g.

+
// imports
+tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2"
+
+// logic
+tfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidParameterException)
+
+

Terraform Plugin SDK Types and Helpers#

+

The Terraform Plugin SDK includes some error types which are used in certain operations and typically preferred over implementing new types:

+ +

The Terraform AWS Provider codebase implements some additional helpers for working with these in the github.com/hashicorp/terraform-provider-aws/internal/tfresource package:

+
    +
  • tfresource.NotFound(err): Returns true if the error is a retry.NotFoundError.
  • +
  • tfresource.TimedOut(err): Returns true if the error is a retry.TimeoutError and contains no LastError. This typically signifies that the retry logic was never signaled for a retry, which can happen when AWS API operations are automatically retrying before returning.
  • +
+

Resource Lifecycle Guidelines#

+

Terraform CLI and the Terraform Plugin SDK have certain expectations and automatic behaviors depending on the lifecycle operation of a resource. This section highlights some common issues that can occur and their expected resolution.

+

Resource Creation#

+

Invoked in the resource via the schema.Resource type Create/CreateWithoutTimeout function.

+

d.IsNewResource() Checks#

+

During resource creation, Terraform CLI expects either a properly applied state for the new resource or an error. To signal proper resource existence, the Terraform Plugin SDK uses an underlying resource identifier (set via d.SetId(/* some value */)). If for some reason the resource creation is returned without an error, but also without the resource identifier being set, Terraform CLI will return an error such as:

+
Error: Provider produced inconsistent result after apply
+
+When applying changes to aws_sns_topic_subscription.sqs,
+provider "registry.terraform.io/hashicorp/aws" produced an unexpected new
+value: Root resource was present, but now absent.
+
+This is a bug in the provider, which should be reported in the provider's own
+issue tracker.
+
+

A typical pattern in resource implementations in the Create/CreateWithoutTimeout function is to return the Read/ReadWithoutTimeout function at the end to fill in the Terraform State for all attributes. Another typical pattern in resource implementations in the Read/ReadWithoutTimeout function is to remove the resource from the Terraform State if the remote system returns an error or status that indicates the remote resource no longer exists by explicitly calling d.SetId("") and returning no error. If the remote system is not strongly read-after-write consistent (eventually consistent), this means the resource creation can return no error and also return no resource state.

+

To prevent this type of Terraform CLI error, the resource implementation should also check against d.IsNewResource() before removing from the Terraform State and returning no error. If that check is true, then remote operation error (or one synthesized from the non-existent status) should be returned instead. While adding this check will not fix the resource implementation to handle the eventually consistent nature of the remote system, the error being returned will be less opaque for operators and code maintainers to troubleshoot.

+

In the Terraform AWS Provider, an initial fix for the Terraform CLI error will typically look like:

+
func resourceServiceThingCreate(d *schema.ResourceData, meta interface{}) error {
+    /* ... */
+
+    return resourceServiceThingRead(d, meta)
+}
+
+func resourceServiceThingRead(d *schema.ResourceData, meta interface{}) error {
+    /* ... */
+
+    output, err := conn.DescribeServiceThing(input)
+
+    if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, "ResourceNotFoundException") {
+        log.Printf("[WARN] {Service} {Thing} (%s) not found, removing from state", d.Id())
+        d.SetId("")
+        return nil
+    }
+
+    if err != nil {
+        return fmt.Errorf("reading {Service} {Thing} (%s): %w", d.Id(), err)
+    }
+
+    /* ... */
+}
+
+

If the remote system is not strongly read-after-write consistent, see the Retries and Waiters documentation on Resource Lifecycle Retries for how to prevent consistency-type errors.

+

Creation Error Message Context#

+

Returning errors during creation should include additional messaging about the location or cause of the error for operators and code maintainers by wrapping with fmt.Errorf():

+
if err != nil {
+    return fmt.Errorf("creating {SERVICE} {THING}: %w", err)
+}
+
+

e.g.

+
if err != nil {
+    return fmt.Errorf("creating EC2 VPC: %w", err)
+}
+
+

Code that also uses waiters or other operations that return errors should follow a similar pattern, including the resource identifier since it has typically been set before this execution:

+
if _, err := VpcAvailable(conn, d.Id()); err != nil {
+    return fmt.Errorf("waiting for EC2 VPC (%s) availability: %w", d.Id(), err)
+}
+
+

Resource Deletion#

+

Invoked in the resource via the schema.Resource type Delete/DeleteWithoutTimeout function.

+

Resource Already Deleted#

+

A typical pattern for resource deletion is to immediately perform the remote system deletion operation without checking existence. This is generally acceptable as operators are encouraged to always refresh their Terraform State prior to performing changes. However in certain scenarios, such as external systems modifying the remote system prior to the Terraform execution, it is certainly still possible that the remote system will return an error signifying that remote resource does not exist. In these cases, resources should implement logic that catches the error and returns no error.

+

NOTE: The Terraform Plugin SDK automatically handles the equivalent of d.SetId("") on deletion, so it is not necessary to include it.

+

For example in the Terraform AWS Provider:

+
func resourceServiceThingDelete(d *schema.ResourceData, meta interface{}) error {
+    /* ... */
+
+    output, err := conn.DeleteServiceThing(input)
+
+    if tfawserr.ErrCodeEquals(err, "ResourceNotFoundException") {
+        return nil
+    }
+
+    if err != nil {
+        return fmt.Errorf("deleting {Service} {Thing} (%s): %w", d.Id(), err)
+    }
+
+    /* ... */
+}
+
+

Deletion Error Message Context#

+

Returning errors during deletion should include the resource identifier and additional messaging about the location or cause of the error for operators and code maintainers by wrapping with fmt.Errorf():

+
if err != nil {
+    return fmt.Errorf("deleting {SERVICE} {THING} (%s): %w", d.Id(), err)
+}
+
+

e.g.

+
if err != nil {
+    return fmt.Errorf("deleting EC2 VPC (%s): %w", d.Id(), err)
+}
+
+

Code that also uses waiters or other operations that return errors should follow a similar pattern:

+
if _, err := VpcDeleted(conn, d.Id()); err != nil {
+    return fmt.Errorf("waiting for EC2 VPC (%s) deletion: %w", d.Id(), err)
+}
+
+

Resource Read#

+

Invoked in the resource via the schema.Resource type Read/ReadWithoutTimeout function.

+

Singular Data Source Errors#

+

A data source which is expected to return Terraform State about a single remote resource is commonly referred to as a "singular" data source. Implementation-wise, it may use any available describe or listing functionality from the remote system to retrieve the information. In addition to any remote operation and other data handling errors that should be returned, these two additional cases should be covered:

+
    +
  • Returning an error when zero results are found.
  • +
  • Returning an error when multiple results are found.
  • +
+

For remote operations that are designed to return an error when the remote resource is not found, this error is typically just passed through similar to other remote operation errors. For remote operations that are designed to return a successful result whether there is zero, one, or multiple multiple results the error must be generated.

+

For example in pseudo-code:

+
output, err := conn.ListServiceThings(input)
+
+if err != nil {
+    return fmt.Errorf("listing {Service} {Thing}s: %w", err)
+}
+
+if output == nil || len(output.Results) == 0 {
+    return fmt.Errorf("no {Service} {Thing} found matching criteria; try different search")
+}
+
+if len(output.Results) > 1 {
+    return fmt.Errorf("multiple {Service} {Thing} found matching criteria; try different search")
+}
+
+

Plural Data Source Errors#

+

An emergent concept is a data source that returns multiple results, acting similar to any available listing functionality available from the remote system. These types of data sources should return no error if zero results are returned and no error if multiple results are found. Remote operation and other data handling errors should still be returned.

+

Read Error Message Context#

+

Returning errors during read should include the resource identifier (for managed resources) and additional messaging about the location or cause of the error for operators and code maintainers by wrapping with fmt.Errorf():

+
if err != nil {
+    return fmt.Errorf("reading {SERVICE} {THING} (%s): %w", d.Id(), err)
+}
+
+

e.g.

+
if err != nil {
+    return fmt.Errorf("reading EC2 VPC (%s): %w", d.Id(), err)
+}
+
+

Resource Update#

+

Invoked in the resource via the schema.Resource type Update/UpdateWithoutTimeout function.

+

Update Error Message Context#

+

Returning errors during update should include the resource identifier and additional messaging about the location or cause of the error for operators and code maintainers by wrapping with fmt.Errorf():

+
if err != nil {
+    return fmt.Errorf("updating {SERVICE} {THING} (%s): %w", d.Id(), err)
+}
+
+

e.g.

+
if err != nil {
+    return fmt.Errorf("updating EC2 VPC (%s): %w", d.Id(), err)
+}
+
+

Code that also uses waiters or other operations that return errors should follow a similar pattern:

+
if _, err := VpcAvailable(conn, d.Id()); err != nil {
+    return fmt.Errorf("waiting for EC2 VPC (%s) update: %w", d.Id(), err)
+}
+
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/faq/index.html b/faq/index.html new file mode 100644 index 000000000000..b4fbfd18432f --- /dev/null +++ b/faq/index.html @@ -0,0 +1,1053 @@ + + + + + + + + + + + + + + + + + + + + + + FAQ - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Frequently Asked Questions#

+ + +

Who are the maintainers?#

+

The HashiCorp Terraform AWS provider team is :

+ +

Why isn’t my PR merged yet?#

+

Unfortunately, due to the volume of issues and new pull requests we receive, we are unable to give each one the full attention that we would like. We always focus on the contributions that provide the greatest value to the most community members. For more information on how we prioritize pull requests, see the prioritization guide.

+

How do you decide what gets merged for each release?#

+

We have a large backlog of pull requests to get through and the team are moving through them as quick as we can. All pull requests must be reviewed by a HashiCorp engineer before inclusion. This is to ensure that the design of the addition fits with what provider users have come to expect, and to ensure that testing and best practices are adhered to. This is particularly important for such a large codebase, to ensure that we sustain its maintainability as its grows.

+

The number one factor we look at when deciding what issues to look at are your 👍 reactions to the original issue/PR description as these can be easily discovered. Comments that further explain desired use cases or poor user experience are also heavily factored. The items with the most support are always on our radar, and we commit to keep the community updated on their status and potential timelines.

+

We publish a roadmap every quarter which describes major themes or specific product areas of focus. What is excluded from the public roadmap is work performed under NDA with AWS on new services, and any ad-hoc work we pick up during the quarter. This ad-hoc work can be responding to bugs, gardening day activity, customer prioritization, and technical debt items.

+

We also are investing time to improve the contributing experience by improving documentation, adding more linter coverage to ensure that incoming PR's can be in as good shape as possible. This will allow us to get through them quicker.

+

My PR hasn't been merged and it now has merge conflicts/failed checks, should I keep it up to date?#

+

We realize that sometimes pull requests sit for a considerable amount of time without being addressed. During this time period they may accumulate merge conflicts and failed linter checks as the provider codebase moves forward. As maintainers we have no expectation that you keep your PR up to date, these issues will be addressed at review time most often by the maintainers themselves. Obviously we would hope that your PR is mergeable when first raised! The mergeability of the PR does not affect its prioritization for review.

+

How often do you release?#

+

We release weekly on Thursday. We release often to ensure we can bring value to the community at a frequent cadence and to ensure we are in a good place to react to AWS region launches and service announcements.

+

Backward Compatibility Promise#

+

Our policy is described on the Terraform website here. While we do our best to prevent breaking changes until major version releases of the provider, it is generally recommended to pin the provider version in your configuration.

+

Due to the constant release pace of AWS and the relatively infrequent major version releases of the provider, there can be cases where a minor version update may contain unexpected changes depending on your configuration or environment. These may include items such as a resource requiring additional IAM permissions to support newer functionality. We typically base these decisions on a pragmatic compromise between introducing a relatively minor one-time inconvenience for a subset of the community versus better overall user experience for the entire community.

+

Once a major release is published, will new features and fixes be backported to previous versions?#

+

Generally new features and fixes will only be added to the most recent major version. Due to the high touch nature of provider development and the extensive regression testing required to ensure stability, maintaining multiple versions of the provider is not sustainable at this time. An exception to this could be a discovered security vulnerability for which backporting may be the most reasonable course of action. These would be reviewed on a case by case basis.

+

AWS just announced a new region, when will I see it in the provider.#

+

Normally pretty quickly. We usually see the region appear within the aws-go-sdk within a couple days of the announcement. Depending on when it lands, we can often get it out within the current or following weekly release. Comparatively, adding support for a new region in the S3 backend can take a little longer, as it is shipped as part of Terraform Core and not via the AWS Provider.

+

Please note that this new region requires a manual process to enable in your account. Once enabled in the console, it takes a few minutes for everything to work properly.

+

If the region is not enabled properly, or the enablement process is still in progress, you may receive errors like these:

+
$ terraform apply
+
+Error: error validating provider credentials: error calling sts:GetCallerIdentity: InvalidClientTokenId: The security token included in the request is invalid.
+    status code: 403, request id: 142f947b-b2c3-11e9-9959-c11ab17bcc63
+
+  on main.tf line 1, in provider "aws":
+   1: provider "aws" {
+
+

To use this new region before support has been added to the Terraform AWS Provider, you can disable the provider's automatic region validation via:

+
provider "aws" {
+  # ... potentially other configuration ...
+
+  region                 = "af-south-1"
+  skip_region_validation = true
+}
+
+

How can I help?#

+

Great question, if you have contributed before check out issues with the help-wanted label. These are normally enhancement issues that will have a great impact, but the maintainers are unable to develop them in the near future. If you are just getting started, take a look at issues with the good-first-issue label. Items with these labels will always be given priority for response.

+

Check out the Contributing Guide for additional information.

+

How can I become a maintainer?#

+

This is an area under active research. Stay tuned!

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/images/breakpoints.png b/images/breakpoints.png new file mode 100644 index 0000000000000000000000000000000000000000..ddc5f3e7cc1d823eea7e1d83ca081394fec00fb8 GIT binary patch literal 109768 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

Igg?VnpFq6>J+vCXxn?Km0% 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 bojbg*-%E$RU?vxxJ#oSvr!E zxpp~4Z;d)u)Vhgt)Z7v$x=lOkp|KnaBE*fBc zFbD+FApc0B0NN4|>kaFG=lI-HTM$SkgiR!Rk%>f@7oQi&j$wj8$bz_hPvp@`I$hhx;zXGAiXrYiV=Pw&7~pL(P%?@qKcK>?Vq4WxSyH z_l%jukTx-HW;$tM7N@+0R}4Nr>3Z|0{a3^8$#y7ZgN1by8%>f$hlZY?XaPI_0tpz1 zw(*ffr9%Y_zo9gqcA zrE9-xvh#goT)H`qu-tPx&wZ0}L&DaYg~nK?9?4wAg(I=MTDeJ)^-m8M_Rf`ca>odc zJLTA%JT$~=*H#zGqQG+Ykfpn(6Xc)Pl*^R-w`Y;r&DZBoL0pbPKFZEqXRjR(U0w<& zJ}{I;=#-t$EY9CvHo5OzGSQ$XYnHS3SL%(6DJk9Faj*SU1IzI~iUn2xim0Ha1U(TAp3=)wW&}mfW;Hu7_MjlsGhN@{z8BE{q_tV$09s} zM`v1yII$}b5Kd$dm>i}s946w#a0T`vJp5CHJz%eFqu{VlA;M@p+~3^`M&$9CFdGZB z1sYBO!(cERKO)keLUR5B2j1{-mQWaLk3uCRBv>R^TJZP>Q5ZWrI}{p=!eY&V2y?+d zxWaIeIagr(ndHAbB&LADXU7WJJT7d7H=NFk6XN0Ul|*0H=kH{S*xxd71z*Yn3Pi1V zP#6m|>MJ(@#jWhxd$C2#m;e%+1GERoL9n*OeG30S$G3#5fc~q1nAN~lj{gPv3YdH% zj{_tW628sl3wX8j3lN7|se4r)J~#Q(F3@EH7>D{gF#_1Or_&GwTB{_J9H=a{iGToP zZ76hEkBHrg^Ge_ET?KugR&*@=IyqC<#~TZkL-}1;lfOurgYkGR?_md1C^=%ocM(6o zIs8KEnf0%?DYpa<6DAb_Grh|B+2-!0i7VYao@;JyE^5w&G{3#!=;;ddh^LcJs#sOe z?($FcQh9g*;*4XenDk&*mIpXuxV}T%eTN`odU`s)v@~aGDi&#KO0F!bETB>jV2iE=a2z)YA7yGgwm%4Vd)w^sv}itu0%&jQ;tjV@QZm^OY+X14rQ1 zn97N}ch&w~nx4KkhQ~9;Vzt}a+NNe^7C(pwtbUeA4nm}@t%eH=3)Qu?PQk&3VzK!8 zE=oz7vGJvW#YMcnfdK>pNxgg5X>M*VjKw-ufyhBy_%Zgj+1S`<85ra=He!o&I`VGZ zycy1B%dHElKbqgmuX^<8(dfhkQ-#jwYkPQjm<^t*(mX zJvL^ly?HYRjRyJo`ALuWyyg){S~G`+hSU+*_RdcA^73*d5-C!SKUJoPF_o`hzeZ%| zE+e?5W~#ed|+WAux~!}4~S{k{rhw~R&ugoMn*==9q^r)!pj~|mnB2oY3q^5?3hIkf7AY?yXbT`mpFotBdp-XSFN=kHrB6~u1NoO_( zc#+`o`^g3T42fiWaY;!-Q&Su30YI5@H77e8JUBGu?dz*15C}TEx>9A>3^w6_8#OrO zp4MAVbo9|{*KAoSA3l6w(CMle4CYjM`Q2+gWqo6#>hD+@jfO^}!)lfUcTN@dwQFi> zvZA6K!@^*Kg(puaR2pq?d|Vt2_m)SOLe%D$7Qf$AF>JCEkH_<9{KUjF5LW{O1BE~+ zY`>~;<@vK`q`*M^TS@b>d_z6GJ1^=&^`1~swNsj)_KHh2HNCWRa&}H@OUt5eo>XdK zWo0!rJ>A}cc&w}K>FL>g^(vjm`woFXsJqj4{PIIfeubTr&V?zrn#B+!BcrpIF5NeB z%-qb6dfzlM6503a6<@-ny~o~DD72iiE!c%8uz5S&@-r$->!Vxw(;cBf#>R zCS?}M_taIAEh!>%6zX6>b|HL6eSQ6HZKDi^;|c!8hTwDG7vYY-%5io$&04wxYk9Bk zt!sZUU+d<1%On;H`N7@8Lsth{IHrm=FsQp;p{Hk)1q9Qpd2g{{Ri6$`Jqn literal 0 HcmV?d00001 diff --git a/images/debug-controls.png b/images/debug-controls.png new file mode 100644 index 0000000000000000000000000000000000000000..f5e160d8de289dfd76de44847436bd99c3c5fc8d GIT binary patch literal 47101 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 z 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 literal 0 HcmV?d00001 diff --git a/images/launch-json.png b/images/launch-json.png new file mode 100644 index 0000000000000000000000000000000000000000..fda099e18ec3d152d22dc357167ed553bcfd35ed GIT binary patch literal 28571 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 000000000000..f90370a73441 --- /dev/null +++ b/index.html @@ -0,0 +1,1063 @@ + + + + + + + + + + + + + + + + + + + + + + Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+ +
+
+ + + +
+
+ + + + + + + + +

Welcome#

+

The Terraform AWS Provider is the work of thousands of contributors, and is maintained by a small team within HashiCorp. This site contains extensive instructions about how to contribute and how the AWS provider works.

+

Please Note: This documentation is intended for Terraform AWS Provider code developers. Typical operators writing and applying Terraform configurations do not need to read or understand this material.

+

Contribute#

+

Please follow the following steps to ensure your contribution goes smoothly.

+

1. Configure Development Environment#

+

Install Terraform and Go. Clone the repository, compile the provider, and set up testing. Refer to Configure Development Environment.

+

2. Debug Code#

+

If you are looking to create or enhance code, such as a new resource or adding an argument to an existing resource, skip to the next step.

+

Finding and fixing errors in the AWS Provider can be difficult. We have a debugging guide to help you get started.

+

3. Change Code#

+

Follow the guide for your contribution type and refer to the Development Reference materials as needed for additional details about provider design, expected naming conventions, guidance for error handling, etc.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Contribution GuideDescription
Small ChangesRequirements for small additions or bug-fixes on existing resources/data sources
ResourcesAllow the management of a logical resource within AWS by adding a new resource to the Terraform AWS Provider.
Data SourceLet your Terraform configurations use data from resources not under local management by creating ready only data sources.
ServicesAllow Terraform (via the AWS Provider) to manage an entirely new AWS service by introducing the resources and data sources required to manage configuration of the service.
AWS RegionNew regions are immediately usable with the provider with the caveat that a configuration workaround is required to skip validation of the region during cli operations. A small set of changes are required to makes this workaround necessary.
Resource Name GenerationAllow a resource to either fully, or partially, generate its own resource names. This can be useful in cases where the resource name uniquely identifes the resource and it needs to be recreated. It can also be used when a name is required, but the specific name is not important.
Tagging SupportMany AWS resources allow assigning metadata via tags. However, frequently AWS services are launched without tagging support so this will often need to be added later.
Import SupportAdding import support allows terraform import to be run targeting an existing unmanaged resource and pulling its configuration into Terraform state. Typically import support is added during initial resource implementation but in some cases this will need to be added later.
Documentation ChangesThe provider documentation is displayed on the Terraform Registry and is sourced and refreshed from the provider repository during the release process.
+

4. Write Tests#

+

We require changes to be covered by acceptance tests for all contributions. If you are unable to pay for acceptance tests for your contributions, mention this in your pull request. We will happily accept "best effort" acceptance tests implementations and run them for you on our side. Your PR may take longer to merge, but this is not a blocker for contributions.

+

5. Update the Changelog#

+

HashiCorp's open-source projects have always maintained a user-friendly, readable CHANGELOG.md that allows users to tell at a glance whether a release should have any effect on them, and to gauge the risk of an upgrade. Not all changes require an entry in the changelog, refer to our Changelog Process for details about when and how to create a changelog.

+

6. Create a Pull Request#

+

When your contribution is ready, Create a Pull Request in the AWS provider repository.

+

Pull requests are usually triaged within a few days of creation and are prioritized based on community reactions. Our Prioritization Guides provides more details about the process.

+

Submit an Issue#

+

In addition to contributions, we welcome bug reports and feature requests.

+

Join the Contributors Slack#

+

For frequent contributors, it's useful to join the contributors Slack channel hosted within the HashiCorp Slack workspace. This Slack channel is used to discuss topics such as general contribution questions, suggestions for improving the contribution process, coordinating on pair programming sessions, etc. The channel is not intended as a place to request status updates on open issues or pull requests. For prioritization questions, instead refer to the prioritization guide.

+

To request to join, fill out the request form and allow time for the request to be reviewed and processed.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/issue-reporting-and-lifecycle/index.html b/issue-reporting-and-lifecycle/index.html new file mode 100644 index 000000000000..c0950617e148 --- /dev/null +++ b/issue-reporting-and-lifecycle/index.html @@ -0,0 +1,1023 @@ + + + + + + + + + + + + + + + + + + + + + + + + Submit an Issue - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Issue Reporting and Lifecycle#

+

Issue Reporting Checklists#

+

We welcome issues of all kinds including feature requests, bug reports, and +general questions. Below you'll find checklists with guidelines for well-formed +issues of each type.

+

We encourage opening new issues rather than commenting on closed issues if a problem has not been completely solved or causes a regression. This ensures we are able to triage it effectively.

+

Bug Reports#

+
    +
  • +

    Test against latest release: Make sure you test against the latest + released version. It is possible we already fixed the bug you're experiencing.

    +
  • +
  • +

    Search for possible duplicate reports: It's helpful to keep bug + reports consolidated to one thread, so do a quick search on existing bug + reports to check if anybody else has reported the same thing. You can scope + searches by the label "bug" to help narrow things down.

    +
  • +
  • +

    Include steps to reproduce: Provide steps to reproduce the issue, + along with your .tf files, with secrets removed, so we can try to + reproduce it. Without this, it makes it much harder to fix the issue.

    +
  • +
  • +

    For panics, include crash.log: If you experienced a panic, please + create a gist of the entire generated crash log + for us to look at. Double check no sensitive items were in the log.

    +
  • +
+

Feature Requests#

+
    +
  • +

    Search for possible duplicate requests: It's helpful to keep requests + consolidated to one thread, so do a quick search on existing requests to + check if anybody else has reported the same thing. You can scope searches by + the label "enhancement" to help narrow things down.

    +
  • +
  • +

    Include a use case description: In addition to describing the + behavior of the feature you'd like to see added, it's helpful to also lay + out the reason why the feature would be important and how it would benefit + Terraform users.

    +
  • +
+

Questions#

+
    +
  • Search for answers in Terraform documentation: We're happy to answer + questions in GitHub Issues, but it helps reduce issue churn and maintainer + workload if you work to find answers to common questions in the + documentation. Oftentimes Question issues result in documentation updates + to help future users, so if you don't find an answer, you can give us + pointers for where you'd expect to see it in the docs.
  • +
+

Issue Lifecycle#

+

Note: For detailed information on how issues are prioritized, see the prioritization guide.

+
    +
  1. +

    The issue is reported.

    +
  2. +
  3. +

    The issue is verified and categorized by a Terraform collaborator. + Categorization is done via GitHub labels. We generally use a two-label + system of (1) issue/PR type, and (2) section of the codebase. Type is + one of "bug", "enhancement", "documentation", or "question", and section + is usually the AWS service name.

    +
  4. +
  5. +

    An initial triage process determines whether the issue is critical and must + be addressed immediately, or can be left open for community discussion.

    +
  6. +
  7. +

    The issue is addressed in a pull request or commit. The issue number will be + referenced in the commit message so that the code that fixes it is clearly + linked.

    +
  8. +
  9. +

    The issue is closed. Sometimes, valid issues will be closed because they are + tracked elsewhere or non-actionable. The issue is still indexed and + available for future viewers, or can be re-opened if necessary.

    +
  10. +
  11. +

    30 days after the issue has been closed it is locked, preventing further comments.

    +
  12. +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/naming/index.html b/naming/index.html new file mode 100644 index 000000000000..3d0e30c983e4 --- /dev/null +++ b/naming/index.html @@ -0,0 +1,1475 @@ + + + + + + + + + + + + + + + + + + + + + + + + Naming Standards - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Naming Conventions for the AWS Provider#

+

Service Identifier#

+

In the AWS Provider, a service identifier should consistently identify an AWS service from code to documentation to provider use by a practitioner. Prominent places you will see service identifiers:

+
    +
  • The package name (e.g., internal/service/<serviceidentifier>)
  • +
  • In resource and data source names (e.g., aws_<serviceidentifier>_thing)
  • +
  • Documentation file names (e.g., website/docs/r/<serviceidentifier>_thing)
  • +
+

Typically, choosing the AWS Provider identifier for a service is simple. AWS consistently uses one name and we use that name as the identifier. However, some services are not simple. To provide consistency, and to help contributors and practitioners know what to expect, we provide this rule for defining a service identifier:

+

Rule#

+
    +
  1. Determine the service package name for AWS Go SDK v2.
  2. +
  3. Determine the AWS CLI v2 command corresponding to the service (i.e., the word following aws in CLI commands; e.g., for aws sts get-caller-identity, sts is the command, get-caller-identity is the subcommand).
  4. +
  5. If the SDK and CLI agree, use that. If the service only exists in one, use that.
  6. +
  7. If they differ, use the shorter of the two.
  8. +
  9. Use lowercase letters and do not include any underscores (_).
  10. +
+

How Well Is It Followed?#

+

With 156+ services having some level of implementation, the following is a summary of how well this rule is currently followed.

+

For AWS provider service package names, only five packages violate this rule: appautoscaling should be applicationautoscaling, codedeploy should be deploy, elasticsearch should be es, cloudwatchlogs should be logs, and simpledb should be sdb.

+

For the service identifiers used in resource and data source configuration names (e.g., aws_acmpca_certificate_authority), 32 wholly or partially violate the rule.

+
    +
  • EC2, ELB, ELBv2, and RDS have legacy but heavily used resources and data sources that do not or inconsistently use service identifiers.
  • +
  • The remaining 28 services violate the rule in a consistent way: appautoscaling should be applicationautoscaling, codedeploy should be deploy, elasticsearch should be es, cloudwatch_log should be logs, simpledb should be sdb, prometheus should be amp, api_gateway should be apigateway, cloudcontrolapi should be cloudcontrol, cognito_identity should be cognitoidentity, cognito should be cognitoidp, config should be configservice, dx should be directconnect, directory_service should be ds, elastic_beanstalk should be elasticbeanstalk, cloudwatch_event should be events, kinesis_firehose should be firehose, msk should be kafka, mskconnect should be kafkaconnect, kinesis_analytics should be kinesisanalytics, kinesis_video should be kinesisvideo, lex should be lexmodels, media_convert should be mediaconvert, media_package should be mediapackage, media_store should be mediastore, route53_resolver should be route53resolver, relevant s3 should be s3control, serverlessapplicationrepository should be serverlessrepo, and service_discovery should be servicediscovery.
  • +
+

Packages#

+

Package names are not seen or used by practitioners. However, they should still be carefully considered.

+

Rule#

+
    +
  1. For service packages (i.e., packages under internal/service), use the AWS Provider service identifier as the package name.
  2. +
  3. For other packages, use a short name for the package. Common Go lengths are 3-9 characters.
  4. +
  5. Use a descriptive name. The name should capture the key purpose of the package.
  6. +
  7. Use lowercase letters and do not include any underscores (_).
  8. +
  9. Avoid useless names like helper. These names convey zero information. Everything in the AWS Provider is helping something or someone do something so the name helper doesn't narrow down the purpose of the package within the codebase.
  10. +
  11. Use a name that is not too narrow or too broad as Go packages should not be too big or too small. Tiny packages can be combined using a broader name encompassing both. For example, verify is a good name because it tells you what the package does and allows a broad set of validation, comparison, and checking functionality.
  12. +
+

Resources and Data Sources#

+

When creating a new resource or data source, it is important to get names right. Once practitioners rely on names, we can only change them through breaking changes. If you are unsure about what to call a resource or data source, discuss it with the community and maintainers.

+

Rule#

+
    +
  1. Follow the AWS SDK for Go v2. Almost always, the API operations make determining the name simple. For example, the Amazon CloudWatch Evidently service includes CreateExperiment, GetExperiment, UpdateExperiment, and DeleteExperiment. Thus, the resource (or data source) name is "Experiment."
  2. +
  3. Give a resource its Terraform configuration (i.e., HCL) name (e.g., aws_imagebuilder_image_pipeline) by joining these three parts with underscores:
      +
    • aws prefix
    • +
    • Service identifier (service identifiers do not include underscores), all lower case (e.g., imagebuilder)
    • +
    • Resource (or data source) name in snake case (spaces replaced with underscores, if any), all lower case (e.g., image_pipeline)
    • +
    +
  4. +
  5. Name the main resource function Resource<ResourceName>(), with the resource name in MixedCaps. Do not include the service name or identifier. For example, define ResourceImagePipeline() in a file called internal/service/imagebuilder/image_pipeline.go.
  6. +
  7. Similarly, name the main data source function DataSource<ResourceName>(), with the data source name in MixedCaps. Do not include the service name or identifier. For example, define DataSourceImagePipeline() in a file called internal/service/imagebuilder/image_pipeline_data_source.go.
  8. +
+

Files#

+

File names should follow Go and Markdown conventions with these additional points.

+

Resource and Data Source Documentation Rule#

+
    +
  1. Resource markdown goes in the website/docs/r directory. Data source markdown goes in the website/docs/d directory.
  2. +
  3. Use the service identifier and resource or data source name, separated by an underscore (_).
  4. +
  5. All letters are lowercase.
  6. +
  7. Use .html.markdown as the extension.
  8. +
  9. Do not include "aws" in the name.
  10. +
+

A correct example is accessanalyzer_analyzer.html.markdown. An incorrect example is service_discovery_instance.html.markdown because the service identifier should not include an underscore.

+

Go File Rule#

+
    +
  1. Resource and data source files are in the internal/service/<service> directory.
  2. +
  3. Do not include the service as part of the file name.
  4. +
  5. Data sources should include _data_source after the data source name (e.g., application_data_source.go).
  6. +
  7. Put unit and acceptance tests in a file ending with _test.go (e.g., custom_domain_association_test.go).
  8. +
  9. Use snake case for multiword names (i.e., all letters are lowercase, words separated by underscores).
  10. +
  11. Use the .go extension.
  12. +
  13. Idiomatic names for common non-resource, non-data-source files include consts.go (service-wide constants), find.go (finders), flex.go (FLatteners and EXpanders), generate.go (directives for code generation), id.go (ID creators and parsers), status.go (status functions), sweep.go (sweepers), tags_gen.go (generated tag code), validate.go (validators), and wait.go (waiters).
  14. +
+

MixedCaps#

+

Write multiword names in Go using MixedCaps (or mixedCaps) rather than underscores.

+

For more details on capitalizations we enforce with CI Semgrep tests, see the Caps List.

+

Initialisms and other abbreviations are a key difference between many camel/Pascal case interpretations and mixedCaps. Abbreviations in mixedCaps should be the correct, human-readable case. After all, names in code are for humans. (The mixedCaps convention aligns with HashiCorp's emphasis on pragmatism and beauty.)

+

For example, an initialism such as "VPC" should either be all capitalized ("VPC") or all lower case ("vpc"), never "Vpc" or "vPC." Similarly, in mixedCaps, "DynamoDB" should either be "DynamoDB" or "dynamoDB", depending on whether an initial cap is needed or not, and never "dynamoDb" or "DynamoDb."

+

Rule#

+
    +
  1. Use mixedCaps for function, type, method, variable, and constant names in the Terraform AWS Provider Go code.
  2. +
+

Functions#

+

In general, follow Go best practices for good function naming. This rule is for functions defined outside of the test context (i.e., not in a file ending with _test.go). For test functions, see Test Support Functions or Acceptance Test Configurations below.

+

Rule#

+
    +
  1. Only export functions (capitalize) when necessary, i.e., when the function is used outside the current package, including in the _test (.test) package.
  2. +
  3. Use MixedCaps (exported) or mixedCaps (not exported). Do not use underscores for multiwords.
  4. +
  5. Do not include the service name in the function name. (If functions are used outside the current package, the import package clarifies a function's origin. For example, the EC2 function FindVPCEndpointByID() is used outside the internal/service/ec2 package but where it is used, the call is tfec2.FindVPCEndpointByID().)
  6. +
  7. For CRUD functions for resources, use this format: resource<ResourceName><CRUDFunction>. For example, resourceImageRecipeUpdate(), resourceBaiduChannelRead().
  8. +
  9. For data sources, for Read functions, use this format: dataSource<DataSourceName>Read. For example, dataSourceBrokerRead(), dataSourceEngineVersionRead().
  10. +
  11. To improve readability, consider including the resource name in helper function names that pertain only to that resource. For example, for an expander function for an "App" resource and a "Campaign Hook" expander, use expandAppCampaignHook().
  12. +
  13. Do not include "AWS" or "Aws" in the name.
  14. +
+

Variables and Constants#

+

In general, follow Go best practices for good variable and constant naming.

+

Rule#

+
    +
  1. Only export variables and constants (capitalize) when necessary, i.e., the variable or constant is used outside the current package, including in the _test (.test) package.
  2. +
  3. Use MixedCaps (exported) or mixedCaps (not exported). Do not use underscores for multiwords.
  4. +
  5. Do not include the service name in variable or constant names. (If variables or constants are used outside the current package, the import package clarifies its origin. For example, IAM's PropagationTimeout is widely used outside of IAM but each instance is through the package import alias, tfiam.PropagationTimeout. "IAM" is unnecessary in the constant name.)
  6. +
  7. To improve readability, consider including the resource name in variable and constant names that pertain only to that resource. For example, for a string constant for a "Role" resource and a "not found" status, use roleStatusNotFound or RoleStatusNotFound, if used outside the service's package.
  8. +
  9. Do not include "AWS" or "Aws" in the name.
  10. +
+

NOTE: Give priority to using constants from the AWS SDK for Go rather than defining new constants for the same values.

+

Acceptance and Unit Tests#

+

With about 6000 acceptance and unit tests, following these naming conventions is essential to organization and (human) context switching between services.

+

There are three types of tests in the AWS Provider: (regular) acceptance tests, serialized acceptance tests, and unit tests. All are functions that take a variable of type *testing.T. Acceptance tests and unit tests have exported (i.e., capitalized) names while serialized tests do not. Serialized tests are called by another exported acceptance test, often ending with _serial. The majority of tests in the AWS provider are acceptance tests.

+

Acceptance Test Rule#

+

Acceptance test names have a minimum of two (e.g., TestAccBackupPlan_tags) or a maximum of three (e.g., TestAccDynamoDBTable_Replica_multiple) parts, joined with underscores:

+
    +
  1. First part: All have a prefix (i.e., TestAcc), service name (e.g., Backup, DynamoDB), and resource name (e.g., Plan, Table), MixedCaps without underscores between. Do not include "AWS" or "Aws" in the name.
  2. +
  3. Middle part (Optional): Test group (e.g., Replica), uppercase, MixedCaps. Consider a metaphor where tests are chapters in a book. If it is helpful, tests can be grouped together like chapters in a book that are sometimes grouped into parts or sections of the book.
  4. +
  5. Last part: Test identifier (e.g., basic, tags, or multiple), lowercase, mixedCaps). The identifier should make the test's purpose clear but be concise. For example, the identifier conflictsWithCloudFrontDefaultCertificate (41 characters) conveys no more information than conflictDefaultCertificate (26 characters), since "CloudFront" is implied and "with" is always implicit. Avoid words that convey no meaning or whose meaning is implied. For example, "with" (e.g., _withTags) is not needed because we imply the name is telling us what the test is with. withTags can be simplified to tags.
  6. +
+

Serialized Acceptance Test Rule#

+

The names of serialized acceptance tests follow the regular acceptance test name rule except serialized acceptance test names:

+
    +
  1. Start with testAcc instead of TestAcc
  2. +
  3. Do not include the name of the service (e.g., a serialized acceptance test would be called testAccApp_basic not testAccAmplifyApp_basic).
  4. +
+

Unit Test Rule#

+

Unit test names follow the same rule as acceptance test names except unit test names:

+
    +
  1. Start with Test, not TestAcc
  2. +
  3. Do not include the name of the service
  4. +
  5. Usually do not have any underscores
  6. +
  7. If they test a function, should include the function name (e.g., a unit test of ExpandListener() should be called TestExpandListener())
  8. +
+

Test Support Functions#

+

This rule is for functions defined in the test context (i.e., in a file ending with _test.go) that do not return a string with Terraform configuration. For non-test functions, see Functions above. Or, see Acceptance Test Configurations below.

+

Rule#

+
    +
  1. Only export functions (capitalize) when necessary, i.e., when the function is used outside the current package. This is very rare.
  2. +
  3. Use MixedCaps (exported) or mixedCaps (not exported). Do not use underscores for multiwords.
  4. +
  5. Do not include the service name in the function name. For example, testAccCheckAMPWorkspaceExists() should be named testAccCheckWorkspaceExists() instead, dropping the service name.
  6. +
  7. Several types of support functions occur commonly and should follow these patterns:
      +
    • Destroy: testAccCheck<Resource>Destroy
    • +
    • Disappears: testAccCheck<Resource>Disappears
    • +
    • Exists: testAccCheck<Resource>Exists
    • +
    • Not Recreated: testAccCheck<Resource>NotRecreated
    • +
    • PreCheck: testAccPreCheck (often, only one PreCheck is needed per service so no resource name is needed)
    • +
    • Recreated: testAccCheck<Resource>Recreated
    • +
    +
  8. +
  9. Do not include "AWS" or "Aws" in the name.
  10. +
+

Acceptance Test Configurations#

+

This rule is for functions defined in the test context (i.e., in a file ending with _test.go) that return a string with Terraform configuration. For test support functions, see Test Support Functions above. Or, for non-test functions, see Functions above.

+

NOTE: This rule is not widely used currently. However, new functions and functions you change should follow it.

+

Rule#

+
    +
  1. Only export functions (capitalize) when necessary, i.e., when the function is used outside the current package. This is very rare.
  2. +
  3. Use MixedCaps (exported) or mixedCaps (not exported). Do not use underscores for multiwords.
  4. +
  5. Do not include the service name in the function name.
  6. +
  7. Follow this pattern: testAccConfig<Resource>_<TestGroup>_<configDescription>
      +
    • _<TestGroup> is optional. Refer to the Acceptance Test Rule test group discussion.
    • +
    • Especially when an acceptance test only uses one configuration, the <configDescription> should be the same as the test identifier discussed in the Acceptance Test Rule.
    • +
    +
  8. +
  9. Do not include "AWS" or "Aws" in the name.
  10. +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/prioritization/index.html b/prioritization/index.html new file mode 100644 index 000000000000..ae255a86d27c --- /dev/null +++ b/prioritization/index.html @@ -0,0 +1,1083 @@ + + + + + + + + + + + + + + + + + + + + + + + + How We Prioritize - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

How We Prioritize#

+

Intro#

+

What this document is#

+

This document describes how we handle prioritization of work from a variety of input sources. Our focus is always to deliver tangible value to the practitioner on a predictable and frequent schedule, and we feel it is important to be transparent in how we weigh input in order to deliver on this goal.

+

What this document is not#

+

Due to the variety of input sources, the scale of the provider, and resource constraints, it is impossible to give a hard number on how each of the factors outlined in this document are weighted. Instead, the goal of the document is to give a transparent, but generalized assessment of each of the sources of input so that the community has a better idea of why things are prioritized the way they are. Additional information may be found in the FAQ.

+

Prioritization#

+

We prioritze work based on a number of factors, including community feedback, issue/PR reactions, as well as the source of the request. While community feedback is heavily weighted, there are times where other factors take precedence. By their nature, some factors are less visible to the community, and so are outlined here as a way to be as transparent as possible. Each of the sources of input are detailed below.

+

Community#

+

Our large community of practitioners are vocal and immensely productive in contributing to the provider codebase. Unfortunately our current team capacity means that we are unable to give every issue or pull request the same level of attention. This means we need to prioritize the issues that provide the most value to the greatest number of practitioners.

+

We will always focus on the issues which have the most attention. The main rubric we have for assessing community wants is GitHub reactions. In addition to reactions, we look at comments, reactions to comments, and links to additional issues and PRs to help get a more holistic view of where the community stands. We try to ensure that for the issues where we have the most community support, we are responsive to that support and attempt to give timelines where-ever possible.

+

Customer#

+

Another source of work that must be weighted are escalations around particular feature requests and bugs from HashiCorp and AWS customers. Escalations typically come via several routes:

+
    +
  • Customer Support
  • +
  • Sales Engineering
  • +
  • AWS Solutions Architects contacting us on behalf of their clients.
  • +
+

These reports flow into an internal board and are triaged on a weekly basis to determine whether the escalation request should be prioritized for an upcoming release or added to the backlog to monitor for additional community support. During triage, we verify whether a GitHub issue or PR exists for the request and will create one if it does not exist. In this way, these requests are visible to the community to some degree. An escalation coming from a customer does not necessarily guarantee that it will be prioritized over requests made by the community. Instead, we assess them based on the following rubric:

+
    +
  • Does the issue have considerable community support?
  • +
  • Does the issue pertain to one of our Core Services?
  • +
+

By weighing these factors, we can make a call to determine whether, and how it is to be prioritized.

+

Partner#

+

AWS Service Teams and Partner representatives regularly contact us to discuss upcoming features or new services. This work is often done under an NDA, so usually needs to be done in private. Often the ask is to enable Terraform support or an upcoming feature or service.

+

As with customer escalations, a request from a partner does not necessarily mean that it will be prioritized over other efforts; capacity restraints require us to prioritize major releases or prefer offerings in line with our core services.

+

Internal#

+

SDK/Core Updates#

+

We endeavor to keep in step with all minor SDK releases, so these are automatically pulled in by our GitHub automation. Major releases normally include breaking changes and usually require us to bump the provider itself to a major version. We plan to make one major version change a year and try to avoid any more than that.

+

Technical Debt#

+

We always include capacity for technical debt work in every iteration, but engineers are free to include minor tech debt work on their own recognizance. For larger items, these are discussed and prioritized in an internal meeting aimed at reviewing technical debt.

+

Adverse User Experience or Security Vulnerabilities#

+

Issues with the provider that provide a poor user experience (bugs, crashes), or involve a threat to security are always prioritized for inclusion. The severity of these will determine how soon they are included for release.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/provider-design/index.html b/provider-design/index.html new file mode 100644 index 000000000000..a880ea024574 --- /dev/null +++ b/provider-design/index.html @@ -0,0 +1,1207 @@ + + + + + + + + + + + + + + + + + + + + + + + + Provider Design - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Provider Design#

+

The Terraform AWS Provider follows the guidelines established in the HashiCorp Provider Design Principles. That general documentation provides many high-level design points gleaned from years of experience with Terraform's design and implementation concepts. Sections below will expand on specific design details between that documentation and this provider, while others will capture other pertinent information that may not be covered there. Other pages of the contributing guide cover implementation details such as code, testing, and documentation specifics.

+

API and SDK Boundary#

+

The AWS provider implements support for the AWS service APIs using the AWS Go SDK. The API and SDK limits extend to the provider. In general, SDK operations manage the lifecycle of AWS components, such as creating, describing, updating, and deleting a database. Operations do not usually handle functionality within those components, such as executing a query on a database. If you are interested in other APIs/SDKs, we invite you to view the many Terraform Providers available, as each has a community of domain expertise.

+

Some examples of functionality that is not expected in this provider:

+ +

Infrastructure as Code Suitability#

+

The provider maintainers' design goal is to cover as much of the AWS API as pragmatically possible. However, not every aspect of the API is compatible with an infrastructure-as-code (IaC) conception. Specifically: IaC is best suited for immutable rather than mutable infrastrcture -- i.e. for resources with a single desired state described in its entirety, as opposed to resources defined via a dynamic process.

+

If such limits affect you, we recommend that you open an AWS Support case and encourage others to do the same. Request that AWS components be made more self-contained and compatible with IaC. These AWS Support cases can also yield insights into the AWS service and API that are not well documented.

+

Resource Type Considerations#

+

Terraform resources work best as the smallest infrastructure blocks on which practitioners can build more complex configurations and abstractions, such as Terraform Modules. The general heuristic guiding when to implement a new Terraform resource for an aspect of AWS is whether the AWS service API provides create, read, update, and delete (CRUD) operations. However, not all AWS service API functionality falls cleanly into CRUD lifecycle management. In these situations, there is extra consideration necessary for properly mapping API operations to Terraform resources.

+

This section highlights design patterns when to consider an implementation within a singular Terraform resource or as separate Terraform resources.

+

Please note: the overall design and implementation across all AWS functionality is federated: individual services may implement concepts and use terminology differently. As such, this guide is not exhaustive. The aim is to provide general concepts and basic terminology that points contributors in the right direction, especially in understanding previous implementations.

+

Authorization and Acceptance Resources#

+

Some AWS services use an authorization-acceptance model for cross-account associations or access. Examples include:

+
    +
  • Direct Connect Association Proposals
  • +
  • GuardDuty Member Invitations
  • +
  • RAM Resource Share Associations
  • +
  • Route 53 VPC Associations
  • +
  • Security Hub Member Invitations
  • +
+

Depending on the API and components, AWS uses two basic ways of creating cross-region and cross-account associations. One way is to generate an invitation (or proposal) identifier from one AWS account to another. Then in the other AWS account, that identifier is used to accept the invitation. The second way is configuring a reference to another AWS account identifier. These may not require explicit acceptance on the receiving account to finish creating the association or begin working.

+

To model creating an association using an invitation or proposal, follow these guidelines.

+
    +
  • Follow the naming in the AWS service API to determine whether to use the term "invitation" or "proposal."
  • +
  • For the originating account, create an "invitation" or "proposal" resource. Make sure that the AWS service API has operations for creating and reading invitations.
  • +
  • For the responding account, create an "accepter" resource. Ensure that the API has operations for accepting, reading, and rejecting invitations in the responding account. Map the operations as follows:
      +
    • Create: Accepts the invitation.
    • +
    • Read: Reads the invitation to determine its status. Note that in some APIs, invitations expire and disappear, complicating associations. If a resource does not find an invitation, the developer should implement a fall back to read the API resource associated with the invitation/proposal.
    • +
    • Delete: Rejects or otherwise deletes the invitation.
    • +
    +
  • +
+

To model the second type of association, implicit associations, create an "association" resource and, optionally, an "authorization" resource. Map create, read, and delete to the corresponding operations in the AWS service API.

+

Cross-Service Functionality#

+

Many AWS service APIs build on top of other AWS services. Some examples of these include:

+
    +
  • EKS Node Groups managing Auto Scaling Groups
  • +
  • Lambda Functions managing EC2 ENIs
  • +
  • Transfer Servers managing EC2 VPC Endpoints
  • +
+

Some cross-service API implementations lack the management or description capabilities of the other service. The lack can make the Terraform resource implementation seem incomplete or unsuccessful in end-to-end configurations. Given the overall “resources should represent a single API object” goal from the HashiCorp Provider Design Principles, a resource must only communicate with a single AWS service API. As such, maintainers will not approve cross-service resources.

+

The rationale behind this design decision includes the following:

+
    +
  • Unexpected IAM permissions being necessary for the resource. In high-security environments, all the service permissions may not be available or acceptable.
  • +
  • Unexpected services generating CloudTrail logs for the resource.
  • +
  • Needing extra and unexpected API endpoints configuration for organizations using custom endpoints, such as VPC endpoints.
  • +
  • Unexpected changes to the AWS service internals for the cross-service implementations. Given that this functionality is not part of the primary service API, these details can change over time and may not be considered as a breaking change by the service team for an API upgrade.
  • +
+

A poignant real-world example of the last point involved a Lambda resource. The resource helped clean up extra resources (ENIs) due to a common misconfiguration. Practitioners found the functionality helpful since the issue was hard to diagnose. Years later, AWS updated the Lambda API. Immediately, practitioners reported that Terraform executions were failing. Downgrading the provider was not possible since many configurations depended on recent releases. For environments running many versions behind, forcing an upgrade with the fix would likely cause unrelated and unexpected changes. In the end, HashiCorp and AWS performed a large-scale outreach to help upgrade and fixing the misconfigurations. Provider maintainers and practitioners lost considerable time.

+

Data Sources#

+

A separate class of Terraform resource types are data sources. These are typically intended as a configuration method to lookup or fetch data in a read-only manner. Data sources should not have side effects on the remote system.

+

When discussing data sources, they are typically classified by the intended number of return objects or data. Singular data sources represent a one-to-one lookup or data operation. Plural data sources represent a one-to-many operation.

+

Plural Data Sources#

+

These data sources are intended to return zero, one, or many results, usually associated with a managed resource type. Typically results are a set unless ordering guarantees are provided by the remote system. These should be named with a plural suffix (e.g., s or es) and should not include any specific attribute in the naming (e.g., prefer aws_ec2_transit_gateways instead of aws_ec2_transit_gateway_ids).

+

Singular Data Sources#

+

These data sources are intended to return one result or an error. These should not include any specific attribute in the naming (e.g., prefer aws_ec2_transit_gateway instead of aws_ec2_transit_gateway_id).

+

IAM Resource-Based Policy Resources#

+

For some AWS components, the AWS API allows specifying an IAM resource-based policy, the IAM policy to associate with a component. Some examples include:

+
    +
  • ECR Repository Policies
  • +
  • EFS File System Policies
  • +
  • SNS Topic Policies
  • +
+

Provider developers should implement this capability in a new resource rather than adding it to the associated resource. Reasons for this include:

+
    +
  • Many of the policies must include the ARN of the resource. Working around this requirement with custom difference handling within a self-contained resource is unnecessarily cumbersome.
  • +
  • Some policies involving multiple resources need to cross-reference each other's ARNs. Without a separate resource, this introduces a configuration cycle.
  • +
  • Splitting the resources allows operators to logically split their configurations into purely operational and security boundaries. This allows environments to have distinct practitioners roles and permissions for IAM versus infrastructure changes.
  • +
+

One rare exception to this guideline is where the policy is required during resource creation.

+

Managing Resource Running State#

+

The AWS API provides the ability to start, stop, enable, or disable some AWS components. Some examples include:

+
    +
  • Batch Job Queues
  • +
  • CloudFront Distributions
  • +
  • RDS DB Event Subscriptions
  • +
+

In this situation, provider developers should implement this ability within the resource instead of creating a separate resource. Since a practitioner cannot practically manage interaction with a resource's states in Terraform's declarative configuration, developers should implement the state management in the resource. This design provides consistency and future-proofing even where updating a resource in the current API is not problematic.

+

Task Execution and Waiter Resources#

+

Some AWS operations are asynchronous. Terraform requests that AWS perform a task. Initially, AWS only notifies Terraform that it received the request. Terraform then requests the status while awaiting completion. Examples of this include:

+
    +
  • ACM Certificate validation
  • +
  • EC2 AMI copying
  • +
  • RDS DB Cluster Snapshot management
  • +
+

In this situation, provider developers should create a separate resource representing the task, assuming that the AWS service API provides operations to start the task and read its status. Adding the task functionality to the parent resource muddies its infrastructure-management purpose. The maintainers prefer this approach even though there is some duplication of an existing resource. For example, the provider has a resource for copying an EC2 AMI in addition to the EC2 AMI resource itself. This modularity allows practitioners to manage the result of the task resource with another resource.

+

For a related consideration, see the Managing Resource Running State section.

+

Versioned Resources#

+

AWS supports having multiple versions of some components. Examples of this include:

+
    +
  • ECS Task Definitions
  • +
  • Lambda Functions
  • +
  • Secrets Manager Secrets
  • +
+

In general, provider developers should create a separate resource to represent a single version. For example, the provider has both the aws_secretsmanager_secret and aws_secretsmanager_secret_version resources. However, in some cases, developers should handle versioning in the main resource.

+

In deciding when to create a separate resource, follow these guidelines:

+
    +
  • If AWS necessarily creates a version when you make a new AWS component, include version handling in the same Terraform resource. Creating an AWS component with one Terraform resource and later using a different resource for updates is confusing.
  • +
  • If the AWS service API allows deleting versions and practitioners will want to delete versions, provider developers should implement a separate version resource.
  • +
  • If the API only supports publishing new versions, either method is acceptable, however most current implementations are self-contained. Terraform's current configuration language does not natively support triggering resource updates or recreation across resources without a state value change. This can make the implementation more difficult for practitioners without special resource and configuration workarounds, such as a triggers attribute. If this changes in the future, then this guidance may be updated towards separate resources, following the Task Execution and Waiter Resources guidance.
  • +
+

Other Considerations#

+

AWS Credential Exfiltration#

+

In the interest of security, the maintainers will not approve data sources that provide the ability to reference or export the AWS credentials of the running provider. There are valid use cases for this information, such as to execute AWS CLI calls as part of the same Terraform configuration. However, this mechanism may allow credentials to be discovered and used outside of Terraform. Some specific concerns include:

+
    +
  • The values may be visible in Terraform user interface output or logging, allowing anyone with user interface or log access to see the credentials.
  • +
  • The values are currently stored in plaintext in the Terraform state, allowing anyone with access to the state file or another Terraform configuration that references the state access to the credentials.
  • +
  • Any new related functionality, while opt-in to implement, is also opt-in to prevent via security controls or policies. Adopting a weaker default security posture requires advance notice and prevents organizations that implement those controls from updating to a version with any such functionality.
  • +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/raising-a-pull-request/index.html b/raising-a-pull-request/index.html new file mode 100644 index 000000000000..c40d04e46d41 --- /dev/null +++ b/raising-a-pull-request/index.html @@ -0,0 +1,1046 @@ + + + + + + + + + + + + + + + + + + + + + + + + Raising a Pull Request - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Raising a Pull Request#

+
    +
  1. +

    Fork the GitHub repository allowing you to make the changes in your own copy of the repository.

    +
  2. +
  3. +

    Create a branch using the following naming prefixes:

    +
      +
    • f = feature
    • +
    • b = bug fix
    • +
    • d = documentation
    • +
    • t = tests
    • +
    • td = technical debt
    • +
    • v = dependencies ("vendoring" previously)
    • +
    +

    Some indicative example branch names would be f-aws_emr_instance_group-refactor or td-staticcheck-st1008

    +
  4. +
  5. +

    Make the changes you would like to include in the provider, add new tests as required, and make sure that all relevant existing tests are passing.

    +
  6. +
  7. +

    Create a changelog entry following the process outlined here

    +
  8. +
  9. +

    Create a pull request. Please ensure (if possible) the 'Allow edits from maintainers' checkbox is checked. This will allow the maintainers to make changes and merge the PR without requiring action from the contributor. + You are welcome to submit your pull request for commentary or review before + it is fully completed by creating a draft pull request. + Please include specific questions or items you'd like feedback on.

    +
  10. +
  11. +

    Once you believe your pull request is ready to be reviewed, ensure the + pull request is not a draft pull request by marking it ready for review + or removing [WIP] from the pull request title if necessary, and a + maintainer will review it. Follow the checklists below + to help ensure that your contribution can be easily reviewed and potentially + merged.

    +
  12. +
  13. +

    One of Terraform's provider team members will look over your contribution and + either approve it or provide comments letting you know if there is anything + left to do. We'll try give you the opportunity to make the required changes yourself, but in some cases we may perform the changes ourselves if it makes sense to (minor changes, or for urgent issues). We do our best to keep up with the volume of PRs waiting for review, but it may take some time depending on the complexity of the work.

    +
  14. +
  15. +

    Once all outstanding comments and checklist items have been addressed, your + contribution will be merged! Merged PRs will be included in the next + Terraform release.

    +
  16. +
  17. +

    In some cases, we might decide that a PR should be closed without merging. + We'll make sure to provide clear reasoning when this happens.

    +
  18. +
+

Go Coding Style#

+

All Go code is automatically checked for compliance with various linters, such as gofmt. These tools can be installed using the GNUMakefile in this repository.

+
$ cd terraform-provider-aws
+$ make tools
+
+

Check your code with the linters:

+
$ make lint
+
+

We use Semgrep to check for other code standards. +This can be run directly on the command line, i.e.,

+
$ semgrep
+
+

or it can be run using Docker via the Makefile, i.e.,

+
$ make semgrep
+
+

gofmt will also fix many simple formatting issues for you. The Makefile includes a target for this:

+
$ make fmt
+
+

The import statement in a Go file follows these rules (see #15903):

+
    +
  1. Import declarations are grouped into a maximum of three groups with the following order:
      +
    • Standard packages (also called short import path or built-in packages)
    • +
    • Third-party packages (also called long import path packages)
    • +
    • Local packages
    • +
    +
  2. +
  3. Groups are separated by a single blank line
  4. +
  5. Packages within each group are alphabetized
  6. +
+

Check your imports:

+
$ make importlint
+
+

For greater detail, the following Go language resources provide common coding preferences that may be referenced during review, if not automatically handled by the project's linting tools.

+ +

Resource Contribution Guidelines#

+

The following resource checks need to be addressed before your contribution can be merged. The exclusion of any applicable check may result in a delayed time to merge. Some of these are not handled by the automated code testing that occurs during submission, so reviewers (even those outside the maintainers) are encouraged to reach out to contributors about any issues to save time.

+

This Contribution Guide also includes separate sections on topics such as Error Handling, which also applies to contributions.

+
    +
  • Passes Testing: All code and documentation changes must pass unit testing, code linting, and website link testing. Resource code changes must pass all acceptance testing for the resource.
  • +
  • Avoids API Calls Across Account, Region, and Service Boundaries: Resources should not implement cross-account, cross-region, or cross-service API calls.
  • +
  • Does Not Set Optional or Required for Non-Configurable Attributes: Resource schema definitions for read-only attributes must not include Optional: true or Required: true.
  • +
  • Avoids retry.RetryContext() without retry.RetryableError(): Resource logic should only implement retry.Retry() if there is a retryable condition (e.g., return retry.RetryableError(err)).
  • +
  • Avoids Reusing Resource Read Function in Data Source Read Function: Data sources should fully implement their own resource Read functionality including duplicating d.Set() calls.
  • +
  • Avoids Reading Schema Structure in Resource Code: The resource Schema should not be read in resource Create/Read/Update/Delete functions to perform looping or otherwise complex attribute logic. Use d.Get() and d.Set() directly with individual attributes instead.
  • +
  • Avoids ResourceData.GetOkExists(): Resource logic should avoid using ResourceData.GetOkExists() as its expected functionality is not guaranteed in all scenarios.
  • +
  • Calls Read After Create and Update: Except where API eventual consistency prohibits immediate reading of resources or updated attributes, resource Create and Update functions should return the resource Read function.
  • +
  • Implements Immediate Resource ID Set During Create: Immediately after calling the API creation function, the resource ID should be set with d.SetId() before other API operations or returning the Read function.
  • +
  • Implements Attribute Refreshes During Read: All attributes available in the API should have d.Set() called their values in the Terraform state during the Read function.
  • +
  • Performs Error Checks with Non-Primitive Attribute Refreshes: When using d.Set() with non-primitive types (schema.TypeList, schema.TypeSet, or schema.TypeMap), perform error checking to prevent issues where the code is not properly able to refresh the Terraform state.
  • +
  • Implements Import Acceptance Testing and Documentation: Support for resource import (Importer in resource schema) must include ImportState acceptance testing (see also the Acceptance Testing Guidelines) and ## Import section in resource documentation.
  • +
  • Implements Customizable Timeouts Documentation: Support for customizable timeouts (Timeouts in resource schema) must include ## Timeouts section in resource documentation.
  • +
  • Implements State Migration When Adding New Virtual Attribute: For new "virtual" attributes (those only in Terraform and not in the API), the schema should implement State Migration to prevent differences for existing configurations that upgrade.
  • +
  • Uses AWS Go SDK Constants: Many AWS services provide string constants for value enumerations, error codes, and status types. See also the "Constants" sections under each of the service packages in the AWS Go SDK documentation.
  • +
  • Uses AWS Go SDK Pointer Conversion Functions: Many APIs return pointer types and these functions return the zero value for the type if the pointer is nil. This prevents potential panics from unchecked * pointer dereferences and can eliminate boilerplate nil checking in many cases. See also the aws package in the AWS Go SDK documentation.
  • +
  • Uses AWS Go SDK Types: Use available SDK structs instead of implementing custom types with indirection.
  • +
  • Uses Existing Validation Functions: Schema definitions including ValidateFunc for attribute validation should use available Terraform helper/validation package functions. All()/Any() can be used for combining multiple validation function behaviors.
  • +
  • Uses tfresource.TimedOut() with retry.Retry(): Resource logic implementing retry.Retry() should error check with tfresource.TimedOut(err error) and potentially unset the error before returning the error. For example:
  • +
+
var output *kms.CreateKeyOutput
+err := retry.Retry(1*time.Minute, func() *retry.RetryError {
+  var err error
+
+  output, err = conn.CreateKey(input)
+
+  /* ... */
+
+  return nil
+})
+
+if tfresource.TimedOut(err) {
+  output, err = conn.CreateKey(input)
+}
+
+if err != nil {
+  return fmt.Errorf("creating KMS External Key: %s", err)
+}
+
+
    +
  • Uses id.UniqueId(): API fields for concurrency protection such as CallerReference and IdempotencyToken should use id.UniqueId(). The implementation includes a monotonic counter which is safer for concurrent operations than solutions such as time.Now().
  • +
  • Skips id Attribute: The id attribute is implicit for all Terraform resources and does not need to be defined in the schema.
  • +
+

The below are style-based items that may be noted during review and are recommended for simplicity, consistency, and quality assurance:

+
    +
  • Implements arn Attribute: APIs that return an ARN should implement arn as an attribute. Alternatively, the ARN can be synthesized using the AWS Go SDK arn.ARN structure. For example:
  • +
+
// Direct Connect Virtual Interface ARN.
+// See https://docs.aws.amazon.com/directconnect/latest/UserGuide/security_iam_service-with-iam.html#security_iam_service-with-iam-id-based-policies-resources.
+  arn := arn.ARN{
+      Partition: meta.(*AWSClient).partition,
+      Region:    meta.(*AWSClient).region,
+      Service:   "directconnect",
+      AccountID: meta.(*AWSClient).accountid,
+      Resource:  fmt.Sprintf("dxvif/%s", d.Id()),
+  }.String()
+  d.Set("arn", arn)
+
+

When the arn attribute is synthesized this way, add the resource to the list of those affected by the provider's skip_requesting_account_id attribute.

+
    +
  • Implements Warning Logging With Resource State Removal: If a resource is removed outside of Terraform (e.g., via different tool, API, or web UI), d.SetId("") and return nil can be used in the resource Read function to trigger resource recreation. When this occurs, a warning log message should be printed beforehand: log.Printf("[WARN] {SERVICE} {THING} (%s) not found, removing from state", d.Id())
  • +
  • Uses American English for Attribute Naming: For any ambiguity with attribute naming, prefer American English over British English. e.g., color instead of colour.
  • +
  • Skips Timestamp Attributes: Generally, creation and modification dates from the API should be omitted from the schema.
  • +
  • Uses Paginated AWS Go SDK Functions When Iterating Over a Collection of Objects: When the API for listing a collection of objects provides a paginated function, use it instead of looping until the next page token is not set. For example, with the EC2 API, DescribeInstancesPages should be used instead of DescribeInstances when more than one result is expected.
  • +
  • Adds Paginated Functions Missing from the AWS Go SDK to Internal Service Package: If the AWS Go SDK does not define a paginated equivalent for a function to list a collection of objects, it should be added to a per-service internal package using the listpages generator. A support case should also be opened with AWS to have the paginated functions added to the AWS Go SDK.
  • +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/resource-filtering/index.html b/resource-filtering/index.html new file mode 100644 index 000000000000..a39b52b560fd --- /dev/null +++ b/resource-filtering/index.html @@ -0,0 +1,969 @@ + + + + + + + + + + + + + + + + + + + + + + + + Resource Filtering - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Adding Resource Filtering Support#

+

AWS provides server-side filtering across many services and resources, which can be used when listing resources of that type, for example in the implementation of a data source. +See the EC2 Listing and filtering your resources page for information about how server-side filtering can be used with EC2 resources.

+

To determine if the supporting AWS API supports this functionality:

+
    +
  • Open the AWS Go SDK documentation for the service, e.g., for service/rds. Note: there can be a delay between the AWS announcement and the updated AWS Go SDK documentation.
  • +
  • Determine if the service API includes functionality for filtering resources (usually a Filters argument to a DescribeThing API call).
  • +
+

Implementing server-side filtering support for Terraform AWS Provider resources requires the following, each with its own section below:

+
    +
  • Generated Service Filtering Code: In the internal code generators (e.g., internal/generate/namevaluesfilters), implementation and customization of how a service handles filtering, which is standardized for the resources.
  • +
  • Resource Filtering Code Implementation: In the resource's equivalent data source code (e.g., internal/service/{servicename}/thing_data_source.go), implementation of filter schema attribute, along with handling in the Read function.
  • +
  • Resource Filtering Documentation Implementation: In the resource's equivalent data source documentation (e.g., website/docs/d/service_thing.html.markdown), addition of filter argument
  • +
+

Adding Service to Filter Generating Code#

+

This step is only necessary for the first implementation and may have been previously completed. If so, move on to the next section.

+

More details about this code generation can be found in the namevaluesfilters documentation.

+

Add the AWS Go SDK service name (e.g., rds) to sliceServiceNames in internal/generate/namevaluesfilters/generators/servicefilters/main.go.

+
    +
  • Run make gen (go generate ./...) and ensure there are no errors via make test (go test ./...)
  • +
+

Resource Filter Code Implementation#

+
    +
  • In the resource's equivalent data source Go file (e.g., internal/service/ec2/internet_gateway_data_source.go), add the following Go import: "github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters"
  • +
  • In the resource schema, add "filter": namevaluesfilters.Schema(),
  • +
  • Implement the logic to build the list of filters:
  • +
+
input := &ec2.DescribeInternetGatewaysInput{}
+
+// Filters based on attributes.
+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()))
+// Add filters based on the custom filtering "filter" attribute.
+filters.Add(d.Get("filter").(*schema.Set))
+
+input.Filters = filters.EC2Filters()
+
+

Resource Filtering Documentation Implementation#

+
    +
  • In the resource's equivalent data source documentation (e.g., website/docs/d/internet_gateway.html.markdown), add the following to the arguments reference:
  • +
+
* `filter` - (Optional) Custom filter block as described below.
+
+More complex filters can be expressed using one or more `filter` sub-blocks, which take the following arguments:
+
+* `name` - (Required) Name of the field to filter by, as defined by
+  [the underlying AWS API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInternetGateways.html).
+
+* `values` - (Required) Set of values that are accepted for the given field.
+  An Internet Gateway will be selected if any one of the given values matches.
+
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/resource-name-generation/index.html b/resource-name-generation/index.html new file mode 100644 index 000000000000..48dae64d7fd1 --- /dev/null +++ b/resource-name-generation/index.html @@ -0,0 +1,1060 @@ + + + + + + + + + + + + + + + + + + + + + + + + Resource Name Generation - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Adding Resource Name Generation Support#

+

Terraform AWS Provider resources can use shared logic to support and test name generation, where the operator can choose between an expected naming value, a generated naming value with a prefix, or a fully generated name.

+

Implementing name generation support for Terraform AWS Provider resources requires the following, each with its own section below:

+
    +
  • Resource Name Generation Code Implementation: In the resource code (e.g., internal/service/{service}/{thing}.go), implementation of name_prefix attribute, along with handling in Create function.
  • +
  • Resource Name Generation Testing Implementation: In the resource acceptance testing (e.g., internal/service/{service}/{thing}_test.go), implementation of new acceptance test functions and configurations to exercise new naming logic.
  • +
  • Resource Name Generation Documentation Implementation: In the resource documentation (e.g., website/docs/r/service_thing.html.markdown), addition of name_prefix argument and update of name argument description.
  • +
+

Resource name generation code implementation#

+
    +
  • In the resource Go file (e.g., internal/service/{service}/{thing}.go), add the following Go import: "github.com/hashicorp/terraform-provider-aws/internal/create"
  • +
  • In the resource schema, add the new name_prefix attribute and adjust the name attribute to be Optional, Computed, and ConflictsWith the name_prefix attribute. Ensure to keep any existing schema fields on name such as ValidateFunc. E.g.
  • +
+
"name": {
+  Type:          schema.TypeString,
+  Optional:      true,
+  Computed:      true,
+  ForceNew:      true,
+  ConflictsWith: []string{"name_prefix"},
+},
+"name_prefix": {
+  Type:          schema.TypeString,
+  Optional:      true,
+  Computed:      true,
+  ForceNew:      true,
+  ConflictsWith: []string{"name"},
+},
+
+
    +
  • In the resource Create function, switch any calls from d.Get("name").(string) to instead use the create.Name() function, e.g.
  • +
+
name := create.Name(d.Get("name").(string), d.Get("name_prefix").(string))
+
+// ... in AWS Go SDK Input types, etc. use aws.String(name)
+
+
    +
  • If the resource supports import, in the resource Read function add a call to d.Set("name_prefix", ...), e.g.
  • +
+
d.Set("name", resp.Name)
+d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(resp.Name)))
+
+

Resource name generation testing implementation#

+
    +
  • In the resource testing (e.g., internal/service/{service}/{thing}_test.go), add the following Go import: "github.com/hashicorp/terraform-provider-aws/internal/create"
  • +
  • In the resource testing, implement two new tests named _Name_Generated and _NamePrefix with associated configurations, that verifies creating the resource without name and name_prefix arguments (for the former) and with only the name_prefix argument (for the latter). E.g.
  • +
+
func TestAccServiceThing_nameGenerated(t *testing.T) {
+  ctx := acctest.Context(t)
+  var thing service.ServiceThing
+  resourceName := "aws_service_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+    ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+    CheckDestroy:             testAccCheckThingDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccThingConfig_nameGenerated(),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckThingExists(ctx, resourceName, &thing),
+          acctest.CheckResourceAttrNameGenerated(resourceName, "name"),
+          resource.TestCheckResourceAttr(resourceName, "name_prefix", id.UniqueIdPrefix),
+        ),
+      },
+      // If the resource supports import:
+      {
+        ResourceName:      resourceName,
+        ImportState:       true,
+        ImportStateVerify: true,
+      },
+    },
+  })
+}
+
+func TestAccServiceThing_namePrefix(t *testing.T) {
+  ctx := acctest.Context(t)
+  var thing service.ServiceThing
+  resourceName := "aws_service_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+    ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+    CheckDestroy:             testAccCheckThingDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccThingConfig_namePrefix("tf-acc-test-prefix-"),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckThingExists(ctx, resourceName, &thing),
+          acctest.CheckResourceAttrNameFromPrefix(resourceName, "name", "tf-acc-test-prefix-"),
+          resource.TestCheckResourceAttr(resourceName, "name_prefix", "tf-acc-test-prefix-"),
+        ),
+      },
+      // If the resource supports import:
+      {
+        ResourceName:      resourceName,
+        ImportState:       true,
+        ImportStateVerify: true,
+      },
+    },
+  })
+}
+
+func testAccThingConfig_nameGenerated() string {
+  return fmt.Sprintf(`
+resource "aws_service_thing" "test" {
+  # ... other configuration ...
+}
+`)
+}
+
+func testAccThingConfig_namePrefix(namePrefix string) string {
+  return fmt.Sprintf(`
+resource "aws_service_thing" "test" {
+  # ... other configuration ...
+
+  name_prefix = %[1]q
+}
+`, namePrefix)
+}
+
+

Resource name generation documentation implementation#

+
    +
  • In the resource documentation (e.g., website/docs/r/service_thing.html.markdown), add the following to the arguments reference:
  • +
+
* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. Conflicts with `name`.
+
+
    +
  • Adjust the existing name argument reference to ensure its denoted as Optional, includes a mention that it can be generated, and that it conflicts with name_prefix:
  • +
+
* `name` - (Optional) Name of the thing. If omitted, Terraform will assign a random, unique name. Conflicts with `name_prefix`.
+
+

Resource name generation with suffix#

+

Some generated resource names require a fixed suffix (for example Amazon SNS FIFO topic names must end in .fifo). +In these cases use create.NameWithSuffix() in the resource Create function and create.NamePrefixFromNameWithSuffix() in the resource Read function, e.g.

+
name := create.NameWithSuffix(d.Get("name").(string), d.Get("name_prefix").(string), ".fifo")
+
+

and

+
d.Set("name", resp.Name)
+d.Set("name_prefix", create.NamePrefixFromNameWithSuffix(aws.StringValue(resp.Name), ".fifo"))
+
+

There are also functions acctest.CheckResourceAttrNameWithSuffixGenerated and acctest.CheckResourceAttrNameWithSuffixFromPrefix for use in tests.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/resource-tagging/index.html b/resource-tagging/index.html new file mode 100644 index 000000000000..123273659a8d --- /dev/null +++ b/resource-tagging/index.html @@ -0,0 +1,1591 @@ + + + + + + + + + + + + + + + + + + + + + + + + Resource Tagging - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Adding Resource Tagging Support#

+

AWS provides key-value metadata across many services and resources, which can be used for a variety of use cases including billing, ownership, and more. See the AWS Tagging Strategy page for more information about tagging at a high level.

+

The Terraform AWS Provider supports default tags configured on the provider in addition to tags configured on the resource. +Implementing tagging support for Terraform AWS Provider resources requires the following, each with its own section below:

+
    +
  • Generated Service Tagging Code: Each service has a generate.go file where generator directives live. + Through these directives and their flags, you can customize code generation for the service. + You can find the code that the tagging generator generates in a tags_gen.go file in a service, such as internal/service/ec2/tags_gen.go. + You should generally not need to edit the generator code itself (i.e., in internal/generate/tags).
  • +
  • Resource Tagging Code Implementation: In the resource code (e.g., internal/service/{service}/{thing}.go), + implementation of tags and tags_all schema attributes, + along with implementation of CustomizeDiff in the resource definition and handling in Create, Read, and Update functions.
  • +
  • Resource Tagging Acceptance Testing Implementation: In the resource acceptance testing (e.g., internal/service/{service}/{thing}_test.go), + implementation of new acceptance test function and configurations to exercise new tagging logic.
  • +
  • Resource Tagging Documentation Implementation: In the resource documentation (e.g., website/docs/r/service_thing.html.markdown), + addition of tags argument and tags_all attributes.
  • +
+

Generating Tag Code for a Service#

+

This step is generally only necessary for the first implementation and may have been previously completed.

+

More details about this code generation, +including fixes for potential error messages in this process, +can be found in the generate package documentation.

+

The generator will create several types of tagging-related code. +All services that support tagging will generate the function KeyValueTags, which converts from service-specific structs returned by the AWS SDK into a common format used by the provider, +and the function Tags, which converts from the common format back to the service-specific structs. +In addition, many services have separate functions to list or update tags, so the corresponding listTags and updateTags can be generated. +Optionally, to retrieve a specific tag, you can generate the GetTag function.

+

If the service directory does not contain a generate.go file, create one. +This file must only contain generate directives and a package declaration (e.g., package eks). +For examples of the generate.go file, many service directories contain one, e.g., internal/service/eks/generate.go.

+

If the generate.go file does not contain a generate directive for tagging code, i.e., //go:generate go run ../../generate/tags/main.go, add it. +Note that without flags, the directive itself will not do anything useful. +You must not include more than one generate/tags/main.go directive, as subsequent directives will overwrite previous directives. +To generate multiple types of tag code, use multiple flags with the directive.

+

Generating Tagging Types#

+

Determine how the service implements tagging: +Some services will use a simple map style (map[string]*string in Go), +while others will have a separate structure, often a []service.Tag struct with Key and Value fields.

+

If the service uses the simple map style, pass the flag -ServiceTagsMap.

+

If the service uses a slice of structs, pass the flag -ServiceTagsSlice. +If the name of the tag struct is not Tag, pass the flag -TagType=<struct name>. +Note that the struct name is used without the package name. +For example, the AppMesh service uses the struct TagRef, so the flag is -TagType=TagRef. +If the key and value fields on the struct are not Key and Value, +specify the names using the flags -TagTypeKeyElem and -TagTypeValElem respectively. +For example, the KMS service uses the struct Tag, but the key and value fields are TagKey and TagValue, +so the flags are -TagTypeKeyElem=TagKey and -TagTypeValElem=TagValue.

+

Some services, such as EC2 and Auto Scaling, return a different type depending on the API call used to retrieve the tag. +To indicate the additional type, include the flag -TagType2=<struct name>. +For example, the Auto Scaling uses the struct Tag as part of resource calls, but returns the struct TagDescription from the DescribeTags API call. The flag used is -TagType2=TagDescription.

+

For more details on flags for generating service keys, see the +documentation for the tag generator

+

Generating Standalone Tag Listing Functions#

+

If the service API uses a standalone function to retrieve tags instead of including them with the resource (usually a ListTags or ListTagsForResource API call), pass the flag -ListTags.

+

If the API call is not ListTagsForResource, pass the flag -ListTagsOp=<API call name>. +Note that this does not include the package name. +For example, the Auto Scaling service uses the API call DescribeTags, so the flag is -ListTagsOp=DescribeTags.

+

If the API call uses a field other than ResourceArn to identify the resource, pass the flag -ListTagsInIDElem=<field name>. +For example, the CloudWatch service uses the field ResourceARN, so the flag is -ListTagsInIDElem=ResourceARN. +Some API calls take a slice of identifiers instead of a single identifier. +In this case, pass the flag -ListTagsInIDNeedSlice=yes.

+

If the field containing the tags in the result of the API call is not named Tags, pass the flag -ListTagsOutTagsElem=<struct name>. +For example, the CloudTrail service returns a nested structure, where the resulting flag is -ListTagsOutTagsElem=ResourceTagList[0].TagsList.

+

In some cases, it can be useful to retrieve single tags. +Pass the flag -GetTag to generate a function to do so.

+

For more details on flags for generating tag listing functions, see the +documentation for the tag generator

+

Generating Standalone Tag Updating Functions#

+

If the service API uses a standalone function to update tags instead of including them when updating the resource (usually a TagResource and UntagResource API call), pass the flag -UpdateTags.

+

If the API call to add tags is not TagResource, pass the flag -TagOp=<API call name>. +Note that this does not include the package name. +For example, the ElastiCache service uses the API call AddTagsToResource, so the flag is -TagOp=AddTagsToResource.

+

If the API call to add tags uses a field other than ResourceArn to identify the resource, pass the flag -TagInIDElem=<field name>. +For example, the EC2 service uses the field Resources, so the flag is -TagInIDElem=Resources. +Some API calls take a slice of identifiers instead of a single identifier. +In this case, pass the flag -TagInIDNeedSlice=yes.

+

If the API call to remove tags is not UntagResource, pass the flag -UntagOp=<API call name>. +Note that this does not include the package name. +For example, the ElastiCache service uses the API call RemoveTagsFromResource, so the flag is -UntagOp=RemoveTagsFromResource.

+

If the API call to remove tags uses a field other than ResourceArn to identify the resource, pass the flag -UntagInTagsElem=<field name>. +For example, the Route 53 service uses the field Keys, so the flag is -UntagInTagsElem=Keys.

+

For more details on flags for generating tag updating functions, see the +documentation for the tag generator

+

Generating Standalone Post-Creation Tag Updating Functions#

+

When creating a resource, some AWS APIs support passing tags in the Create call while others require setting the tags after the initial creation. +If the API does not support tagging on creation, pass the -CreateTags flag to generate a createTags function that can be called from the resource Create handler function.

+

Specifying the AWS SDK for Go version#

+

The vast majority of the Terraform AWS Provider is implemented using version 1 of the AWS SDK for Go. +For new services, however, we have started to use version 2 of the SDK.

+

By default, the generated code uses the AWS SDK for Go v1. +To generate code using the AWS SDK for Go v2, pass the flag -AwsSdkVersion=2.

+

For more information, see the documentation on AWS SDK versions.

+

Running Code generation#

+

Run the command make gen to run the code generators for the project. +To ensure that the code compiles, run make test.

+

Resource Tagging Code Implementation#

+

Resource Schema#

+

Add the following imports to the resource's Go source file:

+
imports (
+  /* ... other imports ... */
+  tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
+    "github.com/hashicorp/terraform-provider-aws/internal/verify"
+    "github.com/hashicorp/terraform-provider-aws/names"
+)
+
+

Add the tags parameter and tags_all attribute to the schema, using constants defined in the names package. +The tags parameter contains the tags set directly on the resource. +The tags_all attribute contains union of the tags set directly on the resource and default tags configured on the provider.

+
func ResourceCluster() *schema.Resource {
+  return &schema.Resource{
+    /* ... other configuration ... */
+        Schema: map[string]*schema.Schema{
+      /* ... other configuration ... */
+            names.AttrTags:    tftags.TagsSchema(),
+            names.AttrTagsAll: tftags.TagsSchemaComputed(),
+    },
+  }
+}
+
+

The function verify.SetTagsDiff handles the combination of tags set on the resource and default tags, +and must be added to the resource's CustomizeDiff function.

+

If the resource has no other CustomizeDiff handler functions, set it directly:

+
func ResourceCluster() *schema.Resource {
+  return &schema.Resource{
+    /* ... other configuration ... */
+    CustomizeDiff: verify.SetTagsDiff,
+  }
+}
+
+

Otherwise, if the resource already contains a CustomizeDiff function, append the SetTagsDiff via the customdiff.All method:

+
func ResourceExample() *schema.Resource {
+  return &schema.Resource{
+    /* ... other configuration ... */
+    CustomizeDiff: customdiff.All(
+      resourceExampleCustomizeDiff,
+      verify.SetTagsDiff,
+    ),
+  }
+}
+
+

Transparent Tagging#

+

Most services can use a facility we call transparent (or implicit) tagging, where the majority of resource tagging functionality is implemented using code located in the provider's runtime packages (see internal/provider/intercept.go and internal/provider/fwprovider/intercept.go for details) and not in the resource's CRUD handler functions. Resource implementers opt-in to transparent tagging by adding an annotation (a specially formatted Go comment) to the resource's factory function (similar to the resource self-registration mechanism).

+
// @SDKResource("aws_accessanalyzer_analyzer", name="Analyzer")
+// @Tags(identifierAttribute="arn")
+func ResourceAnalyzer() *schema.Resource {
+  return &schema.Resource{
+    ...
+  }
+}
+
+

The identifierAttribute argument to the @Tags annotation identifies the attribute in the resource's schema whose value is used in tag listing and updating API calls. Common values are "arn" and "id". +Once the annotation has been added to the resource's code, run make gen to register the resource for transparent tagging. This will add an entry to the service_package_gen.go file located in the service package folder.

+

Resource Create Operation#

+

When creating a resource, some AWS APIs support passing tags in the Create call +while others require setting the tags after the initial creation.

+

If the API supports tagging on creation (e.g., the Input struct accepts a Tags field), +use the getTagsIn function to get any configured tags, e.g., with EKS Clusters:

+
input := &eks.CreateClusterInput{
+  /* ... other configuration ... */
+  Tags: getTagsIn(ctx),
+}
+
+

Otherwise, if the API does not support tagging on creation, call createTags after the resource has been created, e.g., with Device Farm device pools:

+
if err := createTags(ctx, conn, d.Id(), getTagsIn(ctx)); err != nil {
+  return sdkdiag.AppendErrorf(diags, "setting DeviceFarm Device Pool (%s) tags: %s", d.Id(), err)
+}
+
+

Resource Read Operation#

+

In the resource Read operation, use the setTagsOut function to signal to the transparent tagging mechanism that the resource has tags that should be saved into Terraform state, e.g., with EKS Clusters:

+
/* ... other d.Set(...) logic ... */
+
+setTagsOut(ctx, cluster.Tags)
+
+

If the service API does not return the tags directly from reading the resource and requires use of the generated listTags function, do nothing and the transparent tagging mechanism will make the listTags call and save any tags into Terraform state.

+

Resource Update Operation#

+

In the resource Update operation, only non-tags updates need be done as the transparent tagging mechanism makes the updateTags call.

+
if d.HasChangesExcept("tags", "tags_all") {
+  ...
+}
+
+

Ensure that the Update operation always calls the resource Read operation before returning so that the transparent tagging mechanism correctly saves any tags into Terraform state, e.g., with Access Analyzer analyzers:

+
func resourceAnalyzerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+  var diags diag.Diagnostics
+  // Tags only.
+  return append(diags, resourceAnalyzerRead(ctx, d, meta)...)
+}
+
+

Explicit Tagging#

+

If the resource cannot opt-in to transparent tagging, more boilerplate code must be explicitly added to the resource CRUD handler functions. +This section describes how to do this.

+

Resource Create Operation#

+

When creating a resource, some AWS APIs support passing tags in the Create call +while others require setting the tags after the initial creation.

+

If the API supports tagging on creation (e.g., the Input struct accepts a Tags field), +implement the logic to convert the configuration tags into the service tags, e.g., with EKS Clusters:

+
// Typically declared near conn := /* ... */
+defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig
+tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{})))
+
+input := &eks.CreateClusterInput{
+  /* ... other configuration ... */
+  Tags: Tags(tags.IgnoreAWS()),
+}
+
+

If the service API does not allow passing an empty list, the logic can be adjusted similar to:

+
// Typically declared near conn := /* ... */
+defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig
+tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{})))
+
+input := &eks.CreateClusterInput{
+  /* ... other configuration ... */
+}
+
+if len(tags) > 0 {
+  input.Tags = Tags(tags.IgnoreAWS())
+}
+
+

Otherwise, if the API does not support tagging on creation, +implement the logic to convert the configuration tags into the service API call to tag a resource, e.g., with Device Farm device pools:

+
// Typically declared near conn := /* ... */
+defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig
+tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{})))
+
+/* ... creation steps ... */
+
+if len(tags) > 0 {
+  if err := updateTags(ctx, conn, d.Id(), nil, tags); err != nil {
+    return fmt.Errorf("adding DeviceFarm Device Pool (%s) tags: %w", d.Id(), err)
+  }
+}
+
+

Some EC2 resources (e.g., aws_ec2_fleet) have a TagSpecifications field in the InputStruct instead of a Tags field. +In these cases the tagSpecificationsFromKeyValueTags() helper function should be used. +This example shows using TagSpecifications:

+
// Typically declared near conn := /* ... */
+defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig
+tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{})))
+
+input := &ec2.CreateFleetInput{
+  /* ... other configuration ... */
+  TagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeFleet),
+}
+
+

Resource Read Operation#

+

In the resource Read operation, implement the logic to convert the service tags to save them into the Terraform state for drift detection, e.g., with EKS Clusters:

+
// Typically declared near conn := /* ... */
+defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig
+ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig
+
+/* ... other d.Set(...) logic ... */
+
+tags := KeyValueTags(ctx, cluster.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)
+
+if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
+  return fmt.Errorf("setting tags: %w", err)
+}
+
+if err := d.Set("tags_all", tags.Map()); err != nil {
+  return fmt.Errorf("setting tags_all: %w", err)
+}
+
+

If the service API does not return the tags directly from reading the resource and requires a separate API call, +use the generated listTags function, e.g., with Athena Workgroups:

+
// Typically declared near conn := /* ... */
+defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig
+ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig
+
+/* ... other d.Set(...) logic ... */
+
+tags, err := listTags(ctx, conn, arn.String())
+
+if err != nil {
+  return fmt.Errorf("listing tags for resource (%s): %w", arn, err)
+}
+
+tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)
+
+if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
+  return fmt.Errorf("setting tags: %w", err)
+}
+
+if err := d.Set("tags_all", tags.Map()); err != nil {
+  return fmt.Errorf("setting tags_all: %w", err)
+}
+
+

Resource Update Operation#

+

In the resource Update operation, implement the logic to handle tagging updates, e.g., with EKS Clusters:

+
if d.HasChange("tags_all") {
+  o, n := d.GetChange("tags_all")
+  if err := updateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil {
+    return fmt.Errorf("updating tags: %w", err)
+  }
+}
+
+

If the resource Update function applies specific updates to attributes regardless of changes to tags, implement the following e.g., with IAM Policy:

+
if d.HasChangesExcept("tags", "tags_all") {
+  /* ... other logic ...*/
+  request := &iam.CreatePolicyVersionInput{
+    PolicyArn:      aws.String(d.Id()),
+    PolicyDocument: aws.String(d.Get("policy").(string)),
+    SetAsDefault:   aws.Bool(true),
+  }
+
+  if _, err := conn.CreatePolicyVersionWithContext(ctx, request); err != nil {
+      return fmt.Errorf("updating IAM policy (%s): %w", d.Id(), err)
+  }
+}
+
+

Resource Tagging Acceptance Testing Implementation#

+

In the resource testing (e.g., internal/service/eks/cluster_test.go), verify that existing resources without tagging are unaffected and do not have tags saved into their Terraform state. This should be done in the _basic acceptance test by adding one line similar to resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), and one similar to resource.TestCheckResourceAttr(resourceName, "tags_all.%", "0"),

+

In the resource testing, implement a new test named _tags with associated configurations, that verifies creating the resource with tags and updating tags. E.g., EKS Clusters:

+
func TestAccEKSCluster_tags(t *testing.T) {
+    ctx := acctest.Context(t)
+  var cluster1, cluster2, cluster3 eks.Cluster
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  resourceName := "aws_eks_cluster.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:                 func() { acctest.PreCheck(ctx, t); testAccPreCheck(t) },
+    ErrorCheck:               acctest.ErrorCheck(t, eks.EndpointsID),
+    ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+    CheckDestroy:             testAccCheckClusterDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccClusterConfig_tags1(rName, "key1", "value1"),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckClusterExists(ctx, resourceName, &cluster1),
+          resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
+          resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"),
+        ),
+      },
+      {
+        ResourceName:      resourceName,
+        ImportState:       true,
+        ImportStateVerify: true,
+      },
+      {
+        Config: testAccClusterConfig_tags2(rName, "key1", "value1updated", "key2", "value2"),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckClusterExists(ctx, resourceName, &cluster2),
+          resource.TestCheckResourceAttr(resourceName, "tags.%", "2"),
+          resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"),
+          resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
+        ),
+      },
+      {
+        Config: testAccClusterConfig_tags1(rName, "key2", "value2"),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckClusterExists(ctx, resourceName, &cluster3),
+          resource.TestCheckResourceAttr(resourceName, "tags.%", "1"),
+          resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"),
+        ),
+      },
+    },
+  })
+}
+
+func testAccClusterConfig_tags1(rName, tagKey1, tagValue1 string) string {
+  return acctest.ConfigCompose(testAccClusterConfig_base(rName), fmt.Sprintf(`
+resource "aws_eks_cluster" "test" {
+  name     = %[1]q
+  role_arn = aws_iam_role.test.arn
+
+  tags = {
+    %[2]q = %[3]q
+  }
+
+  vpc_config {
+    subnet_ids = aws_subnet.test[*].id
+  }
+
+  depends_on = [aws_iam_role_policy_attachment.test-AmazonEKSClusterPolicy]
+}
+`, rName, tagKey1, tagValue1))
+}
+
+func testAccClusterConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string {
+  return acctest.ConfigCompose(testAccClusterConfig_base(rName), fmt.Sprintf(`
+resource "aws_eks_cluster" "test" {
+  name     = %[1]q
+  role_arn = aws_iam_role.test.arn
+
+  tags = {
+    %[2]q = %[3]q
+    %[4]q = %[5]q
+  }
+
+  vpc_config {
+    subnet_ids = aws_subnet.test[*].id
+  }
+
+  depends_on = [aws_iam_role_policy_attachment.test-AmazonEKSClusterPolicy]
+}
+`, rName, tagKey1, tagValue1, tagKey2, tagValue2))
+}
+
+

Verify all acceptance testing passes for the resource (e.g., make testacc TESTS=TestAccEKSCluster_ PKG=eks)

+

Resource Tagging Documentation Implementation#

+

In the resource documentation (e.g., website/docs/r/eks_cluster.html.markdown), add the following to the arguments reference:

+
* `tags` - (Optional) Key-value mapping 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.
+
+

In the resource documentation (e.g., website/docs/r/eks_cluster.html.markdown), add the following to the attributes reference:

+
* `tags_all` - 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).
+
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/retries-and-waiters/index.html b/retries-and-waiters/index.html new file mode 100644 index 000000000000..4f9a31a7f140 --- /dev/null +++ b/retries-and-waiters/index.html @@ -0,0 +1,1603 @@ + + + + + + + + + + + + + + + + + + + + + + + + Retries and Waiters - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + +

Retries and Waiters#

+

Terraform plugins may run into situations where calling the remote system after an operation may be necessary. These typically fall under three classes where:

+
    +
  • The request never reaches the remote system.
  • +
  • The request reaches the remote system and responds that it cannot handle the request temporarily.
  • +
  • The implementation of the remote system requires additional requests to ensure success.
  • +
+

This guide describes the behavior of the Terraform AWS Provider and provides code implementations that help ensure success in each of these situations.

+

Terraform Plugin SDK Functionality#

+

The Terraform Plugin SDK, which the AWS Provider uses, provides vital tools for handling consistency: the retry.StateChangeConf{} struct, and the retry function retry.RetryContext(). +We will discuss these throughout the rest of this guide. +Since they help keep the AWS Provider code consistent, we heavily prefer them over custom implementations.

+

This guide goes beyond the Terraform Plugin SDK v2 documentation by providing additional context and emergent implementations specific to the Terraform AWS Provider.

+

State Change Configuration and Functions#

+

The retry.StateChangeConf type, along with its receiver methods WaitForState() and WaitForStateContext() is a generic primitive for repeating operations in Terraform resource logic until desired value(s) are received. The "state change" in this case is generic to any value and not specific to the Terraform State. Among other functionality, it supports some of these desirable optional properties:

+
    +
  • Expecting specific value(s) while waiting for the target value(s) to be reached. Unexpected values are returned as an error which can be augmented with additional details.
  • +
  • Expecting the target value(s) to be returned multiple times in succession.
  • +
  • Allowing various polling configurations such as delaying the initial request and setting the time between polls.
  • +
+

Retry Functions#

+

The retry.RetryContext() function provides a simplified retry implementation around retry.StateChangeConf. +The most common use is for simple error-based retries.

+

AWS Request Handling#

+

The Terraform AWS Provider's requests to AWS service APIs happen on top of Hypertext Transfer Protocol (HTTP). The following is a simplified description of the layers and handling that requests pass through:

+
    +
  • A Terraform resource calls an AWS Go SDK function.
  • +
  • The AWS Go SDK generates an AWS-compatible HTTP request using the Go standard library net/http package. This includes the following:
      +
    • Adding HTTP headers for authentication and signing of requests to ensure authenticity.
    • +
    • Converting operation inputs into required HTTP URI parameters and/or request body type (XML or JSON).
    • +
    • If debug logging is enabled, logging of the HTTP request.
    • +
    +
  • +
  • The AWS Go SDK transmits the net/http request using Go's standard handling of the Operating System (OS) and Domain Name System (DNS) configuration.
  • +
  • The AWS service potentially receives the request and responds, typically adding a request identifier HTTP header which can be used for AWS Support cases.
  • +
  • The OS and Go net/http receive the response and pass it to the AWS Go SDK.
  • +
  • The AWS Go SDK attempts to handle the response. This may include:
      +
    • Parsing output
    • +
    • Converting errors into operation errors (Go error type of wrapped awserr.Error type).
    • +
    • Converting response elements into operation outputs (AWS Go SDK operation-specific types).
    • +
    • Triggering automatic request retries based on default and custom logic.
    • +
    +
  • +
  • The Terraform resource receives the response, including any output and errors, from the AWS Go SDK.
  • +
+

The Terraform AWS Provider specific configuration for AWS Go SDK operation handling can be found in aws/conns/conns.go in this codebase and the hashicorp/aws-sdk-go-base codebase.

+

NOTE: The section descibes the current handling with version 1 of the AWS Go SDK. In the future, this codebase will be migrated to version 2 of the AWS Go SDK. The newer version implements a very similar request flow but uses a simpler credential and request handling configuration. As such, the aws-sdk-go-base dependency will likely not receive further updates and will be removed after that migration.

+

Default AWS Go SDK Retries#

+

In some situations, while handling a response, the AWS Go SDK automatically retries a request before returning the output and error. The retry mechanism implements an exponential backoff algorithm. The default conditions triggering automatic retries (implemented through client.DefaultRetryer) include:

+
    +
  • Certain network errors. A common exception to this is connection reset errors.
  • +
  • HTTP status codes 429 and 5xx.
  • +
  • Certain API error codes, which are common across various AWS services (e.g., ThrottledException). However, not all AWS services implement these error codes consistently. A common exception to this is certain expired credentials errors.
  • +
+

By default, the Terraform AWS Provider sets the maximum number of AWS Go SDK retries based on the max_retries provider configuration. The provider configuration defaults to 25 and the exponential backoff roughly equates to one hour of retries. This very high default value was present before the Terraform AWS Provider codebase was split from Terraform CLI in version 0.10.

+

NOTE: The section describes the current handling with version 1 of the AWS Go SDK. In the future, this codebase will be migrated to version 2 of the AWS Go SDK. The newer version implements additional retry conditions by default, such as consistently retrying all common network errors.

+

NOTE: The section describes the current handling with Terraform Plugin SDK resource signatures without context.Context. In the future, this codebase will be migrated to the context-aware resource signatures which currently enforce a 20-minute default timeout that conflicts with the timeout with the default max_retries value. The Terraform Plugin SDK may be updated to support removing this default 20-minute timeout or the default retry mechanism described here will be updated to prevent context cancellation errors where possible.

+

Lower Network Error Retries#

+

Given the very high default number of AWS Go SDK retries configured in the Terraform AWS Provider and the excessive wait that practitioners would face, the hashicorp/aws-sdk-go-base codebase lowers retries to 10 for certain network errors that typically cannot be remediated via retries. This roughly equates to 30 seconds of retries.

+

Terraform AWS Provider Service Retries#

+

The AWS Go SDK provides hooks for injecting custom logic into the service client handlers. We prefer this handling in situations where contributors would need to apply the retry behavior to many resources. For example, in cases where the AWS service API does not mark an error code as automatically retriable. The AWS Provider includes other retry-changing behaviors using this method. You can find them in the aws/config.go file. For example:

+
client.kafkaconn.Handlers.Retry.PushBack(func(r *request.Request) {
+    if tfawserr.ErrMessageContains(r.Error, kafka.ErrCodeTooManyRequestsException, "Too Many Requests") {
+        r.Retryable = aws.Bool(true)
+    }
+})
+
+

Eventual Consistency#

+

Eventual consistency is a temporary condition where the remote system can return outdated information or errors due to not being strongly read-after-write consistent. This is a pattern found in remote systems that must be highly scaled for broad usage.

+

Terraform expects any planned resource lifecycle change (create, update, destroy of the resource itself) and planned resource attribute value change to match after being applied. Conversely, operators typically expect that Terraform resources also implement the concept of drift detection for resources and their attributes, which requires reading information back from the remote system after an operation. A common implementation is refreshing the Terraform State information (d.Set()) during the Read function of a resource after Create and Update.

+

These two concepts conflict with each other and require additional handling in Terraform resource logic as shown in the following sections. These issues are not reliably reproducible, especially in the case of writing acceptance testing, so they can be elusive with false positives to verify fixes.

+

Operation Specific Error Retries#

+

Even given a properly ordered Terraform configuration, eventual consistency can unexpectedly prevent downstream operations from succeeding. +A simple retry after a few seconds resolves many of these issues. +To reduce frustrating behavior for operators, wrap AWS Go SDK operations with the retry.RetryContext() function. +These retries should have a reasonably low timeout (typically two minutes but up to five minutes). +Save them in a constant for reusability. +These functions are preferably in line with the associated resource logic to remove any indirection with the code.

+

Do not use this type of logic to overcome improperly ordered Terraform configurations. +The approach may not work in larger environments.

+
// internal/service/example/wait.go (created if does not exist)
+
+const (
+    // Maximum amount of time to wait for Thing operation eventual consistency
+    ThingOperationTimeout = 2 * time.Minute
+)
+
+
// internal/service/{service}/{thing}.go
+
+// ... Create, Read, Update, or Delete function ...
+    err := retry.RetryContext(ctx, ThingOperationTimeout, func() *retry.RetryError {
+        _, err := conn./* ... AWS Go SDK operation with eventual consistency errors ... */
+
+        // Retryable conditions which can be checked.
+        // These must be updated to match the AWS service API error code and message.
+        if tfawserr.ErrMessageContains(err, /* error code */, /* error message */) {
+            return retry.RetryableError(err)
+        }
+
+        if err != nil {
+            return retry.NonRetryableError(err)
+        }
+
+        return nil
+    })
+
+    // This check is important - it handles when the AWS Go SDK operation retries without returning.
+    // e.g., any automatic retries due to network or throttling errors.
+    if tfresource.TimedOut(err) {
+        // The use of equals assignment (over colon equals) is also important here.
+        // This overwrites the error variable to simplify logic.
+        _, err = conn./* ... AWS Go SDK operation with IAM eventual consistency errors ... */
+    }
+
+    if err != nil {
+        return fmt.Errorf("... error message context ... : %w", err)
+    }
+
+

NOTE: The section descibes the current handling with version 1 of the AWS Go SDK. In the future, this codebase will be migrated to version 2 of the AWS Go SDK. The newer version natively supports operation-specific retries in a more friendly manner, which may replace this type of implementation.

+

IAM Error Retries#

+

A common eventual consistency issue is an error returned due to IAM permissions. The IAM service itself is eventually consistent along with the propagation of its components and permissions to other AWS services. For example, if the following operations occur in quick succession:

+
    +
  • Create an IAM Role
  • +
  • Attach an IAM Policy to the IAM Role
  • +
  • Reference the new IAM Role in another AWS service, such as creating a Lambda Function
  • +
+

The last operation can receive varied API errors ranging from:

+
    +
  • IAM Role being reported as not existing
  • +
  • IAM Role being reported as not having permissions for the other service to use it (assume role permissions)
  • +
  • IAM Role being reported as not having sufficient permissions (inline or attached role permissions)
  • +
+

Each AWS service API (and sometimes even operations within the same API) varies in the implementation of these errors. To handle them, it is recommended to use the Operation Specific Error Retries pattern. The Terraform AWS Provider implements a standard timeout constant of two minutes in the internal/service/iam package which should be used for all retry timeouts associated with IAM errors. This timeout was derived from years of Terraform operational experience with all AWS APIs.

+
// internal/service/{service}/{thing}.go
+
+import (
+    // ... other imports ...
+    tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam"
+)
+
+// ... Create and typically Update function ...
+    err := retry.RetryContext(ctx, iamwaiter.PropagationTimeout, func() *retry.RetryError {
+        _, err := conn./* ... AWS Go SDK operation with IAM eventual consistency errors ... */
+
+        // Example retryable condition
+        // This must be updated to match the AWS service API error code and message.
+        if tfawserr.ErrMessageContains(err, /* error code */, /* error message */) {
+            return retry.RetryableError(err)
+        }
+
+        if err != nil {
+            return retry.NonRetryableError(err)
+        }
+
+        return nil
+    })
+
+    if tfresource.TimedOut(err) {
+        _, err = conn./* ... AWS Go SDK operation with IAM eventual consistency errors ... */
+    }
+
+    if err != nil {
+        return fmt.Errorf("... error message context ... : %w", err)
+    }
+
+

Asynchronous Operation Error Retries#

+

Some remote system operations run asynchronously as detailed in the Asynchronous Operations section. In these cases, it is possible that the initial operation will immediately return as successful, but potentially return a retryable failure while checking the operation status that requires starting everything over. The handling for these is complicated by the fact that there are two timeouts, one for the retryable failure and one for the asynchronous operation status checking.

+

The below code example highlights this situation for a resource creation that also exhibited IAM eventual consistency.

+
// internal/service/{service}/{thing}.go
+
+import (
+    // ... other imports ...
+    tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam"
+)
+
+// ... Create function ...
+
+    // Underlying IAM eventual consistency errors can occur after the creation
+    // operation. The goal is only retry these types of errors up to the IAM
+    // timeout. Since the creation process is asynchronous and can take up to
+    // its own timeout, we store a stop time upfront for checking.
+    iamwaiterStopTime := time.Now().Add(tfiam.PropagationTimeout)
+
+    // Ensure to add IAM eventual consistency timeout in case of retries
+    err = retry.RetryContext(ctx, tfiam.PropagationTimeout+ThingOperationTimeout, func() *retry.RetryError {
+        // Only retry IAM eventual consistency errors up to that timeout
+        iamwaiterRetry := time.Now().Before(iamwaiterStopTime)
+
+        _, err := conn./* ... AWS Go SDK operation without eventual consistency errors ... */
+
+        if err != nil {
+            return retry.NonRetryableError(err)
+        }
+
+        _, err = ThingOperation(conn, d.Id())
+
+        if err != nil {
+            if iamwaiterRetry && /* eventual consistency error checking */ {
+                return retry.RetryableError(err)
+            }
+
+            return retry.NonRetryableError(err)
+        }
+
+        return nil
+    })
+
+    if tfresource.TimedOut(err) {
+        _, err = conn./* ... AWS Go SDK operation without eventual consistency errors ... */
+
+        if err != nil {
+            return err
+        }
+
+        _, err = ThingOperation(conn, d.Id())
+
+        if err != nil {
+            return err
+        }
+    }
+
+

Resource Lifecycle Retries#

+

Resource lifecycle eventual consistency is a type of consistency issue that relates to the existence or state of an AWS infrastructure component. For example, if you create a resource and immediately try to get information about it, some AWS services and operations will return a "not found" error. Depending on the service and general AWS load, these errors can be frequent or rare.

+

In order to avoid this issue, identify operations that make changes. Then, when calling any other operations that rely on the changes, account for the possibility that the AWS service has not yet fully realized them.

+

A typical example is creating an AWS component. After creation, when attempting to read the component's information, provide logic to retry the read if the AWS service returns a "not found" error.

+

The pattern that most resources should follow is to have the Create function return calling the Read function. This fills in computed attributes and ensures that the AWS service applied the configuration correctly. Add retry logic to the Read function to overcome the temporary condition on resource creation.

+

Note that for eventually consistent resources, "not found" errors can still occur in the Read function even after implementing Resource Lifecycle Waiters for the Create function.

+
// internal/service/example/wait.go (created if does not exist)
+
+const (
+    // Maximum amount of time to wait for Thing eventual consistency on creation
+    ThingCreationTimeout = 2 * time.Minute
+)
+
+
// internal/service/{service}/{thing}.go
+
+function ExampleThingCreate(d *schema.ResourceData, meta interface{}) error {
+    // ...
+    return ExampleThingRead(d, meta)
+}
+
+function ExampleThingRead(d *schema.ResourceData, meta interface{}) error {
+    conn := meta.(*AWSClient).ExampleConn()
+
+    input := &example.OperationInput{/* ... */}
+
+    var output *example.OperationOutput
+    err := retry.RetryContext(ctx, ThingCreationTimeout, func() *retry.RetryError {
+        var err error
+        output, err = conn.Operation(input)
+
+        // Retry on any API "not found" errors, but only on new resources.
+        if d.IsNewResource() && tfawserr.ErrorCodeEquals(err, example.ErrCodeResourceNotFoundException) {
+            return retry.RetryableError(err)
+        }
+
+        if err != nil {
+            return retry.NonRetryableError(err)
+        }
+
+        return nil
+    })
+
+    // Retry AWS Go SDK operation if no response from automatic retries.
+    if tfresource.TimedOut(err) {
+        output, err = exampleconn.Operation(input)
+    }
+
+    // Prevent confusing Terraform error messaging to operators by
+    // Only ignoring API "not found" errors if not a new resource.
+    if !d.IsNewResource() && tfawserr.ErrorCodeEquals(err, example.ErrCodeNoSuchEntityException) {
+        log.Printf("[WARN] Example Thing (%s) not found, removing from state", d.Id())
+        d.SetId("")
+        return nil
+    }
+
+    if err != nil {
+        return fmt.Errorf("reading Example Thing (%s): %w", d.Id(), err)
+    }
+
+    // Prevent panics.
+    if output == nil {
+        return fmt.Errorf("reading Example Thing (%s): empty response", d.Id())
+    }
+
+    // ... refresh Terraform state as normal ...
+    d.Set("arn", output.Arn)
+}
+
+

Some other general guidelines are:

+
    +
  • If the Create function uses retry.StateChangeConf, the underlying resource.RefreshStateFunc should return nil, "", nil instead of the API "not found" error. This way the StateChangeConf logic will automatically retry.
  • +
  • If the Create function uses retry.RetryContext(), the API "not found" error should be caught and return retry.RetryableError(err) to automatically retry.
  • +
+

In rare cases, it may be easier to duplicate all Read function logic in the Create function to handle all retries in one place.

+

Resource Attribute Value Waiters#

+

An emergent solution for handling eventual consistency with attribute values on updates is to introduce a custom retry.StateChangeConf and resource.RefreshStateFunc handlers. For example:

+
// internal/service/example/status.go (created if does not exist)
+
+// ThingAttribute fetches the Thing and its Attribute
+func ThingAttribute(conn *example.Example, id string) retry.StateRefreshFunc {
+    return func() (interface{}, string, error) {
+        output, err := /* ... AWS Go SDK operation to fetch resource/value ... */
+
+        if tfawserr.ErrCodeEquals(err, example.ErrCodeResourceNotFoundException) {
+            return nil, "", nil
+        }
+
+        if err != nil {
+            return nil, "", err
+        }
+
+        if output == nil {
+            return nil, "", nil
+        }
+
+        return output, aws.StringValue(output.Attribute), nil
+    }
+}
+
+
// internal/service/example/wait.go (created if does not exist)
+
+const (
+    ThingAttributePropagationTimeout = 2 * time.Minute
+)
+
+// ThingAttributeUpdated is an attribute waiter for ThingAttribute
+func ThingAttributeUpdated(conn *example.Example, id string, expectedValue string) (*example.Thing, error) {
+    stateConf := &retry.StateChangeConf{
+        Target:  []string{expectedValue},
+        Refresh: ThingAttribute(conn, id),
+        Timeout: ThingAttributePropagationTimeout,
+    }
+
+    outputRaw, err := stateConf.WaitForState()
+
+    if output, ok := outputRaw.(*example.Thing); ok {
+        return output, err
+    }
+
+    return nil, err
+}
+
+
// internal/service/{service}/{thing}.go
+
+function ExampleThingUpdate(d *schema.ResourceData, meta interface{}) error {
+    // ...
+
+    d.HasChange("attribute") {
+        // ... AWS Go SDK logic to update attribute ...
+
+        if _, err := ThingAttributeUpdated(conn, d.Id(), d.Get("attribute").(string)); err != nil {
+            return fmt.Errorf("waiting for Example Thing (%s) attribute update: %w", d.Id(), err)
+        }
+    }
+
+    // ...
+}
+
+

Asynchronous Operations#

+

When you initiate a long-running operation, an AWS service may return a successful response immediately and continue working on the request asynchronously. A resource can track the status with a component-level field (e.g., CREATING, UPDATING, etc.) or an explicit tracking identifier.

+

Terraform resources should wait for these background operations to complete. Failing to do so can introduce incomplete state information and downstream errors in other resources. In rare scenarios involving very long-running operations, operators may request a flag to skip the waiting. However, these should only be implemented case-by-case to prevent those previously mentioned confusing issues.

+

AWS Go SDK Waiters#

+

In limited cases, the AWS service API model includes the information to automatically generate a waiter function in the AWS Go SDK for an operation. These are typically named with the prefix WaitUntil.... If available, these functions can be used for an initial resource implementation. For example:

+
if err := conn.WaitUntilEndpointInService(input); err != nil {
+    return fmt.Errorf("waiting for Example Thing (%s) ...: %w", d.Id(), err)
+}
+
+

If it is necessary to customize the timeouts and polling, we generally prefer using Resource Lifecycle Waiters instead since they are more commonly used throughout the codebase.

+

Resource Lifecycle Waiters#

+

Most of the codebase uses retry.StateChangeConf and retry.StateRefreshFunc handlers for tracking either component level status fields or explicit tracking identifiers. These should be placed in the internal/service/{SERVICE} package and split into separate functions. For example:

+
// internal/service/example/status.go (created if does not exist)
+
+// ThingStatus fetches the Thing and its Status
+func ThingStatus(conn *example.Example, id string) retry.StateRefreshFunc {
+    return func() (interface{}, string, error) {
+        output, err := /* ... AWS Go SDK operation to fetch resource/status ... */
+
+        if tfawserr.ErrCodeEquals(err, example.ErrCodeResourceNotFoundException) {
+            return nil, "", nil
+        }
+
+        if err != nil {
+            return nil, "", err
+        }
+
+        if output == nil {
+            return nil, "", nil
+        }
+
+        return output, aws.StringValue(output.Status), nil
+    }
+}
+
+
// internal/service/example/wait.go (created if does not exist)
+
+const (
+    ThingCreationTimeout = 2 * time.Minute
+    ThingDeletionTimeout = 5 * time.Minute
+)
+
+// ThingCreated is a resource waiter for Thing creation
+func ThingCreated(conn *example.Example, id string) (*example.Thing, error) {
+    stateConf := &retry.StateChangeConf{
+        Pending: []string{example.StatusCreating},
+        Target:  []string{example.StatusCreated},
+        Refresh: ThingStatus(conn, id),
+        Timeout: ThingCreationTimeout,
+    }
+
+    outputRaw, err := stateConf.WaitForState()
+
+    if output, ok := outputRaw.(*example.Thing); ok {
+        return output, err
+    }
+
+    return nil, err
+}
+
+// ThingDeleted is a resource waiter for Thing deletion
+func ThingDeleted(conn *example.Example, id string) (*example.Thing, error) {
+    stateConf := &retry.StateChangeConf{
+        Pending: []string{example.StatusDeleting},
+        Target:  []string{}, // Use empty list if the resource disappears and does not have "deleted" status
+        Refresh: ThingStatus(conn, id),
+        Timeout: ThingDeletionTimeout,
+    }
+
+    outputRaw, err := stateConf.WaitForState()
+
+    if output, ok := outputRaw.(*example.Thing); ok {
+        return output, err
+    }
+
+    return nil, err
+}
+
+
// internal/service/{service}/{thing}.go
+
+function ExampleThingCreate(d *schema.ResourceData, meta interface{}) error {
+    // ... AWS Go SDK logic to create resource ...
+
+    if _, err := ThingCreated(conn, d.Id()); err != nil {
+        return fmt.Errorf("waiting for Example Thing (%s) creation: %w", d.Id(), err)
+    }
+
+    return ExampleThingRead(d, meta)
+}
+
+function ExampleThingDelete(d *schema.ResourceData, meta interface{}) error {
+    // ... AWS Go SDK logic to delete resource ...
+
+    if _, err := ThingDeleted(conn, d.Id()); err != nil {
+        return fmt.Errorf("waiting for Example Thing (%s) deletion: %w", d.Id(), err)
+    }
+
+    return ExampleThingRead(d, meta)
+}
+
+

Typically, the AWS Go SDK should include constants for various status field values (e.g., StatusCreating for CREATING). If not, create them in a file named internal/service/{SERVICE}/consts.go.

+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/running-and-writing-acceptance-tests/index.html b/running-and-writing-acceptance-tests/index.html new file mode 100644 index 000000000000..02a1cd335f27 --- /dev/null +++ b/running-and-writing-acceptance-tests/index.html @@ -0,0 +1,3073 @@ + + + + + + + + + + + + + + + + + + + + + + + + Acceptance Tests - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Running and Writing Acceptance Tests#

+

Terraform includes an acceptance test harness that does most of the repetitive +work involved in testing a resource. For additional information about testing +Terraform Providers, see the SDKv2 documentation.

+

Acceptance Tests Often Cost Money to Run#

+

Our acceptance test suite creates real resources, and as a result they cost real money to run. +Because the resources only exist for a short period of time, the total amount +of money required is usually a relatively small amount. That said there are particular services +which are very expensive to run and its important to be prepared for those costs.

+

Some services which can be cost prohibitive include (among others):

+
    +
  • WorkSpaces
  • +
  • Glue
  • +
  • OpenSearch
  • +
  • RDS
  • +
  • ACM (Amazon Certificate Manager)
  • +
  • FSx
  • +
  • Kinesis Analytics
  • +
  • EC2
  • +
  • ElastiCache
  • +
  • Storage Gateway
  • +
+

We don't want financial limitations to be a barrier to contribution, so if you are unable to +pay to run acceptance tests for your contribution, mention this in your +pull request. We will happily accept "best effort" implementations of +acceptance tests and run them for you on our side. This might mean that your PR +takes a bit longer to merge, but it most definitely is not a blocker for +contributions.

+

Running an Acceptance Test#

+

Acceptance tests can be run using the testacc target in the Terraform +Makefile. The individual tests to run can be controlled using a regular +expression. Prior to running the tests provider configuration details such as +access keys must be made available as environment variables.

+

For example, to run an acceptance test against the Amazon Web Services +provider, the following environment variables must be set:

+
# Using a profile
+export AWS_PROFILE=...
+# Otherwise
+export AWS_ACCESS_KEY_ID=...
+export AWS_SECRET_ACCESS_KEY=...
+export AWS_DEFAULT_REGION=...
+
+

Please note that the default region for the testing is us-west-2 and must be +overridden via the AWS_DEFAULT_REGION environment variable, if necessary. This +is especially important for testing AWS GovCloud (US), which requires:

+
export AWS_DEFAULT_REGION=us-gov-west-1
+
+

Tests can then be run by specifying a regular expression defining the tests to +run and the package in which the tests are defined:

+
$ make testacc TESTS=TestAccCloudWatchDashboard_updateName PKG=cloudwatch
+==> Checking that code complies with gofmt requirements...
+TF_ACC=1 go test ./internal/service/cloudwatch/... -v -count 1 -parallel 20 -run=TestAccCloudWatchDashboard_updateName -timeout 180m
+=== RUN   TestAccCloudWatchDashboard_updateName
+=== PAUSE TestAccCloudWatchDashboard_updateName
+=== CONT  TestAccCloudWatchDashboard_updateName
+--- PASS: TestAccCloudWatchDashboard_updateName (25.33s)
+PASS
+ok      github.com/hashicorp/terraform-provider-aws/internal/service/cloudwatch 25.387s
+
+

Entire resource test suites can be targeted by using the naming convention to +write the regular expression. For example, to run all tests of the +aws_cloudwatch_dashboard resource rather than just the updateName test, you +can start testing like this:

+
$ make testacc TESTS=TestAccCloudWatchDashboard PKG=cloudwatch
+==> Checking that code complies with gofmt requirements...
+TF_ACC=1 go test ./internal/service/cloudwatch/... -v -count 1 -parallel 20 -run=TestAccCloudWatchDashboard -timeout 180m
+=== RUN   TestAccCloudWatchDashboard_basic
+=== PAUSE TestAccCloudWatchDashboard_basic
+=== RUN   TestAccCloudWatchDashboard_update
+=== PAUSE TestAccCloudWatchDashboard_update
+=== RUN   TestAccCloudWatchDashboard_updateName
+=== PAUSE TestAccCloudWatchDashboard_updateName
+=== CONT  TestAccCloudWatchDashboard_basic
+=== CONT  TestAccCloudWatchDashboard_updateName
+=== CONT  TestAccCloudWatchDashboard_update
+--- PASS: TestAccCloudWatchDashboard_basic (15.83s)
+--- PASS: TestAccCloudWatchDashboard_updateName (26.69s)
+--- PASS: TestAccCloudWatchDashboard_update (27.72s)
+PASS
+ok      github.com/hashicorp/terraform-provider-aws/internal/service/cloudwatch 27.783s
+
+

Running acceptance tests requires version 0.12.26 or higher of the Terraform CLI to be installed.

+

For advanced developers, the acceptance testing framework accepts some additional environment variables that can be used to control Terraform CLI binary selection, logging, and other behaviors. See the SDKv2 documentation for more information.

+

Please Note: On macOS 10.14 and later (and some Linux distributions), the default user open file limit is 256. This may cause unexpected issues when running the acceptance testing since this can prevent various operations from occurring such as opening network connections to AWS. To view this limit, the ulimit -n command can be run. To update this limit, run ulimit -n 1024 (or higher).

+

Running Cross-Account Tests#

+

Certain testing requires multiple AWS accounts. This additional setup is not typically required and the testing will return an error (shown below) if your current setup does not have the secondary AWS configuration:

+
$ make testacc TESTS=TestAccRDSInstance_DBSubnetGroupName_ramShared PKG=rds
+TF_ACC=1 go test ./internal/service/rds/... -v -count 1 -parallel 20 -run=TestAccRDSInstance_DBSubnetGroupName_ramShared -timeout 180m
+=== RUN   TestAccRDSInstance_DBSubnetGroupName_ramShared
+=== PAUSE TestAccRDSInstance_DBSubnetGroupName_ramShared
+=== CONT  TestAccRDSInstance_DBSubnetGroupName_ramShared
+    acctest.go:674: skipping test because at least one environment variable of [AWS_ALTERNATE_PROFILE AWS_ALTERNATE_ACCESS_KEY_ID] must be set. Usage: credentials for running acceptance testing in alternate AWS account.
+--- SKIP: TestAccRDSInstance_DBSubnetGroupName_ramShared (0.85s)
+PASS
+ok      github.com/hashicorp/terraform-provider-aws/internal/service/rds        0.888s
+
+

Running these acceptance tests is the same as before, except the following additional AWS credential information is required:

+
# Using a profile
+export AWS_ALTERNATE_PROFILE=...
+# Otherwise
+export AWS_ALTERNATE_ACCESS_KEY_ID=...
+export AWS_ALTERNATE_SECRET_ACCESS_KEY=...
+
+

Running Cross-Region Tests#

+

Certain testing requires multiple AWS regions. Additional setup is not typically required because the testing defaults the second AWS region to us-east-1 and the third AWS region to us-east-2.

+

Running these acceptance tests is the same as before, but if you wish to override the second and third regions:

+
export AWS_ALTERNATE_REGION=...
+export AWS_THIRD_REGION=...
+
+

Running Only Short Tests#

+

Some tests have been manually marked as long-running (longer than 300 seconds) and can be skipped using the -short flag. However, we are adding long-running guards little by little and many services have no guarded tests.

+

Where guards have been implemented, do not always skip long-running tests. However, for intermediate test runs during development, or to verify functionality unrelated to the specific long-running tests, skipping long-running tests makes work more efficient. We recommend that for the final test run before submitting a PR that you run affected tests without the -short flag.

+

If you want to run only short-running tests, you can use either one of these equivalent statements. Note the use of -short.

+

For example:

+
$ make testacc TESTS='TestAccECSTaskDefinition_' PKG=ecs TESTARGS=-short
+
+

Or:

+
$ TF_ACC=1 go test ./internal/service/ecs/... -v -count 1 -parallel 20 -run='TestAccECSTaskDefinition_' -short -timeout 180m
+
+

Writing an Acceptance Test#

+

Terraform has a framework for writing acceptance tests which minimizes the +amount of boilerplate code necessary to use common testing patterns. This guide is meant to augment the general SDKv2 documentation with Terraform AWS Provider specific conventions and helpers.

+

Anatomy of an Acceptance Test#

+

This section describes in detail how the Terraform acceptance testing framework operates with respect to the Terraform AWS Provider. We recommend those unfamiliar with this provider, or Terraform resource testing in general, take a look here first to generally understand how we interact with AWS and the resource code to verify functionality.

+

The entry point to the framework is the resource.ParallelTest() function. This wraps our testing to work with the standard Go testing framework, while also preventing unexpected usage of AWS by requiring the TF_ACC=1 environment variable. This function accepts a TestCase parameter, which has all the details about the test itself. For example, this includes the test steps (TestSteps) and how to verify resource deletion in the API after all steps have been run (CheckDestroy).

+

Each TestStep proceeds by applying some +Terraform configuration using the provider under test, and then verifying that +results are as expected by making assertions using the provider API. It is +common for a single test function to exercise both the creation of and updates +to a single resource. Most tests follow a similar structure.

+
    +
  1. Pre-flight checks are made to ensure that sufficient provider configuration + is available to be able to proceed - for example in an acceptance test + targeting AWS, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set prior + to running acceptance tests. This is common to all tests exercising a single + provider.
  2. +
+

Most assertion +functions are defined out of band with the tests. This keeps the tests +readable, and allows reuse of assertion functions across different tests of the +same type of resource. The definition of a complete test looks like this:

+
func TestAccCloudWatchDashboard_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+    var dashboard cloudwatch.GetDashboardOutput
+    rInt := acctest.RandInt()
+
+    resource.ParallelTest(t, resource.TestCase{
+        PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+        ErrorCheck:               acctest.ErrorCheck(t, cloudwatch.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+        CheckDestroy:             testAccCheckDashboardDestroy(ctx),
+        Steps: []resource.TestStep{
+            {
+                Config: testAccDashboardConfig(rInt),
+                Check: resource.ComposeTestCheckFunc(
+                    testAccCheckDashboardExists(ctx, "aws_cloudwatch_dashboard.foobar", &dashboard),
+                    resource.TestCheckResourceAttr("aws_cloudwatch_dashboard.foobar", "dashboard_name", testAccDashboardName(rInt)),
+                ),
+            },
+        },
+    })
+}
+
+

When executing the test, the following steps are taken for each TestStep:

+
    +
  1. +

    The Terraform configuration required for the test is applied. This is + responsible for configuring the resource under test, and any dependencies it + may have. For example, to test the aws_cloudwatch_dashboard resource, a valid configuration with the requisite fields is required. This results in configuration which looks like this:

    +
    resource "aws_cloudwatch_dashboard" "foobar" {
    +  dashboard_name = "terraform-test-dashboard-%[1]d"
    +  dashboard_body = <<EOF
    +  {
    +    "widgets": [{
    +      "type": "text",
    +      "x": 0,
    +      "y": 0,
    +      "width": 6,
    +      "height": 6,
    +      "properties": {
    +        "markdown": "Hi there from Terraform: CloudWatch"
    +      }
    +    }]
    +  }
    +  EOF
    +}
    +
    +
  2. +
  3. +

    Assertions are run using the provider API. These use the provider API + directly rather than asserting against the resource state. For example, to + verify that the aws_cloudwatch_dashboard described above was created + successfully, a test function like this is used:

    +
    func testAccCheckDashboardExists(ctx context.Context, n string, dashboard *cloudwatch.GetDashboardOutput) resource.TestCheckFunc {
    +  return func(s *terraform.State) error {
    +    rs, ok := s.RootModule().Resources[n]
    +    if !ok {
    +      return fmt.Errorf("Not found: %s", n)
    +    }
    +
    +    conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx)
    +    params := cloudwatch.GetDashboardInput{
    +      DashboardName: aws.String(rs.Primary.ID),
    +    }
    +
    +    resp, err := conn.GetDashboardWithContext(ctx, &params)
    +    if err != nil {
    +      return err
    +    }
    +
    +    *dashboard = *resp
    +
    +    return nil
    +  }
    +}
    +
    +
  4. +
+

Notice that the only information used from the Terraform state is the ID of + the resource. For computed properties, we instead assert that the value saved in the Terraform state was the + expected value if possible. The testing framework provides helper functions + for several common types of check - for example:

+
```go
+resource.TestCheckResourceAttr("aws_cloudwatch_dashboard.foobar", "dashboard_name", testAccDashboardName(rInt)),
+```
+
+
    +
  1. +

    The resources created by the test are destroyed. This step happens + automatically, and is the equivalent of calling terraform destroy.

    +
  2. +
  3. +

    Assertions are made against the provider API to verify that the resources + have indeed been removed. If these checks fail, the test fails and reports + "dangling resources". The code to ensure that the aws_cloudwatch_dashboard shown + above has been destroyed looks like this:

    +
    func testAccCheckDashboardDestroy(ctx context.Context) resource.TestCheckFunc {
    +    return func(s *terraform.State) error {
    +    conn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx)
    +
    +    for _, rs := range s.RootModule().Resources {
    +      if rs.Type != "aws_cloudwatch_dashboard" {
    +        continue
    +      }
    +
    +      params := cloudwatch.GetDashboardInput{
    +        DashboardName: aws.String(rs.Primary.ID),
    +      }
    +
    +      _, err := conn.GetDashboardWithContext(ctx, &params)
    +      if err == nil {
    +        return fmt.Errorf("Dashboard still exists: %s", rs.Primary.ID)
    +      }
    +      if !isDashboardNotFoundErr(err) {
    +        return err
    +      }
    +    }
    +
    +    return nil
    +  }
    +}
    +
    +
  4. +
+

These functions usually test only for the resource directly under test.

+

Resource Acceptance Testing#

+

Most resources that implement standard Create, Read, Update, and Delete functionality should follow the pattern below. Each test type has a section that describes them in more detail:

+
    +
  • basic: This represents the bare minimum verification that the resource can be created, read, deleted, and optionally imported.
  • +
  • disappears: A test that verifies Terraform will offer to recreate a resource if it is deleted outside of Terraform (e.g., via the Console) instead of returning an error that it cannot be found.
  • +
  • Per Attribute: A test that verifies the resource with a single additional argument can be created, read, optionally updated (or force resource recreation), deleted, and optionally imported.
  • +
+

The leading sections below highlight additional recommended patterns.

+

Test Configurations#

+

Most of the existing test configurations you will find in the Terraform AWS Provider are written in the following function-based style:

+
func TestAccExampleThing_basic(t *testing.T) {
+  // ... omitted for brevity ...
+
+  resource.ParallelTest(t, resource.TestCase{
+    // ... omitted for brevity ...
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleThingConfig(),
+        // ... omitted for brevity ...
+      },
+    },
+  })
+}
+
+func testAccExampleThingConfig() string {
+  return `
+resource "aws_example_thing" "test" {
+  # ... omitted for brevity ...
+}
+`
+}
+
+

Even when no values need to be passed in to the test configuration, we have found this setup to be the most flexible for allowing that to be easily implemented. Any configurable values are handled via fmt.Sprintf(). Using text/template or other templating styles is explicitly forbidden.

+

For consistency, resources in the test configuration should be named resource "..." "test" unless multiple of that resource are necessary.

+

We discourage re-using test configurations across test files (except for some common configuration helpers we provide) as it is much harder to discover potential testing regressions.

+

Please also note that the newline on the first line of the configuration (before resource) and the newline after the last line of configuration (after }) are important to allow test configurations to be easily combined without generating Terraform configuration language syntax errors.

+

Test Configuration Independence#

+

Across the entire provider, all test configurations should be as indepedent from each other as possible. For example, a common place this concept comes up is with the default VPC. Since we have tests that reconfigure the default VPC, if your configuration requires a VPC, it should not rely on the default VPC. Instead, include a VPC that will be created and destroyed as part of the test.

+

Make sure that your test configuration:

+
    +
  1. Includes everything required for Terraform to run the test
  2. +
  3. Does not assume that any user-managed infrastructure will be in place, such as S3 buckets, IAM roles, KMS keys, VPCs, subnets, etc.
  4. +
+

Combining Test Configurations#

+

We include a helper function, acctest.ConfigCompose() for iteratively building and chaining test configurations together. It accepts any number of configurations to combine them. This simplifies a single resource's testing by allowing the creation of a "base" test configuration for all the other test configurations (if necessary) and also allows the maintainers to curate common configurations. Each of these is described in more detail in below sections.

+

Please note that we do discourage excessive chaining of configurations such as implementing multiple layers of "base" configurations. Usually these configurations are harder for maintainers and other future readers to understand due to the multiple levels of indirection.

+
Base Test Configurations#
+

If a resource requires the same Terraform configuration as a prerequisite for all test configurations, then a common pattern is implementing a "base" test configuration that is combined with each test configuration.

+

For example:

+
func testAccExampleThingConfigBase() string {
+  return `
+resource "aws_iam_role" "test" {
+  # ... omitted for brevity ...
+}
+
+resource "aws_iam_role_policy" "test" {
+  # ... omitted for brevity ...
+}
+`
+}
+
+func testAccExampleThingConfig() string {
+  return acctest.ConfigCompose(
+    testAccExampleThingConfigBase(),
+    `
+resource "aws_example_thing" "test" {
+  # ... omitted for brevity ...
+}
+`)
+}
+
+
Available Common Test Configurations#
+

These test configurations are typical implementations we have found or allow testing to implement best practices easier, since the Terraform AWS Provider testing is expected to run against various AWS Regions and Partitions.

+
    +
  • acctest.AvailableEC2InstanceTypeForRegion("type1", "type2", ...): Typically used to replace hardcoded EC2 Instance Types. Uses aws_ec2_instance_type_offering data source to return an available EC2 Instance Type in preferred ordering. Reference the instance type via: data.aws_ec2_instance_type_offering.available.instance_type. Use acctest.AvailableEC2InstanceTypeForRegionNamed("name", "type1", "type2", ...) to specify a name for the data source
  • +
  • acctest.ConfigLatestAmazonLinuxHVMEBSAMI(): Typically used to replace hardcoded EC2 Image IDs (ami-12345678). Uses aws_ami data source to find the latest Amazon Linux image. Reference the AMI ID via: data.aws_ami.amzn-ami-minimal-hvm-ebs.id
  • +
+

Randomized Naming#

+

For AWS resources that require unique naming, the tests should implement a randomized name, typically coded as a rName variable in the test and passed as a parameter to creating the test configuration.

+

For example:

+
func TestAccExampleThing_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  // ... omitted for brevity ...
+
+  resource.ParallelTest(t, resource.TestCase{
+    // ... omitted for brevity ...
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleThingConfigName(rName),
+        // ... omitted for brevity ...
+      },
+    },
+  })
+}
+
+func testAccExampleThingConfigName(rName string) string {
+  return fmt.Sprintf(`
+resource "aws_example_thing" "test" {
+  name = %[1]q
+}
+`, rName)
+}
+
+

Typically the rName is always the first argument to the test configuration function, if used, for consistency.

+

Note that if rName (or any other variable) is used multiple times in the fmt.Sprintf() statement, do not repeat rName in the fmt.Sprintf() arguments. Using fmt.Sprintf(..., rName, rName), for example, would not be correct. Instead, use the indexed %[1]q (or %[x]q, %[x]s, %[x]t, or %[x]d, where x represents the index number) verb multiple times. For example:

+
func testAccExampleThingConfigName(rName string) string {
+  return fmt.Sprintf(`
+resource "aws_example_thing" "test" {
+  name = %[1]q
+
+  tags = {
+    Name = %[1]q
+  }
+}
+`, rName)
+}
+
+ +

We also typically recommend saving a resourceName variable in the test that contains the resource reference, e.g., aws_example_thing.test, which is repeatedly used in the checks.

+

For example:

+
func TestAccExampleThing_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+  // ... omitted for brevity ...
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    // ... omitted for brevity ...
+    Steps: []resource.TestStep{
+      {
+        // ... omitted for brevity ...
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleThingExists(ctx, resourceName),
+          acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "example", fmt.Sprintf("thing/%s", rName)),
+          resource.TestCheckResourceAttr(resourceName, "description", ""),
+          resource.TestCheckResourceAttr(resourceName, "name", rName),
+        ),
+      },
+      {
+        ResourceName:      resourceName,
+        ImportState:       true,
+        ImportStateVerify: true,
+      },
+    },
+  })
+}
+
+// below all TestAcc functions
+
+func testAccExampleThingConfigName(rName string) string {
+  return fmt.Sprintf(`
+resource "aws_example_thing" "test" {
+  name = %[1]q
+}
+`, rName)
+}
+
+

Basic Acceptance Tests#

+

Usually this test is implemented first. The test configuration should contain only required arguments (Required: true attributes) and it should check the values of all read-only attributes (Computed: true without Optional: true). If the resource supports it, it verifies import. It should NOT perform other TestStep such as updates or verify recreation.

+

These are typically named TestAcc{SERVICE}{THING}_basic, e.g., TestAccCloudWatchDashboard_basic

+

For example:

+
func TestAccExampleThing_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+    CheckDestroy:             testAccCheckExampleThingDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleThingConfigName(rName),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleThingExists(ctx, resourceName),
+          acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "example", fmt.Sprintf("thing/%s", rName)),
+          resource.TestCheckResourceAttr(resourceName, "description", ""),
+          resource.TestCheckResourceAttr(resourceName, "name", rName),
+        ),
+      },
+      {
+        ResourceName:      resourceName,
+        ImportState:       true,
+        ImportStateVerify: true,
+      },
+    },
+  })
+}
+
+// below all TestAcc functions
+
+func testAccExampleThingConfigName(rName string) string {
+  return fmt.Sprintf(`
+resource "aws_example_thing" "test" {
+  name = %[1]q
+}
+`, rName)
+}
+
+

PreChecks#

+

Acceptance test cases have a PreCheck. The PreCheck ensures that the testing environment meets certain preconditions. If the environment does not meet the preconditions, Go skips the test. Skipping a test avoids reporting a failure and wasting resources where the test cannot succeed.

+

Here is an example of the default PreCheck:

+
func TestAccExampleThing_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:     func() { acctest.PreCheck(ctx, t) },
+    // ... additional checks follow ...
+  })
+}
+
+

Extend the default PreCheck by adding calls to functions in the anonymous PreCheck function. The functions can be existing functions in the provider or custom functions you add for new capabilities.

+
Standard Provider PreChecks#
+

If you add a new test that has preconditions which are checked by an existing provider function, use that standard PreCheck instead of creating a new one. Some existing tests are missing standard PreChecks and you can help by adding them where appropriate.

+

These are some of the standard provider PreChecks:

+
    +
  • acctest.PreCheckRegion(t *testing.T, regions ...string) checks that the test region is one of the specified AWS Regions.
  • +
  • acctest.PreCheckRegionNot(t *testing.T, regions ...string) checks that the test region is not one of the specified AWS Regions.
  • +
  • acctest.PreCheckAlternateRegionIs(t *testing.T, region string) checks that the alternate test region is the specified AWS Region.
  • +
  • acctest.PreCheckPartition(t *testing.T, partition string) checks that the test partition is the specified partition.
  • +
  • acctest.PreCheckPartitionNot(t *testing.T, partitions ...string) checks that the test partition is not one of the specified partitions.
  • +
  • acctest.PreCheckPartitionHasService(t *testing.T, serviceID string) checks whether the current partition lists the service as part of its offerings. Note: AWS may not add new or public preview services to the service list immediately. This function will return a false positive in that case.
  • +
  • acctest.PreCheckOrganizationsAccount(ctx context.Context, t *testing.T) checks whether the current account can perform AWS Organizations tests.
  • +
  • acctest.PreCheckAlternateAccount(t *testing.T) checks whether the environment is set up for tests across accounts.
  • +
  • acctest.PreCheckMultipleRegion(t *testing.T, regions int) checks whether the environment is set up for tests across regions.
  • +
+

This is an example of using a standard PreCheck function. For an established service, such as WAF or FSx, use acctest.PreCheckPartitionHasService() and the service endpoint ID to check that a partition supports the service.

+
func TestAccExampleThing_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:     func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, waf.EndpointsID) },
+    // ... additional checks follow ...
+  })
+}
+
+
Custom PreChecks#
+

In situations where standard PreChecks do not test for the required preconditions, create a custom PreCheck.

+

Below is an example of adding a custom PreCheck function. For a new or preview service that AWS does not include in the partition service list yet, you can verify the existence of the service with a simple read-only request (e.g., list all X service things). (For acceptance tests of established services, use acctest.PreCheckPartitionHasService() instead.)

+
func TestAccExampleThing_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:     func() { acctest.PreCheck(ctx, t), testAccPreCheckExample(ctx, t) },
+    // ... additional checks follow ...
+  })
+}
+
+func testAccPreCheckExample(ctx context.Context, t *testing.T) {
+  conn := acctest.Provider.Meta().(*conns.AWSClient).ExampleConn(ctx)
+    input := &example.ListThingsInput{}
+    _, err := conn.ListThingsWithContext(ctx, input)
+    if testAccPreCheckSkipError(err) {
+        t.Skipf("skipping acceptance testing: %s", err)
+    }
+    if err != nil {
+        t.Fatalf("unexpected PreCheck error: %s", err)
+    }
+}
+
+

ErrorChecks#

+

Acceptance test cases have an ErrorCheck. The ErrorCheck provides a chance to take a look at errors before the test fails. While most errors should result in test failure, some should not. For example, an error that indicates an API operation is not supported in a particular region should cause the test to skip instead of fail. Since errors should flow through the ErrorCheck, do not handle the vast majority of failing conditions. Instead, in ErrorCheck, focus on the rare errors that should cause a test to skip, or in other words, be ignored.

+
Common ErrorCheck#
+

In many situations, the common ErrorCheck is sufficient. It will skip tests for several normal occurrences such as when AWS reports a feature is not supported in the current region.

+

Here is an example of the common ErrorCheck:

+
func TestAccExampleThing_basic(t *testing.T) {
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    // PreCheck
+    ErrorCheck:   acctest.ErrorCheck(t, service.EndpointsID),
+    // ... additional checks follow ...
+  })
+}
+
+
Service-Specific ErrorChecks#
+

However, some services have special conditions that aren't caught by the common ErrorCheck. In these cases, you can create a service-specific ErrorCheck.

+

To add a service-specific ErrorCheck, follow these steps:

+
    +
  1. Make sure there is not already an ErrorCheck for the service you have in mind. For example, search the codebase for acctest.RegisterServiceErrorCheckFunc(service.EndpointsID replacing "service" with the package name of the service you're working on (e.g., ec2). If there is already an ErrorCheck for the service, add to the existing service-specific ErrorCheck.
  2. +
  3. Create the service-specific ErrorCheck in an _test.go file for the service. See the example below.
  4. +
  5. Register the new service-specific ErrorCheck in the init() at the top of the _test.go file. See the example below.
  6. +
+

An example of adding a service-specific ErrorCheck:

+
// just after the imports, create or add to the init() function
+func init() {
+  acctest.RegisterServiceErrorCheck(service.EndpointsID, testAccErrorCheckSkipService)
+}
+
+// ... additional code and tests ...
+
+// this is the service-specific ErrorCheck
+func testAccErrorCheckSkipService(t *testing.T) resource.ErrorCheckFunc {
+    return acctest.ErrorCheckSkipMessagesContaining(t,
+        "Error message specific to the service that indicates unsupported features",
+        "You can include from one to many portions of error messages",
+        "Be careful to not inadvertently capture errors that should not be skipped",
+    )
+}
+
+

Long-Running Test Guards#

+

For any acceptance tests that typically run longer than 300 seconds (5 minutes), add a -short test guard at the top of the test function.

+

For example:

+
func TestAccExampleThing_longRunningTest(t *testing.T) {
+  if testing.Short() {
+    t.Skip("skipping long-running test in short mode")
+  }
+
+  // ... omitted for brevity ...
+
+  resource.ParallelTest(t, resource.TestCase{
+    // ... omitted for brevity ...
+  })
+}
+
+

When running acceptances tests, tests with these guards can be skipped using the Go -short flag. See Running Only Short Tests for examples.

+

Disappears Acceptance Tests#

+

This test is generally implemented second. It is straightforward to setup once the basic test is passing since it can reuse that test configuration. It prevents a common bug report with Terraform resources that error when they can not be found (e.g., deleted outside Terraform).

+

These are typically named TestAcc{SERVICE}{THING}_disappears, e.g., TestAccCloudWatchDashboard_disappears

+

For example:

+
func TestAccExampleThing_disappears(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+    CheckDestroy:             testAccCheckExampleThingDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleThingConfigName(rName),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleThingExists(ctx, resourceName, &job),
+          acctest.CheckResourceDisappears(ctx, acctest.Provider, ResourceExampleThing(), resourceName),
+        ),
+        ExpectNonEmptyPlan: true,
+      },
+    },
+  })
+}
+
+

If this test does fail, the fix for this is generally adding error handling immediately after the Read API call that catches the error and tells Terraform to remove the resource before returning the error:

+
output, err := conn.GetThing(input)
+
+if !d.IsNewResource() && tfresource.NotFound(err) {
+  log.Printf("[WARN] Example Thing (%s) not found, removing from state", d.Id())
+  d.SetId("")
+  return nil
+}
+
+if err != nil {
+  return fmt.Errorf("reading Example Thing (%s): %w", d.Id(), err)
+}
+
+

For children resources that are encapsulated by a parent resource, it is also preferable to verify that removing the parent resource will not generate an error either. These are typically named TestAcc{SERVICE}{THING}_disappears_{PARENT}, e.g., TestAccRoute53ZoneAssociation_disappears_Vpc

+
func TestAccExampleChildThing_disappears_ParentThing(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  parentResourceName := "aws_example_parent_thing.test"
+  resourceName := "aws_example_child_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+    CheckDestroy:             testAccCheckExampleChildThingDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleThingConfigName(rName),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleThingExists(ctx, resourceName),
+          acctest.CheckResourceDisappears(ctx, acctest.Provider, ResourceExampleParentThing(), parentResourceName),
+        ),
+        ExpectNonEmptyPlan: true,
+      },
+    },
+  })
+}
+
+

Per Attribute Acceptance Tests#

+

These are typically named TestAcc{SERVICE}{THING}_{ATTRIBUTE}, e.g., TestAccCloudWatchDashboard_Name

+

For example:

+
func TestAccExampleThing_Description(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+        ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+    CheckDestroy:             testAccCheckExampleThingDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleThingConfigDescription(rName, "description1"),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleThingExists(ctx, resourceName),
+          resource.TestCheckResourceAttr(resourceName, "description", "description1"),
+        ),
+      },
+      {
+        ResourceName:      resourceName,
+        ImportState:       true,
+        ImportStateVerify: true,
+      },
+      {
+        Config: testAccExampleThingConfigDescription(rName, "description2"),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleThingExists(ctx, resourceName),
+          resource.TestCheckResourceAttr(resourceName, "description", "description2"),
+        ),
+      },
+    },
+  })
+}
+
+// below all TestAcc functions
+
+func testAccExampleThingConfigDescription(rName string, description string) string {
+  return fmt.Sprintf(`
+resource "aws_example_thing" "test" {
+  description = %[2]q
+  name        = %[1]q
+}
+`, rName, description)
+}
+
+

Cross-Account Acceptance Tests#

+

When testing requires AWS infrastructure in a second AWS account, the below changes to the normal setup will allow the management or reference of resources and data sources across accounts:

+
    +
  • In the PreCheck function, include acctest.PreCheckOrganizationsAccount(ctx, t) to ensure a standardized set of information is required for cross-account testing credentials
  • +
  • Switch usage of ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories to ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t)
  • +
  • Add acctest.ConfigAlternateAccountProvider() to the test configuration and use provider = awsalternate for cross-account resources. The resource that is the focus of the acceptance test should not use the alternate provider identification to simplify the testing setup.
  • +
  • For any TestStep that includes ImportState: true, add the Config that matches the previous TestStep Config
  • +
+

An example acceptance test implementation can be seen below:

+
func TestAccExample_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+  resourceName := "aws_example.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck: func() {
+      acctest.PreCheck(ctx, t)
+      acctest.PreCheckOrganizationsAccount(ctx, t)
+    },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+    ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t),
+    CheckDestroy:             testAccCheckExampleDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleConfig(),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleExists(ctx, resourceName),
+          // ... additional checks ...
+        ),
+      },
+      {
+        Config:            testAccExampleConfig(),
+        ResourceName:      resourceName,
+        ImportState:       true,
+        ImportStateVerify: true,
+      },
+    },
+  })
+}
+
+func testAccExampleConfig() string {
+  return acctest.ConfigAlternateAccountProvider() + fmt.Sprintf(`
+# Cross account resources should be handled by the cross account provider.
+# The standardized provider block to use is awsalternate as seen below.
+resource "aws_cross_account_example" "test" {
+  provider = awsalternate
+
+  # ... configuration ...
+}
+
+# The resource that is the focus of the testing should be handled by the default provider,
+# which is automatically done by not specifying the provider configuration in the resource.
+resource "aws_example" "test" {
+  # ... configuration ...
+}
+`)
+}
+
+

Searching for usage of acctest.PreCheckOrganizationsAccount in the codebase will yield real world examples of this setup in action.

+

Cross-Region Acceptance Tests#

+

When testing requires AWS infrastructure in a second or third AWS region, the below changes to the normal setup will allow the management or reference of resources and data sources across regions:

+
    +
  • In the PreCheck function, include acctest.PreCheckMultipleRegion(t, ###) to ensure a standardized set of information is required for cross-region testing configuration. If the infrastructure in the second AWS region is also in a second AWS account also include acctest.PreCheckOrganizationsAccount(ctx, t)
  • +
  • Switch usage of ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories to ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(ctx, t, 2) (where the last parameter is number of regions, 2 or 3)
  • +
  • Add acctest.ConfigMultipleRegionProvider(###) to the test configuration and use provider = awsalternate (and potentially provider = awsthird) for cross-region resources. The resource that is the focus of the acceptance test should not use the alternative providers to simplify the testing setup. If the infrastructure in the second AWS region is also in a second AWS account use testAccAlternateAccountAlternateRegionProviderConfig() (EC2) instead
  • +
  • For any TestStep that includes ImportState: true, add the Config that matches the previous TestStep Config
  • +
+

An example acceptance test implementation can be seen below:

+
func TestAccExample_basic(t *testing.T) {
+  ctx := acctest.Context(t)
+  var providers []*schema.Provider
+  resourceName := "aws_example.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck: func() {
+      acctest.PreCheck(ctx, t)
+      acctest.PreCheckMultipleRegion(t, 2)
+    },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+    ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(ctx, t, 2),
+    CheckDestroy:             testAccCheckExampleDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleConfig(),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleExists(ctx, resourceName),
+          // ... additional checks ...
+        ),
+      },
+      {
+        Config:            testAccExampleConfig(),
+        ResourceName:      resourceName,
+        ImportState:       true,
+        ImportStateVerify: true,
+      },
+    },
+  })
+}
+
+func testAccExampleConfig() string {
+  return acctest.ConfigMultipleRegionProvider(2) + fmt.Sprintf(`
+# Cross region resources should be handled by the cross region provider.
+# The standardized provider is awsalternate as seen below.
+resource "aws_cross_region_example" "test" {
+  provider = awsalternate
+
+  # ... configuration ...
+}
+
+# The resource that is the focus of the testing should be handled by the default provider,
+# which is automatically done by not specifying the provider configuration in the resource.
+resource "aws_example" "test" {
+  # ... configuration ...
+}
+`)
+}
+
+

Searching for usage of acctest.PreCheckMultipleRegion in the codebase will yield real world examples of this setup in action.

+

Acceptance Test Concurrency#

+

Certain AWS service APIs allow a limited number of a certain component, while the acceptance testing runs at a default concurrency of twenty tests at a time. For example as of this writing, the SageMaker service only allows one SageMaker Domain per AWS Region. Running the tests with the default concurrency will fail with API errors relating to the component quota being exceeded.

+

When encountering these types of components, the acceptance testing can be setup to limit the available concurrency of that particular component. When limited to one component at a time, this may also be referred to as serializing the acceptance tests.

+

To convert to serialized (one test at a time) acceptance testing:

+
    +
  • Convert all existing capital T test functions with the limited component to begin with a lowercase t, e.g., TestAccSageMakerDomain_basic becomes testDomain_basic. This will prevent the test framework from executing these tests directly as the prefix Test is required.
      +
    • In each of these test functions, convert resource.ParallelTest to resource.Test
    • +
    +
  • +
  • Create a capital T TestAcc{Service}{Thing}_serial test function that then references all the lowercase t test functions. If multiple test files are referenced, this new test be created in a new shared file such as internal/service/{SERVICE}/{SERVICE}_test.go. The contents of this test can be setup like the following:
  • +
+
func TestAccExampleThing_serial(t *testing.T) {
+    t.Parallel()
+
+    testCases := map[string]map[string]func(t *testing.T){
+        "Thing": {
+            "basic":        testAccThing_basic,
+            "disappears":   testAccThing_disappears,
+            // ... potentially other resource tests ...
+        },
+        // ... potentially other top level resource test groups ...
+    }
+
+    acctest.RunSerialTests2Levels(t, testCases, 0)
+}
+
+

NOTE: Future iterations of these acceptance testing concurrency instructions will include the ability to handle more than one component at a time including service quota lookup, if supported by the service API.

+

Data Source Acceptance Testing#

+

Writing acceptance testing for data sources is similar to resources, with the biggest changes being:

+
    +
  • Adding DataSource to the test and configuration naming, such as TestAccExampleThingDataSource_Filter
  • +
  • The basic test may be named after the easiest lookup attribute instead, e.g., TestAccExampleThingDataSource_Name
  • +
  • No disappears testing
  • +
  • Almost all checks should be done with resource.TestCheckResourceAttrPair() to compare the data source attributes to the resource attributes
  • +
  • The usage of an additional dataSourceName variable to store a data source reference, e.g., data.aws_example_thing.test
  • +
+

Data sources testing should still use the CheckDestroy function of the resource, just to continue verifying that there are no dangling AWS resources after a test is run.

+

Please note that we do not recommend re-using test configurations between resources and their associated data source as it is harder to discover testing regressions. Authors are encouraged to potentially implement similar "base" configurations though.

+

For example:

+
func TestAccExampleThingDataSource_Name(t *testing.T) {
+  ctx := acctest.Context(t)
+  rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+  dataSourceName := "data.aws_example_thing.test"
+  resourceName := "aws_example_thing.test"
+
+  resource.ParallelTest(t, resource.TestCase{
+    PreCheck:                 func() { acctest.PreCheck(ctx, t) },
+    ErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),
+    ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
+    CheckDestroy:             testAccCheckExampleThingDestroy(ctx),
+    Steps: []resource.TestStep{
+      {
+        Config: testAccExampleThingDataSourceConfigName(rName),
+        Check: resource.ComposeTestCheckFunc(
+          testAccCheckExampleThingExists(ctx, resourceName),
+          resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"),
+          resource.TestCheckResourceAttrPair(resourceName, "description", dataSourceName, "description"),
+          resource.TestCheckResourceAttrPair(resourceName, "name", dataSourceName, "name"),
+        ),
+      },
+    },
+  })
+}
+
+// below all TestAcc functions
+
+func testAccExampleThingDataSourceConfigName(rName string) string {
+  return fmt.Sprintf(`
+resource "aws_example_thing" "test" {
+  name = %[1]q
+}
+
+data "aws_example_thing" "test" {
+  name = aws_example_thing.test.name
+}
+`, rName)
+}
+
+

Acceptance Test Sweepers#

+

When running the acceptance tests, especially when developing or troubleshooting Terraform resources, its possible for code bugs or other issues to prevent the proper destruction of AWS infrastructure. To prevent lingering resources from consuming quota or causing unexpected billing, the Terraform Plugin SDK supports the test sweeper framework to clear out an AWS region of all resources. This section is meant to augment the SDKv2 documentation on test sweepers with Terraform AWS Provider specific details.

+

Running Test Sweepers#

+

WARNING: Test Sweepers will destroy AWS infrastructure and backups in the target AWS account and region! These are designed to override any API deletion protection. Never run these outside a development AWS account that should be completely empty of resources.

+

To run the sweepers for all resources in us-west-2 and us-east-1 (default testing regions):

+
$ make sweep
+
+

To run a specific resource sweeper:

+
$ SWEEPARGS=-sweep-run=aws_example_thing make sweep
+
+

To run sweepers with an assumed role, use the following additional environment variables:

+
    +
  • TF_AWS_ASSUME_ROLE_ARN - Required.
  • +
  • TF_AWS_ASSUME_ROLE_DURATION - Optional, defaults to 1 hour (3600).
  • +
  • TF_AWS_ASSUME_ROLE_EXTERNAL_ID - Optional.
  • +
  • TF_AWS_ASSUME_ROLE_SESSION_NAME - Optional.
  • +
+

Sweeper Checklists#

+
    +
  • Add Resource Sweeper Implementation: See Writing Test Sweepers.
  • +
  • Add Service To Sweeper List: Once a sweep.go file is present in the service subdirectory, run make gen to regenerate the list of imports in internal/sweep/sweep_test.go.
  • +
+

Writing Test Sweepers#

+

Sweeper logic should be written to a file called sweep.go in the appropriate service subdirectory (internal/service/{serviceName}). This file should include the following build tags above the package declaration:

+
//go:build sweep
+// +build sweep
+
+package example
+
+

Next, initialize the resource into the test sweeper framework:

+
func init() {
+  resource.AddTestSweepers("aws_example_thing", &resource.Sweeper{
+    Name: "aws_example_thing",
+    F:    sweepThings,
+    // Optionally
+    Dependencies: []string{
+      "aws_other_thing",
+    },
+  })
+}
+
+

Then add the actual implementation. Preferably, if a paginated SDK call is available:

+
func sweepThings(region string) error {
+  ctx := sweep.Context(region)
+  client, err := sweep.SharedRegionalSweepClient(ctx, region)
+
+  if err != nil {
+    return fmt.Errorf("getting client: %w", err)
+  }
+
+  conn := client.ExampleConn(ctx)
+  sweepResources := make([]sweep.Sweepable, 0)
+  var errs *multierror.Error
+
+  input := &example.ListThingsInput{}
+
+  err = conn.ListThingsPages(input, func(page *example.ListThingsOutput, lastPage bool) bool {
+    if page == nil {
+      return !lastPage
+    }
+
+    for _, thing := range page.Things {
+      r := ResourceThing()
+      d := r.Data(nil)
+
+      id := aws.StringValue(thing.Id)
+      d.SetId(id)
+
+      // Perform resource specific pre-sweep setup.
+      // For example, you may need to perform one or more of these types of pre-sweep tasks, specific to the resource:
+      //
+      // err := sdk.ReadResource(ctx, r, d, client) // fill in data
+      // d.Set("skip_final_snapshot", true)           // set an argument in order to delete
+
+      // This "if" is only needed if the pre-sweep setup can produce errors.
+      // Otherwise, do not include it.
+      if err != nil {
+        err := fmt.Errorf("reading Example Thing (%s): %w", id, err)
+        errs = multierror.Append(errs, err)
+        continue
+      }
+
+      sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client))
+    }
+
+    return !lastPage
+  })
+
+  if sweep.SkipSweepError(err) {
+    log.Printf("[WARN] Skipping Example Thing sweep for %s: %s", region, errs)
+    return nil
+  }
+  if err != nil {
+    errs = multierror.Append(errs, fmt.Errorf("listing Example Things for %s: %w", region, err))
+  }
+
+  if err := sweep.SweepOrchestrator(sweepResources); err != nil {
+    errs = multierror.Append(errs, fmt.Errorf("sweeping Example Things for %s: %w", region, err))
+  }
+
+  return errs.ErrorOrNil()
+}
+
+

If no paginated SDK call is available, +consider generating one using the listpages generator, +or implement the sweeper as follows:

+
func sweepThings(region string) error {
+  ctx := sweep.Context(region)
+  client, err := sweep.SharedRegionalSweepClient(ctx, region)
+
+  if err != nil {
+    return fmt.Errorf("getting client: %w", err)
+  }
+
+  conn := client.ExampleConn(ctx)
+  sweepResources := make([]sweep.Sweepable, 0)
+  var errs *multierror.Error
+
+  input := &example.ListThingsInput{}
+
+  for {
+    output, err := conn.ListThings(input)
+    if sweep.SkipSweepError(err) {
+      log.Printf("[WARN] Skipping Example Thing sweep for %s: %s", region, errs)
+      return nil
+    }
+    if err != nil {
+      errs = multierror.Append(errs, fmt.Errorf("listing Example Things for %s: %w", region, err))
+      return errs.ErrorOrNil()
+    }
+
+    for _, thing := range output.Things {
+      r := ResourceThing()
+      d := r.Data(nil)
+
+      id := aws.StringValue(thing.Id)
+      d.SetId(id)
+
+      // Perform resource specific pre-sweep setup.
+      // For example, you may need to perform one or more of these types of pre-sweep tasks, specific to the resource:
+      //
+      // err := sdk.ReadResource(ctx, r, d, client) // fill in data
+      // d.Set("skip_final_snapshot", true)           // set an argument in order to delete
+
+      // This "if" is only needed if the pre-sweep setup can produce errors.
+      // Otherwise, do not include it.
+      if err != nil {
+        err := fmt.Errorf("reading Example Thing (%s): %w", id, 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 := sweep.SweepOrchestrator(sweepResources); err != nil {
+    errs = multierror.Append(errs, fmt.Errorf("sweeping Example Thing for %s: %w", region, err))
+  }
+
+  return errs.ErrorOrNil()
+}
+
+

Acceptance Test Checklists#

+

There are several aspects to writing good acceptance tests. These checklists will help ensure effective testing from the design stage through to implementation details.

+

Basic Acceptance Test Design#

+

These are basic principles to help guide the creation of acceptance tests.

+
    +
  • Covers Changes: Every line of resource or data source code added or changed should be covered by one or more tests. For example, if a resource has two ways of functioning, tests should cover both possible paths. Nearly every codebase change needs test coverage to ensure functionality and prevent future regressions. If a bug or other problem prompted a fix, a test should be added that previously would have failed, especially if the report included a configuration.
  • +
  • Follows the Single Responsibility Principle: Every test should have a single responsibility and effectively test that responsibility. This may include individual tests for verifying basic functionality of the resource (Create, Read, Delete), separately verifying using and updating a single attribute in a resource, or separately changing between two attributes to verify two "modes"/"types" possible with a resource configuration. In following this principle, test configurations should be as simple as possible. For example, not including extra configuration unless it is necessary for the specific test.
  • +
+

Test Implementation#

+

The below are required items that will be noted during submission review and prevent immediate merging:

+
    +
  • Implements CheckDestroy: Resource testing should include a CheckDestroy function (typically named testAccCheck{SERVICE}{RESOURCE}Destroy) that calls the API to verify that the Terraform resource has been deleted or disassociated as appropriate. More information about CheckDestroy functions can be found in the SDKv2 TestCase documentation.
  • +
  • Implements Exists Check Function: Resource testing should include a TestCheckFunc function (typically named testAccCheck{SERVICE}{RESOURCE}Exists) that calls the API to verify that the Terraform resource has been created or associated as appropriate. Preferably, this function will also accept a pointer to an API object representing the Terraform resource from the API response that can be set for potential usage in later TestCheckFunc. More information about these functions can be found in the SDKv2 Custom Check Functions documentation.
  • +
  • Excludes Provider Declarations: Test configurations should not include provider "aws" {...} declarations. If necessary, only the provider declarations in acctest.go should be used for multiple account/region or otherwise specialized testing.
  • +
  • Passes in us-west-2 Region: Tests default to running in us-west-2 and at a minimum should pass in that region or include necessary PreCheck functions to skip the test when ran outside an expected environment.
  • +
  • Includes ErrorCheck: All acceptance tests should include a call to the common ErrorCheck (ErrorCheck: acctest.ErrorCheck(t, service.EndpointsID),).
  • +
  • Uses resource.ParallelTest: Tests should use resource.ParallelTest() instead of resource.Test() except where serialized testing is absolutely required.
  • +
  • [ ] Uses fmt.Sprintf(): Test configurations preferably should to be separated into their own functions (typically named testAcc{SERVICE}{RESOURCE}Config{PURPOSE}) that call fmt.Sprintf() for variable injection or a string const for completely static configurations. Test configurations should avoid var or other variable injection functionality such as text/template.
  • +
  • Uses Randomized Infrastructure Naming: Test configurations that use resources where a unique name is required should generate a random name. Typically this is created via rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) in the acceptance test function before generating the configuration.
  • +
  • Prevents S3 Bucket Deletion Errors: Test configurations that use aws_s3_bucket resources as a logging destination should include the force_destroy = true configuration. This is to prevent race conditions where logging objects may be written during the testing duration which will cause BucketNotEmpty errors during deletion.
  • +
+

For resources that support import, the additional item below is required that will be noted during submission review and prevent immediate merging:

+
    +
  • Implements ImportState Testing: Tests should include an additional TestStep configuration that verifies resource import via ImportState: true and ImportStateVerify: true. This TestStep should be added to all possible tests for the resource to ensure that all infrastructure configurations are properly imported into Terraform.
  • +
+

The below are style-based items that may be noted during review and are recommended for simplicity, consistency, and quality assurance:

+
    +
  • Uses Builtin Check Functions: Tests should use already available check functions, e.g. resource.TestCheckResourceAttr(), to verify values in the Terraform state over creating custom TestCheckFunc. More information about these functions can be found in the SDKv2 Builtin Check Functions documentation.
  • +
  • Uses TestCheckResourceAttrPair() for Data Sources: Tests should use resource.TestCheckResourceAttrPair() to verify values in the Terraform state for data sources attributes to compare them with their expected resource attributes.
  • +
  • Excludes Timeouts Configurations: Test configurations should not include timeouts {...} configuration blocks except for explicit testing of customizable timeouts (typically very short timeouts with ExpectError).
  • +
  • Implements Default and Zero Value Validation: The basic test for a resource (typically named TestAcc{SERVICE}{RESOURCE}_basic) should use available check functions, e.g. resource.TestCheckResourceAttr(), to verify default and zero values in the Terraform state for all attributes. Empty/missing configuration blocks can be verified with resource.TestCheckResourceAttr(resourceName, "{ATTRIBUTE}.#", "0") and empty maps with resource.TestCheckResourceAttr(resourceName, "{ATTRIBUTE}.%", "0")
  • +
+

Avoid Hard Coding#

+

Avoid hard coding values in acceptance test checks and configurations for consistency and testing flexibility. Resource testing is expected to pass across multiple AWS environments supported by the Terraform AWS Provider (e.g., AWS Standard and AWS GovCloud (US)). Contributors are not expected or required to perform testing outside of AWS Standard, e.g., running only in the us-west-2 region is perfectly acceptable. However, contributors are expected to avoid hard coding with these guidelines.

+

Hardcoded Account IDs#

+
    +
  • Uses Account Data Sources: Any hardcoded account numbers in configuration, e.g., 137112412989, should be replaced with a data source. Depending on the situation, there are several data sources for account IDs including: +
  • +
  • Uses Account Test Checks: Any check required to verify an AWS Account ID of the current testing account or another account should use one of the following available helper functions over the usage of resource.TestCheckResourceAttrSet() and resource.TestMatchResourceAttr():
      +
    • acctest.CheckResourceAttrAccountID(): Validates the state value equals the AWS Account ID of the current account running the test. This is the most common implementation.
    • +
    • acctest.MatchResourceAttrAccountID(): Validates the state value matches any AWS Account ID (e.g. a 12 digit number). This is typically only used in data source testing of AWS managed components.
    • +
    +
  • +
+

Here's an example of using aws_caller_identity:

+
data "aws_caller_identity" "current" {}
+
+resource "aws_backup_selection" "test" {
+  plan_id      = aws_backup_plan.test.id
+  name         = "tf_acc_test_backup_selection_%[1]d"
+  iam_role_arn = "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/service-role/AWSBackupDefaultServiceRole"
+}
+
+

Hardcoded AMI IDs#

+
    +
  • Uses aws_ami Data Source: Any hardcoded AMI ID configuration, e.g. ami-12345678, should be replaced with the aws_ami data source pointing to an Amazon Linux image. The package internal/acctest includes test configuration helper functions to simplify these lookups:
      +
    • acctest.ConfigLatestAmazonLinuxHVMEBSAMI(): The recommended AMI for most situations, using Amazon Linux, HVM virtualization, and EBS storage. To reference the AMI ID in the test configuration: data.aws_ami.amzn-ami-minimal-hvm-ebs.id.
    • +
    • testAccLatestAmazonLinuxHVMInstanceStoreAMIConfig() (EC2): AMI lookup using Amazon Linux, HVM virtualization, and Instance Store storage. Should only be used in testing that requires Instance Store storage rather than EBS. To reference the AMI ID in the test configuration: data.aws_ami.amzn-ami-minimal-hvm-instance-store.id.
    • +
    • testAccLatestAmazonLinuxPVEBSAMIConfig() (EC2): AMI lookup using Amazon Linux, Paravirtual virtualization, and EBS storage. Should only be used in testing that requires Paravirtual over Hardware Virtual Machine (HVM) virtualization. To reference the AMI ID in the test configuration: data.aws_ami.amzn-ami-minimal-pv-ebs.id.
    • +
    • configLatestAmazonLinuxPvInstanceStoreAmi (EC2): AMI lookup using Amazon Linux, Paravirtual virtualization, and Instance Store storage. Should only be used in testing that requires Paravirtual virtualization over HVM and Instance Store storage over EBS. To reference the AMI ID in the test configuration: data.aws_ami.amzn-ami-minimal-pv-instance-store.id.
    • +
    • testAccLatestWindowsServer2016CoreAMIConfig() (EC2): AMI lookup using Windows Server 2016 Core, HVM virtualization, and EBS storage. Should only be used in testing that requires Windows. To reference the AMI ID in the test configuration: data.aws_ami.win2016core-ami.id.
    • +
    +
  • +
+

Here's an example of using acctest.ConfigLatestAmazonLinuxHVMEBSAMI() and data.aws_ami.amzn-ami-minimal-hvm-ebs.id:

+
func testAccLaunchConfigurationDataSourceConfig_basic(rName string) string {
+    return acctest.ConfigCompose(
+        acctest.ConfigLatestAmazonLinuxHVMEBSAMI(),
+        fmt.Sprintf(`
+resource "aws_launch_configuration" "test" {
+  name          = %[1]q
+  image_id      = data.aws_ami.amzn-ami-minimal-hvm-ebs.id
+  instance_type = "m1.small"
+}
+`, rName))
+}
+
+

Hardcoded Availability Zones#

+
    +
  • Uses aws_availability_zones Data Source: Any hardcoded AWS Availability Zone configuration, e.g. us-west-2a, should be replaced with the aws_availability_zones data source. Use the convenience function called acctest.ConfigAvailableAZsNoOptIn() (defined in internal/acctest/acctest.go) to declare data "aws_availability_zones" "available" {...}. You can then reference the data source via data.aws_availability_zones.available.names[0] or data.aws_availability_zones.available.names[count.index] in resources using count.
  • +
+

Here's an example of using acctest.ConfigAvailableAZsNoOptIn() and data.aws_availability_zones.available.names[0]:

+
func testAccInstanceVpcConfigBasic(rName string) string {
+    return acctest.ConfigCompose(
+        acctest.ConfigAvailableAZsNoOptIn(),
+        fmt.Sprintf(`
+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
+}
+`, rName))
+}
+
+

Hardcoded Database Versions#

+
    +
  • Uses Database Version Data Sources: Hardcoded database versions, e.g., RDS MySQL Engine Version 5.7.42, should be removed (which means the AWS-defined default version will be used) or replaced with a list of preferred versions using a data source. Because versions change over times and version offerings vary from region to region and partition to partition, using the default version or providing a list of preferences ensures a version will be available. Depending on the situation, there are several data sources for versions, including: +
  • +
+

Here's an example of using aws_rds_engine_version and data.aws_rds_engine_version.default.version:

+
data "aws_rds_engine_version" "default" {
+  engine = "mysql"
+}
+
+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.t3.small", "db.t2.small", "db.t2.medium"]
+}
+
+resource "aws_db_instance" "bar" {
+  engine               = data.aws_rds_engine_version.default.engine
+  engine_version       = data.aws_rds_engine_version.default.version
+  instance_class       = data.aws_rds_orderable_db_instance.test.instance_class
+  skip_final_snapshot  = true
+  parameter_group_name = "default.${data.aws_rds_engine_version.default.parameter_group_family}"
+}
+
+

Hardcoded Direct Connect Locations#

+
    +
  • Uses aws_dx_locations Data Source: Hardcoded AWS Direct Connect locations, e.g., EqSe2, should be replaced with the aws_dx_locations data source.
  • +
+

Here's an example using data.aws_dx_locations.test.location_codes:

+
data "aws_dx_locations" "test" {}
+
+resource "aws_dx_lag" "test" {
+  name                  = "Test LAG"
+  connections_bandwidth = "1Gbps"
+  location              = tolist(data.aws_dx_locations.test.location_codes)[0]
+  force_destroy         = true
+}
+
+

Hardcoded Instance Types#

+
    +
  • Uses Instance Type Data Source: Singular hardcoded instance types and classes, e.g., t2.micro and db.t2.micro, should be replaced with a list of preferences using a data source. Because offerings vary from region to region and partition to partition, providing a list of preferences dramatically improves the likelihood that one of the options will be available. Depending on the situation, there are several data sources for instance types and classes, including: +
  • +
+

Here's an example of using acctest.AvailableEC2InstanceTypeForRegion() and data.aws_ec2_instance_type_offering.available.instance_type:

+
func testAccSpotInstanceRequestConfig(rInt int) string {
+    return acctest.ConfigCompose(
+        acctest.AvailableEC2InstanceTypeForRegion("t3.micro", "t2.micro"),
+        fmt.Sprintf(`
+resource "aws_spot_instance_request" "test" {
+  instance_type        = data.aws_ec2_instance_type_offering.available.instance_type
+  spot_price           = "0.05"
+  wait_for_fulfillment = true
+}
+`, rInt))
+}
+
+

Here's an example of using aws_rds_orderable_db_instance and data.aws_rds_orderable_db_instance.test.instance_class:

+
data "aws_rds_orderable_db_instance" "test" {
+  engine                     = "mysql"
+  engine_version             = "5.7.31"
+  preferred_instance_classes = ["db.t3.micro", "db.t2.micro", "db.t3.small"]
+}
+
+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
+  skip_final_snapshot = true
+  username            = "test"
+}
+
+

Hardcoded Partition DNS Suffix#

+
    +
  • Uses aws_partition Data Source: Any hardcoded DNS suffix configuration, e.g., the amazonaws.com in a ec2.amazonaws.com service principal, should be replaced with the aws_partition data source. A common pattern is declaring data "aws_partition" "current" {} and referencing it via data.aws_partition.current.dns_suffix.
  • +
+

Here's an example of using aws_partition and data.aws_partition.current.dns_suffix:

+
data "aws_partition" "current" {}
+
+resource "aws_iam_role" "test" {
+  assume_role_policy = <<POLICY
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "",
+      "Effect": "Allow",
+      "Principal": {
+        "Service": "cloudtrail.${data.aws_partition.current.dns_suffix}"
+      },
+      "Action": "sts:AssumeRole"
+    }
+  ]
+}
+POLICY
+}
+
+

Hardcoded Partition in ARN#

+
    +
  • Uses aws_partition Data Source: Any hardcoded AWS Partition configuration, e.g. the aws in a arn:aws:SERVICE:REGION:ACCOUNT:RESOURCE ARN, should be replaced with the aws_partition data source. A common pattern is declaring data "aws_partition" "current" {} and referencing it via data.aws_partition.current.partition.
  • +
  • +

    Uses Builtin ARN Check Functions: Tests should use available ARN check functions to validate ARN attribute values in the Terraform state over resource.TestCheckResourceAttrSet() and resource.TestMatchResourceAttr():

    +
      +
    • acctest.CheckResourceAttrRegionalARN() verifies that an ARN matches the account ID and region of the test execution with an exact resource value
    • +
    • acctest.MatchResourceAttrRegionalARN() verifies that an ARN matches the account ID and region of the test execution with a regular expression of the resource value
    • +
    • acctest.CheckResourceAttrGlobalARN() verifies that an ARN matches the account ID of the test execution with an exact resource value
    • +
    • acctest.MatchResourceAttrGlobalARN() verifies that an ARN matches the account ID of the test execution with a regular expression of the resource value
    • +
    • acctest.CheckResourceAttrRegionalARNNoAccount() verifies than an ARN has no account ID and matches the current region of the test execution with an exact resource value
    • +
    • acctest.CheckResourceAttrGlobalARNNoAccount() verifies than an ARN has no account ID and matches an exact resource value
    • +
    • acctest.CheckResourceAttrRegionalARNAccountID() verifies than an ARN matches a specific account ID and the current region of the test execution with an exact resource value
    • +
    • acctest.CheckResourceAttrGlobalARNAccountID() verifies than an ARN matches a specific account ID with an exact resource value
    • +
    +
  • +
+

Here's an example of using aws_partition and data.aws_partition.current.partition:

+
data "aws_partition" "current" {}
+
+resource "aws_iam_role_policy_attachment" "test" {
+  policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
+  role       = aws_iam_role.test.name
+}
+
+

Hardcoded Region#

+
    +
  • Uses aws_region Data Source: Any hardcoded AWS Region configuration, e.g., us-west-2, should be replaced with the aws_region data source. A common pattern is declaring data "aws_region" "current" {} and referencing it via data.aws_region.current.name
  • +
+

Here's an example of using aws_region and data.aws_region.current.name:

+
data "aws_region" "current" {}
+
+resource "aws_route53_zone" "test" {
+  vpc {
+    vpc_id     = aws_vpc.test.id
+    vpc_region = data.aws_region.current.name
+  }
+}
+
+

Hardcoded Spot Price#

+
    +
  • Uses aws_ec2_spot_price Data Source: Any hardcoded spot prices, e.g., 0.05, should be replaced with the aws_ec2_spot_price data source. A common pattern is declaring data "aws_ec2_spot_price" "current" {} and referencing it via data.aws_ec2_spot_price.current.spot_price.
  • +
+

Here's an example of using aws_ec2_spot_price and data.aws_ec2_spot_price.current.spot_price:

+
data "aws_ec2_spot_price" "current" {
+  instance_type = "t3.medium"
+
+  filter {
+    name   = "product-description"
+    values = ["Linux/UNIX"]
+  }
+}
+
+resource "aws_spot_fleet_request" "test" {
+  spot_price      = data.aws_ec2_spot_price.current.spot_price
+  target_capacity = 2
+}
+
+

Hardcoded SSH Keys#

+
    +
  • Uses acctest.RandSSHKeyPair() or RandSSHKeyPairSize() Functions: Any hardcoded SSH keys should be replaced with random SSH keys generated by either the acceptance testing framework's function RandSSHKeyPair() or the provider function RandSSHKeyPairSize(). RandSSHKeyPair() generates 1024-bit keys.
  • +
+

Here's an example using aws_key_pair

+
func TestAccKeyPair_basic(t *testing.T) {
+  ...
+
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+    publicKey, _, err := acctest.RandSSHKeyPair(acctest.DefaultEmailAddress)
+    if err != nil {
+        t.Fatalf("generating random SSH key: %s", err)
+    }
+
+  resource.ParallelTest(t, resource.TestCase{
+        ...
+        Steps: []resource.TestStep{
+            {
+                Config: testAccKeyPairConfig(rName, publicKey),
+        ...
+      },
+    },
+  })
+}
+
+func testAccKeyPairConfig(rName, publicKey string) string {
+    return fmt.Sprintf(`
+resource "aws_key_pair" "test" {
+  key_name   = %[1]q
+  public_key = %[2]q
+}
+`, rName, publicKey)
+}
+
+

Hardcoded Email Addresses#

+
    +
  • Uses either acctest.DefaultEmailAddress Constant or acctest.RandomEmailAddress() Function: Any hardcoded email addresses should replaced with either the constant acctest.DefaultEmailAddress or the function acctest.RandomEmailAddress().
  • +
+

Using acctest.DefaultEmailAddress is preferred when using a single email address in an acceptance test.

+

Here's an example using acctest.DefaultEmailAddress

+
func TestAccSNSTopicSubscription_email(t *testing.T) {
+    ...
+
+    rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
+
+    resource.ParallelTest(t, resource.TestCase{
+        ...
+        Steps: []resource.TestStep{
+            {
+                Config: testAccTopicSubscriptionEmailConfig(rName, acctest.DefaultEmailAddress),
+                Check: resource.ComposeTestCheckFunc(
+                    ...
+                    resource.TestCheckResourceAttr(resourceName, "endpoint", acctest.DefaultEmailAddress),
+                ),
+            },
+        },
+    })
+}
+
+

Here's an example using acctest.RandomEmailAddress()

+
func TestAccPinpointEmailChannel_basic(t *testing.T) {
+    ...
+
+    domain := acctest.RandomDomainName()
+    address1 := acctest.RandomEmailAddress(domain)
+    address2 := acctest.RandomEmailAddress(domain)
+
+    resource.ParallelTest(t, resource.TestCase{
+        ...
+        Steps: []resource.TestStep{
+            {
+                Config: testAccEmailChannelConfig_FromAddress(domain, address1),
+                Check: resource.ComposeTestCheckFunc(
+                    ...
+                    resource.TestCheckResourceAttr(resourceName, "from_address", address1),
+                ),
+            },
+            {
+                Config: testAccEmailChannelConfig_FromAddress(domain, address2),
+                Check: resource.ComposeTestCheckFunc(
+                    ...
+                    resource.TestCheckResourceAttr(resourceName, "from_address", address2),
+                ),
+            },
+        },
+    })
+}
+
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 000000000000..1ebceccd957b --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Welcome","text":"

The Terraform AWS Provider is the work of thousands of contributors, and is maintained by a small team within HashiCorp. This site contains extensive instructions about how to contribute and how the AWS provider works.

Please Note: This documentation is intended for Terraform AWS Provider code developers. Typical operators writing and applying Terraform configurations do not need to read or understand this material.

"},{"location":"#contribute","title":"Contribute","text":"

Please follow the following steps to ensure your contribution goes smoothly.

"},{"location":"#1-configure-development-environment","title":"1. Configure Development Environment","text":"

Install Terraform and Go. Clone the repository, compile the provider, and set up testing. Refer to Configure Development Environment.

"},{"location":"#2-debug-code","title":"2. Debug Code","text":"

If you are looking to create or enhance code, such as a new resource or adding an argument to an existing resource, skip to the next step.

Finding and fixing errors in the AWS Provider can be difficult. We have a debugging guide to help you get started.

"},{"location":"#3-change-code","title":"3. Change Code","text":"

Follow the guide for your contribution type and refer to the Development Reference materials as needed for additional details about provider design, expected naming conventions, guidance for error handling, etc.

Contribution Guide Description Small Changes Requirements for small additions or bug-fixes on existing resources/data sources Resources Allow the management of a logical resource within AWS by adding a new resource to the Terraform AWS Provider. Data Source Let your Terraform configurations use data from resources not under local management by creating ready only data sources. Services Allow Terraform (via the AWS Provider) to manage an entirely new AWS service by introducing the resources and data sources required to manage configuration of the service. AWS Region New regions are immediately usable with the provider with the caveat that a configuration workaround is required to skip validation of the region during cli operations. A small set of changes are required to makes this workaround necessary. Resource Name Generation Allow a resource to either fully, or partially, generate its own resource names. This can be useful in cases where the resource name uniquely identifes the resource and it needs to be recreated. It can also be used when a name is required, but the specific name is not important. Tagging Support Many AWS resources allow assigning metadata via tags. However, frequently AWS services are launched without tagging support so this will often need to be added later. Import Support Adding import support allows terraform import to be run targeting an existing unmanaged resource and pulling its configuration into Terraform state. Typically import support is added during initial resource implementation but in some cases this will need to be added later. Documentation Changes The provider documentation is displayed on the Terraform Registry and is sourced and refreshed from the provider repository during the release process."},{"location":"#4-write-tests","title":"4. Write Tests","text":"

We require changes to be covered by acceptance tests for all contributions. If you are unable to pay for acceptance tests for your contributions, mention this in your pull request. We will happily accept \"best effort\" acceptance tests implementations and run them for you on our side. Your PR may take longer to merge, but this is not a blocker for contributions.

"},{"location":"#5-update-the-changelog","title":"5. Update the Changelog","text":"

HashiCorp's open-source projects have always maintained a user-friendly, readable CHANGELOG.md that allows users to tell at a glance whether a release should have any effect on them, and to gauge the risk of an upgrade. Not all changes require an entry in the changelog, refer to our Changelog Process for details about when and how to create a changelog.

"},{"location":"#6-create-a-pull-request","title":"6. Create a Pull Request","text":"

When your contribution is ready, Create a Pull Request in the AWS provider repository.

Pull requests are usually triaged within a few days of creation and are prioritized based on community reactions. Our Prioritization Guides provides more details about the process.

"},{"location":"#submit-an-issue","title":"Submit an Issue","text":"

In addition to contributions, we welcome bug reports and feature requests.

"},{"location":"#join-the-contributors-slack","title":"Join the Contributors Slack","text":"

For frequent contributors, it's useful to join the contributors Slack channel hosted within the HashiCorp Slack workspace. This Slack channel is used to discuss topics such as general contribution questions, suggestions for improving the contribution process, coordinating on pair programming sessions, etc. The channel is not intended as a place to request status updates on open issues or pull requests. For prioritization questions, instead refer to the prioritization guide.

To request to join, fill out the request form and allow time for the request to be reviewed and processed.

"},{"location":"acc-test-environment-variables/","title":"Acceptance Testing Environment Variable Dictionary","text":"

Environment variables (beyond standard AWS Go SDK ones) used by acceptance testing. See also the internal/acctest package.

Variable Description ACM_CERTIFICATE_ROOT_DOMAIN Root domain name to use with ACM Certificate testing. ACM_CERTIFICATE_MULTIPLE_ISSUED_DOMAIN Domain name of ACM Certificate with a multiple issued certificates. DEPRECATED: Should be replaced with aws_acm_certficate resource usage in tests. ACM_CERTIFICATE_MULTIPLE_ISSUED_MOST_RECENT_ARN Amazon Resource Name of most recent ACM Certificate with a multiple issued certificates. DEPRECATED: Should be replaced with aws_acm_certficate resource usage in tests. ACM_CERTIFICATE_SINGLE_ISSUED_DOMAIN Domain name of ACM Certificate with a single issued certificate. DEPRECATED: Should be replaced with aws_acm_certficate resource usage in tests. ACM_CERTIFICATE_SINGLE_ISSUED_MOST_RECENT_ARN Amazon Resource Name of most recent ACM Certificate with a single issued certificate. DEPRECATED: Should be replaced with aws_acm_certficate resource usage in tests. ADM_CLIENT_ID Identifier for Amazon Device Manager Client in Pinpoint testing. AMPLIFY_DOMAIN_NAME Domain name to use for Amplify domain association testing. AMPLIFY_GITHUB_ACCESS_TOKEN GitHub access token used for AWS Amplify testing. AMPLIFY_GITHUB_REPOSITORY GitHub repository used for AWS Amplify testing. ADM_CLIENT_SECRET Secret for Amazon Device Manager Client in Pinpoint testing. APNS_BUNDLE_ID Identifier for Apple Push Notification Service Bundle in Pinpoint testing. APNS_CERTIFICATE Certificate (PEM format) for Apple Push Notification Service in Pinpoint testing. APNS_CERTIFICATE_PRIVATE_KEY Private key for Apple Push Notification Service in Pinpoint testing. APNS_SANDBOX_BUNDLE_ID Identifier for Sandbox Apple Push Notification Service Bundle in Pinpoint testing. APNS_SANDBOX_CERTIFICATE Certificate (PEM format) for Sandbox Apple Push Notification Service in Pinpoint testing. APNS_SANDBOX_CERTIFICATE_PRIVATE_KEY Private key for Sandbox Apple Push Notification Service in Pinpoint testing. APNS_SANDBOX_CREDENTIAL Credential contents for Sandbox Apple Push Notification Service in SNS Application Platform testing. Conflicts with APNS_SANDBOX_CREDENTIAL_PATH. APNS_SANDBOX_CREDENTIAL_PATH Path to credential for Sandbox Apple Push Notification Service in SNS Application Platform testing. Conflicts with APNS_SANDBOX_CREDENTIAL. APNS_SANDBOX_PRINCIPAL Principal contents for Sandbox Apple Push Notification Service in SNS Application Platform testing. Conflicts with APNS_SANDBOX_PRINCIPAL_PATH. APNS_SANDBOX_PRINCIPAL_PATH Path to principal for Sandbox Apple Push Notification Service in SNS Application Platform testing. Conflicts with APNS_SANDBOX_PRINCIPAL. APNS_SANDBOX_TEAM_ID Identifier for Sandbox Apple Push Notification Service Team in Pinpoint testing. APNS_SANDBOX_TOKEN_KEY Token key file content (.p8 format) for Sandbox Apple Push Notification Service in Pinpoint testing. APNS_SANDBOX_TOKEN_KEY_ID Identifier for Sandbox Apple Push Notification Service Token Key in Pinpoint testing. APNS_TEAM_ID Identifier for Apple Push Notification Service Team in Pinpoint testing. APNS_TOKEN_KEY Token key file content (.p8 format) for Apple Push Notification Service in Pinpoint testing. APNS_TOKEN_KEY_ID Identifier for Apple Push Notification Service Token Key in Pinpoint testing. APNS_VOIP_BUNDLE_ID Identifier for VOIP Apple Push Notification Service Bundle in Pinpoint testing. APNS_VOIP_CERTIFICATE Certificate (PEM format) for VOIP Apple Push Notification Service in Pinpoint testing. APNS_VOIP_CERTIFICATE_PRIVATE_KEY Private key for VOIP Apple Push Notification Service in Pinpoint testing. APNS_VOIP_TEAM_ID Identifier for VOIP Apple Push Notification Service Team in Pinpoint testing. APNS_VOIP_TOKEN_KEY Token key file content (.p8 format) for VOIP Apple Push Notification Service in Pinpoint testing. APNS_VOIP_TOKEN_KEY_ID Identifier for VOIP Apple Push Notification Service Token Key in Pinpoint testing. APPRUNNER_CUSTOM_DOMAIN A custom domain endpoint (root domain, subdomain, or wildcard) for AppRunner Custom Domain Association testing. AUDITMANAGER_DEREGISTER_ACCOUNT_ON_DESTROY Flag to execute tests that will disable AuditManager in the account upon destruction. AUDITMANAGER_ORGANIZATION_ADMIN_ACCOUNT_ID Organization admin account identifier for use in AuditManager testing. AWS_ALTERNATE_ACCESS_KEY_ID AWS access key ID with access to a secondary AWS account for tests requiring multiple accounts. Requires AWS_ALTERNATE_SECRET_ACCESS_KEY. Conflicts with AWS_ALTERNATE_PROFILE. AWS_ALTERNATE_SECRET_ACCESS_KEY AWS secret access key with access to a secondary AWS account for tests requiring multiple accounts. Requires AWS_ALTERNATE_ACCESS_KEY_ID. Conflicts with AWS_ALTERNATE_PROFILE. AWS_ALTERNATE_PROFILE AWS profile with access to a secondary AWS account for tests requiring multiple accounts. Conflicts with AWS_ALTERNATE_ACCESS_KEY_ID and AWS_ALTERNATE_SECRET_ACCESS_KEY. AWS_ALTERNATE_REGION Secondary AWS region for tests requiring multiple regions. Defaults to us-east-1. AWS_API_GATEWAY_DOMAIN_NAME_CERTIFICATE_BODY Certificate body of publicly trusted certificate for API Gateway Domain Name testing. AWS_API_GATEWAY_DOMAIN_NAME_CERTIFICATE_CHAIN Certificate chain of publicly trusted certificate for API Gateway Domain Name testing. AWS_API_GATEWAY_DOMAIN_NAME_CERTIFICATE_PRIVATE_KEY Private key of publicly trusted certificate for API Gateway Domain Name testing. AWS_API_GATEWAY_DOMAIN_NAME_REGIONAL_CERTIFICATE_NAME_ENABLED Flag to enable API Gateway Domain Name regional certificate upload testing. AWS_CODEBUILD_BITBUCKET_SOURCE_LOCATION BitBucket source URL for CodeBuild testing. CodeBuild must have access to this repository via OAuth or Source Credentials. Defaults to https://terraform@bitbucket.org/terraform/aws-test.git. AWS_CODEBUILD_GITHUB_SOURCE_LOCATION GitHub source URL for CodeBuild testing. CodeBuild must have access to this repository via OAuth or Source Credentials. Defaults to https://github.com/hashibot-test/aws-test.git. AWS_DEFAULT_REGION Primary AWS region for tests. Defaults to us-west-2. AWS_DETECTIVE_MEMBER_EMAIL Email address for Detective Member testing. A valid email address associated with an AWS root account is required for tests to pass. AWS_EC2_CLIENT_VPN_LIMIT Concurrency limit for Client VPN acceptance tests. Default is 5 if not specified. AWS_EC2_EIP_PUBLIC_IPV4_POOL Identifier for EC2 Public IPv4 Pool for EC2 EIP testing. AWS_GUARDDUTY_MEMBER_ACCOUNT_ID Identifier of AWS Account for GuardDuty Member testing. DEPRECATED: Should be replaced with standard alternate account handling for tests. AWS_GUARDDUTY_MEMBER_EMAIL Email address for GuardDuty Member testing. DEPRECATED: It may be possible to use a placeholder email address instead. AWS_LAMBDA_IMAGE_LATEST_ID ECR repository image URI (tagged as latest) for Lambda container image acceptance tests. AWS_LAMBDA_IMAGE_V1_ID ECR repository image URI (tagged as v1) for Lambda container image acceptance tests. AWS_LAMBDA_IMAGE_V2_ID ECR repository image URI (tagged as v2) for Lambda container image acceptance tests. AWS_THIRD_ACCESS_KEY_ID AWS access key ID with access to a third AWS account for tests requiring multiple accounts. Requires AWS_THIRD_SECRET_ACCESS_KEY. Conflicts with AWS_THIRD_PROFILE. AWS_THIRD_SECRET_ACCESS_KEY AWS secret access key with access to a third AWS account for tests requiring multiple accounts. Requires AWS_THIRD_ACCESS_KEY_ID. Conflicts with AWS_THIRD_PROFILE. AWS_THIRD_PROFILE AWS profile with access to a third AWS account for tests requiring multiple accounts. Conflicts with AWS_THIRD_ACCESS_KEY_ID and AWS_THIRD_SECRET_ACCESS_KEY. AWS_THIRD_REGION Third AWS region for tests requiring multiple regions. Defaults to us-east-2. DX_CONNECTION_ID Identifier for Direct Connect Connection testing. DX_VIRTUAL_INTERFACE_ID Identifier for Direct Connect Virtual Interface testing. EC2_SECURITY_GROUP_RULES_PER_GROUP_LIMIT EC2 Quota for Rules per Security Group. Defaults to 50. DEPRECATED: Can be augmented or replaced with Service Quotas lookup. EVENT_BRIDGE_PARTNER_EVENT_BUS_NAME Amazon EventBridge partner event bus name. EVENT_BRIDGE_PARTNER_EVENT_SOURCE_NAME Amazon EventBridge partner event source name. GCM_API_KEY API Key for Google Cloud Messaging in Pinpoint and SNS Platform Application testing. GITHUB_TOKEN GitHub token for CodePipeline testing. GLOBALACCERATOR_BYOIP_IPV4_ADDRESS IPv4 address from a BYOIP CIDR of AWS Account used for testing Global Accelerator's BYOIP accelerator. GRAFANA_SSO_GROUP_ID AWS SSO group ID for Grafana testing. GRAFANA_SSO_USER_ID AWS SSO user ID for Grafana testing. MACIE_MEMBER_ACCOUNT_ID Identifier of AWS Account for Macie Member testing. DEPRECATED: Should be replaced with standard alternate account handling for tests. QUICKSIGHT_NAMESPACE QuickSight namespace name for testing. QUICKSIGHT_ATHENA_TESTING_ENABLED Enable QuickSight tests dependent on Amazon Athena resources. ROUTE53DOMAINS_DOMAIN_NAME Registered domain for Route 53 Domains testing. SAGEMAKER_IMAGE_VERSION_BASE_IMAGE SageMaker base image to use for tests. SERVICEQUOTAS_INCREASE_ON_CREATE_QUOTA_CODE Quota Code for Service Quotas testing (submits support case). SERVICEQUOTAS_INCREASE_ON_CREATE_SERVICE_CODE Service Code for Service Quotas testing (submits support case). SERVICEQUOTAS_INCREASE_ON_CREATE_VALUE Value of quota increase for Service Quotas testing (submits support case). SES_DOMAIN_IDENTITY_ROOT_DOMAIN Root domain name of publicly accessible and Route 53 configurable domain for SES Domain Identity testing. SES_DEDICATED_IP Dedicated IP address for testing IP assignment with a \"Standard\" (non-managed) SES dedicated IP pool. SWF_DOMAIN_TESTING_ENABLED Enables SWF Domain testing (API does not support deletions). TEST_AWS_ORGANIZATION_ACCOUNT_EMAIL_DOMAIN Email address for Organizations Account testing. TEST_AWS_SES_VERIFIED_EMAIL_ARN Verified SES Email Identity for use in Cognito User Pool testing. TF_ACC Enables Go tests containing resource.Test() and resource.ParallelTest(). TF_ACC_ASSUME_ROLE_ARN Amazon Resource Name of existing IAM Role to use for limited permissions acceptance testing. TF_AWS_LICENSE_MANAGER_GRANT_HOME_REGION Region where a License Manager license is imported. TF_AWS_LICENSE_MANAGER_GRANT_LICENSE_ARN ARN for a License Manager license imported into the current account. TF_AWS_LICENSE_MANAGER_GRANT_PRINCIPAL ARN of a principal to share the License Manager license with. Either a root user, Organization, or Organizational Unit. TF_TEST_CLOUDFRONT_RETAIN Flag to disable but dangle CloudFront Distributions during testing to reduce feedback time (must be manually destroyed afterwards)"},{"location":"add-a-new-datasource/","title":"Adding a New Data Source","text":"

New data sources are required when AWS adds a new service, or adds new features within an existing service which would require a new data source to allow practitioners to query existing resources of that type for use in their configurations. Anything with a Describe or Get endpoint could make a data source, but some are more useful than others.

Each data source should be submitted for review in isolation, pull requests containing multiple data sources and/or resources are harder to review and the maintainers will normally ask for them to be broken apart.

"},{"location":"add-a-new-datasource/#prerequisites","title":"Prerequisites","text":"

If this is the first addition of a data source for a new service, please ensure the Service Client for the new service has been added and merged. See Adding a new Service for details.

Determine which version of the AWS SDK for Go the resource will be built upon. For more information and instructions on how to determine this choice, please read AWS SDK for Go Versions

"},{"location":"add-a-new-datasource/#steps-to-add-a-data-source","title":"Steps to Add a Data Source","text":""},{"location":"add-a-new-datasource/#fork-the-provider-and-create-a-feature-branch","title":"Fork the Provider and Create a Feature Branch","text":"

For a new data source use a branch named f-{datasource name} for example: f-ec2-vpc. See Raising a Pull Request for more details.

"},{"location":"add-a-new-datasource/#create-and-name-the-data-source","title":"Create and Name the Data Source","text":"

See the Naming Guide for details on how to name the new resource and the resource file. Not following the naming standards will cause extra delay as maintainers request that you make changes.

Use the skaff provider scaffolding tool to generate new resource and test templates using your chosen name ensuring you provide the v1 flag if you are targeting version 1 of the aws-go-sdk. Doing so will ensure that any boilerplate code, structural best practices and repetitive naming is done for you and always represents our most current standards.

"},{"location":"add-a-new-datasource/#fill-out-the-data-source-schema","title":"Fill out the Data Source Schema","text":"

In the internal/service/<service>/<service>_data_source.go file you will see a Schema property which exists as a map of Schema objects. This relates the AWS API data model with the Terraform resource itself. For each property you want to make available in Terraform, you will need to add it as an attribute, and choose the correct data type.

Attribute names are to specified in snake_case as opposed to the AWS API which is CamelCase

"},{"location":"add-a-new-datasource/#implement-read-handler","title":"Implement Read Handler","text":"

These will map the AWS API response to the data source schema. You will also need to handle different response types (including errors correctly). For complex attributes you will need to implement Flattener or Expander functions. The Data Handling and Conversion Guide covers everything you need to know for mapping AWS API responses to Terraform State and vice-versa. The Error Handling Guide covers everything you need to know about handling AWS API responses consistently.

"},{"location":"add-a-new-datasource/#register-data-source-to-the-provider","title":"Register Data Source to the provider","text":"

Data Sources use a self registration process that adds them to the provider using the @SDKDataSource() annotation in the datasource's comments. Run make gen to register the datasource. This will add an entry to the service_package_gen.go file located in the service package folder.

package something\n\nimport \"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema\"\n\n// @SDKDataSource(\"aws_something_example\", name=\"Example\")\nfunc DataSourceExample() *schema.Resource {\n    return &schema.Resource{\n        // some configuration\n    }\n}\n
"},{"location":"add-a-new-datasource/#write-passing-acceptance-tests","title":"Write Passing Acceptance Tests","text":"

In order to adequately test the data source we will need to write a complete set of Acceptance Tests. You will need an AWS account for this which allows the provider to read to state of the associated resource. See Writing Acceptance Tests for a detailed guide on how to approach these.

You will need at minimum:

  • Basic Test - Tests full lifecycle (CRUD + Import) of a minimal configuration (all required fields, no optional).
  • Disappears Test - Tests what Terraform does if a resource it is tracking can no longer be found.
  • Per Attribute Tests - For each attribute a test should exists which tests that particular attribute in isolation alongside any required fields.
"},{"location":"add-a-new-datasource/#create-documentation-for-the-data-source","title":"Create Documentation for the Data Source","text":"

Add a file covering the use of the new data source in website/docs/d/<service>_<name>.md. You may want to also add examples of the data source in use particularly if its use is complex, or relies on resources in another service. This documentation will appear on the Terraform Registry when the data source is made available in a provider release. It is fine to link out to AWS Documentation where appropriate, particularly for values which are likely to change.

"},{"location":"add-a-new-datasource/#ensure-format-and-lint-checks-are-passing-locally","title":"Ensure Format and Lint Checks are Passing Locally","text":"

Run go fmt to format your code, and install and run all linters to detect and resolve any structural issues with the implementation or documentation.

make fmt\nmake tools        # install linters and dependencies\nmake lint         # run provider linters\nmake docs-lint    # run documentation linters\nmake website-lint # run website documentation linters\n
"},{"location":"add-a-new-datasource/#raise-a-pull-request","title":"Raise a Pull Request","text":"

See Raising a Pull Request.

"},{"location":"add-a-new-datasource/#wait-for-prioritization","title":"Wait for Prioritization","text":"

In general, pull requests are triaged within a few days of creation and are prioritized based on community reactions. Please view our prioritization guide for full details of the process.

"},{"location":"add-a-new-region/","title":"Adding a Newly Released AWS Region","text":"

New regions can typically be used immediately with the provider, with two important caveats:

  • Regions often need to be explicitly enabled via the AWS console. See ap-east-1 launch blog for an example of how to enable a new region for use.
  • Until the provider is aware of the new region, automatic region validation will fail. In order to use the region before validation support is added to the provider you will need to disable region validation by doing the following:
provider \"aws\" {\n  # ... potentially other configuration ...\n\nregion                 = \"me-south-1\"\nskip_region_validation = true\n}\n
"},{"location":"add-a-new-region/#enabling-region-validation","title":"Enabling Region Validation","text":"

Support for region validation requires that the provider has an updated AWS Go SDK dependency that includes the new region. These are added to the AWS Go SDK aws/endpoints/defaults.go file and generally noted in the AWS Go SDK CHANGELOG as aws/endpoints: Updated Regions. This also needs to be done in the core Terraform binary itself to enable it for the S3 backend. The provider currently takes a dependency on both v1 AND v2 of the AWS Go SDK, as we start to base new (and migrate) resources on v2. Many of the authentication and provider level configuration interactions are also located in the aws-go-sdk-base library. As all of these things take direct dependencies and as a result there ends up being quite a few places these dependency updates need to be made.

"},{"location":"add-a-new-region/#update-aws-go-sdk-base","title":"Update aws-go-sdk-base","text":"

aws-go-sdk-base

  • Update aws-go-sdk
  • Update aws-go-sdk-v2
"},{"location":"add-a-new-region/#update-terraform-aws-provider","title":"Update Terraform AWS Provider","text":"

provider

  • Update aws-go-sdk
  • Update aws-go-sdk-v2
  • Update aws-go-sdk-base
"},{"location":"add-a-new-region/#update-terraform-core-s3-backend","title":"Update Terraform Core (S3 Backend)","text":"

core

  • Update aws-go-sdk
  • Update aws-go-sdk-v2
  • Update aws-go-sdk-base
go get github.com/aws/aws-sdk-go@v#.#.#\ngo mod tidy\n

See the Changelog Process document for example changelog format.

"},{"location":"add-a-new-region/#update-region-specific-values-in-static-data-sources","title":"Update Region Specific values in static Data Sources","text":"

Some data sources include static values specific to regions that are not available via a standard AWS API call. These will need to be manually updated. AWS employees can code search previous region values to find new region values in internal packages like RIPStaticConfig if they are not documented yet.

  • Check Elastic Load Balancing endpoints and quotas and add Route53 Hosted Zone ID if available to internal/service/elb/hosted_zone_id_data_source.go and internal/service/elbv2/hosted_zone_id_data_source.go
  • Check Amazon Simple Storage Service endpoints and quotas and add Route53 Hosted Zone ID if available to internal/service/s3/hosted_zones.go
  • Check CloudTrail Supported Regions docs and add AWS Account ID if available to internal/service/cloudtrail/service_account_data_source.go
  • ~~Check Elastic Load Balancing Access Logs docs and add Elastic Load Balancing Account ID if available to internal/service/elb/service_account_data_source.go~~
  • ~~Check Redshift Database Audit Logging docs and add AWS Account ID if available to internal/service/redshift/service_account_data_source.go~~
  • Check AWS Elastic Beanstalk endpoints and quotas and add Route53 Hosted Zone ID if available to internal/service/elasticbeanstalk/hosted_zone_data_source.go
  • Check SageMaker docs and add AWS Account IDs if available to internal/service/sagemaker/prebuilt_ecr_image_data_source.go
"},{"location":"add-a-new-resource/","title":"Adding a New Resource","text":"

New resources are required when AWS adds a new service, or adds new features within an existing service which would require a new resource to manage in Terraform. Typically anything with a new set of CRUD API endpoints is a great candidate for a new resource.

Each resource should be submitted for review in isolation. Pull requests containing multiple resources are harder to review and the maintainers will normally ask for them to be broken apart.

"},{"location":"add-a-new-resource/#prerequisites","title":"Prerequisites","text":"

If this is the first resource for a new service, please ensure the Service Client for the new service has been added and merged. See Adding a new Service for details.

Determine which version of the AWS SDK for Go the resource will be built upon. For more information and instructions on how to determine this choice, please read AWS SDK for Go Versions

"},{"location":"add-a-new-resource/#steps-to-add-a-resource","title":"Steps to Add a Resource","text":""},{"location":"add-a-new-resource/#fork-the-provider-and-create-a-feature-branch","title":"Fork the provider and create a feature branch","text":"

For a new resources use a branch named f-{resource name} for example: f-ec2-vpc. See Raising a Pull Request for more details.

"},{"location":"add-a-new-resource/#create-and-name-the-resource","title":"Create and Name the Resource","text":"

See the Naming Guide for details on how to name the new resource and the resource file. Not following the naming standards will cause extra delay as maintainers request that you make changes.

Use the skaff provider scaffolding tool to generate new resource and test templates using your chosen name ensuring you provide the v1 flag if you are targeting version 1 of the aws-go-sdk. Doing so will ensure that any boilerplate code, structural best practices and repetitive naming is done for you and always represents our most current standards.

"},{"location":"add-a-new-resource/#fill-out-the-resource-schema","title":"Fill out the Resource Schema","text":"

In the internal/service/<service>/<service>.go file you will see a Schema property which exists as a map of Schema objects. This relates the AWS API data model with the Terraform resource itself. For each property you want to make available in Terraform, you will need to add it as an attribute, choose the correct data type and supply the correct Schema Behaviors in order to ensure Terraform knows how to correctly handle the value.

Typically you will add arguments to represent the values that are under control by Terraform, and attributes in order to supply read-only values as references for Terraform. These are distinguished by Schema Behavior.

Attribute names are to specified in camel_case as opposed to the AWS API which is CamelCase

"},{"location":"add-a-new-resource/#implement-crud-handlers","title":"Implement CRUD handlers","text":"

These will map planned Terraform state to the AWS API call, or an AWS API response to an applied Terraform state. You will also need to handle different response types (including errors correctly). For complex attributes you will need to implement Flattener or Expander functions. The Data Handling and Conversion Guide covers everything you need to know for mapping AWS API responses to Terraform State and vice-versa. The Error Handling Guide covers everything you need to know about handling AWS API responses consistently.

"},{"location":"add-a-new-resource/#register-resource-to-the-provider","title":"Register Resource to the provider","text":"

Resources use a self registration process that adds them to the provider using the @SDKResource() annotation in the resource's comments. Run make gen to register the resource. This will add an entry to the service_package_gen.go file located in the service package folder.

package something\n\nimport \"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema\"\n\n// @SDKResource(\"aws_something_example\", name=\"Example)\nfunc ResourceExample() *schema.Resource {\n    return &schema.Resource{\n        // some configuration\n    }\n}\n
"},{"location":"add-a-new-resource/#write-passing-acceptance-tests","title":"Write passing Acceptance Tests","text":"

In order to adequately test the resource we will need to write a complete set of Acceptance Tests. You will need an AWS account for this which allows the creation of that resource. See Writing Acceptance Tests for a detailed guide on how to approach these.

You will need at minimum:

  • Basic Test - Tests full lifecycle (CRUD + Import) of a minimal configuration (all required fields, no optional).
  • Disappears Test - Tests what Terraform does if a resource it is tracking can no longer be found.
  • Argument Tests - All arguments should be tested in a pragmatic way. Ensure that each argument can be initially set, updated, and cleared, as applicable. Depending on the logic and interaction of arguments, this may take one to several separate tests.
"},{"location":"add-a-new-resource/#create-documentation-for-the-resource","title":"Create documentation for the resource","text":"

Add a file covering the use of the new resource in website/docs/r/<service>_<name>.md. Add more examples if it is complex or relies on resources in another service. This documentation will appear on the Terraform Registry when the resource is made available in a provider release. Link to AWS Documentation where appropriate, particularly for values which are likely to change.

"},{"location":"add-a-new-resource/#ensure-format-and-lint-checks-are-passing-locally","title":"Ensure format and lint checks are passing locally","text":"

Format your code and check linters to detect various issues.

make fmt\nmake tools     # install linters and dependencies\nmake lint      # run provider linters\nmake docs-lint # run documentation linters\n
"},{"location":"add-a-new-resource/#raise-a-pull-request","title":"Raise a Pull Request","text":"

See Raising a Pull Request.

"},{"location":"add-a-new-resource/#wait-for-prioritization","title":"Wait for Prioritization","text":"

In general, pull requests are triaged within a few days of creation and are prioritized based on community reactions. Please view our Prioritization Guide for full details of the process.

"},{"location":"add-a-new-service/","title":"Adding a New AWS Service","text":"

AWS frequently launches new services, and Terraform support is frequently desired by the community shortly after launch. Depending on the API surface area of the new service, this could be a major undertaking. The following steps should be followed to prepare for adding the resources that allow for Terraform management of that service.

"},{"location":"add-a-new-service/#perform-service-design","title":"Perform Service Design","text":"

Before adding a new service to the provider its a good idea to familiarize yourself with the primary workflows practitioners are likely to want to accomplish with the provider to ensure the provider design can solve for this. Its not always necessary to cover 100% of the AWS service offering to unblock most workflows.

You should have an idea of what resources and data sources should be added, their dependencies and relative importance in relation to the workflow. This should give you an idea of the order in which resources to be added. It's important to note that generally, we like to review and merge resources in isolation, and avoid combining multiple new resources in one Pull Request.

Using the AWS API documentation as a reference, identify the various API's which correspond to the CRUD operations which consist of the management surface for that resource. These will be the set of API's called from the new resource. The API's model attributes will correspond to your resource schema.

From there begin to map out the list of resources you would like to implement, and note your plan on the GitHub issue relating to the service (or create one if one does not exist) for the community and maintainers to feedback.

"},{"location":"add-a-new-service/#add-a-service-client","title":"Add a Service Client","text":"

Before new resources are submitted, please raise a separate pull request containing just the new AWS SDK for Go service client.

To add an AWS SDK for Go service client:

  1. Check the file names/names_data.csv for the service. If it is already there, you are ready to implement the first resource or data source.

  2. Otherwise, determine the service identifier using the rule described in the Naming Guide.

  3. In names/names_data.csv, add a new line with all the requested information for the service following the guidance in the names README. Be very careful when adding or changing data in names_data.csv! The Provider and generators depend on the file being correct. We strongly recommend using an editor with CSV support.

  4. Run the following then submit the pull request:

make gen\nmake test\ngo mod tidy\n

Once the service client has been added, implement the first resource or data source in a separate PR.

"},{"location":"add-a-new-service/#adding-a-custom-service-client","title":"Adding a Custom Service Client","text":"

If an AWS service must be created in a non-standard way, for example the service API's endpoint must be accessed via a single AWS Region, then:

  1. Add an x in the SkipClientGenerate column for the service in names/names_data.csv

  2. Run make gen

  3. Add a file internal/<service>/service_package.go that contains an API client factory function, for example:

package globalaccelerator\n\nimport (\n\"context\"\n\naws_sdkv1 \"github.com/aws/aws-sdk-go/aws\"\nendpoints_sdkv1 \"github.com/aws/aws-sdk-go/aws/endpoints\"\nsession_sdkv1 \"github.com/aws/aws-sdk-go/aws/session\"\nglobalaccelerator_sdkv1 \"github.com/aws/aws-sdk-go/service/globalaccelerator\"\n)\n\n// NewConn returns a new AWS SDK for Go v1 client for this service package's AWS API.\nfunc (p *servicePackage) NewConn(ctx context.Context) (*globalaccelerator_sdkv1.GlobalAccelerator, error) {\nsess := p.config[\"session\"].(*session_sdkv1.Session)\nconfig := &aws_sdkv1.Config{Endpoint: aws_sdkv1.String(p.config[\"endpoint\"].(string))}\n\n// Force \"global\" services to correct Regions.\nif p.config[\"partition\"].(string) == endpoints_sdkv1.AwsPartitionID {\nconfig.Region = aws_sdkv1.String(endpoints_sdkv1.UsWest2RegionID)\n}\n\nreturn globalaccelerator_sdkv1.New(sess.Copy(config)), nil\n}\n
"},{"location":"add-a-new-service/#customizing-a-new-service-client","title":"Customizing a new Service Client","text":"

If an AWS service must be customized after creation, for example retry handling must be changed, then:

  1. Add a file internal/<service>/service_package.go that contains an API client customization function, for example:
package chime\n\nimport (\n\"context\"\n\naws_sdkv1 \"github.com/aws/aws-sdk-go/aws\"\nrequest_sdkv1 \"github.com/aws/aws-sdk-go/aws/request\"\nchime_sdkv1 \"github.com/aws/aws-sdk-go/service/chime\"\n\"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr\"\n)\n\n// CustomizeConn customizes a new AWS SDK for Go v1 client for this service package's AWS API.\nfunc (p *servicePackage) CustomizeConn(ctx context.Context, conn *chime_sdkv1.Chime) (*chime_sdkv1.Chime, error) {\nconn.Handlers.Retry.PushBack(func(r *request_sdkv1.Request) {\n// When calling CreateVoiceConnector across multiple resources,\n// the API can randomly return a BadRequestException without explanation\nif r.Operation.Name == \"CreateVoiceConnector\" {\nif tfawserr.ErrMessageContains(r.Error, chime_sdkv1.ErrCodeBadRequestException, \"Service received a bad request\") {\nr.Retryable = aws_sdkv1.Bool(true)\n}\n}\n})\n\nreturn conn, nil\n}\n
"},{"location":"add-import-support/","title":"Adding Resource Import Support","text":"

Adding import support for Terraform resources will allow existing infrastructure to be managed within Terraform. This type of enhancement generally requires a small to moderate amount of code changes.

Comprehensive code examples and information about resource import support can be found in the Terraform Plugin SDK v2 documentation.

  • Resource Code Implementation: In the resource code (e.g., internal/service/{service}/{thing}.go), implementation of Importer State function. When possible, prefer using schema.ImportStatePassthroughContext as the Importer State function
  • Resource Acceptance Testing Implementation: In the resource acceptance testing (e.g., internal/service/{service}/{thing}_test.go), implementation of TestSteps with ImportState: true
  • Resource Documentation Implementation: In the resource documentation (e.g., website/docs/r/service_thing.html.markdown), addition of Import documentation section at the bottom of the page
"},{"location":"adding-a-tag-resource/","title":"Adding a New Tag Resource","text":"

Adding a tag resource, similar to the aws_ecs_tag resource, has its own implementation procedure since the resource code and initial acceptance testing functions are automatically generated. The rest of the resource acceptance testing and resource documentation must still be manually created.

  • In internal/generate: Ensure the service is supported by all generators. Run make gen after any modifications.
  • In internal/service/{service}/generate.go: Add the new //go:generate call with the correct generator directives. Run make gen after any modifications.
  • In internal/provider/provider.go: Add the new resource.
  • Run make test and ensure there are no failures.
  • Create internal/service/{service}/tag_gen_test.go with initial acceptance testing similar to the following (where the parent resource is simple to provision):
import (\n\"fmt\"\n\"testing\"\n\n\"github.com/aws/aws-sdk-go/service/{Service}\"\n\"github.com/hashicorp/terraform-plugin-testing/helper/acctest\"\n\"github.com/hashicorp/terraform-plugin-testing/helper/resource\"\n)\n\nfunc TestAcc{Service}Tag_basic(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_{service}_tag.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, {Service}.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheck{Service}TagDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAcc{Service}TagConfig(rName, \"key1\", \"value1\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheck{Service}TagExists(ctx, resourceName),\nresource.TestCheckResourceAttr(resourceName, \"key\", \"key1\"),\nresource.TestCheckResourceAttr(resourceName, \"value\", \"value1\"),\n),\n},\n{\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n},\n})\n}\n\nfunc TestAcc{Service}Tag_disappears(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_{service}_tag.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, {Service}.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheck{Service}TagDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAcc{Service}TagConfig(rName, \"key1\", \"value1\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheck{Service}TagExists(ctx, resourceName),\nacctest.CheckResourceDisappears(ctx, acctest.Provider, resourceAws{Service}Tag(), resourceName),\n),\nExpectNonEmptyPlan: true,\n},\n},\n})\n}\n\nfunc TestAcc{Service}Tag_Value(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_{service}_tag.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, {Service}.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheck{Service}TagDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAcc{Service}TagConfig(rName, \"key1\", \"value1\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheck{Service}TagExists(ctx, resourceName),\nresource.TestCheckResourceAttr(resourceName, \"key\", \"key1\"),\nresource.TestCheckResourceAttr(resourceName, \"value\", \"value1\"),\n),\n},\n{\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n{\nConfig: testAcc{Service}TagConfig(rName, \"key1\", \"value1updated\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheck{Service}TagExists(ctx, resourceName),\nresource.TestCheckResourceAttr(resourceName, \"key\", \"key1\"),\nresource.TestCheckResourceAttr(resourceName, \"value\", \"value1updated\"),\n),\n},\n},\n})\n}\n\nfunc testAcc{Service}TagConfig(rName string, key string, value string) string {\nreturn fmt.Sprintf(`\nresource \"aws_{service}_{thing}\" \"test\" {\n  name = %[1]q\n\n  lifecycle {\n    ignore_changes = [tags]\n  }\n}\n\nresource \"aws_{service}_tag\" \"test\" {\n  resource_arn = aws_{service}_{thing}.test.arn\n  key          = %[2]q\n  value        = %[3]q\n}\n`, rName, key, value)\n}\n
  • Run make testacc TESTS=TestAcc{Service}Tags_ PKG={Service} and ensure there are no failures.
  • Create website/docs/r/{service}_tag.html.markdown with initial documentation similar to the following:
---\nsubcategory: \"{SERVICE}\"\nlayout: \"aws\"\npage_title: \"AWS: aws_{service}_tag\"\ndescription: |-\n  Manages an individual {SERVICE} resource tag\n---\n\n# Resource: aws_{service}_tag\n\nManages an individual {SERVICE} resource tag. This resource should only be used in cases where {SERVICE} resources are created outside Terraform (e.g., {SERVICE} {THING}s implicitly created by {OTHER SERVICE THING}).\n\n~> **NOTE:** This tagging resource should not be combined with the Terraform resource for managing the parent resource. For example, using `aws_{service}_{thing}` and `aws_{service}_tag` to manage tags of the same {SERVICE} {THING} will cause a perpetual difference where the `aws_{service}_{thing}` resource will try to remove the tag being added by the `aws_{service}_tag` resource.\n\n~> **NOTE:** This tagging resource does not use the [provider `ignore_tags` configuration](/docs/providers/aws/index.html#ignore_tags).\n\n## Example Usage\n\n```terraform\nresource \"aws_{service}_tag\" \"example\" {\nresource_arn = \"...\"\nkey          = \"Name\"\nvalue        = \"Hello World\"\n}\n```\n\n## Argument Reference\n\nThe following arguments are supported:\n\n* `resource_arn` - (Required) ARN of the {SERVICE} resource to tag.\n* `key` - (Required) Tag name.\n* `value` - (Required) Tag value.\n\n## Attributes Reference\n\nIn addition to all arguments above, the following attributes are exported:\n\n* `id` - {SERVICE} resource identifier and key, separated by a comma (`,`)\n\n## Import\n\n`aws_{service}_tag` can be imported by using the {SERVICE} resource identifier and key, separated by a comma (`,`), e.g.\n\n```console\n$ terraform import aws_{service}_tag.example arn:aws:{service}:us-east-1:123456789012:{thing}/example,Name\n```\n
"},{"location":"aws-go-sdk-base/","title":"aws-go-sdk-base","text":"

https://github.com/hashicorp/aws-sdk-go-base

This is a base library used by the AWS Provider, AWSCC Provider and the Terraform S3 Backend to allow them to handle authentication and other non-service level AWS interactions consistently.

Typically this changes infrequently and changes are normally performed by HashiCorp maintainers. It should not be necessary to change this library for the majority of provider contributions.

"},{"location":"aws-go-sdk-versions/","title":"AWS Go SDK Versions","text":"

The Terraform AWS Provider relies on the AWS SDK for Go which is maintained and published by AWS to allow us to safely and securely interact with AWS API's in a consistent fashion. There are two versions of this API, both of which are considered Generally Available and fully supported by AWS at present.

  • AWS SDKs and Tools maintenance policy
  • AWS SDKs and Tools version support matrix

While the vast majority of the provider is based on the AWS SDK for Go v1, the provider also allows the use of the AWS SDK for Go v2.

"},{"location":"aws-go-sdk-versions/#which-sdk-version-should-i-use","title":"Which SDK Version should I use?","text":"

Each Terraform provider implementation for an AWS service relies on a service client which in turn is constructed based on a specific SDK version. At present, we are slowly increasing our footprint on SDK v2, but are not actively migrating existing code to use v2. The choice of SDK will be as follows:

For new services, you should use AWS SDK for Go v2. AWS has a migration guide that details the differences between the versions of the SDK.

For existing services, use the version of the SDK that service currently uses. You can determine this by looking at the import section in the service's Go files.

"},{"location":"aws-go-sdk-versions/#what-does-the-sdk-handle","title":"What does the SDK handle?","text":"

The AWS SDKs handle calling the various web service interfaces for AWS services. In addition to encoding and decoding the Go structures in the correct JSON or XML payloads, the SDKs handle authentication, request logging, and retrying requests.

The various language SDKs and the AWS CLI share a consistent configuration interface, using environment variables and shared configuration and credentials files.

The AWS SDKs also automatically retry several common failure cases, such as network errors.

"},{"location":"aws-go-sdk-versions/#how-do-the-sdk-versions-differ","title":"How do the SDK versions differ?","text":"

The AWS SDK for Go v1.0.0 was released in late 2015, when the current version of Go was v1.5. The Go language has evolved significantly since then. Many currently-recommended practices were not possible at that time, including the use of the context package, introduced in Go v1.7, and error wrapping, introduced in Go v1.13.

The AWS SDK for Go v2 uses a modern Go style and has also been modularized, so that individual services are packaged and imported separately.

For details on the specific changes to the AWS SDK for Go v2, see Migrating to the AWS SDK for Go v2, especially the Service Clients section.

"},{"location":"bugs-and-enhancements/","title":"Making Small Changes to Existing Resources","text":"

Most contributions to the provider will take the form of small additions or bug-fixes on existing resources/data sources. In this case the existing resource will give you the best guidance on how the change should be structured, but we require the following to allow the change to be merged:

  • Acceptance test coverage of new behavior: Existing resources each have a set of acceptance tests covering their functionality. These tests should exercise all the behavior of the resource. Whether you are adding something or fixing a bug, the idea is to have an acceptance test that fails if your code were to be removed. Sometimes it is sufficient to \"enhance\" an existing test by adding an assertion or tweaking the config that is used, but it's often better to add a new test. You can copy/paste an existing test and follow the conventions you see there, modifying the test to exercise the behavior of your code.
  • Documentation updates: If your code makes any changes that need to be documented, you should include those documentation changes in the same PR. This includes things like new resource attributes or changes in default values.
  • Well-formed Code: Do your best to follow existing conventions you see in the codebase, and ensure your code is formatted with go fmt. The PR reviewers can help out on this front, and may provide comments with suggestions on how to improve the code.
  • Dependency updates: Create a separate PR if you are updating dependencies. This is to avoid conflicts as version updates tend to be fast- moving targets. We will plan to merge the PR with this change first.
  • Changelog entry: Assuming the code change affects Terraform operators, the relevant PR ought to include a user-facing changelog entry describing the new behavior.
"},{"location":"changelog-process/","title":"Changelog Process","text":"

HashiCorp\u2019s open-source projects have always maintained user-friendly, readable CHANGELOG.md that allow users to tell at a glance whether a release should have any effect on them, and to gauge the risk of an upgrade.

We use the go-changelog to generate and update the changelog from files created in the .changelog/ directory. It is important that when you raise your Pull Request, there is a changelog entry which describes the changes your contribution makes. Not all changes require an entry in the changelog, guidance follows on what changes do.

"},{"location":"changelog-process/#changelog-format","title":"Changelog format","text":"

The changelog format requires an entry in the following format, where HEADER corresponds to the changelog category, and the entry is the changelog entry itself. The entry should be included in a file in the .changelog directory with the naming convention {PR-NUMBER}.txt. For example, to create a changelog entry for pull request 1234, there should be a file named .changelog/1234.txt.

```release-note:{HEADER}\n{ENTRY}\n```\n

If a pull request should contain multiple changelog entries, then multiple blocks can be added to the same changelog file. For example:

```release-note:note\nresource/aws_example_thing: The `broken` attribute has been deprecated. All configurations using `broken` should be updated to use the new `not_broken` attribute instead.\n```\n\n```release-note:enhancement\nresource/aws_example_thing: Add `not_broken` attribute\n```\n
"},{"location":"changelog-process/#pull-request-types-to-changelog","title":"Pull request types to CHANGELOG","text":"

The CHANGELOG is intended to show operator-impacting changes to the codebase for a particular version. If every change or commit to the code resulted in an entry, the CHANGELOG would become less useful for operators. The lists below are general guidelines and examples for when a decision needs to be made to decide whether a change should have an entry.

"},{"location":"changelog-process/#changes-that-should-have-a-changelog-entry","title":"Changes that should have a CHANGELOG entry","text":""},{"location":"changelog-process/#new-resource","title":"New resource","text":"

A new resource entry should only contain the name of the resource, and use the release-note:new-resource header.

```release-note:new-resource\naws_secretsmanager_secret_policy\n```\n
"},{"location":"changelog-process/#new-data-source","title":"New data source","text":"

A new data source entry should only contain the name of the data source, and use the release-note:new-data-source header.

```release-note:new-data-source\naws_workspaces_workspace\n```\n
"},{"location":"changelog-process/#new-full-length-documentation-guides-eg-eks-getting-started-guide-iam-policy-documents-with-terraform","title":"New full-length documentation guides (e.g., EKS Getting Started Guide, IAM Policy Documents with Terraform)","text":"

A new full length documentation entry gives the title of the documentation added, using the release-note:new-guide header.

```release-note:new-guide\nCustom Service Endpoint Configuration\n```\n
"},{"location":"changelog-process/#resource-and-provider-bug-fixes","title":"Resource and provider bug fixes","text":"

A new bug entry should use the release-note:bug header and have a prefix indicating the resource or data source it corresponds to, a colon, then followed by a brief summary. Use a provider prefix for provider level fixes.

```release-note:bug\nresource/aws_glue_classifier: Fix quote_symbol being optional\n```\n
"},{"location":"changelog-process/#resource-and-provider-enhancements","title":"Resource and provider enhancements","text":"

A new enhancement entry should use the release-note:enhancement header and have a prefix indicating the resource or data source it corresponds to, a colon, then followed by a brief summary. Use a provider prefix for provider level enhancements.

```release-note:enhancement\nresource/aws_eip: Add network_border_group argument\n```\n
"},{"location":"changelog-process/#deprecations","title":"Deprecations","text":"

A deprecation entry should use the release-note:note header and have a prefix indicating the resource or data source it corresponds to, a colon, then followed by a brief summary. Use a provider prefix for provider level changes.

```release-note:note\nresource/aws_dx_gateway_association: The vpn_gateway_id attribute is being deprecated in favor of the new associated_gateway_id attribute to support transit gateway associations\n```\n
"},{"location":"changelog-process/#breaking-changes-and-removals","title":"Breaking changes and removals","text":"

A breaking-change entry should use the release-note:breaking-change header and have a prefix indicating the resource or data source it corresponds to, a colon, then followed by a brief summary. Use a provider prefix for provider level changes.

```release-note:breaking-change\nresource/aws_lambda_alias: Resource import no longer converts Lambda Function name to ARN\n```\n
"},{"location":"changelog-process/#region-validation-support","title":"Region validation support","text":"
```release-note:note\nprovider: Region validation now automatically supports the new `XX-XXXXX-#` (Location) region. For AWS operations to work in the new region, the region must be explicitly enabled as outlined in the [AWS Documentation](https://docs.aws.amazon.com/general/latest/gr/rande-manage.html#rande-manage-enable). When the region is not enabled, the Terraform AWS Provider will return errors during credential validation (e.g., `error validating provider credentials: error calling sts:GetCallerIdentity: InvalidClientTokenId: The security token included in the request is invalid`) or AWS operations will throw their own errors (e.g., `data.aws_availability_zones.available: Error fetching Availability Zones: AuthFailure: AWS was not able to validate the provided access credentials`). [GH-####]\n```\n\n```release-note:enhancement\n* provider: Support automatic region validation for `XX-XXXXX-#` [GH-####]\n```\n
"},{"location":"changelog-process/#changes-that-may-have-a-changelog-entry","title":"Changes that may have a CHANGELOG entry","text":"

Dependency updates: If the update contains relevant bug fixes or enhancements that affect operators, those should be called out. Any changes which do not fit into the above categories but warrant highlighting. Use resource/data source/provider prefixes where appropriate.

```release-note:note\nresource/aws_lambda_alias: Resource import no longer converts Lambda Function name to ARN\n```\n
"},{"location":"changelog-process/#changes-that-should-not-have-a-changelog-entry","title":"Changes that should not have a CHANGELOG entry","text":"
  • Resource and provider documentation updates
  • Testing updates
  • Code refactoring
"},{"location":"core-services/","title":"Terraform AWS Provider Core Services","text":"

Core Services are AWS services we have identified as critical for a large majority of our users. Our goal is to continually increase the depth of coverage for these services. We will work to prioritize features and enhancements to these services in each weekly release, even if they are not necessarily highlighted in our quarterly roadmap.

The core services we have identified are:

  • EC2

  • Lambda

  • EKS

  • ECS

  • VPC

  • S3

  • RDS

  • DynamoDB

  • IAM

  • Autoscaling (ASG)

  • ElastiCache

We'll continue to evaluate the selected services as our user base grows and changes.

"},{"location":"data-handling-and-conversion/","title":"Data Handling and Conversion","text":"

The Terraform AWS Provider codebase bridges the implementation of a Terraform Plugin and an AWS API client to support AWS operations and data types as Terraform Resources. Data handling and conversion is a large portion of resource implementation given the domain specific implementations of each side of the provider. The first where Terraform is a generic infrastructure as code tool with a generic data model and the other where the details are driven by AWS API data modeling concepts. This guide is intended to explain and show preferred Terraform AWS Provider code implementations required to successfully translate data between these two systems.

At the bottom of this documentation is a Glossary section, which may be a helpful reference while reading the other sections.

"},{"location":"data-handling-and-conversion/#data-conversions-in-terraform-providers","title":"Data Conversions in Terraform Providers","text":"

Before getting into highly specific documentation about the Terraform AWS Provider handling of data, it may be helpful to briefly highlight how Terraform Plugins (Terraform Providers in this case) interact with Terraform CLI and the Terraform State in general and where this documentation fits into the whole process.

There are two primary data flows that are typically handled by resources within a Terraform Provider. Data is either being converted from a planned new Terraform State into making a remote system request or a remote system response is being converted into a applied new Terraform State. The semantics of how the data of the planned new Terraform State is surfaced to the resource implementation is determined by where a resource is in its lifecycle and mainly handled by Terraform CLI. This concept can be explored further in the Terraform Resource Instance Change Lifecycle documentation, with the caveat that some additional behaviors occur within the Terraform Plugin SDK as well (if the Terraform Plugin uses that implementation detail).

As a generic walkthrough, the following data handling occurs when creating a Terraform Resource:

  • An operator creates a Terraform configuration with a new resource defined and runs terraform apply
  • Terraform CLI merges an empty prior state for the resource, along with the given configuration state, to create a planned new state for the resource
  • Terraform CLI sends a Terraform Plugin Protocol request to create the new resource with its planned new state data
  • If the Terraform Plugin is using a higher level library, such as the Terraform Plugin SDK, that library receives the request and translates the Terraform Plugin Protocol data types into the expected library types
  • Terraform Plugin invokes the resource creation function with the planned new state data
    • The planned new state data is converted into an remote system request (e.g., API creation request) that is invoked
    • The remote system response is received and the data is converted into an applied new state
  • If the Terraform Plugin is using a higher level library, such as the Terraform Plugin SDK, that library translates the library types back into Terraform Plugin Protocol data types
  • Terraform Plugin responds to Terraform Plugin Protocol request with the new state data
  • Terraform CLI verifies and stores the new state

The highlighted lines are the focus of this documentation today. In the future however, the Terraform AWS Provider may replace certain functionality in the items mentioning the Terraform Plugin SDK above to workaround certain limitations of that particular library.

"},{"location":"data-handling-and-conversion/#implicit-state-passthrough","title":"Implicit State Passthrough","text":"

An important behavior to note with Terraform State handling is if the value of a particular root attribute or block is not refreshed during plan or apply operations, then the prior Terraform State is implicitly deep copied to the new Terraform State for that attribute or block.

Given a resource with a writeable root attribute named not_set_attr that never calls d.Set(\"not_set_attr\", /* ... nil or value */), the following happens:

  • If the Terraform configuration contains not_set_attr = \"anything\" on resource creation, the Terraform State contains not_set_attr equal to \"anything\" after apply.
  • If the Terraform configuration is updated to not_set_attr = \"updated\", the Terraform State contains not_set_attr equal to \"updated\" after apply.
  • If the attribute was meant to be associated with a remote system value, it will never update the Terraform State on plan or apply with the remote value. Effectively, it cannot perform drift detection with the remote value.

This however does not apply for nested attributes and blocks if the parent block is refreshed. Given a resource with a root block named parent, nested child attributes named set_attr and not_set_attr, and that calls d.Set(\"parent\", /* ... only refreshes nested set_attr ... */), the Terraform State for the nested not_set_attr will not be copied.

There are valid use cases for passthrough attribute values such as these (see the Virtual Attributes section), however the behavior can be confusing or incorrect for operators if the drift detection is expected. Typically these types of drift detection issues can be discovered by implementing resource import testing with state verification.

"},{"location":"data-handling-and-conversion/#data-conversions-in-the-terraform-aws-provider","title":"Data Conversions in the Terraform AWS Provider","text":"

To expand on the data handling that occurs specifically within the Terraform AWS Provider resource implementations, the above resource creation items become the below in practice given our current usage of the Terraform Plugin SDK:

  • The Create/CreateWithoutTimeout function of a schema.Resource is invoked with *schema.ResourceData containing the planned new state data (conventionally named d) and an AWS API client (conventionally named meta).
    • Note: Before reaching this point, the ResourceData was already translated from the Terraform Plugin Protocol data types by the Terraform Plugin SDK so values can be read by invoking d.Get() and d.GetOk() receiver methods with Attribute and Block names from the Schema of the schema.Resource.
  • An AWS Go SDK operation input type (e.g., *ec2.CreateVpcInput) is initialized
  • For each necessary field to configure in the operation input type, the data is read from the ResourceData (e.g., d.Get(), d.GetOk()) and converted into the AWS Go SDK type for the field (e.g., *string)
  • The AWS Go SDK operation is invoked and the output type (e.g., *ec2.CreateVpcOutput) is initialized
  • For each necessary Attribute, Block, or resource identifier to be saved in the state, the data is read from the AWS Go SDK type for the field (*string), if necessary converted into a ResourceData compatible type, and saved into a mutated ResourceData (e.g., d.Set(), d.SetId())
  • Function is returned
"},{"location":"data-handling-and-conversion/#type-mapping","title":"Type Mapping","text":"

To further understand the necessary data conversions used throughout the Terraform AWS Provider codebase between AWS Go SDK types and the Terraform Plugin SDK, the following table can be referenced for most scenarios:

AWS API Model AWS Go SDK Terraform Plugin SDK Terraform Language/State boolean *bool TypeBool (bool) bool float *float64 TypeFloat (float64) number integer *int64 TypeInt (int) number list []*T TypeList ([]interface{} of T)TypeSet (*schema.Set of T) list(any)set(any) map map[T1]*T2 TypeMap (map[string]interface{}) map(any) string *string TypeString (string) string structure struct TypeList ([]interface{} of map[string]interface{}) with MaxItems: 1 list(object(any)) timestamp *time.Time TypeString (typically RFC3339 formatted) string

You may notice there are type encoding differences the AWS Go SDK and Terraform Plugin SDK:

  • AWS Go SDK types are all Go pointer types, while Terraform Plugin SDK types are not.
  • AWS Go SDK structures are the Go struct type, while there is no semantically equivalent Terraform Plugin SDK type. Instead they are represented as a slice of interfaces with an underlying map of interfaces.
  • AWS Go SDK types are all Go concrete types, while the Terraform Plugin SDK types for collections and maps are interfaces.
  • AWS Go SDK whole numeric type is always 64-bit, while the Terraform Plugin SDK type is implementation-specific.

Conceptually, the first and second items above the most problematic in the Terraform AWS Provider codebase. The first item because non-pointer types in Go cannot implement the concept of no value (nil). The Zero Value Mapping section will go into more details about the implications of this limitation. The second item because it can be confusing to always handle a structure (\"object\") type as a list.

There are efforts to replace the Terraform Plugin type system with one similar the underlying Terraform CLI type system. As these efforts materialize, this documentation will be updated.

"},{"location":"data-handling-and-conversion/#zero-value-mapping","title":"Zero Value Mapping","text":"

As mentioned in the Type Mapping section, there is a discrepancy with how the Terraform Plugin SDK represents values and the reality that a Terraform State may not configure an Attribute. These values will default to the matching underlying Go type \"zero value\" if not set:

Terraform Plugin SDK Go Type Zero Value TypeBool bool false TypeFloat float64 0.0 TypeInt int 0 TypeString string \"\"

For Terraform resource logic this means that these special values must always be accounted for in implementation. The semantics of the API and its meaning of the zero value will determine whether:

  • If it is not used/needed, then generally the zero value can safely be used to store an \"unset\" value and should be ignored when sending to the API.
  • If it is used/needed, whether:
    • A value can always be set and it is safe to always send to the API. Generally, boolean values fall into this category.
    • A different default/sentinel value must be used as the \"unset\" value so it can either match the default of the API or be ignored when sending to the API.
    • A special type implementation is required within the schema to workaround the limitation.

The maintainers can provide guidance on appropriate solutions for cases not mentioned in the Recommended Implementation section.

"},{"location":"data-handling-and-conversion/#root-attributes-versus-block-attributes","title":"Root Attributes Versus Block Attributes","text":"

All Attributes and Blocks at the top level of schema.Resource Schema are considered \"root\" attributes. These will always be handled with receiver methods on ResourceData, such as reading with d.Get(), d.GetOk(), etc. and writing with d.Set(). Any nested Attributes and Blocks inside those root Blocks will then be handled with standard Go types according to the table in the Type Mapping section.

By convention in the codebase, each level of Block handling beyond root attributes should be separated into \"expand\" functions that convert Terraform Plugin SDK data into the equivalent AWS Go SDK type (typically named expand{Service}{Type}) and \"flatten\" functions that convert an AWS Go SDK type into the equivalent Terraform Plugin SDK data (typically named flatten{Service}{Type}). The Recommended Implementations section will go into those details.

NOTE: While it is possible in certain type scenarios to deeply read and write ResourceData information for a Block Attribute, this practice is discouraged in preference of only handling root Attributes and Blocks.

"},{"location":"data-handling-and-conversion/#recommended-implementations","title":"Recommended Implementations","text":"

Given the various complexities around the Terraform Plugin SDK type system, this section contains recommended implementations for Terraform AWS Provider resource code based on the Type Mapping section and the features of the Terraform Plugin SDK and AWS Go SDK. The eventual goal and styling for many of these recommendations is to ease static analysis of the codebase and future potential code generation efforts.

Some of these coding patterns may not be well represented in the codebase, as refactoring the many older styles over years of community development is a large task. However this is meant to represent the preferred implementations today. These will continue to evolve as this codebase and the Terraform Plugin ecosystem changes.

"},{"location":"data-handling-and-conversion/#where-to-define-flex-functions","title":"Where to Define Flex Functions","text":"

Define FLatten and EXpand (i.e., flex) functions at the most local level possible. This table provides guidance on the preferred place to define flex functions based on usage.

Where Used Where to Define Include Service in Name One resource (e.g., aws_instance) Resource file (e.g., internal/service/ec2/instance.go) No Few resources in one service (e.g., EC2) Resource file or service flex file (e.g., internal/service/ec2/flex.go) No Widely used in one service (e.g., EC2) Service flex file (e.g., internal/service/ec2/flex.go) No Two services (e.g., EC2 and EKS) Define a copy in each service If helpful 3+ services internal/flex/flex.go Yes"},{"location":"data-handling-and-conversion/#expand-functions-for-blocks","title":"Expand Functions for Blocks","text":"
func expandStructure(tfMap map[string]interface{}) *service.Structure {\nif tfMap == nil {\nreturn nil\n}\n\napiObject := &service.Structure{}\n\n// ... nested attribute handling ...\n\nreturn apiObject\n}\n\nfunc expandStructures(tfList []interface{}) []*service.Structure {\nif len(tfList) == 0 {\nreturn nil\n}\n\nvar apiObjects []*service.Structure\n\nfor _, tfMapRaw := range tfList {\ntfMap, ok := tfMapRaw.(map[string]interface{})\n\nif !ok {\ncontinue\n}\n\napiObject := expandStructure(tfMap)\n\nif apiObject == nil {\ncontinue\n}\n\napiObjects = append(apiObjects, apiObject)\n}\n\nreturn apiObjects\n}\n
"},{"location":"data-handling-and-conversion/#flatten-functions-for-blocks","title":"Flatten Functions for Blocks","text":"
func flattenStructure(apiObject *service.Structure) map[string]interface{} {\nif apiObject == nil {\nreturn nil\n}\n\ntfMap := map[string]interface{}{}\n\n// ... nested attribute handling ...\n\nreturn tfMap\n}\n\nfunc flattenStructures(apiObjects []*service.Structure) []interface{} {\nif len(apiObjects) == 0 {\nreturn nil\n}\n\nvar tfList []interface{}\n\nfor _, apiObject := range apiObjects {\nif apiObject == nil {\ncontinue\n}\n\ntfList = append(tfList, flattenStructure(apiObject))\n}\n\nreturn tfList\n}\n
"},{"location":"data-handling-and-conversion/#root-typebool-and-aws-boolean","title":"Root TypeBool and AWS Boolean","text":"

To read, if always sending the attribute value is correct:

input := service.ExampleOperationInput{\nAttributeName: aws.String(d.Get(\"attribute_name\").(bool))\n}\n

Otherwise to read, if only sending the attribute value when true is preferred (!ok for opposite):

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok {\ninput.AttributeName = aws.Bool(v.(bool))\n}\n

To write:

d.Set(\"attribute_name\", output.Thing.AttributeName)\n
"},{"location":"data-handling-and-conversion/#root-typefloat-and-aws-float","title":"Root TypeFloat and AWS Float","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok {\ninput.AttributeName = aws.Float64(v.(float64))\n}\n

To write:

d.Set(\"attribute_name\", output.Thing.AttributeName)\n
"},{"location":"data-handling-and-conversion/#root-typeint-and-aws-integer","title":"Root TypeInt and AWS Integer","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok {\ninput.AttributeName = aws.Int64(int64(v.(int)))\n}\n

To write:

d.Set(\"attribute_name\", output.Thing.AttributeName)\n
"},{"location":"data-handling-and-conversion/#root-typelist-of-resource-and-aws-list-of-structure","title":"Root TypeList of Resource and AWS List of Structure","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok && len(v.([]interface{})) > 0 {\ninput.AttributeName = expandStructures(v.([]interface{}))\n}\n

To write:

if err := d.Set(\"attribute_name\", flattenStructures(output.Thing.AttributeName)); err != nil {\nreturn diag.Errorf(\"setting attribute_name: %s\", err)\n}\n
"},{"location":"data-handling-and-conversion/#root-typelist-of-resource-and-aws-structure","title":"Root TypeList of Resource and AWS Structure","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil {\ninput.AttributeName = expandStructure(v.([]interface{})[0].(map[string]interface{}))\n}\n

To write (likely to have helper function introduced soon):

if output.Thing.AttributeName != nil {\nif err := d.Set(\"attribute_name\", []interface{}{flattenStructure(output.Thing.AttributeName)}); err != nil {\nreturn diag.Errorf(\"setting attribute_name: %s\", err)\n}\n} else {\nd.Set(\"attribute_name\", nil)\n}\n
"},{"location":"data-handling-and-conversion/#root-typelist-of-typestring-and-aws-list-of-string","title":"Root TypeList of TypeString and AWS List of String","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok && len(v.([]interface{})) > 0 {\ninput.AttributeName = flex.ExpandStringList(v.([]interface{}))\n}\n

To write:

d.Set(\"attribute_name\", aws.StringValueSlice(output.Thing.AttributeName))\n
"},{"location":"data-handling-and-conversion/#root-typemap-of-typestring-and-aws-map-of-string","title":"Root TypeMap of TypeString and AWS Map of String","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok && len(v.(map[string]interface{})) > 0 {\ninput.AttributeName = flex.ExpandStringMap(v.(map[string]interface{}))\n}\n

To write:

d.Set(\"attribute_name\", aws.StringValueMap(output.Thing.AttributeName))\n
"},{"location":"data-handling-and-conversion/#root-typeset-of-resource-and-aws-list-of-structure","title":"Root TypeSet of Resource and AWS List of Structure","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok && v.(*schema.Set).Len() > 0 {\ninput.AttributeName = expandStructures(v.(*schema.Set).List())\n}\n

To write:

if err := d.Set(\"attribute_name\", flattenStructures(output.Thing.AttributeNames)); err != nil {\nreturn diag.Errorf(\"setting attribute_name: %s\", err)\n}\n
"},{"location":"data-handling-and-conversion/#root-typeset-of-typestring-and-aws-list-of-string","title":"Root TypeSet of TypeString and AWS List of String","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok && v.(*schema.Set).Len() > 0 {\ninput.AttributeName = flex.ExpandStringSet(v.(*schema.Set))\n}\n

To write:

d.Set(\"attribute_name\", aws.StringValueSlice(output.Thing.AttributeName))\n
"},{"location":"data-handling-and-conversion/#root-typestring-and-aws-string","title":"Root TypeString and AWS String","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok {\ninput.AttributeName = aws.String(v.(string))\n}\n

To write:

d.Set(\"attribute_name\", output.Thing.AttributeName)\n
"},{"location":"data-handling-and-conversion/#root-typestring-and-aws-timestamp","title":"Root TypeString and AWS Timestamp","text":"

To ensure that parsing the read string value does not fail, define attribute_name's schema.Schema with an appropriate ValidateFunc:

\"attribute_name\": {\nType:         schema.TypeString,\n// ...\nValidateFunc: validation.IsRFC3339Time,\n},\n

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := d.GetOk(\"attribute_name\"); ok {\nv, _ := time.Parse(time.RFC3339, v.(string))\n\ninput.AttributeName = aws.Time(v)\n}\n

To write:

if output.Thing.AttributeName != nil {\nd.Set(\"attribute_name\", aws.TimeValue(output.Thing.AttributeName).Format(time.RFC3339))\n} else {\nd.Set(\"attribute_name\", nil)\n}\n
"},{"location":"data-handling-and-conversion/#nested-typebool-and-aws-boolean","title":"Nested TypeBool and AWS Boolean","text":"

To read, if always sending the attribute value is correct:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].(bool); ok {\napiObject.NestedAttributeName = aws.Bool(v)\n}\n\n// ...\n}\n

To read, if only sending the attribute value when true is preferred (!v for opposite):

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].(bool); ok && v {\napiObject.NestedAttributeName = aws.Bool(v)\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = aws.BoolValue(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typefloat-and-aws-float","title":"Nested TypeFloat and AWS Float","text":"

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].(float64); ok && v != 0.0 {\napiObject.NestedAttributeName = aws.Float64(v)\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = aws.Float64Value(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typeint-and-aws-integer","title":"Nested TypeInt and AWS Integer","text":"

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].(int); ok && v != 0 {\napiObject.NestedAttributeName = aws.Int64(int64(v))\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = aws.Int64Value(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typelist-of-resource-and-aws-list-of-structure","title":"Nested TypeList of Resource and AWS List of Structure","text":"

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].([]interface{}); ok && len(v) > 0 {\napiObject.NestedAttributeName = expandStructures(v)\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = flattenNestedStructures(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typelist-of-resource-and-aws-structure","title":"Nested TypeList of Resource and AWS Structure","text":"

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].([]interface{}); ok && len(v) > 0 && v[0] != nil {\napiObject.NestedAttributeName = expandStructure(v[0].(map[string]interface{}))\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = []interface{}{flattenNestedStructure(v)}\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typelist-of-typestring-and-aws-list-of-string","title":"Nested TypeList of TypeString and AWS List of String","text":"

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].([]interface{}); ok && len(v) > 0 {\napiObject.NestedAttributeName = flex.ExpandStringList(v)\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = aws.StringValueSlice(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typemap-of-typestring-and-aws-map-of-string","title":"Nested TypeMap of TypeString and AWS Map of String","text":"

To read:

input := service.ExampleOperationInput{}\n\nif v, ok := tfMap[\"nested_attribute_name\"].(map[string]interface{}); ok && len(v) > 0 {\napiObject.NestedAttributeName = flex.ExpandStringMap(v)\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = aws.StringValueMap(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typeset-of-resource-and-aws-list-of-structure","title":"Nested TypeSet of Resource and AWS List of Structure","text":"

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].(*schema.Set); ok && v.Len() > 0 {\napiObject.NestedAttributeName = expandStructures(v.List())\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = flattenNestedStructures(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typeset-of-typestring-and-aws-list-of-string","title":"Nested TypeSet of TypeString and AWS List of String","text":"

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].(*schema.Set); ok && v.Len() > 0 {\napiObject.NestedAttributeName = flex.ExpandStringSet(v)\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = aws.StringValueSlice(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typestring-and-aws-string","title":"Nested TypeString and AWS String","text":"

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].(string); ok && v != \"\" {\napiObject.NestedAttributeName = aws.String(v)\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = aws.StringValue(v)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#nested-typestring-and-aws-timestamp","title":"Nested TypeString and AWS Timestamp","text":"

To ensure that parsing the read string value does not fail, define nested_attribute_name's schema.Schema with an appropriate ValidateFunc:

\"nested_attribute_name\": {\nType:         schema.TypeString,\n// ...\nValidateFunc: validation.IsRFC3339Time,\n},\n

To read:

func expandStructure(tfMap map[string]interface{}) *service.Structure {\n// ...\n\nif v, ok := tfMap[\"nested_attribute_name\"].(string); ok && v != \"\" {\nv, _ := time.Parse(time.RFC3339, v)\n\napiObject.NestedAttributeName = aws.Time(v)\n}\n\n// ...\n}\n

To write:

func flattenStructure(apiObject *service.Structure) map[string]interface{} {\n// ...\n\nif v := apiObject.NestedAttributeName; v != nil {\ntfMap[\"nested_attribute_name\"] = aws.TimeValue(v).Format(time.RFC3339)\n}\n\n// ...\n}\n
"},{"location":"data-handling-and-conversion/#further-guidelines","title":"Further Guidelines","text":"

This section includes additional topics related to data design and decision making from the Terraform AWS Provider maintainers.

"},{"location":"data-handling-and-conversion/#binary-values","title":"Binary Values","text":"

Certain resources may need to interact with binary (non UTF-8) data while the Terraform State only supports UTF-8 data. Configurations attempting to pass binary data to an attribute will receive an error from Terraform CLI. These attributes should expect and store the value as a Base64 string while performing any necessary encoding or decoding in the resource logic.

"},{"location":"data-handling-and-conversion/#destroy-state-values","title":"Destroy State Values","text":"

During resource destroy operations, only previously applied Terraform State values are available to resource logic. Even if the configuration is updated in a manner where both the resource destroy is triggered (e.g., setting the resource meta-argument count = 0) and an attribute value is updated, the resource logic will only have the previously applied data values.

Any usage of attribute values during destroy should explicitly note in the resource documentation that the desired value must be applied into the Terraform State before any apply to destroy the resource.

"},{"location":"data-handling-and-conversion/#hashed-values","title":"Hashed Values","text":"

Attribute values may be very lengthy or potentially contain Sensitive Values. A potential solution might be to use a hashing algorithm, such as MD5 or SHA256, to convert the value before saving in the Terraform State to reduce its relative size or attempt to obfuscate the value. However, there are a few reasons not to do so:

  • Terraform expects any planned values to match applied values. Ensuring proper handling during the various Terraform operations such as difference planning and Terraform State storage can be a burden.
  • Hashed values are generally unusable in downstream attribute references. If a value is hashed, it cannot be successfully used in another resource or provider configuration that expects the real value.
  • Terraform plan differences are meant to be human readable. If a value is hashed, operators will only see the relatively unhelpful hash differences abc123 -> def456 in plans.

Any value hashing implementation will not be accepted. An exception to this guidance is if the remote system explicitly provides a separate hash value in responses, in which a resource can provide a separate attribute with that hashed value.

"},{"location":"data-handling-and-conversion/#sensitive-values","title":"Sensitive Values","text":"

Marking an Attribute in the Terraform Plugin SDK Schema with Sensitive has the following real world implications:

  • All occurrences of the Attribute will have the value hidden in plan difference output. In the context of an Attribute within a Block, all Blocks will hide all values of the Attribute.
  • In Terraform CLI 0.14 (with the provider_sensitive_attrs experiment enabled) and later, any downstream references to the value in other configuration will hide the value in plan difference output.

The value is either always hidden or not as the Terraform Plugin SDK does not currently implement conditional support for this functionality. Since Terraform Configurations have no control over the behavior, hiding values from the plan difference can incur a potentially undesirable user experience cost for operators.

Given that and especially with the improvements in Terraform CLI 0.14, the Terraform AWS Provider maintainers guiding principles for determining whether an Attribute should be marked as Sensitive is if an Attribute value:

  • Objectively will always contain a credential, password, or other secret material. Operators can have differing opinions on what constitutes secret material and the maintainers will make best effort determinations, if necessary consulting with the HashiCorp Security team.
  • If the Attribute is within a Block, that all occurrences of the Attribute value will objectively contain secret material. Some APIs (and therefore the Terraform AWS Provider resources) implement generic \"setting\" and \"value\" structures which likely will contain a mixture of secret and non-secret material. These will generally not be accepted for marking as Sensitive.

If you are unsatisfied with sensitive value handling, the maintainers can recommend ensuring there is a covering issue in the Terraform CLI and/or Terraform Plugin SDK projects explaining the use case. Ultimately, Terraform Plugins including the Terraform AWS Provider cannot implement their own sensitive value abilities if the upstream projects do not implement the appropriate functionality.

"},{"location":"data-handling-and-conversion/#virtual-attributes","title":"Virtual Attributes","text":"

Attributes which only exist within Terraform and not the remote system are typically referred as virtual attributes. Especially in the case of Destroy State Values, these attributes rely on the Implicit State Passthrough behavior of values in Terraform to be available in resource logic. A fictitous example of one of these may be a resource attribute such as a skip_waiting flag, which is used only in the resource logic to skip the typical behavior of waiting for operations to complete.

If a virtual attribute has a default value that does not match the Zero Value Mapping for the type, it is recommended to explicitly call d.Set() with the default value in the schema.Resource Importer State function, for example:

&schema.Resource{\n// ... other fields ...\nImporter: &schema.ResourceImporter{\nState: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {\nd.Set(\"skip_waiting\", true)\n\nreturn []*schema.ResourceData{d}, nil\n},\n},\n}\n

This helps prevent an immediate plan difference after resource import unless the configuration has a non-default value.

"},{"location":"data-handling-and-conversion/#glossary","title":"Glossary","text":"

Below is a listing of relevant terms and descriptions for data handling and conversion in the Terraform AWS Provider to establish common conventions throughout this documentation. This list is not exhaustive of all concepts of Terraform Plugins, the Terraform AWS Provider, or the data handling that occurs during Terraform runs, but these should generally provide enough context about the topics discussed here.

  • AWS Go SDK: Library that converts Go code into AWS Service API compatible operations and data types. Currently refers to version 1 (v1) available since 2015, however version 2 (v2) will reach general availability status soon. Project.
  • AWS Go SDK Model: AWS Go SDK compatible format of AWS Service API Model.
  • AWS Go SDK Service: AWS Service API Go code generated from the AWS Go SDK Model. Generated by the AWS Go SDK code.
  • AWS Service API: Logical boundary of an AWS service by API endpoint. Some large AWS services may be marketed with many different product names under the same service API (e.g., VPC functionality is part of the EC2 API) and vice-versa where some services may be marketed with one product name but are split into multiple service APIs (e.g., Single Sign-On functionality is split into the Identity Store and SSO Admin APIs).
  • AWS Service API Model: Declarative description of the AWS Service API operations and data types. Generated by the AWS service teams. Used to operate the API and generate API clients such as the various AWS Software Development Kits (SDKs).
  • Terraform Language (\"Configuration\"): Configuration syntax interpreted by the Terraform CLI. An implementation of HCL. Full Documentation.
  • Terraform Plugin Protocol: Description of Terraform Plugin operations and data types. Currently based on the Remote Procedure Call (RPC) library gRPC.
  • Terraform Plugin Go: Low-level library that converts Go code into Terraform Plugin Protocol compatible operations and data types. Not currently implemented in the Terraform AWS Provider. Project.
  • Terraform Plugin SDK: High-level library that converts Go code into Terraform Plugin Protocol compatible operations and data types. Project.
  • Terraform Plugin SDK Schema: Declarative description of types and domain specific behaviors for a Terraform provider, including resources and attributes. Full Documentation.
  • Terraform State: Bindings between objects in a remote system (e.g., an EC2 VPC) and a Terraform configuration (e.g., an aws_vpc resource configuration). Full Documentation.

AWS Service API Models use specific terminology to describe data and types:

  • Enumeration: Collection of valid values for a Shape.
  • Operation: An API call. Includes information about input, output, and error Shapes.
  • Shape: Type description.
    • boolean: Boolean value.
    • float: Fractional numeric value. May contain value validation such as maximum or minimum.
    • integer: Whole numeric value. May contain value validation such as maximum or minimum.
    • list: Collection that contains member Shapes. May contain value validation such as maximum or minimum keys.
    • map: Grouping of key Shape to value Shape. May contain value validation such as maximum or minimum keys.
    • string: Sequence of characters. May contain value validation such as an enumeration, regular expression pattern, maximum length, or minimum length.
    • structure: Object that contains member Shapes. May represent an error.
    • timestamp: Date and time value.

The Terraform Language uses the following terminology to describe data and types:

  • Attribute (\"Argument\"): Assigns a name to a data value.
  • Block (\"Configuration Block\"): Container type for Attributes or Blocks.
  • null: Virtual value equivalent to the Attribute not being set.
  • Types: Full Documentation.
    • any: Virtual type representing any concrete type in type declarations.
    • bool: Boolean value.
    • list (\"tuple\"): Ordered collection of values.
    • map (\"object\"): Grouping of string keys to values.
    • number: Numeric value. Can be either whole or fractional numbers.
    • set: Unordered collection of values.
    • string: Sequence of characters.

Terraform Plugin SDK Schemas use the following terminology to describe data and types:

  • Behaviors: Full Documentation.
    • Sensitive: Whether the value should be hidden from user interface output.
    • StateFunc: Conversion function between the value set by the Terraform Plugin and the value seen by Terraform Plugin SDK (and ultimately the Terraform State).
  • Element: Underylying value type for a collection or grouping Schema.
  • Resource Data: Data representation of a Resource Schema. Translation layer between the Schema and Go code of a Terraform Plugin. In the Terraform Plugin SDK, the ResourceData Go type.
  • Resource Schema: Grouping of Schema that represents a Terraform Resource.
  • Schema: Represents an Attribute or Block. Has a Type and Behavior(s).
  • Types: Full Documentation.
    • TypeBool: Boolean value.
    • TypeFloat: Fractional numeric value.
    • TypeInt: Whole numeric value.
    • TypeList: Ordered collection of values or Blocks.
    • TypeMap: Grouping of key Type to value Type.
    • TypeSet: Unordered collection of values or Blocks.
    • TypeString: Sequence of characters value.

Some other terms that may be used:

  • Block Attribute (\"Child Attribute\", \"Nested Attribute\"): Block level Attribute.
  • Expand Function: Function that converts Terraform Plugin SDK data into the equivalent AWS Go SDK type.
  • Flatten Function: Function that converts an AWS Go SDK type into the equivalent Terraform Plugin SDK data.
  • NullableTypeBool: Workaround \"schema type\" created to accept a boolean value that is not configured in addition to true and false. Not implemented in the Terraform Plugin SDK, but uses TypeString (where \"\" represents not configured) and additional validation.
  • NullableTypeFloat: Workaround \"schema type\" created to accept a fractional numeric value that is not configured in addition to 0.0. Not implemented in the Terraform Plugin SDK, but uses TypeString (where \"\" represents not configured) and additional validation.
  • NullableTypeInt: Workaround \"schema type\" created to accept a whole numeric value that is not configured in addition to 0. Not implemented in the Terraform Plugin SDK, but uses TypeString (where \"\" represents not configured) and additional validation.
  • Root Attribute: Resource top level Attribute or Block.

For additional reference, the Terraform documentation also includes a full glossary of terminology.

"},{"location":"debugging/","title":"Debugging","text":"

This guide covers strategies we have found useful in finding runtime and logic errors in the AWS Provider. We do not cover syntax or compiler errors as these are well addressed by Go documentation and IDEs, such as Visual Studio Code (\"VS Code\").

If you have your own debugging tricks for the provider, open a pull request to add them here!

"},{"location":"debugging/#1-reproduce","title":"1. Reproduce","text":"

One of the most crucial steps in the process of bug fixing is to reproduce the bug and create a minimal reproduction of it. In this section, we will discuss why it is so important to reproduce bugs.

TL;DR: for Repro

Perhaps the most important step in debugging is reproducing the bug.

"},{"location":"debugging/#what-is-a-bug","title":"What is a bug?","text":"

Before we dive into the details, let's first define what a bug is. A bug is an error or flaw that produces an incorrect or unexpected result, or causes the AWS Provider to behave in an unintended manner. For the Provider, \"bugs\" generally refer to errors that happen at runtime due to logic problems or unexpected interactions with AWS.

"},{"location":"debugging/#why-is-it-important-to-reproduce-bugs","title":"Why is it important to reproduce bugs?","text":"

Reproducing a bug is the process of intentionally triggering the error or unexpected behavior that occurs when the bug is present. It is important to reproduce bugs because it allows us to:

  1. Verify that the bug exists: By reproducing the bug, we can confirm that the error or unexpected behavior is real and either always happens or happens intermittently and not just a one-time occurrence. This is important because if we can't reproduce the bug, we may end up wasting time trying to fix a non-existent issue.
  2. Understand the cause of the bug: Reproducing the bug can help us understand what is causing the error or unexpected behavior. This is important because without understanding the root cause of the bug, we may end up fixing the symptoms of the bug rather than the underlying problem.
  3. Test the fix: If we know how to reproduce the bug, we also know how to verify we've fixed it.
"},{"location":"debugging/#2-create-a-minimal-reproduction","title":"2. Create a minimal reproduction","text":"

TL;DR: A complete 10-line configuration that reproduces a bug is far more (perhaps 100\u00d7 more) useful than a 1000-line configuration that reproduces the same bug.

Creating a minimal reproduction of a bug is the process of isolating the bug to its simplest form. It is very important to create a minimal reproduction of bugs, especially with Terraform configurations, because it allows us to:

  1. Focus on the root cause of the bug: By eliminating any extraneous configuration or dependencies, we can focus on the specific configuration that is causing the bug. This makes it easier to understand and fix the root cause of the bug.
  2. Save time: By creating a minimal reproduction of the bug, we can reduce the amount of time it takes to reproduce the bug and test the fix. This is because we don't have to navigate through a large configuration or deal with unnecessary dependencies. The minimal configuration becomes the basis of a new acceptance test that verifies the bug is fixed (and stays fixed in the future).
  3. Make it easier for others to reproduce the bug: If we are working on a team, creating a minimal reproduction of the bug makes it easier for other team members to reproduce the bug and understand the root cause.
"},{"location":"debugging/#how-to-create-a-minimal-reproduction-of-a-bug","title":"How to create a minimal reproduction of a bug","text":"

Creating a minimal reproduction of a bug can be a time-consuming process, but it is well worth the effort. Here are some tips for creating a minimal reproduction of a bug:

  1. Start with a simple test case: Start with a simple configuration that causes the bug.
  2. Remove any extraneous configuration: Remove as many resources, dependencies, and arguments as possible to simplify, while still maintaining test independence.
  3. Verify that the configuration still reproduces the bug: After removing configuration, make sure that the configuration still causes the bug. If the bug no longer occurs, you may have removed too much. In this case, add back configuration until the bug reappears.

A minimal configuration is worth its weight in gold. If you're only able to make it this far in the debugging process, create a new issue with your minimal configuration or add it to an existing issue. A minimal configuration is a great way to give some else a jump start in looking at the problem.

"},{"location":"debugging/#3-create-a-new-acceptance-test","title":"3. Create A New Acceptance Test","text":"

Sometimes, we tend to immediately jump into the code without creating a test. However, creating an acceptance test is something we'll need to do eventually anyway but doing it first has the added benefit of allowing us to easily trigger the bug. That makes debugging easier and allows us to use debugging tools.

"},{"location":"debugging/#use-the-minimal-configuration-as-the-basis-for-the-test","title":"Use the Minimal Configuration as the Basis for the Test","text":"

Adding a problematic minimal configuration test is just like creating an acceptance test except that when we run it, it has a problem. Starting with the end in mind focuses our efforts and gives great satisfaction when the code is fixed!

"},{"location":"debugging/#erroring-tests-example","title":"Erroring tests example","text":"

For example, if we're looking at an error in the VPC flow log resource, this is how we might add a test with a minimal configuration to trigger the error.

func TestAccVPCFlowLog_destinationError(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckFlowLogDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig:      testAccVPCFlowLogConfig_destinationError(rName),\n},\n},\n})\n}\n\n// ...\n\nfunc testAccVPCFlowLogConfig_destinationError(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_flow_log\" \"test\" {\n  # minimal configuration causing a bug\n}\n`, rName)\n}\n

If we were to run the test on the command line, this is how the erroring output might look:

% make testacc TESTS=TestAccVPCFlowLog_destinationError PKG=vpc\n==> Checking that code complies with gofmt requirements...\nTF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run='TestAccVPCFlowLog_destinationError'  -timeout 180m\n=== RUN   TestAccVPCFlowLog_destinationError\n=== PAUSE TestAccVPCFlowLog_destinationError\n=== CONT  TestAccVPCFlowLog_destinationError\n    vpc_flow_log_test.go:297: Step 1/1 error: Error running apply: exit status 1\n\n        Error: creating Flow Log (vpc-0c2635533cef2be79): 1 error occurred:\n            * vpc-0c2635533cef2be79: 400: Access Denied for LogDestination: does-not-exist. Please check LogDestination permission\n\n          with aws_flow_log.test,\n          on terraform_plugin_test.tf line 34, in resource \"aws_flow_log\" \"test\":\n          34: resource \"aws_flow_log\" \"test\" {\n\n--- FAIL: TestAccVPCFlowLog_destinationError (13.79s)\nFAIL\nFAIL    github.com/hashicorp/terraform-provider-aws/internal/service/ec2    15.373s\nFAIL\nmake: *** [testacc] Error 1\n
"},{"location":"debugging/#wrong-results-example","title":"Wrong results example","text":"

Of course, not all bugs throw errors. For example, perhaps the bug you are looking at involves a wrong value in the log_group_name attribute. This is an example of adding a test that will error because the value is wrong.

func TestAccVPCFlowLog_destinationError(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckFlowLogDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig:      testAccVPCFlowLogConfig_destinationError(rName),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckFlowLogExists(ctx, resourceName, &flowLog),\nresource.TestCheckResourceAttr(resourceName, \"log_group_name\", \"\"), // this should not be empty\n),\n},\n},\n})\n}\n\n// ...\n\nfunc testAccVPCFlowLogConfig_destinationError(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_flow_log\" \"test\" {\n  # minimal configuration causing a bug\n}\n`, rName)\n}\n

If we were to run the test on the command line, this is how the erroring output might look:

% make testacc TESTS=TestAccVPCFlowLog_LogDestinationType_s3 PKG=vpc\n==> Checking that code complies with gofmt requirements...\nTF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run='TestAccVPCFlowLog_LogDestinationType_s3'  -timeout 180m\n=== RUN   TestAccVPCFlowLog_LogDestinationType_s3\n=== PAUSE TestAccVPCFlowLog_LogDestinationType_s3\n=== CONT  TestAccVPCFlowLog_LogDestinationType_s3\n    vpc_flow_log_test.go:269: Step 1/2 error: Check failed: Check 3/4 error: aws_flow_log.test: Attribute 'log_group_name' expected \"abc-123\", got \"\"\n--- FAIL: TestAccVPCFlowLog_LogDestinationType_s3 (15.49s)\nFAIL\nFAIL    github.com/hashicorp/terraform-provider-aws/internal/service/ec2    17.358s\nFAIL\nmake: *** [testacc] Error 1\n
"},{"location":"debugging/#take-stock","title":"Take Stock","text":"

It may seem like we haven't accomplished much of anything at this point. However, we have! We've:

  1. Reproduced the error
  2. Created a minimal reproduction
  3. Created a test to trigger the error

This will make the rest of debugging a lot more straightforward.

"},{"location":"debugging/#contributing-a-failing-test","title":"Contributing a Failing Test","text":"

If you aren't able to figure out a bug or delving into code is not your thing, open a pull request to contribute just the test that highlights the bug. This is a valuable starting point for future work!

We ask that you make the test \"PASS,\" but use code comments and a GitHub issue to explain what is wrong. With nearly 7,000 acceptance tests, there are always a certain percentage that inexplicably fail. Since we know that the new test we're adding highlights a known bug, we don't want the failure to be lost in the tally of inexplicable failures.

"},{"location":"debugging/#failing-test-contribution-example-errors","title":"Failing Test Contribution Example: Errors","text":"

For example, if you want to contribute just a failing test, the example below shows how to use ExpectError and a code comment to reference an open GitHub issue addressing the bug. Here we're using ExpectError to allow the test to \"pass,\" even though it should not. When the next person comes along to debug, they can simply remove ExpectError.

func TestAccVPCFlowLog_destinationError(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckFlowLogDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig:      testAccVPCFlowLogConfig_destinationError(rName),\n// This error should not happen!\n// See https://github.com/hashicorp/terraform-provider-aws/issues/45912\nExpectError: regexp.MustCompile(`invalid destination`),\n},\n},\n})\n}\n\n// ...\n\nfunc testAccVPCFlowLogConfig_destinationError(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_flow_log\" \"test\" {\n  # minimal configuration causing a bug\n}\n`, rName)\n}\n
"},{"location":"debugging/#failing-test-contribution-example-wrong-results","title":"Failing Test Contribution Example: Wrong Results","text":"

There are other types of bugs besides the error thrown in the previous example. For example, with a wrong results test, you would have resource.TestCheckResourceAttr() highlighting what is wrong, along with a link to the GitHub issue to explain why the attribute value is wrong.

func TestAccVPCFlowLog_destinationError(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, ec2.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckFlowLogDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig:      testAccVPCFlowLogConfig_destinationError(rName),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckFlowLogExists(ctx, resourceName, &flowLog),\n// log_group_name should be \"xyz-123\"\n// See https://github.com/hashicorp/terraform-provider-aws/issues/45912\nresource.TestCheckResourceAttr(resourceName, \"log_group_name\", \"\"),\n),\n},\n},\n})\n}\n\n// ...\n\nfunc testAccVPCFlowLogConfig_destinationError(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_flow_log\" \"test\" {\n  # minimal configuration causing a bug\n}\n`, rName)\n}\n
"},{"location":"debugging/#4-find-out-why-a-bug-happens","title":"4. Find Out Why a Bug Happens","text":"

Now that we can easily reproduce a bug with a test and we have a minimal configuration, we can look more closely at why the bug is happening.

There are several approaches to looking at what is happening in the AWS provider. You might find that some bugs lend themselves to one approach while others are more easily evaluated with another. Also, you might start with one approach and then find you need to move to another, e.g., starting by adding a few fmt.Printf() statements and then moving to an IDE debugger.

"},{"location":"debugging/#use-fmtprintf","title":"Use fmt.Printf()","text":"

One quick and dirty approach that works for simple bugs is to have Go output information to the console (i.e., terminal). This approach is especially helpful if you've reviewed the code, found an error, and have a strong suspicion about what is going on. You can use fmt.Printf() to confirm what you think is happening.

The approach is also helpful to see if the code reaches a certain point or for seeing the value of a variable or two.

This approach does not work well for complex logic, examining many lines of code, or for looking at many different variables. Use more advanced debugging, as described below, for these situations.

This example shows using fmt.Printf() to output information to the console:

func resourceLogFlowCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {\n// other code ...\n\noutputRaw, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) {\nreturn conn.CreateFlowLogsWithContext(ctx, input)\n}, errCodeInvalidParameter, \"Unable to assume given IAM role\")\n\nfmt.Printf(\"reached point %d, with input: %+v\\n\", 3, input)\n\nif err == nil && outputRaw != nil {\nfmt.Printf(\"reached point %d, with output: %+v\\n\", 4, outputRaw)\nerr = UnsuccessfulItemsError(outputRaw.(*ec2.CreateFlowLogsOutput).Unsuccessful)\n}\n\n// other code ...\n}\n

Running the test from the command line, we might see the following output:

% make testacc TESTS=TestAccVPCFlowLog_LogDestinationType_s3 PKG=vpc\n==> Checking that code complies with gofmt requirements...\nTF_ACC=1 go test ./internal/service/ec2/... -v -count 1 -parallel 20 -run='TestAccVPCFlowLog_LogDestinationType_s3'  -timeout 180m\n=== RUN   TestAccVPCFlowLog_LogDestinationType_s3\n=== PAUSE TestAccVPCFlowLog_LogDestinationType_s3\n=== CONT  TestAccVPCFlowLog_LogDestinationType_s3\nreached point 3, with input: {\n  ClientToken: \"terraform-20230403170214312900000001\",\n  LogDestination: \"arn:aws:s3:::tf-acc-test-4802362269206133111\",\n  LogDestinationType: \"s3\",\n  MaxAggregationInterval: 600,\n  ResourceIds: [\"vpc-093265898e824c24e\"],\n  ResourceType: \"VPC\",\n  TagSpecifications: [{\n      ResourceType: \"vpc-flow-log\",\n      Tags: [{\n          Key: \"Name\",\n          Value: \"tf-acc-test-4802362269206133111\"\n        }]\n    }],\n  TrafficType: \"ALL\"\n}\nreached point 4, with output: {\n  ClientToken: \"terraform-20230403170214312900000001\",\n  FlowLogIds: [\"fl-09861862b9f8bb3a3\"]\n}\n--- PASS: TestAccVPCFlowLog_LogDestinationType_s3 (26.45s)\n
"},{"location":"debugging/#use-visual-studio-code-debugging","title":"Use Visual Studio Code Debugging","text":"

Using debugging from within VS Code provides extra benefits but also an extra challenge. The extra benefits include the ability to set break points, step over and into code, and seeing the values of variables. The extra challenge is getting your debug environment properly set up to include access to your AWS credentials and environment variables used for testing.

Special thanks to Drew Mullen for his work on debugging the AWS provider in VS Code.

"},{"location":"debugging/#set-up-launchjson","title":"Set up launch.json","text":"

VS Code uses a hidden directory called .vscode in the root of your project for configuration files. This directory and files it contains are ignored by Git using the .gitignore file included with the AWS provider. This allows you to have your own local configuration without concerns that it will be uploaded to or overwritten by the AWS provider repository.

As shown below, use VS Code to create two files in the .vscode directory: launch.json and private.env.

In launch.json, add this configuration:

{\n\"version\": \"0.2.0\",\n\"configurations\": [\n{\n\"name\": \"Debug Selected Test\",\n\"request\": \"launch\",\n\"type\": \"go\",\n\"args\": [\n\"-test.v\",\n\"-test.run\",\n\"^${selectedText}$\"\n],\n\"mode\": \"auto\",\n\"program\": \"${fileDirname}\",\n\"env\": {\"PKG_NAME\": \"${relativeFileDirname}\"},\n\"envFile\": \"${workspaceFolder}/.vscode/private.env\",\n\"showLog\": true\n}\n]\n}\n

In private.env, add environment variables to represent your AWS provider testing configuration, including replacing aws_provider_profile and aws_alternate_profile below with the names of profiles defined in your ~/.aws/config file:

TF_ACC=1\nTF_LOG=info\nGOFLAGS='-mod=readonly'\nAWS_PROFILE=aws_provider_profile\nAWS_DEFAULT_REGION=us-west-2\nAWS_ALTERNATE_PROFILE=aws_alternate_profile\nAWS_ALTERNATE_REGION=us-east-1\nAWS_THIRD_REGION=us-east-2\nACM_CERTIFICATE_ROOT_DOMAIN=terraform-provider-aws-acctest-acm.com\n

Note that you can set TF_LOG to debug but, if you do, you can receive thousands of lines of additional information that can make it difficult to find useful information.

"},{"location":"debugging/#viewing-run-and-debug","title":"Viewing Run and Debug","text":"

Open the Run and Debug panel in VS Code by pressing Shift-\u2318-D or by clicking on the debug button icon:

To debug, you'll also want to open the debug console by pressing Shift-\u2318-Y or selecting \"Debug Console\" from the View top menu.

"},{"location":"debugging/#run-and-debug","title":"Run and Debug","text":"

With the environment and VS Code GUI set up, you are ready to run and debug.

  1. As shown below, select the name of a test function from a *_test.go file by double-clicking on the name or click-dragging over the name to highlight the entire name. (Do not include the func at the beginning or (t *testing.T) { at the end.)
  2. Make sure that \"Debug Selected Test\" is shown next to the debug play button.
  3. Click on the play button in the Run and Debug panel. Depending on your configuration, there may also be a \"Debug Selected Test\" option at the bottom of the VS Code window. Clicking on that is equivalent to clicking the play button in the Run and Debug panel.

"},{"location":"debugging/#breakpoints","title":"Breakpoints","text":"

Depending on where you suspect the problems are occurring, you can set breakpoints on the resource's Create, Read, Update, Delete, or other functions. The debugger will stop at the first breakpoint it encounters.

Setting breakpoints is built into VS Code and easy. Hover to the left of a line number and you'll see a faded red dot. Click on the faded red dot to turn it into a breakpoint, as shown below.

You can see a list of breakpoints throughout the codebase at the bottom of the Run and Debug panel, as shown below.

"},{"location":"debugging/#stepping-out","title":"Stepping Out","text":"

Once the debugger has stopped at a breakpoint, you can control how it continues from there. VS Code will display the debugger controls, which you can reposition using the grip (i.e., six dots), at the left of the controls.

From left to right, the controls are as follows:

  1. Pause (i.e., two vertical lines) pauses execution wherever it happens to be when you click pause
  2. Step over (i.e., dot with a curved arrow above it) goes to the next statement but will not follow execution inside functions
  3. Step into (i.e., dot with an arrow pointing down) goes to the next statement, following execution inside functions
  4. Step out (i.e., dot with an arrow pointing up) goes to the next statement in the calling function skipping the rest of the execution in the current function
  5. Restart (i.e., the circular arrow) stops the current run and starts at the beginning again
  6. Stop (i.e., the square) stops the current run
"},{"location":"debugging/#use-delve","title":"Use Delve","text":"

Behind the scenes, VS Code and other IDEs use Delve to debug. You can also use Delve without an IDE if you prefer to work on the command line.

Here are some resources to get you started using Delve:

  • Delve on GitHub
  • Golang Debugging With Delve (Step-by-Step)
  • Using the Go Delve Debugger from the command line
  • Stop debugging Go with Println and use Delve instead
"},{"location":"debugging/#5-verify-the-fix-with-a-test","title":"5. Verify the Fix with a Test","text":"

Verify that bugs are fixed with one or more tests. The tests used to help debug, described above, verify that the bug is fixed after debugging. In addition, the tests ensure that future changes don't undo the fix.

"},{"location":"dependency-updates/","title":"Dependency Updates","text":"

Generally dependency updates are handled by maintainers.

"},{"location":"dependency-updates/#go-default-version-update","title":"Go Default Version Update","text":"

This project typically upgrades its Go version for development and testing shortly after release to get the latest and greatest Go functionality. Before beginning the update process, ensure that you review the new version release notes to look for any areas of possible friction when updating.

Create an issue to cover the update noting down any areas of particular interest or friction.

Ensure that the following steps are tracked within the issue and completed within the resulting pull request.

  • Update go version in go.mod
  • Verify make test lint works as expected
  • Verify goreleaser build --snapshot succeeds for all currently supported architectures
  • Verify goenv support for the new version
  • Update docs/development-environment.md
  • Update .go-version
  • Update CHANGELOG.md detailing the update and mention any notes practitioners need to be aware of.

See #9992 / #10206 for a recent example.

"},{"location":"dependency-updates/#aws-go-sdk-updates","title":"AWS Go SDK Updates","text":"

Almost exclusively, github.com/aws/aws-sdk-go updates are additive in nature. It is generally safe to only scan through them before approving and merging. If you have any concerns about any of the service client updates such as suspicious code removals in the update, or deprecations introduced, run the acceptance testing for potentially affected resources before merging.

"},{"location":"dependency-updates/#authentication-changes","title":"Authentication changes","text":"

Occasionally, there will be changes listed in the authentication pieces of the AWS Go SDK codebase, e.g., changes to aws/session. The AWS Go SDK CHANGELOG should include a relevant description of these changes under a heading such as SDK Enhancements or SDK Bug Fixes. If they seem worthy of a callout in the Terraform AWS Provider CHANGELOG, then upon merging we should include a similar message prefixed with the provider subsystem, e.g., * provider: ....

Additionally, if a CHANGELOG addition seemed appropriate, this dependency and version should also be updated in the Terraform S3 Backend, which currently lives in Terraform Core. An example of this can be found with https://github.com/hashicorp/terraform-provider-aws/pull/9305 and https://github.com/hashicorp/terraform/pull/22055.

"},{"location":"dependency-updates/#cloudfront-changes","title":"CloudFront changes","text":"

CloudFront service client updates have previously caused an issue when a new field introduced in the SDK was not included with Terraform and caused all requests to error (https://github.com/hashicorp/terraform-provider-aws/issues/4091). As a precaution, if you see CloudFront updates, run all the CloudFront resource acceptance testing before merging (TestAccCloudFront).

"},{"location":"dependency-updates/#golangci-lint-updates","title":"golangci-lint Updates","text":"

Merge if CI passes.

"},{"location":"dependency-updates/#terraform-plugin-sdk-updates","title":"Terraform Plugin SDK Updates","text":"

Except for trivial changes, run the full acceptance testing suite against the pull request and verify there are no new or unexpected failures.

"},{"location":"dependency-updates/#tfproviderdocs-updates","title":"tfproviderdocs Updates","text":"

Merge if CI passes.

"},{"location":"dependency-updates/#tfproviderlint-updates","title":"tfproviderlint Updates","text":"

Merge if CI passes.

"},{"location":"dependency-updates/#yamlv2-updates","title":"yaml.v2 Updates","text":"

Run the acceptance testing pattern, TestAccCloudFormationStack(_dataSource)?_yaml, and merge if passing.

"},{"location":"development-environment/","title":"Development Environment Setup","text":""},{"location":"development-environment/#requirements","title":"Requirements","text":"
  • Terraform 0.12.26+ (to run acceptance tests)
  • Go 1.19.3+ (to build the provider plugin)
"},{"location":"development-environment/#quick-start","title":"Quick Start","text":"

If you wish to work on the provider, you'll first need Go installed on your machine (please check the requirements before proceeding).

Note: This project uses Go Modules making it safe to work with it outside of your existing GOPATH. The instructions that follow assume a directory in your home directory outside of the standard GOPATH (i.e $HOME/development/hashicorp/).

Clone repository to: $HOME/development/hashicorp/

$ mkdir -p $HOME/development/hashicorp/; cd $HOME/development/hashicorp/\n$ git clone git@github.com:hashicorp/terraform-provider-aws\n...\n

Enter the provider directory and run make tools. This will install the needed tools for the provider.

$ make tools\n

To compile the provider, run make build. This will build the provider and put the provider binary in the $GOPATH/bin directory.

$ make build\n...\n$ $GOPATH/bin/terraform-provider-aws\n...\n
"},{"location":"development-environment/#testing-the-provider","title":"Testing the Provider","text":"

In order to test the provider, you can run make test.

Note: Make sure no AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY variables are set, and there's no [default] section in the AWS credentials file ~/.aws/credentials.

$ make test\n

In order to run the full suite of Acceptance tests, run make testacc.

Note: Acceptance tests create real resources, and often cost money to run. Please read Running and Writing Acceptance Tests in the contribution guidelines for more information on usage.

$ make testacc\n
"},{"location":"development-environment/#using-the-provider","title":"Using the Provider","text":"

With Terraform v0.14 and later, development overrides for provider developers can be leveraged in order to use the provider built from source.

To do this, populate a Terraform CLI configuration file (~/.terraformrc for all platforms other than Windows; terraform.rc in the %APPDATA% directory when using Windows) with at least the following options:

provider_installation {\ndev_overrides {\n\"hashicorp/aws\" = \"[REPLACE WITH GOPATH]/bin\"\n}\ndirect {}\n}\n
"},{"location":"documentation-changes/","title":"End User Documentation Changes","text":"

All practitioner focused documentation is found in the /website folder of the repository.

\u251c\u2500\u2500 website/\n    \u251c\u2500\u2500 r/                     # Documentation for resources\n    \u251c\u2500\u2500 d/                     # Documentation for data sources\n    \u251c\u2500\u2500 guides/                # Long format guides for provider level configuration or provider upgrades.\n    \u2514\u2500\u2500 index.html.markdown    # Home page and all provider level documentation.\n\u2514\u2500\u2500 examples/                  # Large example configurations\n

For any documentation change please raise a pull request including and adhering to the following:

  • Reasoning for Change: Documentation updates should include an explanation for why the update is needed. If the change is a correction which is an alignment to AWS behavior, please include a link to the AWS Documentation in the PR.
  • Prefer AWS Documentation: Documentation about AWS service features and valid argument values that are likely to update over time should link to AWS service user guides and API references where possible.
  • Large Example Configurations: Example Terraform configuration that includes multiple resource definitions should be added to the repository examples directory instead of an individual resource documentation page. Each directory under examples should be self-contained to call terraform apply without special configuration.
  • Avoid Terraform Configuration Language Features: Individual resource documentation pages and examples should refrain from highlighting particular Terraform configuration language syntax workarounds or features such as variable, local, count, and built-in functions.
"},{"location":"error-handling/","title":"Error Handling","text":"

The Terraform AWS Provider codebase bridges the implementation of a Terraform Plugin and an AWS API client to support AWS operations and data types as Terraform Resources. An important aspect of performing resource and remote actions is properly handling those operations, but those operations are not guaranteed to succeed every time. Some common examples include where network connections are unreliable, necessary permissions are not properly setup, incorrect Terraform configurations, or the remote system responds unexpectedly. All these situations lead to an unexpected workflow action that must be surfaced to the Terraform user interface for operators to troubleshoot. This guide is intended to explain and show various Terraform AWS Provider code implementations that are considered best practice for surfacing these issues properly to operators and code maintainers.

For further details about how the AWS SDK for Go v1 and the Terraform AWS Provider resource logic handle retryable errors, see the Retries and Waiters documentation.

"},{"location":"error-handling/#general-guidelines-and-helpers","title":"General Guidelines and Helpers","text":""},{"location":"error-handling/#naming-and-check-style","title":"Naming and Check Style","text":"

Following typical Go conventions, error variables in the Terraform AWS Provider codebase should be named err, e.g.

result, err := strconv.Itoa(\"oh no!\")\n

The code that then checks these errors should prefer if conditionals that usually return (or in the case of looping constructs, break/continue) early, especially in the case of multiple error checks, e.g.

if /* ... something checking err first ... */ {\n// ... return, break, continue, etc. ...\n}\n\nif err != nil {\n// ... return, break, continue, etc. ...\n}\n\n// all good!\n

This is in preference of some other styles of error checking, such as switch conditionals without a condition.

"},{"location":"error-handling/#wrap-errors","title":"Wrap Errors","text":"

Go implements error wrapping, which means that a deeply nested function call can return a particular error type, while each function up the stack can provide additional error message context without losing the ability to determine the original error. Additional information about this concept can be found on the Go blog entry titled Working with Errors in Go 1.13.

For most use cases in this codebase, this means if code is receiving an error and needs to return it, it should implement fmt.Errorf() and the %w verb, e.g.

return fmt.Errorf(\"adding some additional message: %w\", err)\n

This type of error wrapping should be applied to all Terraform resource logic. It should also be applied to any nested functions that contains two or more error conditions (e.g., a function that calls an update API and waits for the update to finish) so practitioners and code maintainers have a clear idea which generated the error. When returning errors in those situations, it is important to only include necessary additional context. Resource logic will typically include the information such as the type of operation and resource identifier (e.g., updating Service Thing (%s): %w), so these messages can be more terse such as waiting for completion: %w.

"},{"location":"error-handling/#aws-sdk-for-go-v1-errors","title":"AWS SDK for Go v1 Errors","text":"

The AWS SDK for Go v1 documentation includes a section on handling errors, which is recommended reading.

For the purposes of this documentation, the most important concepts with handling these errors are:

  • Each response error (which eventually implements awserr.Error) has a string error code (Code) and string error message (Message). When printed as a string, they format as: Code: Message, e.g., InvalidParameterValueException: IAM Role arn:aws:iam::123456789012:role/XXX cannot be assumed by AWS Backup.
  • Error handling is almost exclusively done via those string fields and not other response information, such as HTTP Status Codes.
  • When the error code is non-specific, the error message should also be checked. Unfortunately, AWS APIs generally do not provide documentation or API modeling with the contents of these messages and often the Terraform AWS Provider code must rely on substring matching.
  • Not all errors are returned in the response error from an AWS API operation. This is service- and sometimes API-call-specific. For example, the EC2 DeleteVpcEndpoints API call can return a \"successful\" response (in terms of no response error) but include information in an Unsuccessful field in the response body.

When working with AWS SDK for Go v1 errors, it is preferred to use the helpers outlined below and use the %w format verb. Code should generally avoid type assertions with the underlying awserr.Error type or calling its Code(), Error(), Message(), or String() receiver methods. Using the %v, %#v, or %+v format verbs generally provides extraneous information that is not helpful to operators or code maintainers.

"},{"location":"error-handling/#aws-sdk-for-go-error-helpers","title":"AWS SDK for Go Error Helpers","text":"

To simplify operations with AWS SDK for Go error types, the following helpers are available via the github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr Go package:

  • tfawserr.ErrCodeEquals(err, \"Code\"): Preferred when the error code is specific enough for the check condition. For example, a ResourceNotFoundError code provides enough information that the requested API resource identifier/Amazon Resource Name does not exist.
  • tfawserr.ErrMessageContains(err, \"Code\", \"MessageContains\"): Does simple substring matching for the error message.

The recommendation for error message checking is to be just specific enough to capture the anticipated issue, but not include too much matching as the AWS API can change over time without notice. The maintainers have observed changes in wording and capitalization cause unexpected issues in the past.

For example, given this error code and message:

InvalidParameterValueException: IAM Role arn:aws:iam::123456789012:role/XXX cannot be assumed by AWS Backup\n

An error check for this might be:

if tfawserr.ErrMessageContains(err, backup.ErrCodeInvalidParameterValueException, \"cannot be assumed\") { /* ... */ }\n

The Amazon Resource Name in the error message will be different for every environment and does not add value to the check. The AWS Backup suffix is also extraneous and could change should the service ever rename.

"},{"location":"error-handling/#use-aws-sdk-for-go-v1-error-code-constants","title":"Use AWS SDK for Go v1 Error Code Constants","text":"

Each AWS SDK for Go v1 service API typically implements common error codes, which get exported as public constants in the SDK. In the AWS SDK for Go v1 API Reference, these can be found in each of the service packages under the Constants section (typically named ErrCode{ExceptionName}).

If an AWS SDK for Go service API is missing an error code constant, an AWS Support case should be submitted and a new constant can be added to internal/service/{SERVICE}/errors.go file (created if not present), e.g.

const(\nErrCodeInvalidParameterException = \"InvalidParameterException\"\n)\n

Then referencing code can use it via:

// imports\ntf{SERVICE} \"github.com/hashicorp/terraform-provider-aws/internal/service/{SERVICE}\"\n\n// logic\ntfawserr.ErrCodeEquals(err, tf{SERVICE}.ErrCodeInvalidParameterException)\n

e.g.

// imports\ntfec2 \"github.com/hashicorp/terraform-provider-aws/internal/service/ec2\"\n\n// logic\ntfawserr.ErrCodeEquals(err, tfec2.ErrCodeInvalidParameterException)\n
"},{"location":"error-handling/#terraform-plugin-sdk-types-and-helpers","title":"Terraform Plugin SDK Types and Helpers","text":"

The Terraform Plugin SDK includes some error types which are used in certain operations and typically preferred over implementing new types:

  • retry.NotFoundError
  • retry.TimeoutError: Returned from retry.RetryContext() and (retry.StateChangeConf).WaitForStateContext()

The Terraform AWS Provider codebase implements some additional helpers for working with these in the github.com/hashicorp/terraform-provider-aws/internal/tfresource package:

  • tfresource.NotFound(err): Returns true if the error is a retry.NotFoundError.
  • tfresource.TimedOut(err): Returns true if the error is a retry.TimeoutError and contains no LastError. This typically signifies that the retry logic was never signaled for a retry, which can happen when AWS API operations are automatically retrying before returning.
"},{"location":"error-handling/#resource-lifecycle-guidelines","title":"Resource Lifecycle Guidelines","text":"

Terraform CLI and the Terraform Plugin SDK have certain expectations and automatic behaviors depending on the lifecycle operation of a resource. This section highlights some common issues that can occur and their expected resolution.

"},{"location":"error-handling/#resource-creation","title":"Resource Creation","text":"

Invoked in the resource via the schema.Resource type Create/CreateWithoutTimeout function.

"},{"location":"error-handling/#disnewresource-checks","title":"d.IsNewResource() Checks","text":"

During resource creation, Terraform CLI expects either a properly applied state for the new resource or an error. To signal proper resource existence, the Terraform Plugin SDK uses an underlying resource identifier (set via d.SetId(/* some value */)). If for some reason the resource creation is returned without an error, but also without the resource identifier being set, Terraform CLI will return an error such as:

Error: Provider produced inconsistent result after apply\n\nWhen applying changes to aws_sns_topic_subscription.sqs,\nprovider \"registry.terraform.io/hashicorp/aws\" produced an unexpected new\nvalue: Root resource was present, but now absent.\n\nThis is a bug in the provider, which should be reported in the provider's own\nissue tracker.\n

A typical pattern in resource implementations in the Create/CreateWithoutTimeout function is to return the Read/ReadWithoutTimeout function at the end to fill in the Terraform State for all attributes. Another typical pattern in resource implementations in the Read/ReadWithoutTimeout function is to remove the resource from the Terraform State if the remote system returns an error or status that indicates the remote resource no longer exists by explicitly calling d.SetId(\"\") and returning no error. If the remote system is not strongly read-after-write consistent (eventually consistent), this means the resource creation can return no error and also return no resource state.

To prevent this type of Terraform CLI error, the resource implementation should also check against d.IsNewResource() before removing from the Terraform State and returning no error. If that check is true, then remote operation error (or one synthesized from the non-existent status) should be returned instead. While adding this check will not fix the resource implementation to handle the eventually consistent nature of the remote system, the error being returned will be less opaque for operators and code maintainers to troubleshoot.

In the Terraform AWS Provider, an initial fix for the Terraform CLI error will typically look like:

func resourceServiceThingCreate(d *schema.ResourceData, meta interface{}) error {\n/* ... */\n\nreturn resourceServiceThingRead(d, meta)\n}\n\nfunc resourceServiceThingRead(d *schema.ResourceData, meta interface{}) error {\n/* ... */\n\noutput, err := conn.DescribeServiceThing(input)\n\nif !d.IsNewResource() && tfawserr.ErrCodeEquals(err, \"ResourceNotFoundException\") {\nlog.Printf(\"[WARN] {Service} {Thing} (%s) not found, removing from state\", d.Id())\nd.SetId(\"\")\nreturn nil\n}\n\nif err != nil {\nreturn fmt.Errorf(\"reading {Service} {Thing} (%s): %w\", d.Id(), err)\n}\n\n/* ... */\n}\n

If the remote system is not strongly read-after-write consistent, see the Retries and Waiters documentation on Resource Lifecycle Retries for how to prevent consistency-type errors.

"},{"location":"error-handling/#creation-error-message-context","title":"Creation Error Message Context","text":"

Returning errors during creation should include additional messaging about the location or cause of the error for operators and code maintainers by wrapping with fmt.Errorf():

if err != nil {\nreturn fmt.Errorf(\"creating {SERVICE} {THING}: %w\", err)\n}\n

e.g.

if err != nil {\nreturn fmt.Errorf(\"creating EC2 VPC: %w\", err)\n}\n

Code that also uses waiters or other operations that return errors should follow a similar pattern, including the resource identifier since it has typically been set before this execution:

if _, err := VpcAvailable(conn, d.Id()); err != nil {\nreturn fmt.Errorf(\"waiting for EC2 VPC (%s) availability: %w\", d.Id(), err)\n}\n
"},{"location":"error-handling/#resource-deletion","title":"Resource Deletion","text":"

Invoked in the resource via the schema.Resource type Delete/DeleteWithoutTimeout function.

"},{"location":"error-handling/#resource-already-deleted","title":"Resource Already Deleted","text":"

A typical pattern for resource deletion is to immediately perform the remote system deletion operation without checking existence. This is generally acceptable as operators are encouraged to always refresh their Terraform State prior to performing changes. However in certain scenarios, such as external systems modifying the remote system prior to the Terraform execution, it is certainly still possible that the remote system will return an error signifying that remote resource does not exist. In these cases, resources should implement logic that catches the error and returns no error.

NOTE: The Terraform Plugin SDK automatically handles the equivalent of d.SetId(\"\") on deletion, so it is not necessary to include it.

For example in the Terraform AWS Provider:

func resourceServiceThingDelete(d *schema.ResourceData, meta interface{}) error {\n/* ... */\n\noutput, err := conn.DeleteServiceThing(input)\n\nif tfawserr.ErrCodeEquals(err, \"ResourceNotFoundException\") {\nreturn nil\n}\n\nif err != nil {\nreturn fmt.Errorf(\"deleting {Service} {Thing} (%s): %w\", d.Id(), err)\n}\n\n/* ... */\n}\n
"},{"location":"error-handling/#deletion-error-message-context","title":"Deletion Error Message Context","text":"

Returning errors during deletion should include the resource identifier and additional messaging about the location or cause of the error for operators and code maintainers by wrapping with fmt.Errorf():

if err != nil {\nreturn fmt.Errorf(\"deleting {SERVICE} {THING} (%s): %w\", d.Id(), err)\n}\n

e.g.

if err != nil {\nreturn fmt.Errorf(\"deleting EC2 VPC (%s): %w\", d.Id(), err)\n}\n

Code that also uses waiters or other operations that return errors should follow a similar pattern:

if _, err := VpcDeleted(conn, d.Id()); err != nil {\nreturn fmt.Errorf(\"waiting for EC2 VPC (%s) deletion: %w\", d.Id(), err)\n}\n
"},{"location":"error-handling/#resource-read","title":"Resource Read","text":"

Invoked in the resource via the schema.Resource type Read/ReadWithoutTimeout function.

"},{"location":"error-handling/#singular-data-source-errors","title":"Singular Data Source Errors","text":"

A data source which is expected to return Terraform State about a single remote resource is commonly referred to as a \"singular\" data source. Implementation-wise, it may use any available describe or listing functionality from the remote system to retrieve the information. In addition to any remote operation and other data handling errors that should be returned, these two additional cases should be covered:

  • Returning an error when zero results are found.
  • Returning an error when multiple results are found.

For remote operations that are designed to return an error when the remote resource is not found, this error is typically just passed through similar to other remote operation errors. For remote operations that are designed to return a successful result whether there is zero, one, or multiple multiple results the error must be generated.

For example in pseudo-code:

output, err := conn.ListServiceThings(input)\n\nif err != nil {\nreturn fmt.Errorf(\"listing {Service} {Thing}s: %w\", err)\n}\n\nif output == nil || len(output.Results) == 0 {\nreturn fmt.Errorf(\"no {Service} {Thing} found matching criteria; try different search\")\n}\n\nif len(output.Results) > 1 {\nreturn fmt.Errorf(\"multiple {Service} {Thing} found matching criteria; try different search\")\n}\n
"},{"location":"error-handling/#plural-data-source-errors","title":"Plural Data Source Errors","text":"

An emergent concept is a data source that returns multiple results, acting similar to any available listing functionality available from the remote system. These types of data sources should return no error if zero results are returned and no error if multiple results are found. Remote operation and other data handling errors should still be returned.

"},{"location":"error-handling/#read-error-message-context","title":"Read Error Message Context","text":"

Returning errors during read should include the resource identifier (for managed resources) and additional messaging about the location or cause of the error for operators and code maintainers by wrapping with fmt.Errorf():

if err != nil {\nreturn fmt.Errorf(\"reading {SERVICE} {THING} (%s): %w\", d.Id(), err)\n}\n

e.g.

if err != nil {\nreturn fmt.Errorf(\"reading EC2 VPC (%s): %w\", d.Id(), err)\n}\n
"},{"location":"error-handling/#resource-update","title":"Resource Update","text":"

Invoked in the resource via the schema.Resource type Update/UpdateWithoutTimeout function.

"},{"location":"error-handling/#update-error-message-context","title":"Update Error Message Context","text":"

Returning errors during update should include the resource identifier and additional messaging about the location or cause of the error for operators and code maintainers by wrapping with fmt.Errorf():

if err != nil {\nreturn fmt.Errorf(\"updating {SERVICE} {THING} (%s): %w\", d.Id(), err)\n}\n

e.g.

if err != nil {\nreturn fmt.Errorf(\"updating EC2 VPC (%s): %w\", d.Id(), err)\n}\n

Code that also uses waiters or other operations that return errors should follow a similar pattern:

if _, err := VpcAvailable(conn, d.Id()); err != nil {\nreturn fmt.Errorf(\"waiting for EC2 VPC (%s) update: %w\", d.Id(), err)\n}\n
"},{"location":"faq/","title":"Frequently Asked Questions","text":""},{"location":"faq/#who-are-the-maintainers","title":"Who are the maintainers?","text":"

The HashiCorp Terraform AWS provider team is :

  • Marc Cosentino, Product Manager - GitHub @marcosentino
  • Simon Davis, Engineering Manager - GitHub @breathingdust
  • Justin Retzolk, Technical Community Manager - GitHub @justinretzolk
  • Adrian Johnson, Engineer - GitHub @johnsonaj
  • Dirk Avery, Engineer - GitHub @YakDriver
  • Graham Davison, Engineer - GitHub @gdavison
  • Jared Baker, Engineer - GitHub @jar-b
  • Kerim Satirli, Developer Advocate - GitHub @ksatirli
  • Kit Ewbank, Engineer - GitHub @ewbankkit
  • Sharon Nam, Engineer - GitHub @nam054
"},{"location":"faq/#why-isnt-my-pr-merged-yet","title":"Why isn\u2019t my PR merged yet?","text":"

Unfortunately, due to the volume of issues and new pull requests we receive, we are unable to give each one the full attention that we would like. We always focus on the contributions that provide the greatest value to the most community members. For more information on how we prioritize pull requests, see the prioritization guide.

"},{"location":"faq/#how-do-you-decide-what-gets-merged-for-each-release","title":"How do you decide what gets merged for each release?","text":"

We have a large backlog of pull requests to get through and the team are moving through them as quick as we can. All pull requests must be reviewed by a HashiCorp engineer before inclusion. This is to ensure that the design of the addition fits with what provider users have come to expect, and to ensure that testing and best practices are adhered to. This is particularly important for such a large codebase, to ensure that we sustain its maintainability as its grows.

The number one factor we look at when deciding what issues to look at are your \ud83d\udc4d reactions to the original issue/PR description as these can be easily discovered. Comments that further explain desired use cases or poor user experience are also heavily factored. The items with the most support are always on our radar, and we commit to keep the community updated on their status and potential timelines.

We publish a roadmap every quarter which describes major themes or specific product areas of focus. What is excluded from the public roadmap is work performed under NDA with AWS on new services, and any ad-hoc work we pick up during the quarter. This ad-hoc work can be responding to bugs, gardening day activity, customer prioritization, and technical debt items.

We also are investing time to improve the contributing experience by improving documentation, adding more linter coverage to ensure that incoming PR's can be in as good shape as possible. This will allow us to get through them quicker.

"},{"location":"faq/#my-pr-hasnt-been-merged-and-it-now-has-merge-conflictsfailed-checks-should-i-keep-it-up-to-date","title":"My PR hasn't been merged and it now has merge conflicts/failed checks, should I keep it up to date?","text":"

We realize that sometimes pull requests sit for a considerable amount of time without being addressed. During this time period they may accumulate merge conflicts and failed linter checks as the provider codebase moves forward. As maintainers we have no expectation that you keep your PR up to date, these issues will be addressed at review time most often by the maintainers themselves. Obviously we would hope that your PR is mergeable when first raised! The mergeability of the PR does not affect its prioritization for review.

"},{"location":"faq/#how-often-do-you-release","title":"How often do you release?","text":"

We release weekly on Thursday. We release often to ensure we can bring value to the community at a frequent cadence and to ensure we are in a good place to react to AWS region launches and service announcements.

"},{"location":"faq/#backward-compatibility-promise","title":"Backward Compatibility Promise","text":"

Our policy is described on the Terraform website here. While we do our best to prevent breaking changes until major version releases of the provider, it is generally recommended to pin the provider version in your configuration.

Due to the constant release pace of AWS and the relatively infrequent major version releases of the provider, there can be cases where a minor version update may contain unexpected changes depending on your configuration or environment. These may include items such as a resource requiring additional IAM permissions to support newer functionality. We typically base these decisions on a pragmatic compromise between introducing a relatively minor one-time inconvenience for a subset of the community versus better overall user experience for the entire community.

"},{"location":"faq/#once-a-major-release-is-published-will-new-features-and-fixes-be-backported-to-previous-versions","title":"Once a major release is published, will new features and fixes be backported to previous versions?","text":"

Generally new features and fixes will only be added to the most recent major version. Due to the high touch nature of provider development and the extensive regression testing required to ensure stability, maintaining multiple versions of the provider is not sustainable at this time. An exception to this could be a discovered security vulnerability for which backporting may be the most reasonable course of action. These would be reviewed on a case by case basis.

"},{"location":"faq/#aws-just-announced-a-new-region-when-will-i-see-it-in-the-provider","title":"AWS just announced a new region, when will I see it in the provider.","text":"

Normally pretty quickly. We usually see the region appear within the aws-go-sdk within a couple days of the announcement. Depending on when it lands, we can often get it out within the current or following weekly release. Comparatively, adding support for a new region in the S3 backend can take a little longer, as it is shipped as part of Terraform Core and not via the AWS Provider.

Please note that this new region requires a manual process to enable in your account. Once enabled in the console, it takes a few minutes for everything to work properly.

If the region is not enabled properly, or the enablement process is still in progress, you may receive errors like these:

$ terraform apply\n\nError: error validating provider credentials: error calling sts:GetCallerIdentity: InvalidClientTokenId: The security token included in the request is invalid.\n    status code: 403, request id: 142f947b-b2c3-11e9-9959-c11ab17bcc63\n\n  on main.tf line 1, in provider \"aws\":\n   1: provider \"aws\" {\n

To use this new region before support has been added to the Terraform AWS Provider, you can disable the provider's automatic region validation via:

provider \"aws\" {\n  # ... potentially other configuration ...\n\nregion                 = \"af-south-1\"\nskip_region_validation = true\n}\n
"},{"location":"faq/#how-can-i-help","title":"How can I help?","text":"

Great question, if you have contributed before check out issues with the help-wanted label. These are normally enhancement issues that will have a great impact, but the maintainers are unable to develop them in the near future. If you are just getting started, take a look at issues with the good-first-issue label. Items with these labels will always be given priority for response.

Check out the Contributing Guide for additional information.

"},{"location":"faq/#how-can-i-become-a-maintainer","title":"How can I become a maintainer?","text":"

This is an area under active research. Stay tuned!

"},{"location":"issue-reporting-and-lifecycle/","title":"Issue Reporting and Lifecycle","text":""},{"location":"issue-reporting-and-lifecycle/#issue-reporting-checklists","title":"Issue Reporting Checklists","text":"

We welcome issues of all kinds including feature requests, bug reports, and general questions. Below you'll find checklists with guidelines for well-formed issues of each type.

We encourage opening new issues rather than commenting on closed issues if a problem has not been completely solved or causes a regression. This ensures we are able to triage it effectively.

"},{"location":"issue-reporting-and-lifecycle/#bug-reports","title":"Bug Reports","text":"
  • Test against latest release: Make sure you test against the latest released version. It is possible we already fixed the bug you're experiencing.

  • Search for possible duplicate reports: It's helpful to keep bug reports consolidated to one thread, so do a quick search on existing bug reports to check if anybody else has reported the same thing. You can scope searches by the label \"bug\" to help narrow things down.

  • Include steps to reproduce: Provide steps to reproduce the issue, along with your .tf files, with secrets removed, so we can try to reproduce it. Without this, it makes it much harder to fix the issue.

  • For panics, include crash.log: If you experienced a panic, please create a gist of the entire generated crash log for us to look at. Double check no sensitive items were in the log.

"},{"location":"issue-reporting-and-lifecycle/#feature-requests","title":"Feature Requests","text":"
  • Search for possible duplicate requests: It's helpful to keep requests consolidated to one thread, so do a quick search on existing requests to check if anybody else has reported the same thing. You can scope searches by the label \"enhancement\" to help narrow things down.

  • Include a use case description: In addition to describing the behavior of the feature you'd like to see added, it's helpful to also lay out the reason why the feature would be important and how it would benefit Terraform users.

"},{"location":"issue-reporting-and-lifecycle/#questions","title":"Questions","text":"
  • Search for answers in Terraform documentation: We're happy to answer questions in GitHub Issues, but it helps reduce issue churn and maintainer workload if you work to find answers to common questions in the documentation. Oftentimes Question issues result in documentation updates to help future users, so if you don't find an answer, you can give us pointers for where you'd expect to see it in the docs.
"},{"location":"issue-reporting-and-lifecycle/#issue-lifecycle","title":"Issue Lifecycle","text":"

Note: For detailed information on how issues are prioritized, see the prioritization guide.

  1. The issue is reported.

  2. The issue is verified and categorized by a Terraform collaborator. Categorization is done via GitHub labels. We generally use a two-label system of (1) issue/PR type, and (2) section of the codebase. Type is one of \"bug\", \"enhancement\", \"documentation\", or \"question\", and section is usually the AWS service name.

  3. An initial triage process determines whether the issue is critical and must be addressed immediately, or can be left open for community discussion.

  4. The issue is addressed in a pull request or commit. The issue number will be referenced in the commit message so that the code that fixes it is clearly linked.

  5. The issue is closed. Sometimes, valid issues will be closed because they are tracked elsewhere or non-actionable. The issue is still indexed and available for future viewers, or can be re-opened if necessary.

  6. 30 days after the issue has been closed it is locked, preventing further comments.

"},{"location":"naming/","title":"Naming Conventions for the AWS Provider","text":""},{"location":"naming/#service-identifier","title":"Service Identifier","text":"

In the AWS Provider, a service identifier should consistently identify an AWS service from code to documentation to provider use by a practitioner. Prominent places you will see service identifiers:

  • The package name (e.g., internal/service/<serviceidentifier>)
  • In resource and data source names (e.g., aws_<serviceidentifier>_thing)
  • Documentation file names (e.g., website/docs/r/<serviceidentifier>_thing)

Typically, choosing the AWS Provider identifier for a service is simple. AWS consistently uses one name and we use that name as the identifier. However, some services are not simple. To provide consistency, and to help contributors and practitioners know what to expect, we provide this rule for defining a service identifier:

"},{"location":"naming/#rule","title":"Rule","text":"
  1. Determine the service package name for AWS Go SDK v2.
  2. Determine the AWS CLI v2 command corresponding to the service (i.e., the word following aws in CLI commands; e.g., for aws sts get-caller-identity, sts is the command, get-caller-identity is the subcommand).
  3. If the SDK and CLI agree, use that. If the service only exists in one, use that.
  4. If they differ, use the shorter of the two.
  5. Use lowercase letters and do not include any underscores (_).
"},{"location":"naming/#how-well-is-it-followed","title":"How Well Is It Followed?","text":"

With 156+ services having some level of implementation, the following is a summary of how well this rule is currently followed.

For AWS provider service package names, only five packages violate this rule: appautoscaling should be applicationautoscaling, codedeploy should be deploy, elasticsearch should be es, cloudwatchlogs should be logs, and simpledb should be sdb.

For the service identifiers used in resource and data source configuration names (e.g., aws_acmpca_certificate_authority), 32 wholly or partially violate the rule.

  • EC2, ELB, ELBv2, and RDS have legacy but heavily used resources and data sources that do not or inconsistently use service identifiers.
  • The remaining 28 services violate the rule in a consistent way: appautoscaling should be applicationautoscaling, codedeploy should be deploy, elasticsearch should be es, cloudwatch_log should be logs, simpledb should be sdb, prometheus should be amp, api_gateway should be apigateway, cloudcontrolapi should be cloudcontrol, cognito_identity should be cognitoidentity, cognito should be cognitoidp, config should be configservice, dx should be directconnect, directory_service should be ds, elastic_beanstalk should be elasticbeanstalk, cloudwatch_event should be events, kinesis_firehose should be firehose, msk should be kafka, mskconnect should be kafkaconnect, kinesis_analytics should be kinesisanalytics, kinesis_video should be kinesisvideo, lex should be lexmodels, media_convert should be mediaconvert, media_package should be mediapackage, media_store should be mediastore, route53_resolver should be route53resolver, relevant s3 should be s3control, serverlessapplicationrepository should be serverlessrepo, and service_discovery should be servicediscovery.
"},{"location":"naming/#packages","title":"Packages","text":"

Package names are not seen or used by practitioners. However, they should still be carefully considered.

"},{"location":"naming/#rule_1","title":"Rule","text":"
  1. For service packages (i.e., packages under internal/service), use the AWS Provider service identifier as the package name.
  2. For other packages, use a short name for the package. Common Go lengths are 3-9 characters.
  3. Use a descriptive name. The name should capture the key purpose of the package.
  4. Use lowercase letters and do not include any underscores (_).
  5. Avoid useless names like helper. These names convey zero information. Everything in the AWS Provider is helping something or someone do something so the name helper doesn't narrow down the purpose of the package within the codebase.
  6. Use a name that is not too narrow or too broad as Go packages should not be too big or too small. Tiny packages can be combined using a broader name encompassing both. For example, verify is a good name because it tells you what the package does and allows a broad set of validation, comparison, and checking functionality.
"},{"location":"naming/#resources-and-data-sources","title":"Resources and Data Sources","text":"

When creating a new resource or data source, it is important to get names right. Once practitioners rely on names, we can only change them through breaking changes. If you are unsure about what to call a resource or data source, discuss it with the community and maintainers.

"},{"location":"naming/#rule_2","title":"Rule","text":"
  1. Follow the AWS SDK for Go v2. Almost always, the API operations make determining the name simple. For example, the Amazon CloudWatch Evidently service includes CreateExperiment, GetExperiment, UpdateExperiment, and DeleteExperiment. Thus, the resource (or data source) name is \"Experiment.\"
  2. Give a resource its Terraform configuration (i.e., HCL) name (e.g., aws_imagebuilder_image_pipeline) by joining these three parts with underscores:
    • aws prefix
    • Service identifier (service identifiers do not include underscores), all lower case (e.g., imagebuilder)
    • Resource (or data source) name in snake case (spaces replaced with underscores, if any), all lower case (e.g., image_pipeline)
  3. Name the main resource function Resource<ResourceName>(), with the resource name in MixedCaps. Do not include the service name or identifier. For example, define ResourceImagePipeline() in a file called internal/service/imagebuilder/image_pipeline.go.
  4. Similarly, name the main data source function DataSource<ResourceName>(), with the data source name in MixedCaps. Do not include the service name or identifier. For example, define DataSourceImagePipeline() in a file called internal/service/imagebuilder/image_pipeline_data_source.go.
"},{"location":"naming/#files","title":"Files","text":"

File names should follow Go and Markdown conventions with these additional points.

"},{"location":"naming/#resource-and-data-source-documentation-rule","title":"Resource and Data Source Documentation Rule","text":"
  1. Resource markdown goes in the website/docs/r directory. Data source markdown goes in the website/docs/d directory.
  2. Use the service identifier and resource or data source name, separated by an underscore (_).
  3. All letters are lowercase.
  4. Use .html.markdown as the extension.
  5. Do not include \"aws\" in the name.

A correct example is accessanalyzer_analyzer.html.markdown. An incorrect example is service_discovery_instance.html.markdown because the service identifier should not include an underscore.

"},{"location":"naming/#go-file-rule","title":"Go File Rule","text":"
  1. Resource and data source files are in the internal/service/<service> directory.
  2. Do not include the service as part of the file name.
  3. Data sources should include _data_source after the data source name (e.g., application_data_source.go).
  4. Put unit and acceptance tests in a file ending with _test.go (e.g., custom_domain_association_test.go).
  5. Use snake case for multiword names (i.e., all letters are lowercase, words separated by underscores).
  6. Use the .go extension.
  7. Idiomatic names for common non-resource, non-data-source files include consts.go (service-wide constants), find.go (finders), flex.go (FLatteners and EXpanders), generate.go (directives for code generation), id.go (ID creators and parsers), status.go (status functions), sweep.go (sweepers), tags_gen.go (generated tag code), validate.go (validators), and wait.go (waiters).
"},{"location":"naming/#mixedcaps","title":"MixedCaps","text":"

Write multiword names in Go using MixedCaps (or mixedCaps) rather than underscores.

For more details on capitalizations we enforce with CI Semgrep tests, see the Caps List.

Initialisms and other abbreviations are a key difference between many camel/Pascal case interpretations and mixedCaps. Abbreviations in mixedCaps should be the correct, human-readable case. After all, names in code are for humans. (The mixedCaps convention aligns with HashiCorp's emphasis on pragmatism and beauty.)

For example, an initialism such as \"VPC\" should either be all capitalized (\"VPC\") or all lower case (\"vpc\"), never \"Vpc\" or \"vPC.\" Similarly, in mixedCaps, \"DynamoDB\" should either be \"DynamoDB\" or \"dynamoDB\", depending on whether an initial cap is needed or not, and never \"dynamoDb\" or \"DynamoDb.\"

"},{"location":"naming/#rule_3","title":"Rule","text":"
  1. Use mixedCaps for function, type, method, variable, and constant names in the Terraform AWS Provider Go code.
"},{"location":"naming/#functions","title":"Functions","text":"

In general, follow Go best practices for good function naming. This rule is for functions defined outside of the test context (i.e., not in a file ending with _test.go). For test functions, see Test Support Functions or Acceptance Test Configurations below.

"},{"location":"naming/#rule_4","title":"Rule","text":"
  1. Only export functions (capitalize) when necessary, i.e., when the function is used outside the current package, including in the _test (.test) package.
  2. Use MixedCaps (exported) or mixedCaps (not exported). Do not use underscores for multiwords.
  3. Do not include the service name in the function name. (If functions are used outside the current package, the import package clarifies a function's origin. For example, the EC2 function FindVPCEndpointByID() is used outside the internal/service/ec2 package but where it is used, the call is tfec2.FindVPCEndpointByID().)
  4. For CRUD functions for resources, use this format: resource<ResourceName><CRUDFunction>. For example, resourceImageRecipeUpdate(), resourceBaiduChannelRead().
  5. For data sources, for Read functions, use this format: dataSource<DataSourceName>Read. For example, dataSourceBrokerRead(), dataSourceEngineVersionRead().
  6. To improve readability, consider including the resource name in helper function names that pertain only to that resource. For example, for an expander function for an \"App\" resource and a \"Campaign Hook\" expander, use expandAppCampaignHook().
  7. Do not include \"AWS\" or \"Aws\" in the name.
"},{"location":"naming/#variables-and-constants","title":"Variables and Constants","text":"

In general, follow Go best practices for good variable and constant naming.

"},{"location":"naming/#rule_5","title":"Rule","text":"
  1. Only export variables and constants (capitalize) when necessary, i.e., the variable or constant is used outside the current package, including in the _test (.test) package.
  2. Use MixedCaps (exported) or mixedCaps (not exported). Do not use underscores for multiwords.
  3. Do not include the service name in variable or constant names. (If variables or constants are used outside the current package, the import package clarifies its origin. For example, IAM's PropagationTimeout is widely used outside of IAM but each instance is through the package import alias, tfiam.PropagationTimeout. \"IAM\" is unnecessary in the constant name.)
  4. To improve readability, consider including the resource name in variable and constant names that pertain only to that resource. For example, for a string constant for a \"Role\" resource and a \"not found\" status, use roleStatusNotFound or RoleStatusNotFound, if used outside the service's package.
  5. Do not include \"AWS\" or \"Aws\" in the name.

NOTE: Give priority to using constants from the AWS SDK for Go rather than defining new constants for the same values.

"},{"location":"naming/#acceptance-and-unit-tests","title":"Acceptance and Unit Tests","text":"

With about 6000 acceptance and unit tests, following these naming conventions is essential to organization and (human) context switching between services.

There are three types of tests in the AWS Provider: (regular) acceptance tests, serialized acceptance tests, and unit tests. All are functions that take a variable of type *testing.T. Acceptance tests and unit tests have exported (i.e., capitalized) names while serialized tests do not. Serialized tests are called by another exported acceptance test, often ending with _serial. The majority of tests in the AWS provider are acceptance tests.

"},{"location":"naming/#acceptance-test-rule","title":"Acceptance Test Rule","text":"

Acceptance test names have a minimum of two (e.g., TestAccBackupPlan_tags) or a maximum of three (e.g., TestAccDynamoDBTable_Replica_multiple) parts, joined with underscores:

  1. First part: All have a prefix (i.e., TestAcc), service name (e.g., Backup, DynamoDB), and resource name (e.g., Plan, Table), MixedCaps without underscores between. Do not include \"AWS\" or \"Aws\" in the name.
  2. Middle part (Optional): Test group (e.g., Replica), uppercase, MixedCaps. Consider a metaphor where tests are chapters in a book. If it is helpful, tests can be grouped together like chapters in a book that are sometimes grouped into parts or sections of the book.
  3. Last part: Test identifier (e.g., basic, tags, or multiple), lowercase, mixedCaps). The identifier should make the test's purpose clear but be concise. For example, the identifier conflictsWithCloudFrontDefaultCertificate (41 characters) conveys no more information than conflictDefaultCertificate (26 characters), since \"CloudFront\" is implied and \"with\" is always implicit. Avoid words that convey no meaning or whose meaning is implied. For example, \"with\" (e.g., _withTags) is not needed because we imply the name is telling us what the test is with. withTags can be simplified to tags.
"},{"location":"naming/#serialized-acceptance-test-rule","title":"Serialized Acceptance Test Rule","text":"

The names of serialized acceptance tests follow the regular acceptance test name rule except serialized acceptance test names:

  1. Start with testAcc instead of TestAcc
  2. Do not include the name of the service (e.g., a serialized acceptance test would be called testAccApp_basic not testAccAmplifyApp_basic).
"},{"location":"naming/#unit-test-rule","title":"Unit Test Rule","text":"

Unit test names follow the same rule as acceptance test names except unit test names:

  1. Start with Test, not TestAcc
  2. Do not include the name of the service
  3. Usually do not have any underscores
  4. If they test a function, should include the function name (e.g., a unit test of ExpandListener() should be called TestExpandListener())
"},{"location":"naming/#test-support-functions","title":"Test Support Functions","text":"

This rule is for functions defined in the test context (i.e., in a file ending with _test.go) that do not return a string with Terraform configuration. For non-test functions, see Functions above. Or, see Acceptance Test Configurations below.

"},{"location":"naming/#rule_6","title":"Rule","text":"
  1. Only export functions (capitalize) when necessary, i.e., when the function is used outside the current package. This is very rare.
  2. Use MixedCaps (exported) or mixedCaps (not exported). Do not use underscores for multiwords.
  3. Do not include the service name in the function name. For example, testAccCheckAMPWorkspaceExists() should be named testAccCheckWorkspaceExists() instead, dropping the service name.
  4. Several types of support functions occur commonly and should follow these patterns:
    • Destroy: testAccCheck<Resource>Destroy
    • Disappears: testAccCheck<Resource>Disappears
    • Exists: testAccCheck<Resource>Exists
    • Not Recreated: testAccCheck<Resource>NotRecreated
    • PreCheck: testAccPreCheck (often, only one PreCheck is needed per service so no resource name is needed)
    • Recreated: testAccCheck<Resource>Recreated
  5. Do not include \"AWS\" or \"Aws\" in the name.
"},{"location":"naming/#acceptance-test-configurations","title":"Acceptance Test Configurations","text":"

This rule is for functions defined in the test context (i.e., in a file ending with _test.go) that return a string with Terraform configuration. For test support functions, see Test Support Functions above. Or, for non-test functions, see Functions above.

NOTE: This rule is not widely used currently. However, new functions and functions you change should follow it.

"},{"location":"naming/#rule_7","title":"Rule","text":"
  1. Only export functions (capitalize) when necessary, i.e., when the function is used outside the current package. This is very rare.
  2. Use MixedCaps (exported) or mixedCaps (not exported). Do not use underscores for multiwords.
  3. Do not include the service name in the function name.
  4. Follow this pattern: testAccConfig<Resource>_<TestGroup>_<configDescription>
    • _<TestGroup> is optional. Refer to the Acceptance Test Rule test group discussion.
    • Especially when an acceptance test only uses one configuration, the <configDescription> should be the same as the test identifier discussed in the Acceptance Test Rule.
  5. Do not include \"AWS\" or \"Aws\" in the name.
"},{"location":"prioritization/","title":"How We Prioritize","text":""},{"location":"prioritization/#intro","title":"Intro","text":""},{"location":"prioritization/#what-this-document-is","title":"What this document is","text":"

This document describes how we handle prioritization of work from a variety of input sources. Our focus is always to deliver tangible value to the practitioner on a predictable and frequent schedule, and we feel it is important to be transparent in how we weigh input in order to deliver on this goal.

"},{"location":"prioritization/#what-this-document-is-not","title":"What this document is not","text":"

Due to the variety of input sources, the scale of the provider, and resource constraints, it is impossible to give a hard number on how each of the factors outlined in this document are weighted. Instead, the goal of the document is to give a transparent, but generalized assessment of each of the sources of input so that the community has a better idea of why things are prioritized the way they are. Additional information may be found in the FAQ.

"},{"location":"prioritization/#prioritization","title":"Prioritization","text":"

We prioritze work based on a number of factors, including community feedback, issue/PR reactions, as well as the source of the request. While community feedback is heavily weighted, there are times where other factors take precedence. By their nature, some factors are less visible to the community, and so are outlined here as a way to be as transparent as possible. Each of the sources of input are detailed below.

"},{"location":"prioritization/#community","title":"Community","text":"

Our large community of practitioners are vocal and immensely productive in contributing to the provider codebase. Unfortunately our current team capacity means that we are unable to give every issue or pull request the same level of attention. This means we need to prioritize the issues that provide the most value to the greatest number of practitioners.

We will always focus on the issues which have the most attention. The main rubric we have for assessing community wants is GitHub reactions. In addition to reactions, we look at comments, reactions to comments, and links to additional issues and PRs to help get a more holistic view of where the community stands. We try to ensure that for the issues where we have the most community support, we are responsive to that support and attempt to give timelines where-ever possible.

"},{"location":"prioritization/#customer","title":"Customer","text":"

Another source of work that must be weighted are escalations around particular feature requests and bugs from HashiCorp and AWS customers. Escalations typically come via several routes:

  • Customer Support
  • Sales Engineering
  • AWS Solutions Architects contacting us on behalf of their clients.

These reports flow into an internal board and are triaged on a weekly basis to determine whether the escalation request should be prioritized for an upcoming release or added to the backlog to monitor for additional community support. During triage, we verify whether a GitHub issue or PR exists for the request and will create one if it does not exist. In this way, these requests are visible to the community to some degree. An escalation coming from a customer does not necessarily guarantee that it will be prioritized over requests made by the community. Instead, we assess them based on the following rubric:

  • Does the issue have considerable community support?
  • Does the issue pertain to one of our Core Services?

By weighing these factors, we can make a call to determine whether, and how it is to be prioritized.

"},{"location":"prioritization/#partner","title":"Partner","text":"

AWS Service Teams and Partner representatives regularly contact us to discuss upcoming features or new services. This work is often done under an NDA, so usually needs to be done in private. Often the ask is to enable Terraform support or an upcoming feature or service.

As with customer escalations, a request from a partner does not necessarily mean that it will be prioritized over other efforts; capacity restraints require us to prioritize major releases or prefer offerings in line with our core services.

"},{"location":"prioritization/#internal","title":"Internal","text":""},{"location":"prioritization/#sdkcore-updates","title":"SDK/Core Updates","text":"

We endeavor to keep in step with all minor SDK releases, so these are automatically pulled in by our GitHub automation. Major releases normally include breaking changes and usually require us to bump the provider itself to a major version. We plan to make one major version change a year and try to avoid any more than that.

"},{"location":"prioritization/#technical-debt","title":"Technical Debt","text":"

We always include capacity for technical debt work in every iteration, but engineers are free to include minor tech debt work on their own recognizance. For larger items, these are discussed and prioritized in an internal meeting aimed at reviewing technical debt.

"},{"location":"prioritization/#adverse-user-experience-or-security-vulnerabilities","title":"Adverse User Experience or Security Vulnerabilities","text":"

Issues with the provider that provide a poor user experience (bugs, crashes), or involve a threat to security are always prioritized for inclusion. The severity of these will determine how soon they are included for release.

"},{"location":"provider-design/","title":"Provider Design","text":"

The Terraform AWS Provider follows the guidelines established in the HashiCorp Provider Design Principles. That general documentation provides many high-level design points gleaned from years of experience with Terraform's design and implementation concepts. Sections below will expand on specific design details between that documentation and this provider, while others will capture other pertinent information that may not be covered there. Other pages of the contributing guide cover implementation details such as code, testing, and documentation specifics.

"},{"location":"provider-design/#api-and-sdk-boundary","title":"API and SDK Boundary","text":"

The AWS provider implements support for the AWS service APIs using the AWS Go SDK. The API and SDK limits extend to the provider. In general, SDK operations manage the lifecycle of AWS components, such as creating, describing, updating, and deleting a database. Operations do not usually handle functionality within those components, such as executing a query on a database. If you are interested in other APIs/SDKs, we invite you to view the many Terraform Providers available, as each has a community of domain expertise.

Some examples of functionality that is not expected in this provider:

  • Raw HTTP(S) handling. See the Terraform HTTP Provider and Terraform TLS Provider instead.
  • Kubernetes resource management beyond the EKS service APIs. See the Terraform Kubernetes Provider instead.
  • Active Directory or other protocol clients. See the Terraform Active Directory Provider and other available provider instead.
  • Functionality that requires additional software beyond the Terraform AWS Provider to be installed on the host executing Terraform. This currently includes the AWS CLI. See the Terraform External Provider and other available providers instead.
"},{"location":"provider-design/#infrastructure-as-code-suitability","title":"Infrastructure as Code Suitability","text":"

The provider maintainers' design goal is to cover as much of the AWS API as pragmatically possible. However, not every aspect of the API is compatible with an infrastructure-as-code (IaC) conception. Specifically: IaC is best suited for immutable rather than mutable infrastrcture -- i.e. for resources with a single desired state described in its entirety, as opposed to resources defined via a dynamic process.

If such limits affect you, we recommend that you open an AWS Support case and encourage others to do the same. Request that AWS components be made more self-contained and compatible with IaC. These AWS Support cases can also yield insights into the AWS service and API that are not well documented.

"},{"location":"provider-design/#resource-type-considerations","title":"Resource Type Considerations","text":"

Terraform resources work best as the smallest infrastructure blocks on which practitioners can build more complex configurations and abstractions, such as Terraform Modules. The general heuristic guiding when to implement a new Terraform resource for an aspect of AWS is whether the AWS service API provides create, read, update, and delete (CRUD) operations. However, not all AWS service API functionality falls cleanly into CRUD lifecycle management. In these situations, there is extra consideration necessary for properly mapping API operations to Terraform resources.

This section highlights design patterns when to consider an implementation within a singular Terraform resource or as separate Terraform resources.

Please note: the overall design and implementation across all AWS functionality is federated: individual services may implement concepts and use terminology differently. As such, this guide is not exhaustive. The aim is to provide general concepts and basic terminology that points contributors in the right direction, especially in understanding previous implementations.

"},{"location":"provider-design/#authorization-and-acceptance-resources","title":"Authorization and Acceptance Resources","text":"

Some AWS services use an authorization-acceptance model for cross-account associations or access. Examples include:

  • Direct Connect Association Proposals
  • GuardDuty Member Invitations
  • RAM Resource Share Associations
  • Route 53 VPC Associations
  • Security Hub Member Invitations

Depending on the API and components, AWS uses two basic ways of creating cross-region and cross-account associations. One way is to generate an invitation (or proposal) identifier from one AWS account to another. Then in the other AWS account, that identifier is used to accept the invitation. The second way is configuring a reference to another AWS account identifier. These may not require explicit acceptance on the receiving account to finish creating the association or begin working.

To model creating an association using an invitation or proposal, follow these guidelines.

  • Follow the naming in the AWS service API to determine whether to use the term \"invitation\" or \"proposal.\"
  • For the originating account, create an \"invitation\" or \"proposal\" resource. Make sure that the AWS service API has operations for creating and reading invitations.
  • For the responding account, create an \"accepter\" resource. Ensure that the API has operations for accepting, reading, and rejecting invitations in the responding account. Map the operations as follows:
    • Create: Accepts the invitation.
    • Read: Reads the invitation to determine its status. Note that in some APIs, invitations expire and disappear, complicating associations. If a resource does not find an invitation, the developer should implement a fall back to read the API resource associated with the invitation/proposal.
    • Delete: Rejects or otherwise deletes the invitation.

To model the second type of association, implicit associations, create an \"association\" resource and, optionally, an \"authorization\" resource. Map create, read, and delete to the corresponding operations in the AWS service API.

"},{"location":"provider-design/#cross-service-functionality","title":"Cross-Service Functionality","text":"

Many AWS service APIs build on top of other AWS services. Some examples of these include:

  • EKS Node Groups managing Auto Scaling Groups
  • Lambda Functions managing EC2 ENIs
  • Transfer Servers managing EC2 VPC Endpoints

Some cross-service API implementations lack the management or description capabilities of the other service. The lack can make the Terraform resource implementation seem incomplete or unsuccessful in end-to-end configurations. Given the overall \u201cresources should represent a single API object\u201d goal from the HashiCorp Provider Design Principles, a resource must only communicate with a single AWS service API. As such, maintainers will not approve cross-service resources.

The rationale behind this design decision includes the following:

  • Unexpected IAM permissions being necessary for the resource. In high-security environments, all the service permissions may not be available or acceptable.
  • Unexpected services generating CloudTrail logs for the resource.
  • Needing extra and unexpected API endpoints configuration for organizations using custom endpoints, such as VPC endpoints.
  • Unexpected changes to the AWS service internals for the cross-service implementations. Given that this functionality is not part of the primary service API, these details can change over time and may not be considered as a breaking change by the service team for an API upgrade.

A poignant real-world example of the last point involved a Lambda resource. The resource helped clean up extra resources (ENIs) due to a common misconfiguration. Practitioners found the functionality helpful since the issue was hard to diagnose. Years later, AWS updated the Lambda API. Immediately, practitioners reported that Terraform executions were failing. Downgrading the provider was not possible since many configurations depended on recent releases. For environments running many versions behind, forcing an upgrade with the fix would likely cause unrelated and unexpected changes. In the end, HashiCorp and AWS performed a large-scale outreach to help upgrade and fixing the misconfigurations. Provider maintainers and practitioners lost considerable time.

"},{"location":"provider-design/#data-sources","title":"Data Sources","text":"

A separate class of Terraform resource types are data sources. These are typically intended as a configuration method to lookup or fetch data in a read-only manner. Data sources should not have side effects on the remote system.

When discussing data sources, they are typically classified by the intended number of return objects or data. Singular data sources represent a one-to-one lookup or data operation. Plural data sources represent a one-to-many operation.

"},{"location":"provider-design/#plural-data-sources","title":"Plural Data Sources","text":"

These data sources are intended to return zero, one, or many results, usually associated with a managed resource type. Typically results are a set unless ordering guarantees are provided by the remote system. These should be named with a plural suffix (e.g., s or es) and should not include any specific attribute in the naming (e.g., prefer aws_ec2_transit_gateways instead of aws_ec2_transit_gateway_ids).

"},{"location":"provider-design/#singular-data-sources","title":"Singular Data Sources","text":"

These data sources are intended to return one result or an error. These should not include any specific attribute in the naming (e.g., prefer aws_ec2_transit_gateway instead of aws_ec2_transit_gateway_id).

"},{"location":"provider-design/#iam-resource-based-policy-resources","title":"IAM Resource-Based Policy Resources","text":"

For some AWS components, the AWS API allows specifying an IAM resource-based policy, the IAM policy to associate with a component. Some examples include:

  • ECR Repository Policies
  • EFS File System Policies
  • SNS Topic Policies

Provider developers should implement this capability in a new resource rather than adding it to the associated resource. Reasons for this include:

  • Many of the policies must include the ARN of the resource. Working around this requirement with custom difference handling within a self-contained resource is unnecessarily cumbersome.
  • Some policies involving multiple resources need to cross-reference each other's ARNs. Without a separate resource, this introduces a configuration cycle.
  • Splitting the resources allows operators to logically split their configurations into purely operational and security boundaries. This allows environments to have distinct practitioners roles and permissions for IAM versus infrastructure changes.

One rare exception to this guideline is where the policy is required during resource creation.

"},{"location":"provider-design/#managing-resource-running-state","title":"Managing Resource Running State","text":"

The AWS API provides the ability to start, stop, enable, or disable some AWS components. Some examples include:

  • Batch Job Queues
  • CloudFront Distributions
  • RDS DB Event Subscriptions

In this situation, provider developers should implement this ability within the resource instead of creating a separate resource. Since a practitioner cannot practically manage interaction with a resource's states in Terraform's declarative configuration, developers should implement the state management in the resource. This design provides consistency and future-proofing even where updating a resource in the current API is not problematic.

"},{"location":"provider-design/#task-execution-and-waiter-resources","title":"Task Execution and Waiter Resources","text":"

Some AWS operations are asynchronous. Terraform requests that AWS perform a task. Initially, AWS only notifies Terraform that it received the request. Terraform then requests the status while awaiting completion. Examples of this include:

  • ACM Certificate validation
  • EC2 AMI copying
  • RDS DB Cluster Snapshot management

In this situation, provider developers should create a separate resource representing the task, assuming that the AWS service API provides operations to start the task and read its status. Adding the task functionality to the parent resource muddies its infrastructure-management purpose. The maintainers prefer this approach even though there is some duplication of an existing resource. For example, the provider has a resource for copying an EC2 AMI in addition to the EC2 AMI resource itself. This modularity allows practitioners to manage the result of the task resource with another resource.

For a related consideration, see the Managing Resource Running State section.

"},{"location":"provider-design/#versioned-resources","title":"Versioned Resources","text":"

AWS supports having multiple versions of some components. Examples of this include:

  • ECS Task Definitions
  • Lambda Functions
  • Secrets Manager Secrets

In general, provider developers should create a separate resource to represent a single version. For example, the provider has both the aws_secretsmanager_secret and aws_secretsmanager_secret_version resources. However, in some cases, developers should handle versioning in the main resource.

In deciding when to create a separate resource, follow these guidelines:

  • If AWS necessarily creates a version when you make a new AWS component, include version handling in the same Terraform resource. Creating an AWS component with one Terraform resource and later using a different resource for updates is confusing.
  • If the AWS service API allows deleting versions and practitioners will want to delete versions, provider developers should implement a separate version resource.
  • If the API only supports publishing new versions, either method is acceptable, however most current implementations are self-contained. Terraform's current configuration language does not natively support triggering resource updates or recreation across resources without a state value change. This can make the implementation more difficult for practitioners without special resource and configuration workarounds, such as a triggers attribute. If this changes in the future, then this guidance may be updated towards separate resources, following the Task Execution and Waiter Resources guidance.
"},{"location":"provider-design/#other-considerations","title":"Other Considerations","text":""},{"location":"provider-design/#aws-credential-exfiltration","title":"AWS Credential Exfiltration","text":"

In the interest of security, the maintainers will not approve data sources that provide the ability to reference or export the AWS credentials of the running provider. There are valid use cases for this information, such as to execute AWS CLI calls as part of the same Terraform configuration. However, this mechanism may allow credentials to be discovered and used outside of Terraform. Some specific concerns include:

  • The values may be visible in Terraform user interface output or logging, allowing anyone with user interface or log access to see the credentials.
  • The values are currently stored in plaintext in the Terraform state, allowing anyone with access to the state file or another Terraform configuration that references the state access to the credentials.
  • Any new related functionality, while opt-in to implement, is also opt-in to prevent via security controls or policies. Adopting a weaker default security posture requires advance notice and prevents organizations that implement those controls from updating to a version with any such functionality.
"},{"location":"raising-a-pull-request/","title":"Raising a Pull Request","text":"
  1. Fork the GitHub repository allowing you to make the changes in your own copy of the repository.

  2. Create a branch using the following naming prefixes:

    • f = feature
    • b = bug fix
    • d = documentation
    • t = tests
    • td = technical debt
    • v = dependencies (\"vendoring\" previously)

    Some indicative example branch names would be f-aws_emr_instance_group-refactor or td-staticcheck-st1008

  3. Make the changes you would like to include in the provider, add new tests as required, and make sure that all relevant existing tests are passing.

  4. Create a changelog entry following the process outlined here

  5. Create a pull request. Please ensure (if possible) the 'Allow edits from maintainers' checkbox is checked. This will allow the maintainers to make changes and merge the PR without requiring action from the contributor. You are welcome to submit your pull request for commentary or review before it is fully completed by creating a draft pull request. Please include specific questions or items you'd like feedback on.

  6. Once you believe your pull request is ready to be reviewed, ensure the pull request is not a draft pull request by marking it ready for review or removing [WIP] from the pull request title if necessary, and a maintainer will review it. Follow the checklists below to help ensure that your contribution can be easily reviewed and potentially merged.

  7. One of Terraform's provider team members will look over your contribution and either approve it or provide comments letting you know if there is anything left to do. We'll try give you the opportunity to make the required changes yourself, but in some cases we may perform the changes ourselves if it makes sense to (minor changes, or for urgent issues). We do our best to keep up with the volume of PRs waiting for review, but it may take some time depending on the complexity of the work.

  8. Once all outstanding comments and checklist items have been addressed, your contribution will be merged! Merged PRs will be included in the next Terraform release.

  9. In some cases, we might decide that a PR should be closed without merging. We'll make sure to provide clear reasoning when this happens.

"},{"location":"raising-a-pull-request/#go-coding-style","title":"Go Coding Style","text":"

All Go code is automatically checked for compliance with various linters, such as gofmt. These tools can be installed using the GNUMakefile in this repository.

$ cd terraform-provider-aws\n$ make tools\n

Check your code with the linters:

$ make lint\n

We use Semgrep to check for other code standards. This can be run directly on the command line, i.e.,

$ semgrep\n

or it can be run using Docker via the Makefile, i.e.,

$ make semgrep\n

gofmt will also fix many simple formatting issues for you. The Makefile includes a target for this:

$ make fmt\n

The import statement in a Go file follows these rules (see #15903):

  1. Import declarations are grouped into a maximum of three groups with the following order:
    • Standard packages (also called short import path or built-in packages)
    • Third-party packages (also called long import path packages)
    • Local packages
  2. Groups are separated by a single blank line
  3. Packages within each group are alphabetized

Check your imports:

$ make importlint\n

For greater detail, the following Go language resources provide common coding preferences that may be referenced during review, if not automatically handled by the project's linting tools.

  • Effective Go
  • Go Code Review Comments
"},{"location":"raising-a-pull-request/#resource-contribution-guidelines","title":"Resource Contribution Guidelines","text":"

The following resource checks need to be addressed before your contribution can be merged. The exclusion of any applicable check may result in a delayed time to merge. Some of these are not handled by the automated code testing that occurs during submission, so reviewers (even those outside the maintainers) are encouraged to reach out to contributors about any issues to save time.

This Contribution Guide also includes separate sections on topics such as Error Handling, which also applies to contributions.

  • Passes Testing: All code and documentation changes must pass unit testing, code linting, and website link testing. Resource code changes must pass all acceptance testing for the resource.
  • Avoids API Calls Across Account, Region, and Service Boundaries: Resources should not implement cross-account, cross-region, or cross-service API calls.
  • Does Not Set Optional or Required for Non-Configurable Attributes: Resource schema definitions for read-only attributes must not include Optional: true or Required: true.
  • Avoids retry.RetryContext() without retry.RetryableError(): Resource logic should only implement retry.Retry() if there is a retryable condition (e.g., return retry.RetryableError(err)).
  • Avoids Reusing Resource Read Function in Data Source Read Function: Data sources should fully implement their own resource Read functionality including duplicating d.Set() calls.
  • Avoids Reading Schema Structure in Resource Code: The resource Schema should not be read in resource Create/Read/Update/Delete functions to perform looping or otherwise complex attribute logic. Use d.Get() and d.Set() directly with individual attributes instead.
  • Avoids ResourceData.GetOkExists(): Resource logic should avoid using ResourceData.GetOkExists() as its expected functionality is not guaranteed in all scenarios.
  • Calls Read After Create and Update: Except where API eventual consistency prohibits immediate reading of resources or updated attributes, resource Create and Update functions should return the resource Read function.
  • Implements Immediate Resource ID Set During Create: Immediately after calling the API creation function, the resource ID should be set with d.SetId() before other API operations or returning the Read function.
  • Implements Attribute Refreshes During Read: All attributes available in the API should have d.Set() called their values in the Terraform state during the Read function.
  • Performs Error Checks with Non-Primitive Attribute Refreshes: When using d.Set() with non-primitive types (schema.TypeList, schema.TypeSet, or schema.TypeMap), perform error checking to prevent issues where the code is not properly able to refresh the Terraform state.
  • Implements Import Acceptance Testing and Documentation: Support for resource import (Importer in resource schema) must include ImportState acceptance testing (see also the Acceptance Testing Guidelines) and ## Import section in resource documentation.
  • Implements Customizable Timeouts Documentation: Support for customizable timeouts (Timeouts in resource schema) must include ## Timeouts section in resource documentation.
  • Implements State Migration When Adding New Virtual Attribute: For new \"virtual\" attributes (those only in Terraform and not in the API), the schema should implement State Migration to prevent differences for existing configurations that upgrade.
  • Uses AWS Go SDK Constants: Many AWS services provide string constants for value enumerations, error codes, and status types. See also the \"Constants\" sections under each of the service packages in the AWS Go SDK documentation.
  • Uses AWS Go SDK Pointer Conversion Functions: Many APIs return pointer types and these functions return the zero value for the type if the pointer is nil. This prevents potential panics from unchecked * pointer dereferences and can eliminate boilerplate nil checking in many cases. See also the aws package in the AWS Go SDK documentation.
  • Uses AWS Go SDK Types: Use available SDK structs instead of implementing custom types with indirection.
  • Uses Existing Validation Functions: Schema definitions including ValidateFunc for attribute validation should use available Terraform helper/validation package functions. All()/Any() can be used for combining multiple validation function behaviors.
  • Uses tfresource.TimedOut() with retry.Retry(): Resource logic implementing retry.Retry() should error check with tfresource.TimedOut(err error) and potentially unset the error before returning the error. For example:
var output *kms.CreateKeyOutput\nerr := retry.Retry(1*time.Minute, func() *retry.RetryError {\nvar err error\n\noutput, err = conn.CreateKey(input)\n\n/* ... */\n\nreturn nil\n})\n\nif tfresource.TimedOut(err) {\noutput, err = conn.CreateKey(input)\n}\n\nif err != nil {\nreturn fmt.Errorf(\"creating KMS External Key: %s\", err)\n}\n
  • Uses id.UniqueId(): API fields for concurrency protection such as CallerReference and IdempotencyToken should use id.UniqueId(). The implementation includes a monotonic counter which is safer for concurrent operations than solutions such as time.Now().
  • Skips id Attribute: The id attribute is implicit for all Terraform resources and does not need to be defined in the schema.

The below are style-based items that may be noted during review and are recommended for simplicity, consistency, and quality assurance:

  • Implements arn Attribute: APIs that return an ARN should implement arn as an attribute. Alternatively, the ARN can be synthesized using the AWS Go SDK arn.ARN structure. For example:
// Direct Connect Virtual Interface ARN.\n// See https://docs.aws.amazon.com/directconnect/latest/UserGuide/security_iam_service-with-iam.html#security_iam_service-with-iam-id-based-policies-resources.\narn := arn.ARN{\nPartition: meta.(*AWSClient).partition,\nRegion:    meta.(*AWSClient).region,\nService:   \"directconnect\",\nAccountID: meta.(*AWSClient).accountid,\nResource:  fmt.Sprintf(\"dxvif/%s\", d.Id()),\n}.String()\nd.Set(\"arn\", arn)\n

When the arn attribute is synthesized this way, add the resource to the list of those affected by the provider's skip_requesting_account_id attribute.

  • Implements Warning Logging With Resource State Removal: If a resource is removed outside of Terraform (e.g., via different tool, API, or web UI), d.SetId(\"\") and return nil can be used in the resource Read function to trigger resource recreation. When this occurs, a warning log message should be printed beforehand: log.Printf(\"[WARN] {SERVICE} {THING} (%s) not found, removing from state\", d.Id())
  • Uses American English for Attribute Naming: For any ambiguity with attribute naming, prefer American English over British English. e.g., color instead of colour.
  • Skips Timestamp Attributes: Generally, creation and modification dates from the API should be omitted from the schema.
  • Uses Paginated AWS Go SDK Functions When Iterating Over a Collection of Objects: When the API for listing a collection of objects provides a paginated function, use it instead of looping until the next page token is not set. For example, with the EC2 API, DescribeInstancesPages should be used instead of DescribeInstances when more than one result is expected.
  • Adds Paginated Functions Missing from the AWS Go SDK to Internal Service Package: If the AWS Go SDK does not define a paginated equivalent for a function to list a collection of objects, it should be added to a per-service internal package using the listpages generator. A support case should also be opened with AWS to have the paginated functions added to the AWS Go SDK.
"},{"location":"resource-filtering/","title":"Adding Resource Filtering Support","text":"

AWS provides server-side filtering across many services and resources, which can be used when listing resources of that type, for example in the implementation of a data source. See the EC2 Listing and filtering your resources page for information about how server-side filtering can be used with EC2 resources.

To determine if the supporting AWS API supports this functionality:

  • Open the AWS Go SDK documentation for the service, e.g., for service/rds. Note: there can be a delay between the AWS announcement and the updated AWS Go SDK documentation.
  • Determine if the service API includes functionality for filtering resources (usually a Filters argument to a DescribeThing API call).

Implementing server-side filtering support for Terraform AWS Provider resources requires the following, each with its own section below:

  • Generated Service Filtering Code: In the internal code generators (e.g., internal/generate/namevaluesfilters), implementation and customization of how a service handles filtering, which is standardized for the resources.
  • Resource Filtering Code Implementation: In the resource's equivalent data source code (e.g., internal/service/{servicename}/thing_data_source.go), implementation of filter schema attribute, along with handling in the Read function.
  • Resource Filtering Documentation Implementation: In the resource's equivalent data source documentation (e.g., website/docs/d/service_thing.html.markdown), addition of filter argument
"},{"location":"resource-filtering/#adding-service-to-filter-generating-code","title":"Adding Service to Filter Generating Code","text":"

This step is only necessary for the first implementation and may have been previously completed. If so, move on to the next section.

More details about this code generation can be found in the namevaluesfilters documentation.

Add the AWS Go SDK service name (e.g., rds) to sliceServiceNames in internal/generate/namevaluesfilters/generators/servicefilters/main.go.

  • Run make gen (go generate ./...) and ensure there are no errors via make test (go test ./...)
"},{"location":"resource-filtering/#resource-filter-code-implementation","title":"Resource Filter Code Implementation","text":"
  • In the resource's equivalent data source Go file (e.g., internal/service/ec2/internet_gateway_data_source.go), add the following Go import: \"github.com/hashicorp/terraform-provider-aws/internal/generate/namevaluesfilters\"
  • In the resource schema, add \"filter\": namevaluesfilters.Schema(),
  • Implement the logic to build the list of filters:
input := &ec2.DescribeInternetGatewaysInput{}\n\n// Filters based on attributes.\nfilters := namevaluesfilters.New(map[string]string{\n\"internet-gateway-id\": d.Get(\"internet_gateway_id\").(string),\n})\n// Add filters based on keyvalue tags (N.B. Not applicable to all AWS services that support filtering)\nfilters.Add(namevaluesfilters.EC2Tags(keyvaluetags.New(d.Get(\"tags\").(map[string]interface{})).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()))\n// Add filters based on the custom filtering \"filter\" attribute.\nfilters.Add(d.Get(\"filter\").(*schema.Set))\n\ninput.Filters = filters.EC2Filters()\n
"},{"location":"resource-filtering/#resource-filtering-documentation-implementation","title":"Resource Filtering Documentation Implementation","text":"
  • In the resource's equivalent data source documentation (e.g., website/docs/d/internet_gateway.html.markdown), add the following to the arguments reference:
* `filter` - (Optional) Custom filter block as described below.\n\nMore complex filters can be expressed using one or more `filter` sub-blocks, which take the following arguments:\n\n* `name` - (Required) Name of the field to filter by, as defined by\n  [the underlying AWS API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInternetGateways.html).\n\n* `values` - (Required) Set of values that are accepted for the given field.\n  An Internet Gateway will be selected if any one of the given values matches.\n
"},{"location":"resource-name-generation/","title":"Adding Resource Name Generation Support","text":"

Terraform AWS Provider resources can use shared logic to support and test name generation, where the operator can choose between an expected naming value, a generated naming value with a prefix, or a fully generated name.

Implementing name generation support for Terraform AWS Provider resources requires the following, each with its own section below:

  • Resource Name Generation Code Implementation: In the resource code (e.g., internal/service/{service}/{thing}.go), implementation of name_prefix attribute, along with handling in Create function.
  • Resource Name Generation Testing Implementation: In the resource acceptance testing (e.g., internal/service/{service}/{thing}_test.go), implementation of new acceptance test functions and configurations to exercise new naming logic.
  • Resource Name Generation Documentation Implementation: In the resource documentation (e.g., website/docs/r/service_thing.html.markdown), addition of name_prefix argument and update of name argument description.
"},{"location":"resource-name-generation/#resource-name-generation-code-implementation","title":"Resource name generation code implementation","text":"
  • In the resource Go file (e.g., internal/service/{service}/{thing}.go), add the following Go import: \"github.com/hashicorp/terraform-provider-aws/internal/create\"
  • In the resource schema, add the new name_prefix attribute and adjust the name attribute to be Optional, Computed, and ConflictsWith the name_prefix attribute. Ensure to keep any existing schema fields on name such as ValidateFunc. E.g.
\"name\": {\nType:          schema.TypeString,\nOptional:      true,\nComputed:      true,\nForceNew:      true,\nConflictsWith: []string{\"name_prefix\"},\n},\n\"name_prefix\": {\nType:          schema.TypeString,\nOptional:      true,\nComputed:      true,\nForceNew:      true,\nConflictsWith: []string{\"name\"},\n},\n
  • In the resource Create function, switch any calls from d.Get(\"name\").(string) to instead use the create.Name() function, e.g.
name := create.Name(d.Get(\"name\").(string), d.Get(\"name_prefix\").(string))\n\n// ... in AWS Go SDK Input types, etc. use aws.String(name)\n
  • If the resource supports import, in the resource Read function add a call to d.Set(\"name_prefix\", ...), e.g.
d.Set(\"name\", resp.Name)\nd.Set(\"name_prefix\", create.NamePrefixFromName(aws.StringValue(resp.Name)))\n
"},{"location":"resource-name-generation/#resource-name-generation-testing-implementation","title":"Resource name generation testing implementation","text":"
  • In the resource testing (e.g., internal/service/{service}/{thing}_test.go), add the following Go import: \"github.com/hashicorp/terraform-provider-aws/internal/create\"
  • In the resource testing, implement two new tests named _Name_Generated and _NamePrefix with associated configurations, that verifies creating the resource without name and name_prefix arguments (for the former) and with only the name_prefix argument (for the latter). E.g.
func TestAccServiceThing_nameGenerated(t *testing.T) {\nctx := acctest.Context(t)\nvar thing service.ServiceThing\nresourceName := \"aws_service_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckThingDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccThingConfig_nameGenerated(),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckThingExists(ctx, resourceName, &thing),\nacctest.CheckResourceAttrNameGenerated(resourceName, \"name\"),\nresource.TestCheckResourceAttr(resourceName, \"name_prefix\", id.UniqueIdPrefix),\n),\n},\n// If the resource supports import:\n{\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n},\n})\n}\n\nfunc TestAccServiceThing_namePrefix(t *testing.T) {\nctx := acctest.Context(t)\nvar thing service.ServiceThing\nresourceName := \"aws_service_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckThingDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccThingConfig_namePrefix(\"tf-acc-test-prefix-\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckThingExists(ctx, resourceName, &thing),\nacctest.CheckResourceAttrNameFromPrefix(resourceName, \"name\", \"tf-acc-test-prefix-\"),\nresource.TestCheckResourceAttr(resourceName, \"name_prefix\", \"tf-acc-test-prefix-\"),\n),\n},\n// If the resource supports import:\n{\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n},\n})\n}\n\nfunc testAccThingConfig_nameGenerated() string {\nreturn fmt.Sprintf(`\nresource \"aws_service_thing\" \"test\" {\n  # ... other configuration ...\n}\n`)\n}\n\nfunc testAccThingConfig_namePrefix(namePrefix string) string {\nreturn fmt.Sprintf(`\nresource \"aws_service_thing\" \"test\" {\n  # ... other configuration ...\n\n  name_prefix = %[1]q\n}\n`, namePrefix)\n}\n
"},{"location":"resource-name-generation/#resource-name-generation-documentation-implementation","title":"Resource name generation documentation implementation","text":"
  • In the resource documentation (e.g., website/docs/r/service_thing.html.markdown), add the following to the arguments reference:
* `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. Conflicts with `name`.\n
  • Adjust the existing name argument reference to ensure its denoted as Optional, includes a mention that it can be generated, and that it conflicts with name_prefix:
* `name` - (Optional) Name of the thing. If omitted, Terraform will assign a random, unique name. Conflicts with `name_prefix`.\n
"},{"location":"resource-name-generation/#resource-name-generation-with-suffix","title":"Resource name generation with suffix","text":"

Some generated resource names require a fixed suffix (for example Amazon SNS FIFO topic names must end in .fifo). In these cases use create.NameWithSuffix() in the resource Create function and create.NamePrefixFromNameWithSuffix() in the resource Read function, e.g.

name := create.NameWithSuffix(d.Get(\"name\").(string), d.Get(\"name_prefix\").(string), \".fifo\")\n

and

d.Set(\"name\", resp.Name)\nd.Set(\"name_prefix\", create.NamePrefixFromNameWithSuffix(aws.StringValue(resp.Name), \".fifo\"))\n

There are also functions acctest.CheckResourceAttrNameWithSuffixGenerated and acctest.CheckResourceAttrNameWithSuffixFromPrefix for use in tests.

"},{"location":"resource-tagging/","title":"Adding Resource Tagging Support","text":"

AWS provides key-value metadata across many services and resources, which can be used for a variety of use cases including billing, ownership, and more. See the AWS Tagging Strategy page for more information about tagging at a high level.

The Terraform AWS Provider supports default tags configured on the provider in addition to tags configured on the resource. Implementing tagging support for Terraform AWS Provider resources requires the following, each with its own section below:

  • Generated Service Tagging Code: Each service has a generate.go file where generator directives live. Through these directives and their flags, you can customize code generation for the service. You can find the code that the tagging generator generates in a tags_gen.go file in a service, such as internal/service/ec2/tags_gen.go. You should generally not need to edit the generator code itself (i.e., in internal/generate/tags).
  • Resource Tagging Code Implementation: In the resource code (e.g., internal/service/{service}/{thing}.go), implementation of tags and tags_all schema attributes, along with implementation of CustomizeDiff in the resource definition and handling in Create, Read, and Update functions.
  • Resource Tagging Acceptance Testing Implementation: In the resource acceptance testing (e.g., internal/service/{service}/{thing}_test.go), implementation of new acceptance test function and configurations to exercise new tagging logic.
  • Resource Tagging Documentation Implementation: In the resource documentation (e.g., website/docs/r/service_thing.html.markdown), addition of tags argument and tags_all attributes.
"},{"location":"resource-tagging/#generating-tag-code-for-a-service","title":"Generating Tag Code for a Service","text":"

This step is generally only necessary for the first implementation and may have been previously completed.

More details about this code generation, including fixes for potential error messages in this process, can be found in the generate package documentation.

The generator will create several types of tagging-related code. All services that support tagging will generate the function KeyValueTags, which converts from service-specific structs returned by the AWS SDK into a common format used by the provider, and the function Tags, which converts from the common format back to the service-specific structs. In addition, many services have separate functions to list or update tags, so the corresponding listTags and updateTags can be generated. Optionally, to retrieve a specific tag, you can generate the GetTag function.

If the service directory does not contain a generate.go file, create one. This file must only contain generate directives and a package declaration (e.g., package eks). For examples of the generate.go file, many service directories contain one, e.g., internal/service/eks/generate.go.

If the generate.go file does not contain a generate directive for tagging code, i.e., //go:generate go run ../../generate/tags/main.go, add it. Note that without flags, the directive itself will not do anything useful. You must not include more than one generate/tags/main.go directive, as subsequent directives will overwrite previous directives. To generate multiple types of tag code, use multiple flags with the directive.

"},{"location":"resource-tagging/#generating-tagging-types","title":"Generating Tagging Types","text":"

Determine how the service implements tagging: Some services will use a simple map style (map[string]*string in Go), while others will have a separate structure, often a []service.Tag struct with Key and Value fields.

If the service uses the simple map style, pass the flag -ServiceTagsMap.

If the service uses a slice of structs, pass the flag -ServiceTagsSlice. If the name of the tag struct is not Tag, pass the flag -TagType=<struct name>. Note that the struct name is used without the package name. For example, the AppMesh service uses the struct TagRef, so the flag is -TagType=TagRef. If the key and value fields on the struct are not Key and Value, specify the names using the flags -TagTypeKeyElem and -TagTypeValElem respectively. For example, the KMS service uses the struct Tag, but the key and value fields are TagKey and TagValue, so the flags are -TagTypeKeyElem=TagKey and -TagTypeValElem=TagValue.

Some services, such as EC2 and Auto Scaling, return a different type depending on the API call used to retrieve the tag. To indicate the additional type, include the flag -TagType2=<struct name>. For example, the Auto Scaling uses the struct Tag as part of resource calls, but returns the struct TagDescription from the DescribeTags API call. The flag used is -TagType2=TagDescription.

For more details on flags for generating service keys, see the documentation for the tag generator

"},{"location":"resource-tagging/#generating-standalone-tag-listing-functions","title":"Generating Standalone Tag Listing Functions","text":"

If the service API uses a standalone function to retrieve tags instead of including them with the resource (usually a ListTags or ListTagsForResource API call), pass the flag -ListTags.

If the API call is not ListTagsForResource, pass the flag -ListTagsOp=<API call name>. Note that this does not include the package name. For example, the Auto Scaling service uses the API call DescribeTags, so the flag is -ListTagsOp=DescribeTags.

If the API call uses a field other than ResourceArn to identify the resource, pass the flag -ListTagsInIDElem=<field name>. For example, the CloudWatch service uses the field ResourceARN, so the flag is -ListTagsInIDElem=ResourceARN. Some API calls take a slice of identifiers instead of a single identifier. In this case, pass the flag -ListTagsInIDNeedSlice=yes.

If the field containing the tags in the result of the API call is not named Tags, pass the flag -ListTagsOutTagsElem=<struct name>. For example, the CloudTrail service returns a nested structure, where the resulting flag is -ListTagsOutTagsElem=ResourceTagList[0].TagsList.

In some cases, it can be useful to retrieve single tags. Pass the flag -GetTag to generate a function to do so.

For more details on flags for generating tag listing functions, see the documentation for the tag generator

"},{"location":"resource-tagging/#generating-standalone-tag-updating-functions","title":"Generating Standalone Tag Updating Functions","text":"

If the service API uses a standalone function to update tags instead of including them when updating the resource (usually a TagResource and UntagResource API call), pass the flag -UpdateTags.

If the API call to add tags is not TagResource, pass the flag -TagOp=<API call name>. Note that this does not include the package name. For example, the ElastiCache service uses the API call AddTagsToResource, so the flag is -TagOp=AddTagsToResource.

If the API call to add tags uses a field other than ResourceArn to identify the resource, pass the flag -TagInIDElem=<field name>. For example, the EC2 service uses the field Resources, so the flag is -TagInIDElem=Resources. Some API calls take a slice of identifiers instead of a single identifier. In this case, pass the flag -TagInIDNeedSlice=yes.

If the API call to remove tags is not UntagResource, pass the flag -UntagOp=<API call name>. Note that this does not include the package name. For example, the ElastiCache service uses the API call RemoveTagsFromResource, so the flag is -UntagOp=RemoveTagsFromResource.

If the API call to remove tags uses a field other than ResourceArn to identify the resource, pass the flag -UntagInTagsElem=<field name>. For example, the Route 53 service uses the field Keys, so the flag is -UntagInTagsElem=Keys.

For more details on flags for generating tag updating functions, see the documentation for the tag generator

"},{"location":"resource-tagging/#generating-standalone-post-creation-tag-updating-functions","title":"Generating Standalone Post-Creation Tag Updating Functions","text":"

When creating a resource, some AWS APIs support passing tags in the Create call while others require setting the tags after the initial creation. If the API does not support tagging on creation, pass the -CreateTags flag to generate a createTags function that can be called from the resource Create handler function.

"},{"location":"resource-tagging/#specifying-the-aws-sdk-for-go-version","title":"Specifying the AWS SDK for Go version","text":"

The vast majority of the Terraform AWS Provider is implemented using version 1 of the AWS SDK for Go. For new services, however, we have started to use version 2 of the SDK.

By default, the generated code uses the AWS SDK for Go v1. To generate code using the AWS SDK for Go v2, pass the flag -AwsSdkVersion=2.

For more information, see the documentation on AWS SDK versions.

"},{"location":"resource-tagging/#running-code-generation","title":"Running Code generation","text":"

Run the command make gen to run the code generators for the project. To ensure that the code compiles, run make test.

"},{"location":"resource-tagging/#resource-tagging-code-implementation","title":"Resource Tagging Code Implementation","text":""},{"location":"resource-tagging/#resource-schema","title":"Resource Schema","text":"

Add the following imports to the resource's Go source file:

imports (\n/* ... other imports ... */\ntftags \"github.com/hashicorp/terraform-provider-aws/internal/tags\"\n\"github.com/hashicorp/terraform-provider-aws/internal/verify\"\n\"github.com/hashicorp/terraform-provider-aws/names\"\n)\n

Add the tags parameter and tags_all attribute to the schema, using constants defined in the names package. The tags parameter contains the tags set directly on the resource. The tags_all attribute contains union of the tags set directly on the resource and default tags configured on the provider.

func ResourceCluster() *schema.Resource {\nreturn &schema.Resource{\n/* ... other configuration ... */\nSchema: map[string]*schema.Schema{\n/* ... other configuration ... */\nnames.AttrTags:    tftags.TagsSchema(),\nnames.AttrTagsAll: tftags.TagsSchemaComputed(),\n},\n}\n}\n

The function verify.SetTagsDiff handles the combination of tags set on the resource and default tags, and must be added to the resource's CustomizeDiff function.

If the resource has no other CustomizeDiff handler functions, set it directly:

func ResourceCluster() *schema.Resource {\nreturn &schema.Resource{\n/* ... other configuration ... */\nCustomizeDiff: verify.SetTagsDiff,\n}\n}\n

Otherwise, if the resource already contains a CustomizeDiff function, append the SetTagsDiff via the customdiff.All method:

func ResourceExample() *schema.Resource {\nreturn &schema.Resource{\n/* ... other configuration ... */\nCustomizeDiff: customdiff.All(\nresourceExampleCustomizeDiff,\nverify.SetTagsDiff,\n),\n}\n}\n
"},{"location":"resource-tagging/#transparent-tagging","title":"Transparent Tagging","text":"

Most services can use a facility we call transparent (or implicit) tagging, where the majority of resource tagging functionality is implemented using code located in the provider's runtime packages (see internal/provider/intercept.go and internal/provider/fwprovider/intercept.go for details) and not in the resource's CRUD handler functions. Resource implementers opt-in to transparent tagging by adding an annotation (a specially formatted Go comment) to the resource's factory function (similar to the resource self-registration mechanism).

// @SDKResource(\"aws_accessanalyzer_analyzer\", name=\"Analyzer\")\n// @Tags(identifierAttribute=\"arn\")\nfunc ResourceAnalyzer() *schema.Resource {\nreturn &schema.Resource{\n...\n}\n}\n

The identifierAttribute argument to the @Tags annotation identifies the attribute in the resource's schema whose value is used in tag listing and updating API calls. Common values are \"arn\" and \"id\". Once the annotation has been added to the resource's code, run make gen to register the resource for transparent tagging. This will add an entry to the service_package_gen.go file located in the service package folder.

"},{"location":"resource-tagging/#resource-create-operation","title":"Resource Create Operation","text":"

When creating a resource, some AWS APIs support passing tags in the Create call while others require setting the tags after the initial creation.

If the API supports tagging on creation (e.g., the Input struct accepts a Tags field), use the getTagsIn function to get any configured tags, e.g., with EKS Clusters:

input := &eks.CreateClusterInput{\n/* ... other configuration ... */\nTags: getTagsIn(ctx),\n}\n

Otherwise, if the API does not support tagging on creation, call createTags after the resource has been created, e.g., with Device Farm device pools:

if err := createTags(ctx, conn, d.Id(), getTagsIn(ctx)); err != nil {\nreturn sdkdiag.AppendErrorf(diags, \"setting DeviceFarm Device Pool (%s) tags: %s\", d.Id(), err)\n}\n
"},{"location":"resource-tagging/#resource-read-operation","title":"Resource Read Operation","text":"

In the resource Read operation, use the setTagsOut function to signal to the transparent tagging mechanism that the resource has tags that should be saved into Terraform state, e.g., with EKS Clusters:

/* ... other d.Set(...) logic ... */\n\nsetTagsOut(ctx, cluster.Tags)\n

If the service API does not return the tags directly from reading the resource and requires use of the generated listTags function, do nothing and the transparent tagging mechanism will make the listTags call and save any tags into Terraform state.

"},{"location":"resource-tagging/#resource-update-operation","title":"Resource Update Operation","text":"

In the resource Update operation, only non-tags updates need be done as the transparent tagging mechanism makes the updateTags call.

if d.HasChangesExcept(\"tags\", \"tags_all\") {\n...\n}\n

Ensure that the Update operation always calls the resource Read operation before returning so that the transparent tagging mechanism correctly saves any tags into Terraform state, e.g., with Access Analyzer analyzers:

func resourceAnalyzerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {\nvar diags diag.Diagnostics\n// Tags only.\nreturn append(diags, resourceAnalyzerRead(ctx, d, meta)...)\n}\n
"},{"location":"resource-tagging/#explicit-tagging","title":"Explicit Tagging","text":"

If the resource cannot opt-in to transparent tagging, more boilerplate code must be explicitly added to the resource CRUD handler functions. This section describes how to do this.

"},{"location":"resource-tagging/#resource-create-operation_1","title":"Resource Create Operation","text":"

When creating a resource, some AWS APIs support passing tags in the Create call while others require setting the tags after the initial creation.

If the API supports tagging on creation (e.g., the Input struct accepts a Tags field), implement the logic to convert the configuration tags into the service tags, e.g., with EKS Clusters:

// Typically declared near conn := /* ... */\ndefaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig\ntags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get(\"tags\").(map[string]interface{})))\n\ninput := &eks.CreateClusterInput{\n/* ... other configuration ... */\nTags: Tags(tags.IgnoreAWS()),\n}\n

If the service API does not allow passing an empty list, the logic can be adjusted similar to:

// Typically declared near conn := /* ... */\ndefaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig\ntags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get(\"tags\").(map[string]interface{})))\n\ninput := &eks.CreateClusterInput{\n/* ... other configuration ... */\n}\n\nif len(tags) > 0 {\ninput.Tags = Tags(tags.IgnoreAWS())\n}\n

Otherwise, if the API does not support tagging on creation, implement the logic to convert the configuration tags into the service API call to tag a resource, e.g., with Device Farm device pools:

// Typically declared near conn := /* ... */\ndefaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig\ntags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get(\"tags\").(map[string]interface{})))\n\n/* ... creation steps ... */\n\nif len(tags) > 0 {\nif err := updateTags(ctx, conn, d.Id(), nil, tags); err != nil {\nreturn fmt.Errorf(\"adding DeviceFarm Device Pool (%s) tags: %w\", d.Id(), err)\n}\n}\n

Some EC2 resources (e.g., aws_ec2_fleet) have a TagSpecifications field in the InputStruct instead of a Tags field. In these cases the tagSpecificationsFromKeyValueTags() helper function should be used. This example shows using TagSpecifications:

// Typically declared near conn := /* ... */\ndefaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig\ntags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get(\"tags\").(map[string]interface{})))\n\ninput := &ec2.CreateFleetInput{\n/* ... other configuration ... */\nTagSpecifications: tagSpecificationsFromKeyValueTags(tags, ec2.ResourceTypeFleet),\n}\n
"},{"location":"resource-tagging/#resource-read-operation_1","title":"Resource Read Operation","text":"

In the resource Read operation, implement the logic to convert the service tags to save them into the Terraform state for drift detection, e.g., with EKS Clusters:

// Typically declared near conn := /* ... */\ndefaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig\nignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig\n\n/* ... other d.Set(...) logic ... */\n\ntags := KeyValueTags(ctx, cluster.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig)\n\nif err := d.Set(\"tags\", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {\nreturn fmt.Errorf(\"setting tags: %w\", err)\n}\n\nif err := d.Set(\"tags_all\", tags.Map()); err != nil {\nreturn fmt.Errorf(\"setting tags_all: %w\", err)\n}\n

If the service API does not return the tags directly from reading the resource and requires a separate API call, use the generated listTags function, e.g., with Athena Workgroups:

// Typically declared near conn := /* ... */\ndefaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig\nignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig\n\n/* ... other d.Set(...) logic ... */\n\ntags, err := listTags(ctx, conn, arn.String())\n\nif err != nil {\nreturn fmt.Errorf(\"listing tags for resource (%s): %w\", arn, err)\n}\n\ntags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)\n\nif err := d.Set(\"tags\", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {\nreturn fmt.Errorf(\"setting tags: %w\", err)\n}\n\nif err := d.Set(\"tags_all\", tags.Map()); err != nil {\nreturn fmt.Errorf(\"setting tags_all: %w\", err)\n}\n
"},{"location":"resource-tagging/#resource-update-operation_1","title":"Resource Update Operation","text":"

In the resource Update operation, implement the logic to handle tagging updates, e.g., with EKS Clusters:

if d.HasChange(\"tags_all\") {\no, n := d.GetChange(\"tags_all\")\nif err := updateTags(ctx, conn, d.Get(\"arn\").(string), o, n); err != nil {\nreturn fmt.Errorf(\"updating tags: %w\", err)\n}\n}\n

If the resource Update function applies specific updates to attributes regardless of changes to tags, implement the following e.g., with IAM Policy:

if d.HasChangesExcept(\"tags\", \"tags_all\") {\n/* ... other logic ...*/\nrequest := &iam.CreatePolicyVersionInput{\nPolicyArn:      aws.String(d.Id()),\nPolicyDocument: aws.String(d.Get(\"policy\").(string)),\nSetAsDefault:   aws.Bool(true),\n}\n\nif _, err := conn.CreatePolicyVersionWithContext(ctx, request); err != nil {\nreturn fmt.Errorf(\"updating IAM policy (%s): %w\", d.Id(), err)\n}\n}\n
"},{"location":"resource-tagging/#resource-tagging-acceptance-testing-implementation","title":"Resource Tagging Acceptance Testing Implementation","text":"

In the resource testing (e.g., internal/service/eks/cluster_test.go), verify that existing resources without tagging are unaffected and do not have tags saved into their Terraform state. This should be done in the _basic acceptance test by adding one line similar to resource.TestCheckResourceAttr(resourceName, \"tags.%\", \"0\"), and one similar to resource.TestCheckResourceAttr(resourceName, \"tags_all.%\", \"0\"),

In the resource testing, implement a new test named _tags with associated configurations, that verifies creating the resource with tags and updating tags. E.g., EKS Clusters:

func TestAccEKSCluster_tags(t *testing.T) {\nctx := acctest.Context(t)\nvar cluster1, cluster2, cluster3 eks.Cluster\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_eks_cluster.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t); testAccPreCheck(t) },\nErrorCheck:               acctest.ErrorCheck(t, eks.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckClusterDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccClusterConfig_tags1(rName, \"key1\", \"value1\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckClusterExists(ctx, resourceName, &cluster1),\nresource.TestCheckResourceAttr(resourceName, \"tags.%\", \"1\"),\nresource.TestCheckResourceAttr(resourceName, \"tags.key1\", \"value1\"),\n),\n},\n{\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n{\nConfig: testAccClusterConfig_tags2(rName, \"key1\", \"value1updated\", \"key2\", \"value2\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckClusterExists(ctx, resourceName, &cluster2),\nresource.TestCheckResourceAttr(resourceName, \"tags.%\", \"2\"),\nresource.TestCheckResourceAttr(resourceName, \"tags.key1\", \"value1updated\"),\nresource.TestCheckResourceAttr(resourceName, \"tags.key2\", \"value2\"),\n),\n},\n{\nConfig: testAccClusterConfig_tags1(rName, \"key2\", \"value2\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckClusterExists(ctx, resourceName, &cluster3),\nresource.TestCheckResourceAttr(resourceName, \"tags.%\", \"1\"),\nresource.TestCheckResourceAttr(resourceName, \"tags.key2\", \"value2\"),\n),\n},\n},\n})\n}\n\nfunc testAccClusterConfig_tags1(rName, tagKey1, tagValue1 string) string {\nreturn acctest.ConfigCompose(testAccClusterConfig_base(rName), fmt.Sprintf(`\nresource \"aws_eks_cluster\" \"test\" {\n  name     = %[1]q\n  role_arn = aws_iam_role.test.arn\n\n  tags = {\n    %[2]q = %[3]q\n  }\n\n  vpc_config {\n    subnet_ids = aws_subnet.test[*].id\n  }\n\n  depends_on = [aws_iam_role_policy_attachment.test-AmazonEKSClusterPolicy]\n}\n`, rName, tagKey1, tagValue1))\n}\n\nfunc testAccClusterConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string {\nreturn acctest.ConfigCompose(testAccClusterConfig_base(rName), fmt.Sprintf(`\nresource \"aws_eks_cluster\" \"test\" {\n  name     = %[1]q\n  role_arn = aws_iam_role.test.arn\n\n  tags = {\n    %[2]q = %[3]q\n    %[4]q = %[5]q\n  }\n\n  vpc_config {\n    subnet_ids = aws_subnet.test[*].id\n  }\n\n  depends_on = [aws_iam_role_policy_attachment.test-AmazonEKSClusterPolicy]\n}\n`, rName, tagKey1, tagValue1, tagKey2, tagValue2))\n}\n

Verify all acceptance testing passes for the resource (e.g., make testacc TESTS=TestAccEKSCluster_ PKG=eks)

"},{"location":"resource-tagging/#resource-tagging-documentation-implementation","title":"Resource Tagging Documentation Implementation","text":"

In the resource documentation (e.g., website/docs/r/eks_cluster.html.markdown), add the following to the arguments reference:

* `tags` - (Optional) Key-value mapping 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.\n

In the resource documentation (e.g., website/docs/r/eks_cluster.html.markdown), add the following to the attributes reference:

* `tags_all` - 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).\n
"},{"location":"retries-and-waiters/","title":"Retries and Waiters","text":"

Terraform plugins may run into situations where calling the remote system after an operation may be necessary. These typically fall under three classes where:

  • The request never reaches the remote system.
  • The request reaches the remote system and responds that it cannot handle the request temporarily.
  • The implementation of the remote system requires additional requests to ensure success.

This guide describes the behavior of the Terraform AWS Provider and provides code implementations that help ensure success in each of these situations.

"},{"location":"retries-and-waiters/#terraform-plugin-sdk-functionality","title":"Terraform Plugin SDK Functionality","text":"

The Terraform Plugin SDK, which the AWS Provider uses, provides vital tools for handling consistency: the retry.StateChangeConf{} struct, and the retry function retry.RetryContext(). We will discuss these throughout the rest of this guide. Since they help keep the AWS Provider code consistent, we heavily prefer them over custom implementations.

This guide goes beyond the Terraform Plugin SDK v2 documentation by providing additional context and emergent implementations specific to the Terraform AWS Provider.

"},{"location":"retries-and-waiters/#state-change-configuration-and-functions","title":"State Change Configuration and Functions","text":"

The retry.StateChangeConf type, along with its receiver methods WaitForState() and WaitForStateContext() is a generic primitive for repeating operations in Terraform resource logic until desired value(s) are received. The \"state change\" in this case is generic to any value and not specific to the Terraform State. Among other functionality, it supports some of these desirable optional properties:

  • Expecting specific value(s) while waiting for the target value(s) to be reached. Unexpected values are returned as an error which can be augmented with additional details.
  • Expecting the target value(s) to be returned multiple times in succession.
  • Allowing various polling configurations such as delaying the initial request and setting the time between polls.
"},{"location":"retries-and-waiters/#retry-functions","title":"Retry Functions","text":"

The retry.RetryContext() function provides a simplified retry implementation around retry.StateChangeConf. The most common use is for simple error-based retries.

"},{"location":"retries-and-waiters/#aws-request-handling","title":"AWS Request Handling","text":"

The Terraform AWS Provider's requests to AWS service APIs happen on top of Hypertext Transfer Protocol (HTTP). The following is a simplified description of the layers and handling that requests pass through:

  • A Terraform resource calls an AWS Go SDK function.
  • The AWS Go SDK generates an AWS-compatible HTTP request using the Go standard library net/http package. This includes the following:
    • Adding HTTP headers for authentication and signing of requests to ensure authenticity.
    • Converting operation inputs into required HTTP URI parameters and/or request body type (XML or JSON).
    • If debug logging is enabled, logging of the HTTP request.
  • The AWS Go SDK transmits the net/http request using Go's standard handling of the Operating System (OS) and Domain Name System (DNS) configuration.
  • The AWS service potentially receives the request and responds, typically adding a request identifier HTTP header which can be used for AWS Support cases.
  • The OS and Go net/http receive the response and pass it to the AWS Go SDK.
  • The AWS Go SDK attempts to handle the response. This may include:
    • Parsing output
    • Converting errors into operation errors (Go error type of wrapped awserr.Error type).
    • Converting response elements into operation outputs (AWS Go SDK operation-specific types).
    • Triggering automatic request retries based on default and custom logic.
  • The Terraform resource receives the response, including any output and errors, from the AWS Go SDK.

The Terraform AWS Provider specific configuration for AWS Go SDK operation handling can be found in aws/conns/conns.go in this codebase and the hashicorp/aws-sdk-go-base codebase.

NOTE: The section descibes the current handling with version 1 of the AWS Go SDK. In the future, this codebase will be migrated to version 2 of the AWS Go SDK. The newer version implements a very similar request flow but uses a simpler credential and request handling configuration. As such, the aws-sdk-go-base dependency will likely not receive further updates and will be removed after that migration.

"},{"location":"retries-and-waiters/#default-aws-go-sdk-retries","title":"Default AWS Go SDK Retries","text":"

In some situations, while handling a response, the AWS Go SDK automatically retries a request before returning the output and error. The retry mechanism implements an exponential backoff algorithm. The default conditions triggering automatic retries (implemented through client.DefaultRetryer) include:

  • Certain network errors. A common exception to this is connection reset errors.
  • HTTP status codes 429 and 5xx.
  • Certain API error codes, which are common across various AWS services (e.g., ThrottledException). However, not all AWS services implement these error codes consistently. A common exception to this is certain expired credentials errors.

By default, the Terraform AWS Provider sets the maximum number of AWS Go SDK retries based on the max_retries provider configuration. The provider configuration defaults to 25 and the exponential backoff roughly equates to one hour of retries. This very high default value was present before the Terraform AWS Provider codebase was split from Terraform CLI in version 0.10.

NOTE: The section describes the current handling with version 1 of the AWS Go SDK. In the future, this codebase will be migrated to version 2 of the AWS Go SDK. The newer version implements additional retry conditions by default, such as consistently retrying all common network errors.

NOTE: The section describes the current handling with Terraform Plugin SDK resource signatures without context.Context. In the future, this codebase will be migrated to the context-aware resource signatures which currently enforce a 20-minute default timeout that conflicts with the timeout with the default max_retries value. The Terraform Plugin SDK may be updated to support removing this default 20-minute timeout or the default retry mechanism described here will be updated to prevent context cancellation errors where possible.

"},{"location":"retries-and-waiters/#lower-network-error-retries","title":"Lower Network Error Retries","text":"

Given the very high default number of AWS Go SDK retries configured in the Terraform AWS Provider and the excessive wait that practitioners would face, the hashicorp/aws-sdk-go-base codebase lowers retries to 10 for certain network errors that typically cannot be remediated via retries. This roughly equates to 30 seconds of retries.

"},{"location":"retries-and-waiters/#terraform-aws-provider-service-retries","title":"Terraform AWS Provider Service Retries","text":"

The AWS Go SDK provides hooks for injecting custom logic into the service client handlers. We prefer this handling in situations where contributors would need to apply the retry behavior to many resources. For example, in cases where the AWS service API does not mark an error code as automatically retriable. The AWS Provider includes other retry-changing behaviors using this method. You can find them in the aws/config.go file. For example:

client.kafkaconn.Handlers.Retry.PushBack(func(r *request.Request) {\nif tfawserr.ErrMessageContains(r.Error, kafka.ErrCodeTooManyRequestsException, \"Too Many Requests\") {\nr.Retryable = aws.Bool(true)\n}\n})\n
"},{"location":"retries-and-waiters/#eventual-consistency","title":"Eventual Consistency","text":"

Eventual consistency is a temporary condition where the remote system can return outdated information or errors due to not being strongly read-after-write consistent. This is a pattern found in remote systems that must be highly scaled for broad usage.

Terraform expects any planned resource lifecycle change (create, update, destroy of the resource itself) and planned resource attribute value change to match after being applied. Conversely, operators typically expect that Terraform resources also implement the concept of drift detection for resources and their attributes, which requires reading information back from the remote system after an operation. A common implementation is refreshing the Terraform State information (d.Set()) during the Read function of a resource after Create and Update.

These two concepts conflict with each other and require additional handling in Terraform resource logic as shown in the following sections. These issues are not reliably reproducible, especially in the case of writing acceptance testing, so they can be elusive with false positives to verify fixes.

"},{"location":"retries-and-waiters/#operation-specific-error-retries","title":"Operation Specific Error Retries","text":"

Even given a properly ordered Terraform configuration, eventual consistency can unexpectedly prevent downstream operations from succeeding. A simple retry after a few seconds resolves many of these issues. To reduce frustrating behavior for operators, wrap AWS Go SDK operations with the retry.RetryContext() function. These retries should have a reasonably low timeout (typically two minutes but up to five minutes). Save them in a constant for reusability. These functions are preferably in line with the associated resource logic to remove any indirection with the code.

Do not use this type of logic to overcome improperly ordered Terraform configurations. The approach may not work in larger environments.

// internal/service/example/wait.go (created if does not exist)\n\nconst (\n// Maximum amount of time to wait for Thing operation eventual consistency\nThingOperationTimeout = 2 * time.Minute\n)\n
// internal/service/{service}/{thing}.go\n\n// ... Create, Read, Update, or Delete function ...\nerr := retry.RetryContext(ctx, ThingOperationTimeout, func() *retry.RetryError {\n_, err := conn./* ... AWS Go SDK operation with eventual consistency errors ... */\n\n// Retryable conditions which can be checked.\n// These must be updated to match the AWS service API error code and message.\nif tfawserr.ErrMessageContains(err, /* error code */, /* error message */) {\nreturn retry.RetryableError(err)\n}\n\nif err != nil {\nreturn retry.NonRetryableError(err)\n}\n\nreturn nil\n})\n\n// This check is important - it handles when the AWS Go SDK operation retries without returning.\n// e.g., any automatic retries due to network or throttling errors.\nif tfresource.TimedOut(err) {\n// The use of equals assignment (over colon equals) is also important here.\n// This overwrites the error variable to simplify logic.\n_, err = conn./* ... AWS Go SDK operation with IAM eventual consistency errors ... */\n}\n\nif err != nil {\nreturn fmt.Errorf(\"... error message context ... : %w\", err)\n}\n

NOTE: The section descibes the current handling with version 1 of the AWS Go SDK. In the future, this codebase will be migrated to version 2 of the AWS Go SDK. The newer version natively supports operation-specific retries in a more friendly manner, which may replace this type of implementation.

"},{"location":"retries-and-waiters/#iam-error-retries","title":"IAM Error Retries","text":"

A common eventual consistency issue is an error returned due to IAM permissions. The IAM service itself is eventually consistent along with the propagation of its components and permissions to other AWS services. For example, if the following operations occur in quick succession:

  • Create an IAM Role
  • Attach an IAM Policy to the IAM Role
  • Reference the new IAM Role in another AWS service, such as creating a Lambda Function

The last operation can receive varied API errors ranging from:

  • IAM Role being reported as not existing
  • IAM Role being reported as not having permissions for the other service to use it (assume role permissions)
  • IAM Role being reported as not having sufficient permissions (inline or attached role permissions)

Each AWS service API (and sometimes even operations within the same API) varies in the implementation of these errors. To handle them, it is recommended to use the Operation Specific Error Retries pattern. The Terraform AWS Provider implements a standard timeout constant of two minutes in the internal/service/iam package which should be used for all retry timeouts associated with IAM errors. This timeout was derived from years of Terraform operational experience with all AWS APIs.

// internal/service/{service}/{thing}.go\n\nimport (\n// ... other imports ...\ntfiam \"github.com/hashicorp/terraform-provider-aws/internal/service/iam\"\n)\n\n// ... Create and typically Update function ...\nerr := retry.RetryContext(ctx, iamwaiter.PropagationTimeout, func() *retry.RetryError {\n_, err := conn./* ... AWS Go SDK operation with IAM eventual consistency errors ... */\n\n// Example retryable condition\n// This must be updated to match the AWS service API error code and message.\nif tfawserr.ErrMessageContains(err, /* error code */, /* error message */) {\nreturn retry.RetryableError(err)\n}\n\nif err != nil {\nreturn retry.NonRetryableError(err)\n}\n\nreturn nil\n})\n\nif tfresource.TimedOut(err) {\n_, err = conn./* ... AWS Go SDK operation with IAM eventual consistency errors ... */\n}\n\nif err != nil {\nreturn fmt.Errorf(\"... error message context ... : %w\", err)\n}\n
"},{"location":"retries-and-waiters/#asynchronous-operation-error-retries","title":"Asynchronous Operation Error Retries","text":"

Some remote system operations run asynchronously as detailed in the Asynchronous Operations section. In these cases, it is possible that the initial operation will immediately return as successful, but potentially return a retryable failure while checking the operation status that requires starting everything over. The handling for these is complicated by the fact that there are two timeouts, one for the retryable failure and one for the asynchronous operation status checking.

The below code example highlights this situation for a resource creation that also exhibited IAM eventual consistency.

// internal/service/{service}/{thing}.go\n\nimport (\n// ... other imports ...\ntfiam \"github.com/hashicorp/terraform-provider-aws/internal/service/iam\"\n)\n\n// ... Create function ...\n\n// Underlying IAM eventual consistency errors can occur after the creation\n// operation. The goal is only retry these types of errors up to the IAM\n// timeout. Since the creation process is asynchronous and can take up to\n// its own timeout, we store a stop time upfront for checking.\niamwaiterStopTime := time.Now().Add(tfiam.PropagationTimeout)\n\n// Ensure to add IAM eventual consistency timeout in case of retries\nerr = retry.RetryContext(ctx, tfiam.PropagationTimeout+ThingOperationTimeout, func() *retry.RetryError {\n// Only retry IAM eventual consistency errors up to that timeout\niamwaiterRetry := time.Now().Before(iamwaiterStopTime)\n\n_, err := conn./* ... AWS Go SDK operation without eventual consistency errors ... */\n\nif err != nil {\nreturn retry.NonRetryableError(err)\n}\n\n_, err = ThingOperation(conn, d.Id())\n\nif err != nil {\nif iamwaiterRetry && /* eventual consistency error checking */ {\nreturn retry.RetryableError(err)\n}\n\nreturn retry.NonRetryableError(err)\n}\n\nreturn nil\n})\n\nif tfresource.TimedOut(err) {\n_, err = conn./* ... AWS Go SDK operation without eventual consistency errors ... */\n\nif err != nil {\nreturn err\n}\n\n_, err = ThingOperation(conn, d.Id())\n\nif err != nil {\nreturn err\n}\n}\n
"},{"location":"retries-and-waiters/#resource-lifecycle-retries","title":"Resource Lifecycle Retries","text":"

Resource lifecycle eventual consistency is a type of consistency issue that relates to the existence or state of an AWS infrastructure component. For example, if you create a resource and immediately try to get information about it, some AWS services and operations will return a \"not found\" error. Depending on the service and general AWS load, these errors can be frequent or rare.

In order to avoid this issue, identify operations that make changes. Then, when calling any other operations that rely on the changes, account for the possibility that the AWS service has not yet fully realized them.

A typical example is creating an AWS component. After creation, when attempting to read the component's information, provide logic to retry the read if the AWS service returns a \"not found\" error.

The pattern that most resources should follow is to have the Create function return calling the Read function. This fills in computed attributes and ensures that the AWS service applied the configuration correctly. Add retry logic to the Read function to overcome the temporary condition on resource creation.

Note that for eventually consistent resources, \"not found\" errors can still occur in the Read function even after implementing Resource Lifecycle Waiters for the Create function.

// internal/service/example/wait.go (created if does not exist)\n\nconst (\n// Maximum amount of time to wait for Thing eventual consistency on creation\nThingCreationTimeout = 2 * time.Minute\n)\n
// internal/service/{service}/{thing}.go\n\nfunction ExampleThingCreate(d *schema.ResourceData, meta interface{}) error {\n// ...\nreturn ExampleThingRead(d, meta)\n}\n\nfunction ExampleThingRead(d *schema.ResourceData, meta interface{}) error {\nconn := meta.(*AWSClient).ExampleConn()\n\ninput := &example.OperationInput{/* ... */}\n\nvar output *example.OperationOutput\nerr := retry.RetryContext(ctx, ThingCreationTimeout, func() *retry.RetryError {\nvar err error\noutput, err = conn.Operation(input)\n\n// Retry on any API \"not found\" errors, but only on new resources.\nif d.IsNewResource() && tfawserr.ErrorCodeEquals(err, example.ErrCodeResourceNotFoundException) {\nreturn retry.RetryableError(err)\n}\n\nif err != nil {\nreturn retry.NonRetryableError(err)\n}\n\nreturn nil\n})\n\n// Retry AWS Go SDK operation if no response from automatic retries.\nif tfresource.TimedOut(err) {\noutput, err = exampleconn.Operation(input)\n}\n\n// Prevent confusing Terraform error messaging to operators by\n// Only ignoring API \"not found\" errors if not a new resource.\nif !d.IsNewResource() && tfawserr.ErrorCodeEquals(err, example.ErrCodeNoSuchEntityException) {\nlog.Printf(\"[WARN] Example Thing (%s) not found, removing from state\", d.Id())\nd.SetId(\"\")\nreturn nil\n}\n\nif err != nil {\nreturn fmt.Errorf(\"reading Example Thing (%s): %w\", d.Id(), err)\n}\n\n// Prevent panics.\nif output == nil {\nreturn fmt.Errorf(\"reading Example Thing (%s): empty response\", d.Id())\n}\n\n// ... refresh Terraform state as normal ...\nd.Set(\"arn\", output.Arn)\n}\n

Some other general guidelines are:

  • If the Create function uses retry.StateChangeConf, the underlying resource.RefreshStateFunc should return nil, \"\", nil instead of the API \"not found\" error. This way the StateChangeConf logic will automatically retry.
  • If the Create function uses retry.RetryContext(), the API \"not found\" error should be caught and return retry.RetryableError(err) to automatically retry.

In rare cases, it may be easier to duplicate all Read function logic in the Create function to handle all retries in one place.

"},{"location":"retries-and-waiters/#resource-attribute-value-waiters","title":"Resource Attribute Value Waiters","text":"

An emergent solution for handling eventual consistency with attribute values on updates is to introduce a custom retry.StateChangeConf and resource.RefreshStateFunc handlers. For example:

// internal/service/example/status.go (created if does not exist)\n\n// ThingAttribute fetches the Thing and its Attribute\nfunc ThingAttribute(conn *example.Example, id string) retry.StateRefreshFunc {\nreturn func() (interface{}, string, error) {\noutput, err := /* ... AWS Go SDK operation to fetch resource/value ... */\n\nif tfawserr.ErrCodeEquals(err, example.ErrCodeResourceNotFoundException) {\nreturn nil, \"\", nil\n}\n\nif err != nil {\nreturn nil, \"\", err\n}\n\nif output == nil {\nreturn nil, \"\", nil\n}\n\nreturn output, aws.StringValue(output.Attribute), nil\n}\n}\n
// internal/service/example/wait.go (created if does not exist)\n\nconst (\nThingAttributePropagationTimeout = 2 * time.Minute\n)\n\n// ThingAttributeUpdated is an attribute waiter for ThingAttribute\nfunc ThingAttributeUpdated(conn *example.Example, id string, expectedValue string) (*example.Thing, error) {\nstateConf := &retry.StateChangeConf{\nTarget:  []string{expectedValue},\nRefresh: ThingAttribute(conn, id),\nTimeout: ThingAttributePropagationTimeout,\n}\n\noutputRaw, err := stateConf.WaitForState()\n\nif output, ok := outputRaw.(*example.Thing); ok {\nreturn output, err\n}\n\nreturn nil, err\n}\n
// internal/service/{service}/{thing}.go\n\nfunction ExampleThingUpdate(d *schema.ResourceData, meta interface{}) error {\n// ...\n\nd.HasChange(\"attribute\") {\n// ... AWS Go SDK logic to update attribute ...\n\nif _, err := ThingAttributeUpdated(conn, d.Id(), d.Get(\"attribute\").(string)); err != nil {\nreturn fmt.Errorf(\"waiting for Example Thing (%s) attribute update: %w\", d.Id(), err)\n}\n}\n\n// ...\n}\n
"},{"location":"retries-and-waiters/#asynchronous-operations","title":"Asynchronous Operations","text":"

When you initiate a long-running operation, an AWS service may return a successful response immediately and continue working on the request asynchronously. A resource can track the status with a component-level field (e.g., CREATING, UPDATING, etc.) or an explicit tracking identifier.

Terraform resources should wait for these background operations to complete. Failing to do so can introduce incomplete state information and downstream errors in other resources. In rare scenarios involving very long-running operations, operators may request a flag to skip the waiting. However, these should only be implemented case-by-case to prevent those previously mentioned confusing issues.

"},{"location":"retries-and-waiters/#aws-go-sdk-waiters","title":"AWS Go SDK Waiters","text":"

In limited cases, the AWS service API model includes the information to automatically generate a waiter function in the AWS Go SDK for an operation. These are typically named with the prefix WaitUntil.... If available, these functions can be used for an initial resource implementation. For example:

if err := conn.WaitUntilEndpointInService(input); err != nil {\nreturn fmt.Errorf(\"waiting for Example Thing (%s) ...: %w\", d.Id(), err)\n}\n

If it is necessary to customize the timeouts and polling, we generally prefer using Resource Lifecycle Waiters instead since they are more commonly used throughout the codebase.

"},{"location":"retries-and-waiters/#resource-lifecycle-waiters","title":"Resource Lifecycle Waiters","text":"

Most of the codebase uses retry.StateChangeConf and retry.StateRefreshFunc handlers for tracking either component level status fields or explicit tracking identifiers. These should be placed in the internal/service/{SERVICE} package and split into separate functions. For example:

// internal/service/example/status.go (created if does not exist)\n\n// ThingStatus fetches the Thing and its Status\nfunc ThingStatus(conn *example.Example, id string) retry.StateRefreshFunc {\nreturn func() (interface{}, string, error) {\noutput, err := /* ... AWS Go SDK operation to fetch resource/status ... */\n\nif tfawserr.ErrCodeEquals(err, example.ErrCodeResourceNotFoundException) {\nreturn nil, \"\", nil\n}\n\nif err != nil {\nreturn nil, \"\", err\n}\n\nif output == nil {\nreturn nil, \"\", nil\n}\n\nreturn output, aws.StringValue(output.Status), nil\n}\n}\n
// internal/service/example/wait.go (created if does not exist)\n\nconst (\nThingCreationTimeout = 2 * time.Minute\nThingDeletionTimeout = 5 * time.Minute\n)\n\n// ThingCreated is a resource waiter for Thing creation\nfunc ThingCreated(conn *example.Example, id string) (*example.Thing, error) {\nstateConf := &retry.StateChangeConf{\nPending: []string{example.StatusCreating},\nTarget:  []string{example.StatusCreated},\nRefresh: ThingStatus(conn, id),\nTimeout: ThingCreationTimeout,\n}\n\noutputRaw, err := stateConf.WaitForState()\n\nif output, ok := outputRaw.(*example.Thing); ok {\nreturn output, err\n}\n\nreturn nil, err\n}\n\n// ThingDeleted is a resource waiter for Thing deletion\nfunc ThingDeleted(conn *example.Example, id string) (*example.Thing, error) {\nstateConf := &retry.StateChangeConf{\nPending: []string{example.StatusDeleting},\nTarget:  []string{}, // Use empty list if the resource disappears and does not have \"deleted\" status\nRefresh: ThingStatus(conn, id),\nTimeout: ThingDeletionTimeout,\n}\n\noutputRaw, err := stateConf.WaitForState()\n\nif output, ok := outputRaw.(*example.Thing); ok {\nreturn output, err\n}\n\nreturn nil, err\n}\n
// internal/service/{service}/{thing}.go\n\nfunction ExampleThingCreate(d *schema.ResourceData, meta interface{}) error {\n// ... AWS Go SDK logic to create resource ...\n\nif _, err := ThingCreated(conn, d.Id()); err != nil {\nreturn fmt.Errorf(\"waiting for Example Thing (%s) creation: %w\", d.Id(), err)\n}\n\nreturn ExampleThingRead(d, meta)\n}\n\nfunction ExampleThingDelete(d *schema.ResourceData, meta interface{}) error {\n// ... AWS Go SDK logic to delete resource ...\n\nif _, err := ThingDeleted(conn, d.Id()); err != nil {\nreturn fmt.Errorf(\"waiting for Example Thing (%s) deletion: %w\", d.Id(), err)\n}\n\nreturn ExampleThingRead(d, meta)\n}\n

Typically, the AWS Go SDK should include constants for various status field values (e.g., StatusCreating for CREATING). If not, create them in a file named internal/service/{SERVICE}/consts.go.

"},{"location":"running-and-writing-acceptance-tests/","title":"Running and Writing Acceptance Tests","text":"

Terraform includes an acceptance test harness that does most of the repetitive work involved in testing a resource. For additional information about testing Terraform Providers, see the SDKv2 documentation.

"},{"location":"running-and-writing-acceptance-tests/#acceptance-tests-often-cost-money-to-run","title":"Acceptance Tests Often Cost Money to Run","text":"

Our acceptance test suite creates real resources, and as a result they cost real money to run. Because the resources only exist for a short period of time, the total amount of money required is usually a relatively small amount. That said there are particular services which are very expensive to run and its important to be prepared for those costs.

Some services which can be cost prohibitive include (among others):

  • WorkSpaces
  • Glue
  • OpenSearch
  • RDS
  • ACM (Amazon Certificate Manager)
  • FSx
  • Kinesis Analytics
  • EC2
  • ElastiCache
  • Storage Gateway

We don't want financial limitations to be a barrier to contribution, so if you are unable to pay to run acceptance tests for your contribution, mention this in your pull request. We will happily accept \"best effort\" implementations of acceptance tests and run them for you on our side. This might mean that your PR takes a bit longer to merge, but it most definitely is not a blocker for contributions.

"},{"location":"running-and-writing-acceptance-tests/#running-an-acceptance-test","title":"Running an Acceptance Test","text":"

Acceptance tests can be run using the testacc target in the Terraform Makefile. The individual tests to run can be controlled using a regular expression. Prior to running the tests provider configuration details such as access keys must be made available as environment variables.

For example, to run an acceptance test against the Amazon Web Services provider, the following environment variables must be set:

# Using a profile\nexport AWS_PROFILE=...\n# Otherwise\nexport AWS_ACCESS_KEY_ID=...\nexport AWS_SECRET_ACCESS_KEY=...\nexport AWS_DEFAULT_REGION=...\n

Please note that the default region for the testing is us-west-2 and must be overridden via the AWS_DEFAULT_REGION environment variable, if necessary. This is especially important for testing AWS GovCloud (US), which requires:

export AWS_DEFAULT_REGION=us-gov-west-1\n

Tests can then be run by specifying a regular expression defining the tests to run and the package in which the tests are defined:

$ make testacc TESTS=TestAccCloudWatchDashboard_updateName PKG=cloudwatch\n==> Checking that code complies with gofmt requirements...\nTF_ACC=1 go test ./internal/service/cloudwatch/... -v -count 1 -parallel 20 -run=TestAccCloudWatchDashboard_updateName -timeout 180m\n=== RUN   TestAccCloudWatchDashboard_updateName\n=== PAUSE TestAccCloudWatchDashboard_updateName\n=== CONT  TestAccCloudWatchDashboard_updateName\n--- PASS: TestAccCloudWatchDashboard_updateName (25.33s)\nPASS\nok      github.com/hashicorp/terraform-provider-aws/internal/service/cloudwatch 25.387s\n

Entire resource test suites can be targeted by using the naming convention to write the regular expression. For example, to run all tests of the aws_cloudwatch_dashboard resource rather than just the updateName test, you can start testing like this:

$ make testacc TESTS=TestAccCloudWatchDashboard PKG=cloudwatch\n==> Checking that code complies with gofmt requirements...\nTF_ACC=1 go test ./internal/service/cloudwatch/... -v -count 1 -parallel 20 -run=TestAccCloudWatchDashboard -timeout 180m\n=== RUN   TestAccCloudWatchDashboard_basic\n=== PAUSE TestAccCloudWatchDashboard_basic\n=== RUN   TestAccCloudWatchDashboard_update\n=== PAUSE TestAccCloudWatchDashboard_update\n=== RUN   TestAccCloudWatchDashboard_updateName\n=== PAUSE TestAccCloudWatchDashboard_updateName\n=== CONT  TestAccCloudWatchDashboard_basic\n=== CONT  TestAccCloudWatchDashboard_updateName\n=== CONT  TestAccCloudWatchDashboard_update\n--- PASS: TestAccCloudWatchDashboard_basic (15.83s)\n--- PASS: TestAccCloudWatchDashboard_updateName (26.69s)\n--- PASS: TestAccCloudWatchDashboard_update (27.72s)\nPASS\nok      github.com/hashicorp/terraform-provider-aws/internal/service/cloudwatch 27.783s\n

Running acceptance tests requires version 0.12.26 or higher of the Terraform CLI to be installed.

For advanced developers, the acceptance testing framework accepts some additional environment variables that can be used to control Terraform CLI binary selection, logging, and other behaviors. See the SDKv2 documentation for more information.

Please Note: On macOS 10.14 and later (and some Linux distributions), the default user open file limit is 256. This may cause unexpected issues when running the acceptance testing since this can prevent various operations from occurring such as opening network connections to AWS. To view this limit, the ulimit -n command can be run. To update this limit, run ulimit -n 1024 (or higher).

"},{"location":"running-and-writing-acceptance-tests/#running-cross-account-tests","title":"Running Cross-Account Tests","text":"

Certain testing requires multiple AWS accounts. This additional setup is not typically required and the testing will return an error (shown below) if your current setup does not have the secondary AWS configuration:

$ make testacc TESTS=TestAccRDSInstance_DBSubnetGroupName_ramShared PKG=rds\nTF_ACC=1 go test ./internal/service/rds/... -v -count 1 -parallel 20 -run=TestAccRDSInstance_DBSubnetGroupName_ramShared -timeout 180m\n=== RUN   TestAccRDSInstance_DBSubnetGroupName_ramShared\n=== PAUSE TestAccRDSInstance_DBSubnetGroupName_ramShared\n=== CONT  TestAccRDSInstance_DBSubnetGroupName_ramShared\n    acctest.go:674: skipping test because at least one environment variable of [AWS_ALTERNATE_PROFILE AWS_ALTERNATE_ACCESS_KEY_ID] must be set. Usage: credentials for running acceptance testing in alternate AWS account.\n--- SKIP: TestAccRDSInstance_DBSubnetGroupName_ramShared (0.85s)\nPASS\nok      github.com/hashicorp/terraform-provider-aws/internal/service/rds        0.888s\n

Running these acceptance tests is the same as before, except the following additional AWS credential information is required:

# Using a profile\nexport AWS_ALTERNATE_PROFILE=...\n# Otherwise\nexport AWS_ALTERNATE_ACCESS_KEY_ID=...\nexport AWS_ALTERNATE_SECRET_ACCESS_KEY=...\n
"},{"location":"running-and-writing-acceptance-tests/#running-cross-region-tests","title":"Running Cross-Region Tests","text":"

Certain testing requires multiple AWS regions. Additional setup is not typically required because the testing defaults the second AWS region to us-east-1 and the third AWS region to us-east-2.

Running these acceptance tests is the same as before, but if you wish to override the second and third regions:

export AWS_ALTERNATE_REGION=...\nexport AWS_THIRD_REGION=...\n
"},{"location":"running-and-writing-acceptance-tests/#running-only-short-tests","title":"Running Only Short Tests","text":"

Some tests have been manually marked as long-running (longer than 300 seconds) and can be skipped using the -short flag. However, we are adding long-running guards little by little and many services have no guarded tests.

Where guards have been implemented, do not always skip long-running tests. However, for intermediate test runs during development, or to verify functionality unrelated to the specific long-running tests, skipping long-running tests makes work more efficient. We recommend that for the final test run before submitting a PR that you run affected tests without the -short flag.

If you want to run only short-running tests, you can use either one of these equivalent statements. Note the use of -short.

For example:

$ make testacc TESTS='TestAccECSTaskDefinition_' PKG=ecs TESTARGS=-short\n

Or:

$ TF_ACC=1 go test ./internal/service/ecs/... -v -count 1 -parallel 20 -run='TestAccECSTaskDefinition_' -short -timeout 180m\n
"},{"location":"running-and-writing-acceptance-tests/#writing-an-acceptance-test","title":"Writing an Acceptance Test","text":"

Terraform has a framework for writing acceptance tests which minimizes the amount of boilerplate code necessary to use common testing patterns. This guide is meant to augment the general SDKv2 documentation with Terraform AWS Provider specific conventions and helpers.

"},{"location":"running-and-writing-acceptance-tests/#anatomy-of-an-acceptance-test","title":"Anatomy of an Acceptance Test","text":"

This section describes in detail how the Terraform acceptance testing framework operates with respect to the Terraform AWS Provider. We recommend those unfamiliar with this provider, or Terraform resource testing in general, take a look here first to generally understand how we interact with AWS and the resource code to verify functionality.

The entry point to the framework is the resource.ParallelTest() function. This wraps our testing to work with the standard Go testing framework, while also preventing unexpected usage of AWS by requiring the TF_ACC=1 environment variable. This function accepts a TestCase parameter, which has all the details about the test itself. For example, this includes the test steps (TestSteps) and how to verify resource deletion in the API after all steps have been run (CheckDestroy).

Each TestStep proceeds by applying some Terraform configuration using the provider under test, and then verifying that results are as expected by making assertions using the provider API. It is common for a single test function to exercise both the creation of and updates to a single resource. Most tests follow a similar structure.

  1. Pre-flight checks are made to ensure that sufficient provider configuration is available to be able to proceed - for example in an acceptance test targeting AWS, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be set prior to running acceptance tests. This is common to all tests exercising a single provider.

Most assertion functions are defined out of band with the tests. This keeps the tests readable, and allows reuse of assertion functions across different tests of the same type of resource. The definition of a complete test looks like this:

func TestAccCloudWatchDashboard_basic(t *testing.T) {\nctx := acctest.Context(t)\nvar dashboard cloudwatch.GetDashboardOutput\nrInt := acctest.RandInt()\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, cloudwatch.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckDashboardDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccDashboardConfig(rInt),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckDashboardExists(ctx, \"aws_cloudwatch_dashboard.foobar\", &dashboard),\nresource.TestCheckResourceAttr(\"aws_cloudwatch_dashboard.foobar\", \"dashboard_name\", testAccDashboardName(rInt)),\n),\n},\n},\n})\n}\n

When executing the test, the following steps are taken for each TestStep:

  1. The Terraform configuration required for the test is applied. This is responsible for configuring the resource under test, and any dependencies it may have. For example, to test the aws_cloudwatch_dashboard resource, a valid configuration with the requisite fields is required. This results in configuration which looks like this:

    resource \"aws_cloudwatch_dashboard\" \"foobar\" {\ndashboard_name = \"terraform-test-dashboard-%[1]d\"\ndashboard_body = <<EOF\n  {\n    \"widgets\": [{\n      \"type\": \"text\",\n      \"x\": 0,\n      \"y\": 0,\n      \"width\": 6,\n      \"height\": 6,\n      \"properties\": {\n        \"markdown\": \"Hi there from Terraform: CloudWatch\"\n      }\n    }]\n  }\n  EOF\n}\n
  2. Assertions are run using the provider API. These use the provider API directly rather than asserting against the resource state. For example, to verify that the aws_cloudwatch_dashboard described above was created successfully, a test function like this is used:

    func testAccCheckDashboardExists(ctx context.Context, n string, dashboard *cloudwatch.GetDashboardOutput) resource.TestCheckFunc {\nreturn func(s *terraform.State) error {\nrs, ok := s.RootModule().Resources[n]\nif !ok {\nreturn fmt.Errorf(\"Not found: %s\", n)\n}\n\nconn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx)\nparams := cloudwatch.GetDashboardInput{\nDashboardName: aws.String(rs.Primary.ID),\n}\n\nresp, err := conn.GetDashboardWithContext(ctx, &params)\nif err != nil {\nreturn err\n}\n\n*dashboard = *resp\n\nreturn nil\n}\n}\n

Notice that the only information used from the Terraform state is the ID of the resource. For computed properties, we instead assert that the value saved in the Terraform state was the expected value if possible. The testing framework provides helper functions for several common types of check - for example:

```go\nresource.TestCheckResourceAttr(\"aws_cloudwatch_dashboard.foobar\", \"dashboard_name\", testAccDashboardName(rInt)),\n```\n
  1. The resources created by the test are destroyed. This step happens automatically, and is the equivalent of calling terraform destroy.

  2. Assertions are made against the provider API to verify that the resources have indeed been removed. If these checks fail, the test fails and reports \"dangling resources\". The code to ensure that the aws_cloudwatch_dashboard shown above has been destroyed looks like this:

    func testAccCheckDashboardDestroy(ctx context.Context) resource.TestCheckFunc {\nreturn func(s *terraform.State) error {\nconn := acctest.Provider.Meta().(*conns.AWSClient).CloudWatchConn(ctx)\n\nfor _, rs := range s.RootModule().Resources {\nif rs.Type != \"aws_cloudwatch_dashboard\" {\ncontinue\n}\n\nparams := cloudwatch.GetDashboardInput{\nDashboardName: aws.String(rs.Primary.ID),\n}\n\n_, err := conn.GetDashboardWithContext(ctx, &params)\nif err == nil {\nreturn fmt.Errorf(\"Dashboard still exists: %s\", rs.Primary.ID)\n}\nif !isDashboardNotFoundErr(err) {\nreturn err\n}\n}\n\nreturn nil\n}\n}\n

These functions usually test only for the resource directly under test.

"},{"location":"running-and-writing-acceptance-tests/#resource-acceptance-testing","title":"Resource Acceptance Testing","text":"

Most resources that implement standard Create, Read, Update, and Delete functionality should follow the pattern below. Each test type has a section that describes them in more detail:

  • basic: This represents the bare minimum verification that the resource can be created, read, deleted, and optionally imported.
  • disappears: A test that verifies Terraform will offer to recreate a resource if it is deleted outside of Terraform (e.g., via the Console) instead of returning an error that it cannot be found.
  • Per Attribute: A test that verifies the resource with a single additional argument can be created, read, optionally updated (or force resource recreation), deleted, and optionally imported.

The leading sections below highlight additional recommended patterns.

"},{"location":"running-and-writing-acceptance-tests/#test-configurations","title":"Test Configurations","text":"

Most of the existing test configurations you will find in the Terraform AWS Provider are written in the following function-based style:

func TestAccExampleThing_basic(t *testing.T) {\n// ... omitted for brevity ...\n\nresource.ParallelTest(t, resource.TestCase{\n// ... omitted for brevity ...\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleThingConfig(),\n// ... omitted for brevity ...\n},\n},\n})\n}\n\nfunc testAccExampleThingConfig() string {\nreturn `\nresource \"aws_example_thing\" \"test\" {\n  # ... omitted for brevity ...\n}\n`\n}\n

Even when no values need to be passed in to the test configuration, we have found this setup to be the most flexible for allowing that to be easily implemented. Any configurable values are handled via fmt.Sprintf(). Using text/template or other templating styles is explicitly forbidden.

For consistency, resources in the test configuration should be named resource \"...\" \"test\" unless multiple of that resource are necessary.

We discourage re-using test configurations across test files (except for some common configuration helpers we provide) as it is much harder to discover potential testing regressions.

Please also note that the newline on the first line of the configuration (before resource) and the newline after the last line of configuration (after }) are important to allow test configurations to be easily combined without generating Terraform configuration language syntax errors.

"},{"location":"running-and-writing-acceptance-tests/#test-configuration-independence","title":"Test Configuration Independence","text":"

Across the entire provider, all test configurations should be as indepedent from each other as possible. For example, a common place this concept comes up is with the default VPC. Since we have tests that reconfigure the default VPC, if your configuration requires a VPC, it should not rely on the default VPC. Instead, include a VPC that will be created and destroyed as part of the test.

Make sure that your test configuration:

  1. Includes everything required for Terraform to run the test
  2. Does not assume that any user-managed infrastructure will be in place, such as S3 buckets, IAM roles, KMS keys, VPCs, subnets, etc.
"},{"location":"running-and-writing-acceptance-tests/#combining-test-configurations","title":"Combining Test Configurations","text":"

We include a helper function, acctest.ConfigCompose() for iteratively building and chaining test configurations together. It accepts any number of configurations to combine them. This simplifies a single resource's testing by allowing the creation of a \"base\" test configuration for all the other test configurations (if necessary) and also allows the maintainers to curate common configurations. Each of these is described in more detail in below sections.

Please note that we do discourage excessive chaining of configurations such as implementing multiple layers of \"base\" configurations. Usually these configurations are harder for maintainers and other future readers to understand due to the multiple levels of indirection.

"},{"location":"running-and-writing-acceptance-tests/#base-test-configurations","title":"Base Test Configurations","text":"

If a resource requires the same Terraform configuration as a prerequisite for all test configurations, then a common pattern is implementing a \"base\" test configuration that is combined with each test configuration.

For example:

func testAccExampleThingConfigBase() string {\nreturn `\nresource \"aws_iam_role\" \"test\" {\n  # ... omitted for brevity ...\n}\n\nresource \"aws_iam_role_policy\" \"test\" {\n  # ... omitted for brevity ...\n}\n`\n}\n\nfunc testAccExampleThingConfig() string {\nreturn acctest.ConfigCompose(\ntestAccExampleThingConfigBase(),\n`\nresource \"aws_example_thing\" \"test\" {\n  # ... omitted for brevity ...\n}\n`)\n}\n
"},{"location":"running-and-writing-acceptance-tests/#available-common-test-configurations","title":"Available Common Test Configurations","text":"

These test configurations are typical implementations we have found or allow testing to implement best practices easier, since the Terraform AWS Provider testing is expected to run against various AWS Regions and Partitions.

  • acctest.AvailableEC2InstanceTypeForRegion(\"type1\", \"type2\", ...): Typically used to replace hardcoded EC2 Instance Types. Uses aws_ec2_instance_type_offering data source to return an available EC2 Instance Type in preferred ordering. Reference the instance type via: data.aws_ec2_instance_type_offering.available.instance_type. Use acctest.AvailableEC2InstanceTypeForRegionNamed(\"name\", \"type1\", \"type2\", ...) to specify a name for the data source
  • acctest.ConfigLatestAmazonLinuxHVMEBSAMI(): Typically used to replace hardcoded EC2 Image IDs (ami-12345678). Uses aws_ami data source to find the latest Amazon Linux image. Reference the AMI ID via: data.aws_ami.amzn-ami-minimal-hvm-ebs.id
"},{"location":"running-and-writing-acceptance-tests/#randomized-naming","title":"Randomized Naming","text":"

For AWS resources that require unique naming, the tests should implement a randomized name, typically coded as a rName variable in the test and passed as a parameter to creating the test configuration.

For example:

func TestAccExampleThing_basic(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\n// ... omitted for brevity ...\n\nresource.ParallelTest(t, resource.TestCase{\n// ... omitted for brevity ...\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleThingConfigName(rName),\n// ... omitted for brevity ...\n},\n},\n})\n}\n\nfunc testAccExampleThingConfigName(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_example_thing\" \"test\" {\n  name = %[1]q\n}\n`, rName)\n}\n

Typically the rName is always the first argument to the test configuration function, if used, for consistency.

Note that if rName (or any other variable) is used multiple times in the fmt.Sprintf() statement, do not repeat rName in the fmt.Sprintf() arguments. Using fmt.Sprintf(..., rName, rName), for example, would not be correct. Instead, use the indexed %[1]q (or %[x]q, %[x]s, %[x]t, or %[x]d, where x represents the index number) verb multiple times. For example:

func testAccExampleThingConfigName(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_example_thing\" \"test\" {\n  name = %[1]q\n\n  tags = {\n    Name = %[1]q\n  }\n}\n`, rName)\n}\n
"},{"location":"running-and-writing-acceptance-tests/#other-recommended-variables","title":"Other Recommended Variables","text":"

We also typically recommend saving a resourceName variable in the test that contains the resource reference, e.g., aws_example_thing.test, which is repeatedly used in the checks.

For example:

func TestAccExampleThing_basic(t *testing.T) {\nctx := acctest.Context(t)\n// ... omitted for brevity ...\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\n// ... omitted for brevity ...\nSteps: []resource.TestStep{\n{\n// ... omitted for brevity ...\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleThingExists(ctx, resourceName),\nacctest.CheckResourceAttrRegionalARN(resourceName, \"arn\", \"example\", fmt.Sprintf(\"thing/%s\", rName)),\nresource.TestCheckResourceAttr(resourceName, \"description\", \"\"),\nresource.TestCheckResourceAttr(resourceName, \"name\", rName),\n),\n},\n{\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n},\n})\n}\n\n// below all TestAcc functions\n\nfunc testAccExampleThingConfigName(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_example_thing\" \"test\" {\n  name = %[1]q\n}\n`, rName)\n}\n
"},{"location":"running-and-writing-acceptance-tests/#basic-acceptance-tests","title":"Basic Acceptance Tests","text":"

Usually this test is implemented first. The test configuration should contain only required arguments (Required: true attributes) and it should check the values of all read-only attributes (Computed: true without Optional: true). If the resource supports it, it verifies import. It should NOT perform other TestStep such as updates or verify recreation.

These are typically named TestAcc{SERVICE}{THING}_basic, e.g., TestAccCloudWatchDashboard_basic

For example:

func TestAccExampleThing_basic(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckExampleThingDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleThingConfigName(rName),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleThingExists(ctx, resourceName),\nacctest.CheckResourceAttrRegionalARN(resourceName, \"arn\", \"example\", fmt.Sprintf(\"thing/%s\", rName)),\nresource.TestCheckResourceAttr(resourceName, \"description\", \"\"),\nresource.TestCheckResourceAttr(resourceName, \"name\", rName),\n),\n},\n{\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n},\n})\n}\n\n// below all TestAcc functions\n\nfunc testAccExampleThingConfigName(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_example_thing\" \"test\" {\n  name = %[1]q\n}\n`, rName)\n}\n
"},{"location":"running-and-writing-acceptance-tests/#prechecks","title":"PreChecks","text":"

Acceptance test cases have a PreCheck. The PreCheck ensures that the testing environment meets certain preconditions. If the environment does not meet the preconditions, Go skips the test. Skipping a test avoids reporting a failure and wasting resources where the test cannot succeed.

Here is an example of the default PreCheck:

func TestAccExampleThing_basic(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:     func() { acctest.PreCheck(ctx, t) },\n// ... additional checks follow ...\n})\n}\n

Extend the default PreCheck by adding calls to functions in the anonymous PreCheck function. The functions can be existing functions in the provider or custom functions you add for new capabilities.

"},{"location":"running-and-writing-acceptance-tests/#standard-provider-prechecks","title":"Standard Provider PreChecks","text":"

If you add a new test that has preconditions which are checked by an existing provider function, use that standard PreCheck instead of creating a new one. Some existing tests are missing standard PreChecks and you can help by adding them where appropriate.

These are some of the standard provider PreChecks:

  • acctest.PreCheckRegion(t *testing.T, regions ...string) checks that the test region is one of the specified AWS Regions.
  • acctest.PreCheckRegionNot(t *testing.T, regions ...string) checks that the test region is not one of the specified AWS Regions.
  • acctest.PreCheckAlternateRegionIs(t *testing.T, region string) checks that the alternate test region is the specified AWS Region.
  • acctest.PreCheckPartition(t *testing.T, partition string) checks that the test partition is the specified partition.
  • acctest.PreCheckPartitionNot(t *testing.T, partitions ...string) checks that the test partition is not one of the specified partitions.
  • acctest.PreCheckPartitionHasService(t *testing.T, serviceID string) checks whether the current partition lists the service as part of its offerings. Note: AWS may not add new or public preview services to the service list immediately. This function will return a false positive in that case.
  • acctest.PreCheckOrganizationsAccount(ctx context.Context, t *testing.T) checks whether the current account can perform AWS Organizations tests.
  • acctest.PreCheckAlternateAccount(t *testing.T) checks whether the environment is set up for tests across accounts.
  • acctest.PreCheckMultipleRegion(t *testing.T, regions int) checks whether the environment is set up for tests across regions.

This is an example of using a standard PreCheck function. For an established service, such as WAF or FSx, use acctest.PreCheckPartitionHasService() and the service endpoint ID to check that a partition supports the service.

func TestAccExampleThing_basic(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:     func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, waf.EndpointsID) },\n// ... additional checks follow ...\n})\n}\n
"},{"location":"running-and-writing-acceptance-tests/#custom-prechecks","title":"Custom PreChecks","text":"

In situations where standard PreChecks do not test for the required preconditions, create a custom PreCheck.

Below is an example of adding a custom PreCheck function. For a new or preview service that AWS does not include in the partition service list yet, you can verify the existence of the service with a simple read-only request (e.g., list all X service things). (For acceptance tests of established services, use acctest.PreCheckPartitionHasService() instead.)

func TestAccExampleThing_basic(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:     func() { acctest.PreCheck(ctx, t), testAccPreCheckExample(ctx, t) },\n// ... additional checks follow ...\n})\n}\n\nfunc testAccPreCheckExample(ctx context.Context, t *testing.T) {\nconn := acctest.Provider.Meta().(*conns.AWSClient).ExampleConn(ctx)\ninput := &example.ListThingsInput{}\n_, err := conn.ListThingsWithContext(ctx, input)\nif testAccPreCheckSkipError(err) {\nt.Skipf(\"skipping acceptance testing: %s\", err)\n}\nif err != nil {\nt.Fatalf(\"unexpected PreCheck error: %s\", err)\n}\n}\n
"},{"location":"running-and-writing-acceptance-tests/#errorchecks","title":"ErrorChecks","text":"

Acceptance test cases have an ErrorCheck. The ErrorCheck provides a chance to take a look at errors before the test fails. While most errors should result in test failure, some should not. For example, an error that indicates an API operation is not supported in a particular region should cause the test to skip instead of fail. Since errors should flow through the ErrorCheck, do not handle the vast majority of failing conditions. Instead, in ErrorCheck, focus on the rare errors that should cause a test to skip, or in other words, be ignored.

"},{"location":"running-and-writing-acceptance-tests/#common-errorcheck","title":"Common ErrorCheck","text":"

In many situations, the common ErrorCheck is sufficient. It will skip tests for several normal occurrences such as when AWS reports a feature is not supported in the current region.

Here is an example of the common ErrorCheck:

func TestAccExampleThing_basic(t *testing.T) {\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\n// PreCheck\nErrorCheck:   acctest.ErrorCheck(t, service.EndpointsID),\n// ... additional checks follow ...\n})\n}\n
"},{"location":"running-and-writing-acceptance-tests/#service-specific-errorchecks","title":"Service-Specific ErrorChecks","text":"

However, some services have special conditions that aren't caught by the common ErrorCheck. In these cases, you can create a service-specific ErrorCheck.

To add a service-specific ErrorCheck, follow these steps:

  1. Make sure there is not already an ErrorCheck for the service you have in mind. For example, search the codebase for acctest.RegisterServiceErrorCheckFunc(service.EndpointsID replacing \"service\" with the package name of the service you're working on (e.g., ec2). If there is already an ErrorCheck for the service, add to the existing service-specific ErrorCheck.
  2. Create the service-specific ErrorCheck in an _test.go file for the service. See the example below.
  3. Register the new service-specific ErrorCheck in the init() at the top of the _test.go file. See the example below.

An example of adding a service-specific ErrorCheck:

// just after the imports, create or add to the init() function\nfunc init() {\nacctest.RegisterServiceErrorCheck(service.EndpointsID, testAccErrorCheckSkipService)\n}\n\n// ... additional code and tests ...\n\n// this is the service-specific ErrorCheck\nfunc testAccErrorCheckSkipService(t *testing.T) resource.ErrorCheckFunc {\nreturn acctest.ErrorCheckSkipMessagesContaining(t,\n\"Error message specific to the service that indicates unsupported features\",\n\"You can include from one to many portions of error messages\",\n\"Be careful to not inadvertently capture errors that should not be skipped\",\n)\n}\n
"},{"location":"running-and-writing-acceptance-tests/#long-running-test-guards","title":"Long-Running Test Guards","text":"

For any acceptance tests that typically run longer than 300 seconds (5 minutes), add a -short test guard at the top of the test function.

For example:

func TestAccExampleThing_longRunningTest(t *testing.T) {\nif testing.Short() {\nt.Skip(\"skipping long-running test in short mode\")\n}\n\n// ... omitted for brevity ...\n\nresource.ParallelTest(t, resource.TestCase{\n// ... omitted for brevity ...\n})\n}\n

When running acceptances tests, tests with these guards can be skipped using the Go -short flag. See Running Only Short Tests for examples.

"},{"location":"running-and-writing-acceptance-tests/#disappears-acceptance-tests","title":"Disappears Acceptance Tests","text":"

This test is generally implemented second. It is straightforward to setup once the basic test is passing since it can reuse that test configuration. It prevents a common bug report with Terraform resources that error when they can not be found (e.g., deleted outside Terraform).

These are typically named TestAcc{SERVICE}{THING}_disappears, e.g., TestAccCloudWatchDashboard_disappears

For example:

func TestAccExampleThing_disappears(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckExampleThingDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleThingConfigName(rName),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleThingExists(ctx, resourceName, &job),\nacctest.CheckResourceDisappears(ctx, acctest.Provider, ResourceExampleThing(), resourceName),\n),\nExpectNonEmptyPlan: true,\n},\n},\n})\n}\n

If this test does fail, the fix for this is generally adding error handling immediately after the Read API call that catches the error and tells Terraform to remove the resource before returning the error:

output, err := conn.GetThing(input)\n\nif !d.IsNewResource() && tfresource.NotFound(err) {\nlog.Printf(\"[WARN] Example Thing (%s) not found, removing from state\", d.Id())\nd.SetId(\"\")\nreturn nil\n}\n\nif err != nil {\nreturn fmt.Errorf(\"reading Example Thing (%s): %w\", d.Id(), err)\n}\n

For children resources that are encapsulated by a parent resource, it is also preferable to verify that removing the parent resource will not generate an error either. These are typically named TestAcc{SERVICE}{THING}_disappears_{PARENT}, e.g., TestAccRoute53ZoneAssociation_disappears_Vpc

func TestAccExampleChildThing_disappears_ParentThing(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nparentResourceName := \"aws_example_parent_thing.test\"\nresourceName := \"aws_example_child_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckExampleChildThingDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleThingConfigName(rName),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleThingExists(ctx, resourceName),\nacctest.CheckResourceDisappears(ctx, acctest.Provider, ResourceExampleParentThing(), parentResourceName),\n),\nExpectNonEmptyPlan: true,\n},\n},\n})\n}\n
"},{"location":"running-and-writing-acceptance-tests/#per-attribute-acceptance-tests","title":"Per Attribute Acceptance Tests","text":"

These are typically named TestAcc{SERVICE}{THING}_{ATTRIBUTE}, e.g., TestAccCloudWatchDashboard_Name

For example:

func TestAccExampleThing_Description(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckExampleThingDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleThingConfigDescription(rName, \"description1\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleThingExists(ctx, resourceName),\nresource.TestCheckResourceAttr(resourceName, \"description\", \"description1\"),\n),\n},\n{\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n{\nConfig: testAccExampleThingConfigDescription(rName, \"description2\"),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleThingExists(ctx, resourceName),\nresource.TestCheckResourceAttr(resourceName, \"description\", \"description2\"),\n),\n},\n},\n})\n}\n\n// below all TestAcc functions\n\nfunc testAccExampleThingConfigDescription(rName string, description string) string {\nreturn fmt.Sprintf(`\nresource \"aws_example_thing\" \"test\" {\n  description = %[2]q\n  name        = %[1]q\n}\n`, rName, description)\n}\n
"},{"location":"running-and-writing-acceptance-tests/#cross-account-acceptance-tests","title":"Cross-Account Acceptance Tests","text":"

When testing requires AWS infrastructure in a second AWS account, the below changes to the normal setup will allow the management or reference of resources and data sources across accounts:

  • In the PreCheck function, include acctest.PreCheckOrganizationsAccount(ctx, t) to ensure a standardized set of information is required for cross-account testing credentials
  • Switch usage of ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories to ProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t)
  • Add acctest.ConfigAlternateAccountProvider() to the test configuration and use provider = awsalternate for cross-account resources. The resource that is the focus of the acceptance test should not use the alternate provider identification to simplify the testing setup.
  • For any TestStep that includes ImportState: true, add the Config that matches the previous TestStep Config

An example acceptance test implementation can be seen below:

func TestAccExample_basic(t *testing.T) {\nctx := acctest.Context(t)\nresourceName := \"aws_example.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck: func() {\nacctest.PreCheck(ctx, t)\nacctest.PreCheckOrganizationsAccount(ctx, t)\n},\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5FactoriesAlternate(ctx, t),\nCheckDestroy:             testAccCheckExampleDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleConfig(),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleExists(ctx, resourceName),\n// ... additional checks ...\n),\n},\n{\nConfig:            testAccExampleConfig(),\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n},\n})\n}\n\nfunc testAccExampleConfig() string {\nreturn acctest.ConfigAlternateAccountProvider() + fmt.Sprintf(`\n# Cross account resources should be handled by the cross account provider.\n# The standardized provider block to use is awsalternate as seen below.\nresource \"aws_cross_account_example\" \"test\" {\n  provider = awsalternate\n\n  # ... configuration ...\n}\n\n# The resource that is the focus of the testing should be handled by the default provider,\n# which is automatically done by not specifying the provider configuration in the resource.\nresource \"aws_example\" \"test\" {\n  # ... configuration ...\n}\n`)\n}\n

Searching for usage of acctest.PreCheckOrganizationsAccount in the codebase will yield real world examples of this setup in action.

"},{"location":"running-and-writing-acceptance-tests/#cross-region-acceptance-tests","title":"Cross-Region Acceptance Tests","text":"

When testing requires AWS infrastructure in a second or third AWS region, the below changes to the normal setup will allow the management or reference of resources and data sources across regions:

  • In the PreCheck function, include acctest.PreCheckMultipleRegion(t, ###) to ensure a standardized set of information is required for cross-region testing configuration. If the infrastructure in the second AWS region is also in a second AWS account also include acctest.PreCheckOrganizationsAccount(ctx, t)
  • Switch usage of ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories to ProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(ctx, t, 2) (where the last parameter is number of regions, 2 or 3)
  • Add acctest.ConfigMultipleRegionProvider(###) to the test configuration and use provider = awsalternate (and potentially provider = awsthird) for cross-region resources. The resource that is the focus of the acceptance test should not use the alternative providers to simplify the testing setup. If the infrastructure in the second AWS region is also in a second AWS account use testAccAlternateAccountAlternateRegionProviderConfig() (EC2) instead
  • For any TestStep that includes ImportState: true, add the Config that matches the previous TestStep Config

An example acceptance test implementation can be seen below:

func TestAccExample_basic(t *testing.T) {\nctx := acctest.Context(t)\nvar providers []*schema.Provider\nresourceName := \"aws_example.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck: func() {\nacctest.PreCheck(ctx, t)\nacctest.PreCheckMultipleRegion(t, 2)\n},\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5FactoriesMultipleRegions(ctx, t, 2),\nCheckDestroy:             testAccCheckExampleDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleConfig(),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleExists(ctx, resourceName),\n// ... additional checks ...\n),\n},\n{\nConfig:            testAccExampleConfig(),\nResourceName:      resourceName,\nImportState:       true,\nImportStateVerify: true,\n},\n},\n})\n}\n\nfunc testAccExampleConfig() string {\nreturn acctest.ConfigMultipleRegionProvider(2) + fmt.Sprintf(`\n# Cross region resources should be handled by the cross region provider.\n# The standardized provider is awsalternate as seen below.\nresource \"aws_cross_region_example\" \"test\" {\n  provider = awsalternate\n\n  # ... configuration ...\n}\n\n# The resource that is the focus of the testing should be handled by the default provider,\n# which is automatically done by not specifying the provider configuration in the resource.\nresource \"aws_example\" \"test\" {\n  # ... configuration ...\n}\n`)\n}\n

Searching for usage of acctest.PreCheckMultipleRegion in the codebase will yield real world examples of this setup in action.

"},{"location":"running-and-writing-acceptance-tests/#acceptance-test-concurrency","title":"Acceptance Test Concurrency","text":"

Certain AWS service APIs allow a limited number of a certain component, while the acceptance testing runs at a default concurrency of twenty tests at a time. For example as of this writing, the SageMaker service only allows one SageMaker Domain per AWS Region. Running the tests with the default concurrency will fail with API errors relating to the component quota being exceeded.

When encountering these types of components, the acceptance testing can be setup to limit the available concurrency of that particular component. When limited to one component at a time, this may also be referred to as serializing the acceptance tests.

To convert to serialized (one test at a time) acceptance testing:

  • Convert all existing capital T test functions with the limited component to begin with a lowercase t, e.g., TestAccSageMakerDomain_basic becomes testDomain_basic. This will prevent the test framework from executing these tests directly as the prefix Test is required.
    • In each of these test functions, convert resource.ParallelTest to resource.Test
  • Create a capital T TestAcc{Service}{Thing}_serial test function that then references all the lowercase t test functions. If multiple test files are referenced, this new test be created in a new shared file such as internal/service/{SERVICE}/{SERVICE}_test.go. The contents of this test can be setup like the following:
func TestAccExampleThing_serial(t *testing.T) {\nt.Parallel()\n\ntestCases := map[string]map[string]func(t *testing.T){\n\"Thing\": {\n\"basic\":        testAccThing_basic,\n\"disappears\":   testAccThing_disappears,\n// ... potentially other resource tests ...\n},\n// ... potentially other top level resource test groups ...\n}\n\nacctest.RunSerialTests2Levels(t, testCases, 0)\n}\n

NOTE: Future iterations of these acceptance testing concurrency instructions will include the ability to handle more than one component at a time including service quota lookup, if supported by the service API.

"},{"location":"running-and-writing-acceptance-tests/#data-source-acceptance-testing","title":"Data Source Acceptance Testing","text":"

Writing acceptance testing for data sources is similar to resources, with the biggest changes being:

  • Adding DataSource to the test and configuration naming, such as TestAccExampleThingDataSource_Filter
  • The basic test may be named after the easiest lookup attribute instead, e.g., TestAccExampleThingDataSource_Name
  • No disappears testing
  • Almost all checks should be done with resource.TestCheckResourceAttrPair() to compare the data source attributes to the resource attributes
  • The usage of an additional dataSourceName variable to store a data source reference, e.g., data.aws_example_thing.test

Data sources testing should still use the CheckDestroy function of the resource, just to continue verifying that there are no dangling AWS resources after a test is run.

Please note that we do not recommend re-using test configurations between resources and their associated data source as it is harder to discover testing regressions. Authors are encouraged to potentially implement similar \"base\" configurations though.

For example:

func TestAccExampleThingDataSource_Name(t *testing.T) {\nctx := acctest.Context(t)\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\ndataSourceName := \"data.aws_example_thing.test\"\nresourceName := \"aws_example_thing.test\"\n\nresource.ParallelTest(t, resource.TestCase{\nPreCheck:                 func() { acctest.PreCheck(ctx, t) },\nErrorCheck:               acctest.ErrorCheck(t, service.EndpointsID),\nProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,\nCheckDestroy:             testAccCheckExampleThingDestroy(ctx),\nSteps: []resource.TestStep{\n{\nConfig: testAccExampleThingDataSourceConfigName(rName),\nCheck: resource.ComposeTestCheckFunc(\ntestAccCheckExampleThingExists(ctx, resourceName),\nresource.TestCheckResourceAttrPair(resourceName, \"arn\", dataSourceName, \"arn\"),\nresource.TestCheckResourceAttrPair(resourceName, \"description\", dataSourceName, \"description\"),\nresource.TestCheckResourceAttrPair(resourceName, \"name\", dataSourceName, \"name\"),\n),\n},\n},\n})\n}\n\n// below all TestAcc functions\n\nfunc testAccExampleThingDataSourceConfigName(rName string) string {\nreturn fmt.Sprintf(`\nresource \"aws_example_thing\" \"test\" {\n  name = %[1]q\n}\n\ndata \"aws_example_thing\" \"test\" {\n  name = aws_example_thing.test.name\n}\n`, rName)\n}\n
"},{"location":"running-and-writing-acceptance-tests/#acceptance-test-sweepers","title":"Acceptance Test Sweepers","text":"

When running the acceptance tests, especially when developing or troubleshooting Terraform resources, its possible for code bugs or other issues to prevent the proper destruction of AWS infrastructure. To prevent lingering resources from consuming quota or causing unexpected billing, the Terraform Plugin SDK supports the test sweeper framework to clear out an AWS region of all resources. This section is meant to augment the SDKv2 documentation on test sweepers with Terraform AWS Provider specific details.

"},{"location":"running-and-writing-acceptance-tests/#running-test-sweepers","title":"Running Test Sweepers","text":"

WARNING: Test Sweepers will destroy AWS infrastructure and backups in the target AWS account and region! These are designed to override any API deletion protection. Never run these outside a development AWS account that should be completely empty of resources.

To run the sweepers for all resources in us-west-2 and us-east-1 (default testing regions):

$ make sweep\n

To run a specific resource sweeper:

$ SWEEPARGS=-sweep-run=aws_example_thing make sweep\n

To run sweepers with an assumed role, use the following additional environment variables:

  • TF_AWS_ASSUME_ROLE_ARN - Required.
  • TF_AWS_ASSUME_ROLE_DURATION - Optional, defaults to 1 hour (3600).
  • TF_AWS_ASSUME_ROLE_EXTERNAL_ID - Optional.
  • TF_AWS_ASSUME_ROLE_SESSION_NAME - Optional.
"},{"location":"running-and-writing-acceptance-tests/#sweeper-checklists","title":"Sweeper Checklists","text":"
  • Add Resource Sweeper Implementation: See Writing Test Sweepers.
  • Add Service To Sweeper List: Once a sweep.go file is present in the service subdirectory, run make gen to regenerate the list of imports in internal/sweep/sweep_test.go.
"},{"location":"running-and-writing-acceptance-tests/#writing-test-sweepers","title":"Writing Test Sweepers","text":"

Sweeper logic should be written to a file called sweep.go in the appropriate service subdirectory (internal/service/{serviceName}). This file should include the following build tags above the package declaration:

//go:build sweep\n// +build sweep\n\npackage example\n

Next, initialize the resource into the test sweeper framework:

func init() {\nresource.AddTestSweepers(\"aws_example_thing\", &resource.Sweeper{\nName: \"aws_example_thing\",\nF:    sweepThings,\n// Optionally\nDependencies: []string{\n\"aws_other_thing\",\n},\n})\n}\n

Then add the actual implementation. Preferably, if a paginated SDK call is available:

func sweepThings(region string) error {\nctx := sweep.Context(region)\nclient, err := sweep.SharedRegionalSweepClient(ctx, region)\n\nif err != nil {\nreturn fmt.Errorf(\"getting client: %w\", err)\n}\n\nconn := client.ExampleConn(ctx)\nsweepResources := make([]sweep.Sweepable, 0)\nvar errs *multierror.Error\n\ninput := &example.ListThingsInput{}\n\nerr = conn.ListThingsPages(input, func(page *example.ListThingsOutput, lastPage bool) bool {\nif page == nil {\nreturn !lastPage\n}\n\nfor _, thing := range page.Things {\nr := ResourceThing()\nd := r.Data(nil)\n\nid := aws.StringValue(thing.Id)\nd.SetId(id)\n\n// Perform resource specific pre-sweep setup.\n// For example, you may need to perform one or more of these types of pre-sweep tasks, specific to the resource:\n//\n// err := sdk.ReadResource(ctx, r, d, client) // fill in data\n// d.Set(\"skip_final_snapshot\", true)           // set an argument in order to delete\n\n// This \"if\" is only needed if the pre-sweep setup can produce errors.\n// Otherwise, do not include it.\nif err != nil {\nerr := fmt.Errorf(\"reading Example Thing (%s): %w\", id, err)\nerrs = multierror.Append(errs, err)\ncontinue\n}\n\nsweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client))\n}\n\nreturn !lastPage\n})\n\nif sweep.SkipSweepError(err) {\nlog.Printf(\"[WARN] Skipping Example Thing sweep for %s: %s\", region, errs)\nreturn nil\n}\nif err != nil {\nerrs = multierror.Append(errs, fmt.Errorf(\"listing Example Things for %s: %w\", region, err))\n}\n\nif err := sweep.SweepOrchestrator(sweepResources); err != nil {\nerrs = multierror.Append(errs, fmt.Errorf(\"sweeping Example Things for %s: %w\", region, err))\n}\n\nreturn errs.ErrorOrNil()\n}\n

If no paginated SDK call is available, consider generating one using the listpages generator, or implement the sweeper as follows:

func sweepThings(region string) error {\nctx := sweep.Context(region)\nclient, err := sweep.SharedRegionalSweepClient(ctx, region)\n\nif err != nil {\nreturn fmt.Errorf(\"getting client: %w\", err)\n}\n\nconn := client.ExampleConn(ctx)\nsweepResources := make([]sweep.Sweepable, 0)\nvar errs *multierror.Error\n\ninput := &example.ListThingsInput{}\n\nfor {\noutput, err := conn.ListThings(input)\nif sweep.SkipSweepError(err) {\nlog.Printf(\"[WARN] Skipping Example Thing sweep for %s: %s\", region, errs)\nreturn nil\n}\nif err != nil {\nerrs = multierror.Append(errs, fmt.Errorf(\"listing Example Things for %s: %w\", region, err))\nreturn errs.ErrorOrNil()\n}\n\nfor _, thing := range output.Things {\nr := ResourceThing()\nd := r.Data(nil)\n\nid := aws.StringValue(thing.Id)\nd.SetId(id)\n\n// Perform resource specific pre-sweep setup.\n// For example, you may need to perform one or more of these types of pre-sweep tasks, specific to the resource:\n//\n// err := sdk.ReadResource(ctx, r, d, client) // fill in data\n// d.Set(\"skip_final_snapshot\", true)           // set an argument in order to delete\n\n// This \"if\" is only needed if the pre-sweep setup can produce errors.\n// Otherwise, do not include it.\nif err != nil {\nerr := fmt.Errorf(\"reading Example Thing (%s): %w\", id, err)\nerrs = multierror.Append(errs, err)\ncontinue\n}\n\nsweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client))\n}\n\nif aws.StringValue(output.NextToken) == \"\" {\nbreak\n}\n\ninput.NextToken = output.NextToken\n}\n\nif err := sweep.SweepOrchestrator(sweepResources); err != nil {\nerrs = multierror.Append(errs, fmt.Errorf(\"sweeping Example Thing for %s: %w\", region, err))\n}\n\nreturn errs.ErrorOrNil()\n}\n
"},{"location":"running-and-writing-acceptance-tests/#acceptance-test-checklists","title":"Acceptance Test Checklists","text":"

There are several aspects to writing good acceptance tests. These checklists will help ensure effective testing from the design stage through to implementation details.

"},{"location":"running-and-writing-acceptance-tests/#basic-acceptance-test-design","title":"Basic Acceptance Test Design","text":"

These are basic principles to help guide the creation of acceptance tests.

  • Covers Changes: Every line of resource or data source code added or changed should be covered by one or more tests. For example, if a resource has two ways of functioning, tests should cover both possible paths. Nearly every codebase change needs test coverage to ensure functionality and prevent future regressions. If a bug or other problem prompted a fix, a test should be added that previously would have failed, especially if the report included a configuration.
  • Follows the Single Responsibility Principle: Every test should have a single responsibility and effectively test that responsibility. This may include individual tests for verifying basic functionality of the resource (Create, Read, Delete), separately verifying using and updating a single attribute in a resource, or separately changing between two attributes to verify two \"modes\"/\"types\" possible with a resource configuration. In following this principle, test configurations should be as simple as possible. For example, not including extra configuration unless it is necessary for the specific test.
"},{"location":"running-and-writing-acceptance-tests/#test-implementation","title":"Test Implementation","text":"

The below are required items that will be noted during submission review and prevent immediate merging:

  • Implements CheckDestroy: Resource testing should include a CheckDestroy function (typically named testAccCheck{SERVICE}{RESOURCE}Destroy) that calls the API to verify that the Terraform resource has been deleted or disassociated as appropriate. More information about CheckDestroy functions can be found in the SDKv2 TestCase documentation.
  • Implements Exists Check Function: Resource testing should include a TestCheckFunc function (typically named testAccCheck{SERVICE}{RESOURCE}Exists) that calls the API to verify that the Terraform resource has been created or associated as appropriate. Preferably, this function will also accept a pointer to an API object representing the Terraform resource from the API response that can be set for potential usage in later TestCheckFunc. More information about these functions can be found in the SDKv2 Custom Check Functions documentation.
  • Excludes Provider Declarations: Test configurations should not include provider \"aws\" {...} declarations. If necessary, only the provider declarations in acctest.go should be used for multiple account/region or otherwise specialized testing.
  • Passes in us-west-2 Region: Tests default to running in us-west-2 and at a minimum should pass in that region or include necessary PreCheck functions to skip the test when ran outside an expected environment.
  • Includes ErrorCheck: All acceptance tests should include a call to the common ErrorCheck (ErrorCheck: acctest.ErrorCheck(t, service.EndpointsID),).
  • Uses resource.ParallelTest: Tests should use resource.ParallelTest() instead of resource.Test() except where serialized testing is absolutely required.
  • [ ] Uses fmt.Sprintf(): Test configurations preferably should to be separated into their own functions (typically named testAcc{SERVICE}{RESOURCE}Config{PURPOSE}) that call fmt.Sprintf() for variable injection or a string const for completely static configurations. Test configurations should avoid var or other variable injection functionality such as text/template.
  • Uses Randomized Infrastructure Naming: Test configurations that use resources where a unique name is required should generate a random name. Typically this is created via rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) in the acceptance test function before generating the configuration.
  • Prevents S3 Bucket Deletion Errors: Test configurations that use aws_s3_bucket resources as a logging destination should include the force_destroy = true configuration. This is to prevent race conditions where logging objects may be written during the testing duration which will cause BucketNotEmpty errors during deletion.

For resources that support import, the additional item below is required that will be noted during submission review and prevent immediate merging:

  • Implements ImportState Testing: Tests should include an additional TestStep configuration that verifies resource import via ImportState: true and ImportStateVerify: true. This TestStep should be added to all possible tests for the resource to ensure that all infrastructure configurations are properly imported into Terraform.

The below are style-based items that may be noted during review and are recommended for simplicity, consistency, and quality assurance:

  • Uses Builtin Check Functions: Tests should use already available check functions, e.g. resource.TestCheckResourceAttr(), to verify values in the Terraform state over creating custom TestCheckFunc. More information about these functions can be found in the SDKv2 Builtin Check Functions documentation.
  • Uses TestCheckResourceAttrPair() for Data Sources: Tests should use resource.TestCheckResourceAttrPair() to verify values in the Terraform state for data sources attributes to compare them with their expected resource attributes.
  • Excludes Timeouts Configurations: Test configurations should not include timeouts {...} configuration blocks except for explicit testing of customizable timeouts (typically very short timeouts with ExpectError).
  • Implements Default and Zero Value Validation: The basic test for a resource (typically named TestAcc{SERVICE}{RESOURCE}_basic) should use available check functions, e.g. resource.TestCheckResourceAttr(), to verify default and zero values in the Terraform state for all attributes. Empty/missing configuration blocks can be verified with resource.TestCheckResourceAttr(resourceName, \"{ATTRIBUTE}.#\", \"0\") and empty maps with resource.TestCheckResourceAttr(resourceName, \"{ATTRIBUTE}.%\", \"0\")
"},{"location":"running-and-writing-acceptance-tests/#avoid-hard-coding","title":"Avoid Hard Coding","text":"

Avoid hard coding values in acceptance test checks and configurations for consistency and testing flexibility. Resource testing is expected to pass across multiple AWS environments supported by the Terraform AWS Provider (e.g., AWS Standard and AWS GovCloud (US)). Contributors are not expected or required to perform testing outside of AWS Standard, e.g., running only in the us-west-2 region is perfectly acceptable. However, contributors are expected to avoid hard coding with these guidelines.

"},{"location":"running-and-writing-acceptance-tests/#hardcoded-account-ids","title":"Hardcoded Account IDs","text":"
  • Uses Account Data Sources: Any hardcoded account numbers in configuration, e.g., 137112412989, should be replaced with a data source. Depending on the situation, there are several data sources for account IDs including:
    • aws_caller_identity data source,
    • aws_canonical_user_id data source,
    • aws_billing_service_account data source, and
    • aws_sagemaker_prebuilt_ecr_image data source.
  • Uses Account Test Checks: Any check required to verify an AWS Account ID of the current testing account or another account should use one of the following available helper functions over the usage of resource.TestCheckResourceAttrSet() and resource.TestMatchResourceAttr():
    • acctest.CheckResourceAttrAccountID(): Validates the state value equals the AWS Account ID of the current account running the test. This is the most common implementation.
    • acctest.MatchResourceAttrAccountID(): Validates the state value matches any AWS Account ID (e.g. a 12 digit number). This is typically only used in data source testing of AWS managed components.

Here's an example of using aws_caller_identity:

data \"aws_caller_identity\" \"current\" {}\n\nresource \"aws_backup_selection\" \"test\" {\nplan_id      = aws_backup_plan.test.id\nname         = \"tf_acc_test_backup_selection_%[1]d\"\niam_role_arn = \"arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:role/service-role/AWSBackupDefaultServiceRole\"\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-ami-ids","title":"Hardcoded AMI IDs","text":"
  • Uses aws_ami Data Source: Any hardcoded AMI ID configuration, e.g. ami-12345678, should be replaced with the aws_ami data source pointing to an Amazon Linux image. The package internal/acctest includes test configuration helper functions to simplify these lookups:
    • acctest.ConfigLatestAmazonLinuxHVMEBSAMI(): The recommended AMI for most situations, using Amazon Linux, HVM virtualization, and EBS storage. To reference the AMI ID in the test configuration: data.aws_ami.amzn-ami-minimal-hvm-ebs.id.
    • testAccLatestAmazonLinuxHVMInstanceStoreAMIConfig() (EC2): AMI lookup using Amazon Linux, HVM virtualization, and Instance Store storage. Should only be used in testing that requires Instance Store storage rather than EBS. To reference the AMI ID in the test configuration: data.aws_ami.amzn-ami-minimal-hvm-instance-store.id.
    • testAccLatestAmazonLinuxPVEBSAMIConfig() (EC2): AMI lookup using Amazon Linux, Paravirtual virtualization, and EBS storage. Should only be used in testing that requires Paravirtual over Hardware Virtual Machine (HVM) virtualization. To reference the AMI ID in the test configuration: data.aws_ami.amzn-ami-minimal-pv-ebs.id.
    • configLatestAmazonLinuxPvInstanceStoreAmi (EC2): AMI lookup using Amazon Linux, Paravirtual virtualization, and Instance Store storage. Should only be used in testing that requires Paravirtual virtualization over HVM and Instance Store storage over EBS. To reference the AMI ID in the test configuration: data.aws_ami.amzn-ami-minimal-pv-instance-store.id.
    • testAccLatestWindowsServer2016CoreAMIConfig() (EC2): AMI lookup using Windows Server 2016 Core, HVM virtualization, and EBS storage. Should only be used in testing that requires Windows. To reference the AMI ID in the test configuration: data.aws_ami.win2016core-ami.id.

Here's an example of using acctest.ConfigLatestAmazonLinuxHVMEBSAMI() and data.aws_ami.amzn-ami-minimal-hvm-ebs.id:

func testAccLaunchConfigurationDataSourceConfig_basic(rName string) string {\nreturn acctest.ConfigCompose(\nacctest.ConfigLatestAmazonLinuxHVMEBSAMI(),\nfmt.Sprintf(`\nresource \"aws_launch_configuration\" \"test\" {\n  name          = %[1]q\n  image_id      = data.aws_ami.amzn-ami-minimal-hvm-ebs.id\n  instance_type = \"m1.small\"\n}\n`, rName))\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-availability-zones","title":"Hardcoded Availability Zones","text":"
  • Uses aws_availability_zones Data Source: Any hardcoded AWS Availability Zone configuration, e.g. us-west-2a, should be replaced with the aws_availability_zones data source. Use the convenience function called acctest.ConfigAvailableAZsNoOptIn() (defined in internal/acctest/acctest.go) to declare data \"aws_availability_zones\" \"available\" {...}. You can then reference the data source via data.aws_availability_zones.available.names[0] or data.aws_availability_zones.available.names[count.index] in resources using count.

Here's an example of using acctest.ConfigAvailableAZsNoOptIn() and data.aws_availability_zones.available.names[0]:

func testAccInstanceVpcConfigBasic(rName string) string {\nreturn acctest.ConfigCompose(\nacctest.ConfigAvailableAZsNoOptIn(),\nfmt.Sprintf(`\nresource \"aws_subnet\" \"test\" {\n  availability_zone = data.aws_availability_zones.available.names[0]\n  cidr_block        = \"10.0.0.0/24\"\n  vpc_id            = aws_vpc.test.id\n}\n`, rName))\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-database-versions","title":"Hardcoded Database Versions","text":"
  • Uses Database Version Data Sources: Hardcoded database versions, e.g., RDS MySQL Engine Version 5.7.42, should be removed (which means the AWS-defined default version will be used) or replaced with a list of preferred versions using a data source. Because versions change over times and version offerings vary from region to region and partition to partition, using the default version or providing a list of preferences ensures a version will be available. Depending on the situation, there are several data sources for versions, including:
    • aws_rds_engine_version data source,
    • aws_docdb_engine_version data source, and
    • aws_neptune_engine_version data source.

Here's an example of using aws_rds_engine_version and data.aws_rds_engine_version.default.version:

data \"aws_rds_engine_version\" \"default\" {\nengine = \"mysql\"\n}\n\ndata \"aws_rds_orderable_db_instance\" \"test\" {\nengine                     = data.aws_rds_engine_version.default.engine\nengine_version             = data.aws_rds_engine_version.default.version\npreferred_instance_classes = [\"db.t3.small\", \"db.t2.small\", \"db.t2.medium\"]\n}\n\nresource \"aws_db_instance\" \"bar\" {\nengine               = data.aws_rds_engine_version.default.engine\nengine_version       = data.aws_rds_engine_version.default.version\ninstance_class       = data.aws_rds_orderable_db_instance.test.instance_class\nskip_final_snapshot  = true\nparameter_group_name = \"default.${data.aws_rds_engine_version.default.parameter_group_family}\"\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-direct-connect-locations","title":"Hardcoded Direct Connect Locations","text":"
  • Uses aws_dx_locations Data Source: Hardcoded AWS Direct Connect locations, e.g., EqSe2, should be replaced with the aws_dx_locations data source.

Here's an example using data.aws_dx_locations.test.location_codes:

data \"aws_dx_locations\" \"test\" {}\n\nresource \"aws_dx_lag\" \"test\" {\nname                  = \"Test LAG\"\nconnections_bandwidth = \"1Gbps\"\nlocation              = tolist(data.aws_dx_locations.test.location_codes)[0]\nforce_destroy         = true\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-instance-types","title":"Hardcoded Instance Types","text":"
  • Uses Instance Type Data Source: Singular hardcoded instance types and classes, e.g., t2.micro and db.t2.micro, should be replaced with a list of preferences using a data source. Because offerings vary from region to region and partition to partition, providing a list of preferences dramatically improves the likelihood that one of the options will be available. Depending on the situation, there are several data sources for instance types and classes, including:
    • aws_ec2_instance_type_offering data source - Convenience functions declare configurations that are referenced with data.aws_ec2_instance_type_offering.available including:
      • The acctest.AvailableEC2InstanceTypeForAvailabilityZone() function for test configurations using an EC2 Subnet which is inherently within a single Availability Zone
      • The acctest.AvailableEC2InstanceTypeForRegion() function for test configurations that do not include specific Availability Zones
    • aws_rds_orderable_db_instance data source,
    • aws_neptune_orderable_db_instance data source, and
    • aws_docdb_orderable_db_instance data source.

Here's an example of using acctest.AvailableEC2InstanceTypeForRegion() and data.aws_ec2_instance_type_offering.available.instance_type:

func testAccSpotInstanceRequestConfig(rInt int) string {\nreturn acctest.ConfigCompose(\nacctest.AvailableEC2InstanceTypeForRegion(\"t3.micro\", \"t2.micro\"),\nfmt.Sprintf(`\nresource \"aws_spot_instance_request\" \"test\" {\n  instance_type        = data.aws_ec2_instance_type_offering.available.instance_type\n  spot_price           = \"0.05\"\n  wait_for_fulfillment = true\n}\n`, rInt))\n}\n

Here's an example of using aws_rds_orderable_db_instance and data.aws_rds_orderable_db_instance.test.instance_class:

data \"aws_rds_orderable_db_instance\" \"test\" {\nengine                     = \"mysql\"\nengine_version             = \"5.7.31\"\npreferred_instance_classes = [\"db.t3.micro\", \"db.t2.micro\", \"db.t3.small\"]\n}\n\nresource \"aws_db_instance\" \"test\" {\nengine              = data.aws_rds_orderable_db_instance.test.engine\nengine_version      = data.aws_rds_orderable_db_instance.test.engine_version\ninstance_class      = data.aws_rds_orderable_db_instance.test.instance_class\nskip_final_snapshot = true\nusername            = \"test\"\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-partition-dns-suffix","title":"Hardcoded Partition DNS Suffix","text":"
  • Uses aws_partition Data Source: Any hardcoded DNS suffix configuration, e.g., the amazonaws.com in a ec2.amazonaws.com service principal, should be replaced with the aws_partition data source. A common pattern is declaring data \"aws_partition\" \"current\" {} and referencing it via data.aws_partition.current.dns_suffix.

Here's an example of using aws_partition and data.aws_partition.current.dns_suffix:

data \"aws_partition\" \"current\" {}\n\nresource \"aws_iam_role\" \"test\" {\nassume_role_policy = <<POLICY\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Sid\": \"\",\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Service\": \"cloudtrail.${data.aws_partition.current.dns_suffix}\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n  ]\n}\nPOLICY\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-partition-in-arn","title":"Hardcoded Partition in ARN","text":"
  • Uses aws_partition Data Source: Any hardcoded AWS Partition configuration, e.g. the aws in a arn:aws:SERVICE:REGION:ACCOUNT:RESOURCE ARN, should be replaced with the aws_partition data source. A common pattern is declaring data \"aws_partition\" \"current\" {} and referencing it via data.aws_partition.current.partition.
  • Uses Builtin ARN Check Functions: Tests should use available ARN check functions to validate ARN attribute values in the Terraform state over resource.TestCheckResourceAttrSet() and resource.TestMatchResourceAttr():

    • acctest.CheckResourceAttrRegionalARN() verifies that an ARN matches the account ID and region of the test execution with an exact resource value
    • acctest.MatchResourceAttrRegionalARN() verifies that an ARN matches the account ID and region of the test execution with a regular expression of the resource value
    • acctest.CheckResourceAttrGlobalARN() verifies that an ARN matches the account ID of the test execution with an exact resource value
    • acctest.MatchResourceAttrGlobalARN() verifies that an ARN matches the account ID of the test execution with a regular expression of the resource value
    • acctest.CheckResourceAttrRegionalARNNoAccount() verifies than an ARN has no account ID and matches the current region of the test execution with an exact resource value
    • acctest.CheckResourceAttrGlobalARNNoAccount() verifies than an ARN has no account ID and matches an exact resource value
    • acctest.CheckResourceAttrRegionalARNAccountID() verifies than an ARN matches a specific account ID and the current region of the test execution with an exact resource value
    • acctest.CheckResourceAttrGlobalARNAccountID() verifies than an ARN matches a specific account ID with an exact resource value

Here's an example of using aws_partition and data.aws_partition.current.partition:

data \"aws_partition\" \"current\" {}\n\nresource \"aws_iam_role_policy_attachment\" \"test\" {\npolicy_arn = \"arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole\"\nrole       = aws_iam_role.test.name\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-region","title":"Hardcoded Region","text":"
  • Uses aws_region Data Source: Any hardcoded AWS Region configuration, e.g., us-west-2, should be replaced with the aws_region data source. A common pattern is declaring data \"aws_region\" \"current\" {} and referencing it via data.aws_region.current.name

Here's an example of using aws_region and data.aws_region.current.name:

data \"aws_region\" \"current\" {}\n\nresource \"aws_route53_zone\" \"test\" {\nvpc {\nvpc_id     = aws_vpc.test.id\nvpc_region = data.aws_region.current.name\n}\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-spot-price","title":"Hardcoded Spot Price","text":"
  • Uses aws_ec2_spot_price Data Source: Any hardcoded spot prices, e.g., 0.05, should be replaced with the aws_ec2_spot_price data source. A common pattern is declaring data \"aws_ec2_spot_price\" \"current\" {} and referencing it via data.aws_ec2_spot_price.current.spot_price.

Here's an example of using aws_ec2_spot_price and data.aws_ec2_spot_price.current.spot_price:

data \"aws_ec2_spot_price\" \"current\" {\ninstance_type = \"t3.medium\"\n\nfilter {\nname   = \"product-description\"\nvalues = [\"Linux/UNIX\"]\n}\n}\n\nresource \"aws_spot_fleet_request\" \"test\" {\nspot_price      = data.aws_ec2_spot_price.current.spot_price\ntarget_capacity = 2\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-ssh-keys","title":"Hardcoded SSH Keys","text":"
  • Uses acctest.RandSSHKeyPair() or RandSSHKeyPairSize() Functions: Any hardcoded SSH keys should be replaced with random SSH keys generated by either the acceptance testing framework's function RandSSHKeyPair() or the provider function RandSSHKeyPairSize(). RandSSHKeyPair() generates 1024-bit keys.

Here's an example using aws_key_pair

func TestAccKeyPair_basic(t *testing.T) {\n...\n\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\npublicKey, _, err := acctest.RandSSHKeyPair(acctest.DefaultEmailAddress)\nif err != nil {\nt.Fatalf(\"generating random SSH key: %s\", err)\n}\n\nresource.ParallelTest(t, resource.TestCase{\n...\nSteps: []resource.TestStep{\n{\nConfig: testAccKeyPairConfig(rName, publicKey),\n...\n},\n},\n})\n}\n\nfunc testAccKeyPairConfig(rName, publicKey string) string {\nreturn fmt.Sprintf(`\nresource \"aws_key_pair\" \"test\" {\n  key_name   = %[1]q\n  public_key = %[2]q\n}\n`, rName, publicKey)\n}\n
"},{"location":"running-and-writing-acceptance-tests/#hardcoded-email-addresses","title":"Hardcoded Email Addresses","text":"
  • Uses either acctest.DefaultEmailAddress Constant or acctest.RandomEmailAddress() Function: Any hardcoded email addresses should replaced with either the constant acctest.DefaultEmailAddress or the function acctest.RandomEmailAddress().

Using acctest.DefaultEmailAddress is preferred when using a single email address in an acceptance test.

Here's an example using acctest.DefaultEmailAddress

func TestAccSNSTopicSubscription_email(t *testing.T) {\n...\n\nrName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)\n\nresource.ParallelTest(t, resource.TestCase{\n...\nSteps: []resource.TestStep{\n{\nConfig: testAccTopicSubscriptionEmailConfig(rName, acctest.DefaultEmailAddress),\nCheck: resource.ComposeTestCheckFunc(\n...\nresource.TestCheckResourceAttr(resourceName, \"endpoint\", acctest.DefaultEmailAddress),\n),\n},\n},\n})\n}\n

Here's an example using acctest.RandomEmailAddress()

func TestAccPinpointEmailChannel_basic(t *testing.T) {\n...\n\ndomain := acctest.RandomDomainName()\naddress1 := acctest.RandomEmailAddress(domain)\naddress2 := acctest.RandomEmailAddress(domain)\n\nresource.ParallelTest(t, resource.TestCase{\n...\nSteps: []resource.TestStep{\n{\nConfig: testAccEmailChannelConfig_FromAddress(domain, address1),\nCheck: resource.ComposeTestCheckFunc(\n...\nresource.TestCheckResourceAttr(resourceName, \"from_address\", address1),\n),\n},\n{\nConfig: testAccEmailChannelConfig_FromAddress(domain, address2),\nCheck: resource.ComposeTestCheckFunc(\n...\nresource.TestCheckResourceAttr(resourceName, \"from_address\", address2),\n),\n},\n},\n})\n}\n
"},{"location":"skaff/","title":"Provider Scaffolding (skaff)","text":"

skaff is a Terraform AWS Provider scaffolding command line tool. It generates resource/data source files and accompanying test files which adhere to the latest best practice. These files are heavily commented with instructions so serve as the best way to get started with provider development.

"},{"location":"skaff/#overview-workflow-steps","title":"Overview workflow steps","text":"
  1. Figure out what you're trying to do:
    • Create a resource or a data source?
    • AWS Go SDK v1 or v2 code?
    • Terraform Plugin Framework or Plugin SDKv2 based?
    • Name of the new resource or data source?
  2. Use skaff to generate provider code
  3. Go through the generated code completing code and customizing for the AWS Go SDK API
  4. Run, test, refine
  5. Remove \"TIP\" comments
  6. Submit code in pull request
"},{"location":"skaff/#running-skaff","title":"Running skaff","text":"
  1. Use Git to clone the GitHub https://github.com/hashicorp/terraform-provider-aws repository.
  2. cd skaff
  3. go install .
  4. Change directories to the service where your new resource will reside. E.g., cd ../internal/service/mq.
  5. To get help, enter skaff without arguments.
  6. Generate a resource. E.g., skaff resource --name BrokerReboot (or equivalently skaff resource -n BrokerReboot).
"},{"location":"skaff/#usage","title":"Usage","text":""},{"location":"skaff/#help","title":"Help","text":"
$ skaff --help\nUsage:\n  skaff [command]\n\nAvailable Commands:\n  completion  Generate the autocompletion script for the specified shell\n  datasource  Create scaffolding for a data source\n  help        Help about any command\n  resource    Create scaffolding for a resource\n\nFlags:\n  -h, --help   help for skaff\n
"},{"location":"skaff/#autocompletion","title":"Autocompletion","text":"

Generate the autocompletion script for skaff for the specified shell

$ skaff completion --help\nUsage:\n  skaff completion [command]\n\nAvailable Commands:\n  bash        Generate the autocompletion script for bash\n  fish        Generate the autocompletion script for fish\n  powershell  Generate the autocompletion script for powershell\n  zsh         Generate the autocompletion script for zsh\n\nFlags:\n  -h, --help   help for completion\n\nUse \"skaff completion [command] --help\" for more information about a command\n
"},{"location":"skaff/#data-source","title":"Data Source","text":"

Create scaffolding for a data source

$ skaff datasource --help\nUsage:\n  skaff datasource [flags]\n\nFlags:\n  -c, --clear-comments     do not include instructional comments in source\n  -f, --force              force creation, overwriting existing files\n  -h, --help               help for datasource\n  -n, --name string        name of the entity\n  -p, --plugin-framework   generate for Terraform Plugin-Framework\n  -s, --snakename string   if skaff doesn't get it right, explicitly give name in snake case (e.g., db_vpc_instance)\n  -o, --v1                 generate for AWS Go SDK v1 (some existing services)\n
"},{"location":"skaff/#resource","title":"Resource","text":"

Create scaffolding for a resource

$ skaff resource --help\nUsage:\n  skaff resource [flags]\n\nFlags:\n  -c, --clear-comments     do not include instructional comments in source\n  -f, --force              force creation, overwriting existing files\n  -h, --help               help for resource\n  -n, --name string        name of the entity\n  -p, --plugin-framework   generate for Terraform Plugin-Framework\n  -s, --snakename string   if skaff doesn't get it right, explicitly give name in snake case (e.g., db_vpc_instance)\n  -o, --v1                 generate for AWS Go SDK v1 (some existing services)\n
"},{"location":"terraform-plugin-versions/","title":"Terraform Plugin Versions","text":"

The Terraform AWS Provider is constructed with HashiCorp-maintained packages for building plugins. Most existing resources are implemented with Terraform Plugin SDKv2, while newer resources may use Terraform Plugin Framework. A thorough comparison of the packages can be found here.

At this time community contributions in either package will be accepted. The AWS Provider is muxed to allow resources and data sources implemented in both packages. As AWS Provider tooling around Plugin Framework (and the library itself) matures, we will being requiring all net-new resources be implemented with it. skaff currently supports generating Plugin Framework based resources using the optional -p/--plugin-framework flag. Factors to consider when choosing between packages are:

  1. What other resources in a given service use
  2. Level of comfort with the new idioms introduced in Plugin Framework
  3. Advantages Plugin Framework may afford over Plugin SDKv2 (improved null handling, plan modifications, etc.)
"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000000..21f86d90eef8 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,163 @@ + + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + + None + 2023-07-06 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..cec9656472728fcd5b953a7eb70d1938b16f99bc GIT binary patch literal 216 zcmb2|=HN(`S)Ri5zc{lbH8-(9uOc^x;q7&AzC#KEY!7Pv9dC$lP}}G9^4wBwTZV-< z%oYYPuDus`OX{-onH-5awa;rGUfpf}VrlJ*Il3R)40^VNu6vgg{OI=AIr0UnM*qI3 zz38~KfmbyzS;lWl-(wFa@lySEhdKEf0lB+aesOCmyXzQD$%@)gQe60W$_|UwmUDM~ zZRXinzEvY)AOunoeoQ0HFev?^7ZnIrp^6Xu**wq;y=#=jO;Q6D1^=I{x7@5 O<7EPGZ|4 + + + + + + + + + + + + + + + + + + + + + + Provider Scaffolding (skaff) - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Provider Scaffolding (skaff)#

+

skaff is a Terraform AWS Provider scaffolding command line tool. It generates resource/data source files and accompanying test files which adhere to the latest best practice. These files are heavily commented with instructions so serve as the best way to get started with provider development.

+

Overview workflow steps#

+
    +
  1. Figure out what you're trying to do: +
  2. +
  3. Use skaff to generate provider code
  4. +
  5. Go through the generated code completing code and customizing for the AWS Go SDK API
  6. +
  7. Run, test, refine
  8. +
  9. Remove "TIP" comments
  10. +
  11. Submit code in pull request
  12. +
+

Running skaff#

+
    +
  1. Use Git to clone the GitHub https://github.com/hashicorp/terraform-provider-aws repository.
  2. +
  3. cd skaff
  4. +
  5. go install .
  6. +
  7. Change directories to the service where your new resource will reside. E.g., cd ../internal/service/mq.
  8. +
  9. To get help, enter skaff without arguments.
  10. +
  11. Generate a resource. E.g., skaff resource --name BrokerReboot (or equivalently skaff resource -n BrokerReboot).
  12. +
+

Usage#

+

Help#

+
$ skaff --help
+Usage:
+  skaff [command]
+
+Available Commands:
+  completion  Generate the autocompletion script for the specified shell
+  datasource  Create scaffolding for a data source
+  help        Help about any command
+  resource    Create scaffolding for a resource
+
+Flags:
+  -h, --help   help for skaff
+
+

Autocompletion#

+

Generate the autocompletion script for skaff for the specified shell

+
$ skaff completion --help
+Usage:
+  skaff completion [command]
+
+Available Commands:
+  bash        Generate the autocompletion script for bash
+  fish        Generate the autocompletion script for fish
+  powershell  Generate the autocompletion script for powershell
+  zsh         Generate the autocompletion script for zsh
+
+Flags:
+  -h, --help   help for completion
+
+Use "skaff completion [command] --help" for more information about a command
+
+

Data Source#

+

Create scaffolding for a data source

+
$ skaff datasource --help
+Usage:
+  skaff datasource [flags]
+
+Flags:
+  -c, --clear-comments     do not include instructional comments in source
+  -f, --force              force creation, overwriting existing files
+  -h, --help               help for datasource
+  -n, --name string        name of the entity
+  -p, --plugin-framework   generate for Terraform Plugin-Framework
+  -s, --snakename string   if skaff doesn't get it right, explicitly give name in snake case (e.g., db_vpc_instance)
+  -o, --v1                 generate for AWS Go SDK v1 (some existing services)
+
+

Resource#

+

Create scaffolding for a resource

+
$ skaff resource --help
+Usage:
+  skaff resource [flags]
+
+Flags:
+  -c, --clear-comments     do not include instructional comments in source
+  -f, --force              force creation, overwriting existing files
+  -h, --help               help for resource
+  -n, --name string        name of the entity
+  -p, --plugin-framework   generate for Terraform Plugin-Framework
+  -s, --snakename string   if skaff doesn't get it right, explicitly give name in snake case (e.g., db_vpc_instance)
+  -o, --v1                 generate for AWS Go SDK v1 (some existing services)
+
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/stylesheets/extra.css b/stylesheets/extra.css new file mode 100644 index 000000000000..1f666ea4d756 --- /dev/null +++ b/stylesheets/extra.css @@ -0,0 +1,8 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +.md-footer__inner:not([hidden]) { + display: none +} \ No newline at end of file diff --git a/terraform-plugin-versions/index.html b/terraform-plugin-versions/index.html new file mode 100644 index 000000000000..58db7d331c44 --- /dev/null +++ b/terraform-plugin-versions/index.html @@ -0,0 +1,835 @@ + + + + + + + + + + + + + + + + + + + + + + + + Terraform Plugin Versions - Terraform AWS Provider - Contributor Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + +

Terraform Plugin Versions#

+

The Terraform AWS Provider is constructed with HashiCorp-maintained packages for building plugins. Most existing resources are implemented with Terraform Plugin SDKv2, while newer resources may use Terraform Plugin Framework. A thorough comparison of the packages can be found here.

+

At this time community contributions in either package will be accepted. The AWS Provider is muxed to allow resources and data sources implemented in both packages. As AWS Provider tooling around Plugin Framework (and the library itself) matures, we will being requiring all net-new resources be implemented with it. skaff currently supports generating Plugin Framework based resources using the optional -p/--plugin-framework flag. Factors to consider when choosing between packages are:

+
    +
  1. What other resources in a given service use
  2. +
  3. Level of comfort with the new idioms introduced in Plugin Framework
  4. +
  5. Advantages Plugin Framework may afford over Plugin SDKv2 (improved null handling, plan modifications, etc.)
  6. +
+ + + + + + +
+
+ + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file