Skip to content

Commit 2569e75

Browse files
authored
Enable rotation config and topics in modules/secret-manager (#4141)
1 parent acff1dd commit 2569e75

4 files changed

Lines changed: 62 additions & 21 deletions

File tree

modules/secret-manager/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This module allows managing one or more secrets with versions and IAM bindings.
77
- [Regional Secrets](#regional-secrets)
88
- [IAM Bindings](#iam-bindings)
99
- [Secret Versions](#secret-versions)
10+
- [Secret Rotation](#secret-rotation)
1011
- [Context Interpolations](#context-interpolations)
1112
- [Variables](#variables)
1213
- [Outputs](#outputs)
@@ -155,6 +156,29 @@ foo-secret
155156
# tftest-file id=0 path=test-data/secret-b.txt
156157
```
157158

159+
## Secret Rotation
160+
161+
Automated rotation schedules and Pub/Sub notification topics can be configured on both global and regional secrets via `rotation_config` and `topics`. Note that the Secret Manager API requires at least one Pub/Sub topic when rotation is configured.
162+
163+
```hcl
164+
module "secret-manager" {
165+
source = "./fabric/modules/secret-manager"
166+
project_id = var.project_id
167+
secrets = {
168+
test-rotated = {
169+
rotation_config = {
170+
period = "7776000s" # 90 days
171+
next_time = "2026-12-01T00:00:00Z"
172+
}
173+
topics = [
174+
"projects/project-id/topics/secret-rotation-topic"
175+
]
176+
}
177+
}
178+
}
179+
# tftest modules=1 resources=1 skip-tofu
180+
```
181+
158182
## Context Interpolations
159183

160184
Similarly to other core modules in this repository, this module also supports context-based interpolations, which are populated via the `context` variable.

modules/secret-manager/global.tf

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,19 @@ resource "google_secret_manager_secret" "default" {
7171
}
7272
}
7373
}
74-
# dynamic "rotation" {
75-
# for_each = try(each.value.rotation_config, null) == null ? [] : [""]
76-
# content {
77-
# next_rotation_time = each.value.rotation_config.next_time
78-
# rotation_period = each.value.rotation_config.period
79-
# }
80-
# }
81-
# topics
74+
dynamic "rotation" {
75+
for_each = try(each.value.rotation_config, null) == null ? [] : [""]
76+
content {
77+
next_rotation_time = each.value.rotation_config.next_time
78+
rotation_period = each.value.rotation_config.period
79+
}
80+
}
81+
dynamic "topics" {
82+
for_each = coalesce(try(each.value.topics, null), [])
83+
content {
84+
name = topics.value
85+
}
86+
}
8287
lifecycle {
8388
ignore_changes = [
8489
rotation[0].next_rotation_time

modules/secret-manager/regional.tf

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,19 @@ resource "google_secret_manager_regional_secret" "default" {
4343
)
4444
}
4545
}
46-
# dynamic "rotation" {
47-
# for_each = try(each.value.rotation_config, null) == null ? [] : [""]
48-
# content {
49-
# next_rotation_time = each.value.rotation_config.next_time
50-
# rotation_period = each.value.rotation_config.period
51-
# }
52-
# }
53-
# topics
46+
dynamic "rotation" {
47+
for_each = try(each.value.rotation_config, null) == null ? [] : [""]
48+
content {
49+
next_rotation_time = each.value.rotation_config.next_time
50+
rotation_period = each.value.rotation_config.period
51+
}
52+
}
53+
dynamic "topics" {
54+
for_each = coalesce(try(each.value.topics, null), [])
55+
content {
56+
name = topics.value
57+
}
58+
}
5459
lifecycle {
5560
ignore_changes = [
5661
rotation[0].next_rotation_time

modules/secret-manager/variables.tf

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,20 @@ variable "secrets" {
100100
write_only_version = optional(number)
101101
}))
102102
})), {})
103-
# rotation_config = optional(object({
104-
# next_time = string
105-
# period = number
106-
# }))
107-
# topics
103+
rotation_config = optional(object({
104+
next_time = optional(string)
105+
period = string
106+
}))
107+
topics = optional(list(string))
108108
}))
109109
default = {}
110+
validation {
111+
condition = alltrue([
112+
for k, v in var.secrets :
113+
try(v.rotation_config, null) == null || (try(length(v.topics), 0) > 0)
114+
])
115+
error_message = "At least one topic must be configured when rotation_config is set."
116+
}
110117
validation {
111118
condition = alltrue([
112119
for k, v in var.secrets :

0 commit comments

Comments
 (0)