Add support for billing budgets to project factory (#2112)
* align factory variable name in project factory module * tested * align fast stage
This commit is contained in:
parent
a34d93fb43
commit
dbabfb9ae0
|
@ -79,8 +79,8 @@ terraform apply
|
|||
| name | description | type | required | default | producer |
|
||||
|---|---|:---:|:---:|:---:|:---:|
|
||||
| [billing_account](variables.tf#L19) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | <code title="object({ id = string is_org_level = optional(bool, true) })">object({…})</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [prefix](variables.tf#L39) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
| [factory_data_path](variables.tf#L32) | Path to folder containing YAML project data files. | <code>string</code> | | <code>"data/projects"</code> | |
|
||||
| [factories_config](variables.tf#L32) | Path to folder with YAML resource description data files. | <code title="object({ projects_data_path = string budgets = optional(object({ billing_account = string budgets_data_path = string notification_channels = optional(map(any), {}) })) })">object({…})</code> | ✓ | | |
|
||||
| [prefix](variables.tf#L45) | Prefix used for resources that need unique names. Use 9 characters or less. | <code>string</code> | ✓ | | <code>0-bootstrap</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ module "projects" {
|
|||
data_overrides = {
|
||||
prefix = "${var.prefix}-dev"
|
||||
}
|
||||
factory_data_path = var.factory_data_path
|
||||
factories_config = var.factories_config
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -29,11 +29,17 @@ variable "billing_account" {
|
|||
}
|
||||
}
|
||||
|
||||
variable "factory_data_path" {
|
||||
description = "Path to folder containing YAML project data files."
|
||||
type = string
|
||||
nullable = false
|
||||
default = "data/projects"
|
||||
variable "factories_config" {
|
||||
description = "Path to folder with YAML resource description data files."
|
||||
type = object({
|
||||
projects_data_path = string
|
||||
budgets = optional(object({
|
||||
billing_account = string
|
||||
budgets_data_path = string
|
||||
notification_channels = optional(map(any), {})
|
||||
}))
|
||||
})
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "prefix" {
|
||||
|
|
|
@ -230,6 +230,9 @@ module "billing-account" {
|
|||
}
|
||||
}
|
||||
}
|
||||
factories_config = {
|
||||
budgets_data_path = "data/billing-budgets"
|
||||
}
|
||||
}
|
||||
# tftest modules=1 resources=2 files=test-1 inventory=budget-monitoring-channel.yaml
|
||||
```
|
||||
|
@ -268,7 +271,7 @@ update_rules:
|
|||
| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | <code title="map(object({ members = list(string) role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [iam_bindings_additive](variables-iam.tf#L39) | Individual additive IAM bindings. Keys are arbitrary. | <code title="map(object({ member = string role = string condition = optional(object({ expression = string title = string description = optional(string) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [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. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L135) | Logging sinks to create for the organization. | <code title="map(object({ destination = string type = string bq_partitioned_table = optional(bool, false) description = optional(string) disabled = optional(bool, false) exclusions = optional(map(object({ filter = string description = optional(string) disabled = optional(bool) })), {}) filter = optional(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L135) | Logging sinks to create for the billing account. | <code title="map(object({ destination = string type = string bq_partitioned_table = optional(bool, false) description = optional(string) disabled = optional(bool, false) exclusions = optional(map(object({ filter = string description = optional(string) disabled = optional(bool) })), {}) filter = optional(string) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [projects](variables.tf#L168) | Projects associated with this billing account. | <code>list(string)</code> | | <code>[]</code> |
|
||||
|
||||
## Outputs
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
# any changes to this factory should be mirrored in the project factory
|
||||
|
||||
locals {
|
||||
_factory_data = {
|
||||
for f in fileset("${local._factory_path}", "**/*.yaml") :
|
||||
|
|
|
@ -133,7 +133,7 @@ variable "id" {
|
|||
}
|
||||
|
||||
variable "logging_sinks" {
|
||||
description = "Logging sinks to create for the organization."
|
||||
description = "Logging sinks to create for the billing account."
|
||||
type = map(object({
|
||||
destination = string
|
||||
type = string
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
data
|
|
@ -4,12 +4,10 @@ This module implements in code the end-to-end project creation process for multi
|
|||
|
||||
It supports
|
||||
|
||||
- all project-level attributes exposed by the [project module](../project/), including Shared VPC host/service configuration
|
||||
- optional service account creation in the project, including basic IAM grants
|
||||
- KMS key encrypt/decrypt permissions for service identities in the project
|
||||
- membership in VPC SC standard or bridge perimeters
|
||||
- billing budgets (TODO)
|
||||
- per-project IaC configuration (TODO)
|
||||
- multiple project creation and management exposing the full configuration options available in the [project module](../project/), including KMS key grants and VPC-SC perimeter membership
|
||||
- optional per-project [service account management](#service-accounts) including basic IAM grants
|
||||
- optional [billing budgets](#billing-budgets) factory and budget/project associations
|
||||
- optional per-project IaC configuration (TODO)
|
||||
|
||||
The factory is implemented as a thin wrapping layer, so that no "magic" or hidden side effects are implemented in code, and debugging or integration of new features are simple.
|
||||
|
||||
|
@ -18,6 +16,17 @@ The code is meant to be executed by a high level service accounts with powerful
|
|||
- Shared VPC connection if service project attachment is desired
|
||||
- project creation on the nodes (folder or org) where projects will be defined
|
||||
|
||||
<!-- BEGIN TOC -->
|
||||
- [Leveraging data defaults, merges, optionals](#leveraging-data-defaults-merges-optionals)
|
||||
- [Additional resources](#additional-resources)
|
||||
- [Service accounts](#service-accounts)
|
||||
- [Billing budgets](#billing-budgets)
|
||||
- [Example](#example)
|
||||
- [Variables](#variables)
|
||||
- [Outputs](#outputs)
|
||||
- [Tests](#tests)
|
||||
<!-- END TOC -->
|
||||
|
||||
## Leveraging data defaults, merges, optionals
|
||||
|
||||
In addition to the YAML-based project configurations, the factory accepts three additional sets of inputs via Terraform variables:
|
||||
|
@ -26,7 +35,49 @@ In addition to the YAML-based project configurations, the factory accepts three
|
|||
- the `data_overrides` variable works similarly to defaults, but the values specified here take precedence over those in YAML files
|
||||
- the `data_merges` variable allows specifying additional values for map or set based variables, which are merged with the data coming from YAML
|
||||
|
||||
Some examples on where to use each of the three sets are provided below.
|
||||
Some examples on where to use each of the three sets are [provided below](#example).
|
||||
|
||||
## Additional resources
|
||||
|
||||
### Service accounts
|
||||
|
||||
Service accounts can be managed as part of each project's YAML configuration. This allows creation of default service accounts used for GCE instances, in firewall rules, or for application-level credentials without resorting to a separate Terraform configuration.
|
||||
|
||||
Each service account is represented by one key and a set of optional key/value pairs in the `service_accounts` top-level YAML map, like in this example:
|
||||
|
||||
```yaml
|
||||
service_accounts:
|
||||
be-0: {}
|
||||
fe-1:
|
||||
display_name: GCE frontend service account.
|
||||
iam_project_roles:
|
||||
- roles/storage.objectViewer
|
||||
```
|
||||
|
||||
Both the `display_name` and `iam_project_roles` attributes are optional.
|
||||
|
||||
### Billing budgets
|
||||
|
||||
The project factory integrates the billing budgets factory exposed by the `[`billing-account`](../billing-account/) module, and adds support for easy referencing budgets in project files.
|
||||
|
||||
To enable support for billing budgets, set the billing account id, optional notification channels, and the data folder for budgets in the `factories_config.budgets` variable, then create billing budgets using YAML definitions following the format described in the `billing-account` module.
|
||||
|
||||
Once budgets are defined, they can be referenced in a project file using their file name:
|
||||
|
||||
```yaml
|
||||
billing_account: 012345-67890A-BCDEF0
|
||||
labels:
|
||||
app: app-1
|
||||
team: foo
|
||||
parent: folders/12345678
|
||||
services:
|
||||
- container.googleapis.com
|
||||
- storage.googleapis.com
|
||||
billing_budgets:
|
||||
- test-100
|
||||
```
|
||||
|
||||
The example below shows how to use the billing budgets factory.
|
||||
|
||||
## Example
|
||||
|
||||
|
@ -35,7 +86,7 @@ module "project-factory" {
|
|||
source = "./fabric/modules/project-factory"
|
||||
# use a default billing account if none is specified via yaml
|
||||
data_defaults = {
|
||||
billing_account = "012345-67890A-ABCDEF"
|
||||
billing_account = var.billing_account_id
|
||||
}
|
||||
# make sure the environment label and stackdriver service are always added
|
||||
data_merges = {
|
||||
|
@ -54,12 +105,28 @@ module "project-factory" {
|
|||
prefix = "test-pf"
|
||||
}
|
||||
# location where the yaml files are read from
|
||||
factory_data_path = "data"
|
||||
factories_config = {
|
||||
budgets = {
|
||||
billing_account = var.billing_account_id
|
||||
budgets_data_path = "data/budgets"
|
||||
notification_channels = {
|
||||
billing-default = {
|
||||
project_id = "foo-billing-audit"
|
||||
type = "email"
|
||||
labels = {
|
||||
email_address = "gcp-billing-admins@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
projects_data_path = "data/projects"
|
||||
}
|
||||
}
|
||||
# tftest modules=7 resources=33 files=prj-app-1,prj-app-2,prj-app-3 inventory=example.yaml
|
||||
# tftest modules=8 resources=35 files=prj-app-1,prj-app-2,prj-app-3,budget-test-100 inventory=example.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# project app-1
|
||||
billing_account: 012345-67890A-BCDEF0
|
||||
labels:
|
||||
app: app-1
|
||||
|
@ -78,11 +145,13 @@ service_accounts:
|
|||
- roles/monitoring.metricWriter
|
||||
app-1-fe:
|
||||
display_name: "Test app 1 frontend."
|
||||
|
||||
# tftest-file id=prj-app-1 path=data/prj-app-1.yaml
|
||||
billing_budgets:
|
||||
- test-100
|
||||
# tftest-file id=prj-app-1 path=data/projects/prj-app-1.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# project app-2
|
||||
labels:
|
||||
app: app-2
|
||||
team: foo
|
||||
|
@ -115,24 +184,45 @@ shared_vpc_service_config:
|
|||
europe-west1/prod-default-ew1:
|
||||
- group:team-1@example.com
|
||||
|
||||
# tftest-file id=prj-app-2 path=data/prj-app-2.yaml
|
||||
# tftest-file id=prj-app-2 path=data/projects/prj-app-2.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# project app-3
|
||||
parent: folders/12345678
|
||||
services:
|
||||
- run.googleapis.com
|
||||
- storage.googleapis.com
|
||||
|
||||
# tftest-file id=prj-app-3 path=data/prj-app-3.yaml
|
||||
# tftest-file id=prj-app-3 path=data/projects/prj-app-3.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
# billing budget test-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-file id=budget-test-100 path=data/budgets/test-100.yaml
|
||||
```
|
||||
<!-- BEGIN TFDOC -->
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [factory_data_path](variables.tf#L91) | Path to folder with YAML project description data files. | <code>string</code> | ✓ | |
|
||||
| [factories_config](variables.tf#L91) | Path to folder with YAML resource description data files. | <code title="object({ projects_data_path = string budgets = optional(object({ billing_account = string budgets_data_path = string notification_channels = optional(map(any), {}) })) })">object({…})</code> | ✓ | |
|
||||
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string)), {}) service_perimeter_bridges = optional(list(string), []) service_perimeter_standard = optional(string) services = optional(list(string), []) shared_vpc_service_config = optional(object({ host_project = string network_users = optional(list(string), []) service_identity_iam = optional(map(list(string)), {}) service_identity_subnet_iam = optional(map(list(string)), {}) service_iam_grants = optional(list(string), []) network_subnet_users = optional(map(list(string)), {}) }), { host_project = null }) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) })), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [data_merges](variables.tf#L49) | Optional values that will be merged with corresponding data from files. Combines with `data_defaults`, file data, and `data_overrides`. | <code title="object({ contacts = optional(map(list(string)), {}) labels = optional(map(string), {}) metric_scopes = optional(list(string), []) service_encryption_key_ids = optional(map(list(string)), {}) service_perimeter_bridges = optional(list(string), []) services = optional(list(string), []) tag_bindings = optional(map(string), {}) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) })), {}) })">object({…})</code> | | <code>{}</code> |
|
||||
| [data_overrides](variables.tf#L69) | Optional values that override corresponding data from files. Takes precedence over file data and `data_defaults`. | <code title="object({ billing_account = optional(string) contacts = optional(map(list(string))) parent = optional(string) prefix = optional(string) service_encryption_key_ids = optional(map(list(string))) service_perimeter_bridges = optional(list(string)) service_perimeter_standard = optional(string) tag_bindings = optional(map(string)) services = optional(list(string)) service_accounts = optional(map(object({ display_name = optional(string, "Terraform-managed.") iam_project_roles = optional(list(string)) }))) })">object({…})</code> | | <code>{}</code> |
|
||||
|
@ -144,7 +234,6 @@ services:
|
|||
| [projects](outputs.tf#L17) | Project module outputs. | |
|
||||
| [service_accounts](outputs.tf#L22) | Service account emails. | |
|
||||
<!-- END TFDOC -->
|
||||
|
||||
## Tests
|
||||
|
||||
These tests validate fixes to the project factory.
|
||||
|
@ -166,7 +255,9 @@ module "project-factory" {
|
|||
data_overrides = {
|
||||
prefix = "foo"
|
||||
}
|
||||
factory_data_path = "data"
|
||||
factories_config = {
|
||||
projects_data_path = "data/projects"
|
||||
}
|
||||
}
|
||||
# tftest modules=4 resources=14 files=test-0,test-1,test-2
|
||||
```
|
||||
|
@ -177,7 +268,7 @@ services:
|
|||
- iam.googleapis.com
|
||||
- contactcenteraiplatform.googleapis.com
|
||||
- container.googleapis.com
|
||||
# tftest-file id=test-0 path=data/test-0.yaml
|
||||
# tftest-file id=test-0 path=data/projects/test-0.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
|
@ -185,7 +276,7 @@ parent: folders/1234567890
|
|||
services:
|
||||
- iam.googleapis.com
|
||||
- contactcenteraiplatform.googleapis.com
|
||||
# tftest-file id=test-1 path=data/test-1.yaml
|
||||
# tftest-file id=test-1 path=data/projects/test-1.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
|
@ -193,5 +284,5 @@ parent: folders/1234567890
|
|||
services:
|
||||
- iam.googleapis.com
|
||||
- storage.googleapis.com
|
||||
# tftest-file id=test-2 path=data/test-2.yaml
|
||||
# tftest-file id=test-2 path=data/projects/test-2.yaml
|
||||
```
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
locals {
|
||||
# reimplement the billing account factory here to interpolate projects
|
||||
_budget_path = try(pathexpand(var.factories_config.budgets.budgets_data_path), null)
|
||||
_budgets = (
|
||||
{
|
||||
for f in try(fileset(local._budget_path, "**/*.yaml"), []) :
|
||||
trimsuffix(f, ".yaml") => yamldecode(file("${local._budget_path}/${f}"))
|
||||
}
|
||||
)
|
||||
budgets = {
|
||||
for k, v in local._budgets : k => merge(v, {
|
||||
amount = merge(
|
||||
{
|
||||
currency_code = null
|
||||
nanos = null
|
||||
units = null
|
||||
use_last_period = null
|
||||
},
|
||||
try(v.amount, {})
|
||||
)
|
||||
display_name = try(v.display_name, null)
|
||||
filter = try(v.filter, null) == null ? null : {
|
||||
credit_types_treatment = (
|
||||
try(v.filter.credit_types_treatment, null) == null
|
||||
? null
|
||||
: merge(
|
||||
{ exclude_all = null, include_specified = null },
|
||||
v.filter.credit_types_treatment
|
||||
)
|
||||
)
|
||||
label = try(v.filter.label, null)
|
||||
projects = concat(
|
||||
try(v.projects, []),
|
||||
[
|
||||
for p in lookup(local.project_budgets, k, []) :
|
||||
"projects/${module.projects[p].project_id}"
|
||||
]
|
||||
)
|
||||
resource_ancestors = try(v.filter.resource_ancestors, null)
|
||||
services = try(v.filter.services, null)
|
||||
subaccounts = try(v.filter.subaccounts, null)
|
||||
}
|
||||
threshold_rules = [
|
||||
for vv in try(v.threshold_rules, []) : merge({
|
||||
percent = null
|
||||
forecasted_spend = null
|
||||
}, vv)
|
||||
]
|
||||
update_rules = {
|
||||
for kk, vv in try(v.update_rules, {}) : kk => merge({
|
||||
disable_default_iam_recipients = null
|
||||
monitoring_notification_channels = null
|
||||
pubsub_topic = null
|
||||
}, vv)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -13,19 +13,27 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
locals {
|
||||
_data = (
|
||||
_project_path = try(pathexpand(var.factories_config.projects_data_path), null)
|
||||
_projects = (
|
||||
{
|
||||
for f in fileset(local._data_path, "**/*.yaml") :
|
||||
trimsuffix(f, ".yaml") => yamldecode(file("${local._data_path}/${f}"))
|
||||
for f in try(fileset(local._project_path, "**/*.yaml"), []) :
|
||||
trimsuffix(f, ".yaml") => yamldecode(file("${local._project_path}/${f}"))
|
||||
}
|
||||
)
|
||||
_data_path = var.factory_data_path == null ? null : pathexpand(
|
||||
var.factory_data_path
|
||||
)
|
||||
_project_budgets = flatten([
|
||||
for k, v in local._projects : [
|
||||
for b in try(v.billing_budgets, []) : {
|
||||
budget = b
|
||||
project = k
|
||||
}
|
||||
]
|
||||
])
|
||||
project_budgets = {
|
||||
for v in local._project_budgets : v.budget => v.project...
|
||||
}
|
||||
projects = {
|
||||
for k, v in local._data : k => merge(v, {
|
||||
for k, v in local._projects : k => merge(v, {
|
||||
billing_account = try(coalesce(
|
||||
var.data_overrides.billing_account,
|
||||
try(v.billing_account, null),
|
|
@ -76,3 +76,13 @@ module "service-accounts" {
|
|||
(module.projects[each.value.project].project_id) = each.value.iam_project_roles
|
||||
}
|
||||
}
|
||||
|
||||
module "billing-account" {
|
||||
source = "../billing-account"
|
||||
count = var.factories_config.budgets == null ? 0 : 1
|
||||
id = var.factories_config.budgets.billing_account
|
||||
budget_notification_channels = (
|
||||
var.factories_config.budgets.notification_channels
|
||||
)
|
||||
budgets = local.budgets
|
||||
}
|
||||
|
|
|
@ -88,8 +88,15 @@ variable "data_overrides" {
|
|||
default = {}
|
||||
}
|
||||
|
||||
variable "factory_data_path" {
|
||||
description = "Path to folder with YAML project description data files."
|
||||
type = string
|
||||
nullable = false
|
||||
variable "factories_config" {
|
||||
description = "Path to folder with YAML resource description data files."
|
||||
type = object({
|
||||
projects_data_path = string
|
||||
budgets = optional(object({
|
||||
billing_account = string
|
||||
budgets_data_path = string
|
||||
notification_channels = optional(map(any), {})
|
||||
}))
|
||||
})
|
||||
nullable = false
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
factory_data = {
|
||||
data_path = "../../../../tests/fast/stages/s3_project_factory/data/projects/"
|
||||
factories_config = {
|
||||
projects_data_path = "../../../../tests/fast/stages/s3_project_factory/data/projects/"
|
||||
}
|
||||
prefix = "test"
|
||||
billing_account = {
|
||||
|
|
|
@ -83,7 +83,7 @@ values:
|
|||
timeouts: null
|
||||
module.project-factory.module.projects["prj-app-2"].google_project.project[0]:
|
||||
auto_create_network: false
|
||||
billing_account: 012345-67890A-ABCDEF
|
||||
billing_account: 123456-123456-123456
|
||||
effective_labels:
|
||||
app: app-2
|
||||
environment: test
|
||||
|
@ -179,7 +179,7 @@ values:
|
|||
timeouts: null
|
||||
module.project-factory.module.projects["prj-app-3"].google_project.project[0]:
|
||||
auto_create_network: false
|
||||
billing_account: 012345-67890A-ABCDEF
|
||||
billing_account: 123456-123456-123456
|
||||
effective_labels:
|
||||
environment: test
|
||||
folder_id: '12345678'
|
||||
|
@ -241,17 +241,19 @@ values:
|
|||
timeouts: null
|
||||
|
||||
counts:
|
||||
google_billing_budget: 1
|
||||
google_compute_shared_vpc_service_project: 1
|
||||
google_compute_subnetwork_iam_member: 3
|
||||
google_essential_contacts_contact: 3
|
||||
google_kms_crypto_key_iam_member: 1
|
||||
google_monitoring_notification_channel: 1
|
||||
google_org_policy_policy: 1
|
||||
google_project: 3
|
||||
google_project_iam_member: 4
|
||||
google_project_service: 11
|
||||
google_service_account: 3
|
||||
google_storage_project_service_account: 3
|
||||
google_org_policy_policy: 1
|
||||
modules: 7
|
||||
resources: 33
|
||||
modules: 8
|
||||
resources: 35
|
||||
|
||||
outputs: {}
|
||||
|
|
Loading…
Reference in New Issue