Skip to content

Commit

Permalink
Aws backup (#1)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Riqardos authored Feb 21, 2023
1 parent ba1ac01 commit 17874b1
Show file tree
Hide file tree
Showing 26 changed files with 866 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
env:
PYTHON_VERSION: "3.10"
TERRAFORM_DOCS_VERSION: "v0.16.0"
TFLINT_VERSION: "v0.40.1"
TFLINT_VERSION: "v0.45.0"

permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ jobs:

- name: Terraform Validate
id: validate
run: terraform validate
run: terraform -chdir=examples validate
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ repos:
- id: terraform_fmt
- id: terraform_tflint
- id: terraform_validate
exclude: '^[^/]+$'
- id: terraform_checkov
- id: terraform_docs
args:
Expand Down
2 changes: 1 addition & 1 deletion .tflint.hcl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugin "terraform" {
enabled = true
version = "0.1.1"
version = "0.2.2"
source = "github.com/terraform-linters/tflint-ruleset-terraform"
preset = "recommended"
}
Expand Down
60 changes: 49 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,87 @@
# AWS <$module-name> Terraform module
# AWS Backup Terraform module

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

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/>

---

[![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)
[![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)
[![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)
[![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)

## Description

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

## Related Projects

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

## Examples

See [Basic example](examples/basic/README.md) for further information.
- [Single account example](examples/single-account/README.md)
- [Cross account example](examples/cross-account/README.md)
- Backup vault in other account will be created

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

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 4.19.0 |

## Modules

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

## Resources

No resources.
| Name | Type |
|------|------|
| [aws_backup_plan.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_plan) | resource |
| [aws_backup_selection.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource |
| [aws_backup_selection.tag](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource |
| [aws_backup_vault.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource |
| [aws_backup_vault.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource |
| [aws_backup_vault_policy.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy) | resource |
| [aws_backup_vault_policy.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy) | resource |
| [aws_caller_identity.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_caller_identity.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.kms_source_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.kms_target_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.source_vault](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.target_vault](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

No inputs.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <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 |
| <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 |
| <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 |
| <a name="input_enabled"></a> [enabled](#input\_enabled) | Variable indicating whether deployment is enabled | `bool` | `true` | no |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |

## Outputs

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

## Contributing and reporting issues
Expand Down
130 changes: 130 additions & 0 deletions aws_backup.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Source vault
resource "aws_backup_vault" "source" {
count = var.enabled ? 1 : 0
provider = aws.source
name = module.source_label.id
kms_key_arn = module.source_kms_key.key_arn
tags = module.source_label.tags
force_destroy = true
}

resource "aws_backup_vault_policy" "source" {
count = var.enabled ? 1 : 0
provider = aws.source
backup_vault_name = aws_backup_vault.source[0].name
policy = data.aws_iam_policy_document.source_vault.json
}

resource "aws_backup_plan" "source" {
provider = aws.source
for_each = { for bp in var.backup_plans : bp.name => bp if var.enabled }

name = each.value.name
tags = module.source_label.tags

dynamic "rule" {
for_each = each.value.rules
content {
rule_name = rule.value.name
target_vault_name = aws_backup_vault.source[0].name
schedule = rule.value.schedule
start_window = try(rule.value.start_window, 60)
completion_window = try(rule.value.completion_window, 180)
recovery_point_tags = try(rule.value.recovery_point_tags, null)
enable_continuous_backup = try(rule.value.enable_continuous_backup, null)


dynamic "lifecycle" {
for_each = try(rule.value.lifecycle, null) != null ? [true] : []
content {
cold_storage_after = try(rule.value.lifecycle.cold_storage_after, null)
delete_after = try(rule.value.lifecycle.delete_after, null)
}
}

dynamic "copy_action" {
for_each = var.is_cross_account_backup_enabled == true ? [true] : []
content {
dynamic "lifecycle" {
for_each = try(rule.value.copy_action_lifecycle, null) != null ? [true] : []
content {
cold_storage_after = try(rule.value.copy_action_lifecycle.cold_storage_after, null)
delete_after = try(rule.value.copy_action_lifecycle.delete_after, null)
}
}
destination_vault_arn = aws_backup_vault.target[0].arn
}

}
}
}

dynamic "advanced_backup_setting" {
for_each = try(each.value.advanced_backup_setting, null) != null ? [true] : []

content {
backup_options = {
WindowsVSS = try(each.value.advanced_backup_setting.WindowsVSS, null)
}
resource_type = try(each.value.advanced_backup_setting.resource_type, null)
}
}
}

# Resource selection by arn
resource "aws_backup_selection" "source" {
for_each = { for bp in flatten([
for bp_plan in var.backup_plans : [
for resource in bp_plan.resources : {
backup_plan_key : bp_plan.name
resource_arn : resource
}
]
]) : md5("${bp.backup_plan_key}${bp.resource_arn}") => bp if var.enabled }

provider = aws.source
iam_role_arn = module.source_role.arn
plan_id = aws_backup_plan.source[each.value.backup_plan_key].id
name = substr("${module.source_label.id}-${each.key}", 0, 50)
resources = [each.value.resource_arn]
}

# Resource selection by tag
resource "aws_backup_selection" "tag" {
for_each = { for bp in flatten([
for bp_plan in var.backup_plans : [
for selection_tag in bp_plan.selection_tags : {
backup_plan_key : bp_plan.name
selection_tag : selection_tag
}
]
]) : md5("${bp.backup_plan_key}${bp.selection_tag["type"]}${bp.selection_tag["key"]}${bp.selection_tag["value"]}") => bp if var.enabled }

provider = aws.source
iam_role_arn = module.source_role.arn
plan_id = aws_backup_plan.source[each.value.backup_plan_key].id
name = substr("${module.source_label.id}-${each.key}", 0, 50)
resources = ["*"]
selection_tag {
type = each.value.selection_tag["type"]
key = each.value.selection_tag["key"]
value = each.value.selection_tag["value"]
}
}

# Target vault
resource "aws_backup_vault" "target" {
count = var.enabled && var.is_cross_account_backup_enabled ? 1 : 0
provider = aws.target
name = module.target_label.id
kms_key_arn = module.target_kms_key.key_arn
tags = module.source_label.tags
force_destroy = true
}

resource "aws_backup_vault_policy" "target" {
count = var.enabled && var.is_cross_account_backup_enabled ? 1 : 0
provider = aws.target
backup_vault_name = aws_backup_vault.target[0].name
policy = data.aws_iam_policy_document.target_vault[0].json
}
94 changes: 94 additions & 0 deletions context.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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 "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 "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 "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 "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`."
}
}
3 changes: 0 additions & 3 deletions examples/basic/main.tf

This file was deleted.

3 changes: 0 additions & 3 deletions examples/basic/providers.tf

This file was deleted.

7 changes: 5 additions & 2 deletions examples/basic/README.md → examples/cross-account/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ The code in this example shows how to use the module with basic configuration an

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

## Resources

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

## Inputs

Expand Down
Loading

0 comments on commit 17874b1

Please sign in to comment.