diff --git a/fast/stages/0-org-setup/schemas/folder.schema.json b/fast/stages/0-org-setup/schemas/folder.schema.json
index 837e33538..907b2dff1 100644
--- a/fast/stages/0-org-setup/schemas/folder.schema.json
+++ b/fast/stages/0-org-setup/schemas/folder.schema.json
@@ -74,6 +74,12 @@
}
}
},
+ "billing_budgets": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
"contacts": {
"type": "object",
"additionalProperties": false,
diff --git a/fast/stages/2-networking/schemas/folder.schema.json b/fast/stages/2-networking/schemas/folder.schema.json
index 837e33538..907b2dff1 100644
--- a/fast/stages/2-networking/schemas/folder.schema.json
+++ b/fast/stages/2-networking/schemas/folder.schema.json
@@ -74,6 +74,12 @@
}
}
},
+ "billing_budgets": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
"contacts": {
"type": "object",
"additionalProperties": false,
diff --git a/fast/stages/2-project-factory/schemas/folder.schema.json b/fast/stages/2-project-factory/schemas/folder.schema.json
index 837e33538..907b2dff1 100644
--- a/fast/stages/2-project-factory/schemas/folder.schema.json
+++ b/fast/stages/2-project-factory/schemas/folder.schema.json
@@ -74,6 +74,12 @@
}
}
},
+ "billing_budgets": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
"contacts": {
"type": "object",
"additionalProperties": false,
diff --git a/fast/stages/2-security/schemas/folder.schema.json b/fast/stages/2-security/schemas/folder.schema.json
index 837e33538..907b2dff1 100644
--- a/fast/stages/2-security/schemas/folder.schema.json
+++ b/fast/stages/2-security/schemas/folder.schema.json
@@ -74,6 +74,12 @@
}
}
},
+ "billing_budgets": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
"contacts": {
"type": "object",
"additionalProperties": false,
diff --git a/modules/billing-account/README.md b/modules/billing-account/README.md
index 6b71127a8..b3950fa15 100644
--- a/modules/billing-account/README.md
+++ b/modules/billing-account/README.md
@@ -277,17 +277,17 @@ update_rules:
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [id](variables.tf#L145) | Billing account id. | string | ✓ | |
+| [id](variables.tf#L147) | Billing account id. | string | ✓ | |
| [budget_notification_channels](variables.tf#L17) | Notification channels used by budget alerts. | map(object({…})) | | {} |
-| [budgets](variables.tf#L47) | Billing budgets. Notification channels are either keys in corresponding variable, or external ids. | map(object({…})) | | {} |
-| [context](variables.tf#L122) | Context-specific interpolations. | object({…}) | | {} |
-| [factories_config](variables.tf#L136) | Path to folder containing budget alerts data files. | object({…}) | | {} |
+| [budgets](variables.tf#L47) | Billing budgets. Notification channels are either keys in corresponding variable, or external ids. | map(object({…})) | | {} |
+| [context](variables.tf#L122) | Context-specific interpolations. | object({…}) | | {} |
+| [factories_config](variables.tf#L138) | Path to folder containing budget alerts data files. | object({…}) | | {} |
| [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} |
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} |
| [iam_by_principals](variables-iam.tf#L54) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} |
-| [logging_sinks](variables.tf#L150) | Logging sinks to create for the billing account. | map(object({…})) | | {} |
-| [projects](variables.tf#L183) | Projects associated with this billing account. | list(string) | | [] |
+| [logging_sinks](variables.tf#L152) | Logging sinks to create for the billing account. | map(object({…})) | | {} |
+| [projects](variables.tf#L185) | Projects associated with this billing account. | list(string) | | [] |
## Outputs
diff --git a/modules/billing-account/budgets.tf b/modules/billing-account/budgets.tf
index 0e381f00b..aaa38ae1a 100644
--- a/modules/billing-account/budgets.tf
+++ b/modules/billing-account/budgets.tf
@@ -48,6 +48,19 @@ resource "google_monitoring_notification_channel" "default" {
}
}
+# resource "terraform_data" "defaults_preconditions" {
+# lifecycle {
+# precondition {
+# condition = local.factory_budgets == null
+# error_message = yamlencode(local.factory_budgets)
+# }
+# precondition {
+# condition = local.factory_budgets == null
+# error_message = yamlencode(local.ctx.project_sets)
+# }
+# }
+# }
+
resource "google_billing_budget" "default" {
for_each = merge(local.factory_budgets, var.budgets)
billing_account = var.id
@@ -83,14 +96,20 @@ resource "google_billing_budget" "default" {
labels = each.value.filter.label == null ? null : {
(each.value.filter.label.key) = each.value.filter.label.value
}
- projects = each.value.filter.projects == null ? [] : [
- for v in each.value.filter.projects :
- lookup(local.ctx.project_ids, v, v)
- ]
- resource_ancestors = each.value.filter.resource_ancestors == null ? [] : [
- for v in each.value.filter.resource_ancestors :
- lookup(local.ctx.folder_ids, v, v)
- ]
+ projects = concat(
+ [
+ for v in each.value.filter.projects :
+ lookup(local.ctx.project_ids, v, v)
+ ],
+ lookup(local.ctx.project_sets, "$project_sets:${each.key}", [])
+ )
+ resource_ancestors = concat(
+ [
+ for v in each.value.filter.resource_ancestors :
+ lookup(local.ctx.folder_ids, v, v)
+ ],
+ lookup(local.ctx.folder_sets, "$folder_sets:${each.key}", [])
+ )
services = each.value.filter.services
subaccounts = each.value.filter.subaccounts
dynamic "custom_period" {
diff --git a/modules/billing-account/factory.tf b/modules/billing-account/factory.tf
index 343cfdca8..2092f1605 100644
--- a/modules/billing-account/factory.tf
+++ b/modules/billing-account/factory.tf
@@ -45,8 +45,8 @@ locals {
)
)
label = try(v.filter.label, null)
- projects = try(v.filter.projects, null)
- resource_ancestors = try(v.filter.resource_ancestors, null)
+ projects = try(v.filter.projects, [])
+ resource_ancestors = try(v.filter.resource_ancestors, [])
services = try(v.filter.services, null)
subaccounts = try(v.filter.subaccounts, null)
}
diff --git a/modules/billing-account/variables.tf b/modules/billing-account/variables.tf
index f9d736edd..061767540 100644
--- a/modules/billing-account/variables.tf
+++ b/modules/billing-account/variables.tf
@@ -79,8 +79,8 @@ variable "budgets" {
}))
}))
}))
- projects = optional(list(string))
- resource_ancestors = optional(list(string))
+ projects = optional(list(string), [])
+ resource_ancestors = optional(list(string), [])
services = optional(list(string))
subaccounts = optional(list(string))
}))
@@ -124,9 +124,11 @@ variable "context" {
type = object({
custom_roles = optional(map(string), {})
folder_ids = optional(map(string), {})
+ folder_sets = optional(map(list(string)), {})
iam_principals = optional(map(string), {})
notification_channels = optional(map(string), {})
project_ids = optional(map(string), {})
+ project_sets = optional(map(list(string)), {})
storage_buckets = optional(map(string), {})
})
default = {}
diff --git a/modules/project-factory/budgets.tf b/modules/project-factory/budgets.tf
index 702c01870..c7466a2db 100644
--- a/modules/project-factory/budgets.tf
+++ b/modules/project-factory/budgets.tf
@@ -16,13 +16,40 @@
# tfdoc:file:description Billing budget factory locals.
+locals {
+ budget_folder_sets = flatten([
+ for k, v in local.folders_input : [
+ for vv in try(v.billing_budgets, []) : {
+ folder = k
+ budget = replace(vv, "$billing_budgets:", "")
+ } if trimspace(vv) != ""
+ ]
+ ])
+ budget_project_sets = flatten([
+ for k, v in local.projects_input : [
+ for vv in try(v.billing_budgets, []) : {
+ project = k
+ budget = replace(vv, "$billing_budgets:", "")
+ } if trimspace(vv) != ""
+ ]
+ ])
+}
+
module "billing-budgets" {
source = "../billing-account"
count = var.factories_config.budgets != null ? 1 : 0
id = var.factories_config.budgets.billing_account_id
context = merge(local.ctx, {
- folder_ids = local.ctx.folder_ids
+ folder_ids = local.ctx.folder_ids
+ folder_sets = {
+ for v in local.budget_folder_sets :
+ v.budget => local.folder_ids[v.folder]...
+ }
project_ids = local.ctx_project_ids
+ project_sets = {
+ for v in local.budget_project_sets :
+ v.budget => "projects/${local.outputs_projects[v.project].number}"...
+ }
})
factories_config = {
budgets_data_path = var.factories_config.budgets.data
diff --git a/modules/project-factory/main.tf b/modules/project-factory/main.tf
index 1dba2cc4a..90d85bf22 100644
--- a/modules/project-factory/main.tf
+++ b/modules/project-factory/main.tf
@@ -39,7 +39,7 @@ resource "terraform_data" "defaults_preconditions" {
}
# precondition {
# condition = local.projects_input == null
- # error_message = yamlencode(local.projects_input["iac-0"])
+ # error_message = yamlencode(local.budget_project_sets)
# }
}
}
diff --git a/modules/project-factory/schemas/folder.schema.json b/modules/project-factory/schemas/folder.schema.json
index 837e33538..907b2dff1 100644
--- a/modules/project-factory/schemas/folder.schema.json
+++ b/modules/project-factory/schemas/folder.schema.json
@@ -74,6 +74,12 @@
}
}
},
+ "billing_budgets": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
"contacts": {
"type": "object",
"additionalProperties": false,
diff --git a/tests/modules/project_factory/examples/example.yaml b/tests/modules/project_factory/examples/example.yaml
index 75e3736ee..5486cf544 100644
--- a/tests/modules/project_factory/examples/example.yaml
+++ b/tests/modules/project_factory/examples/example.yaml
@@ -96,7 +96,6 @@ values:
credit_types: null
credit_types_treatment: INCLUDE_ALL_CREDITS
custom_period: []
- projects: null
resource_ancestors:
- folders/1234567890
subaccounts: null