diff --git a/README.md b/README.md
index 84dbdca2..08907864 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ The current list of modules supports most of the core foundational and networkin
Currently available modules:
-- **foundational** - [billing budget](./modules/billing-budget), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [project](./modules/project), [projects-data-source](./modules/projects-data-source)
+- **foundational** - [billing account](./modules/billing-account), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [project](./modules/project), [projects-data-source](./modules/projects-data-source)
- **networking** - [DNS](./modules/dns), [DNS Response Policy](./modules/dns-response-policy/), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [VLAN Attachment](./modules/net-vlan-attachment/), [External Application LB](./modules/net-lb-app-ext/), [External Passthrough Network LB](./modules/net-lb-ext), [Firewall policy](./modules/net-firewall-policy), [Internal Application LB](./modules/net-lb-app-int), [Internal Passthrough Network LB](./modules/net-lb-int), [Internal Proxy Network LB](./modules/net-lb-proxy-int), [IPSec over Interconnect](./modules/net-ipsec-over-interconnect), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory), [Secure Web Proxy](./modules/net-swp)
- **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster-standard), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool), [GCVE private cloud](./modules/gcve-private-cloud)
- **data** - [BigQuery dataset](./modules/bigquery-dataset), [Bigtable instance](./modules/bigtable-instance), [Dataplex](./modules/dataplex), [Dataplex DataScan](./modules/dataplex-datascan/), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub)
diff --git a/fast/stages/0-bootstrap/billing.tf b/fast/stages/0-bootstrap/billing.tf
index 203ecbe8..82185461 100644
--- a/fast/stages/0-bootstrap/billing.tf
+++ b/fast/stages/0-bootstrap/billing.tf
@@ -74,12 +74,3 @@ resource "google_billing_account_iam_member" "billing_ext_admin" {
role = "roles/billing.admin"
member = each.key
}
-
-resource "google_billing_account_iam_member" "billing_ext_cost_manager" {
- for_each = toset(
- local.billing_mode == "resource" ? local.billing_ext_admins : []
- )
- billing_account_id = var.billing_account.id
- role = "roles/billing.costsManager"
- member = each.key
-}
diff --git a/fast/stages/0-bootstrap/organization-iam.tf b/fast/stages/0-bootstrap/organization-iam.tf
index 9cc99665..26285ac1 100644
--- a/fast/stages/0-bootstrap/organization-iam.tf
+++ b/fast/stages/0-bootstrap/organization-iam.tf
@@ -34,8 +34,7 @@ locals {
authoritative = []
additive = (
local.billing_mode != "org" ? [] : [
- "roles/billing.admin",
- "roles/billing.costsManager"
+ "roles/billing.admin"
]
)
}
@@ -66,8 +65,7 @@ locals {
"roles/orgpolicy.policyAdmin"
],
local.billing_mode != "org" ? [] : [
- "roles/billing.admin",
- "roles/billing.costsManager"
+ "roles/billing.admin"
]
)
}
@@ -111,8 +109,7 @@ locals {
"roles/orgpolicy.policyAdmin"
],
local.billing_mode != "org" ? [] : [
- "roles/billing.admin",
- "roles/billing.costsManager"
+ "roles/billing.admin"
]
)
}
@@ -129,8 +126,7 @@ locals {
"roles/orgpolicy.policyAdmin"
],
local.billing_mode != "org" ? [] : [
- "roles/billing.admin",
- "roles/billing.costsManager"
+ "roles/billing.admin"
]
)
}
@@ -148,8 +144,7 @@ locals {
# TODO: align additive roles with the README
additive = (
local.billing_mode != "org" ? [] : [
- "roles/billing.admin",
- "roles/billing.costsManager"
+ "roles/billing.admin"
]
)
}
diff --git a/modules/README.md b/modules/README.md
index 44df93be..4fbbd140 100644
--- a/modules/README.md
+++ b/modules/README.md
@@ -30,14 +30,14 @@ These modules are used in the examples included in this repository. If you are u
## Foundational modules
-- [billing budget](./billing-budget)
+- [Billing account](./billing-account)
- [Cloud Identity group](./cloud-identity-group/)
-- [folder](./folder)
-- [service accounts](./iam-service-account)
-- [logging bucket](./logging-bucket)
-- [organization](./organization)
-- [project](./project)
-- [projects-data-source](./projects-data-source)
+- [Folder](./folder)
+- [Service accounts](./iam-service-account)
+- [Logging bucket](./logging-bucket)
+- [Organization](./organization)
+- [Project](./project)
+- [Projects (data source)](./projects-data-source)
## Networking modules
diff --git a/modules/billing-account/README.md b/modules/billing-account/README.md
new file mode 100644
index 00000000..f5fd4e2f
--- /dev/null
+++ b/modules/billing-account/README.md
@@ -0,0 +1,237 @@
+# Billing Account Module
+
+This module allows managing resources and policies related to a billing account:
+
+- IAM bindings
+- log sinks
+- billing budgets and their notifications
+
+Managing billing-related resources via application default credentials [requires a billing project to be set](https://cloud.google.com/docs/authentication/troubleshoot-adc#user-creds-client-based). To configure one via Terraform you can use a snippet similar to this one:
+
+```hcl
+provider "google" {
+ billing_project = "my-project"
+ user_project_override = true
+}
+# tftest skip
+```
+
+
+- [Examples](#examples)
+ - [IAM bindings](#iam-bindings)
+ - [Log sinks](#log-sinks)
+ - [Billing budgets](#billing-budgets)
+ - [PubSub update rules](#pubsub-update-rules)
+ - [Monitoring channels](#monitoring-channels)
+- [Variables](#variables)
+- [Outputs](#outputs)
+
+
+## Examples
+
+### IAM bindings
+
+Billing account IAM bindings implement [the same interface](../__docs/20230816-iam-refactor.md) used for all other modules.
+
+```hcl
+module "billing-account" {
+ source = "./fabric/modules/billing-account"
+ id = "012345-ABCDEF-012345"
+ group_iam = {
+ "billing-admins@example.org" = ["roles/billing.admin"]
+ }
+ iam = {
+ "roles/billing.admin" = [
+ "serviceAccount:foo@myprj.iam.gserviceaccount.com"
+ ]
+ }
+ iam_bindings = {
+ conditional-admin = {
+ members = [
+ "serviceAccount:pf-dev@myprj.iam.gserviceaccount.com"
+ ]
+ role = "roles/billing.admin"
+ condition = {
+ title = "pf-dev-conditional-billing-admin"
+ expression = (
+ "resource.matchTag('123456/environment', 'development')"
+ )
+ }
+ }
+ }
+ iam_bindings_additive = {
+ sa-net-iac-user = {
+ member = "serviceAccount:net-iac-0@myprj.iam.gserviceaccount.com"
+ role = "roles/billing.user"
+ }
+ }
+}
+# tftest modules=1 resources=3 inventory=iam.yaml
+```
+
+### Log sinks
+
+Billing account log sinks use the same format used for log sinks in the resource manager modules (organization, folder, project).
+
+```hcl
+module "log-bucket-all" {
+ source = "./fabric/modules/logging-bucket"
+ parent_type = "project"
+ parent = "myprj"
+ id = "billing-account-all"
+}
+
+module "billing-account" {
+ source = "./fabric/modules/billing-account"
+ id = "012345-ABCDEF-012345"
+ logging_sinks = {
+ all = {
+ destination = module.log-bucket-all.id
+ type = "logging"
+ }
+ }
+}
+# tftest modules=2 resources=3 inventory=logging.yaml
+```
+
+### Billing budgets
+
+Billing budgets expose all the attributes of the underlying resource, and allow using external notification channels, or creating them via this same module.
+
+```hcl
+module "billing-account" {
+ source = "./fabric/modules/billing-account"
+ id = "012345-ABCDEF-012345"
+ budgets = {
+ folder-net-month-current-100 = {
+ display_name = "100 dollars in current spend"
+ amount = {
+ units = 100
+ }
+ filter = {
+ period = {
+ calendar = "MONTH"
+ }
+ resource_ancestors = ["folders/1234567890"]
+ }
+ threshold_rules = [
+ { percent = 0.5 },
+ { percent = 0.75 }
+ ]
+ }
+ }
+}
+# tftest modules=1 resources=1 inventory=budget-simple.yaml
+```
+
+#### PubSub update rules
+
+Update rules can notify pubsub topics.
+
+```hcl
+module "pubsub-billing-topic" {
+ source = "./fabric/modules/pubsub"
+ project_id = "my-prj"
+ name = "budget-default"
+}
+
+module "billing-account" {
+ source = "./fabric/modules/billing-account"
+ id = "012345-ABCDEF-012345"
+ budgets = {
+ folder-net-month-current-100 = {
+ display_name = "100 dollars in current spend"
+ amount = {
+ units = 100
+ }
+ filter = {
+ period = {
+ calendar = "MONTH"
+ }
+ resource_ancestors = ["folders/1234567890"]
+ }
+ threshold_rules = [
+ { percent = 0.5 },
+ { percent = 0.75 }
+ ]
+ update_rules = {
+ default = {
+ pubsub_topic = module.pubsub-billing-topic.id
+ }
+ }
+ }
+ }
+}
+# tftest modules=2 resources=2 inventory=budget-pubsub.yaml
+```
+
+#### Monitoring channels
+
+Monitoring channels can be referenced in update rules either by passing in an existing channel id, or by using a reference to a key in the `budget_notification_channels` variable, that allows managing ad hoc monitoring channels.
+
+
+
+```hcl
+module "billing-account" {
+ source = "./fabric/modules/billing-account"
+ id = "012345-ABCDEF-012345"
+ budget_notification_channels = {
+ billing-default = {
+ project_id = "tf-playground-simple"
+ type = "email"
+ labels = {
+ email_address = "gcp-billing-admins@example.com"
+ }
+ }
+ }
+ budgets = {
+ folder-net-month-current-100 = {
+ display_name = "100 dollars in current spend"
+ amount = {
+ units = 100
+ }
+ filter = {
+ period = {
+ calendar = "MONTH"
+ }
+ resource_ancestors = ["folders/1234567890"]
+ }
+ threshold_rules = [
+ { percent = 0.5 },
+ { percent = 0.75 }
+ ]
+ update_rules = {
+ default = {
+ disable_default_iam_recipients = true
+ monitoring_notification_channels = ["billing-default"]
+ }
+ }
+ }
+ }
+}
+# tftest modules=1 resources=2 inventory=budget-monitoring-channel.yaml
+```
+
+
+
+## Variables
+
+| name | description | type | required | default |
+|---|---|:---:|:---:|:---:|
+| [id](variables.tf#L165) | 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({…}))
| | {}
|
+| [group_iam](variables.tf#L121) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | map(list(string))
| | {}
|
+| [iam](variables.tf#L128) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string))
| | {}
|
+| [iam_bindings](variables.tf#L135) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [iam_bindings_additive](variables.tf#L150) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…}))
| | {}
|
+| [logging_sinks](variables.tf#L170) | Logging sinks to create for the organization. | map(object({…}))
| | {}
|
+| [projects](variables.tf#L203) | Projects associated with this billing account. | list(string)
| | []
|
+
+## Outputs
+
+| name | description | sensitive |
+|---|---|:---:|
+| [billing_budget_ids](outputs.tf#L17) | Billing budget ids. | |
+| [monitoring_notification_channel_ids](outputs.tf#L25) | Monitoring notification channel ids. | |
+
diff --git a/modules/billing-account/budgets.tf b/modules/billing-account/budgets.tf
new file mode 100644
index 00000000..a1aed400
--- /dev/null
+++ b/modules/billing-account/budgets.tf
@@ -0,0 +1,126 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+resource "google_monitoring_notification_channel" "default" {
+ for_each = var.budget_notification_channels
+ description = each.value.description
+ display_name = coalesce(
+ each.value.display_name, "Budget email notification ${each.key}."
+ )
+ project = each.value.project_id
+ enabled = each.value.enabled
+ force_delete = each.value.force_delete
+ type = each.value.type
+ labels = each.value.labels
+ user_labels = each.value.user_labels
+ dynamic "sensitive_labels" {
+ for_each = toset(coalesce(each.value.sensitive_labels, []))
+ content {
+ auth_token = sensitive_labels.value.auth_token
+ password = sensitive_labels.value.password
+ service_key = sensitive_labels.value.service_key
+ }
+ }
+}
+
+resource "google_billing_budget" "default" {
+ for_each = var.budgets
+ billing_account = var.id
+ display_name = each.value.display_name
+ dynamic "amount" {
+ for_each = each.value.amount.use_last_period == true ? [""] : []
+ content {
+ last_period_amount = true
+ }
+ }
+ dynamic "amount" {
+ for_each = each.value.amount.use_last_period != true ? [""] : []
+ content {
+ specified_amount {
+ currency_code = each.value.amount.currency_code
+ nanos = each.value.amount.nanos
+ units = each.value.amount.units
+ }
+ }
+ }
+ budget_filter {
+ calendar_period = try(each.value.filter.period.calendar, null)
+ credit_types_treatment = (
+ try(each.value.filter.credit_types_treatment.exclude_all, null) == true
+ ? "EXCLUDE_ALL_CREDITS"
+ : (
+ try(each.value.filter.credit_types_treatment.include_specified, null) != null
+ ? "INCLUDE_SPECIFIED_CREDITS"
+ : "INCLUDE_ALL_CREDITS"
+ )
+ )
+ labels = each.value.filter.label == null ? null : {
+ (each.value.filter.label.key) = each.value.filter.label.value
+ }
+ projects = each.value.filter.projects
+ resource_ancestors = each.value.filter.resource_ancestors
+ services = each.value.filter.services
+ subaccounts = each.value.filter.subaccounts
+ dynamic "custom_period" {
+ for_each = try(each.value.filter.period.custom, null) != null ? [""] : []
+ content {
+ start_date {
+ day = each.value.filter.period.custom.start_date.day
+ month = each.value.filter.period.custom.start_date.month
+ year = each.value.filter.period.custom.start_date.year
+ }
+ dynamic "end_date" {
+ for_each = try(each.value.filter.period.custom.end_date, null) != null ? [""] : []
+ content {
+ day = each.value.filter.period.custom.end_date.day
+ month = each.value.filter.period.custom.end_date.month
+ year = each.value.filter.period.custom.end_date.year
+ }
+ }
+ }
+ }
+ }
+ dynamic "threshold_rules" {
+ for_each = toset(each.value.threshold_rules)
+ iterator = rule
+ content {
+ threshold_percent = rule.value.percent
+ spend_basis = (
+ rule.value.forecasted_spend == true
+ ? "FORECASTED_SPEND"
+ : "CURRENT_SPEND"
+ )
+ }
+ }
+ dynamic "all_updates_rule" {
+ for_each = each.value.update_rules
+ iterator = rule
+ content {
+ pubsub_topic = rule.value.pubsub_topic
+ schema_version = "1.0"
+ disable_default_iam_recipients = rule.value.disable_default_iam_recipients
+ monitoring_notification_channels = (
+ rule.value.monitoring_notification_channels == null
+ ? null
+ : [
+ for v in rule.value.monitoring_notification_channels : try(
+ google_monitoring_notification_channel.default[v].id, v
+ )
+ ]
+ )
+ }
+ }
+}
diff --git a/modules/billing-account/iam.tf b/modules/billing-account/iam.tf
new file mode 100644
index 00000000..d8cec258
--- /dev/null
+++ b/modules/billing-account/iam.tf
@@ -0,0 +1,70 @@
+/**
+ * Copyright 2022 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description IAM bindings.
+
+locals {
+ _group_iam_roles = distinct(flatten(values(var.group_iam)))
+ _group_iam = {
+ for r in local._group_iam_roles : r => [
+ for k, v in var.group_iam : "group:${k}" if try(index(v, r), null) != null
+ ]
+ }
+ iam = {
+ for role in distinct(concat(keys(var.iam), keys(local._group_iam))) :
+ role => concat(
+ try(var.iam[role], []),
+ try(local._group_iam[role], [])
+ )
+ }
+}
+
+resource "google_billing_account_iam_binding" "authoritative" {
+ for_each = local.iam
+ billing_account_id = var.id
+ role = each.key
+ members = each.value
+}
+
+resource "google_billing_account_iam_binding" "bindings" {
+ for_each = var.iam_bindings
+ billing_account_id = var.id
+ role = each.value.role
+ members = each.value.members
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
+
+resource "google_billing_account_iam_member" "bindings" {
+ for_each = var.iam_bindings_additive
+ billing_account_id = var.id
+ role = each.value.role
+ member = each.value.member
+ dynamic "condition" {
+ for_each = each.value.condition == null ? [] : [""]
+ content {
+ expression = each.value.condition.expression
+ title = each.value.condition.title
+ description = each.value.condition.description
+ }
+ }
+}
diff --git a/modules/billing-account/logging.tf b/modules/billing-account/logging.tf
new file mode 100644
index 00000000..9c96ea93
--- /dev/null
+++ b/modules/billing-account/logging.tf
@@ -0,0 +1,92 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+# tfdoc:file:description Log sinks and supporting resources.
+
+locals {
+ sink_bindings = {
+ for type in ["bigquery", "pubsub", "logging", "storage"] :
+ type => {
+ for name, sink in var.logging_sinks :
+ name => sink
+ if sink.type == type
+ }
+ }
+}
+
+resource "google_logging_billing_account_sink" "sink" {
+ for_each = var.logging_sinks
+ name = each.key
+ description = coalesce(each.value.description, "${each.key} (Terraform-managed).")
+ billing_account = var.id
+ destination = "${each.value.type}.googleapis.com/${each.value.destination}"
+ filter = each.value.filter
+ disabled = each.value.disabled
+
+ dynamic "bigquery_options" {
+ for_each = each.value.type == "biquery" && each.value.bq_partitioned_table != false ? [""] : []
+ content {
+ use_partitioned_tables = each.value.bq_partitioned_table
+ }
+ }
+
+ dynamic "exclusions" {
+ for_each = each.value.exclusions
+ iterator = exclusion
+ content {
+ name = exclusion.key
+ filter = exclusion.value.filter
+ description = exclusion.value.description
+ disabled = exclusion.value.disabled
+ }
+ }
+}
+
+resource "google_storage_bucket_iam_member" "gcs-sinks-binding" {
+ for_each = local.sink_bindings["storage"]
+ bucket = each.value.destination
+ role = "roles/storage.objectCreator"
+ member = google_logging_billing_account_sink.sink[each.key].writer_identity
+}
+
+resource "google_bigquery_dataset_iam_member" "bq-sinks-binding" {
+ for_each = local.sink_bindings["bigquery"]
+ project = split("/", each.value.destination)[1]
+ dataset_id = split("/", each.value.destination)[3]
+ role = "roles/bigquery.dataEditor"
+ member = google_logging_billing_account_sink.sink[each.key].writer_identity
+}
+
+resource "google_pubsub_topic_iam_member" "pubsub-sinks-binding" {
+ for_each = local.sink_bindings["pubsub"]
+ project = split("/", each.value.destination)[1]
+ topic = split("/", each.value.destination)[3]
+ role = "roles/pubsub.publisher"
+ member = google_logging_billing_account_sink.sink[each.key].writer_identity
+}
+
+resource "google_project_iam_member" "bucket-sinks-binding" {
+ for_each = local.sink_bindings["logging"]
+ project = split("/", each.value.destination)[1]
+ role = "roles/logging.bucketWriter"
+ member = google_logging_billing_account_sink.sink[each.key].writer_identity
+
+ condition {
+ title = "${each.key} bucket writer"
+ description = "Grants bucketWriter to ${google_logging_billing_account_sink.sink[each.key].writer_identity} used by log sink ${each.key} on billing account ${var.id}"
+ expression = "resource.name.endsWith('${each.value.destination}')"
+ }
+}
diff --git a/modules/billing-budget/outputs.tf b/modules/billing-account/main.tf
similarity index 70%
rename from modules/billing-budget/outputs.tf
rename to modules/billing-account/main.tf
index 530f8573..f477cbb4 100644
--- a/modules/billing-budget/outputs.tf
+++ b/modules/billing-account/main.tf
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Google LLC
+ * Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,8 @@
* limitations under the License.
*/
-output "budget" {
- description = "Budget resource."
- value = google_billing_budget.budget
-}
-
-output "id" {
- description = "Fully qualified budget id."
- value = google_billing_budget.budget.id
+resource "google_billing_project_info" "default" {
+ for_each = toset(var.projects)
+ billing_account = var.id
+ project = each.key
}
diff --git a/modules/billing-account/outputs.tf b/modules/billing-account/outputs.tf
new file mode 100644
index 00000000..f8359c53
--- /dev/null
+++ b/modules/billing-account/outputs.tf
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+output "billing_budget_ids" {
+ description = "Billing budget ids."
+ value = {
+ for k, v in google_billing_budget.default :
+ k => v.id
+ }
+}
+
+output "monitoring_notification_channel_ids" {
+ description = "Monitoring notification channel ids."
+ value = {
+ for k, v in google_monitoring_notification_channel.default :
+ k => v.id
+ }
+}
diff --git a/modules/billing-account/variables.tf b/modules/billing-account/variables.tf
new file mode 100644
index 00000000..6d949ea6
--- /dev/null
+++ b/modules/billing-account/variables.tf
@@ -0,0 +1,208 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+variable "budget_notification_channels" {
+ description = "Notification channels used by budget alerts."
+ type = map(object({
+ project_id = string
+ type = string
+ description = optional(string)
+ display_name = optional(string)
+ enabled = optional(bool, true)
+ force_delete = optional(bool)
+ labels = optional(map(string))
+ sensitive_labels = optional(list(object({
+ auth_token = optional(string)
+ password = optional(string)
+ service_key = optional(string)
+ })))
+ user_labels = optional(map(string))
+ }))
+ nullable = false
+ default = {}
+ validation {
+ condition = alltrue([
+ for k, v in var.budget_notification_channels : contains([
+ "campfire", "email", "google_chat", "hipchat", "pagerduty",
+ "pubsub", "slack", "sms", "webhook_basicauth", "webhook_tokenauth"
+ ], v.type)
+ ])
+ error_message = "Invalid notification channel type."
+ }
+}
+
+variable "budgets" {
+ description = "Billing budgets. Notification channels are either keys in corresponding variable, or external ids."
+ type = map(object({
+ amount = object({
+ currency_code = optional(string)
+ nanos = optional(number)
+ units = optional(number)
+ use_last_period = optional(bool)
+ })
+ display_name = optional(string)
+ filter = optional(object({
+ credit_types_treatment = optional(object({
+ exclude_all = optional(bool)
+ include_specified = optional(list(string))
+ }))
+ label = optional(object({
+ key = string
+ value = string
+ }))
+ period = optional(object({
+ calendar = optional(string)
+ custom = optional(object({
+ start_date = object({
+ day = number
+ month = number
+ year = number
+ })
+ end_date = optional(object({
+ day = number
+ month = number
+ year = number
+ }))
+ }))
+ }))
+ projects = optional(list(string))
+ resource_ancestors = optional(list(string))
+ services = optional(list(string))
+ subaccounts = optional(list(string))
+ }))
+ threshold_rules = optional(list(object({
+ percent = number
+ forecasted_spend = optional(bool)
+ })), [])
+ update_rules = optional(map(object({
+ disable_default_iam_recipients = optional(bool)
+ monitoring_notification_channels = optional(list(string))
+ pubsub_topic = optional(string)
+ })), {})
+ }))
+ nullable = false
+ default = {}
+ validation {
+ condition = alltrue([
+ for k, v in var.budgets : v.amount != null && (
+ try(v.amount.use_last_period, null) == true ||
+ try(v.amount.units, null) != null
+ )
+ ])
+ error_message = "Each budget needs to have amount units specified, or use last period."
+ }
+ validation {
+ condition = alltrue(flatten([
+ for k, v in var.budgets : [
+ for kk, vv in v.update_rules : [
+ vv.monitoring_notification_channels != null
+ ||
+ vv.pubsub_topic != null
+ ]
+ ]
+ ]))
+ error_message = "Budget notification rules need either a pubsub topic or monitoring channels defined."
+ }
+}
+
+variable "group_iam" {
+ description = "Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable."
+ type = map(list(string))
+ default = {}
+ nullable = false
+}
+
+variable "iam" {
+ description = "IAM bindings in {ROLE => [MEMBERS]} format."
+ type = map(list(string))
+ default = {}
+ nullable = false
+}
+
+variable "iam_bindings" {
+ description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary."
+ type = map(object({
+ members = list(string)
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ }))
+ nullable = false
+ default = {}
+}
+
+variable "iam_bindings_additive" {
+ description = "Individual additive IAM bindings. Keys are arbitrary."
+ type = map(object({
+ member = string
+ role = string
+ condition = optional(object({
+ expression = string
+ title = string
+ description = optional(string)
+ }))
+ }))
+ nullable = false
+ default = {}
+}
+
+variable "id" {
+ description = "Billing account id."
+ type = string
+}
+
+variable "logging_sinks" {
+ description = "Logging sinks to create for the organization."
+ type = map(object({
+ destination = string
+ type = string
+ bq_partitioned_table = optional(bool)
+ description = optional(string)
+ disabled = optional(bool, false)
+ exclusions = optional(map(object({
+ filter = string
+ description = optional(string)
+ disabled = optional(bool)
+ })), {})
+ filter = optional(string)
+ }))
+ default = {}
+ nullable = false
+ validation {
+ condition = alltrue([
+ for k, v in var.logging_sinks :
+ contains(["bigquery", "logging", "pubsub", "storage"], v.type)
+ ])
+ error_message = "Type must be one of 'bigquery', 'logging', 'pubsub', 'storage'."
+ }
+ validation {
+ condition = alltrue([
+ for k, v in var.logging_sinks :
+ v.bq_partitioned_table != true || v.type == "bigquery"
+ ])
+ error_message = "Can only set bq_partitioned_table when type is `bigquery`."
+ }
+}
+
+variable "projects" {
+ description = "Projects associated with this billing account."
+ type = list(string)
+ nullable = false
+ default = []
+}
diff --git a/modules/billing-budget/versions.tf b/modules/billing-account/versions.tf
similarity index 99%
rename from modules/billing-budget/versions.tf
rename to modules/billing-account/versions.tf
index 3963660f..3adb6d44 100644
--- a/modules/billing-budget/versions.tf
+++ b/modules/billing-account/versions.tf
@@ -25,5 +25,3 @@ terraform {
}
}
}
-
-
diff --git a/modules/billing-budget/README.md b/modules/billing-budget/README.md
deleted file mode 100644
index 72fe574b..00000000
--- a/modules/billing-budget/README.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# Google Cloud Billing Budget Module
-
-This module allows creating a Cloud Billing budget for a set of services and projects.
-
-To create billing budgets you need one of the following IAM roles on the target billing account:
-
-* Billing Account Administrator
-* Billing Account Costs Manager
-
-## Examples
-
-### Simple email notification
-
-Send a notification to an email when a set of projects reach $100 of spend.
-
-```hcl
-module "budget" {
- source = "./fabric/modules/billing-budget"
- billing_account = var.billing_account_id
- name = "$100 budget"
- amount = 100
- thresholds = {
- current = [0.5, 0.75, 1.0]
- forecasted = [1.0]
- }
- projects = [
- "projects/123456789000",
- "projects/123456789111"
- ]
- email_recipients = {
- project_id = "my-project"
- emails = ["user@example.com"]
- }
-}
-# tftest modules=1 resources=2 inventory=email.yaml
-```
-
-### Pubsub notification
-
-Send a notification to a PubSub topic the total spend of a billing account reaches the previous month's spend.
-
-
-```hcl
-module "budget" {
- source = "./fabric/modules/billing-budget"
- billing_account = var.billing_account_id
- name = "previous period budget"
- amount = 0
- thresholds = {
- current = [1.0]
- forecasted = []
- }
- pubsub_topic = module.pubsub.id
-}
-
-module "pubsub" {
- source = "./fabric/modules/pubsub"
- project_id = var.project_id
- name = "budget-topic"
-}
-
-# tftest modules=2 resources=2 inventory=pubsub.yaml
-```
-
-
-## Variables
-
-| name | description | type | required | default |
-|---|---|:---:|:---:|:---:|
-| [billing_account](variables.tf#L23) | Billing account id. | string
| ✓ | |
-| [name](variables.tf#L50) | Budget name. | string
| ✓ | |
-| [thresholds](variables.tf#L85) | Thresholds percentages at which alerts are sent. Must be a value between 0 and 1. | object({…})
| ✓ | |
-| [amount](variables.tf#L17) | Amount in the billing account's currency for the budget. Use 0 to set budget to 100% of last period's spend. | number
| | 0
|
-| [credit_treatment](variables.tf#L28) | How credits should be treated when determining spend for threshold calculations. Only INCLUDE_ALL_CREDITS or EXCLUDE_ALL_CREDITS are supported. | string
| | "INCLUDE_ALL_CREDITS"
|
-| [email_recipients](variables.tf#L41) | Emails where budget notifications will be sent. Setting this will create a notification channel for each email in the specified project. | object({…})
| | null
|
-| [notification_channels](variables.tf#L55) | Monitoring notification channels where to send updates. | list(string)
| | null
|
-| [notify_default_recipients](variables.tf#L61) | Notify Billing Account Administrators and Billing Account Users IAM roles for the target account. | bool
| | false
|
-| [projects](variables.tf#L67) | List of projects of the form projects/{project_number}, specifying that usage from only this set of projects should be included in the budget. Set to null to include all projects linked to the billing account. | list(string)
| | null
|
-| [pubsub_topic](variables.tf#L73) | The ID of the Cloud Pub/Sub topic where budget related messages will be published. | string
| | null
|
-| [services](variables.tf#L79) | List of services of the form services/{service_id}, specifying that usage from only this set of services should be included in the budget. Set to null to include usage for all services. | list(string)
| | null
|
-
-## Outputs
-
-| name | description | sensitive |
-|---|---|:---:|
-| [budget](outputs.tf#L17) | Budget resource. | |
-| [id](outputs.tf#L22) | Fully qualified budget id. | |
-
-
diff --git a/modules/billing-budget/main.tf b/modules/billing-budget/main.tf
deleted file mode 100644
index 2c6838dc..00000000
--- a/modules/billing-budget/main.tf
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * Copyright 2022 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-locals {
- spend_basis = {
- current = "CURRENT_SPEND"
- forecasted = "FORECASTED_SPEND"
- }
- threshold_pairs = flatten([
- for type, values in var.thresholds : [
- for value in values : {
- spend_basis = local.spend_basis[type]
- threshold_percent = value
- }
- ]
- ])
-
- notification_channels = concat(
- [for channel in google_monitoring_notification_channel.email_channels : channel.id],
- coalesce(var.notification_channels, [])
- )
-}
-
-resource "google_monitoring_notification_channel" "email_channels" {
- for_each = toset(try(var.email_recipients.emails, []))
- display_name = "${var.name} budget email notification (${each.value})"
- type = "email"
- project = var.email_recipients.project_id
- labels = {
- email_address = each.value
- }
- user_labels = {}
-}
-
-
-resource "google_billing_budget" "budget" {
- billing_account = var.billing_account
- display_name = var.name
-
- budget_filter {
- projects = var.projects
- credit_types_treatment = var.credit_treatment
- services = var.services
- }
-
- dynamic "amount" {
- for_each = var.amount == 0 ? [1] : []
- content {
- last_period_amount = true
- }
- }
-
- dynamic "amount" {
- for_each = var.amount != 0 ? [1] : []
- content {
- dynamic "specified_amount" {
- for_each = var.amount != 0 ? [1] : []
- content {
- units = var.amount
- }
- }
- }
- }
-
- dynamic "threshold_rules" {
- for_each = local.threshold_pairs
- iterator = threshold
- content {
- threshold_percent = threshold.value.threshold_percent
- spend_basis = threshold.value.spend_basis
- }
- }
-
- all_updates_rule {
- monitoring_notification_channels = local.notification_channels
- pubsub_topic = var.pubsub_topic
- # disable_default_iam_recipients can only be set if
- # monitoring_notification_channels is nonempty
- disable_default_iam_recipients = try(length(var.notification_channels), 0) > 0 && !var.notify_default_recipients
- schema_version = "1.0"
- }
-}
diff --git a/modules/billing-budget/variables.tf b/modules/billing-budget/variables.tf
deleted file mode 100644
index 003f928e..00000000
--- a/modules/billing-budget/variables.tf
+++ /dev/null
@@ -1,95 +0,0 @@
-/**
- * Copyright 2022 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-variable "amount" {
- description = "Amount in the billing account's currency for the budget. Use 0 to set budget to 100% of last period's spend."
- type = number
- default = 0
-}
-
-variable "billing_account" {
- description = "Billing account id."
- type = string
-}
-
-variable "credit_treatment" {
- description = "How credits should be treated when determining spend for threshold calculations. Only INCLUDE_ALL_CREDITS or EXCLUDE_ALL_CREDITS are supported."
- type = string
- default = "INCLUDE_ALL_CREDITS"
- validation {
- condition = (
- var.credit_treatment == "INCLUDE_ALL_CREDITS" ||
- var.credit_treatment == "EXCLUDE_ALL_CREDITS"
- )
- error_message = "Argument credit_treatment must be INCLUDE_ALL_CREDITS or EXCLUDE_ALL_CREDITS."
- }
-}
-
-variable "email_recipients" {
- description = "Emails where budget notifications will be sent. Setting this will create a notification channel for each email in the specified project."
- type = object({
- project_id = string
- emails = list(string)
- })
- default = null
-}
-
-variable "name" {
- description = "Budget name."
- type = string
-}
-
-variable "notification_channels" {
- description = "Monitoring notification channels where to send updates."
- type = list(string)
- default = null
-}
-
-variable "notify_default_recipients" {
- description = "Notify Billing Account Administrators and Billing Account Users IAM roles for the target account."
- type = bool
- default = false
-}
-
-variable "projects" {
- description = "List of projects of the form projects/{project_number}, specifying that usage from only this set of projects should be included in the budget. Set to null to include all projects linked to the billing account."
- type = list(string)
- default = null
-}
-
-variable "pubsub_topic" {
- description = "The ID of the Cloud Pub/Sub topic where budget related messages will be published."
- type = string
- default = null
-}
-
-variable "services" {
- description = "List of services of the form services/{service_id}, specifying that usage from only this set of services should be included in the budget. Set to null to include usage for all services."
- type = list(string)
- default = null
-}
-
-variable "thresholds" {
- description = "Thresholds percentages at which alerts are sent. Must be a value between 0 and 1."
- type = object({
- current = list(number)
- forecasted = list(number)
- })
- validation {
- condition = length(var.thresholds.current) > 0 || length(var.thresholds.forecasted) > 0
- error_message = "Must specify at least one budget threshold."
- }
-}
diff --git a/modules/folder/README.md b/modules/folder/README.md
index 65661210..2ba7e910 100644
--- a/modules/folder/README.md
+++ b/modules/folder/README.md
@@ -272,7 +272,7 @@ module "folder" {
| name | description | resources |
|---|---|---|
-| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | google_folder_iam_binding
· google_folder_iam_member
|
+| [iam.tf](./iam.tf) | IAM bindings. | google_folder_iam_binding
· google_folder_iam_member
|
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member
· google_folder_iam_audit_config
· google_logging_folder_exclusion
· google_logging_folder_sink
· google_project_iam_member
· google_pubsub_topic_iam_member
· google_storage_bucket_iam_member
|
| [main.tf](./main.tf) | Module-level locals and resources. | google_compute_firewall_policy_association
· google_essential_contacts_contact
· google_folder
|
| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | google_org_policy_policy
|
@@ -295,7 +295,7 @@ module "folder" {
| [id](variables.tf#L83) | Folder ID in case you use folder_create=false. | string
| | null
|
| [logging_data_access](variables.tf#L89) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | map(map(list(string)))
| | {}
|
| [logging_exclusions](variables.tf#L104) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string)
| | {}
|
-| [logging_sinks](variables.tf#L111) | Logging sinks to create for the organization. | map(object({…}))
| | {}
|
+| [logging_sinks](variables.tf#L111) | Logging sinks to create for the folder. | map(object({…}))
| | {}
|
| [name](variables.tf#L141) | Folder name. | string
| | null
|
| [org_policies](variables.tf#L147) | Organization policies applied to this folder keyed by policy name. | map(object({…}))
| | {}
|
| [org_policies_data_path](variables.tf#L174) | Path containing org policies in YAML format. | string
| | null
|
diff --git a/modules/folder/iam.tf b/modules/folder/iam.tf
index 20025b28..56ed6212 100644
--- a/modules/folder/iam.tf
+++ b/modules/folder/iam.tf
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-# tfdoc:file:description IAM bindings, roles and audit logging resources.
+# tfdoc:file:description IAM bindings.
locals {
_group_iam_roles = distinct(flatten(values(var.group_iam)))
diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf
index 86efc215..1c551681 100644
--- a/modules/folder/variables.tf
+++ b/modules/folder/variables.tf
@@ -109,7 +109,7 @@ variable "logging_exclusions" {
}
variable "logging_sinks" {
- description = "Logging sinks to create for the organization."
+ description = "Logging sinks to create for the folder."
type = map(object({
bq_partitioned_table = optional(bool)
description = optional(string)
diff --git a/tests/fast/stages/s0_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml
index b73cda2b..0afb9d02 100644
--- a/tests/fast/stages/s0_bootstrap/simple.yaml
+++ b/tests/fast/stages/s0_bootstrap/simple.yaml
@@ -18,7 +18,7 @@ counts:
google_logging_organization_sink: 2
google_organization_iam_binding: 20
google_organization_iam_custom_role: 3
- google_organization_iam_member: 17
+ google_organization_iam_member: 13
google_project: 3
google_project_iam_binding: 9
google_project_iam_member: 3
diff --git a/tests/modules/billing_account/examples/budget-monitoring-channel.yaml b/tests/modules/billing_account/examples/budget-monitoring-channel.yaml
new file mode 100644
index 00000000..3909336c
--- /dev/null
+++ b/tests/modules/billing_account/examples/budget-monitoring-channel.yaml
@@ -0,0 +1,60 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.billing-account.google_billing_budget.default["folder-net-month-current-100"]:
+ all_updates_rule:
+ - disable_default_iam_recipients: true
+ pubsub_topic: null
+ schema_version: '1.0'
+ amount:
+ - last_period_amount: null
+ specified_amount:
+ - nanos: null
+ units: '100'
+ billing_account: 012345-ABCDEF-012345
+ budget_filter:
+ - calendar_period: null
+ credit_types_treatment: INCLUDE_ALL_CREDITS
+ custom_period: []
+ projects: null
+ resource_ancestors:
+ - folders/1234567890
+ display_name: 100 dollars in current spend
+ threshold_rules:
+ - spend_basis: CURRENT_SPEND
+ threshold_percent: 0.5
+ - spend_basis: CURRENT_SPEND
+ threshold_percent: 0.75
+ timeouts: null
+ module.billing-account.google_monitoring_notification_channel.default["billing-default"]:
+ description: null
+ display_name: Budget email notification billing-default.
+ enabled: true
+ force_delete: false
+ labels:
+ email_address: gcp-billing-admins@example.com
+ project: tf-playground-simple
+ sensitive_labels: []
+ timeouts: null
+ type: email
+ user_labels: null
+
+counts:
+ google_billing_budget: 1
+ google_monitoring_notification_channel: 1
+ modules: 1
+ resources: 2
+
+outputs: {}
diff --git a/tests/modules/billing_account/examples/budget-pubsub.yaml b/tests/modules/billing_account/examples/budget-pubsub.yaml
new file mode 100644
index 00000000..76c1f559
--- /dev/null
+++ b/tests/modules/billing_account/examples/budget-pubsub.yaml
@@ -0,0 +1,56 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.billing-account.google_billing_budget.default["folder-net-month-current-100"]:
+ all_updates_rule:
+ - disable_default_iam_recipients: false
+ monitoring_notification_channels: null
+ pubsub_topic: projects/my-prj/topics/budget-default
+ schema_version: '1.0'
+ amount:
+ - last_period_amount: null
+ specified_amount:
+ - nanos: null
+ units: '100'
+ billing_account: 012345-ABCDEF-012345
+ budget_filter:
+ - calendar_period: null
+ credit_types_treatment: INCLUDE_ALL_CREDITS
+ custom_period: []
+ projects: null
+ resource_ancestors:
+ - folders/1234567890
+ display_name: 100 dollars in current spend
+ threshold_rules:
+ - spend_basis: CURRENT_SPEND
+ threshold_percent: 0.5
+ - spend_basis: CURRENT_SPEND
+ threshold_percent: 0.75
+ timeouts: null
+ module.pubsub-billing-topic.google_pubsub_topic.default:
+ kms_key_name: null
+ labels: null
+ message_retention_duration: null
+ name: budget-default
+ project: my-prj
+ timeouts: null
+
+counts:
+ google_billing_budget: 1
+ google_pubsub_topic: 1
+ modules: 2
+ resources: 2
+
+outputs: {}
diff --git a/tests/modules/billing_account/examples/budget-simple.yaml b/tests/modules/billing_account/examples/budget-simple.yaml
new file mode 100644
index 00000000..8fd87441
--- /dev/null
+++ b/tests/modules/billing_account/examples/budget-simple.yaml
@@ -0,0 +1,44 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.billing-account.google_billing_budget.default["folder-net-month-current-100"]:
+ all_updates_rule: []
+ amount:
+ - last_period_amount: null
+ specified_amount:
+ - nanos: null
+ units: '100'
+ billing_account: 012345-ABCDEF-012345
+ budget_filter:
+ - calendar_period: null
+ credit_types_treatment: INCLUDE_ALL_CREDITS
+ custom_period: []
+ projects: null
+ resource_ancestors:
+ - folders/1234567890
+ display_name: 100 dollars in current spend
+ threshold_rules:
+ - spend_basis: CURRENT_SPEND
+ threshold_percent: 0.5
+ - spend_basis: CURRENT_SPEND
+ threshold_percent: 0.75
+ timeouts: null
+
+counts:
+ google_billing_budget: 1
+ modules: 1
+ resources: 1
+
+outputs: {}
diff --git a/tests/modules/billing_account/examples/iam.yaml b/tests/modules/billing_account/examples/iam.yaml
new file mode 100644
index 00000000..1cac36d5
--- /dev/null
+++ b/tests/modules/billing_account/examples/iam.yaml
@@ -0,0 +1,45 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.billing-account.google_billing_account_iam_binding.authoritative["roles/billing.admin"]:
+ billing_account_id: 012345-ABCDEF-012345
+ condition: []
+ members:
+ - group:billing-admins@example.org
+ - serviceAccount:foo@myprj.iam.gserviceaccount.com
+ role: roles/billing.admin
+ module.billing-account.google_billing_account_iam_binding.bindings["conditional-admin"]:
+ billing_account_id: 012345-ABCDEF-012345
+ condition:
+ - description: null
+ expression: resource.matchTag('123456/environment', 'development')
+ title: pf-dev-conditional-billing-admin
+ members:
+ - serviceAccount:pf-dev@myprj.iam.gserviceaccount.com
+ role: roles/billing.admin
+ module.billing-account.google_billing_account_iam_member.bindings["sa-net-iac-user"]:
+ billing_account_id: 012345-ABCDEF-012345
+ condition: []
+ member: serviceAccount:net-iac-0@myprj.iam.gserviceaccount.com
+ role: roles/billing.user
+
+counts:
+ google_billing_account_iam_binding: 2
+ google_billing_account_iam_member: 1
+ modules: 1
+ resources: 3
+
+outputs: {}
+
diff --git a/tests/modules/billing_account/examples/logging.yaml b/tests/modules/billing_account/examples/logging.yaml
new file mode 100644
index 00000000..4496f01a
--- /dev/null
+++ b/tests/modules/billing_account/examples/logging.yaml
@@ -0,0 +1,43 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ module.billing-account.google_logging_billing_account_sink.sink["all"]:
+ billing_account: 012345-ABCDEF-012345
+ description: all (Terraform-managed).
+ disabled: false
+ exclusions: []
+ filter: null
+ name: all
+ module.billing-account.google_project_iam_member.bucket-sinks-binding["all"]:
+ condition:
+ - title: all bucket writer
+ role: roles/logging.bucketWriter
+ module.log-bucket-all.google_logging_project_bucket_config.bucket[0]:
+ bucket_id: billing-account-all
+ cmek_settings: []
+ enable_analytics: false
+ location: global
+ locked: null
+ project: myprj
+ retention_days: 30
+
+counts:
+ google_logging_billing_account_sink: 1
+ google_logging_project_bucket_config: 1
+ google_project_iam_member: 1
+ modules: 2
+ resources: 3
+
+outputs: {}