diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..e1be721
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,29 @@
+# Unix-style newlines with a newline ending every file
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.go]
+indent_size = 2
+indent_style = tab
+
+[*.{tf,tfvars}]
+indent_size = 2
+indent_style = space
+
+[*.md]
+max_line_length = 0
+trim_trailing_whitespace = false
+
+# Override for Makefile
+[{Makefile, makefile, GNUmakefile, Makefile.*}]
+tab_width = 2
+indent_style = tab
+indent_size = 4
+
+[COMMIT_EDITMSG]
+max_line_length = 0
diff --git a/.gitignore b/.gitignore
index e6a8e99..10b689c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,28 @@
-# Compiled files
+# Local .terraform directories
+**/.terraform
+**/.terraform.d
+
+# .tfstate files
*.tfstate
-*.tfstate.backup
-**/.terraform.lock.hcl
+*.tfstate.*
+.terraform
+.terraform.tfstate.lock.info
+.terraform.lock.hcl
+
+**/.idea
+**/*.iml
+**/.vscode
+
+# Cloud Posse Build Harness https://github.com/cloudposse/build-harness
+**/.build-harness
+**/build-harness
+
+# Crash log files
+crash.log
+test.log
-# Module directory
-.terraform/
-.idea
-terraform-aws-tfstate-backend.iml
+# Editor backups
+*.orig
+*.draft
+*~
-.build-harness
-build-harness
diff --git a/LICENSE b/LICENSE
index 7afefb9..e17aa36 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright 2018-2023 Cloud Posse, LLC
+ Copyright 2020 Cloud Posse, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index aa194bd..a21f330 100644
--- a/README.md
+++ b/README.md
@@ -28,22 +28,20 @@
-->
-Terraform module to provision an S3 bucket to store `terraform.tfstate` file and a DynamoDB table to lock the state file
-to prevent concurrent modifications and state corruption.
+Terraform module to provision an S3 backend to hold Terraform state.
-The module supports the following:
+The module supports the following options:
-1. Forced server-side encryption at rest for the S3 bucket
-2. S3 bucket versioning to allow for Terraform state recovery in the case of accidental deletions and human errors
-3. State locking and consistency checking via DynamoDB table to prevent concurrent operations
-4. DynamoDB server-side encryption
-
-https://www.terraform.io/docs/backends/types/s3.html
+1. Creation of DynamoDB table to provide consistency checking and state locking to prevent concurrent operations
+2. Replication of state and locks to a second region, to ensure continued availability in the event of a region outage
+The module enforces the following:
-__NOTE:__ The operators of the module (IAM Users) must have permissions to create S3 buckets and DynamoDB tables when performing `terraform plan` and `terraform apply`
+1. Forced server-side encryption at rest for the S3 bucket and DynamoDB table
+2. S3 bucket versioning to allow for Terraform state recovery in the case of accidental deletions and human errors
+3. Point-in-time recovery enabled for DynamoDB table
-__NOTE:__ This module cannot be used to apply changes to the `mfa_delete` feature of the bucket. Changes regarding mfa_delete can only be made manually using the root credentials with MFA of the AWS Account where the bucket resides. Please see: https://github.com/terraform-providers/terraform-provider-aws/issues/62
+https://www.terraform.io/docs/backends/types/s3.html
---
@@ -248,109 +246,100 @@ Available targets:
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 0.13.0 |
-| [aws](#requirement\_aws) | >= 2.0 |
-| [local](#requirement\_local) | >= 1.3 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
+| [aws](#requirement\_aws) | >= 4.0 |
## Providers
| Name | Version |
|------|---------|
-| [aws](#provider\_aws) | >= 2.0 |
-| [local](#provider\_local) | >= 1.3 |
+| [aws](#provider\_aws) | >= 4.0 |
+| [aws.blue](#provider\_aws.blue) | >= 4.0 |
+| [aws.green](#provider\_aws.green) | >= 4.0 |
## Modules
| Name | Source | Version |
|------|--------|---------|
-| [dynamodb\_table\_label](#module\_dynamodb\_table\_label) | cloudposse/label/null | 0.25.0 |
-| [log\_storage](#module\_log\_storage) | cloudposse/s3-log-storage/aws | 0.26.0 |
+| [blue\_bucket](#module\_blue\_bucket) | ./modules/s3-bucket | n/a |
+| [blue\_label](#module\_blue\_label) | cloudposse/label/null | 0.25.0 |
+| [dynamodb\_label](#module\_dynamodb\_label) | cloudposse/label/null | 0.25.0 |
+| [green\_bucket](#module\_green\_bucket) | ./modules/s3-bucket | n/a |
+| [green\_label](#module\_green\_label) | cloudposse/label/null | 0.25.0 |
+| [region\_utils](#module\_region\_utils) | cloudposse/utils/aws | 1.1.0 |
+| [replication\_label](#module\_replication\_label) | cloudposse/label/null | 0.25.0 |
| [this](#module\_this) | cloudposse/label/null | 0.25.0 |
## Resources
| Name | Type |
|------|------|
-| [aws_dynamodb_table.with_server_side_encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
-| [aws_dynamodb_table.without_server_side_encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
+| [aws_dynamodb_table.locks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
| [aws_iam_policy.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
-| [aws_s3_bucket.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
-| [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
-| [local_file.terraform_backend_config](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
-| [aws_iam_policy_document.prevent_unencrypted_uploads](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_s3_bucket_replication_configuration.blue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource |
+| [aws_s3_bucket_replication_configuration.green](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource |
| [aws_iam_policy_document.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
-| [aws_iam_policy_document.replication_sts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
-| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
+| [aws_iam_policy_document.replication_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_kms_alias.blue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_alias) | data source |
+| [aws_kms_alias.green](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_alias) | data source |
+| [aws_region.blue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
+| [aws_region.green](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [acl](#input\_acl) | The canned ACL to apply to the S3 bucket | `string` | `"private"` | no |
| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no |
-| [arn\_format](#input\_arn\_format) | ARN format to be used. May be changed to support deployment in GovCloud/China regions. | `string` | `"arn:aws"` | no |
| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no |
-| [billing\_mode](#input\_billing\_mode) | DynamoDB billing mode | `string` | `"PROVISIONED"` | no |
-| [block\_public\_acls](#input\_block\_public\_acls) | Whether Amazon S3 should block public ACLs for this bucket | `bool` | `true` | no |
-| [block\_public\_policy](#input\_block\_public\_policy) | Whether Amazon S3 should block public bucket policies for this bucket | `bool` | `true` | no |
-| [bucket\_enabled](#input\_bucket\_enabled) | Whether to create the s3 bucket. | `bool` | `true` | no |
+| [blue\_bucket\_logging](#input\_blue\_bucket\_logging) | Destination for S3 Server Access Logs for the blue bucket. |
list(object({
target_bucket = string
target_prefix = string
}))
| `[]` | no |
+| [blue\_kms\_key\_arn](#input\_blue\_kms\_key\_arn) | The KMS Key ARN for encrypting object (SSE-KMS) in the blue bucket. Default is to use default (SSE-S3) key.
Note: If you are not using the default key and have replication enabled, you must grant the replication role
permission to use the key (`kms:Encrypt` and `kms:Decrypt`). | `list(string)` | `[]` | no |
+| [blue\_s3\_bucket\_name](#input\_blue\_s3\_bucket\_name) | S3 bucket name for bucket in blue region. If not provided, the name will be generated from context. | `list(string)` | `[]` | no |
| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` | {
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no |
| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no |
-| [dynamodb\_enabled](#input\_dynamodb\_enabled) | Whether to create the dynamodb table. | `bool` | `true` | no |
-| [dynamodb\_table\_name](#input\_dynamodb\_table\_name) | Override the name of the DynamoDB table which defaults to using `module.dynamodb_table_label.id` | `string` | `null` | no |
-| [enable\_point\_in\_time\_recovery](#input\_enable\_point\_in\_time\_recovery) | Enable DynamoDB point-in-time recovery | `bool` | `true` | no |
-| [enable\_public\_access\_block](#input\_enable\_public\_access\_block) | Enable Bucket Public Access Block | `bool` | `true` | no |
-| [enable\_server\_side\_encryption](#input\_enable\_server\_side\_encryption) | Enable DynamoDB server-side encryption | `bool` | `true` | no |
+| [dynamodb\_table\_name](#input\_dynamodb\_table\_name) | The name of the DynamoDB table. If not provided, the name will be generated from context. | `list(string)` | `[]` | no |
| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
-| [force\_destroy](#input\_force\_destroy) | A boolean that indicates the S3 bucket can be destroyed even if it contains objects. These objects are not recoverable | `bool` | `false` | no |
+| [force\_destroy](#input\_force\_destroy) | FOR TESTING ONLY! When set to true, `terraform destroy` will destroy the S3 buckets and all the objects in them.
These objects are not recoverable even if you have versioning or backups enabled. | `bool` | `false` | no |
+| [green\_bucket\_logging](#input\_green\_bucket\_logging) | Destination for S3 Server Access Logs for the green bucket. | list(object({
target_bucket = string
target_prefix = string
}))
| `[]` | no |
+| [green\_kms\_key\_arn](#input\_green\_kms\_key\_arn) | The KMS Key ARN for encrypting object (SSE-KMS) in the green bucket. Default is to use default (SSE-S3) key. | `list(string)` | `[]` | no |
+| [green\_s3\_bucket\_name](#input\_green\_s3\_bucket\_name) | S3 bucket name for bucket in green region. If not provided, the name will be generated from context. | `list(string)` | `[]` | no |
| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no |
-| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Whether Amazon S3 should ignore public ACLs for this bucket | `bool` | `true` | no |
| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no |
| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no |
| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no |
| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` | [
"default"
]
| no |
-| [logging](#input\_logging) | Bucket access logging configuration. | object({
bucket_name = string
prefix = string
})
| `null` | no |
-| [logging\_bucket\_enabled](#input\_logging\_bucket\_enabled) | Whether to create the s3 access log bucket. | `bool` | `false` | no |
-| [logging\_bucket\_expiration\_days](#input\_logging\_bucket\_expiration\_days) | Whether to create the s3 access log bucket. | `number` | `90` | no |
-| [logging\_bucket\_glacier\_transition\_days](#input\_logging\_bucket\_glacier\_transition\_days) | Whether to create the s3 access log bucket. | `number` | `60` | no |
-| [logging\_bucket\_standard\_transition\_days](#input\_logging\_bucket\_standard\_transition\_days) | Whether to create the s3 access log bucket. | `number` | `30` | no |
-| [mfa\_delete](#input\_mfa\_delete) | A boolean that indicates that versions of S3 objects can only be deleted with MFA. ( Terraform cannot apply changes of this value; https://github.com/terraform-providers/terraform-provider-aws/issues/629 ) | `bool` | `false` | no |
+| [lock\_table\_enabled](#input\_lock\_table\_enabled) | Set true to create a DynamoDB table to provide Terraform state locking. Highly recommended.
If replication is enabled, a global DynamoDB table will be created in both the blue and green regions. | `bool` | `true` | no |
| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no |
| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no |
-| [prevent\_unencrypted\_uploads](#input\_prevent\_unencrypted\_uploads) | Prevent uploads of unencrypted objects to S3 | `bool` | `true` | no |
-| [profile](#input\_profile) | AWS profile name as set in the shared credentials file | `string` | `""` | no |
-| [read\_capacity](#input\_read\_capacity) | DynamoDB read capacity units | `number` | `5` | no |
+| [permissions\_boundary](#input\_permissions\_boundary) | The ARN of the policy that sets the permissions boundary for the IAM role used for replication. | `list(string)` | `[]` | no |
| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
-| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Whether Amazon S3 should restrict public bucket policies for this bucket | `bool` | `true` | no |
-| [role\_arn](#input\_role\_arn) | The role to be assumed | `string` | `""` | no |
-| [s3\_bucket\_name](#input\_s3\_bucket\_name) | S3 bucket name. If not provided, the name will be generated by the label module in the format namespace-stage-name | `string` | `""` | no |
-| [s3\_replica\_bucket\_arn](#input\_s3\_replica\_bucket\_arn) | The ARN of the S3 replica bucket (destination) | `string` | `""` | no |
-| [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replica_bucket_arn` to enable replication | `bool` | `false` | no |
+| [replication\_enabled](#input\_replication\_enabled) | Set true to enable bidirectional replication and creation of hot standby for quick failover. Highly recommended.
If set to `false`, the "green" configuration will be ignored.
Replication is not supported in AWS GovCloud (US) because S3 Replication Time Control (S3 RTC) is not available. | `bool` | `true` | no |
+| [replication\_role\_name](#input\_replication\_role\_name) | The name to give to the IAM role created for replication. If not provided, the name will be generated from context. | `list(string)` | `[]` | no |
| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no |
| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no |
-| [terraform\_backend\_config\_file\_name](#input\_terraform\_backend\_config\_file\_name) | Name of terraform backend config file | `string` | `"terraform.tf"` | no |
-| [terraform\_backend\_config\_file\_path](#input\_terraform\_backend\_config\_file\_path) | Directory for the terraform backend config file, usually `.`. The default is to create no file. | `string` | `""` | no |
-| [terraform\_backend\_config\_template\_file](#input\_terraform\_backend\_config\_template\_file) | The path to the template used to generate the config file | `string` | `""` | no |
-| [terraform\_state\_file](#input\_terraform\_state\_file) | The path to the state file inside the bucket | `string` | `"terraform.tfstate"` | no |
-| [terraform\_version](#input\_terraform\_version) | The minimum required terraform version | `string` | `"0.12.2"` | no |
-| [write\_capacity](#input\_write\_capacity) | DynamoDB write capacity units | `number` | `5` | no |
## Outputs
| Name | Description |
|------|-------------|
-| [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | DynamoDB table ARN |
+| [blue\_backend\_config](#output\_blue\_backend\_config) | Backend configuration for the blue Terraform state |
+| [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | (Deprecated, use `dynamodb_table_arns` instead) DynamoDB table ARN |
+| [dynamodb\_table\_arns](#output\_dynamodb\_table\_arns) | Map (by region) of the ARNs for DynamoDB tables created by this module to store Terraform state locks.
Note that in general you should only refer to the tables by name and region, not by ARN. |
| [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | DynamoDB table ID |
| [dynamodb\_table\_name](#output\_dynamodb\_table\_name) | DynamoDB table name |
-| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | S3 bucket ARN |
-| [s3\_bucket\_domain\_name](#output\_s3\_bucket\_domain\_name) | S3 bucket domain name |
-| [s3\_bucket\_id](#output\_s3\_bucket\_id) | S3 bucket ID |
-| [terraform\_backend\_config](#output\_terraform\_backend\_config) | Rendered Terraform backend config file |
+| [green\_backend\_config](#output\_green\_backend\_config) | Backend configuration for the green Terraform state |
+| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | (Deprecated, use `s3_bucket_arns` instead): S3 bucket ARN |
+| [s3\_bucket\_arns](#output\_s3\_bucket\_arns) | Map (by region) of the ARNs of the created S3 buckets |
+| [s3\_bucket\_domain\_name](#output\_s3\_bucket\_domain\_name) | (Deprecated, use `s3_bucket_domains` instead): S3 bucket domain name |
+| [s3\_bucket\_domains](#output\_s3\_bucket\_domains) | Map (by region) of the domain names of the created S3 buckets |
+| [s3\_bucket\_id](#output\_s3\_bucket\_id) | (Deprecated, use `s3_bucket_ids` instead): S3 bucket ID |
+| [s3\_bucket\_ids](#output\_s3\_bucket\_ids) | Map (by region) of the IDs (names) of the created S3 buckets |
+| [s3\_bucket\_kms\_key\_arns](#output\_s3\_bucket\_kms\_key\_arns) | Map (by bucket name) of the ARNs of the KMS keys used to encrypt the created S3 buckets |
+| [sns\_topic\_arns](#output\_sns\_topic\_arns) | Map (by region) of the ARNs for the SNS Topics created by this module to receive
replication event notifications regarding the created S3 bucket |
diff --git a/README.yaml b/README.yaml
index 2809f75..d0803ea 100644
--- a/README.yaml
+++ b/README.yaml
@@ -50,22 +50,20 @@ related:
# Short description of this project
description: |-
- Terraform module to provision an S3 bucket to store `terraform.tfstate` file and a DynamoDB table to lock the state file
- to prevent concurrent modifications and state corruption.
+ Terraform module to provision an S3 backend to hold Terraform state.
- The module supports the following:
+ The module supports the following options:
- 1. Forced server-side encryption at rest for the S3 bucket
- 2. S3 bucket versioning to allow for Terraform state recovery in the case of accidental deletions and human errors
- 3. State locking and consistency checking via DynamoDB table to prevent concurrent operations
- 4. DynamoDB server-side encryption
-
- https://www.terraform.io/docs/backends/types/s3.html
+ 1. Creation of DynamoDB table to provide consistency checking and state locking to prevent concurrent operations
+ 2. Replication of state and locks to a second region, to ensure continued availability in the event of a region outage
+ The module enforces the following:
- __NOTE:__ The operators of the module (IAM Users) must have permissions to create S3 buckets and DynamoDB tables when performing `terraform plan` and `terraform apply`
+ 1. Forced server-side encryption at rest for the S3 bucket and DynamoDB table
+ 2. S3 bucket versioning to allow for Terraform state recovery in the case of accidental deletions and human errors
+ 3. Point-in-time recovery enabled for DynamoDB table
- __NOTE:__ This module cannot be used to apply changes to the `mfa_delete` feature of the bucket. Changes regarding mfa_delete can only be made manually using the root credentials with MFA of the AWS Account where the bucket resides. Please see: https://github.com/terraform-providers/terraform-provider-aws/issues/62
+ https://www.terraform.io/docs/backends/types/s3.html
# How to use this project
usage: |-
diff --git a/docs/migration-v0-v1.md b/docs/migration-v0-v1.md
new file mode 100644
index 0000000..afa7313
--- /dev/null
+++ b/docs/migration-v0-v1.md
@@ -0,0 +1,93 @@
+# Migration Notes for Terraform State Backend v1
+
+## Key changes in v1.0
+- Support added for bi-directional replication of 2 S3 buckets and DynamoDB lock table.
+This maintains availability of the Terraform state backend in the event of a region
+outage, although you will need to manually reconfigure your Terraform backend to
+target the secondary region.
+- Because of the above, this module now requires 2 AWS providers: `aws.blue`
+and `aws.green`, configured for the primary and secondary regions respectively.
+If you do not want to enable replication and a second bucket, you still must
+pass in providers for both colors, but you can use the same provider for both.
+- When replication is enabled, an SNS topic is created to publish notifications
+of replication events. You must subscribe to this topic to receive notifications.
+- Server-side encryption is enabled for the S3 buckets and cannot be disabled.
+There is therefore no longer a block against unencrypted uploads, since they
+will be immediately encrypted anyway.
+- S3 ACLs are no longer set on the S3 buckets. Instead, the buckets are
+configured with S3 Object Ownership set to `BucketOwnerEnforced`.
+- All public access to the S3 bucket is blocked.
+- DynamoDB table is billed in `PAY_PER_REQUEST` mode instead of `PROVISIONED`.
+AWS highly recommends this mode to support replication. In practice, the
+costs are negligible either way.
+- Point-in-time recovery is enabled for the DynamoDB table and cannot be disabled.
+- This module will no longer create an S3 bucket for S3 Server Access Logging.
+If you want to enable logging, you must create a bucket and pass in the
+logging configfuration in the `blue_bucket_logging` and `green_bucket_logging` inputs.
+Note that you can also log access to the bucket via CloudTrail.
+See [Logging options for Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/logging-with-S3.html) for details about the
+differences.
+- Support added for attaching a permission boundary to the IAM role that is
+created for the S3 bucket replication.
+- Many options have been removed and the rest have been renamed.
+This module is now much simpler, and correspondingly more opinionated.
+- Terraform version 1.3.0 or later required
+- Terraform AWS Provider 4.0 or later required
+
+## Migration Guide
+
+Version 0.x of this module will remain available and we will consider pull requests
+to update it, so you can continue to use it if you prefer. However, we recommend
+upgrading to version 1.0, with the caveat that the migration can range from
+easy to difficult.
+
+### Easy
+
+If you were already using encryption everywhere and not using replication
+or logging, then the migration should be easy. Your new module will be
+like this (if you are using Cloud Posse's "null-label" `context.tf`:
+
+```hcl
+module "tfstate" {
+ source = "cloudposse/tfstate-backend/aws"
+ version = "1.0.0"
+
+ providers = {
+ aws.blue = aws
+ aws.green = aws
+ }
+
+ replication_enabled = false
+
+ context = module.this.context
+}
+```
+
+If you were not using `context.tf` then you would continue to provide
+whatever inputs to [null-label](https://github.com/cloudposse/terraform-null-label/)
+you were providing before.
+
+The S3 bucket and DynamoDB table will automatically "moved" by
+`terraform` to their new "Terraform addresses". If Terraform wants
+to delete and recreate either of them because of name changes,
+then you can use the `blue_s3_bucket_name` and `dynamodb_table_name`
+inputs to force the names to be the same as before.
+
+The Terraform plan will show something like this:
+
+- DynamoDB table will be updated in place:
+ - `billing_mode` changed from `PROVISIONED` to `PAY_PER_REQUEST`
+ - `point_in_time_recovery` changed from `false` to `true`
+- `aws_s3_bucket`, `aws_s3_bucket_policy`, and `aws_s3_bucket_public_access_block` will be moved to new Terraform addresses.
+- `aws_s3_bucket_ownership_controls`, `aws_s3_bucket_server_side_encryption_configuration`,
+and `aws_s3_bucket_versioning` will be created.
+
+The conversion of the DynamoDB table from `PROVISIONED` to `PAY_PER_REQUEST`
+may take over 10 minutes. Otherwise, these changes will be very quick.
+
+The module no longer outputs a `terraform_backend_config` file, so that
+you do not need to input things like profile and role ARN. Instead, the
+module provides `blue_backend_config` as a map of the backend configuration
+generated by the module. You can add profile, role ARN, workspace key
+prefix, and any other settings you want to this map to generate a complete
+backend configuration.
diff --git a/docs/terraform.md b/docs/terraform.md
index 39fc4ba..148c6da 100644
--- a/docs/terraform.md
+++ b/docs/terraform.md
@@ -3,107 +3,98 @@
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 0.13.0 |
-| [aws](#requirement\_aws) | >= 2.0 |
-| [local](#requirement\_local) | >= 1.3 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
+| [aws](#requirement\_aws) | >= 4.0 |
## Providers
| Name | Version |
|------|---------|
-| [aws](#provider\_aws) | >= 2.0 |
-| [local](#provider\_local) | >= 1.3 |
+| [aws](#provider\_aws) | >= 4.0 |
+| [aws.blue](#provider\_aws.blue) | >= 4.0 |
+| [aws.green](#provider\_aws.green) | >= 4.0 |
## Modules
| Name | Source | Version |
|------|--------|---------|
-| [dynamodb\_table\_label](#module\_dynamodb\_table\_label) | cloudposse/label/null | 0.25.0 |
-| [log\_storage](#module\_log\_storage) | cloudposse/s3-log-storage/aws | 0.26.0 |
+| [blue\_bucket](#module\_blue\_bucket) | ./modules/s3-bucket | n/a |
+| [blue\_label](#module\_blue\_label) | cloudposse/label/null | 0.25.0 |
+| [dynamodb\_label](#module\_dynamodb\_label) | cloudposse/label/null | 0.25.0 |
+| [green\_bucket](#module\_green\_bucket) | ./modules/s3-bucket | n/a |
+| [green\_label](#module\_green\_label) | cloudposse/label/null | 0.25.0 |
+| [region\_utils](#module\_region\_utils) | cloudposse/utils/aws | 1.1.0 |
+| [replication\_label](#module\_replication\_label) | cloudposse/label/null | 0.25.0 |
| [this](#module\_this) | cloudposse/label/null | 0.25.0 |
## Resources
| Name | Type |
|------|------|
-| [aws_dynamodb_table.with_server_side_encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
-| [aws_dynamodb_table.without_server_side_encryption](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
+| [aws_dynamodb_table.locks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
| [aws_iam_policy.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
-| [aws_s3_bucket.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
-| [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
-| [local_file.terraform_backend_config](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
-| [aws_iam_policy_document.prevent_unencrypted_uploads](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_s3_bucket_replication_configuration.blue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource |
+| [aws_s3_bucket_replication_configuration.green](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource |
| [aws_iam_policy_document.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
-| [aws_iam_policy_document.replication_sts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
-| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
+| [aws_iam_policy_document.replication_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_kms_alias.blue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_alias) | data source |
+| [aws_kms_alias.green](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_alias) | data source |
+| [aws_region.blue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
+| [aws_region.green](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [acl](#input\_acl) | The canned ACL to apply to the S3 bucket | `string` | `"private"` | no |
| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no |
-| [arn\_format](#input\_arn\_format) | ARN format to be used. May be changed to support deployment in GovCloud/China regions. | `string` | `"arn:aws"` | no |
| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no |
-| [billing\_mode](#input\_billing\_mode) | DynamoDB billing mode | `string` | `"PROVISIONED"` | no |
-| [block\_public\_acls](#input\_block\_public\_acls) | Whether Amazon S3 should block public ACLs for this bucket | `bool` | `true` | no |
-| [block\_public\_policy](#input\_block\_public\_policy) | Whether Amazon S3 should block public bucket policies for this bucket | `bool` | `true` | no |
-| [bucket\_enabled](#input\_bucket\_enabled) | Whether to create the s3 bucket. | `bool` | `true` | no |
+| [blue\_bucket\_logging](#input\_blue\_bucket\_logging) | Destination for S3 Server Access Logs for the blue bucket. | list(object({
target_bucket = string
target_prefix = string
}))
| `[]` | no |
+| [blue\_kms\_key\_arn](#input\_blue\_kms\_key\_arn) | The KMS Key ARN for encrypting object (SSE-KMS) in the blue bucket. Default is to use default (SSE-S3) key.
Note: If you are not using the default key and have replication enabled, you must grant the replication role
permission to use the key (`kms:Encrypt` and `kms:Decrypt`). | `list(string)` | `[]` | no |
+| [blue\_s3\_bucket\_name](#input\_blue\_s3\_bucket\_name) | S3 bucket name for bucket in blue region. If not provided, the name will be generated from context. | `list(string)` | `[]` | no |
| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` | {
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no |
| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no |
-| [dynamodb\_enabled](#input\_dynamodb\_enabled) | Whether to create the dynamodb table. | `bool` | `true` | no |
-| [dynamodb\_table\_name](#input\_dynamodb\_table\_name) | Override the name of the DynamoDB table which defaults to using `module.dynamodb_table_label.id` | `string` | `null` | no |
-| [enable\_point\_in\_time\_recovery](#input\_enable\_point\_in\_time\_recovery) | Enable DynamoDB point-in-time recovery | `bool` | `true` | no |
-| [enable\_public\_access\_block](#input\_enable\_public\_access\_block) | Enable Bucket Public Access Block | `bool` | `true` | no |
-| [enable\_server\_side\_encryption](#input\_enable\_server\_side\_encryption) | Enable DynamoDB server-side encryption | `bool` | `true` | no |
+| [dynamodb\_table\_name](#input\_dynamodb\_table\_name) | The name of the DynamoDB table. If not provided, the name will be generated from context. | `list(string)` | `[]` | no |
| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
-| [force\_destroy](#input\_force\_destroy) | A boolean that indicates the S3 bucket can be destroyed even if it contains objects. These objects are not recoverable | `bool` | `false` | no |
+| [force\_destroy](#input\_force\_destroy) | FOR TESTING ONLY! When set to true, `terraform destroy` will destroy the S3 buckets and all the objects in them.
These objects are not recoverable even if you have versioning or backups enabled. | `bool` | `false` | no |
+| [green\_bucket\_logging](#input\_green\_bucket\_logging) | Destination for S3 Server Access Logs for the green bucket. | list(object({
target_bucket = string
target_prefix = string
}))
| `[]` | no |
+| [green\_kms\_key\_arn](#input\_green\_kms\_key\_arn) | The KMS Key ARN for encrypting object (SSE-KMS) in the green bucket. Default is to use default (SSE-S3) key. | `list(string)` | `[]` | no |
+| [green\_s3\_bucket\_name](#input\_green\_s3\_bucket\_name) | S3 bucket name for bucket in green region. If not provided, the name will be generated from context. | `list(string)` | `[]` | no |
| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no |
-| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Whether Amazon S3 should ignore public ACLs for this bucket | `bool` | `true` | no |
| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no |
| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no |
| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no |
| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` | [
"default"
]
| no |
-| [logging](#input\_logging) | Bucket access logging configuration. | object({
bucket_name = string
prefix = string
})
| `null` | no |
-| [logging\_bucket\_enabled](#input\_logging\_bucket\_enabled) | Whether to create the s3 access log bucket. | `bool` | `false` | no |
-| [logging\_bucket\_expiration\_days](#input\_logging\_bucket\_expiration\_days) | Whether to create the s3 access log bucket. | `number` | `90` | no |
-| [logging\_bucket\_glacier\_transition\_days](#input\_logging\_bucket\_glacier\_transition\_days) | Whether to create the s3 access log bucket. | `number` | `60` | no |
-| [logging\_bucket\_standard\_transition\_days](#input\_logging\_bucket\_standard\_transition\_days) | Whether to create the s3 access log bucket. | `number` | `30` | no |
-| [mfa\_delete](#input\_mfa\_delete) | A boolean that indicates that versions of S3 objects can only be deleted with MFA. ( Terraform cannot apply changes of this value; https://github.com/terraform-providers/terraform-provider-aws/issues/629 ) | `bool` | `false` | no |
+| [lock\_table\_enabled](#input\_lock\_table\_enabled) | Set true to create a DynamoDB table to provide Terraform state locking. Highly recommended.
If replication is enabled, a global DynamoDB table will be created in both the blue and green regions. | `bool` | `true` | no |
| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no |
| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no |
-| [prevent\_unencrypted\_uploads](#input\_prevent\_unencrypted\_uploads) | Prevent uploads of unencrypted objects to S3 | `bool` | `true` | no |
-| [profile](#input\_profile) | AWS profile name as set in the shared credentials file | `string` | `""` | no |
-| [read\_capacity](#input\_read\_capacity) | DynamoDB read capacity units | `number` | `5` | no |
+| [permissions\_boundary](#input\_permissions\_boundary) | The ARN of the policy that sets the permissions boundary for the IAM role used for replication. | `list(string)` | `[]` | no |
| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
-| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Whether Amazon S3 should restrict public bucket policies for this bucket | `bool` | `true` | no |
-| [role\_arn](#input\_role\_arn) | The role to be assumed | `string` | `""` | no |
-| [s3\_bucket\_name](#input\_s3\_bucket\_name) | S3 bucket name. If not provided, the name will be generated by the label module in the format namespace-stage-name | `string` | `""` | no |
-| [s3\_replica\_bucket\_arn](#input\_s3\_replica\_bucket\_arn) | The ARN of the S3 replica bucket (destination) | `string` | `""` | no |
-| [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replica_bucket_arn` to enable replication | `bool` | `false` | no |
+| [replication\_enabled](#input\_replication\_enabled) | Set true to enable bidirectional replication and creation of hot standby for quick failover. Highly recommended.
If set to `false`, the "green" configuration will be ignored.
Replication is not supported in AWS GovCloud (US) because S3 Replication Time Control (S3 RTC) is not available. | `bool` | `true` | no |
+| [replication\_role\_name](#input\_replication\_role\_name) | The name to give to the IAM role created for replication. If not provided, the name will be generated from context. | `list(string)` | `[]` | no |
| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no |
| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no |
-| [terraform\_backend\_config\_file\_name](#input\_terraform\_backend\_config\_file\_name) | Name of terraform backend config file | `string` | `"terraform.tf"` | no |
-| [terraform\_backend\_config\_file\_path](#input\_terraform\_backend\_config\_file\_path) | Directory for the terraform backend config file, usually `.`. The default is to create no file. | `string` | `""` | no |
-| [terraform\_backend\_config\_template\_file](#input\_terraform\_backend\_config\_template\_file) | The path to the template used to generate the config file | `string` | `""` | no |
-| [terraform\_state\_file](#input\_terraform\_state\_file) | The path to the state file inside the bucket | `string` | `"terraform.tfstate"` | no |
-| [terraform\_version](#input\_terraform\_version) | The minimum required terraform version | `string` | `"0.12.2"` | no |
-| [write\_capacity](#input\_write\_capacity) | DynamoDB write capacity units | `number` | `5` | no |
## Outputs
| Name | Description |
|------|-------------|
-| [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | DynamoDB table ARN |
+| [blue\_backend\_config](#output\_blue\_backend\_config) | Backend configuration for the blue Terraform state |
+| [dynamodb\_table\_arn](#output\_dynamodb\_table\_arn) | (Deprecated, use `dynamodb_table_arns` instead) DynamoDB table ARN |
+| [dynamodb\_table\_arns](#output\_dynamodb\_table\_arns) | Map (by region) of the ARNs for DynamoDB tables created by this module to store Terraform state locks.
Note that in general you should only refer to the tables by name and region, not by ARN. |
| [dynamodb\_table\_id](#output\_dynamodb\_table\_id) | DynamoDB table ID |
| [dynamodb\_table\_name](#output\_dynamodb\_table\_name) | DynamoDB table name |
-| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | S3 bucket ARN |
-| [s3\_bucket\_domain\_name](#output\_s3\_bucket\_domain\_name) | S3 bucket domain name |
-| [s3\_bucket\_id](#output\_s3\_bucket\_id) | S3 bucket ID |
-| [terraform\_backend\_config](#output\_terraform\_backend\_config) | Rendered Terraform backend config file |
+| [green\_backend\_config](#output\_green\_backend\_config) | Backend configuration for the green Terraform state |
+| [s3\_bucket\_arn](#output\_s3\_bucket\_arn) | (Deprecated, use `s3_bucket_arns` instead): S3 bucket ARN |
+| [s3\_bucket\_arns](#output\_s3\_bucket\_arns) | Map (by region) of the ARNs of the created S3 buckets |
+| [s3\_bucket\_domain\_name](#output\_s3\_bucket\_domain\_name) | (Deprecated, use `s3_bucket_domains` instead): S3 bucket domain name |
+| [s3\_bucket\_domains](#output\_s3\_bucket\_domains) | Map (by region) of the domain names of the created S3 buckets |
+| [s3\_bucket\_id](#output\_s3\_bucket\_id) | (Deprecated, use `s3_bucket_ids` instead): S3 bucket ID |
+| [s3\_bucket\_ids](#output\_s3\_bucket\_ids) | Map (by region) of the IDs (names) of the created S3 buckets |
+| [s3\_bucket\_kms\_key\_arns](#output\_s3\_bucket\_kms\_key\_arns) | Map (by bucket name) of the ARNs of the KMS keys used to encrypt the created S3 buckets |
+| [sns\_topic\_arns](#output\_sns\_topic\_arns) | Map (by region) of the ARNs for the SNS Topics created by this module to receive
replication event notifications regarding the created S3 bucket |
diff --git a/dynamodb.tf b/dynamodb.tf
new file mode 100644
index 0000000..72f6081
--- /dev/null
+++ b/dynamodb.tf
@@ -0,0 +1,45 @@
+locals {
+ lock_table_enabled = local.enabled && var.lock_table_enabled
+}
+# All DynamoDB tables are now encrypted at rest by default. No need to set this explicitly.
+# see https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table#server_side_encryption
+resource "aws_dynamodb_table" "locks" {
+ count = local.lock_table_enabled ? 1 : 0
+
+ provider = aws.blue
+
+ name = length(var.dynamodb_table_name) > 0 ? var.dynamodb_table_name[0] : module.dynamodb_label.id
+
+ # PAY_PER_REQUEST is the recommended billing mode for Global (Replicated) Tables.
+ # The Terraform lock table is very low traffic, so it's a good candidate for PAY_PER_REQUEST anyway.
+ billing_mode = "PAY_PER_REQUEST"
+ # Streams are required for Global Tables.
+ stream_enabled = var.replication_enabled
+ stream_view_type = var.replication_enabled ? "NEW_AND_OLD_IMAGES" : null
+
+ # https://www.terraform.io/docs/backends/types/s3.html#dynamodb_table
+ hash_key = "LockID"
+
+ attribute {
+ name = "LockID"
+ type = "S"
+ }
+
+ point_in_time_recovery {
+ enabled = true
+ }
+
+ dynamic "replica" {
+ for_each = var.replication_enabled ? [true] : []
+ content {
+ region_name = local.green_region
+ point_in_time_recovery = true
+ }
+ }
+
+ tags = module.dynamodb_label.tags
+
+ # Recommended lifecycle rules. See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table
+ lifecycle { ignore_changes = [replica] }
+}
+
diff --git a/examples/complete/backend-test/main.tf b/examples/complete/backend-test/main.tf
new file mode 100644
index 0000000..1f4386f
--- /dev/null
+++ b/examples/complete/backend-test/main.tf
@@ -0,0 +1,19 @@
+// This file is a minimal partial configuration of a Terraform component.
+// We fill in the backend configuration with Terratest.
+// All we really want to do is verify that some data is stored in the backend correctly,
+// so we just store and test outputs without creating any resources.
+
+terraform {
+ backend "s3" {}
+ required_version = ">= 1.0"
+}
+
+variable "test" {
+ type = string
+ description = "Some input to save as output, for testing"
+}
+
+output "test" {
+ value = var.test
+ description = "Some output that should be saved in the backend"
+}
diff --git a/examples/complete/backend-workspace-test/main.tf b/examples/complete/backend-workspace-test/main.tf
new file mode 100644
index 0000000..1f4386f
--- /dev/null
+++ b/examples/complete/backend-workspace-test/main.tf
@@ -0,0 +1,19 @@
+// This file is a minimal partial configuration of a Terraform component.
+// We fill in the backend configuration with Terratest.
+// All we really want to do is verify that some data is stored in the backend correctly,
+// so we just store and test outputs without creating any resources.
+
+terraform {
+ backend "s3" {}
+ required_version = ">= 1.0"
+}
+
+variable "test" {
+ type = string
+ description = "Some input to save as output, for testing"
+}
+
+output "test" {
+ value = var.test
+ description = "Some output that should be saved in the backend"
+}
diff --git a/examples/complete/fixtures.us-east-2.tfvars b/examples/complete/fixtures.us-east-2.tfvars
index c386faa..324e075 100644
--- a/examples/complete/fixtures.us-east-2.tfvars
+++ b/examples/complete/fixtures.us-east-2.tfvars
@@ -1,7 +1,7 @@
-enabled = true
-
region = "us-east-2"
+green_region = "us-west-2"
+
namespace = "eg"
environment = "use2"
diff --git a/examples/complete/main.tf b/examples/complete/main.tf
index 71ca3c0..3495f2f 100644
--- a/examples/complete/main.tf
+++ b/examples/complete/main.tf
@@ -1,14 +1,15 @@
-provider "aws" {
- region = var.region
-}
-module "tfstate_backend" {
- source = "../../"
+module "tfstate" {
+ source = "../.."
- force_destroy = true
+ providers = {
+ aws.blue = aws
+ aws.green = aws.green
+ }
- bucket_enabled = var.bucket_enabled
- dynamodb_enabled = var.dynamodb_enabled
+ force_destroy = true
+ lock_table_enabled = true
+ replication_enabled = true
context = module.this.context
}
diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf
index 16f2400..77b3a30 100644
--- a/examples/complete/outputs.tf
+++ b/examples/complete/outputs.tf
@@ -1,14 +1,47 @@
-output "s3_bucket_id" {
- value = module.tfstate_backend.s3_bucket_id
- description = "S3 bucket ID"
+output "s3_bucket_ids" {
+ description = "Names of the created S3 buckets"
+ value = module.tfstate.s3_bucket_ids
+}
+
+output "s3_bucket_arns" {
+ description = "ARNs of the created S3 buckets"
+ value = module.tfstate.s3_bucket_arns
+}
+
+output "s3_bucket_domains" {
+ description = "Map (by region) of the domain names of the created S3 buckets"
+ value = module.tfstate.s3_bucket_domains
+}
+
+output "s3_bucket_kms_key_arns" {
+ description = "Map (by S3 bucket name) of the KMS key ARNs used to encrypt the S3 buckets"
+ value = module.tfstate.s3_bucket_kms_key_arns
}
output "dynamodb_table_name" {
- value = module.tfstate_backend.dynamodb_table_name
description = "DynamoDB table name"
+ value = module.tfstate.dynamodb_table_name
+}
+
+output "dynamodb_table_arns" {
+ description = "DynamoDB table ARNs"
+ value = module.tfstate.dynamodb_table_arns
+}
+
+output "sns_topic_arns" {
+ description = <<-EOT
+ Map (by region) of the ARNs for the SNS Topics created by this module to receive
+ replication event notifications regarding the created S3 bucket
+ EOT
+ value = module.tfstate.sns_topic_arns
+}
+
+output "blue_backend_config" {
+ value = module.tfstate.blue_backend_config
+ description = "Backend configuration for the blue Terraform state"
}
-output "dynamodb_table_id" {
- value = module.tfstate_backend.dynamodb_table_id
- description = "DynamoDB table ID"
+output "green_backend_config" {
+ value = module.tfstate.green_backend_config
+ description = "Backend configuration for the green Terraform state"
}
diff --git a/examples/complete/providers.tf b/examples/complete/providers.tf
new file mode 100644
index 0000000..11e4126
--- /dev/null
+++ b/examples/complete/providers.tf
@@ -0,0 +1,8 @@
+provider "aws" {
+ region = var.region
+}
+
+provider "aws" {
+ alias = "green"
+ region = coalesce(var.green_region, var.region)
+}
diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf
index bb1e49d..f81750b 100644
--- a/examples/complete/variables.tf
+++ b/examples/complete/variables.tf
@@ -1,170 +1,11 @@
variable "region" {
- type = string
-}
-
-variable "arn_format" {
- type = string
- default = "arn:aws"
- description = "ARN format to be used. May be changed to support deployment in GovCloud/China regions."
-}
-
-variable "acl" {
- type = string
- description = "The canned ACL to apply to the S3 bucket"
- default = "private"
-}
-
-variable "billing_mode" {
- default = "PROVISIONED"
- description = "DynamoDB billing mode"
-}
-
-variable "read_capacity" {
- default = 5
- description = "DynamoDB read capacity units"
-}
-
-variable "write_capacity" {
- default = 5
- description = "DynamoDB write capacity units"
-}
-
-variable "force_destroy" {
- type = bool
- description = "A boolean that indicates the S3 bucket can be destroyed even if it contains objects. These objects are not recoverable"
- default = false
-}
-
-variable "mfa_delete" {
- type = bool
- description = "A boolean that indicates that versions of S3 objects can only be deleted with MFA. ( Terraform cannot apply changes of this value; https://github.com/terraform-providers/terraform-provider-aws/issues/629 )"
- default = false
-}
-
-variable "enable_point_in_time_recovery" {
- type = bool
- description = "Enable DynamoDB point-in-time recovery"
- default = true
-}
-
-variable "enable_server_side_encryption" {
- type = bool
- description = "Enable DynamoDB server-side encryption"
- default = true
-}
-
-variable "enable_public_access_block" {
- type = bool
- description = "Enable Bucket Public Access Block"
- default = true
-}
-
-variable "block_public_acls" {
- type = bool
- description = "Whether Amazon S3 should block public ACLs for this bucket"
- default = true
-}
-
-variable "ignore_public_acls" {
- type = bool
- description = "Whether Amazon S3 should ignore public ACLs for this bucket"
- default = true
-}
-
-variable "block_public_policy" {
- description = "Whether Amazon S3 should block public bucket policies for this bucket"
- default = true
-}
-
-variable "restrict_public_buckets" {
- type = bool
- description = "Whether Amazon S3 should restrict public bucket policies for this bucket"
- default = true
-}
-
-variable "prevent_unencrypted_uploads" {
- type = bool
- default = true
- description = "Prevent uploads of unencrypted objects to S3"
-}
-
-variable "profile" {
- type = string
- default = ""
- description = "AWS profile name as set in the shared credentials file"
-}
-
-variable "role_arn" {
- type = string
- default = ""
- description = "The role to be assumed"
-}
-
-variable "terraform_backend_config_file_name" {
- type = string
- default = "terraform.tf"
- description = "Name of terraform backend config file"
-}
-
-variable "terraform_backend_config_file_path" {
type = string
- default = ""
- description = "Directory for the terraform backend config file, usually `.`. The default is to create no file."
+ description = "The region you plan to use in regular operations. In the module, referred to as the \"blue\" region."
}
-variable "terraform_backend_config_template_file" {
+variable "green_region" {
type = string
- default = ""
- description = "The path to the template used to generate the config file"
-}
-
-variable "terraform_version" {
- type = string
- default = "0.12.2"
- description = "The minimum required terraform version"
-}
-
-variable "terraform_state_file" {
- type = string
- default = "terraform.tfstate"
- description = "The path to the state file inside the bucket"
-}
-
-variable "s3_bucket_name" {
- type = string
- default = ""
- description = "S3 bucket name. If not provided, the name will be generated by the label module in the format namespace-stage-name"
-}
-
-variable "s3_replication_enabled" {
- type = bool
- default = false
- description = "Set this to true and specify `s3_replica_bucket_arn` to enable replication"
-}
-
-variable "s3_replica_bucket_arn" {
- type = string
- default = ""
- description = "The ARN of the S3 replica bucket (destination)"
-}
-
-variable "logging" {
- type = object({
- bucket_name = string
- prefix = string
- })
default = null
- description = "Bucket access logging configuration."
+ description = "The region you plan to use when the regular region has problems. In the module, referred to as the \"green\" region."
}
-variable "bucket_enabled" {
- type = bool
- default = true
- description = "Whether to create the s3 bucket."
-}
-
-variable "dynamodb_enabled" {
- type = bool
- default = true
- description = "Whether to create the dynamodb table."
-}
diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf
index 2080158..6fac0db 100644
--- a/examples/complete/versions.tf
+++ b/examples/complete/versions.tf
@@ -1,14 +1,10 @@
terraform {
- required_version = ">= 0.13.0"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
source = "hashicorp/aws"
- version = ">= 2.0"
- }
- local = {
- source = "hashicorp/local"
- version = ">= 1.3"
+ version = ">= 4.0.0"
}
}
}
diff --git a/examples/no-replication/context.tf b/examples/no-replication/context.tf
new file mode 100644
index 0000000..5e0ef88
--- /dev/null
+++ b/examples/no-replication/context.tf
@@ -0,0 +1,279 @@
+#
+# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label
+# All other instances of this file should be a copy of that one
+#
+#
+# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf
+# and then place it in your Terraform module to automatically get
+# Cloud Posse's standard configuration inputs suitable for passing
+# to Cloud Posse modules.
+#
+# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf
+#
+# Modules should access the whole context as `module.this.context`
+# to get the input variables with nulls for defaults,
+# for example `context = module.this.context`,
+# and access individual variables as `module.this.`,
+# with final values filled in.
+#
+# For example, when using defaults, `module.this.context.delimiter`
+# will be null, and `module.this.delimiter` will be `-` (hyphen).
+#
+
+module "this" {
+ source = "cloudposse/label/null"
+ version = "0.25.0" # requires Terraform >= 0.13.0
+
+ enabled = var.enabled
+ namespace = var.namespace
+ tenant = var.tenant
+ environment = var.environment
+ stage = var.stage
+ name = var.name
+ delimiter = var.delimiter
+ attributes = var.attributes
+ tags = var.tags
+ additional_tag_map = var.additional_tag_map
+ label_order = var.label_order
+ regex_replace_chars = var.regex_replace_chars
+ id_length_limit = var.id_length_limit
+ label_key_case = var.label_key_case
+ label_value_case = var.label_value_case
+ descriptor_formats = var.descriptor_formats
+ labels_as_tags = var.labels_as_tags
+
+ context = var.context
+}
+
+# Copy contents of cloudposse/terraform-null-label/variables.tf here
+
+variable "context" {
+ type = any
+ default = {
+ enabled = true
+ namespace = null
+ tenant = null
+ environment = null
+ stage = null
+ name = null
+ delimiter = null
+ attributes = []
+ tags = {}
+ additional_tag_map = {}
+ regex_replace_chars = null
+ label_order = []
+ id_length_limit = null
+ label_key_case = null
+ label_value_case = null
+ descriptor_formats = {}
+ # Note: we have to use [] instead of null for unset lists due to
+ # https://github.com/hashicorp/terraform/issues/28137
+ # which was not fixed until Terraform 1.0.0,
+ # but we want the default to be all the labels in `label_order`
+ # and we want users to be able to prevent all tag generation
+ # by setting `labels_as_tags` to `[]`, so we need
+ # a different sentinel to indicate "default"
+ labels_as_tags = ["unset"]
+ }
+ description = <<-EOT
+ Single object for setting entire context at once.
+ See description of individual variables for details.
+ Leave string and numeric variables as `null` to use default value.
+ Individual variable settings (non-null) override settings in context object,
+ except for attributes, tags, and additional_tag_map, which are merged.
+ EOT
+
+ validation {
+ condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+
+ validation {
+ condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
+
+variable "enabled" {
+ type = bool
+ default = null
+ description = "Set to false to prevent the module from creating any resources"
+}
+
+variable "namespace" {
+ type = string
+ default = null
+ description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique"
+}
+
+variable "tenant" {
+ type = string
+ default = null
+ description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for"
+}
+
+variable "environment" {
+ type = string
+ default = null
+ description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'"
+}
+
+variable "stage" {
+ type = string
+ default = null
+ description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'"
+}
+
+variable "name" {
+ type = string
+ default = null
+ description = <<-EOT
+ ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
+ This is the only ID element not also included as a `tag`.
+ The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input.
+ EOT
+}
+
+variable "delimiter" {
+ type = string
+ default = null
+ description = <<-EOT
+ Delimiter to be used between ID elements.
+ Defaults to `-` (hyphen). Set to `""` to use no delimiter at all.
+ EOT
+}
+
+variable "attributes" {
+ type = list(string)
+ default = []
+ description = <<-EOT
+ ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
+ in the order they appear in the list. New attributes are appended to the
+ end of the list. The elements of the list are joined by the `delimiter`
+ and treated as a single ID element.
+ EOT
+}
+
+variable "labels_as_tags" {
+ type = set(string)
+ default = ["default"]
+ description = <<-EOT
+ Set of labels (ID elements) to include as tags in the `tags` output.
+ Default is to include all labels.
+ Tags with empty values will not be included in the `tags` output.
+ Set to `[]` to suppress all generated tags.
+ **Notes:**
+ The value of the `name` tag, if included, will be the `id`, not the `name`.
+ Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
+ changed in later chained modules. Attempts to change it will be silently ignored.
+ EOT
+}
+
+variable "tags" {
+ type = map(string)
+ default = {}
+ description = <<-EOT
+ Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
+ Neither the tag keys nor the tag values will be modified by this module.
+ EOT
+}
+
+variable "additional_tag_map" {
+ type = map(string)
+ default = {}
+ description = <<-EOT
+ Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
+ This is for some rare cases where resources want additional configuration of tags
+ and therefore take a list of maps with tag key, value, and additional configuration.
+ EOT
+}
+
+variable "label_order" {
+ type = list(string)
+ default = null
+ description = <<-EOT
+ The order in which the labels (ID elements) appear in the `id`.
+ Defaults to ["namespace", "environment", "stage", "name", "attributes"].
+ You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present.
+ EOT
+}
+
+variable "regex_replace_chars" {
+ type = string
+ default = null
+ description = <<-EOT
+ Terraform regular expression (regex) string.
+ Characters matching the regex will be removed from the ID elements.
+ If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits.
+ EOT
+}
+
+variable "id_length_limit" {
+ type = number
+ default = null
+ description = <<-EOT
+ Limit `id` to this many characters (minimum 6).
+ Set to `0` for unlimited length.
+ Set to `null` for keep the existing setting, which defaults to `0`.
+ Does not affect `id_full`.
+ EOT
+ validation {
+ condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
+ error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
+ }
+}
+
+variable "label_key_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ Controls the letter case of the `tags` keys (label names) for tags generated by this module.
+ Does not affect keys of tags passed in via the `tags` input.
+ Possible values: `lower`, `title`, `upper`.
+ Default value: `title`.
+ EOT
+
+ validation {
+ condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+}
+
+variable "label_value_case" {
+ type = string
+ default = null
+ description = <<-EOT
+ Controls the letter case of ID elements (labels) as included in `id`,
+ set as tag values, and output by this module individually.
+ Does not affect values of tags passed in via the `tags` input.
+ Possible values: `lower`, `title`, `upper` and `none` (no transformation).
+ Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
+ Default value: `lower`.
+ EOT
+
+ validation {
+ condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
+
+variable "descriptor_formats" {
+ type = any
+ default = {}
+ description = <<-EOT
+ Describe additional descriptors to be output in the `descriptors` output map.
+ Map of maps. Keys are names of descriptors. Values are maps of the form
+ `{
+ format = string
+ labels = list(string)
+ }`
+ (Type is `any` so the map values can later be enhanced to provide additional options.)
+ `format` is a Terraform format string to be passed to the `format()` function.
+ `labels` is a list of labels, in order, to pass to `format()` function.
+ Label values will be normalized before being passed to `format()` so they will be
+ identical to how they appear in `id`.
+ Default is `{}` (`descriptors` output will be empty).
+ EOT
+}
+
+#### End of copy of cloudposse/terraform-null-label/variables.tf
diff --git a/examples/no-replication/fixtures.us-east-2.tfvars b/examples/no-replication/fixtures.us-east-2.tfvars
new file mode 100644
index 0000000..c386faa
--- /dev/null
+++ b/examples/no-replication/fixtures.us-east-2.tfvars
@@ -0,0 +1,11 @@
+enabled = true
+
+region = "us-east-2"
+
+namespace = "eg"
+
+environment = "use2"
+
+stage = "test"
+
+name = "terraform-tfstate-backend"
diff --git a/examples/no-replication/main.tf b/examples/no-replication/main.tf
new file mode 100644
index 0000000..e65d5e4
--- /dev/null
+++ b/examples/no-replication/main.tf
@@ -0,0 +1,18 @@
+provider "aws" {
+ region = var.region
+}
+
+module "tfstate_backend" {
+ source = "../../"
+
+ providers = {
+ aws.blue = aws
+ aws.green = aws
+ }
+
+ force_destroy = true
+
+ replication_enabled = false
+
+ context = module.this.context
+}
diff --git a/examples/no-replication/outputs.tf b/examples/no-replication/outputs.tf
new file mode 100644
index 0000000..aa57b44
--- /dev/null
+++ b/examples/no-replication/outputs.tf
@@ -0,0 +1,19 @@
+output "s3_bucket_id" {
+ value = module.tfstate_backend.s3_bucket_id
+ description = "S3 bucket ID"
+}
+
+output "dynamodb_table_name" {
+ value = module.tfstate_backend.dynamodb_table_name
+ description = "DynamoDB table name"
+}
+
+output "dynamodb_table_id" {
+ value = module.tfstate_backend.dynamodb_table_id
+ description = "DynamoDB table ID"
+}
+
+output "backend_config" {
+ value = module.tfstate_backend.blue_backend_config
+ description = "Terraform state backend configuration (as a map)"
+}
diff --git a/examples/no-replication/variables.tf b/examples/no-replication/variables.tf
new file mode 100644
index 0000000..d2cdd03
--- /dev/null
+++ b/examples/no-replication/variables.tf
@@ -0,0 +1,3 @@
+variable "region" {
+ type = string
+}
diff --git a/examples/no-replication/versions.tf b/examples/no-replication/versions.tf
new file mode 100644
index 0000000..84482d0
--- /dev/null
+++ b/examples/no-replication/versions.tf
@@ -0,0 +1,14 @@
+terraform {
+ required_version = ">= 1.3.0"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 4.0"
+ }
+ local = {
+ source = "hashicorp/local"
+ version = ">= 1.3"
+ }
+ }
+}
diff --git a/labels.tf b/labels.tf
new file mode 100644
index 0000000..7bf6727
--- /dev/null
+++ b/labels.tf
@@ -0,0 +1,57 @@
+locals {
+ az_map = module.region_utils.region_az_alt_code_maps["to_short"]
+ blue_environment = try(local.az_map[local.blue_region], "blue")
+ green_environment = try(local.az_map[local.green_region], "green")
+}
+
+module "region_utils" {
+ source = "cloudposse/utils/aws"
+ version = "1.1.0"
+
+ context = module.this.context
+}
+
+module "blue_label" {
+ source = "cloudposse/label/null"
+ version = "0.25.0"
+
+ environment = local.replication_enabled ? local.blue_environment : module.this.environment
+
+ id_length_limit = 63
+
+ context = module.this.context
+}
+
+module "green_label" {
+ source = "cloudposse/label/null"
+ version = "0.25.0"
+
+ environment = local.green_environment
+
+ id_length_limit = 63
+
+ context = module.this.context
+}
+
+module "replication_label" {
+ source = "cloudposse/label/null"
+ version = "0.25.0"
+
+ enabled = local.replication_enabled
+ environment = "gbl"
+ attributes = ["replication"]
+
+ id_length_limit = 64
+
+ context = module.this.context
+}
+
+module "dynamodb_label" {
+ source = "cloudposse/label/null"
+ version = "0.25.0"
+
+ environment = var.replication_enabled ? "gbl" : module.blue_label.environment
+ attributes = ["lock"]
+
+ context = module.this.context
+}
diff --git a/main.tf b/main.tf
index 3c19190..1384633 100644
--- a/main.tf
+++ b/main.tf
@@ -1,280 +1,74 @@
-locals {
- enabled = module.this.enabled
-
- bucket_enabled = local.enabled && var.bucket_enabled
- dynamodb_enabled = local.enabled && var.dynamodb_enabled
-
- dynamodb_table_name = local.dynamodb_enabled ? coalesce(var.dynamodb_table_name, module.dynamodb_table_label.id) : ""
-
- prevent_unencrypted_uploads = local.enabled && var.prevent_unencrypted_uploads && var.enable_server_side_encryption
-
- policy = local.prevent_unencrypted_uploads ? join(
- "",
- data.aws_iam_policy_document.prevent_unencrypted_uploads.*.json
- ) : ""
-
- terraform_backend_config_file = format(
- "%s/%s",
- var.terraform_backend_config_file_path,
- var.terraform_backend_config_file_name
- )
-
- terraform_backend_config_template_file = var.terraform_backend_config_template_file != "" ? var.terraform_backend_config_template_file : "${path.module}/templates/terraform.tf.tpl"
-
- terraform_backend_config_content = templatefile(local.terraform_backend_config_template_file, {
- region = data.aws_region.current.name
- bucket = join("", aws_s3_bucket.default.*.id)
-
- dynamodb_table = local.dynamodb_enabled ? element(
- coalescelist(
- aws_dynamodb_table.with_server_side_encryption.*.name,
- aws_dynamodb_table.without_server_side_encryption.*.name
- ),
- 0
- ) : ""
-
- encrypt = var.enable_server_side_encryption ? "true" : "false"
- role_arn = var.role_arn
- profile = var.profile
- terraform_version = var.terraform_version
- terraform_state_file = var.terraform_state_file
- namespace = var.namespace
- stage = var.stage
- environment = var.environment
- name = var.name
- })
-
- bucket_name = var.s3_bucket_name != "" ? var.s3_bucket_name : module.this.id
-
- logging_bucket_enabled = local.bucket_enabled && var.logging_bucket_enabled
- logging_bucket_name_default = try(var.logging["bucket_name"], "${local.bucket_name}-logs")
- logging_prefix_default = try(var.logging["prefix"], "logs/")
- logging_bucket_name = local.logging_bucket_enabled ? module.log_storage.bucket_id : local.logging_bucket_name_default
- logging_prefix = local.logging_bucket_enabled ? module.log_storage.prefix : local.logging_prefix_default
-}
-
-data "aws_iam_policy_document" "prevent_unencrypted_uploads" {
- count = local.prevent_unencrypted_uploads ? 1 : 0
-
- statement {
- sid = "DenyIncorrectEncryptionHeader"
-
- effect = "Deny"
-
- principals {
- identifiers = ["*"]
- type = "AWS"
- }
-
- actions = [
- "s3:PutObject"
- ]
-
- resources = [
- "${var.arn_format}:s3:::${local.bucket_name}/*",
- ]
-
- condition {
- test = "StringNotEquals"
- variable = "s3:x-amz-server-side-encryption"
-
- values = [
- "AES256",
- "aws:kms"
- ]
- }
- }
-
- statement {
- sid = "DenyUnEncryptedObjectUploads"
-
- effect = "Deny"
-
- principals {
- identifiers = ["*"]
- type = "AWS"
- }
-
- actions = [
- "s3:PutObject"
- ]
-
- resources = [
- "${var.arn_format}:s3:::${local.bucket_name}/*",
- ]
-
- condition {
- test = "Null"
- variable = "s3:x-amz-server-side-encryption"
-
- values = [
- "true"
- ]
- }
- }
-
- statement {
- sid = "EnforceTlsRequestsOnly"
-
- effect = "Deny"
- principals {
- type = "AWS"
- identifiers = ["*"]
- }
-
- actions = ["s3:*"]
-
- resources = [
- "${var.arn_format}:s3:::${local.bucket_name}",
- "${var.arn_format}:s3:::${local.bucket_name}/*",
- ]
+locals {
+ enabled = module.this.enabled
+ blue_region = one(data.aws_region.blue[*].name)
+ green_region = one(data.aws_region.green[*].name)
+ blue_kms_key_arn = length(var.blue_kms_key_arn) == 0 ? one(data.aws_kms_alias.blue[*].arn) : one(var.blue_kms_key_arn[*])
+ green_kms_key_arn = length(var.green_kms_key_arn) == 0 ? one(data.aws_kms_alias.green[*].arn) : one(var.green_kms_key_arn[*])
+
+ blue_backend_config = !local.enabled || try(module.blue_bucket[0].bucket, null) == null ? null : merge({
+ encrypt = true
+ key = "terraform.tfstate"
+ region = module.blue_bucket[0].bucket.region
+ },
+ var.replication_enabled ? {
+ bucket = "multi-region"
+ endpoint = module.blue_bucket[0].bucket.bucket_regional_domain_name
+ force_path_style = true } : {
+ bucket = module.blue_bucket[0].bucket.id
+ },
+ var.lock_table_enabled ? {
+ dynamodb_table = one(aws_dynamodb_table.locks[*].id)
+ } : {})
+
+ green_backend_config = !local.replication_enabled || try(module.green_bucket[0].bucket, null) == null ? null : merge({
+ encrypt = true
+ key = "terraform.tfstate"
+ region = module.green_bucket[0].bucket.region
+ },
+ var.replication_enabled ? {
+ bucket = "multi-region"
+ endpoint = module.green_bucket[0].bucket.bucket_regional_domain_name
+ force_path_style = true } : {
+ bucket = module.green_bucket[0].bucket.id
+ },
+ var.lock_table_enabled ? {
+ dynamodb_table = one(aws_dynamodb_table.locks[*].id)
+ } : {})
- condition {
- test = "Bool"
- variable = "aws:SecureTransport"
- values = ["false"]
- }
- }
}
-module "log_storage" {
- source = "cloudposse/s3-log-storage/aws"
- version = "0.26.0"
-
- enabled = local.logging_bucket_enabled
- access_log_bucket_prefix = local.logging_prefix_default
- acl = "log-delivery-write"
- expiration_days = var.logging_bucket_expiration_days
- glacier_transition_days = var.logging_bucket_glacier_transition_days
- name = local.logging_bucket_name_default
- standard_transition_days = var.logging_bucket_standard_transition_days
+data "aws_region" "blue" {
+ count = local.enabled ? 1 : 0
- context = module.this.context
+ provider = aws.blue
}
-resource "aws_s3_bucket" "default" {
- count = local.bucket_enabled ? 1 : 0
-
- #bridgecrew:skip=BC_AWS_S3_13:Skipping `Enable S3 Bucket Logging` check until Bridgecrew will support dynamic blocks (https://github.com/bridgecrewio/checkov/issues/776).
- #bridgecrew:skip=CKV_AWS_52:Skipping `Ensure S3 bucket has MFA delete enabled` check due to issues operating with `mfa_delete` in terraform
- bucket = substr(local.bucket_name, 0, 63)
- acl = var.acl
- force_destroy = var.force_destroy
- policy = local.policy
-
- versioning {
- enabled = true
- mfa_delete = var.mfa_delete
- }
-
- server_side_encryption_configuration {
- rule {
- apply_server_side_encryption_by_default {
- sse_algorithm = "AES256"
- }
- }
- }
-
- dynamic "replication_configuration" {
- for_each = var.s3_replication_enabled ? toset([var.s3_replica_bucket_arn]) : []
- content {
- role = aws_iam_role.replication[0].arn
+data "aws_region" "green" {
+ count = local.replication_enabled ? 1 : 0
- rules {
- id = module.this.id
- prefix = ""
- status = "Enabled"
+ provider = aws.green
- destination {
- bucket = var.s3_replica_bucket_arn
- storage_class = "STANDARD"
- }
- }
+ lifecycle {
+ postcondition {
+ condition = self.name != local.blue_region
+ error_message = format("If replication is enabled, aws.blue and aws.green providers must reference different AWS regions. Both are %s", local.blue_region)
}
}
-
- dynamic "logging" {
- for_each = var.logging == null ? [] : [1]
- content {
- target_bucket = local.logging_bucket_name
- target_prefix = local.logging_prefix
- }
- }
-
- tags = module.this.tags
}
-resource "aws_s3_bucket_public_access_block" "default" {
- count = local.bucket_enabled && var.enable_public_access_block ? 1 : 0
- bucket = join("", aws_s3_bucket.default.*.id)
- block_public_acls = var.block_public_acls
- ignore_public_acls = var.ignore_public_acls
- block_public_policy = var.block_public_policy
- restrict_public_buckets = var.restrict_public_buckets
-}
-
-module "dynamodb_table_label" {
- source = "cloudposse/label/null"
- version = "0.25.0"
- attributes = ["lock"]
- context = module.this.context
- enabled = local.dynamodb_enabled
-}
-resource "aws_dynamodb_table" "with_server_side_encryption" {
- count = local.dynamodb_enabled && var.enable_server_side_encryption ? 1 : 0
- name = local.dynamodb_table_name
- billing_mode = var.billing_mode
- read_capacity = var.billing_mode == "PROVISIONED" ? var.read_capacity : null
- write_capacity = var.billing_mode == "PROVISIONED" ? var.write_capacity : null
+data "aws_kms_alias" "blue" {
+ count = local.enabled && length(var.blue_kms_key_arn) == 0 ? 1 : 0
- # https://www.terraform.io/docs/backends/types/s3.html#dynamodb_table
- hash_key = "LockID"
-
- server_side_encryption {
- enabled = true
- }
-
- point_in_time_recovery {
- enabled = var.enable_point_in_time_recovery
- }
-
- attribute {
- name = "LockID"
- type = "S"
- }
-
- tags = module.dynamodb_table_label.tags
+ provider = aws.blue
+ name = "alias/aws/s3"
}
-resource "aws_dynamodb_table" "without_server_side_encryption" {
- count = local.dynamodb_enabled && ! var.enable_server_side_encryption ? 1 : 0
- name = local.dynamodb_table_name
- billing_mode = var.billing_mode
- read_capacity = var.billing_mode == "PROVISIONED" ? var.read_capacity : null
- write_capacity = var.billing_mode == "PROVISIONED" ? var.write_capacity : null
-
- # https://www.terraform.io/docs/backends/types/s3.html#dynamodb_table
- hash_key = "LockID"
-
- point_in_time_recovery {
- enabled = var.enable_point_in_time_recovery
- }
-
- attribute {
- name = "LockID"
- type = "S"
- }
+data "aws_kms_alias" "green" {
+ count = local.enabled && length(var.green_kms_key_arn) == 0 ? 1 : 0
- tags = module.dynamodb_table_label.tags
+ provider = aws.green
+ name = "alias/aws/s3"
}
-data "aws_region" "current" {}
-
-resource "local_file" "terraform_backend_config" {
- count = local.enabled && var.terraform_backend_config_file_path != "" ? 1 : 0
- content = local.terraform_backend_config_content
- filename = local.terraform_backend_config_file
- file_permission = "0644"
-}
diff --git a/modules/s3-bucket/main.tf b/modules/s3-bucket/main.tf
new file mode 100644
index 0000000..9052d85
--- /dev/null
+++ b/modules/s3-bucket/main.tf
@@ -0,0 +1,55 @@
+resource "aws_s3_bucket" "each" {
+ bucket = var.bucket_name
+
+ force_destroy = var.force_destroy
+ tags = var.tags
+}
+
+resource "aws_s3_bucket_versioning" "each" {
+ bucket = aws_s3_bucket.each.id
+ versioning_configuration {
+ status = "Enabled"
+ }
+}
+
+resource "aws_s3_bucket_public_access_block" "each" {
+ bucket = aws_s3_bucket.each.id
+
+ block_public_acls = true
+ block_public_policy = true
+ ignore_public_acls = true
+ restrict_public_buckets = true
+}
+
+# After you apply the bucket owner enforced setting for Object Ownership, ACLs are disabled for the bucket.
+# See https://docs.aws.amazon.com/AmazonS3/latest/userguide/about-object-ownership.html
+resource "aws_s3_bucket_ownership_controls" "each" {
+ bucket = aws_s3_bucket.each.id
+
+ rule {
+ object_ownership = "BucketOwnerEnforced"
+ }
+}
+
+resource "aws_s3_bucket_server_side_encryption_configuration" "each" {
+ bucket = aws_s3_bucket.each.id
+
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = length(var.kms_key_arn) == 0 ? "AES256" : "aws:kms"
+ kms_master_key_id = one(var.kms_key_arn[*])
+ }
+ # Bucket keys are unique per user per session. Since Terraform generally uses
+ # a separate session for each update, bucket keys actually add overhead rather than reduce it.
+ bucket_key_enabled = false
+ }
+}
+
+resource "aws_s3_bucket_logging" "each" {
+ count = length(var.bucket_logging) > 0 ? 1 : 0
+
+ bucket = aws_s3_bucket.each.id
+
+ target_bucket = var.bucket_logging[0].target_bucket
+ target_prefix = var.bucket_logging[0].target_prefix
+}
diff --git a/modules/s3-bucket/outputs.tf b/modules/s3-bucket/outputs.tf
new file mode 100644
index 0000000..aca0581
--- /dev/null
+++ b/modules/s3-bucket/outputs.tf
@@ -0,0 +1,12 @@
+output "bucket" {
+ value = aws_s3_bucket.each
+ description = "The metadata for the S3 bucket created by this module"
+}
+
+output "sns_topic" {
+ value = aws_sns_topic.replication
+ description = <<-EOT
+ The metadata for the SNS Topic created by this module to receive
+ replication event notifications regarding the created S3 bucket
+ EOT
+}
diff --git a/modules/s3-bucket/sns.tf b/modules/s3-bucket/sns.tf
new file mode 100644
index 0000000..bdaba70
--- /dev/null
+++ b/modules/s3-bucket/sns.tf
@@ -0,0 +1,47 @@
+
+locals {
+ topic_name = "${var.bucket_name}-replication"
+}
+
+data "aws_iam_policy_document" "topic" {
+ count = var.sns_enabled ? 1 : 0
+
+ statement {
+ effect = "Allow"
+
+ principals {
+ type = "Service"
+ identifiers = ["s3.amazonaws.com"]
+ }
+
+ actions = ["SNS:Publish"]
+ resources = ["arn:aws:sns:*:*:${local.topic_name}"]
+
+ condition {
+ test = "ArnLike"
+ variable = "aws:SourceArn"
+ values = [aws_s3_bucket.each.arn]
+ }
+ }
+}
+resource "aws_sns_topic" "replication" {
+ count = var.sns_enabled ? 1 : 0
+
+ name = local.topic_name
+ policy = one(data.aws_iam_policy_document.topic[*].json)
+
+ kms_master_key_id = "alias/aws/sns"
+ tags = var.tags
+}
+
+resource "aws_s3_bucket_notification" "bucket_notification" {
+ count = var.sns_enabled ? 1 : 0
+
+ bucket = aws_s3_bucket.each.id
+
+ topic {
+ id = local.topic_name
+ topic_arn = one(aws_sns_topic.replication[*].arn)
+ events = ["s3:Replication:*"]
+ }
+}
diff --git a/modules/s3-bucket/variables.tf b/modules/s3-bucket/variables.tf
new file mode 100644
index 0000000..3d0c0cb
--- /dev/null
+++ b/modules/s3-bucket/variables.tf
@@ -0,0 +1,39 @@
+variable "bucket_name" {
+ type = string
+ description = "The name of the S3 bucket"
+}
+
+variable "kms_key_arn" {
+ type = list(string)
+ description = "the ARN of the KMS key to use for server-side encryption"
+}
+
+variable "tags" {
+ type = map(string)
+ description = "A map of tags to add to all resources"
+ default = {}
+}
+
+variable "bucket_logging" {
+ type = list(object({
+ target_bucket = string
+ target_prefix = string
+ }))
+ description = "Destination for S3 Server Access Logs for the bucket."
+ default = []
+}
+
+variable "sns_enabled" {
+ type = bool
+ description = "Whether to enable SNS notifications for replication failures"
+ default = false
+}
+
+variable "force_destroy" {
+ type = bool
+ description = <<-EOT
+ FOR TESTING ONLY! When set to true, `terraform destroy` will destroy the S3 buckets and all the objects in them.
+ These objects are not recoverable even if you have versioning or backups enabled.
+ EOT
+ default = false
+}
diff --git a/modules/s3-bucket/versions.tf b/modules/s3-bucket/versions.tf
new file mode 100644
index 0000000..521a51d
--- /dev/null
+++ b/modules/s3-bucket/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 4.0.0"
+ }
+ }
+}
diff --git a/moved.tf b/moved.tf
new file mode 100644
index 0000000..9de86b3
--- /dev/null
+++ b/moved.tf
@@ -0,0 +1,19 @@
+moved {
+ from = aws_dynamodb_table.with_server_side_encryption
+ to = aws_dynamodb_table.locks
+}
+
+moved {
+ from = aws_s3_bucket.default[0]
+ to = module.blue_bucket[0].aws_s3_bucket.each
+}
+
+moved {
+ from = aws_s3_bucket_public_access_block.default[0]
+ to = module.blue_bucket[0].aws_s3_bucket_public_access_block.each
+}
+
+# To revert, terraform state mv:
+# 'module.tfstate_backend.aws_dynamodb_table.locks[0]' 'module.tfstate_backend.aws_dynamodb_table.with_server_side_encryption[0]'
+# 'module.tfstate_backend.module.blue_bucket[0].aws_s3_bucket.each' 'module.tfstate_backend.aws_s3_bucket.default[0]'
+# 'module.tfstate_backend.module.blue_bucket[0].aws_s3_bucket_public_access_block.each' 'module.tfstate_backend.aws_s3_bucket_public_access_block.default[0]'
diff --git a/outputs.tf b/outputs.tf
index e758eba..840ec17 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -1,55 +1,95 @@
-output "s3_bucket_domain_name" {
- value = join("", aws_s3_bucket.default.*.bucket_domain_name)
- description = "S3 bucket domain name"
+output "s3_bucket_ids" {
+ description = "Map (by region) of the IDs (names) of the created S3 buckets"
+ value = local.enabled ? { for k in local.colors : local.s3_regions[k] => local.s3_bucket_names[k] } : null
}
-output "s3_bucket_id" {
- value = join("", aws_s3_bucket.default.*.id)
- description = "S3 bucket ID"
+output "s3_bucket_arns" {
+ description = "Map (by region) of the ARNs of the created S3 buckets"
+ value = local.enabled ? {
+ for k in local.colors : local.s3_buckets[k].region => local.s3_buckets[k].arn if try(local.s3_buckets[k].region, null) != null
+ } : null
}
-output "s3_bucket_arn" {
- value = join("", aws_s3_bucket.default.*.arn)
- description = "S3 bucket ARN"
+output "s3_bucket_domains" {
+ description = "Map (by region) of the domain names of the created S3 buckets"
+ value = local.enabled ? {
+ for k in local.colors : local.s3_buckets[k].region => local.s3_buckets[k].bucket_regional_domain_name if try(local.s3_buckets[k].region, null) != null
+ } : null
}
-output "dynamodb_table_name" {
- value = element(
- coalescelist(
- aws_dynamodb_table.with_server_side_encryption.*.name,
- aws_dynamodb_table.without_server_side_encryption.*.name,
- [""]
- ),
- 0
- )
- description = "DynamoDB table name"
+output "s3_bucket_kms_key_arns" {
+ description = "Map (by bucket name) of the ARNs of the KMS keys used to encrypt the created S3 buckets"
+ value = local.enabled ? merge(one(module.blue_bucket[*].bucket.id) == null ? {} : {
+ (module.blue_bucket[0].bucket.id) = local.blue_kms_key_arn },
+ local.replication_enabled && one(module.green_bucket[*].bucket.id) != null ? {
+ (module.green_bucket[0].bucket.id) = local.green_kms_key_arn } : {}
+ ) : null
+}
+
+output "sns_topic_arns" {
+ description = <<-EOT
+ Map (by region) of the ARNs for the SNS Topics created by this module to receive
+ replication event notifications regarding the created S3 bucket
+ EOT
+ value = local.replication_enabled ? try({
+ (module.blue_bucket[0].bucket.region) = (module.blue_bucket[0].sns_topic)
+ (module.green_bucket[0].bucket.region) = (module.green_bucket[0].sns_topic)
+ }, null) : null
}
output "dynamodb_table_id" {
- value = element(
- coalescelist(
- aws_dynamodb_table.with_server_side_encryption.*.id,
- aws_dynamodb_table.without_server_side_encryption.*.id,
- [""]
- ),
- 0
- )
+ value = one(aws_dynamodb_table.locks[*].id)
description = "DynamoDB table ID"
}
+output "dynamodb_table_name" {
+ value = one(aws_dynamodb_table.locks[*].name)
+ description = "DynamoDB table name"
+}
+
+output "dynamodb_table_arns" {
+ value = local.lock_table_enabled ? merge({
+ (local.blue_region) = one(aws_dynamodb_table.locks[*].arn) },
+ local.replication_enabled ? {
+ (local.green_region) = one(aws_dynamodb_table.locks[*]) == null ? null : one(aws_dynamodb_table.locks[0].replica[*].arn) } : {}
+ ) : null
+ description = <<-EOT
+ Map (by region) of the ARNs for DynamoDB tables created by this module to store Terraform state locks.
+ Note that in general you should only refer to the tables by name and region, not by ARN.
+ EOT
+}
+
+output "blue_backend_config" {
+ value = local.blue_backend_config
+ description = "Backend configuration for the blue Terraform state"
+}
+
+output "green_backend_config" {
+ value = local.green_backend_config
+ description = "Backend configuration for the green Terraform state"
+}
+
+#### Deprecated Outputs
+# These outputs are holdovers from version 0 of this module and are only
+# supported when the `replication_enabled` variable is set to `false`.
+
+output "s3_bucket_arn" {
+ value = local.replication_enabled ? null : try(local.s3_buckets["blue"].arn, null)
+ description = "(Deprecated, use `s3_bucket_arns` instead): S3 bucket ARN"
+}
+
+output "s3_bucket_domain_name" {
+ value = local.replication_enabled ? null : try(local.s3_buckets["blue"].bucket_domain_name, null)
+ description = "(Deprecated, use `s3_bucket_domains` instead): S3 bucket domain name"
+}
+
+output "s3_bucket_id" {
+ value = local.replication_enabled ? null : try(local.s3_buckets["blue"].id, null)
+ description = "(Deprecated, use `s3_bucket_ids` instead): S3 bucket ID"
+}
+
+
output "dynamodb_table_arn" {
- value = element(
- coalescelist(
- aws_dynamodb_table.with_server_side_encryption.*.arn,
- aws_dynamodb_table.without_server_side_encryption.*.arn,
- [""]
- ),
- 0
- )
- description = "DynamoDB table ARN"
-}
-
-output "terraform_backend_config" {
- value = local.enabled ? local.terraform_backend_config_content : ""
- description = "Rendered Terraform backend config file"
+ value = local.replication_enabled ? null : one(aws_dynamodb_table.locks[*].arn)
+ description = "(Deprecated, use `dynamodb_table_arns` instead) DynamoDB table ARN"
}
diff --git a/replication.tf b/replication.tf
index da11db2..ae2bcd5 100644
--- a/replication.tf
+++ b/replication.tf
@@ -1,66 +1,165 @@
-resource "aws_iam_role" "replication" {
- count = local.enabled && var.s3_replication_enabled ? 1 : 0
+# Create an IAM role that allows cross-account replication
- name = format("%s-replication", module.this.id)
- assume_role_policy = data.aws_iam_policy_document.replication_sts[0].json
+locals {
+ bucket_arns = compact([one(module.blue_bucket[*].bucket.arn), one(module.green_bucket[*].bucket.arn)])
+ replication_enabled = local.enabled && var.replication_enabled
+ replication_role_name = length(var.replication_role_name) > 0 ? var.replication_role_name[0] : module.replication_label.id
}
-data "aws_iam_policy_document" "replication_sts" {
- count = local.enabled && var.s3_replication_enabled ? 1 : 0
+data "aws_iam_policy_document" "replication_assume_role" {
+ count = local.replication_enabled ? 1 : 0
statement {
- sid = "AllowPrimaryToAssumeServiceRole"
- effect = "Allow"
- actions = [
- "sts:AssumeRole"
- ]
+ sid = "AWSReplicationRole"
+ effect = "Allow"
+ actions = ["sts:AssumeRole"]
principals {
- type = "Service"
- identifiers = ["s3.amazonaws.com"]
+ type = "Service"
+
+ identifiers = [
+ "s3.amazonaws.com",
+ "batchoperations.s3.amazonaws.com",
+ ]
}
}
}
-resource "aws_iam_policy" "replication" {
- count = local.enabled && var.s3_replication_enabled ? 1 : 0
+resource "aws_iam_role" "replication" {
+ count = local.replication_enabled ? 1 : 0
- name = format("%s-replication", module.this.id)
- policy = data.aws_iam_policy_document.replication[0].json
+ name = module.replication_label.id
+ assume_role_policy = one(data.aws_iam_policy_document.replication_assume_role[*].json)
+ tags = module.replication_label.tags
+ permissions_boundary = one(var.permissions_boundary[*])
}
data "aws_iam_policy_document" "replication" {
- count = local.enabled && var.s3_replication_enabled ? 1 : 0
+ count = local.replication_enabled ? 1 : 0
statement {
- sid = "AllowPrimaryToGetReplicationConfiguration"
+ sid = "GetObjectsToReplicate"
effect = "Allow"
+
+ resources = concat(local.bucket_arns, formatlist("%s/*", local.bucket_arns))
+
actions = [
- "s3:Get*",
- "s3:ListBucket"
- ]
- resources = [
- join("", aws_s3_bucket.default.*.arn),
- "${join("", aws_s3_bucket.default.*.arn)}/*"
+ "s3:ListBucket",
+ "s3:GetReplicationConfiguration",
+ "s3:GetObjectVersionForReplication",
+ "s3:GetObjectVersionAcl",
+ "s3:GetObjectVersionTagging",
+ "s3:GetObjectRetention",
+ "s3:GetObjectLegalHold",
]
}
statement {
- sid = "AllowPrimaryToReplicate"
+ sid = "ReplicateObjects"
effect = "Allow"
+
+ resources = formatlist("%s/*", local.bucket_arns)
+
actions = [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags",
- "s3:GetObjectVersionTagging"
+ "s3:ObjectOwnerOverrideToBucketOwner",
]
+ }
+
+ # Allow the role to use the KMS key to encrypt/decrypt objects in both buckets
+ statement {
+ sid = "DecryptBlueObjects"
+ effect = "Allow"
+ resources = [local.blue_kms_key_arn]
+ actions = ["kms:Decrypt"]
- resources = ["${var.s3_replica_bucket_arn}/*"]
+ condition {
+ test = "StringLike"
+ variable = "kms:ViaService"
+ values = ["s3.${local.blue_region}.amazonaws.com"]
+ }
+
+ condition {
+ test = "StringLike"
+ variable = "kms:EncryptionContext:aws:s3:arn"
+ values = [format("%s/*", one(module.blue_bucket[*].bucket.arn))]
+ }
+ }
+
+ statement {
+ sid = "DecryptGreenObjects"
+ effect = "Allow"
+ resources = [local.green_kms_key_arn]
+ actions = ["kms:Decrypt"]
+
+ condition {
+ test = "StringLike"
+ variable = "kms:ViaService"
+ values = ["s3.${local.green_region}.amazonaws.com"]
+ }
+
+ condition {
+ test = "StringLike"
+ variable = "kms:EncryptionContext:aws:s3:arn"
+ values = [format("%s/*", one(module.green_bucket[*].bucket.arn))]
+ }
+ }
+
+
+ statement {
+ sid = "EncryptBlueObjects"
+ effect = "Allow"
+ resources = [local.blue_kms_key_arn]
+ actions = ["kms:Encrypt"]
+
+ condition {
+ test = "StringLike"
+ variable = "kms:ViaService"
+ values = ["s3.${local.blue_region}.amazonaws.com"]
+ }
+
+ condition {
+ test = "StringLike"
+ variable = "kms:EncryptionContext:aws:s3:arn"
+ values = [format("%s/*", one(module.blue_bucket[*].bucket.arn))]
+ }
+ }
+
+ statement {
+ sid = "EncryptGreenObjects"
+ effect = "Allow"
+ resources = [local.green_kms_key_arn]
+ actions = ["kms:Encrypt"]
+
+ condition {
+ test = "StringLike"
+ variable = "kms:ViaService"
+ values = ["s3.${local.green_region}.amazonaws.com"]
+ }
+
+ condition {
+ test = "StringLike"
+ variable = "kms:EncryptionContext:aws:s3:arn"
+ values = [format("%s/*", one(module.green_bucket[*].bucket.arn))]
+ }
}
}
+resource "aws_iam_policy" "replication" {
+ count = local.replication_enabled ? 1 : 0
+
+ name = module.replication_label.id
+ description = "S3 bucket replication policy"
+ policy = one(data.aws_iam_policy_document.replication[*].json)
+ tags = module.replication_label.tags
+}
+
+# attach policy to role
resource "aws_iam_role_policy_attachment" "replication" {
- count = local.enabled && var.s3_replication_enabled ? 1 : 0
- role = aws_iam_role.replication[0].name
- policy_arn = aws_iam_policy.replication[0].arn
+ count = local.replication_enabled ? 1 : 0
+
+ role = one(aws_iam_role.replication[*].name)
+ policy_arn = one(aws_iam_policy.replication[*].arn)
}
diff --git a/s3.tf b/s3.tf
new file mode 100644
index 0000000..0d4e05e
--- /dev/null
+++ b/s3.tf
@@ -0,0 +1,175 @@
+# Using Terraform, create 2 S3 buckets in different regions with bi-directional cross-region replication
+
+locals {
+ colors = local.enabled ? (var.replication_enabled ? toset(["blue", "green"]) : toset(["blue"])) : toset([])
+ other_color = {
+ blue = "green"
+ green = "blue"
+ }
+ s3_regions = {
+ blue = local.blue_region
+ green = local.green_region
+ }
+ s3_bucket_names = {
+ blue = length(var.blue_s3_bucket_name) > 0 ? var.blue_s3_bucket_name[0] : module.blue_label.id
+ green = length(var.green_s3_bucket_name) > 0 ? var.green_s3_bucket_name[0] : module.green_label.id
+ }
+ s3_buckets = {
+ // If a bucket was destroyed outside of Terraform and then you run `terraform destroy`,
+ // and you had replication enabled, module.blue_bucket[0] would exist but not have a `bucket`.
+ blue = try(one(module.blue_bucket[*].bucket), null)
+ green = try(one(module.green_bucket[*].bucket), null)
+ }
+}
+
+# Create the S3 buckets
+# This would be a whole lot easier if Terraform supported dynamic providers,
+# but it doesn't, so we have to instantiate the module twice.
+
+module "blue_bucket" {
+ count = local.enabled ? 1 : 0
+ source = "./modules/s3-bucket"
+
+ providers = {
+ aws = aws.blue
+ }
+ bucket_name = local.s3_bucket_names["blue"]
+ # In order to ensure that replication has access to the KMS key,
+ # we explicitly set the key when replication is enabled.
+ # Otherwise, we use the default KMS key (S3-SSE).
+ kms_key_arn = local.replication_enabled ? [local.blue_kms_key_arn] : var.blue_kms_key_arn
+ tags = module.blue_label.tags
+ bucket_logging = var.blue_bucket_logging
+ sns_enabled = local.replication_enabled
+ force_destroy = var.force_destroy
+}
+
+module "green_bucket" {
+ count = local.replication_enabled ? 1 : 0
+ source = "./modules/s3-bucket"
+
+ providers = {
+ aws = aws.green
+ }
+ bucket_name = local.s3_bucket_names["green"]
+ kms_key_arn = [local.green_kms_key_arn]
+ tags = module.green_label.tags
+ bucket_logging = var.green_bucket_logging
+ force_destroy = var.force_destroy
+}
+
+
+# Configure the replication rules
+
+resource "aws_s3_bucket_replication_configuration" "blue" {
+ count = local.replication_enabled ? 1 : 0
+
+ provider = aws.blue
+
+ bucket = one(module.blue_bucket[*].bucket.id)
+ role = one(aws_iam_role.replication[*].arn)
+
+ rule {
+ id = "blue-to-green"
+
+ status = "Enabled"
+ filter {}
+
+ source_selection_criteria {
+ replica_modifications {
+ status = "Enabled"
+ }
+ sse_kms_encrypted_objects {
+ status = "Enabled"
+ }
+ }
+
+ delete_marker_replication {
+ status = "Enabled"
+ }
+
+ destination {
+ bucket = one(module.green_bucket[*].bucket.arn)
+ storage_class = "STANDARD"
+
+ encryption_configuration {
+ replica_kms_key_id = local.green_kms_key_arn
+ }
+
+ metrics {
+ event_threshold {
+ minutes = 15
+ }
+ status = "Enabled"
+ }
+
+ replication_time {
+ status = "Enabled"
+ time {
+ minutes = 15
+ }
+ }
+ }
+ }
+
+ # Must have bucket versioning enabled first
+ depends_on = [module.blue_bucket, module.green_bucket]
+}
+
+
+resource "aws_s3_bucket_replication_configuration" "green" {
+ count = local.replication_enabled ? 1 : 0
+
+ provider = aws.green
+
+ bucket = one(module.green_bucket[*].bucket.id)
+ role = one(aws_iam_role.replication[*].arn)
+
+ rule {
+ id = "green-to-blue"
+
+ status = "Enabled"
+ filter {}
+
+ source_selection_criteria {
+ replica_modifications {
+ status = "Enabled"
+ }
+ sse_kms_encrypted_objects {
+ status = "Enabled"
+ }
+ }
+
+ delete_marker_replication {
+ status = "Enabled"
+ }
+
+ destination {
+ bucket = one(module.blue_bucket[*].bucket.arn)
+ storage_class = "STANDARD"
+
+ encryption_configuration {
+ replica_kms_key_id = local.blue_kms_key_arn
+ }
+
+ metrics {
+ event_threshold {
+ minutes = 15
+ }
+ status = "Enabled"
+ }
+
+ replication_time {
+ status = "Enabled"
+ time {
+ minutes = 15
+ }
+ }
+ }
+ }
+
+ # Must have bucket versioning enabled first
+ depends_on = [module.blue_bucket, module.green_bucket]
+}
+
+
diff --git a/test/Makefile b/test/Makefile
index 17b2fe7..3a32e12 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -30,14 +30,17 @@ clean:
[ "$(TEST_HARNESS_PATH)" == "/" ] || rm -rf $(TEST_HARNESS_PATH)
## Run all tests
-all: module examples/complete
+all: module examples/complete examples/no-replication
+# This module does not pass `terraform validate` because of
+# https://github.com/hashicorp/terraform/issues/28490
+# so we remove it from the list of tests on the module
## Run basic sanity checks against the module itself
-module: export TESTS ?= installed lint get-modules module-pinning get-plugins provider-pinning validate terraform-docs input-descriptions output-descriptions
+module: export TESTS ?= installed lint module-pinning provider-pinning terraform-docs input-descriptions output-descriptions
module: deps
$(call RUN_TESTS, ../)
## Run tests against example
-examples/complete: export TESTS ?= installed lint get-modules get-plugins validate
-examples/complete: deps
+examples/complete examples/no-replication: export TESTS ?= installed lint validate
+examples/complete examples/no-replication: deps
$(call RUN_TESTS, ../$@)
diff --git a/test/src/Makefile b/test/src/Makefile
index 81c71b6..c6037f0 100644
--- a/test/src/Makefile
+++ b/test/src/Makefile
@@ -15,7 +15,7 @@ init:
## Run tests
test: init
go mod download
- go test -v -timeout 60m
+ go test -v -timeout 60m -parallel 8
## Run tests in docker container
docker/test:
diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go
index 1d10a61..7102f0e 100644
--- a/test/src/examples_complete_test.go
+++ b/test/src/examples_complete_test.go
@@ -1,50 +1,211 @@
package test
import (
- "math/rand"
- "strconv"
- "testing"
- "time"
-
+ "fmt"
+ "github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/terraform"
+ testStructure "github.com/gruntwork-io/terratest/modules/test-structure"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "path"
+ "regexp"
+ "strings"
+ "testing"
+ "time"
)
+const WorkspaceKeyPrefix = "workspace"
+const Workspace = "eg-test"
+
// Test the Terraform module in examples/complete using Terratest.
func TestExamplesComplete(t *testing.T) {
t.Parallel()
+ randID := strings.ToLower(random.UniqueId())
+ attributes := []string{randID}
+
+ rootFolder := "../../"
+ terraformFolderRelativeToRoot := "examples/complete"
+ varFiles := []string{"fixtures.us-east-2.tfvars"}
+
+ tempTestFolder := testStructure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
+
+ terraformOptions := &terraform.Options{
+ // The path to where our Terraform code is located
+ TerraformDir: tempTestFolder,
+ Upgrade: true,
+ // Variables to pass to our Terraform code using -var-file options
+ VarFiles: varFiles,
+ Vars: map[string]interface{}{
+ "attributes": attributes,
+ },
+ }
+
+ // At the end of the test, run `terraform destroy` to clean up any resources that were created
+ defer cleanup(t, terraformOptions, tempTestFolder)
+
+ // This will run `terraform init` and `terraform apply` and fail the test if there are any errors
+ if _, err := terraform.InitAndApplyE(t, terraformOptions); err != nil {
+ require.FailNow(t, "Terraform \"apply\" failed, skipping the rest of the tests")
+ }
+
+ t.Log("Waiting 15 seconds for S3 to set up replication")
+ time.Sleep(15 * time.Second)
+
+ // Deploy a null resource (an output) in the blue region.
+ // Verify that the resource is in the Terraform state in the blue S3 bucket
+ // and that the state object is replicated.
+ blueBackendMap := terraform.OutputMap(t, terraformOptions, "blue_backend_config")
+ blueRegion := blueBackendMap["region"]
+ s3BucketNames := terraform.OutputMap(t, terraformOptions, "s3_bucket_ids")
+ blueBucket := s3BucketNames[blueRegion]
+ require.NotEmpty(t, blueBucket, "Could not find bucket name for blue region")
+ greenBackendMap := terraform.OutputMap(t, terraformOptions, "green_backend_config")
+ greenRegion := greenBackendMap["region"]
+
+ blueConfig := BackendTestConfig{
+ region: blueBackendMap["region"],
+ bucketName: s3BucketNames[blueRegion],
+ backendConfig: mapToConfig(blueBackendMap),
+ // For multi-region, the keys are prefixed with what is configured as the bucket name
+ tfStateKey: fmt.Sprintf("%s/%s", blueBackendMap["bucket"], blueBackendMap["key"]),
+ testData: fmt.Sprintf("data-for-blue-test-%s", randID),
+ testFolder: path.Join(tempTestFolder, "backend-test"),
+ workspace: "",
+ }
+ blueWorkspaceBackendConfig := mapToConfig(blueBackendMap)
+ blueWorkspaceBackendConfig["workspace_key_prefix"] = WorkspaceKeyPrefix
+
+ blueWorkspaceConfig := BackendTestConfig{
+ region: blueConfig.region,
+ bucketName: blueConfig.bucketName,
+ backendConfig: blueWorkspaceBackendConfig,
+ tfStateKey: fmt.Sprintf("%s/%s/%s/%s", blueBackendMap["bucket"], WorkspaceKeyPrefix, Workspace, blueBackendMap["key"]),
+ testData: fmt.Sprintf("data-for-blue-workspace-test-%s", randID),
+ testFolder: path.Join(tempTestFolder, "backend-workspace-test"),
+ workspace: Workspace,
+ }
+
+ greenConfig := BackendTestConfig{
+ region: greenBackendMap["region"],
+ bucketName: s3BucketNames[greenRegion],
+ backendConfig: mapToConfig(greenBackendMap),
+ tfStateKey: fmt.Sprintf("%s/%s", greenBackendMap["bucket"], greenBackendMap["key"]),
+ testData: blueConfig.testData,
+ testFolder: path.Join(tempTestFolder, "backend-test"),
+ workspace: "",
+ }
+ greenWorkspaceBackendConfig := mapToConfig(greenBackendMap)
+ greenWorkspaceBackendConfig["workspace_key_prefix"] = WorkspaceKeyPrefix
+
+ greenWorkspaceConfig := BackendTestConfig{
+ region: greenConfig.region,
+ bucketName: greenConfig.bucketName,
+ backendConfig: greenWorkspaceBackendConfig,
+ tfStateKey: fmt.Sprintf("%s/%s/%s/%s", greenBackendMap["bucket"], WorkspaceKeyPrefix, Workspace, greenBackendMap["key"]),
+ testData: blueWorkspaceConfig.testData,
+ testFolder: path.Join(tempTestFolder, "backend-workspace-test"),
+ workspace: Workspace,
+ }
+
+ // Run the tests for with and without workspaces in parallel,
+ // and wait for them both to finish before proceeding.
+ t.Run("group", func(t *testing.T) {
+ testCrossRegionProvisioning(t, blueConfig, greenConfig)
+ testCrossRegionProvisioning(t, blueWorkspaceConfig, greenWorkspaceConfig)
+ })
+}
+
+func TestExamplesCompleteDisabled(t *testing.T) {
+ t.Parallel()
+ randID := strings.ToLower(random.UniqueId())
+ attributes := []string{randID}
+
+ rootFolder := "../../"
+ terraformFolderRelativeToRoot := "examples/complete"
+ varFiles := []string{"fixtures.us-east-2.tfvars"}
+
+ tempTestFolder := testStructure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
+
+ terraformOptions := &terraform.Options{
+ // The path to where our Terraform code is located
+ TerraformDir: tempTestFolder,
+ Upgrade: true,
+ // Variables to pass to our Terraform code using -var-file options
+ VarFiles: varFiles,
+ Vars: map[string]interface{}{
+ "attributes": attributes,
+ "enabled": "false",
+ },
+ }
+
+ // At the end of the test, run `terraform destroy` to clean up any resources that were created
+ defer cleanup(t, terraformOptions, tempTestFolder)
+
+ // This will run `terraform init` and `terraform apply` and fail the test if there are any errors
+ results := terraform.InitAndApply(t, terraformOptions)
+
+ // Should complete successfully without creating or changing any resources.
+ // Extract the "Resources:" section of the output to make the error message more readable.
+ re := regexp.MustCompile(`Resources: [^.]+\.`)
+ match := re.FindString(results)
+ assert.Equal(t, "Resources: 0 added, 0 changed, 0 destroyed.", match,
+ "Applying with `enabled == false` should not create or change any resources")
+}
+
+func TestExamplesCompleteDestroyByDisable(t *testing.T) {
+ t.Parallel()
+ randID := strings.ToLower(random.UniqueId())
+ attributes := []string{randID}
- rand.Seed(time.Now().UnixNano())
+ rootFolder := "../../"
+ terraformFolderRelativeToRoot := "examples/complete"
+ varFiles := []string{"fixtures.us-east-2.tfvars"}
- randId := strconv.Itoa(rand.Intn(100000))
- attributes := []string{randId}
+ tempTestFolder := testStructure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
terraformOptions := &terraform.Options{
// The path to where our Terraform code is located
- TerraformDir: "../../examples/complete",
+ TerraformDir: tempTestFolder,
Upgrade: true,
// Variables to pass to our Terraform code using -var-file options
- VarFiles: []string{"fixtures.us-east-2.tfvars"},
+ VarFiles: varFiles,
Vars: map[string]interface{}{
"attributes": attributes,
},
}
// At the end of the test, run `terraform destroy` to clean up any resources that were created
- defer terraform.Destroy(t, terraformOptions)
+ defer cleanup(t, terraformOptions, tempTestFolder)
// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
- terraform.InitAndApply(t, terraformOptions)
-
- // Run `terraform output` to get the value of an output variable
- s3BucketId := terraform.Output(t, terraformOptions, "s3_bucket_id")
- expectedS3BucketId := "eg-use2-test-terraform-tfstate-backend-" + randId
- // Verify we're getting back the outputs we expect
- assert.Equal(t, expectedS3BucketId, s3BucketId)
-
- // Run `terraform output` to get the value of an output variable
- dynamodbTableName := terraform.Output(t, terraformOptions, "dynamodb_table_name")
- expectedDynamodbTableName := "eg-use2-test-terraform-tfstate-backend-" + randId + "-lock"
- // Verify we're getting back the outputs we expect
- assert.Equal(t, expectedDynamodbTableName, dynamodbTableName)
+ results, err := terraform.InitAndApplyE(t, terraformOptions)
+ if err != nil {
+ require.FailNow(t, "Unable to create backend for testing")
+ }
+
+ terraformOptions.Vars = map[string]interface{}{
+ "attributes": attributes,
+ "enabled": "false",
+ }
+
+ results, err = terraform.ApplyE(t, terraformOptions)
+ if err != nil {
+ require.FailNow(t, "Error while trying to destroy the backend by setting `enabled` to `false`")
+ }
+
+ results, err = terraform.DestroyE(t, terraformOptions)
+ if err != nil {
+ require.FailNow(t, "Error while trying to destroy the `enabled == false` backend")
+ }
+
+ // Should complete successfully destroying any resources.
+ // Extract the "Resources:" section of the output to make the error message more readable.
+ re := regexp.MustCompile(`Resources: [^.]+\.`)
+ match := re.FindString(results)
+ assert.Equal(t, "Resources: 0 destroyed.", match,
+ "Destroying after `enabled == false` should not destroy any resources")
+
+ // Terraform has "count" issues with destroying a project already destroyed, so we create it
+ // again here so that the cleanup function will not fail.
+ terraform.Apply(t, terraformOptions)
}
diff --git a/test/src/examples_no_replication_test.go b/test/src/examples_no_replication_test.go
new file mode 100644
index 0000000..51fd1f0
--- /dev/null
+++ b/test/src/examples_no_replication_test.go
@@ -0,0 +1,73 @@
+package test
+
+import (
+ "fmt"
+ "github.com/gruntwork-io/terratest/modules/random"
+ "github.com/gruntwork-io/terratest/modules/terraform"
+ testStructure "github.com/gruntwork-io/terratest/modules/test-structure"
+ "path"
+
+ "github.com/stretchr/testify/assert"
+ "strings"
+ "testing"
+)
+
+// Test the Terraform module in examples/complete using Terratest.
+func TestExamplesNoReplication(t *testing.T) {
+ t.Parallel()
+ randID := strings.ToLower(random.UniqueId())
+ attributes := []string{randID}
+
+ rootFolder := "../../"
+ terraformFolderRelativeToRoot := "examples/no-replication"
+ varFiles := []string{"fixtures.us-east-2.tfvars"}
+
+ tempTestFolder := testStructure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
+
+ terraformOptions := &terraform.Options{
+ // The path to where our Terraform code is located
+ TerraformDir: tempTestFolder,
+ Upgrade: true,
+ // Variables to pass to our Terraform code using -var-file options
+ VarFiles: varFiles,
+ Vars: map[string]interface{}{
+ "attributes": attributes,
+ },
+ }
+
+ // At the end of the test, run `terraform destroy` to clean up any resources that were created
+ defer cleanup(t, terraformOptions, tempTestFolder)
+
+ // This will run `terraform init` and `terraform apply` and fail the test if there are any errors
+ terraform.InitAndApply(t, terraformOptions)
+
+ // Run `terraform output` to get the value of an output variable
+ s3BucketId := terraform.Output(t, terraformOptions, "s3_bucket_id")
+ expectedS3BucketId := "eg-use2-test-terraform-tfstate-backend-" + randID
+ // Verify we're getting back the outputs we expect
+ assert.Equal(t, expectedS3BucketId, s3BucketId)
+
+ // Run `terraform output` to get the value of an output variable
+ dynamodbTableName := terraform.Output(t, terraformOptions, "dynamodb_table_name")
+ expectedDynamodbTableName := "eg-use2-test-terraform-tfstate-backend-" + randID + "-lock"
+ // Verify we're getting back the outputs we expect
+ assert.Equal(t, expectedDynamodbTableName, dynamodbTableName)
+
+ backendMap := terraform.OutputMap(t, terraformOptions, "backend_config")
+
+ backendTestConfig := BackendTestConfig{
+ region: backendMap["region"],
+ bucketName: s3BucketId,
+ backendConfig: mapToConfig(backendMap),
+ tfStateKey: backendMap["key"],
+ testData: fmt.Sprintf("data-for-no-replication-test-%s", randID),
+ testFolder: path.Join(path.Dir(tempTestFolder), "complete", "backend-test"),
+ workspace: "",
+ }
+
+ if !provisionInBlue(t, backendTestConfig) {
+ assert.FailNow(t, "No-replication backend not working as expected")
+ }
+
+ verifyAndChange(t, "only", backendTestConfig)
+}
diff --git a/test/src/go.mod b/test/src/go.mod
index 90f635d..a57b5ed 100644
--- a/test/src/go.mod
+++ b/test/src/go.mod
@@ -1,60 +1,118 @@
module github.com/cloudposse/terraform-aws-tfstate-backend
-go 1.17
+go 1.20
require (
- // Known security flaws in terratest dependencies prior to v0.40.15
- github.com/gruntwork-io/terratest v0.40.16
- github.com/stretchr/testify v1.7.0
+ github.com/aws/aws-sdk-go v1.44.244
+ github.com/aws/aws-sdk-go-v2/config v1.15.13
+ github.com/aws/aws-sdk-go-v2/service/s3 v1.27.1
+ github.com/gruntwork-io/terratest v0.41.18
+ github.com/stretchr/testify v1.8.2
)
require (
- cloud.google.com/go v0.83.0 // indirect
- cloud.google.com/go/storage v1.10.0 // indirect
+ cloud.google.com/go v0.110.0 // indirect
+ cloud.google.com/go/compute v1.19.1 // indirect
+ cloud.google.com/go/compute/metadata v0.2.3 // indirect
+ cloud.google.com/go/iam v1.0.0 // indirect
+ cloud.google.com/go/storage v1.30.1 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
- github.com/aws/aws-sdk-go v1.40.56 // indirect
+ github.com/aws/aws-sdk-go-v2 v1.16.16 // indirect
+ github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.12.8 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.8 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/ini v1.3.15 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.5 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.3 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.9 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.8 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.8 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.11.11 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.16.9 // indirect
+ github.com/aws/smithy-go v1.13.3 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
+ github.com/boombuler/barcode v1.0.1 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
- github.com/golang/protobuf v1.5.2 // indirect
- github.com/golang/snappy v0.0.3 // indirect
- github.com/googleapis/gax-go/v2 v2.0.5 // indirect
- github.com/hashicorp/errwrap v1.0.0 // indirect
+ github.com/emicklei/go-restful/v3 v3.10.2 // indirect
+ github.com/go-errors/errors v1.4.2 // indirect
+ github.com/go-logr/logr v1.2.4 // indirect
+ github.com/go-openapi/jsonpointer v0.19.6 // indirect
+ github.com/go-openapi/jsonreference v0.20.2 // indirect
+ github.com/go-openapi/swag v0.22.3 // indirect
+ github.com/go-sql-driver/mysql v1.7.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/gnostic v0.6.9 // indirect
+ github.com/google/go-cmp v0.5.9 // indirect
+ github.com/google/gofuzz v1.2.0 // indirect
+ github.com/google/s2a-go v0.1.1 // indirect
+ github.com/google/uuid v1.3.0 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
+ github.com/googleapis/gax-go/v2 v2.8.0 // indirect
+ github.com/gruntwork-io/go-commons v0.16.1 // indirect
+ github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
- github.com/hashicorp/go-getter v1.6.1 // indirect
- github.com/hashicorp/go-multierror v1.1.0 // indirect
+ github.com/hashicorp/go-getter v1.7.1 // indirect
+ github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
- github.com/hashicorp/go-version v1.3.0 // indirect
- github.com/hashicorp/hcl/v2 v2.9.1 // indirect
- github.com/hashicorp/terraform-json v0.13.0 // indirect
- github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect
+ github.com/hashicorp/go-version v1.6.0 // indirect
+ github.com/hashicorp/hcl/v2 v2.16.2 // indirect
+ github.com/hashicorp/terraform-json v0.16.0 // indirect
+ github.com/imdario/mergo v0.3.15 // indirect
+ github.com/jinzhu/copier v0.3.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
- github.com/jstemmer/go-junit-report v0.9.1 // indirect
- github.com/klauspost/compress v1.13.0 // indirect
- github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/compress v1.16.4 // indirect
+ github.com/mailru/easyjson v0.7.7 // indirect
+ github.com/mattn/go-zglob v0.0.4 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
- github.com/mitchellh/go-testing-interface v1.0.0 // indirect
+ github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
+ github.com/moby/spdystream v0.2.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/tmccombs/hcl2json v0.3.3 // indirect
- github.com/ulikunitz/xz v0.5.8 // indirect
- github.com/zclconf/go-cty v1.9.1 // indirect
- go.opencensus.io v0.23.0 // indirect
- golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
- golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
- golang.org/x/mod v0.4.2 // indirect
- golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
- golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect
- golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e // indirect
- golang.org/x/text v0.3.6 // indirect
- golang.org/x/tools v0.1.2 // indirect
- golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
- google.golang.org/api v0.47.0 // indirect
+ github.com/pquerna/otp v1.4.0 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/tmccombs/hcl2json v0.5.0 // indirect
+ github.com/ulikunitz/xz v0.5.11 // indirect
+ github.com/urfave/cli/v2 v2.25.1 // indirect
+ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+ github.com/zclconf/go-cty v1.13.1 // indirect
+ go.opencensus.io v0.24.0 // indirect
+ golang.org/x/crypto v0.8.0 // indirect
+ golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
+ golang.org/x/net v0.9.0 // indirect
+ golang.org/x/oauth2 v0.7.0 // indirect
+ golang.org/x/sys v0.7.0 // indirect
+ golang.org/x/term v0.7.0 // indirect
+ golang.org/x/text v0.9.0 // indirect
+ golang.org/x/time v0.3.0 // indirect
+ golang.org/x/tools v0.8.0 // indirect
+ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
+ google.golang.org/api v0.118.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
- google.golang.org/grpc v1.38.0 // indirect
- google.golang.org/protobuf v1.26.0 // indirect
- gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
- gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
+ google.golang.org/grpc v1.54.0 // indirect
+ google.golang.org/protobuf v1.30.0 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/api v0.27.1 // indirect
+ k8s.io/apimachinery v0.27.1 // indirect
+ k8s.io/client-go v0.27.1 // indirect
+ k8s.io/klog/v2 v2.90.1 // indirect
+ k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c // indirect
+ k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
+ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
)
diff --git a/test/src/go.sum b/test/src/go.sum
index c8feb9f..c542eea 100644
--- a/test/src/go.sum
+++ b/test/src/go.sum
@@ -18,42 +18,233 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
-cloud.google.com/go v0.83.0 h1:bAMqZidYkmIsUqe6PtkEPT7Q+vfizScn+jfNA6jwK9c=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
+cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
+cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
+cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
+cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
+cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
+cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
+cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
+cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU=
+cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
+cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
+cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
+cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw=
+cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY=
+cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI=
+cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
+cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4=
+cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
+cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ=
+cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk=
+cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o=
+cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s=
+cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0=
+cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY=
+cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw=
+cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI=
+cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0=
+cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA=
+cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
+cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
+cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM=
+cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI=
+cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY=
+cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI=
+cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
+cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
+cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
+cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
+cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
+cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
+cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
+cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
+cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
+cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
+cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs=
+cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc=
+cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM=
+cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
+cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo=
+cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE=
+cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I=
+cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
+cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo=
+cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo=
+cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ=
+cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4=
+cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0=
+cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8=
+cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU=
+cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU=
+cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y=
+cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
+cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk=
+cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=
+cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk=
+cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg=
+cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM=
+cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA=
+cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o=
+cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
+cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0=
+cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
+cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
+cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc=
+cloud.google.com/go/iam v1.0.0 h1:hlQJMovyJJwYjZcTohUH4o1L8Z8kYz+E+W/zktiLCBc=
+cloud.google.com/go/iam v1.0.0/go.mod h1:ikbQ4f1r91wTmBmmOtBCOtuEOei6taatNXytzB7Cxew=
+cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
+cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
+cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8=
+cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
+cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
+cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4=
+cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
+cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE=
+cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM=
+cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY=
+cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s=
+cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
+cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
+cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ=
+cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
+cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY=
+cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34=
+cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs=
+cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg=
+cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E=
+cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU=
+cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0=
+cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
+cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0=
+cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4=
+cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o=
+cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk=
+cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo=
+cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg=
+cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
+cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg=
+cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c=
+cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y=
+cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A=
+cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4=
+cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY=
+cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s=
+cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI=
+cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA=
+cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4=
+cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0=
+cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU=
+cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU=
+cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc=
+cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs=
+cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg=
+cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
+cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
+cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc=
+cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
+cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
+cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
+cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw=
+cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g=
+cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU=
+cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4=
+cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0=
+cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo=
+cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo=
+cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE=
+cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg=
+cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0=
+cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
-github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
-github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
-github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
-github.com/aws/aws-sdk-go v1.40.56 h1:FM2yjR0UUYFzDTMx+mH9Vyw1k1EUUxsAFzk+BjkzANA=
-github.com/aws/aws-sdk-go v1.40.56/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
+github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go v1.44.244 h1:QzBWLD5HjZHdRZyTMTOWtD9Pobzf1n8/CeTJB4giXi0=
+github.com/aws/aws-sdk-go v1.44.244/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
+github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw=
+github.com/aws/aws-sdk-go-v2 v1.16.16 h1:M1fj4FE2lB4NzRb9Y0xdWsn2P0+2UHVxwKyOa4YJNjk=
+github.com/aws/aws-sdk-go-v2 v1.16.16/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3 h1:S/ZBwevQkr7gv5YxONYpGQxlMFFYSRfz3RMcjsC9Qhk=
+github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.3/go.mod h1:gNsR5CaXKmQSSzrmGxmwmct/r+ZBfbxorAuXYsj/M5Y=
+github.com/aws/aws-sdk-go-v2/config v1.15.13 h1:CJH9zn/Enst7lDiGpoguVt0lZr5HcpNVlRJWbJ6qreo=
+github.com/aws/aws-sdk-go-v2/config v1.15.13/go.mod h1:AcMu50uhV6wMBUlURnEXhr9b3fX6FLSTlEV89krTEGk=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.8 h1:niTa7zc7uyOP2ufri0jPESBt1h9yP3Zc0q+xzih3h8o=
+github.com/aws/aws-sdk-go-v2/credentials v1.12.8/go.mod h1:P2Hd4Sy7mXRxPNcQMPBmqszSJoDXexX8XEDaT6lucO0=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.8 h1:VfBdn2AxwMbFyJN/lF/xuT3SakomJ86PZu3rCxb5K0s=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.8/go.mod h1:oL1Q3KuCq1D4NykQnIvtRiBGLUXhcpY5pl6QZB2XEPU=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 h1:s4g/wnzMf+qepSNgTvaQQHNxyMLKSawNhKCPNy++2xY=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23/go.mod h1:2DFxAQ9pfIRy0imBCJv+vZ2X6RKxves6fbnEuSry6b4=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 h1:/K482T5A3623WJgWT8w1yRAFK4RzGzEl7y39yhtn9eA=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17/go.mod h1:pRwaTYCJemADaqCbUAxltMoHKata7hmB5PjEXeu0kfg=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.15 h1:QquxR7NH3ULBsKC+NoTpilzbKKS+5AELfNREInbhvas=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.15/go.mod h1:Tkrthp/0sNBShQQsamR7j/zY4p19tVTAs+nnqhH6R3c=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.5 h1:tEEHn+PGAxRVqMPEhtU8oCSW/1Ge3zP5nUgPrGQNUPs=
+github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.5/go.mod h1:aIwFF3dUk95ocCcA3zfk3nhz0oLkpzHFWuMp8l/4nNs=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.3 h1:4n4KCtv5SUoT5Er5XV41huuzrCqepxlW3SDI9qHQebc=
+github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.3/go.mod h1:gkb2qADY+OHaGLKNTYxMaQNacfeyQpZ4csDTQMeFmcw=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.9 h1:gVv2vXOMqJeR4ZHHV32K7LElIJIIzyw/RU1b0lSfWTQ=
+github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.9/go.mod h1:EF5RLnD9l0xvEWwMRcktIS/dI6lF8lU5eV3B13k6sWo=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.8 h1:oKnAXxSF2FUvfgw8uzU/v9OTYorJJZ8eBmWhr9TWVVQ=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.8/go.mod h1:rDVhIMAX9N2r8nWxDUlbubvvaFMnfsm+3jAV7q+rpM4=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.8 h1:TlN1UC39A0LUNoD51ubO5h32haznA+oVe15jO9O4Lj0=
+github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.8/go.mod h1:JlVwmWtT/1c5W+6oUsjXjAJ0iJZ+hlghdrDy/8JxGCU=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.27.1 h1:OKQIQ0QhEBmGr2LfT952meIZz3ujrPYnxH+dO/5ldnI=
+github.com/aws/aws-sdk-go-v2/service/s3 v1.27.1/go.mod h1:NffjpNsMUFXp6Ok/PahrktAncoekWrywvmIK83Q2raE=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.11 h1:XOJWXNFXJyapJqQuCIPfftsOf0XZZioM0kK6OPRt9MY=
+github.com/aws/aws-sdk-go-v2/service/sso v1.11.11/go.mod h1:MO4qguFjs3wPGcCSpQ7kOFTwRvb+eu+fn+1vKleGHUk=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.9 h1:yOfILxyjmtr2ubRkRJldlHDFBhf5vw4CzhbwWIBmimQ=
+github.com/aws/aws-sdk-go-v2/service/sts v1.16.9/go.mod h1:O1IvkYxr+39hRf960Us6j0x1P8pDqhTX+oXM5kQNl/Y=
+github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
+github.com/aws/smithy-go v1.13.3 h1:l7LYxGuzK6/K+NzJ2mC+VvLUbae0sL3bXU//04MkmnA=
+github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
+github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
+github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -62,29 +253,60 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/emicklei/go-restful/v3 v3.10.2 h1:hIovbnmBTLjHXkqEBUz3HGpXZdM7ZrE9fJIZIqlJLqE=
+github.com/emicklei/go-restful/v3 v3.10.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
+github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
+github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
+github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
+github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
+github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
-github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@@ -93,7 +315,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
-github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -110,12 +332,14 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=
+github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -128,13 +352,19 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -147,123 +377,177 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
+github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/s2a-go v0.1.1 h1:XJQvZvUdPzOGdf4ZMQc78wYt9XrdIZOl//n03i8P68Q=
+github.com/google/s2a-go v0.1.1/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
+github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
+github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
+github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/gruntwork-io/terratest v0.40.16 h1:vBJpOqzZbg1KgjNTgu6bno8S56kNShKzxOkslTQe32s=
-github.com/gruntwork-io/terratest v0.40.16/go.mod h1:enUpxNMsQfiJTTJMGqiznxohqJ4cYPU+nzI3bQNw1WM=
-github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
+github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
+github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
+github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
+github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
+github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
+github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
+github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc=
+github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
+github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/gruntwork-io/go-commons v0.16.1 h1:EhrTKRg5XK/wDL/s99zisjL9+zhTaEcepEkJeyvj3s8=
+github.com/gruntwork-io/go-commons v0.16.1/go.mod h1:/nnLMhO4HpItt43K+8ACM66g67iMy3oo2nnwq59n3wg=
+github.com/gruntwork-io/terratest v0.41.18 h1:xFLF9c6bQ/dpcRera3Bbn1D5RJE+NyRXRndtV/Mjiuc=
+github.com/gruntwork-io/terratest v0.41.18/go.mod h1:O6gajNBjO1wvc7Wl9WtbO+ORcdnhAV2GQiBE71ycwIk=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
-github.com/hashicorp/go-getter v1.6.1 h1:NASsgP4q6tL94WH6nJxKWj8As2H/2kop/bB1d8JMyRY=
-github.com/hashicorp/go-getter v1.6.1/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA=
-github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
-github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY=
+github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
-github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
-github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/hcl/v2 v2.9.1 h1:eOy4gREY0/ZQHNItlfuEZqtcQbXIxzojlP301hDpnac=
-github.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
-github.com/hashicorp/terraform-json v0.13.0 h1:Li9L+lKD1FO5RVFRM1mMMIBDoUHslOniyEi5CM+FWGY=
-github.com/hashicorp/terraform-json v0.13.0/go.mod h1:y5OdLBCT+rxbwnpxZs9kGL7R9ExU76+cpdY8zHwoazk=
+github.com/hashicorp/hcl/v2 v2.16.2 h1:mpkHZh/Tv+xet3sy3F9Ld4FyI2tUpWe9x3XtPx9f1a0=
+github.com/hashicorp/hcl/v2 v2.16.2/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng=
+github.com/hashicorp/terraform-json v0.16.0 h1:UKkeWRWb23do5LNAFlh/K3N0ymn1qTOO8c+85Albo3s=
+github.com/hashicorp/terraform-json v0.16.0/go.mod h1:v0Ufk9jJnk6tcIZvScHvetlKfiNTC+WS21mnXIlc0B0=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
-github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
-github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
+github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
+github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
+github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.13.0 h1:2T7tUoQrQT+fQWdaY5rjWztFGAFwbGD04iPJg90ZiOs=
-github.com/klauspost/compress v1.13.0/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
+github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
+github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
-github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
-github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg=
-github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
-github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM=
+github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
+github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
-github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
+github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk=
+github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
+github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
-github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
-github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tmccombs/hcl2json v0.3.3 h1:+DLNYqpWE0CsOQiEZu+OZm5ZBImake3wtITYxQ8uLFQ=
-github.com/tmccombs/hcl2json v0.3.3/go.mod h1:Y2chtz2x9bAeRTvSibVRVgbLJhLJXKlUeIvjeVdnm4w=
-github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
-github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
-github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
-github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
-github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/tmccombs/hcl2json v0.5.0 h1:cT2sXStOzKL06c8ZTf9vh+0N8GKGzV7+9RUaY5/iUP8=
+github.com/tmccombs/hcl2json v0.5.0/go.mod h1:B0ZpBthAKbQur6yZRKrtaqDmYLCvgnwHOBApE0faCpU=
+github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
+github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
+github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
-github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
-github.com/zclconf/go-cty v1.8.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
-github.com/zclconf/go-cty v1.9.1 h1:viqrgQwFl5UpSxc046qblj78wZXVDFnSOufaOTER+cc=
-github.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
-github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/zclconf/go-cty v1.13.1 h1:0a6bRwuiSHtAmqCqNOE+c2oHgepv0ctoxU4FUe43kwc=
+github.com/zclconf/go-cty v1.13.1/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
-golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -274,6 +558,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
+golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -287,7 +573,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -299,10 +584,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -337,8 +621,22 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
-golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
+golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -350,8 +648,22 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
+golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
+golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
+golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
+golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -363,12 +675,15 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -403,10 +718,38 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e h1:w36l2Uw3dRan1K3TyXriXvY+6T56GNmlKGcqiQUJDfM=
-golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -414,11 +757,16 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -456,6 +804,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@@ -464,15 +813,25 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
+golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -494,8 +853,35 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.47.0 h1:sQLWZQvP6jPGIP4JGPkJu4zHswrv81iobiyszr3b/0I=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
+google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
+google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
+google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
+google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
+google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
+google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
+google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
+google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
+google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
+google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
+google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g=
+google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
+google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
+google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s=
+google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
+google.golang.org/api v0.118.0 h1:FNfHq9Z2GKULxu7cEhCaB0wWQHg43UpomrrN+24ZRdE=
+google.golang.org/api v0.118.0/go.mod h1:76TtD3vkgmZ66zZzp72bUUklpmQmKlhh6sYtIjYK+5E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -527,6 +913,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@@ -542,10 +929,71 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
+google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
+google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
+google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
+google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
+google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
+google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
+google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
+google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc=
+google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk=
+google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo=
+google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw=
+google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI=
+google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
+google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM=
+google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
+google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -559,6 +1007,7 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
@@ -566,8 +1015,22 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
+google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
+google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -580,20 +1043,30 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
+google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -601,6 +1074,24 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/api v0.27.1 h1:Z6zUGQ1Vd10tJ+gHcNNNgkV5emCyW+v2XTmn+CLjSd0=
+k8s.io/api v0.27.1/go.mod h1:z5g/BpAiD+f6AArpqNjkY+cji8ueZDU/WV1jcj5Jk4E=
+k8s.io/apimachinery v0.27.1 h1:EGuZiLI95UQQcClhanryclaQE6xjg1Bts6/L3cD7zyc=
+k8s.io/apimachinery v0.27.1/go.mod h1:5ikh59fK3AJ287GUvpUsryoMFtH9zj/ARfWCo3AyXTM=
+k8s.io/client-go v0.27.1 h1:oXsfhW/qncM1wDmWBIuDzRHNS2tLhK3BZv512Nc59W8=
+k8s.io/client-go v0.27.1/go.mod h1:f8LHMUkVb3b9N8bWturc+EDtVVVwZ7ueTVquFAJb2vA=
+k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
+k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c h1:EFfsozyzZ/pggw5qNx7ftTVZdp7WZl+3ih89GEjYEK8=
+k8s.io/kube-openapi v0.0.0-20230327201221-f5883ff37f0c/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg=
+k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=
+k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
+sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/test/src/subtests.go b/test/src/subtests.go
new file mode 100644
index 0000000..9b6cc44
--- /dev/null
+++ b/test/src/subtests.go
@@ -0,0 +1,236 @@
+package test
+
+import (
+ "context"
+ "fmt"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/aws/aws-sdk-go-v2/service/s3/types"
+ ttaws "github.com/gruntwork-io/terratest/modules/aws"
+ "github.com/gruntwork-io/terratest/modules/random"
+ "github.com/gruntwork-io/terratest/modules/terraform"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "regexp"
+ "testing"
+ "time"
+)
+
+type BackendTestConfig struct {
+ region string
+ bucketName string
+ backendConfig map[string]any
+ tfStateKey string
+ testData string
+ testFolder string
+ workspace string
+}
+
+func testCrossRegionProvisioning(t *testing.T, blueConfig BackendTestConfig, greenConfig BackendTestConfig) {
+ name := "Cross Region Without Workspace"
+ if len(blueConfig.workspace) > 0 {
+ name = "Cross Region With Workspace " + blueConfig.workspace
+ }
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+
+ if !provisionInBlue(t, blueConfig) {
+ assert.FailNow(t, "Blue backend not working as expected")
+ }
+
+ // Wait for replication from blue to green
+ waitForReplication(t, "blue", blueConfig)
+
+ // Verify that the values are visible in the green region, that
+ // setting them to the same value does not change anything,
+ // and that they can be changed in the green region (in preparation for
+ // testing propagation back to blue).
+
+ greenTestData, greenSuccess := verifyAndChange(t, "green", greenConfig)
+ if !greenSuccess {
+ assert.FailNow(t, "Green backend not working as expected")
+ }
+
+ // wait for replication from green to blue
+ waitForReplication(t, "green", greenConfig)
+
+ blueConfig.testData = greenTestData
+ verifyAndChange(t, "blue", blueConfig)
+ })
+}
+
+// Wait for the tfstateKey object to be replicated
+func waitForReplication(t *testing.T, color string, cfg BackendTestConfig) {
+ var (
+ otherColor string
+ pollingDuration time.Duration
+ replicaStatus *s3.HeadObjectOutput
+ rsError error
+ )
+ if color == "green" {
+ otherColor = "blue"
+ } else {
+ otherColor = "green"
+ }
+ retryTime := 10 * time.Second
+ replicaStatusRequest := &s3.HeadObjectInput{
+ Bucket: aws.String(cfg.bucketName),
+ Key: aws.String(cfg.tfStateKey),
+ }
+ s3Client := s3.NewFromConfig(AWSConfig(cfg.region))
+
+ for i := 0; i < 15*6; i++ {
+ replicaStatus, rsError = s3Client.HeadObject(context.TODO(), replicaStatusRequest)
+ if rsError == nil && replicaStatus.ReplicationStatus != types.ReplicationStatusPending {
+ break
+ }
+ if rsError != nil {
+ t.Logf("HeadObject reports error %v", rsError)
+ if i > 5 {
+ require.FailNow(t, "Terminating test due to repeated HeadObject failures")
+ }
+ }
+ t.Logf("Waiting for Terraform state to replicate from %s to %s... (%v elapsed)", color, otherColor, pollingDuration)
+ pollingDuration += retryTime
+ time.Sleep(retryTime)
+ }
+ require.NotNil(t, replicaStatus)
+ // types.ReplicationStatusComplete is actually wrong
+ // See https://github.com/aws/aws-sdk-go-v2/issues/2101
+ require.EqualValues(t, "COMPLETED", replicaStatus.ReplicationStatus)
+
+ t.Logf("Replication from %s to %s completed successfully", color, otherColor)
+}
+
+// Provision the resources the first time, using the blue backend
+func provisionInBlue(t *testing.T, cfg BackendTestConfig) bool {
+ testData := cfg.testData
+ return t.Run("Provision in blue", func(t *testing.T) {
+
+ terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
+ // The path to where our Terraform code is located
+ TerraformDir: cfg.testFolder,
+ NoColor: true,
+ Upgrade: true,
+ Vars: map[string]interface{}{
+ "test": testData,
+ },
+ BackendConfig: cfg.backendConfig,
+ })
+
+ var results string
+
+ if len(cfg.workspace) == 0 {
+ results = terraform.InitAndApply(t, terraformOptions)
+ } else {
+ if _, err := terraform.InitE(t, terraformOptions); err != nil {
+ require.FailNow(t, "Unable to initialize project in directory %s", cfg.testFolder)
+ }
+ if _, err := terraform.WorkspaceSelectOrNewE(t, terraformOptions, cfg.workspace); err != nil {
+ require.FailNow(t, "Unable to create workspace %s", cfg.workspace)
+ }
+ results = terraform.Apply(t, terraformOptions)
+ }
+
+ outputsRegex := regexp.MustCompile(`(?s)\n(Changes to Outputs:\n.+?\n)\n`)
+ outputsMatch := outputsRegex.FindString(results)
+ assert.NotEmpty(t, outputsMatch, "Apply should change outputs")
+
+ outData := terraform.Output(t, terraformOptions, "test")
+ require.Equal(t, testData, outData, "Unable to create resource in blue backend")
+
+ // Check a state file actually got stored in S3 and contains our data in it somewhere
+ // (since that data is used in an output of the Terraform code)
+ contents := ttaws.GetS3ObjectContents(t, cfg.region, cfg.bucketName, cfg.tfStateKey)
+ require.Contains(t, contents, testData)
+ })
+}
+
+// Verify that the values are visible in a region, that
+// setting them to the same value does not change anything,
+// and that they can be changed in the region (in preparation for
+// testing propagation back to the other region).
+
+func verifyAndChange(t *testing.T, color string, cfg BackendTestConfig) (string, bool) {
+ // tempTestFolder string, backendMap map[string]string, bucketName string, tfstateKey string, testData string) (string, bool) {
+ region := cfg.region
+ newTestData := fmt.Sprintf("Test data for %s region %s: (%s)", color, region, random.UniqueId())
+ return newTestData, t.Run(fmt.Sprintf("Verify and change in %s", color), func(t *testing.T) {
+ s3Client := s3.NewFromConfig(AWSConfig(region))
+
+ // Check a state file actually got stored in S3 and contains our data in it somewhere
+ // (since that data is used in an output of the Terraform code)
+ contents := ttaws.GetS3ObjectContents(t, region, cfg.bucketName, cfg.tfStateKey)
+ require.Contains(t, contents, cfg.testData)
+
+ if color != "only" {
+ // Check object's replica status
+ replicaStatusRequest := &s3.HeadObjectInput{
+ Bucket: aws.String(cfg.bucketName),
+ Key: aws.String(cfg.tfStateKey),
+ }
+
+ replicaStatus, rsError := s3Client.HeadObject(context.TODO(), replicaStatusRequest)
+ assert.NoError(t, rsError, fmt.Sprintf("Error getting status of tfstate object in %s bucket", color))
+ assert.NotNil(t, replicaStatus, fmt.Sprintf("No status returned from HeadObject for tfstate object in %s bucket", color))
+
+ if replicaStatus != nil {
+ assert.Equal(t, types.ReplicationStatusReplica, replicaStatus.ReplicationStatus, "Replicated tfstate not marked as REPLICA")
+ }
+ }
+
+ terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
+ // The path to where our Terraform code is located
+ TerraformDir: cfg.testFolder,
+ NoColor: true,
+ Reconfigure: true,
+ Upgrade: true,
+ Vars: map[string]interface{}{
+ "test": cfg.testData,
+ },
+ BackendConfig: cfg.backendConfig,
+ })
+
+ if _, err := terraform.InitE(t, terraformOptions); err != nil {
+ // Error will have already been logged, no need to log it again
+ require.FailNow(t, fmt.Sprintf("Terminating test: unable to initialize %s backend", color))
+ }
+
+ if len(cfg.workspace) > 0 {
+ if _, err := terraform.WorkspaceSelectOrNewE(t, terraformOptions, cfg.workspace); err != nil {
+ require.FailNow(t, "Unable to select workspace %s", cfg.workspace)
+ }
+ }
+
+ outData := terraform.Output(t, terraformOptions, "test")
+ assert.Equal(t, cfg.testData, outData, fmt.Sprintf("Unable to read resource in %s backend", color))
+
+ results := terraform.Apply(t, terraformOptions)
+
+ // Should complete successfully without creating or changing any resources.
+ // Extract the "Resources:" section of the output to make the error message more readable.
+ idempotentMessage := fmt.Sprintf("Re-applying the same configuration in the %s backend should not change any ", color)
+ resourcesRegex := regexp.MustCompile(`Resources: [^.]+\.`)
+ resourcesMatch := resourcesRegex.FindString(results)
+ assert.Equal(t, "Resources: 0 added, 0 changed, 0 destroyed.", resourcesMatch, idempotentMessage+"resources")
+
+ outputsRegex := regexp.MustCompile(`(?s)\n(Changes to Outputs:\n.+?\n)\n`)
+ outputsMatchSlice := outputsRegex.FindStringSubmatch(results)
+
+ if len(outputsMatchSlice) > 0 {
+ assert.Empty(t, outputsMatchSlice[1], idempotentMessage+"outputs")
+ }
+
+ terraformOptions.Vars = map[string]interface{}{
+ "test": newTestData,
+ }
+
+ results = terraform.Apply(t, terraformOptions)
+
+ outputsMatch := outputsRegex.FindString(results)
+ assert.NotEmpty(t, outputsMatch, "Apply should change outputs")
+
+ outData = terraform.Output(t, terraformOptions, "test")
+ require.Equal(t, newTestData, outData, "Output not updated")
+ })
+}
diff --git a/test/src/utils.go b/test/src/utils.go
new file mode 100644
index 0000000..ff9aabe
--- /dev/null
+++ b/test/src/utils.go
@@ -0,0 +1,43 @@
+package test
+
+import (
+ "context"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/config"
+ "github.com/gruntwork-io/terratest/modules/terraform"
+ "github.com/stretchr/testify/require"
+ "log"
+ "os"
+ "testing"
+)
+
+func cleanup(t *testing.T, terraformOptions *terraform.Options, tempTestFolder string) {
+ t.Logf("Cleanup running Terraform destroy in folder %s\n", tempTestFolder)
+ // If Destroy fails, it will log the error, so we do not need to log it again,
+ // but we want to fail immediately rather than delete the temp folder, so we
+ // have a chance to inspect the state, fix what went wrong, and destroy the resources.
+ if _, err := terraform.DestroyE(t, terraformOptions); err != nil {
+ require.FailNow(t, "Terraform destroy failed.\nNot deleting temp test folder (%s)", tempTestFolder)
+ }
+ if err := os.RemoveAll(tempTestFolder); err != nil {
+ t.Logf("Error deleting temp folder %v: \n %v", tempTestFolder, err)
+ }
+}
+
+func mapToConfig(m map[string]string) map[string]any {
+ c := make(map[string]any)
+ for k, v := range m {
+ c[k] = v
+ }
+ return c
+}
+
+func AWSConfig(region string) aws.Config {
+ // Load the default AWS Configuration
+ cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ return cfg
+}
diff --git a/variables.tf b/variables.tf
index f80d6b8..c677723 100644
--- a/variables.tf
+++ b/variables.tf
@@ -1,196 +1,127 @@
-variable "arn_format" {
- type = string
- default = "arn:aws"
- description = "ARN format to be used. May be changed to support deployment in GovCloud/China regions."
-}
-
-variable "acl" {
- type = string
- description = "The canned ACL to apply to the S3 bucket"
- default = "private"
-}
-
-variable "billing_mode" {
- default = "PROVISIONED"
- description = "DynamoDB billing mode"
-}
-
-variable "read_capacity" {
- default = 5
- description = "DynamoDB read capacity units"
-}
-
-variable "write_capacity" {
- default = 5
- description = "DynamoDB write capacity units"
-}
-
-variable "force_destroy" {
+variable "blue_s3_bucket_name" {
+ type = list(string)
+ description = "S3 bucket name for bucket in blue region. If not provided, the name will be generated from context."
+ default = []
+ validation {
+ condition = length(var.blue_s3_bucket_name) < 2 && try(length(var.blue_s3_bucket_name[0]), 0) <= 63
+ error_message = "Only 1 blue_s3_bucket_name can be provided, and it must be no more than 63 characters."
+ }
+}
+
+variable "blue_kms_key_arn" {
+ type = list(string)
+ description = <<-EOT
+ The KMS Key ARN for encrypting object (SSE-KMS) in the blue bucket. Default is to use default (SSE-S3) key.
+ Note: If you are not using the default key and have replication enabled, you must grant the replication role
+ permission to use the key (`kms:Encrypt` and `kms:Decrypt`).
+ EOT
+ default = []
+ validation {
+ condition = length(var.blue_kms_key_arn) < 2
+ error_message = "Only 1 blue_kms_key_arn can be provided."
+ }
+}
+
+variable "green_s3_bucket_name" {
+ type = list(string)
+ description = "S3 bucket name for bucket in green region. If not provided, the name will be generated from context."
+ default = []
+ validation {
+ condition = length(var.green_s3_bucket_name) < 2 && try(length(var.green_s3_bucket_name[0]), 0) <= 63
+ error_message = "Only 1 green_s3_bucket_name can be provided, and it must be no more than 63 characters."
+ }
+}
+
+variable "green_kms_key_arn" {
+ type = list(string)
+ description = "The KMS Key ARN for encrypting object (SSE-KMS) in the green bucket. Default is to use default (SSE-S3) key."
+ default = []
+ validation {
+ condition = length(var.green_kms_key_arn) < 2
+ error_message = "Only 1 green_kms_key_arn can be provided."
+ }
+}
+
+variable "replication_enabled" {
type = bool
- description = "A boolean that indicates the S3 bucket can be destroyed even if it contains objects. These objects are not recoverable"
- default = false
-}
-
-variable "mfa_delete" {
- type = bool
- description = "A boolean that indicates that versions of S3 objects can only be deleted with MFA. ( Terraform cannot apply changes of this value; https://github.com/terraform-providers/terraform-provider-aws/issues/629 )"
- default = false
-}
-
-variable "enable_point_in_time_recovery" {
- type = bool
- description = "Enable DynamoDB point-in-time recovery"
+ description = <<-EOT
+ Set true to enable bidirectional replication and creation of hot standby for quick failover. Highly recommended.
+ If set to `false`, the "green" configuration will be ignored.
+ Replication is not supported in AWS GovCloud (US) because S3 Replication Time Control (S3 RTC) is not available.
+ EOT
default = true
}
-variable "enable_server_side_encryption" {
- type = bool
- description = "Enable DynamoDB server-side encryption"
- default = true
+variable "replication_role_name" {
+ type = list(string)
+ description = "The name to give to the IAM role created for replication. If not provided, the name will be generated from context."
+ default = []
+ validation {
+ condition = length(var.replication_role_name) < 2 && try(length(var.replication_role_name[0]), 0) <= 64
+ error_message = "Only 1 replication_role_name can be provided, and it must be no more than 64 characters."
+ }
}
-variable "enable_public_access_block" {
+variable "lock_table_enabled" {
type = bool
- description = "Enable Bucket Public Access Block"
+ description = <<-EOT
+ Set true to create a DynamoDB table to provide Terraform state locking. Highly recommended.
+ If replication is enabled, a global DynamoDB table will be created in both the blue and green regions.
+ EOT
default = true
}
-variable "block_public_acls" {
- type = bool
- description = "Whether Amazon S3 should block public ACLs for this bucket"
- default = true
-}
-
-variable "ignore_public_acls" {
- type = bool
- description = "Whether Amazon S3 should ignore public ACLs for this bucket"
- default = true
-}
-
-variable "block_public_policy" {
- description = "Whether Amazon S3 should block public bucket policies for this bucket"
- default = true
-}
-
-variable "restrict_public_buckets" {
- type = bool
- description = "Whether Amazon S3 should restrict public bucket policies for this bucket"
- default = true
-}
-
-variable "prevent_unencrypted_uploads" {
- type = bool
- default = true
- description = "Prevent uploads of unencrypted objects to S3"
-}
-
-variable "profile" {
- type = string
- default = ""
- description = "AWS profile name as set in the shared credentials file"
-}
-
-variable "role_arn" {
- type = string
- default = ""
- description = "The role to be assumed"
-}
-
-variable "terraform_backend_config_file_name" {
- type = string
- default = "terraform.tf"
- description = "Name of terraform backend config file"
-}
-
-variable "terraform_backend_config_file_path" {
- type = string
- default = ""
- description = "Directory for the terraform backend config file, usually `.`. The default is to create no file."
-}
-
-variable "terraform_backend_config_template_file" {
- type = string
- default = ""
- description = "The path to the template used to generate the config file"
-}
-
-variable "terraform_version" {
- type = string
- default = "0.12.2"
- description = "The minimum required terraform version"
-}
-
-variable "terraform_state_file" {
- type = string
- default = "terraform.tfstate"
- description = "The path to the state file inside the bucket"
-}
-
-variable "s3_bucket_name" {
- type = string
- default = ""
- description = "S3 bucket name. If not provided, the name will be generated by the label module in the format namespace-stage-name"
-}
-
-variable "s3_replication_enabled" {
- type = bool
- default = false
- description = "Set this to true and specify `s3_replica_bucket_arn` to enable replication"
-}
-
-variable "s3_replica_bucket_arn" {
- type = string
- default = ""
- description = "The ARN of the S3 replica bucket (destination)"
-}
-
-variable "logging" {
- type = object({
- bucket_name = string
- prefix = string
- })
- default = null
- description = "Bucket access logging configuration."
+variable "dynamodb_table_name" {
+ type = list(string)
+ description = "The name of the DynamoDB table. If not provided, the name will be generated from context."
+ default = []
+ validation {
+ condition = length(var.dynamodb_table_name) < 2
+ error_message = "Only 1 dynamodb_table_name can be provided."
+ }
+}
+
+variable "permissions_boundary" {
+ type = list(string)
+ description = "The ARN of the policy that sets the permissions boundary for the IAM role used for replication."
+ default = []
+ validation {
+ condition = length(var.permissions_boundary) < 2
+ error_message = "Only 1 permissions_boundary can be provided."
+ }
+}
+
+variable "blue_bucket_logging" {
+ type = list(object({
+ target_bucket = string
+ target_prefix = string
+ }))
+ description = "Destination for S3 Server Access Logs for the blue bucket."
+ default = []
+ validation {
+ condition = length(var.blue_bucket_logging) < 2
+ error_message = "Only 1 blue bucket logging configuration can be provided."
+ }
+}
+
+variable "green_bucket_logging" {
+ type = list(object({
+ target_bucket = string
+ target_prefix = string
+ }))
+ description = "Destination for S3 Server Access Logs for the green bucket."
+ default = []
+ validation {
+ condition = length(var.green_bucket_logging) < 2
+ error_message = "Only 1 green bucket logging configuration can be provided."
+ }
}
-variable "logging_bucket_enabled" {
+variable "force_destroy" {
type = bool
+ description = <<-EOT
+ FOR TESTING ONLY! When set to true, `terraform destroy` will destroy the S3 buckets and all the objects in them.
+ These objects are not recoverable even if you have versioning or backups enabled.
+ EOT
default = false
- description = "Whether to create the s3 access log bucket."
-}
-
-variable "logging_bucket_standard_transition_days" {
- type = number
- default = 30
- description = "Whether to create the s3 access log bucket."
-}
-
-variable "logging_bucket_glacier_transition_days" {
- type = number
- default = 60
- description = "Whether to create the s3 access log bucket."
-}
-
-variable "logging_bucket_expiration_days" {
- type = number
- default = 90
- description = "Whether to create the s3 access log bucket."
-}
-
-variable "bucket_enabled" {
- type = bool
- default = true
- description = "Whether to create the s3 bucket."
-}
-
-variable "dynamodb_enabled" {
- type = bool
- default = true
- description = "Whether to create the dynamodb table."
-}
-
-variable "dynamodb_table_name" {
- type = string
- default = null
- description = "Override the name of the DynamoDB table which defaults to using `module.dynamodb_table_label.id`"
}
diff --git a/versions.tf b/versions.tf
index 2080158..2a8218f 100644
--- a/versions.tf
+++ b/versions.tf
@@ -1,14 +1,11 @@
terraform {
- required_version = ">= 0.13.0"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
- source = "hashicorp/aws"
- version = ">= 2.0"
- }
- local = {
- source = "hashicorp/local"
- version = ">= 1.3"
+ source = "hashicorp/aws"
+ version = ">= 4.0"
+ configuration_aliases = [aws.blue, aws.green]
}
}
}