1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
# Variables ====================================================================
variable "name_prefix" {
description = "Prefix prepended to resource names created by this module"
type = string
validation {
condition = length(trimspace(var.name_prefix)) > 0
error_message = "name_prefix must not be empty."
}
}
variable "tags" {
description = "Tags applied to all resources in this module."
type = map(string)
default = {}
}
variable "vaults" {
description = <<-EOT
Map of local backup vaults to create in this spoke account. Most spoke
stacks need exactly one vault, referenced by every job via
target_vault_key. Multiple vaults are supported for cases needing
separate KMS keys or isolation within the same account.
EOT
type = map(object({
name_suffix = string
kms_key_arn = optional(string, null)
}))
default = {}
validation {
condition = alltrue([
for k, v in var.vaults : can(regex("^[a-zA-Z0-9_-]+$", v.name_suffix))
])
error_message = <<-EOT
Vault name_suffix values must contain only alphanumerics, hyphens,
and underscores (no dots, spaces, or other characters).
EOT
}
}
variable "jobs" {
description = <<-EOT
Map of backup jobs. Each entry is a flattened unit covering the plan
rule, the resource selection, and an optional cross-account copy
action in one place — the common case of one schedule mapping to one
selection. If resource_arns is non-empty, it takes precedence over
selection_tags for that job's selection (both may still be set; only
resource_arns is used if so).
EOT
type = map(object({
target_vault_key = string
schedule = string
start_window_minutes = optional(number, 60)
completion_window_minutes = optional(number, 180)
delete_after_days = optional(number, 35)
cold_storage_after_days = optional(number, null)
enable_continuous_backup = optional(bool, false)
# Selection scope — resource_arns takes precedence over selection_tags
# when both are set.
resource_arns = optional(list(string), [])
not_resource_arns = optional(list(string), [])
selection_tags = optional(map(string), {})
# IAM role AWS Backup assumes to run this job. Must already exist in
# this account — this module does not create it. See README.
iam_role_arn = string
# Optional cross-account copy to a hub vault.
copy_action = optional(object({
destination_vault_arn = string
delete_after_days = optional(number, 90)
cold_storage_after_days = optional(number, null)
}), null)
}))
default = {}
validation {
condition = alltrue([
for k, v in var.jobs : can(regex("^[a-zA-Z0-9_-]+$", k))
])
error_message = "Job keys must contain only alphanumerics, hyphens, and underscores — they are used to derive plan and selection names."
}
validation {
condition = alltrue([
for k, v in var.jobs :
length(v.resource_arns) > 0 || length(v.selection_tags) > 0
])
error_message = <<-EOT
Every job must define either resource_arns or selection_tags.
A selection with neither scopes to nothing.
EOT
}
validation {
condition = alltrue([
for k, v in var.jobs : length(trimspace(v.iam_role_arn)) > 0
])
error_message = "Every job must set iam_role_arn — this module does not create or default it."
}
validation {
condition = alltrue([
for k, v in var.jobs :
v.cold_storage_after_days == null ? true : (v.delete_after_days - v.cold_storage_after_days) >= 90
])
error_message = <<-EOT
AWS Backup requires at least 90 days between cold_storage_after_days
and delete_after_days. Either raise delete_after_days, lower
cold_storage_after_days, or leave cold_storage_after_days unset.
EOT
}
}
|