Skip to content

Commit 17874b1

Browse files
authored
Aws backup (#1)
* aws-backup init * update readme * add enabled option * exclude tf validate root dir * add separate copy_action * update validate path * refactor format * refactor tags * add tag resource selection, add params * added tags to kms, iam role * change delete_after value * update tflint ruleset * update tf-lint version * fix tf-lint version * fix * notation to brackets * edit example args * rename example resources * substitute naming * rename * add copy_action_lifecycle * fix outputs error when module disabled * fix typo * fix typo, refactor labels * add context to label module
1 parent ba1ac01 commit 17874b1

26 files changed

+866
-25
lines changed

.github/workflows/pre-commit.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
env:
1111
PYTHON_VERSION: "3.10"
1212
TERRAFORM_DOCS_VERSION: "v0.16.0"
13-
TFLINT_VERSION: "v0.40.1"
13+
TFLINT_VERSION: "v0.45.0"
1414

1515
permissions:
1616
contents: read

.github/workflows/validate.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,4 @@ jobs:
4949

5050
- name: Terraform Validate
5151
id: validate
52-
run: terraform validate
52+
run: terraform -chdir=examples validate

.pre-commit-config.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ repos:
1515
- id: terraform_fmt
1616
- id: terraform_tflint
1717
- id: terraform_validate
18+
exclude: '^[^/]+$'
1819
- id: terraform_checkov
1920
- id: terraform_docs
2021
args:

.tflint.hcl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
plugin "terraform" {
22
enabled = true
3-
version = "0.1.1"
3+
version = "0.2.2"
44
source = "github.com/terraform-linters/tflint-ruleset-terraform"
55
preset = "recommended"
66
}

README.md

+49-11
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,87 @@
1-
# AWS <$module-name> Terraform module
1+
# AWS Backup Terraform module
22

33
[<img src="https://lablabs.io/static/ll-logo.png" width=350px>](https://lablabs.io/)
44

55
We help companies build, run, deploy and scale software and infrastructure by embracing the right technologies and principles. Check out our website at <https://lablabs.io/>
66

77
---
88

9-
[![Terraform validate](https://github.com/lablabs/terraform-aws-<$module-name>/actions/workflows/validate.yaml/badge.svg)](https://github.com/lablabs/terraform-aws-<$module-name>/actions/workflows/validate.yaml)
10-
[![pre-commit](https://github.com/lablabs/terraform-aws-<$module-name>/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/lablabs/terraform-aws-<$module-name>/actions/workflows/pre-commit.yml)
9+
[![Terraform validate](https://github.com/lablabs/terraform-aws-backup/actions/workflows/validate.yaml/badge.svg)](https://github.com/lablabs/terraform-aws-backup/actions/workflows/validate.yaml)
10+
[![pre-commit](https://github.com/lablabs/terraform-aws-backup/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/lablabs/terraform-aws-backup/actions/workflows/pre-commit.yml)
1111

1212
## Description
1313

14-
A Terraform module to provision <$module-name>
14+
A Terraform module to provision AWS Backup
1515

1616
## Related Projects
1717

1818
Check out other [terraform modules](https://github.com/orgs/lablabs/repositories?q=terraform-aws&type=public&language=&sort=).
1919

2020
## Examples
21-
22-
See [Basic example](examples/basic/README.md) for further information.
21+
- [Single account example](examples/single-account/README.md)
22+
- [Cross account example](examples/cross-account/README.md)
23+
- Backup vault in other account will be created
2324

2425
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
2526
## Requirements
2627

2728
| Name | Version |
2829
|------|---------|
29-
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
30+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3 |
3031
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 4.19.0 |
3132

3233
## Modules
3334

34-
No modules.
35+
| Name | Source | Version |
36+
|------|--------|---------|
37+
| <a name="module_source_kms_key"></a> [source\_kms\_key](#module\_source\_kms\_key) | cloudposse/kms-key/aws | 0.12.1 |
38+
| <a name="module_source_label"></a> [source\_label](#module\_source\_label) | cloudposse/label/null | 0.25.0 |
39+
| <a name="module_source_role"></a> [source\_role](#module\_source\_role) | cloudposse/iam-role/aws | 0.17.0 |
40+
| <a name="module_target_kms_key"></a> [target\_kms\_key](#module\_target\_kms\_key) | cloudposse/kms-key/aws | 0.12.1 |
41+
| <a name="module_target_label"></a> [target\_label](#module\_target\_label) | cloudposse/label/null | 0.25.0 |
42+
| <a name="module_target_role"></a> [target\_role](#module\_target\_role) | cloudposse/iam-role/aws | 0.17.0 |
3543

3644
## Resources
3745

38-
No resources.
46+
| Name | Type |
47+
|------|------|
48+
| [aws_backup_plan.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_plan) | resource |
49+
| [aws_backup_selection.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource |
50+
| [aws_backup_selection.tag](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource |
51+
| [aws_backup_vault.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource |
52+
| [aws_backup_vault.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource |
53+
| [aws_backup_vault_policy.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy) | resource |
54+
| [aws_backup_vault_policy.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy) | resource |
55+
| [aws_caller_identity.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
56+
| [aws_caller_identity.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
57+
| [aws_iam_policy_document.kms_source_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
58+
| [aws_iam_policy_document.kms_target_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
59+
| [aws_iam_policy_document.source_vault](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
60+
| [aws_iam_policy_document.target_vault](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
3961

4062
## Inputs
4163

42-
No inputs.
64+
| Name | Description | Type | Default | Required |
65+
|------|-------------|------|---------|:--------:|
66+
| <a name="input_attributes"></a> [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,<br>in the order they appear in the list. New attributes are appended to the<br>end of the list. The elements of the list are joined by the `delimiter`<br>and treated as a single ID element. | `list(string)` | `[]` | no |
67+
| <a name="input_backup_plans"></a> [backup\_plans](#input\_backup\_plans) | Backup plans config along with rule and resources setup | <pre>list(object({<br> name = string<br> resources = optional(list(string), [])<br> selection_tags = optional(list(object({<br> type = string<br> key = string<br> value = string<br> })), [])<br> rules = list(object({<br> name = string<br> schedule = string<br> enable_continuous_backup = optional(bool)<br> start_window = optional(string, 60)<br> completion_window = optional(number, 180)<br> lifecycle = optional(object({<br> cold_storage_after = optional(number)<br> delete_after = optional(number)<br> }))<br> copy_action_lifecycle = optional(object({<br> cold_storage_after = optional(number)<br> delete_after = optional(number)<br> }))<br> recovery_point_tags = optional(map(string))<br> }))<br> advanced_backup_setting = optional(object({<br> WindowsVSS = optional(string, null)<br> resource_type = optional(string, null)<br> }), null)<br> }))</pre> | `[]` | no |
68+
| <a name="input_context"></a> [context](#input\_context) | Single object for setting entire context at once.<br>See description of individual variables for details.<br>Leave string and numeric variables as `null` to use default value.<br>Individual variable settings (non-null) override settings in context object,<br>except for attributes, tags, and additional\_tag\_map, which are merged. | `any` | <pre>{<br> "additional_tag_map": {},<br> "attributes": [],<br> "delimiter": null,<br> "descriptor_formats": {},<br> "enabled": true,<br> "environment": null,<br> "id_length_limit": null,<br> "label_key_case": null,<br> "label_order": [],<br> "label_value_case": null,<br> "labels_as_tags": [<br> "unset"<br> ],<br> "name": null,<br> "namespace": null,<br> "regex_replace_chars": null,<br> "stage": null,<br> "tags": {},<br> "tenant": null<br>}</pre> | no |
69+
| <a name="input_enabled"></a> [enabled](#input\_enabled) | Variable indicating whether deployment is enabled | `bool` | `true` | no |
70+
| <a name="input_environment"></a> [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
71+
| <a name="input_is_cross_account_backup_enabled"></a> [is\_cross\_account\_backup\_enabled](#input\_is\_cross\_account\_backup\_enabled) | Create backup vault on different account and turn on copy action to this vault (provider.target needs to be set) | `bool` | `false` | no |
72+
| <a name="input_name"></a> [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.<br>This is the only ID element not also included as a `tag`.<br>The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no |
73+
| <a name="input_namespace"></a> [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 |
74+
| <a name="input_stage"></a> [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
75+
| <a name="input_tags"></a> [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).<br>Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no |
4376

4477
## Outputs
4578

46-
No outputs.
79+
| Name | Description |
80+
|------|-------------|
81+
| <a name="output_source_backup_vault_arn"></a> [source\_backup\_vault\_arn](#output\_source\_backup\_vault\_arn) | Backup Vault ARN of source backup vault |
82+
| <a name="output_source_backup_vault_id"></a> [source\_backup\_vault\_id](#output\_source\_backup\_vault\_id) | Backup Vault ID of source backup vault |
83+
| <a name="output_target_backup_vault_arn"></a> [target\_backup\_vault\_arn](#output\_target\_backup\_vault\_arn) | Backup Vault ARN of target backup vault |
84+
| <a name="output_target_backup_vault_id"></a> [target\_backup\_vault\_id](#output\_target\_backup\_vault\_id) | Backup Vault ID of target backup vault |
4785
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
4886

4987
## Contributing and reporting issues

aws_backup.tf

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Source vault
2+
resource "aws_backup_vault" "source" {
3+
count = var.enabled ? 1 : 0
4+
provider = aws.source
5+
name = module.source_label.id
6+
kms_key_arn = module.source_kms_key.key_arn
7+
tags = module.source_label.tags
8+
force_destroy = true
9+
}
10+
11+
resource "aws_backup_vault_policy" "source" {
12+
count = var.enabled ? 1 : 0
13+
provider = aws.source
14+
backup_vault_name = aws_backup_vault.source[0].name
15+
policy = data.aws_iam_policy_document.source_vault.json
16+
}
17+
18+
resource "aws_backup_plan" "source" {
19+
provider = aws.source
20+
for_each = { for bp in var.backup_plans : bp.name => bp if var.enabled }
21+
22+
name = each.value.name
23+
tags = module.source_label.tags
24+
25+
dynamic "rule" {
26+
for_each = each.value.rules
27+
content {
28+
rule_name = rule.value.name
29+
target_vault_name = aws_backup_vault.source[0].name
30+
schedule = rule.value.schedule
31+
start_window = try(rule.value.start_window, 60)
32+
completion_window = try(rule.value.completion_window, 180)
33+
recovery_point_tags = try(rule.value.recovery_point_tags, null)
34+
enable_continuous_backup = try(rule.value.enable_continuous_backup, null)
35+
36+
37+
dynamic "lifecycle" {
38+
for_each = try(rule.value.lifecycle, null) != null ? [true] : []
39+
content {
40+
cold_storage_after = try(rule.value.lifecycle.cold_storage_after, null)
41+
delete_after = try(rule.value.lifecycle.delete_after, null)
42+
}
43+
}
44+
45+
dynamic "copy_action" {
46+
for_each = var.is_cross_account_backup_enabled == true ? [true] : []
47+
content {
48+
dynamic "lifecycle" {
49+
for_each = try(rule.value.copy_action_lifecycle, null) != null ? [true] : []
50+
content {
51+
cold_storage_after = try(rule.value.copy_action_lifecycle.cold_storage_after, null)
52+
delete_after = try(rule.value.copy_action_lifecycle.delete_after, null)
53+
}
54+
}
55+
destination_vault_arn = aws_backup_vault.target[0].arn
56+
}
57+
58+
}
59+
}
60+
}
61+
62+
dynamic "advanced_backup_setting" {
63+
for_each = try(each.value.advanced_backup_setting, null) != null ? [true] : []
64+
65+
content {
66+
backup_options = {
67+
WindowsVSS = try(each.value.advanced_backup_setting.WindowsVSS, null)
68+
}
69+
resource_type = try(each.value.advanced_backup_setting.resource_type, null)
70+
}
71+
}
72+
}
73+
74+
# Resource selection by arn
75+
resource "aws_backup_selection" "source" {
76+
for_each = { for bp in flatten([
77+
for bp_plan in var.backup_plans : [
78+
for resource in bp_plan.resources : {
79+
backup_plan_key : bp_plan.name
80+
resource_arn : resource
81+
}
82+
]
83+
]) : md5("${bp.backup_plan_key}${bp.resource_arn}") => bp if var.enabled }
84+
85+
provider = aws.source
86+
iam_role_arn = module.source_role.arn
87+
plan_id = aws_backup_plan.source[each.value.backup_plan_key].id
88+
name = substr("${module.source_label.id}-${each.key}", 0, 50)
89+
resources = [each.value.resource_arn]
90+
}
91+
92+
# Resource selection by tag
93+
resource "aws_backup_selection" "tag" {
94+
for_each = { for bp in flatten([
95+
for bp_plan in var.backup_plans : [
96+
for selection_tag in bp_plan.selection_tags : {
97+
backup_plan_key : bp_plan.name
98+
selection_tag : selection_tag
99+
}
100+
]
101+
]) : md5("${bp.backup_plan_key}${bp.selection_tag["type"]}${bp.selection_tag["key"]}${bp.selection_tag["value"]}") => bp if var.enabled }
102+
103+
provider = aws.source
104+
iam_role_arn = module.source_role.arn
105+
plan_id = aws_backup_plan.source[each.value.backup_plan_key].id
106+
name = substr("${module.source_label.id}-${each.key}", 0, 50)
107+
resources = ["*"]
108+
selection_tag {
109+
type = each.value.selection_tag["type"]
110+
key = each.value.selection_tag["key"]
111+
value = each.value.selection_tag["value"]
112+
}
113+
}
114+
115+
# Target vault
116+
resource "aws_backup_vault" "target" {
117+
count = var.enabled && var.is_cross_account_backup_enabled ? 1 : 0
118+
provider = aws.target
119+
name = module.target_label.id
120+
kms_key_arn = module.target_kms_key.key_arn
121+
tags = module.source_label.tags
122+
force_destroy = true
123+
}
124+
125+
resource "aws_backup_vault_policy" "target" {
126+
count = var.enabled && var.is_cross_account_backup_enabled ? 1 : 0
127+
provider = aws.target
128+
backup_vault_name = aws_backup_vault.target[0].name
129+
policy = data.aws_iam_policy_document.target_vault[0].json
130+
}

context.tf

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
variable "namespace" {
2+
type = string
3+
default = null
4+
description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique"
5+
}
6+
7+
variable "environment" {
8+
type = string
9+
default = null
10+
description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'"
11+
}
12+
13+
variable "stage" {
14+
type = string
15+
default = null
16+
description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'"
17+
}
18+
19+
variable "name" {
20+
type = string
21+
default = null
22+
description = <<-EOT
23+
ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
24+
This is the only ID element not also included as a `tag`.
25+
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input.
26+
EOT
27+
}
28+
29+
variable "attributes" {
30+
type = list(string)
31+
default = []
32+
description = <<-EOT
33+
ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
34+
in the order they appear in the list. New attributes are appended to the
35+
end of the list. The elements of the list are joined by the `delimiter`
36+
and treated as a single ID element.
37+
EOT
38+
}
39+
40+
variable "tags" {
41+
type = map(string)
42+
default = {}
43+
description = <<-EOT
44+
Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
45+
Neither the tag keys nor the tag values will be modified by this module.
46+
EOT
47+
}
48+
49+
variable "context" {
50+
type = any
51+
default = {
52+
enabled = true
53+
namespace = null
54+
tenant = null
55+
environment = null
56+
stage = null
57+
name = null
58+
delimiter = null
59+
attributes = []
60+
tags = {}
61+
additional_tag_map = {}
62+
regex_replace_chars = null
63+
label_order = []
64+
id_length_limit = null
65+
label_key_case = null
66+
label_value_case = null
67+
descriptor_formats = {}
68+
# Note: we have to use [] instead of null for unset lists due to
69+
# https://github.com/hashicorp/terraform/issues/28137
70+
# which was not fixed until Terraform 1.0.0,
71+
# but we want the default to be all the labels in `label_order`
72+
# and we want users to be able to prevent all tag generation
73+
# by setting `labels_as_tags` to `[]`, so we need
74+
# a different sentinel to indicate "default"
75+
labels_as_tags = ["unset"]
76+
}
77+
description = <<-EOT
78+
Single object for setting entire context at once.
79+
See description of individual variables for details.
80+
Leave string and numeric variables as `null` to use default value.
81+
Individual variable settings (non-null) override settings in context object,
82+
except for attributes, tags, and additional_tag_map, which are merged.
83+
EOT
84+
85+
validation {
86+
condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
87+
error_message = "Allowed values: `lower`, `title`, `upper`."
88+
}
89+
90+
validation {
91+
condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
92+
error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
93+
}
94+
}

examples/basic/main.tf

-3
This file was deleted.

examples/basic/providers.tf

-3
This file was deleted.

examples/basic/README.md renamed to examples/cross-account/README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ The code in this example shows how to use the module with basic configuration an
1414

1515
| Name | Source | Version |
1616
|------|--------|---------|
17-
| <a name="module_example_module"></a> [example\_module](#module\_example\_module) | ../../ | n/a |
17+
| <a name="module_aws-backup-dev-audit"></a> [aws-backup-dev-audit](#module\_aws-backup-dev-audit) | ../../ | n/a |
1818

1919
## Resources
2020

21-
No resources.
21+
| Name | Type |
22+
|------|------|
23+
| [aws_backup_global_settings.aws_backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_global_settings) | resource |
24+
| [aws_dynamodb_table.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
2225

2326
## Inputs
2427

0 commit comments

Comments
 (0)