From 0ecffa1bb005332f90ae981d3f841e9d323b4f64 Mon Sep 17 00:00:00 2001 From: Jerome Wolff Date: Thu, 2 Nov 2023 20:34:37 +0100 Subject: [PATCH 1/4] * Rewrite large paths * Split rules to predefined rules/custom rules * Add tests --- .editorconfig | 2 +- .github/workflows/test.yaml | 57 ++++++ .terraform-docs.yml | 9 +- README.md | 151 +++++++++++--- data.tf | 11 - docs/20-badges.md | 1 + examples/basic-example/main.tf | 8 - examples/minimal/main.tf | 7 + .../main.tf | 12 +- main.tf | 138 +++++++------ outputs.tf | 19 ++ rules.tf | 88 ++++++++ test/.gitignore | 0 tests/custom_rules.tftest.hcl | 66 ++++++ tests/custom_rules_override.tftest.hcl | 48 +++++ tests/predefined_rules.tftest.hcl | 57 ++++++ variables.tf | 188 ++++++++++-------- 17 files changed, 657 insertions(+), 205 deletions(-) create mode 100644 .github/workflows/test.yaml delete mode 100644 data.tf delete mode 100644 examples/basic-example/main.tf create mode 100644 examples/minimal/main.tf rename examples/{with-rules => with-predefined-rules}/main.tf (61%) create mode 100644 rules.tf delete mode 100644 test/.gitignore create mode 100644 tests/custom_rules.tftest.hcl create mode 100644 tests/custom_rules_override.tftest.hcl create mode 100644 tests/predefined_rules.tftest.hcl diff --git a/.editorconfig b/.editorconfig index 0a5f88d..2705490 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ end_of_line = lf indent_size = 2 indent_style = space insert_final_newline = true -max_line_length = 80 +max_line_length = 120 trim_trailing_whitespace = true [*.md] diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..ff7d648 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,57 @@ +--- +############### +## Run tests ## +############### + +# +# Documentation: +# https://help.github.com/en/articles/workflow-syntax-for-github-actions +# + +name: Test +on: + pull_request: + push: + branches: [ main ] + +########################## +# Prevent duplicate jobs # +########################## +concurrency: + group: ${{ github.repository }} + cancel-in-progress: false + +permissions: + id-token: write + contents: read + +############### +# Run the job # +############### +jobs: + terraform-test: + name: Terraform Test + runs-on: ubuntu-latest + steps: + ############################ + # Checkout the source code # + ############################ + - name: Checkout + uses: actions/checkout@v3 + + ############################# + # Configure AWS credentials # + ############################# + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + role-to-assume: arn:aws:iam::${{ vars.AWS_TESTING_ACCOUNT_ID }}:role/${{ vars.AWS_TESTING_ROLE }} + aws-region: ${{ vars.AWS_TESTING_REGION }} + mask-aws-account-id: false + + ############# + # Run tests # + ############# + - name: Run Tests + timeout-minutes: 30 + run: terraform init && terraform test diff --git a/.terraform-docs.yml b/.terraform-docs.yml index b0056d2..18d7275 100644 --- a/.terraform-docs.yml +++ b/.terraform-docs.yml @@ -26,14 +26,19 @@ content: |- # Examples ### Basic Example ```hcl - {{ include "examples/basic-example/main.tf" }} + {{ include "examples/minimal/main.tf" }} ``` ### With Rules ```hcl - {{ include "examples/with-rules/main.tf" }} + {{ include "examples/with-predefined-rules/main.tf" }} ``` + # Predefined Rules + ```hcl + {{ include "rules.tf" }} + ``` + output: file: "README.md" mode: inject diff --git a/README.md b/README.md index e8966d3..0443174 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Release](https://github.com/geekcell/terraform-aws-backup/actions/workflows/release.yaml/badge.svg)](https://github.com/geekcell/terraform-aws-backup/actions/workflows/release.yaml) [![Validate](https://github.com/geekcell/terraform-aws-backup/actions/workflows/validate.yaml/badge.svg)](https://github.com/geekcell/terraform-aws-backup/actions/workflows/validate.yaml) [![Lint](https://github.com/geekcell/terraform-aws-backup/actions/workflows/linter.yaml/badge.svg)](https://github.com/geekcell/terraform-aws-backup/actions/workflows/linter.yaml) +[![Test](https://github.com/geekcell/terraform-aws-backup/actions/workflows/test.yaml/badge.svg)](https://github.com/geekcell/terraform-aws-backup/actions/workflows/test.yaml) ### Security [![Infrastructure Tests](https://www.bridgecrew.cloud/badges/github/geekcell/terraform-aws-backup/general)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=geekcell%2Fterraform-aws-backup&benchmark=INFRASTRUCTURE+SECURITY) @@ -37,7 +38,7 @@ This Terraform module provides a preconfigured solution for setting up AWS Backup in your AWS account. With this module, you can easily and -efficiently create and manage backup policies for your AWS resources. Our +efficiently create and manage backups for your AWS resources. Our team has extensive experience working with AWS Backup and has optimized this module to provide the best possible experience for users. @@ -53,19 +54,30 @@ great choice. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [backup\_name](#input\_backup\_name) | The display name of a backup plan. | `string` | n/a | yes | -| [changeable\_for\_days](#input\_changeable\_for\_days) | The number of days before the lock date. If omitted creates a vault lock in governance mode, otherwise it will create a vault lock in compliance mode. | `number` | `null` | no | +| [changeable\_for\_days](#input\_changeable\_for\_days) | The number of days before the lock date. If omitted creates a vault lock in governance mode, otherwise it will create
a vault lock in compliance mode. When you apply this setting:

The vault will become immutable in 3 days after applying. You have 3 days of grace time to manage or delete the vault
lock before it becomes immutable. During this time, only those users with specific IAM permissions can make changes.

Once the vault is locked in compliance mode, it cannot be managed or deleted by anyone, even the root user or AWS.
The only way to deactivate the lock is to terminate the account, which will delete all the backups.

Since you cannot delete the Vault, it will be charged for backups until that date. Be careful! | `number` | `null` | no | +| [custom\_rules](#input\_custom\_rules) | Backup rules to add to the AWS Backup Vault. See examples for usage. |
list(object({
name = string
schedule = optional(string)

start_window = optional(number)
completion_window = optional(number)

enable_continuous_backup = optional(bool)
recovery_point_tags = optional(map(string), {})

lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))

copy_action = optional(object({
destination_vault_arn = optional(string)
lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))
}))
}))
| `[]` | no | +| [enable\_customer\_managed\_kms](#input\_enable\_customer\_managed\_kms) | Whether to enable customer managed KMS encryption for the backup vault. | `bool` | `false` | no | +| [enable\_vault\_lock](#input\_enable\_vault\_lock) | Whether to enable Vault Lock for the backup vault. | `bool` | `false` | no | +| [enable\_windows\_vss\_backup](#input\_enable\_windows\_vss\_backup) | Whether to enable Windows VSS backup for the backup plan. | `bool` | `false` | no | +| [kms\_key\_id](#input\_kms\_key\_id) | The ARN of the KMS Key to use to encrypt your backups. If left empty, the default AWS KMS will be used. | `string` | `null` | no | | [max\_retention\_days](#input\_max\_retention\_days) | The maximum retention period that the vault retains its recovery points. | `number` | `365` | no | | [min\_retention\_days](#input\_min\_retention\_days) | The minimum retention period that the vault retains its recovery points. | `number` | `7` | no | -| [resources](#input\_resources) | An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to assign to a backup plan. | `list(string)` | n/a | yes | -| [rules](#input\_rules) | Backup rules to add to the AWS Backup Vault. See examples for usage. |
list(object({
name = string
schedule = string
start_window = number
completion_window = number
enable_continuous_backup = bool
lifecycle = map(string)
}))
|
[
{
"completion_window": 240,
"enable_continuous_backup": false,
"lifecycle": {
"cold_storage_after": 1,
"delete_after": 365
},
"name": "weekly-snapshot",
"schedule": "cron(0 3 ? * 2,3,4,5,6,7,1 *)",
"start_window": 60
},
{
"completion_window": 240,
"enable_continuous_backup": false,
"lifecycle": {
"cold_storage_after": 1,
"delete_after": 365
},
"name": "monthly-snapshot",
"schedule": "cron(0 3 1 * ? *)",
"start_window": 60
},
{
"completion_window": 240,
"enable_continuous_backup": false,
"lifecycle": {
"cold_storage_after": 1,
"delete_after": 730
},
"name": "quarterly-snapshot",
"schedule": "cron(0 3 1 1,4,7,10 ? *)",
"start_window": 60
},
{
"completion_window": 240,
"enable_continuous_backup": false,
"lifecycle": {
"cold_storage_after": 1,
"delete_after": 3650
},
"name": "yearly-snapshot",
"schedule": "cron(0 3 1 1 ? *)",
"start_window": 60
},
{
"completion_window": 240,
"enable_continuous_backup": true,
"lifecycle": {
"cold_storage_after": null,
"delete_after": 35
},
"name": "daily-snapshot",
"schedule": "cron(0 3 ? * * *)",
"start_window": 60
}
]
| no | -| [service](#input\_service) | The service that the resource belongs to. | `string` | n/a | yes | +| [plan\_name](#input\_plan\_name) | The display name of the backup plan. | `string` | n/a | yes | +| [predefined\_rules](#input\_predefined\_rules) | A list of predefined backup rules to add to the AWS Backup Plan. See examples for usage. | `list(string)` | `[]` | no | +| [resources](#input\_resources) | An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to assign to a backup plan. | `list(string)` | `[]` | no | +| [role\_arn](#input\_role\_arn) | The ARN of the IAM role that AWS Backup uses to authenticate when restoring or backing up the target resources. If left empty, a default role will be created. | `string` | `null` | no | | [tags](#input\_tags) | Tags to add to the AWS Backup. | `map(any)` | `{}` | no | +| [vault\_force\_destroy](#input\_vault\_force\_destroy) | Whether to allow the backup vault to be destroyed even if it contains recovery points. | `string` | `false` | no | | [vault\_name](#input\_vault\_name) | Name of the backup vault to create. | `string` | n/a | yes | ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| [backup\_plan\_arn](#output\_backup\_plan\_arn) | The ARN of the backup plan. | +| [backup\_plan\_id](#output\_backup\_plan\_id) | The ID of the backup plan. | +| [backup\_vault\_arn](#output\_backup\_vault\_arn) | The ARN of the backup vault. | +| [backup\_vault\_id](#output\_backup\_vault\_id) | The ID of the backup vault. | ## Providers @@ -75,16 +87,10 @@ No outputs. ## Resources -- resource.aws_backup_plan.main (main.tf#55) -- resource.aws_backup_selection.main (main.tf#48) -- resource.aws_backup_vault.main (main.tf#18) -- resource.aws_backup_vault_lock_configuration.main (main.tf#25) -- resource.aws_iam_role.main (main.tf#96) -- resource.aws_iam_role_policy_attachment.main_backup (main.tf#103) -- resource.aws_iam_role_policy_attachment.main_restore (main.tf#108) -- resource.aws_iam_role_policy_attachment.s3_backup (main.tf#113) -- resource.aws_iam_role_policy_attachment.s3_restore (main.tf#118) -- data source.aws_iam_policy_document.main (data.tf#1) +- resource.aws_backup_plan.main (main.tf#45) +- resource.aws_backup_selection.main (main.tf#103) +- resource.aws_backup_vault.main (main.tf#27) +- resource.aws_backup_vault_lock_configuration.main (main.tf#35) # Examples ### Basic Example @@ -92,10 +98,9 @@ No outputs. module "basic-example" { source = "../../" - vault_name = "main" - backup_name = "rds" - service = "s3" - resources = ["arn:aws:s3:::my-bucket"] + vault_name = "main" + plan_name = "s3" + resources = ["arn:aws:s3:::my-bucket"] } ``` @@ -104,14 +109,14 @@ module "basic-example" { module "with-rules" { source = "../../" - vault_name = "main" - backup_name = "rds" - service = "s3" - resources = ["arn:aws:s3:::my-bucket"] + vault_name = "main" + plan_name = "s3" + resources = ["arn:aws:s3:::my-bucket"] - rules = [ + predefined_rules = ["daily-snapshot", "monthly-snapshot"] + custom_rules = [ { - name = "weekly-snapshot" + name = "my-custom-rule" schedule = "cron(0 3 ? * 2,3,4,5,6,7,1 *)" start_window = 60 completion_window = 240 @@ -125,4 +130,96 @@ module "with-rules" { ] } ``` + +# Predefined Rules +```hcl +locals { + predefined_rules = [ + # At 03:00 AM UTC, daily + { + name = "daily-snapshot" + schedule = "cron(0 3 ? * * *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = true + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = null + delete_after = 35 # 5 weeks + } + + copy_action = null + }, + + # At 03:00 AM UTC, every Sunday + { + name = "weekly-snapshot" + schedule = "cron(0 3 ? * SUN *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = true + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = null + delete_after = 183 # 6 months + } + + copy_action = null + }, + + # At 03:00 AM UTC, on day 1 of the month + { + name = "monthly-snapshot" + schedule = "cron(0 3 1 * ? *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = false + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = 1 # day + delete_after = 365 # 1 year + } + + copy_action = null + }, + + # At 03:00 AM UTC, on day 1 of the month, only in January, April, July, and October + { + name = "quarterly-snapshot" + schedule = "cron(0 3 1 1,4,7,10 ? *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = false + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = 1 # day + delete_after = 730 # 2 years + } + + copy_action = null + }, + + # At 03:00 AM UTC, on day 1 of the month, only in January + { + name = "yearly-snapshot" + schedule = "cron(0 3 1 1 ? *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = false + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = 1 # day + delete_after = 3650 # 10 years + } + + copy_action = null + } + ] +} +``` diff --git a/data.tf b/data.tf deleted file mode 100644 index bc54422..0000000 --- a/data.tf +++ /dev/null @@ -1,11 +0,0 @@ -data "aws_iam_policy_document" "main" { - - statement { - effect = "Allow" - principals { - identifiers = ["backup.amazonaws.com"] - type = "Service" - } - actions = ["sts:AssumeRole"] - } -} diff --git a/docs/20-badges.md b/docs/20-badges.md index 6eea637..1513117 100644 --- a/docs/20-badges.md +++ b/docs/20-badges.md @@ -4,6 +4,7 @@ [![Release](https://github.com/geekcell/terraform-aws-backup/actions/workflows/release.yaml/badge.svg)](https://github.com/geekcell/terraform-aws-backup/actions/workflows/release.yaml) [![Validate](https://github.com/geekcell/terraform-aws-backup/actions/workflows/validate.yaml/badge.svg)](https://github.com/geekcell/terraform-aws-backup/actions/workflows/validate.yaml) [![Lint](https://github.com/geekcell/terraform-aws-backup/actions/workflows/linter.yaml/badge.svg)](https://github.com/geekcell/terraform-aws-backup/actions/workflows/linter.yaml) +[![Test](https://github.com/geekcell/terraform-aws-backup/actions/workflows/test.yaml/badge.svg)](https://github.com/geekcell/terraform-aws-backup/actions/workflows/test.yaml) ### Security [![Infrastructure Tests](https://www.bridgecrew.cloud/badges/github/geekcell/terraform-aws-backup/general)](https://www.bridgecrew.cloud/link/badge?vcs=github&fullRepo=geekcell%2Fterraform-aws-backup&benchmark=INFRASTRUCTURE+SECURITY) diff --git a/examples/basic-example/main.tf b/examples/basic-example/main.tf deleted file mode 100644 index 860df35..0000000 --- a/examples/basic-example/main.tf +++ /dev/null @@ -1,8 +0,0 @@ -module "basic-example" { - source = "../../" - - vault_name = "main" - backup_name = "rds" - service = "s3" - resources = ["arn:aws:s3:::my-bucket"] -} diff --git a/examples/minimal/main.tf b/examples/minimal/main.tf new file mode 100644 index 0000000..48a1678 --- /dev/null +++ b/examples/minimal/main.tf @@ -0,0 +1,7 @@ +module "basic-example" { + source = "../../" + + vault_name = "main" + plan_name = "s3" + resources = ["arn:aws:s3:::my-bucket"] +} diff --git a/examples/with-rules/main.tf b/examples/with-predefined-rules/main.tf similarity index 61% rename from examples/with-rules/main.tf rename to examples/with-predefined-rules/main.tf index 8243dd0..acd70bd 100644 --- a/examples/with-rules/main.tf +++ b/examples/with-predefined-rules/main.tf @@ -1,14 +1,14 @@ module "with-rules" { source = "../../" - vault_name = "main" - backup_name = "rds" - service = "s3" - resources = ["arn:aws:s3:::my-bucket"] + vault_name = "main" + plan_name = "s3" + resources = ["arn:aws:s3:::my-bucket"] - rules = [ + predefined_rules = ["daily-snapshot", "monthly-snapshot"] + custom_rules = [ { - name = "weekly-snapshot" + name = "my-custom-rule" schedule = "cron(0 3 ? * 2,3,4,5,6,7,1 *)" start_window = 60 completion_window = 240 diff --git a/main.tf b/main.tf index 3ba3bda..b60808d 100644 --- a/main.tf +++ b/main.tf @@ -3,7 +3,7 @@ * * This Terraform module provides a preconfigured solution for setting up * AWS Backup in your AWS account. With this module, you can easily and - * efficiently create and manage backup policies for your AWS resources. Our + * efficiently create and manage backups for your AWS resources. Our * team has extensive experience working with AWS Backup and has optimized * this module to provide the best possible experience for users. * @@ -15,115 +15,127 @@ * streamline your existing backup processes, this Terraform module is a * great choice. */ +locals { + # Merge predefined rules with the passed rules. If the names of a predefined rule and a passed rule match, + # the passed rule will take precedence and they will be merged. + merged_rules = merge( + { for rule in local.predefined_rules : rule.name => rule if contains(var.predefined_rules, rule.name) }, + { for rule in var.custom_rules : rule.name => rule } + ) +} + resource "aws_backup_vault" "main" { - name = var.vault_name - kms_key_arn = module.kms.key_arn + name = var.vault_name + force_destroy = var.vault_force_destroy + kms_key_arn = var.enable_customer_managed_kms ? module.kms[0].key_arn : var.kms_key_id tags = var.tags } resource "aws_backup_vault_lock_configuration" "main" { - backup_vault_name = var.vault_name + count = var.enable_vault_lock ? 1 : 0 - min_retention_days = var.min_retention_days - max_retention_days = var.max_retention_days - - # When you apply these settings: - # - # The vault will become immutable in 3 days after applying. - # You have 3 days of grace time to manage or delete the vault - # lock before it becomes immutable. During this time, only - # those users with specific IAM permissions can make changes. - # - # Once the vault is locked in compliance mode, it cannot be - # managed or deleted by anyone, even the root user or AWS. - # The only way to deactivate the lock is to terminate the account, - # which will delete all the backups. - # - # Since you cannot delete the Vault, it will be charged - # for backups until that date. Be careful! + backup_vault_name = var.vault_name changeable_for_days = var.changeable_for_days -} -resource "aws_backup_selection" "main" { - iam_role_arn = aws_iam_role.main.arn - name = "${var.vault_name}-backup" - plan_id = aws_backup_plan.main.id - resources = var.resources + min_retention_days = var.min_retention_days + max_retention_days = var.max_retention_days } resource "aws_backup_plan" "main" { - name = var.backup_name + name = var.plan_name dynamic "rule" { - for_each = var.rules + for_each = local.merged_rules content { - target_vault_name = aws_backup_vault.main.name + target_vault_name = aws_backup_vault.main.name + rule_name = rule.value.name schedule = rule.value.schedule start_window = rule.value.start_window completion_window = rule.value.completion_window enable_continuous_backup = rule.value.enable_continuous_backup + recovery_point_tags = merge(var.tags, rule.value.recovery_point_tags) dynamic "lifecycle" { - for_each = [true] + for_each = rule.value.lifecycle != null ? [rule.value.lifecycle] : [] content { - delete_after = rule.value.lifecycle.delete_after - cold_storage_after = rule.value.lifecycle.cold_storage_after + delete_after = lifecycle.value.delete_after + cold_storage_after = lifecycle.value.cold_storage_after } } - copy_action { - destination_vault_arn = aws_backup_vault.main.arn - } + dynamic "copy_action" { + for_each = rule.value.copy_action != null ? [rule.value.copy_action] : [] - recovery_point_tags = { - Name = rule.value.name + content { + destination_vault_arn = copy_action.value.destination_vault_arn + + dynamic "lifecycle" { + for_each = copy_action.value.lifecycle != null ? [copy_action.value.lifecycle] : [] + + content { + delete_after = lifecycle.value.delete_after + cold_storage_after = lifecycle.value.cold_storage_after + } + } + } } } } - tags = merge( - var.tags, - { - "ServiceType" = "backup" + dynamic "advanced_backup_setting" { + for_each = var.enable_windows_vss_backup ? [true] : [] + + content { + resource_type = "EC2" + backup_options = { + WindowsVSS = "enabled" + } } - ) + } + + tags = var.tags } -resource "aws_iam_role" "main" { - name = "${var.vault_name}-backup" - description = "This role is responsible for the backup in the Vault." +resource "aws_backup_selection" "main" { + count = length(var.resources) > 0 ? 1 : 0 - assume_role_policy = data.aws_iam_policy_document.main.json -} + name = "${var.vault_name}-backup" + plan_id = aws_backup_plan.main.id -resource "aws_iam_role_policy_attachment" "main_backup" { - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup" - role = aws_iam_role.main.name + iam_role_arn = coalesce(var.role_arn, module.iam_role[0].arn) + resources = var.resources } -resource "aws_iam_role_policy_attachment" "main_restore" { - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores" - role = aws_iam_role.main.name -} +module "iam_role" { + count = var.role_arn == null ? 1 : 0 -resource "aws_iam_role_policy_attachment" "s3_backup" { - policy_arn = "arn:aws:iam::aws:policy/AWSBackupServiceRolePolicyForS3Backup" - role = aws_iam_role.main.name -} + source = "geekcell/iam-role/aws" + version = ">= 1.0.0, < 2.0.0" -resource "aws_iam_role_policy_attachment" "s3_restore" { - policy_arn = "arn:aws:iam::aws:policy/AWSBackupServiceRolePolicyForS3Restore" - role = aws_iam_role.main.name + name = "${var.vault_name}-backup" + + description = "This role is responsible for restoring/backing up the resources in the Vault." + assume_roles = { "Service" : { identifiers = ["backup.amazonaws.com"] } } + policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup", + "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores", + "arn:aws:iam::aws:policy/AWSBackupServiceRolePolicyForS3Backup", + "arn:aws:iam::aws:policy/AWSBackupServiceRolePolicyForS3Restore" + ] + + tags = var.tags } module "kms" { + count = var.enable_customer_managed_kms ? 1 : 0 + source = "geekcell/kms/aws" version = ">= 1.0.0, < 2.0.0" - alias = format("%s/backup/vault/%s", var.service, var.vault_name) + alias = "/backup/vault/${var.vault_name}" tags = var.tags } diff --git a/outputs.tf b/outputs.tf index e69de29..5a58353 100644 --- a/outputs.tf +++ b/outputs.tf @@ -0,0 +1,19 @@ +output "backup_vault_id" { + description = "The ID of the backup vault." + value = aws_backup_vault.main.id +} + +output "backup_vault_arn" { + description = "The ARN of the backup vault." + value = aws_backup_vault.main.arn +} + +output "backup_plan_id" { + description = "The ID of the backup plan." + value = aws_backup_plan.main.id +} + +output "backup_plan_arn" { + description = "The ARN of the backup plan." + value = aws_backup_plan.main.arn +} diff --git a/rules.tf b/rules.tf new file mode 100644 index 0000000..6412ac3 --- /dev/null +++ b/rules.tf @@ -0,0 +1,88 @@ +locals { + predefined_rules = [ + # At 03:00 AM UTC, daily + { + name = "daily-snapshot" + schedule = "cron(0 3 ? * * *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = true + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = null + delete_after = 35 # 5 weeks + } + + copy_action = null + }, + + # At 03:00 AM UTC, every Sunday + { + name = "weekly-snapshot" + schedule = "cron(0 3 ? * SUN *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = true + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = null + delete_after = 183 # 6 months + } + + copy_action = null + }, + + # At 03:00 AM UTC, on day 1 of the month + { + name = "monthly-snapshot" + schedule = "cron(0 3 1 * ? *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = false + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = 1 # day + delete_after = 365 # 1 year + } + + copy_action = null + }, + + # At 03:00 AM UTC, on day 1 of the month, only in January, April, July, and October + { + name = "quarterly-snapshot" + schedule = "cron(0 3 1 1,4,7,10 ? *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = false + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = 1 # day + delete_after = 730 # 2 years + } + + copy_action = null + }, + + # At 03:00 AM UTC, on day 1 of the month, only in January + { + name = "yearly-snapshot" + schedule = "cron(0 3 1 1 ? *)" + start_window = 60 + completion_window = 240 + enable_continuous_backup = false + recovery_point_tags = {} + + lifecycle = { + cold_storage_after = 1 # day + delete_after = 3650 # 10 years + } + + copy_action = null + } + ] +} diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/tests/custom_rules.tftest.hcl b/tests/custom_rules.tftest.hcl new file mode 100644 index 0000000..22d0e0f --- /dev/null +++ b/tests/custom_rules.tftest.hcl @@ -0,0 +1,66 @@ +run "create_vault_with_custom_rules" { + variables { + vault_name = "example-vault" + plan_name = "example-plan" + + custom_rules = [ + { + name = "backup-rule-1" + schedule = "cron(0 8 ? * * *)" + }, + { + name = "backup-rule-2" + schedule = "cron(0 12 1 1 ? *)" + + lifecycle = { + delete_after = 3650 # 10 years + } + } + ] + + resources = [ + "arn:aws:s3:::example-bucket-arn", + "arn:aws:elasticfilesystem:eu-central-1:*:file-system/fs-0123456789abcdef8" + ] + + tags = { + Environment = "Dev" + Foo = "Bar" + } + } + + assert { + condition = length(output.backup_vault_id) >= 1 + error_message = "Expected Backup Vault to be created." + } + + assert { + condition = length(aws_backup_vault.main.kms_key_arn) >= 1 + error_message = "Expected Backup Plan to be encrypted by default AWS KMS key." + } + + assert { + condition = length(output.backup_plan_id) >= 1 + error_message = "Expected Backup Plan to be created." + } + + assert { + condition = length(aws_backup_plan.main.rule) == 2 + error_message = "Expected Backup Plan to have 2 rules." + } + + assert { + condition = length(aws_backup_selection.main[0].resources) == 2 + error_message = "Expected backup selection to contain 2 resources." + } + + assert { + condition = length(module.iam_role) == 1 + error_message = "Expected default IAM role to be created." + } + + assert { + condition = aws_backup_selection.main[0].iam_role_arn == module.iam_role[0].arn + error_message = "Expected backup selection IAM role to be the default one." + } +} diff --git a/tests/custom_rules_override.tftest.hcl b/tests/custom_rules_override.tftest.hcl new file mode 100644 index 0000000..9d5acca --- /dev/null +++ b/tests/custom_rules_override.tftest.hcl @@ -0,0 +1,48 @@ +run "create_vault_with_custom_rules" { + variables { + vault_name = "example-vault" + plan_name = "example-plan" + + predefined_rules = ["daily-snapshot", "yearly-snapshot"] + custom_rules = [ + # Daily Snapshot but change the schedule to run at 12:00 UTC + { + name = "daily-snapshot" + schedule = "cron(0 12 ? * * *)" + }, + + # Yearly Snapshot but delete after 3 years instead + { + name = "yearly-snapshot" + lifecycle = { + delete_after = 1095 + } + } + ] + } + + assert { + condition = length(output.backup_vault_id) >= 1 + error_message = "Expected Backup Vault to be created." + } + + assert { + condition = length(output.backup_plan_id) >= 1 + error_message = "Expected Backup Plan to be created." + } + + assert { + condition = length(aws_backup_plan.main.rule) == 2 + error_message = "Expected Backup Plan to have 2 rules." + } + + assert { + condition = one([for rule in aws_backup_plan.main.rule : rule if rule.rule_name == "daily-snapshot" ]).schedule == "cron(0 12 ? * * *)" + error_message = "Expected daily-snapshot rule to have new schedule." + } + + assert { + condition = one(one([for rule in aws_backup_plan.main.rule : rule if rule.rule_name == "yearly-snapshot" ]).lifecycle).delete_after == 1095 + error_message = "Expected yearly-snapshot rule to have new lifecycle." + } +} diff --git a/tests/predefined_rules.tftest.hcl b/tests/predefined_rules.tftest.hcl new file mode 100644 index 0000000..23754a4 --- /dev/null +++ b/tests/predefined_rules.tftest.hcl @@ -0,0 +1,57 @@ +run "create_vault_with_predefined_rules" { + variables { + vault_name = "example-vault" + plan_name = "example-plan" + + # Not possible on the Testing Account + enable_vault_lock = false + enable_windows_vss_backup = true + + predefined_rules = ["daily-snapshot", "monthly-snapshot", "yearly-snapshot"] + + tags = { + Environment = "Dev" + Foo = "Bar" + } + } + + assert { + condition = length(output.backup_vault_id) >= 1 + error_message = "Expected Backup Vault to be created." + } + + assert { + condition = length(aws_backup_vault.main.kms_key_arn) >= 1 + error_message = "Expected Backup Plan to be encrypted by default AWS KMS key." + } + + assert { + condition = length(output.backup_plan_id) >= 1 + error_message = "Expected Backup Plan to be created." + } + + assert { + condition = length(aws_backup_plan.main.rule) == 3 + error_message = "Expected Backup Plan to have 3 rules." + } + + assert { + condition = one(aws_backup_plan.main.advanced_backup_setting).backup_options.WindowsVSS == "enabled" + error_message = "Expected Backup Plan to have WindowsVSS enabled." + } + + assert { + condition = length(aws_backup_vault_lock_configuration.main) == 0 + error_message = "Expected Vault to be not locked." + } + + assert { + condition = length(aws_backup_vault.main.tags) == 2 + error_message = "Expected Vault to have 2 tags." + } + + assert { + condition = length(aws_backup_plan.main.tags) == 2 + error_message = "Expected Vault to have 2 tags." + } +} diff --git a/variables.tf b/variables.tf index b8aed1e..a3e73c7 100644 --- a/variables.tf +++ b/variables.tf @@ -5,14 +5,38 @@ variable "tags" { type = map(any) } -# AWS Backup -variable "backup_name" { - description = "The display name of a backup plan." +# Backup Vault +variable "vault_name" { + description = "Name of the backup vault to create." type = string } +variable "vault_force_destroy" { + description = "Whether to allow the backup vault to be destroyed even if it contains recovery points." + default = false + type = string +} + +# Vault Lock +variable "enable_vault_lock" { + description = "Whether to enable Vault Lock for the backup vault." + default = false + type = bool +} + variable "changeable_for_days" { - description = " The number of days before the lock date. If omitted creates a vault lock in governance mode, otherwise it will create a vault lock in compliance mode." + description = < Date: Thu, 2 Nov 2023 20:37:16 +0100 Subject: [PATCH 2/4] Run fmt recursive --- .pre-commit-config.yaml | 2 ++ tests/custom_rules_override.tftest.hcl | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d96a421..6a64bc4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,6 +4,8 @@ repos: hooks: - id: terraform_docs - id: terraform_fmt + args: + - --args=-recursive - id: terraform_validate args: - --hook-config=--retry-once-with-cleanup=true diff --git a/tests/custom_rules_override.tftest.hcl b/tests/custom_rules_override.tftest.hcl index 9d5acca..0e1185d 100644 --- a/tests/custom_rules_override.tftest.hcl +++ b/tests/custom_rules_override.tftest.hcl @@ -4,7 +4,7 @@ run "create_vault_with_custom_rules" { plan_name = "example-plan" predefined_rules = ["daily-snapshot", "yearly-snapshot"] - custom_rules = [ + custom_rules = [ # Daily Snapshot but change the schedule to run at 12:00 UTC { name = "daily-snapshot" @@ -13,7 +13,7 @@ run "create_vault_with_custom_rules" { # Yearly Snapshot but delete after 3 years instead { - name = "yearly-snapshot" + name = "yearly-snapshot" lifecycle = { delete_after = 1095 } @@ -37,12 +37,12 @@ run "create_vault_with_custom_rules" { } assert { - condition = one([for rule in aws_backup_plan.main.rule : rule if rule.rule_name == "daily-snapshot" ]).schedule == "cron(0 12 ? * * *)" + condition = one([for rule in aws_backup_plan.main.rule : rule if rule.rule_name == "daily-snapshot"]).schedule == "cron(0 12 ? * * *)" error_message = "Expected daily-snapshot rule to have new schedule." } assert { - condition = one(one([for rule in aws_backup_plan.main.rule : rule if rule.rule_name == "yearly-snapshot" ]).lifecycle).delete_after == 1095 + condition = one(one([for rule in aws_backup_plan.main.rule : rule if rule.rule_name == "yearly-snapshot"]).lifecycle).delete_after == 1095 error_message = "Expected yearly-snapshot rule to have new lifecycle." } } From 4f7fe7b183c8e8a0647ac8c0a121194b89ea55ac Mon Sep 17 00:00:00 2001 From: Jerome Wolff Date: Thu, 2 Nov 2023 21:18:03 +0100 Subject: [PATCH 3/4] Change resources variable to selections object to allow more customization --- README.md | 36 ++++++++++++++++++++------ examples/minimal/main.tf | 16 +++++++++--- examples/with-predefined-rules/main.tf | 16 +++++++++--- main.tf | 18 ++++++++++--- tests/custom_rules.tftest.hcl | 23 +++++++++++----- variables.tf | 14 ++++++++-- 6 files changed, 97 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 0443174..4fa8cc4 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ great choice. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [changeable\_for\_days](#input\_changeable\_for\_days) | The number of days before the lock date. If omitted creates a vault lock in governance mode, otherwise it will create
a vault lock in compliance mode. When you apply this setting:

The vault will become immutable in 3 days after applying. You have 3 days of grace time to manage or delete the vault
lock before it becomes immutable. During this time, only those users with specific IAM permissions can make changes.

Once the vault is locked in compliance mode, it cannot be managed or deleted by anyone, even the root user or AWS.
The only way to deactivate the lock is to terminate the account, which will delete all the backups.

Since you cannot delete the Vault, it will be charged for backups until that date. Be careful! | `number` | `null` | no | -| [custom\_rules](#input\_custom\_rules) | Backup rules to add to the AWS Backup Vault. See examples for usage. |
list(object({
name = string
schedule = optional(string)

start_window = optional(number)
completion_window = optional(number)

enable_continuous_backup = optional(bool)
recovery_point_tags = optional(map(string), {})

lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))

copy_action = optional(object({
destination_vault_arn = optional(string)
lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))
}))
}))
| `[]` | no | +| [custom\_rules](#input\_custom\_rules) | Backup rules to add to the AWS Backup Vault. See examples for usage. |
list(object({
name = string
schedule = optional(string)

start_window = optional(number)
completion_window = optional(number)

enable_continuous_backup = optional(bool)
recovery_point_tags = optional(map(string), {})

lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))

copy_action = optional(object({
destination_vault_arn = optional(string)
lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))
}))
}))
| `[]` | no | | [enable\_customer\_managed\_kms](#input\_enable\_customer\_managed\_kms) | Whether to enable customer managed KMS encryption for the backup vault. | `bool` | `false` | no | | [enable\_vault\_lock](#input\_enable\_vault\_lock) | Whether to enable Vault Lock for the backup vault. | `bool` | `false` | no | | [enable\_windows\_vss\_backup](#input\_enable\_windows\_vss\_backup) | Whether to enable Windows VSS backup for the backup plan. | `bool` | `false` | no | @@ -64,8 +64,8 @@ great choice. | [min\_retention\_days](#input\_min\_retention\_days) | The minimum retention period that the vault retains its recovery points. | `number` | `7` | no | | [plan\_name](#input\_plan\_name) | The display name of the backup plan. | `string` | n/a | yes | | [predefined\_rules](#input\_predefined\_rules) | A list of predefined backup rules to add to the AWS Backup Plan. See examples for usage. | `list(string)` | `[]` | no | -| [resources](#input\_resources) | An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to assign to a backup plan. | `list(string)` | `[]` | no | | [role\_arn](#input\_role\_arn) | The ARN of the IAM role that AWS Backup uses to authenticate when restoring or backing up the target resources. If left empty, a default role will be created. | `string` | `null` | no | +| [selections](#input\_selections) | An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to assign to a backup plan. |
list(object({
name = string
role_arn = optional(string)

arns = optional(list(string))
tag = optional(object({
type = string
key = string
value = string
}))
}))
| `[]` | no | | [tags](#input\_tags) | Tags to add to the AWS Backup. | `map(any)` | `{}` | no | | [vault\_force\_destroy](#input\_vault\_force\_destroy) | Whether to allow the backup vault to be destroyed even if it contains recovery points. | `string` | `false` | no | | [vault\_name](#input\_vault\_name) | Name of the backup vault to create. | `string` | n/a | yes | @@ -98,9 +98,19 @@ great choice. module "basic-example" { source = "../../" - vault_name = "main" - plan_name = "s3" - resources = ["arn:aws:s3:::my-bucket"] + vault_name = "my-project" + plan_name = "customer-data" + + selections = [ + { + name = "s3-buckets" + arns = ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-other-bucket"] + }, + { + name = "db-snaps" + arns = ["arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance"] + } + ] } ``` @@ -109,9 +119,8 @@ module "basic-example" { module "with-rules" { source = "../../" - vault_name = "main" - plan_name = "s3" - resources = ["arn:aws:s3:::my-bucket"] + vault_name = "my-project" + plan_name = "customer-data" predefined_rules = ["daily-snapshot", "monthly-snapshot"] custom_rules = [ @@ -128,6 +137,17 @@ module "with-rules" { } } ] + + selections = [ + { + name = "s3-buckets" + arns = ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-other-bucket"] + }, + { + name = "db-snaps" + arns = ["arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance"] + } + ] } ``` diff --git a/examples/minimal/main.tf b/examples/minimal/main.tf index 48a1678..50e782f 100644 --- a/examples/minimal/main.tf +++ b/examples/minimal/main.tf @@ -1,7 +1,17 @@ module "basic-example" { source = "../../" - vault_name = "main" - plan_name = "s3" - resources = ["arn:aws:s3:::my-bucket"] + vault_name = "my-project" + plan_name = "customer-data" + + selections = [ + { + name = "s3-buckets" + arns = ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-other-bucket"] + }, + { + name = "db-snaps" + arns = ["arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance"] + } + ] } diff --git a/examples/with-predefined-rules/main.tf b/examples/with-predefined-rules/main.tf index acd70bd..aff1f9c 100644 --- a/examples/with-predefined-rules/main.tf +++ b/examples/with-predefined-rules/main.tf @@ -1,9 +1,8 @@ module "with-rules" { source = "../../" - vault_name = "main" - plan_name = "s3" - resources = ["arn:aws:s3:::my-bucket"] + vault_name = "my-project" + plan_name = "customer-data" predefined_rules = ["daily-snapshot", "monthly-snapshot"] custom_rules = [ @@ -20,4 +19,15 @@ module "with-rules" { } } ] + + selections = [ + { + name = "s3-buckets" + arns = ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-other-bucket"] + }, + { + name = "db-snaps" + arns = ["arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance"] + } + ] } diff --git a/main.tf b/main.tf index b60808d..32506b7 100644 --- a/main.tf +++ b/main.tf @@ -101,13 +101,23 @@ resource "aws_backup_plan" "main" { } resource "aws_backup_selection" "main" { - count = length(var.resources) > 0 ? 1 : 0 + for_each = { for sel in var.selections : sel.name => sel } - name = "${var.vault_name}-backup" + name = "${var.vault_name}-${each.key}" plan_id = aws_backup_plan.main.id - iam_role_arn = coalesce(var.role_arn, module.iam_role[0].arn) - resources = var.resources + iam_role_arn = coalesce(each.value.role_arn, module.iam_role[0].arn) + resources = each.value.arns + + dynamic "selection_tag" { + for_each = each.value.tag != null ? [each.value.tag] : [] + + content { + key = selection_tag.value.key + type = selection_tag.value.type + value = selection_tag.value.value + } + } } module "iam_role" { diff --git a/tests/custom_rules.tftest.hcl b/tests/custom_rules.tftest.hcl index 22d0e0f..1aa2742 100644 --- a/tests/custom_rules.tftest.hcl +++ b/tests/custom_rules.tftest.hcl @@ -18,9 +18,15 @@ run "create_vault_with_custom_rules" { } ] - resources = [ - "arn:aws:s3:::example-bucket-arn", - "arn:aws:elasticfilesystem:eu-central-1:*:file-system/fs-0123456789abcdef8" + selections = [ + { + name = "s3-buckets" + arns = ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-other-bucket"] + }, + { + name = "db-snaps" + arns = ["arn:aws:rds:eu-central-1:*:db:my-mysql-instance"] + } ] tags = { @@ -50,8 +56,13 @@ run "create_vault_with_custom_rules" { } assert { - condition = length(aws_backup_selection.main[0].resources) == 2 - error_message = "Expected backup selection to contain 2 resources." + condition = length(aws_backup_selection.main["s3-buckets"].resources) == 2 + error_message = "Expected S3 backup selection to contain 2 resources." + } + + assert { + condition = length(aws_backup_selection.main["db-snaps"].resources) == 1 + error_message = "Expected DB backup selection to contain 1 resources." } assert { @@ -60,7 +71,7 @@ run "create_vault_with_custom_rules" { } assert { - condition = aws_backup_selection.main[0].iam_role_arn == module.iam_role[0].arn + condition = aws_backup_selection.main["s3-buckets"].iam_role_arn == module.iam_role[0].arn error_message = "Expected backup selection IAM role to be the default one." } } diff --git a/variables.tf b/variables.tf index a3e73c7..4f91b0e 100644 --- a/variables.tf +++ b/variables.tf @@ -115,10 +115,20 @@ variable "enable_windows_vss_backup" { } # Backup Selection -variable "resources" { +variable "selections" { description = "An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to assign to a backup plan." default = [] - type = list(string) + type = list(object({ + name = string + role_arn = optional(string) + + arns = optional(list(string)) + tag = optional(object({ + type = string + key = string + value = string + })) + })) } variable "role_arn" { From dbb6b951cd7e9c2512dad16093cad83687bd4310 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 2 Nov 2023 20:18:47 +0000 Subject: [PATCH 4/4] terraform-docs: automated action --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4fa8cc4..805040e 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ great choice. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [changeable\_for\_days](#input\_changeable\_for\_days) | The number of days before the lock date. If omitted creates a vault lock in governance mode, otherwise it will create
a vault lock in compliance mode. When you apply this setting:

The vault will become immutable in 3 days after applying. You have 3 days of grace time to manage or delete the vault
lock before it becomes immutable. During this time, only those users with specific IAM permissions can make changes.

Once the vault is locked in compliance mode, it cannot be managed or deleted by anyone, even the root user or AWS.
The only way to deactivate the lock is to terminate the account, which will delete all the backups.

Since you cannot delete the Vault, it will be charged for backups until that date. Be careful! | `number` | `null` | no | -| [custom\_rules](#input\_custom\_rules) | Backup rules to add to the AWS Backup Vault. See examples for usage. |
list(object({
name = string
schedule = optional(string)

start_window = optional(number)
completion_window = optional(number)

enable_continuous_backup = optional(bool)
recovery_point_tags = optional(map(string), {})

lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))

copy_action = optional(object({
destination_vault_arn = optional(string)
lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))
}))
}))
| `[]` | no | +| [custom\_rules](#input\_custom\_rules) | Backup rules to add to the AWS Backup Vault. See examples for usage. |
list(object({
name = string
schedule = optional(string)

start_window = optional(number)
completion_window = optional(number)

enable_continuous_backup = optional(bool)
recovery_point_tags = optional(map(string), {})

lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))

copy_action = optional(object({
destination_vault_arn = optional(string)
lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))
}))
}))
| `[]` | no | | [enable\_customer\_managed\_kms](#input\_enable\_customer\_managed\_kms) | Whether to enable customer managed KMS encryption for the backup vault. | `bool` | `false` | no | | [enable\_vault\_lock](#input\_enable\_vault\_lock) | Whether to enable Vault Lock for the backup vault. | `bool` | `false` | no | | [enable\_windows\_vss\_backup](#input\_enable\_windows\_vss\_backup) | Whether to enable Windows VSS backup for the backup plan. | `bool` | `false` | no | @@ -65,7 +65,7 @@ great choice. | [plan\_name](#input\_plan\_name) | The display name of the backup plan. | `string` | n/a | yes | | [predefined\_rules](#input\_predefined\_rules) | A list of predefined backup rules to add to the AWS Backup Plan. See examples for usage. | `list(string)` | `[]` | no | | [role\_arn](#input\_role\_arn) | The ARN of the IAM role that AWS Backup uses to authenticate when restoring or backing up the target resources. If left empty, a default role will be created. | `string` | `null` | no | -| [selections](#input\_selections) | An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to assign to a backup plan. |
list(object({
name = string
role_arn = optional(string)

arns = optional(list(string))
tag = optional(object({
type = string
key = string
value = string
}))
}))
| `[]` | no | +| [selections](#input\_selections) | An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to assign to a backup plan. |
list(object({
name = string
role_arn = optional(string)

arns = optional(list(string))
tag = optional(object({
type = string
key = string
value = string
}))
}))
| `[]` | no | | [tags](#input\_tags) | Tags to add to the AWS Backup. | `map(any)` | `{}` | no | | [vault\_force\_destroy](#input\_vault\_force\_destroy) | Whether to allow the backup vault to be destroyed even if it contains recovery points. | `string` | `false` | no | | [vault\_name](#input\_vault\_name) | Name of the backup vault to create. | `string` | n/a | yes |