Add folder factory to project-factory module (#2152)

* WIP Folder Factory

* parent keys and general fixes

* changes

* update README and example test, add support for hierarchy projects

---------

Co-authored-by: Ludo <ludomagno@google.com>
This commit is contained in:
Julio Castillo 2024-03-14 15:03:42 +03:00 committed by GitHub
parent 93e9909166
commit 28f02688ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 233 additions and 321 deletions

View File

@ -1,33 +1,57 @@
# Project Factory
# Project and Folder Factory
This module implements in code the end-to-end project creation process for multiple projects via YAML data configurations.
This module implements end-to-end creation processes for a folder hierarchy, projects and billing budgets via YAML data configurations.
It supports
- filesystem-driven folder hierarchy exposing the full configuration options available in the [folder module](../folder/)
- 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
- cross-referencing of hierarchy folders in projects
- 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.
The factory is implemented as a thin data translation layer for the underlying modules, so that no "magic" or hidden side effects are implemented in code, and debugging or integration of new features are simple.
The code is meant to be executed by a high level service accounts with powerful permissions:
- Shared VPC connection if service project attachment is desired
- forlder admin permissions for the hierarchy
- project creation on the nodes (folder or org) where projects will be defined
- Shared VPC connection if service project attachment is desired
- billing cost manager permissions to manage budgets and monitoring permissions if notifications should also be managed here
<!-- BEGIN TOC -->
- [Leveraging data defaults, merges, optionals](#leveraging-data-defaults-merges-optionals)
- [Additional resources](#additional-resources)
- [Folder hierarchy](#folder-hierarchy)
- [Projects](#projects)
- [Leveraging project defaults, merges, optionals](#leveraging-project-defaults-merges-optionals)
- [Service accounts](#service-accounts)
- [Billing budgets](#billing-budgets)
- [Billing budgets](#billing-budgets)
- [Example](#example)
- [Variables](#variables)
- [Outputs](#outputs)
- [Tests](#tests)
<!-- END TOC -->
## Leveraging data defaults, merges, optionals
## Folder hierarchy
The hierarchy supports up to three levels of folders, which are defined via filesystem directories each including a `_config.yaml` files detailing their attributes.
The hierarchy factory is configured via the `factories_config.hierarchy` variable via one mandatory and one optional argument:
- `factories_config.hierarchy.folders_data_path` is required to enable the hierarchy factory, and must be set to the path containing the YAML definitions
- `factories_config.hierarchy.parent_ids` is an optional map where keys are arbitrary and values are set to resource node ids
Top-level folders in the filesystem hierarchy have no explicit parent, so their parent ids need to be provided in the YAML by either referencing the full id (e.g. `folders/12345678`) or by referencing a key in the `parent_ids` attribute described above. As a shortcut, a `default` key can be defined whose value is used for any top-level folder which does not directly provide a parent id.
Filesystem directories can also contain project definitions in the same YAML format described below. This approach must be used with caution and is best adopted for stable scenarios, as problems in the filesystem hierarchy definitions might result in the project files not being read and the resources being deleted by Terraform.
Refer to the [example](#example) below for actual examples of the YAML definitions.
## Projects
The project factory is configured via the `factories_config.projects_data_path` variable, and project files are also read from the hierarchy describe in the previous section when enabled. The YAML format mirrors the project module, refer to the [example](#example) below for actual examples of the YAML definitions.
### Leveraging project defaults, merges, optionals
In addition to the YAML-based project configurations, the factory accepts three additional sets of inputs via Terraform variables:
@ -37,8 +61,6 @@ In addition to the YAML-based project configurations, the factory accepts three
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.
@ -59,9 +81,9 @@ service_accounts:
Both the `display_name` and `iam_self_roles` attributes are optional.
### Billing budgets
## 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.
The billing budgets factory integrates the `[`billing-account`](../billing-account/) module functionality, 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.
@ -84,6 +106,8 @@ The example below shows how to use the billing budgets factory.
## Example
The module invocation using all optional features:
```hcl
module "project-factory" {
source = "./fabric/modules/project-factory"
@ -122,12 +146,56 @@ module "project-factory" {
}
}
}
hierarchy = {
folders_data_path = "data/hierarchy"
parent_ids = {
default = "folders/12345678"
}
}
projects_data_path = "data/projects"
}
}
# tftest modules=8 resources=37 files=prj-app-1,prj-app-2,prj-app-3,budget-test-100
# tftest modules=13 resources=48 files=prj-app-1,prj-app-2,prj-app-3,budget-test-100,h-0-0,h-1-0,h-0-1,h-1-1,h-1-1-p0 inventory=example.yaml
```
A simple hierarchy of folders:
```yaml
name: Foo (level 1)
iam:
roles/viewer:
- group:a@example.com
# tftest-file id=h-0-0 path=data/hierarchy/foo/_config.yaml
```
```yaml
name: Bar (level 1)
parent: folders/4567890
# tftest-file id=h-1-0 path=data/hierarchy/bar/_config.yaml
```
```yaml
name: Foo Baz (level 2)
# tftest-file id=h-0-1 path=data/hierarchy/foo/baz/_config.yaml
```
```yaml
name: Bar Baz (level 2)
# tftest-file id=h-1-1 path=data/hierarchy/bar/baz/_config.yaml
```
One project defined within the folder hierarchy:
```yaml
billing_account: 012345-67890A-BCDEF0
services:
- container.googleapis.com
- storage.googleapis.com
# tftest-file id=h-1-1-p0 path=data/hierarchy/bar/baz/bar-baz-0.yaml
```
More traditional project definitions via the project factory data:
```yaml
# project app-1
billing_account: 012345-67890A-BCDEF0
@ -206,6 +274,8 @@ services:
# tftest-file id=prj-app-3 path=data/projects/prj-app-3.yaml
```
And a billing budget:
```yaml
# billing budget test-100
display_name: 100 dollars in current spend
@ -226,12 +296,13 @@ update_rules:
- billing-default
# tftest-file id=budget-test-100 path=data/budgets/test-100.yaml
```
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [factories_config](variables.tf#L91) | Path to folder with YAML resource description data files. | <code title="object&#40;&#123;&#10; projects_data_path &#61; string&#10; budgets &#61; optional&#40;object&#40;&#123;&#10; billing_account &#61; string&#10; budgets_data_path &#61; string&#10; notification_channels &#61; optional&#40;map&#40;any&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [factories_config](variables.tf#L91) | Path to folder with YAML resource description data files. | <code title="object&#40;&#123;&#10; hierarchy &#61; optional&#40;object&#40;&#123;&#10; folders_data_path &#61; string&#10; parent_ids &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;&#10; projects_data_path &#61; optional&#40;string&#41;&#10; budgets &#61; optional&#40;object&#40;&#123;&#10; billing_account &#61; string&#10; budgets_data_path &#61; string&#10; notification_channels &#61; optional&#40;map&#40;any&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [data_defaults](variables.tf#L17) | Optional default values used when corresponding project data from files are missing. | <code title="object&#40;&#123;&#10; billing_account &#61; optional&#40;string&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; parent &#61; optional&#40;string&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_perimeter_standard &#61; optional&#40;string&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; shared_vpc_service_config &#61; optional&#40;object&#40;&#123;&#10; host_project &#61; string&#10; network_users &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_identity_subnet_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_iam_grants &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; network_subnet_users &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, &#123; host_project &#61; null &#125;&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_self_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</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&#40;&#123;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; labels &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; metric_scopes &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_self_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</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&#40;&#123;&#10; billing_account &#61; optional&#40;string&#41;&#10; contacts &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10; parent &#61; optional&#40;string&#41;&#10; prefix &#61; optional&#40;string&#41;&#10; service_encryption_key_ids &#61; optional&#40;map&#40;list&#40;string&#41;&#41;&#41;&#10; service_perimeter_bridges &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_perimeter_standard &#61; optional&#40;string&#41;&#10; tag_bindings &#61; optional&#40;map&#40;string&#41;&#41;&#10; services &#61; optional&#40;list&#40;string&#41;&#41;&#10; service_accounts &#61; optional&#40;map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; iam_self_roles &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
@ -240,8 +311,9 @@ update_rules:
| name | description | sensitive |
|---|---|:---:|
| [projects](outputs.tf#L17) | Project module outputs. | |
| [service_accounts](outputs.tf#L22) | Service account emails. | |
| [folders](outputs.tf#L17) | Folder ids. | |
| [projects](outputs.tf#L22) | Project module outputs. | |
| [service_accounts](outputs.tf#L27) | Service account emails. | |
<!-- END TFDOC -->
## Tests

View File

@ -0,0 +1,103 @@
/**
* Copyright 2024 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 {
_folders_path = try(
pathexpand(var.factories_config.hierarchy.folders_data_path), null
)
_folders = {
for f in local._hierarchy_files : dirname(f) => yamldecode(file(
"${coalesce(var.factories_config.hierarchy.folders_data_path, "-")}/${f}"
))
}
_hierarchy_files = try(
fileset(local._folders_path, "**/_config.yaml"),
[]
)
folders = {
for key, data in local._folders : key => merge(data, {
key = key
level = length(split("/", key))
parent_key = dirname(key)
})
}
hierarchy = merge(
try(var.factories_config.hierarchy.parent_ids, {}),
{ for k, v in module.hierarchy-folder-lvl-1 : k => v.id },
{ for k, v in module.hierarchy-folder-lvl-2 : k => v.id },
{ for k, v in module.hierarchy-folder-lvl-3 : k => v.id },
)
}
check "hierarchy-data" {
assert {
condition = (
var.factories_config.hierarchy == null ||
try(var.factories_config.hierarchy.parent_ids.default, null) != null
)
error_message = "No default set for hierarchy parent ids."
}
}
module "hierarchy-folder-lvl-1" {
source = "../folder"
for_each = { for k, v in local.folders : k => v if v.level == 1 }
parent = try(
# allow the YAML data to set the parent for this level
lookup(
var.factories_config.hierarchy.parent_ids,
each.value.parent,
# use the value as is if it's not in the parents map
each.value.parent
),
# use the default value in the initial parents map
var.factories_config.hierarchy.parent_ids.default
# fail if we don't have an explicit parent
)
name = each.value.name
iam = lookup(each.value, "iam", {})
iam_bindings = lookup(each.value, "iam_bindings", {})
iam_bindings_additive = lookup(each.value, "iam_bindings_additive", {})
iam_by_principals = lookup(each.value, "iam_by_principals", {})
org_policies = lookup(each.value, "org_policies", {})
tag_bindings = lookup(each.value, "tag_bindings", {})
}
module "hierarchy-folder-lvl-2" {
source = "../folder"
for_each = { for k, v in local.folders : k => v if v.level == 2 }
parent = module.hierarchy-folder-lvl-1[each.value.parent_key].id
name = each.value.name
iam = lookup(each.value, "iam", {})
iam_bindings = lookup(each.value, "iam_bindings", {})
iam_bindings_additive = lookup(each.value, "iam_bindings_additive", {})
iam_by_principals = lookup(each.value, "iam_by_principals", {})
org_policies = lookup(each.value, "org_policies", {})
tag_bindings = lookup(each.value, "tag_bindings", {})
}
module "hierarchy-folder-lvl-3" {
source = "../folder"
for_each = { for k, v in local.folders : k => v if v.level == 3 }
parent = module.hierarchy-folder-lvl-2[each.value.parent_key].id
name = each.value.name
iam = lookup(each.value, "iam", {})
iam_bindings = lookup(each.value, "iam_bindings", {})
iam_bindings_additive = lookup(each.value, "iam_bindings_additive", {})
iam_by_principals = lookup(each.value, "iam_by_principals", {})
org_policies = lookup(each.value, "org_policies", {})
tag_bindings = lookup(each.value, "tag_bindings", {})
}

View File

@ -14,12 +14,23 @@
* limitations under the License.
*/
locals {
_hierarchy_projects = (
{
for f in try(fileset(local._folders_path, "**/*.yaml"), []) :
basename(trimsuffix(f, ".yaml")) => merge(
{ parent = dirname(f) },
yamldecode(file("${local._folders_path}/${f}"))
)
if !endswith(f, "/_config.yaml")
}
)
_project_path = try(pathexpand(var.factories_config.projects_data_path), null)
_projects = (
_projects = merge(
{
for f in try(fileset(local._project_path, "**/*.yaml"), []) :
trimsuffix(f, ".yaml") => yamldecode(file("${local._project_path}/${f}"))
}
},
local._hierarchy_projects
)
_project_budgets = flatten([
for k, v in local._projects : [

View File

@ -15,11 +15,13 @@
*/
module "projects" {
source = "../project"
for_each = local.projects
billing_account = each.value.billing_account
name = each.key
parent = try(each.value.parent, null)
source = "../project"
for_each = local.projects
billing_account = each.value.billing_account
name = each.key
parent = try(
lookup(local.hierarchy, each.value.parent, each.value.parent), null
)
prefix = each.value.prefix
auto_create_network = try(each.value.auto_create_network, false)
compute_metadata = try(each.value.compute_metadata, {})

View File

@ -14,6 +14,11 @@
* limitations under the License.
*/
output "folders" {
description = "Folder ids."
value = local.folders
}
output "projects" {
description = "Project module outputs."
value = module.projects

View File

@ -1,5 +1,5 @@
/**
* Copyright 2023 Google LLC
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -91,10 +91,15 @@ variable "data_overrides" {
variable "factories_config" {
description = "Path to folder with YAML resource description data files."
type = object({
projects_data_path = string
hierarchy = optional(object({
folders_data_path = string
parent_ids = optional(map(string), {})
}))
projects_data_path = optional(string)
budgets = optional(object({
billing_account = string
budgets_data_path = string
billing_account = string
budgets_data_path = string
# TODO: allow defining notification channels via YAML files
notification_channels = optional(map(any), {})
}))
})

View File

@ -12,306 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
values:
module.project-factory.module.billing-account[0].google_billing_budget.default["test-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: 123456-123456-123456
budget_filter:
- calendar_period: null
credit_types_treatment: INCLUDE_ALL_CREDITS
custom_period: []
projects:
- projects/test-pf-prj-app-1
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.project-factory.module.billing-account[0].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: foo-billing-audit
sensitive_labels: []
timeouts: null
type: email
user_labels: null
module.project-factory.module.projects["prj-app-1"].data.google_storage_project_service_account.gcs_sa[0]:
project: test-pf-prj-app-1
user_project: null
module.project-factory.module.projects["prj-app-1"].google_essential_contacts_contact.contact["admin@example.com"]:
email: admin@example.com
language_tag: en
notification_category_subscriptions:
- ALL
parent: projects/test-pf-prj-app-1
timeouts: null
? module.project-factory.module.projects["prj-app-1"].google_kms_crypto_key_iam_member.service_identity_cmek["compute.projects/kms-central-prj/locations/europe-west3/keyRings/my-keyring/cryptoKeys/europe3-gce"]
: condition: []
crypto_key_id: projects/kms-central-prj/locations/europe-west3/keyRings/my-keyring/cryptoKeys/europe3-gce
role: roles/cloudkms.cryptoKeyEncrypterDecrypter
module.project-factory.module.projects["prj-app-1"].google_project.project[0]:
auto_create_network: false
billing_account: 012345-67890A-BCDEF0
effective_labels:
app: app-1
environment: test
team: foo
folder_id: '12345678'
labels:
app: app-1
environment: test
team: foo
name: test-pf-prj-app-1
org_id: null
project_id: test-pf-prj-app-1
skip_delete: false
terraform_labels:
app: app-1
environment: test
team: foo
timeouts: null
module.project-factory.module.projects["prj-app-1"].google_project_service.project_services["container.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-1
service: container.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-1"].google_project_service.project_services["stackdriver.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-1
service: stackdriver.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-1"].google_project_service.project_services["storage.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-1
service: storage.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-2"].data.google_storage_project_service_account.gcs_sa[0]:
project: test-pf-prj-app-2
user_project: null
module.project-factory.module.projects["prj-app-2"].google_compute_shared_vpc_service_project.shared_vpc_service[0]:
deletion_policy: null
host_project: foo-host
service_project: test-pf-prj-app-2
timeouts: null
? module.project-factory.module.projects["prj-app-2"].google_compute_subnetwork_iam_member.shared_vpc_host_robots["europe-west1:prod-default-ew1:cloudservices"]
: condition: []
project: foo-host
region: europe-west1
role: roles/compute.networkUser
subnetwork: prod-default-ew1
? module.project-factory.module.projects["prj-app-2"].google_compute_subnetwork_iam_member.shared_vpc_host_robots["europe-west1:prod-default-ew1:container-engine"]
: condition: []
project: foo-host
region: europe-west1
role: roles/compute.networkUser
subnetwork: prod-default-ew1
? module.project-factory.module.projects["prj-app-2"].google_compute_subnetwork_iam_member.shared_vpc_host_subnets_iam["europe-west1:prod-default-ew1:group:team-1@example.com"]
: condition: []
member: group:team-1@example.com
project: foo-host
region: europe-west1
role: roles/compute.networkUser
subnetwork: prod-default-ew1
module.project-factory.module.projects["prj-app-2"].google_essential_contacts_contact.contact["admin@example.com"]:
email: admin@example.com
language_tag: en
notification_category_subscriptions:
- ALL
parent: projects/test-pf-prj-app-2
timeouts: null
? module.project-factory.module.projects["prj-app-2"].google_org_policy_policy.default["compute.restrictSharedVpcSubnetworks"]
: dry_run_spec: []
name: projects/test-pf-prj-app-2/policies/compute.restrictSharedVpcSubnetworks
parent: projects/test-pf-prj-app-2
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: null
values:
- allowed_values:
- projects/foo-host/regions/europe-west1/subnetworks/prod-default-ew1
denied_values: null
timeouts: null
module.project-factory.module.projects["prj-app-2"].google_project.project[0]:
auto_create_network: false
billing_account: 123456-123456-123456
effective_labels:
app: app-2
environment: test
team: foo
folder_id: '12345678'
labels:
app: app-2
environment: test
team: foo
name: test-pf-prj-app-2
org_id: null
project_id: test-pf-prj-app-2
skip_delete: false
terraform_labels:
app: app-2
environment: test
team: foo
timeouts: null
? module.project-factory.module.projects["prj-app-2"].google_project_iam_member.shared_vpc_host_robots["roles/container.hostServiceAgentUser:container-engine"]
: condition: []
project: foo-host
role: roles/container.hostServiceAgentUser
? module.project-factory.module.projects["prj-app-2"].google_project_iam_member.shared_vpc_host_robots["roles/vpcaccess.user:cloudrun"]
: condition: []
project: foo-host
role: roles/vpcaccess.user
module.project-factory.module.projects["prj-app-2"].google_project_service.project_services["compute.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-2
service: compute.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-2"].google_project_service.project_services["container.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-2
service: container.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-2"].google_project_service.project_services["run.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-2
service: run.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-2"].google_project_service.project_services["stackdriver.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-2
service: stackdriver.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-2"].google_project_service.project_services["storage.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-2
service: storage.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-3"].data.google_storage_project_service_account.gcs_sa[0]:
project: test-pf-prj-app-3
user_project: null
module.project-factory.module.projects["prj-app-3"].google_essential_contacts_contact.contact["admin@example.com"]:
email: admin@example.com
language_tag: en
notification_category_subscriptions:
- ALL
parent: projects/test-pf-prj-app-3
timeouts: null
module.project-factory.module.projects["prj-app-3"].google_project.project[0]:
auto_create_network: false
billing_account: 123456-123456-123456
effective_labels:
environment: test
folder_id: '12345678'
labels:
environment: test
name: test-pf-prj-app-3
org_id: null
project_id: test-pf-prj-app-3
skip_delete: false
terraform_labels:
environment: test
timeouts: null
module.project-factory.module.projects["prj-app-3"].google_project_service.project_services["run.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-3
service: run.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-3"].google_project_service.project_services["stackdriver.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-3
service: stackdriver.googleapis.com
timeouts: null
module.project-factory.module.projects["prj-app-3"].google_project_service.project_services["storage.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: test-pf-prj-app-3
service: storage.googleapis.com
timeouts: null
? module.project-factory.module.service-accounts["prj-app-1/app-1-be"].google_project_iam_member.project-roles["my-host-project-roles/compute.networkUser"]
: condition: []
project: my-host-project
role: roles/compute.networkUser
? module.project-factory.module.service-accounts["prj-app-1/app-1-be"].google_project_iam_member.project-roles["test-pf-prj-app-1-roles/logging.logWriter"]
: condition: []
project: test-pf-prj-app-1
role: roles/logging.logWriter
? module.project-factory.module.service-accounts["prj-app-1/app-1-be"].google_project_iam_member.project-roles["test-pf-prj-app-1-roles/monitoring.metricWriter"]
: condition: []
project: test-pf-prj-app-1
role: roles/monitoring.metricWriter
module.project-factory.module.service-accounts["prj-app-1/app-1-be"].google_service_account.service_account[0]:
account_id: app-1-be
create_ignore_already_exists: null
description: null
disabled: false
display_name: Terraform-managed.
project: test-pf-prj-app-1
timeouts: null
? module.project-factory.module.service-accounts["prj-app-1/app-1-fe"].google_project_iam_member.project-roles["my-host-project-roles/compute.networkUser"]
: condition: []
project: my-host-project
role: roles/compute.networkUser
module.project-factory.module.service-accounts["prj-app-1/app-1-fe"].google_service_account.service_account[0]:
account_id: app-1-fe
create_ignore_already_exists: null
description: null
disabled: false
display_name: Test app 1 frontend.
project: test-pf-prj-app-1
timeouts: null
module.project-factory.module.service-accounts["prj-app-2/app-2-be"].google_service_account.service_account[0]:
account_id: app-2-be
create_ignore_already_exists: null
description: null
disabled: false
display_name: Terraform-managed.
project: test-pf-prj-app-2
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_essential_contacts_contact: 4
google_folder: 4
google_folder_iam_binding: 1
google_kms_crypto_key_iam_member: 1
google_monitoring_notification_channel: 1
google_org_policy_policy: 1
google_project: 3
google_project: 4
google_project_iam_member: 6
google_project_service: 11
google_project_service: 14
google_service_account: 3
google_storage_project_service_account: 3
modules: 8
resources: 37
outputs: {}
google_storage_project_service_account: 4
modules: 13
resources: 48