Merge branch 'master' into jd/serverless-program
This commit is contained in:
commit
7d8f569cd1
43
CHANGELOG.md
43
CHANGELOG.md
|
@ -4,7 +4,48 @@ All notable changes to this project will be documented in this file.
|
|||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
## [Unreleased]
|
||||
<!-- None < 2023-09-18 07:03:09+00:00 -->
|
||||
<!-- None < 2023-10-04 10:23:37+00:00 -->
|
||||
|
||||
### BLUEPRINTS
|
||||
|
||||
- [[#1748](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1748)] Bump golang.org/x/net from 0.7.0 to 0.17.0 in /blueprints/cloud-operations/unmanaged-instances-healthcheck/function/restarter ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 2023-10-12 05:41:41+00:00 -->
|
||||
- [[#1747](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1747)] Bump golang.org/x/net from 0.7.0 to 0.17.0 in /blueprints/cloud-operations/unmanaged-instances-healthcheck/function/healthchecker ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 2023-10-12 05:21:10+00:00 -->
|
||||
- [[#1735](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1735)] Make deletion protection consistent across all modules ([juliocc](https://github.com/juliocc)) <!-- 2023-10-05 15:31:08+00:00 -->
|
||||
|
||||
### DOCUMENTATION
|
||||
|
||||
- [[#1743](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1743)] Billing account module ([ludoo](https://github.com/ludoo)) <!-- 2023-10-15 15:02:50+00:00 -->
|
||||
|
||||
### FAST
|
||||
|
||||
- [[#1760](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1760)] Add support for psa peered domains to fast stages ([ludoo](https://github.com/ludoo)) <!-- 2023-10-16 06:57:18+00:00 -->
|
||||
- [[#1759](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1759)] Minor edits to FAST network stage READMEs ([ludoo](https://github.com/ludoo)) <!-- 2023-10-15 16:14:48+00:00 -->
|
||||
- [[#1743](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1743)] Billing account module ([ludoo](https://github.com/ludoo)) <!-- 2023-10-15 15:02:50+00:00 -->
|
||||
- [[#1735](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1735)] Make deletion protection consistent across all modules ([juliocc](https://github.com/juliocc)) <!-- 2023-10-05 15:31:08+00:00 -->
|
||||
- [[#1734](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1734)] Update to lint.sh and wording to some tf ([bluPhy](https://github.com/bluPhy)) <!-- 2023-10-05 06:32:08+00:00 -->
|
||||
- [[#1733](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1733)] Fix typo in FAST stage 2 README ([bluPhy](https://github.com/bluPhy)) <!-- 2023-10-04 22:22:44+00:00 -->
|
||||
|
||||
### MODULES
|
||||
|
||||
- [[#1762](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1762)] Make subnets depend on proxy only subnets ([juliocc](https://github.com/juliocc)) <!-- 2023-10-16 11:39:52+00:00 -->
|
||||
- [[#1757](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1757)] Add autoclass to GCS ([jeroenmonteban](https://github.com/jeroenmonteban)) <!-- 2023-10-16 07:45:10+00:00 -->
|
||||
- [[#1756](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1756)] Exposed stack_type variable in compute_vm module ([luigi-bitonti](https://github.com/luigi-bitonti)) <!-- 2023-10-16 06:28:57+00:00 -->
|
||||
- [[#1743](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1743)] Billing account module ([ludoo](https://github.com/ludoo)) <!-- 2023-10-15 15:02:50+00:00 -->
|
||||
- [[#1752](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1752)] Add outputs to BigQuery dataset module ([devuonocar](https://github.com/devuonocar)) <!-- 2023-10-13 15:02:48+00:00 -->
|
||||
- [[#1754](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1754)] Fix typo in GKE nodepool taints ([ludoo](https://github.com/ludoo)) <!-- 2023-10-12 12:04:15+00:00 -->
|
||||
- [[#1746](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1746)] Module autopilot bug fixes ([luigi-bitonti](https://github.com/luigi-bitonti)) <!-- 2023-10-12 10:40:29+00:00 -->
|
||||
- [[#1745](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1745)] Add missing fields to Cloud Storage bucket ([devuonocar](https://github.com/devuonocar)) <!-- 2023-10-10 20:40:30+00:00 -->
|
||||
- [[#1744](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1744)] Append "s" to pubsub backoff times ([juliocc](https://github.com/juliocc)) <!-- 2023-10-10 10:32:20+00:00 -->
|
||||
- [[#1741](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1741)] Add PSA peered domains support to `net-vpc` ([juliocc](https://github.com/juliocc)) <!-- 2023-10-06 15:31:33+00:00 -->
|
||||
- [[#1737](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1737)] Enforce mandatory types in all variables ([juliocc](https://github.com/juliocc)) <!-- 2023-10-06 09:44:34+00:00 -->
|
||||
- [[#1732](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1732)] Added FQDN Network Policy feature on GKE Cluster ([luigi-bitonti](https://github.com/luigi-bitonti)) <!-- 2023-10-06 08:05:54+00:00 -->
|
||||
- [[#1735](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1735)] Make deletion protection consistent across all modules ([juliocc](https://github.com/juliocc)) <!-- 2023-10-05 15:31:08+00:00 -->
|
||||
- [[#1726](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1726)] Add materialized views for bigquery ([devuonocar](https://github.com/devuonocar)) <!-- 2023-10-04 12:25:57+00:00 -->
|
||||
|
||||
### TOOLS
|
||||
|
||||
- [[#1737](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1737)] Enforce mandatory types in all variables ([juliocc](https://github.com/juliocc)) <!-- 2023-10-06 09:44:34+00:00 -->
|
||||
- [[#1734](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1734)] Update to lint.sh and wording to some tf ([bluPhy](https://github.com/bluPhy)) <!-- 2023-10-05 06:32:08+00:00 -->
|
||||
|
||||
## [27.0.0] - 2023-10-04
|
||||
|
||||
|
|
|
@ -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** - <!-- [AlloyDB instance](./modules/alloydb-instance), --> [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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -147,15 +147,12 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl
|
|||
### VPCs
|
||||
|
||||
VPCs are defined in separate files, one for `landing` and one for each of `prod` and `dev`.
|
||||
Each file contains the same resources, described in the following paragraphs.
|
||||
|
||||
The **project** ([`project`](../../../modules/project)) contains the VPC, and enables the required APIs and sets itself as a "[host project](https://cloud.google.com/vpc/docs/shared-vpc)".
|
||||
These files contain different resources:
|
||||
|
||||
The **VPC** ([`net-vpc`](../../../modules/net-vpc)) manages the DNS inbound policy (for Landing), explicit routes for `{private,restricted}.googleapis.com`, and its **subnets**. Subnets are created leveraging a "resource factory" paradigm, where the configuration is separated from the module that implements it, and stored in a well-structured file. To add a new subnet, simply create a new file in the `data_folder` directory defined in the module, following the examples found in the [Fabric `net-vpc` documentation](../../../modules/net-vpc#subnet-factory). Sample subnets are shipped in [data/subnets](./data/subnets), and can be easily customised to fit your needs.
|
||||
|
||||
Subnets for [L7 ILBs](https://cloud.google.com/load-balancing/docs/l7-internal/proxy-only-subnets) are handled differently, and defined in variable `l7ilb_subnets`, while ranges for [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by variable `psa_ranges` - such variables are consumed by spoke VPCs.
|
||||
|
||||
**Cloud NAT** ([`net-cloudnat`](../../../modules/net-cloudnat)) manages the networking infrastructure required to enable internet egress.
|
||||
- **project** ([`projects`](../../../modules/project)): the "[host projects](https://cloud.google.com/vpc/docs/shared-vpc)" containing the VPCs and enabling the required APIs.
|
||||
- **VPCs** ([`net-vpc`](../../../modules/net-vpc)): manages the subnets, the explicit routes for `{private,restricted}.googleapis.com` and the DNS inbound policy for the trusted landing VPC. Non-infrastructural subnets are created leveraging resource factories. Sample subnets are shipped in [data/subnets](./data/subnets) and can be easily customized to fit users' needs. [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by the variable `psa_ranges` if managed services are needed.
|
||||
- **Cloud NAT** ([`net-cloudnat`](../../../modules/net-cloudnat)) manages the networking infrastructure required to enable internet egress.
|
||||
|
||||
### VPNs
|
||||
|
||||
|
@ -282,17 +279,12 @@ terraform apply
|
|||
|
||||
#### Private Google Access
|
||||
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment.
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment:
|
||||
|
||||
For PGA to work:
|
||||
- DNS response policies in the landing project implement rules for all supported domains reachable via PGA
|
||||
- routes for the private and restricted ranges are defined in all VPCs
|
||||
|
||||
- Private Google Access should be enabled on the subnet. \
|
||||
Subnets created by the `net-vpc` module are PGA-enabled by default.
|
||||
|
||||
- 199.36.153.4/30 (`restricted.googleapis.com`) and 199.36.153.8/30 (`private.googleapis.com`) should be routed from on-prem to VPC, and from there to the `default-internet-gateway`. \
|
||||
Per variable `vpn_onprem_configs` such ranges are advertised to onprem - furthermore every VPC (e.g. see `landing-vpc` in [`landing.tf`](./landing.tf)) has explicit routes set in case the `0.0.0.0/0` route is changed.
|
||||
|
||||
- A private DNS zone for `googleapis.com` should be created and configured per [this article](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid#config-domain), as implemented in module `googleapis-private-zone` in [`dns-landing.tf`](./dns-landing.tf)
|
||||
To enable PGA access from on premises advertise the private/restricted ranges via the `landing-to-onprem-primary-vpn` variable, using router or tunnel custom advertisements.
|
||||
|
||||
## Customizations
|
||||
|
||||
|
@ -406,10 +398,10 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
|
|||
| [factories_config](variables.tf#L80) | Configuration for network resource factories. | <code title="object({ data_dir = optional(string, "data") dns_policy_rules_file = optional(string, "data/dns-policy-rules.yaml") firewall_policy_name = optional(string, "net-default") })">object({…})</code> | | <code title="{ data_dir = "data" }">{…}</code> | |
|
||||
| [outputs_location](variables.tf#L121) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [peering_configs](variables-peerings.tf#L19) | Peering configurations. | <code title="object({ dev = optional(object({ export = optional(bool, true) import = optional(bool, true) public_export = optional(bool) public_import = optional(bool) }), {}) prod = optional(object({ export = optional(bool, true) import = optional(bool, true) public_export = optional(bool) public_import = optional(bool) }), {}) })">object({…})</code> | | <code>{}</code> | |
|
||||
| [psa_ranges](variables.tf#L138) | IP ranges used for Private Service Access (CloudSQL, etc.). | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L155) | Region definitions. | <code title="object({ primary = string secondary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" secondary = "europe-west4" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L167) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_onprem_primary_config](variables.tf#L181) | VPN gateway configuration for onprem interconnection in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [psa_ranges](variables.tf#L138) | IP ranges used for Private Service Access (CloudSQL, etc.). | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L157) | Region definitions. | <code title="object({ primary = string secondary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" secondary = "europe-west4" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L169) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_onprem_primary_config](variables.tf#L183) | VPN gateway configuration for onprem interconnection in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -142,11 +142,13 @@ variable "psa_ranges" {
|
|||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
prod = object({
|
||||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
})
|
||||
default = null
|
||||
|
|
|
@ -153,15 +153,11 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl
|
|||
### VPCs
|
||||
|
||||
VPCs are defined in separate files, one for `landing` and one for each of `prod` and `dev`.
|
||||
Each file contains the same resources, described in the following paragraphs.
|
||||
These files contain different resources:
|
||||
|
||||
The **project** ([`project`](../../../modules/project)) contains the VPC, and enables the required APIs and sets itself as a "[host project](https://cloud.google.com/vpc/docs/shared-vpc)".
|
||||
|
||||
The **VPC** ([`net-vpc`](../../../modules/net-vpc)) manages the DNS inbound policy (for Landing), explicit routes for `{private,restricted}.googleapis.com`, and its **subnets**. Subnets are created leveraging a "resource factory" paradigm, where the configuration is separated from the module that implements it, and stored in a well-structured file. To add a new subnet, simply create a new file in the `data_folder` directory defined in the module, following the examples found in the [Fabric `net-vpc` documentation](../../../modules/net-vpc#subnet-factory). Sample subnets are shipped in [data/subnets](./data/subnets), and can be easily customised to fit your needs.
|
||||
|
||||
Subnets for [L7 ILBs](https://cloud.google.com/load-balancing/docs/l7-internal/proxy-only-subnets) are handled differently, and defined in variable `l7ilb_subnets`, while ranges for [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by variable `psa_ranges` - such variables are consumed by spoke VPCs.
|
||||
|
||||
**Cloud NAT** ([`net-cloudnat`](../../../modules/net-cloudnat)) manages the networking infrastructure required to enable internet egress.
|
||||
- **project** ([`projects`](../../../modules/project)): the "[host projects](https://cloud.google.com/vpc/docs/shared-vpc)" containing the VPCs and enabling the required APIs.
|
||||
- **VPCs** ([`net-vpc`](../../../modules/net-vpc)): manages the subnets, the explicit routes for `{private,restricted}.googleapis.com` and the DNS inbound policy for the trusted landing VPC. Non-infrastructural subnets are created leveraging resource factories. Sample subnets are shipped in [data/subnets](./data/subnets) and can be easily customized to fit users' needs. [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by the variable `psa_ranges` if managed services are needed.
|
||||
- **Cloud NAT** ([`net-cloudnat`](../../../modules/net-cloudnat)) manages the networking infrastructure required to enable internet egress.
|
||||
|
||||
### VPNs
|
||||
|
||||
|
@ -296,17 +292,12 @@ terraform apply
|
|||
|
||||
#### Private Google Access
|
||||
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment.
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment:
|
||||
|
||||
For PGA to work:
|
||||
- DNS response policies in the landing project implement rules for all supported domains reachable via PGA
|
||||
- routes for the private and restricted ranges are defined in all VPCs
|
||||
|
||||
- Private Google Access should be enabled on the subnet. \
|
||||
Subnets created by the `net-vpc` module are PGA-enabled by default.
|
||||
|
||||
- 199.36.153.4/30 (`restricted.googleapis.com`) and 199.36.153.8/30 (`private.googleapis.com`) should be routed from on-prem to VPC, and from there to the `default-internet-gateway`. \
|
||||
Per variable `vpn_onprem_configs` such ranges are advertised to onprem - furthermore every VPC (e.g. see `landing-vpc` in [`landing.tf`](./landing.tf)) has explicit routes set in case the `0.0.0.0/0` route is changed.
|
||||
|
||||
- A private DNS zone for `googleapis.com` should be created and configured per [this article](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid#config-domain), as implemented in module `googleapis-private-zone` in [`dns-landing.tf`](./dns-landing.tf)
|
||||
To enable PGA access from on premises advertise the private/restricted ranges via the `landing-to-onprem-primary-vpn` variable, using router or tunnel custom advertisements.
|
||||
|
||||
## Customizations
|
||||
|
||||
|
@ -430,11 +421,11 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
|
|||
| [dns](variables.tf#L72) | Onprem DNS resolvers. | <code>map(list(string))</code> | | <code title="{ onprem = ["10.0.200.3"] }">{…}</code> | |
|
||||
| [factories_config](variables.tf#L80) | Configuration for network resource factories. | <code title="object({ data_dir = optional(string, "data") dns_policy_rules_file = optional(string, "data/dns-policy-rules.yaml") firewall_policy_name = optional(string, "net-default") })">object({…})</code> | | <code title="{ data_dir = "data" }">{…}</code> | |
|
||||
| [outputs_location](variables.tf#L121) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [psa_ranges](variables.tf#L138) | IP ranges used for Private Service Access (CloudSQL, etc.). | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L155) | Region definitions. | <code title="object({ primary = string secondary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" secondary = "europe-west4" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L167) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [psa_ranges](variables.tf#L138) | IP ranges used for Private Service Access (CloudSQL, etc.). | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L157) | Region definitions. | <code title="object({ primary = string secondary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" secondary = "europe-west4" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L169) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_configs](variables-vpn.tf#L17) | Hub to spokes VPN configurations. | <code title="object({ dev = object({ asn = number custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) landing = object({ asn = number custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) prod = object({ asn = number custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) })">object({…})</code> | | <code title="{ dev = { asn = 65501 } landing = { asn = 65500 } prod = { asn = 65502 } }">{…}</code> | |
|
||||
| [vpn_onprem_primary_config](variables.tf#L181) | VPN gateway configuration for onprem interconnection in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [vpn_onprem_primary_config](variables.tf#L183) | VPN gateway configuration for onprem interconnection in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -142,11 +142,13 @@ variable "psa_ranges" {
|
|||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
prod = object({
|
||||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
})
|
||||
default = null
|
||||
|
|
|
@ -125,19 +125,7 @@ This stage uses a dedicated /11 block (10.64.0.0/11), which should be sized to t
|
|||
|
||||
The /11 block is evenly split in eight, smaller /16 blocks, assigned to different areas of the GCP network: *landing untrusted europe-west1*, *landing untrusted europe-west4*, *landing trusted europe-west1*, *landing untrusted europe-west4*, *development europe-west1*, *development europe-west4*, *production europe-west1*, *production europe-west4*.
|
||||
|
||||
The first /24 range in every area is allocated for a default subnet, which can be removed or modified as needed.
|
||||
|
||||
Spoke VPCs also define and reserve three "special" CIDR ranges, derived from their respective /16, dedicated to
|
||||
|
||||
- [PSA (Private Service Access)](https://cloud.google.com/vpc/docs/private-services-access):
|
||||
|
||||
- The second-last /24 range is used for PSA (CloudSQL, Postrgres)
|
||||
|
||||
- The third-last /24 range is used for PSA (CloudSQL, MySQL)
|
||||
|
||||
- [Internal Application Load Balancers (L7 LBs)](https://cloud.google.com/load-balancing/docs/l7-internal):
|
||||
|
||||
- The last /24 range
|
||||
The first /24 range in every area is allocated for a default subnet, which can be removed or modified as needed. The last three /24 ranges can be used for [PSA (Private Service Access)](https://cloud.google.com/vpc/docs/private-services-access)via the `psa_ranges` variable, or for [Internal Application Load Balancers (L7 LBs)](https://cloud.google.com/load-balancing/docs/l7-internal) subnets via the factory.
|
||||
|
||||
This is a summary of the subnets allocated by default in this setup:
|
||||
|
||||
|
@ -148,23 +136,23 @@ This is a summary of the subnets allocated by default in this setup:
|
|||
| landing-untrusted-default-ew1 | Untrusted landing subnet - europe-west1 | 10.128.0.0/24 |
|
||||
| landing-untrusted-default-ew4 | Untrusted landing subnet - europe-west4 | 10.128.32.0/24 |
|
||||
| dev-default-ew1 | Dev spoke subnet - europe-west1 | 10.68.0.0/24 |
|
||||
| dev-default-ew1 (PSA MySQL) | PSA subnet for MySQL in dev spoke - europe-west1 | 10.68.253.0/24 |
|
||||
| dev-default-ew1 (PSA SQL Server) | PSA subnet for Postgres in dev spoke - europe-west1 | 10.68.254.0/24 |
|
||||
| dev-default-ew1 (L7 ILB) | L7 ILB subnet for dev spoke - europe-west1 | 10.68.255.0/24 |
|
||||
| dev-default-ew1 | Free (PSA) - europe-west1 | 10.68.253.0/24 |
|
||||
| dev-default-ew1 | Free (PSA) - europe-west1 | 10.68.254.0/24 |
|
||||
| dev-default-ew1 | Free (L7 ILB) - europe-west1 | 10.68.255.0/24 |
|
||||
| dev-default-ew4 | Dev spoke subnet - europe-west4 | 10.84.0.0/24 |
|
||||
| dev-default-ew4 (PSA MySQL) | PSA subnet for MySQL in dev spoke - europe-west4 | 10.84.253.0/24 |
|
||||
| dev-default-ew4 (PSA SQL Server) | PSA subnet for Postgres in dev spoke - europe-west4 | 10.84.254.0/24 |
|
||||
| dev-default-ew4 (L7 ILB) | L7 ILB subnet for dev spoke - europe-west4 | 10.84.255.0/24 |
|
||||
| dev-default-ew4 | Free (PSA) - europe-west4 | 10.84.253.0/24 |
|
||||
| dev-default-ew4 | Free (PSA) - europe-west4 | 10.84.254.0/24 |
|
||||
| dev-default-ew4 | Free (L7 ILB) - europe-west4 | 10.84.255.0/24 |
|
||||
| prod-default-ew1 | Prod spoke subnet - europe-west1 | 10.72.0.0/24 |
|
||||
| prod-default-ew1 (PSA MySQL) | PSA subnet for MySQL in prod spoke - europe-west1 | 10.72.253.0/24 |
|
||||
| prod-default-ew1 (PSA SQL Server) | PSA subnet for Postgres in prod spoke - europe-west1 | 10.72.254.0/24 |
|
||||
| prod-default-ew1 (L7 ILB) | L7 ILB subnet for prod spoke - europe-west1 | 10.72.255.0/24 |
|
||||
| prod-default-ew1 | Free (PSA) - europe-west1 | 10.72.253.0/24 |
|
||||
| prod-default-ew1 | Free (PSA) - europe-west1 | 10.72.254.0/24 |
|
||||
| prod-default-ew1 | Free (L7 ILB) - europe-west1 | 10.72.255.0/24 |
|
||||
| prod-default-ew4 | Prod spoke subnet - europe-west4 | 10.88.0.0/24 |
|
||||
| prod-default-ew4 (PSA MySQL) | PSA subnet for MySQL in prod spoke - europe-west4 | 10.88.253.0/24 |
|
||||
| prod-default-ew4 (PSA SQL Server) | PSA subnet for Postgres in prod spoke - europe-west4 | 10.88.254.0/24 |
|
||||
| prod-default-ew4 (L7 ILB) | L7 ILB subnet for prod spoke - europe-west4 | 10.88.255.0/24 |
|
||||
| prod-default-ew4 | Free (PSA) - europe-west4 | 10.88.253.0/24 |
|
||||
| prod-default-ew4 | Free (PSA) - europe-west4 | 10.88.254.0/24 |
|
||||
| prod-default-ew4 | Free (L7 ILB) - europe-west4 | 10.88.255.0/24 |
|
||||
|
||||
These subnets can advertised to on-premises as an aggregate /11 range (10.64.0.0/11). Refer to the `var.vpn_onprem_primary_config.router_config` and `var.vpn_onprem_secondary_config.router_config` variables to configure it.
|
||||
These subnets can be advertised to on-premises as an aggregate /11 range (10.64.0.0/11). Refer to the `var.vpn_onprem_primary_config.router_config` and `var.vpn_onprem_secondary_config.router_config` variables to configure it.
|
||||
|
||||
Routes in GCP are either automatically created (for example, when a subnet is added to a VPC), manually created via static routes, dynamically exchanged through VPC peerings, or dynamically programmed by [Cloud Routers](https://cloud.google.com/network-connectivity/docs/router#docs) when a BGP session is established. BGP sessions can be configured to advertise VPC ranges, and/or custom ranges via custom advertisements.
|
||||
|
||||
|
@ -231,10 +219,7 @@ VPCs are defined in separate files, one for `landing` (trusted and untrusted), o
|
|||
These files contain different resources:
|
||||
|
||||
- **project** ([`projects`](../../../modules/project)): the "[host projects](https://cloud.google.com/vpc/docs/shared-vpc)" containing the VPCs and enabling the required APIs.
|
||||
- **VPCs** ([`net-vpc`](../../../modules/net-vpc)): manage the subnets, the explicit routes for `{private,restricted}.googleapis.com` and the DNS inbound policy (for the trusted landing VPC). Subnets are created leveraging "resource factories": the configuration is separated from the module that implements it, and stored in a well-structured file. To add a new subnet, simply create a new file in the `data_folder` directory defined in the module, following the examples found in the [Fabric `net-vpc` documentation](../../../modules/net-vpc#subnet-factory). Sample subnets are shipped in [data/subnets](./data/subnets) and can be easily customized to fit users' needs.
|
||||
|
||||
Subnets for [L7 ILBs](https://cloud.google.com/load-balancing/docs/l7-internal/proxy-only-subnets) are handled differently, and defined in variable `l7ilb_subnets`, while ranges for [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by variable `psa_ranges` - such variables are consumed by spoke VPCs.
|
||||
|
||||
- **VPCs** ([`net-vpc`](../../../modules/net-vpc)): manages the subnets, the explicit routes for `{private,restricted}.googleapis.com` and the DNS inbound policy for the trusted landing VPC. Non-infrastructural subnets are created leveraging resource factories. Sample subnets are shipped in [data/subnets](./data/subnets) and can be easily customized to fit users' needs. [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by the variable `psa_ranges` if managed services are needed.
|
||||
- **Cloud NAT** ([`net-cloudnat`](../../../modules/net-cloudnat)) (in the untrusted landing VPC only): it manages the networking infrastructure required to enable the Internet egress.
|
||||
|
||||
### VPNs
|
||||
|
@ -364,17 +349,12 @@ terraform apply
|
|||
|
||||
#### Private Google Access
|
||||
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment.
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment:
|
||||
|
||||
For PGA to work:
|
||||
- DNS response policies in the landing project implement rules for all supported domains reachable via PGA
|
||||
- routes for the private and restricted ranges are defined in all VPCs except untrusted
|
||||
|
||||
- Private Google Access should be enabled on the subnet. \
|
||||
Subnets created by the `net-vpc` module are PGA-enabled by default.
|
||||
|
||||
- 199.36.153.4/30 (`restricted.googleapis.com`) and 199.36.153.8/30 (`private.googleapis.com`) should be routed from on-prem to VPC, and from there to the `default-internet-gateway`. \
|
||||
Per variable `vpn_onprem_configs` such ranges are advertised to onprem - furthermore every VPC (e.g. see `landing-vpc` in [`landing.tf`](./landing.tf)) has explicit routes set in case the `0.0.0.0/0` route is changed.
|
||||
|
||||
- A private DNS zone for `googleapis.com` should be created and configured per [this article](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid#config-domain), as implemented in module `googleapis-private-zone` in [`dns-landing.tf`](./dns-landing.tf)
|
||||
To enable PGA access from on premises advertise the private/restricted ranges via the `vpn_onprem_primary_config` and `vpn_onprem_secondary_config` variables, using router or tunnel custom advertisements.
|
||||
|
||||
## Customizations
|
||||
|
||||
|
@ -488,11 +468,11 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
|
|||
| [gcp_ranges](variables.tf#L111) | GCP address ranges in name => range format. | <code>map(string)</code> | | <code title="{ gcp_dev_primary = "10.68.0.0/16" gcp_dev_secondary = "10.84.0.0/16" gcp_landing_trusted_primary = "10.64.0.0/17" gcp_landing_trusted_secondary = "10.80.0.0/17" gcp_landing_untrusted_primary = "10.64.127.0/17" gcp_landing_untrusted_secondary = "10.80.127.0/17" gcp_prod_primary = "10.72.0.0/16" gcp_prod_secondary = "10.88.0.0/16" }">{…}</code> | |
|
||||
| [onprem_cidr](variables.tf#L126) | Onprem addresses in name => range format. | <code>map(string)</code> | | <code title="{ main = "10.0.0.0/24" }">{…}</code> | |
|
||||
| [outputs_location](variables.tf#L144) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [psa_ranges](variables.tf#L161) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L178) | Region definitions. | <code title="object({ primary = string secondary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" secondary = "europe-west4" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L190) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_onprem_primary_config](variables.tf#L204) | VPN gateway configuration for onprem interconnection in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [vpn_onprem_secondary_config](variables.tf#L247) | VPN gateway configuration for onprem interconnection in the secondary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [psa_ranges](variables.tf#L161) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L180) | Region definitions. | <code title="object({ primary = string secondary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" secondary = "europe-west4" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L192) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_onprem_primary_config](variables.tf#L206) | VPN gateway configuration for onprem interconnection in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [vpn_onprem_secondary_config](variables.tf#L249) | VPN gateway configuration for onprem interconnection in the secondary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -165,11 +165,13 @@ variable "psa_ranges" {
|
|||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
prod = object({
|
||||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
})
|
||||
default = null
|
||||
|
|
|
@ -112,15 +112,12 @@ This configuration is battle-tested, and flexible enough to lend itself to simpl
|
|||
### VPCs
|
||||
|
||||
VPCs are defined in separate files, one for each of `prod` and `dev`.
|
||||
Each file contains the same resources, described in the following paragraphs.
|
||||
|
||||
The **project** ([`project`](../../../modules/project)) contains the VPC, and enables the required APIs and sets itself as a "[host project](https://cloud.google.com/vpc/docs/shared-vpc)".
|
||||
These files contain different resources:
|
||||
|
||||
The **VPC** ([`net-vpc`](../../../modules/net-vpc)) manages the DNS inbound policy, explicit routes for `{private,restricted}.googleapis.com`, and its **subnets**. Subnets are created leveraging a "resource factory" paradigm, where the configuration is separated from the module that implements it, and stored in a well-structured file. To add a new subnet, simply create a new file in the `data_folder` directory defined in the module, following the examples found in the [Fabric `net-vpc` documentation](../../../modules/net-vpc#subnet-factory). Sample subnets are shipped in [data/subnets](./data/subnets), and can be easily customised to fit your needs.
|
||||
|
||||
Subnets for [L7 ILBs](https://cloud.google.com/load-balancing/docs/l7-internal/proxy-only-subnets) are handled differently, and defined in variable `l7ilb_subnets`, while ranges for [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by variable `psa_ranges` - such variables are consumed by spoke VPCs.
|
||||
|
||||
**Cloud NAT** ([`net-cloudnat`](../../../modules/net-cloudnat)) manages the networking infrastructure required to enable internet egress.
|
||||
- **project** ([`project`](../../../modules/project)) contains the VPC, and enables the required APIs and sets itself as a "[host project](https://cloud.google.com/vpc/docs/shared-vpc)".
|
||||
- **VPCs** ([`net-vpc`](../../../modules/net-vpc)): manages the subnets, the explicit routes for `{private,restricted}.googleapis.com` and the DNS inbound policy for the trusted landing VPC. Non-infrastructural subnets are created leveraging resource factories. Sample subnets are shipped in [data/subnets](./data/subnets) and can be easily customized to fit users' needs. [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by the variable `psa_ranges` if managed services are needed.
|
||||
- **Cloud NAT** ([`net-cloudnat`](../../../modules/net-cloudnat)) manages the networking infrastructure required to enable internet egress.
|
||||
|
||||
### VPNs
|
||||
|
||||
|
@ -244,17 +241,12 @@ terraform apply
|
|||
|
||||
#### Private Google Access
|
||||
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment.
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment:
|
||||
|
||||
For PGA to work:
|
||||
- DNS response policies in the landing project implement rules for all supported domains reachable via PGA
|
||||
- routes for the private and restricted ranges are defined in all VPCs
|
||||
|
||||
- Private Google Access should be enabled on the subnet. \
|
||||
Subnets created by the `net-vpc` module are PGA-enabled by default.
|
||||
|
||||
- 199.36.153.4/30 (`restricted.googleapis.com`) and 199.36.153.8/30 (`private.googleapis.com`) should be routed from on-prem to VPC, and from there to the `default-internet-gateway`. \
|
||||
Per variable `vpn_onprem_configs` such ranges are advertised to onprem - furthermore every VPC has explicit routes set in case the `0.0.0.0/0` route is changed.
|
||||
|
||||
- A private DNS zone for `googleapis.com` should be created and configured per [this article](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid#config-domain)
|
||||
To enable PGA access from on premises advertise the private/restricted ranges via the `vpn_onprem_dev_primary_config` and `vpn_onprem_prod_primary_config` variables, using router or tunnel custom advertisements.
|
||||
|
||||
## Customizations
|
||||
|
||||
|
@ -348,11 +340,11 @@ Regions are defined via the `regions` variable which sets up a mapping between t
|
|||
| [dns](variables.tf#L72) | Onprem DNS resolvers. | <code>map(list(string))</code> | | <code title="{ prod = ["10.0.1.1"] dev = ["10.0.2.1"] }">{…}</code> | |
|
||||
| [factories_config](variables.tf#L81) | Configuration for network resource factories. | <code title="object({ data_dir = optional(string, "data") dns_policy_rules_file = optional(string, "data/dns-policy-rules.yaml") firewall_policy_name = optional(string, "net-default") })">object({…})</code> | | <code title="{ data_dir = "data" }">{…}</code> | |
|
||||
| [outputs_location](variables.tf#L122) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [psa_ranges](variables.tf#L139) | IP ranges used for Private Service Access (e.g. CloudSQL). | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L156) | Region definitions. | <code title="object({ primary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L166) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_onprem_dev_primary_config](variables.tf#L180) | VPN gateway configuration for onprem interconnection from dev in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [vpn_onprem_prod_primary_config](variables.tf#L223) | VPN gateway configuration for onprem interconnection from prod in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [psa_ranges](variables.tf#L139) | IP ranges used for Private Service Access (e.g. CloudSQL). | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L158) | Region definitions. | <code title="object({ primary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L168) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_onprem_dev_primary_config](variables.tf#L182) | VPN gateway configuration for onprem interconnection from dev in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [vpn_onprem_prod_primary_config](variables.tf#L225) | VPN gateway configuration for onprem interconnection from prod in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -143,11 +143,13 @@ variable "psa_ranges" {
|
|||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
prod = object({
|
||||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
})
|
||||
default = null
|
||||
|
|
|
@ -148,19 +148,7 @@ This stage uses a dedicated /11 block (10.64.0.0/11), which should be sized to t
|
|||
|
||||
The /11 block is evenly split in eight, smaller /16 blocks, assigned to different areas of the GCP network: *landing untrusted europe-west1*, *landing untrusted europe-west4*, *landing trusted europe-west1*, *landing untrusted europe-west4*, *development europe-west1*, *development europe-west4*, *production europe-west1*, *production europe-west4*.
|
||||
|
||||
The first /24 range in every area is allocated for a default subnet, which can be removed or modified as needed.
|
||||
|
||||
Spoke VPCs also define and reserve three "special" CIDR ranges, derived from their respective /16, dedicated to
|
||||
|
||||
- [PSA (Private Service Access)](https://cloud.google.com/vpc/docs/private-services-access):
|
||||
|
||||
- The second-last /24 range is used for PSA (CloudSQL, Postrgres)
|
||||
|
||||
- The third-last /24 range is used for PSA (CloudSQL, MySQL)
|
||||
|
||||
- [Internal Application Load Balancers (L7 LBs)](https://cloud.google.com/load-balancing/docs/l7-internal):
|
||||
|
||||
- The last /24 range
|
||||
The first /24 range in every area is allocated for a default subnet, which can be removed or modified as needed. The last three /24 ranges can be used for [PSA (Private Service Access)](https://cloud.google.com/vpc/docs/private-services-access)via the `psa_ranges` variable, or for [Internal Application Load Balancers (L7 LBs)](https://cloud.google.com/load-balancing/docs/l7-internal) subnets via the factory.
|
||||
|
||||
This is a summary of the subnets allocated by default in this setup:
|
||||
|
||||
|
@ -171,23 +159,23 @@ This is a summary of the subnets allocated by default in this setup:
|
|||
| landing-untrusted-default-ew1 | Untrusted landing subnet - europe-west1 | 10.128.0.0/24 |
|
||||
| landing-untrusted-default-ew4 | Untrusted landing subnet - europe-west4 | 10.128.32.0/24 |
|
||||
| dev-default-ew1 | Dev spoke subnet - europe-west1 | 10.68.0.0/24 |
|
||||
| dev-default-ew1 (PSA MySQL) | PSA subnet for MySQL in dev spoke - europe-west1 | 10.68.253.0/24 |
|
||||
| dev-default-ew1 (PSA SQL Server) | PSA subnet for Postgres in dev spoke - europe-west1 | 10.68.254.0/24 |
|
||||
| dev-default-ew1 (L7 ILB) | L7 ILB subnet for dev spoke - europe-west1 | 10.68.255.0/24 |
|
||||
| dev-default-ew1 | Free (PSA) - europe-west1 | 10.68.253.0/24 |
|
||||
| dev-default-ew1 | Free (PSA) - europe-west1 | 10.68.254.0/24 |
|
||||
| dev-default-ew1 | Free (L7 ILB) - europe-west1 | 10.68.255.0/24 |
|
||||
| dev-default-ew4 | Dev spoke subnet - europe-west4 | 10.84.0.0/24 |
|
||||
| dev-default-ew4 (PSA MySQL) | PSA subnet for MySQL in dev spoke - europe-west4 | 10.84.253.0/24 |
|
||||
| dev-default-ew4 (PSA SQL Server) | PSA subnet for Postgres in dev spoke - europe-west4 | 10.84.254.0/24 |
|
||||
| dev-default-ew4 (L7 ILB) | L7 ILB subnet for dev spoke - europe-west4 | 10.84.255.0/24 |
|
||||
| dev-default-ew4 | Free (PSA) - europe-west4 | 10.84.253.0/24 |
|
||||
| dev-default-ew4 | Free (PSA) - europe-west4 | 10.84.254.0/24 |
|
||||
| dev-default-ew4 | Free (L7 ILB) - europe-west4 | 10.84.255.0/24 |
|
||||
| prod-default-ew1 | Prod spoke subnet - europe-west1 | 10.72.0.0/24 |
|
||||
| prod-default-ew1 (PSA MySQL) | PSA subnet for MySQL in prod spoke - europe-west1 | 10.72.253.0/24 |
|
||||
| prod-default-ew1 (PSA SQL Server) | PSA subnet for Postgres in prod spoke - europe-west1 | 10.72.254.0/24 |
|
||||
| prod-default-ew1 (L7 ILB) | L7 ILB subnet for prod spoke - europe-west1 | 10.72.255.0/24 |
|
||||
| prod-default-ew1 | Free (PSA) - europe-west1 | 10.72.253.0/24 |
|
||||
| prod-default-ew1 | Free (PSA) - europe-west1 | 10.72.254.0/24 |
|
||||
| prod-default-ew1 | Free (L7 ILB) - europe-west1 | 10.72.255.0/24 |
|
||||
| prod-default-ew4 | Prod spoke subnet - europe-west4 | 10.88.0.0/24 |
|
||||
| prod-default-ew4 (PSA MySQL) | PSA subnet for MySQL in prod spoke - europe-west4 | 10.88.253.0/24 |
|
||||
| prod-default-ew4 (PSA SQL Server) | PSA subnet for Postgres in prod spoke - europe-west4 | 10.88.254.0/24 |
|
||||
| prod-default-ew4 (L7 ILB) | L7 ILB subnet for prod spoke - europe-west4 | 10.88.255.0/24 |
|
||||
| prod-default-ew4 | Free (PSA) - europe-west4 | 10.88.253.0/24 |
|
||||
| prod-default-ew4 | Free (PSA) - europe-west4 | 10.88.254.0/24 |
|
||||
| prod-default-ew4 | Free (L7 ILB) - europe-west4 | 10.88.255.0/24 |
|
||||
|
||||
These subnets can advertised to on-premises as an aggregate /11 range (10.64.0.0/11). Refer to the `var.vpn_onprem_primary_config.router_config` and `var.vpn_onprem_secondary_config.router_config` variables to configure it.
|
||||
These subnets can be advertised to on-premises as an aggregate /11 range (10.64.0.0/11). Refer to the `var.vpn_onprem_primary_config.router_config` and `var.vpn_onprem_secondary_config.router_config` variables to configure it.
|
||||
|
||||
Routes in GCP are either automatically created (for example, when a subnet is added to a VPC), manually created via static routes, dynamically exchanged through VPC peerings, or dynamically programmed by [Cloud Routers](https://cloud.google.com/network-connectivity/docs/router#docs) when a BGP session is established. BGP sessions can be configured to advertise VPC ranges, and/or custom ranges via custom advertisements.
|
||||
|
||||
|
@ -252,10 +240,7 @@ VPCs are defined in separate files, one for `landing` (trusted and untrusted), o
|
|||
These files contain different resources:
|
||||
|
||||
- **project** ([`projects`](../../../modules/project)): the "[host projects](https://cloud.google.com/vpc/docs/shared-vpc)" containing the VPCs and enabling the required APIs.
|
||||
- **VPCs** ([`net-vpc`](../../../modules/net-vpc)): manage the subnets, the explicit routes for `{private,restricted}.googleapis.com` and the DNS inbound policy (for the trusted landing VPC). Subnets are created leveraging "resource factories": the configuration is separated from the module that implements it, and stored in a well-structured file. To add a new subnet, simply create a new file in the `data_folder` directory defined in the module, following the examples found in the [Fabric `net-vpc` documentation](../../../modules/net-vpc#subnet-factory). Sample subnets are shipped in [data/subnets](./data/subnets) and can be easily customized to fit users' needs.
|
||||
|
||||
Subnets for [L7 ILBs](https://cloud.google.com/load-balancing/docs/l7-internal/proxy-only-subnets) are handled differently, and defined in variable `l7ilb_subnets`, while ranges for [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by variable `psa_ranges` - such variables are consumed by spoke VPCs.
|
||||
|
||||
- **VPCs** ([`net-vpc`](../../../modules/net-vpc)): manages the subnets, the explicit routes for `{private,restricted}.googleapis.com` and the DNS inbound policy for the trusted landing VPC. Non-infrastructural subnets are created leveraging resource factories. Sample subnets are shipped in [data/subnets](./data/subnets) and can be easily customized to fit users' needs. [PSA](https://cloud.google.com/vpc/docs/configure-private-services-access#allocating-range) are configured by the variable `psa_ranges` if managed services are needed.
|
||||
- **Cloud NAT** ([`net-cloudnat`](../../../modules/net-cloudnat)) (in the untrusted landing VPC only): it manages the networking infrastructure required to enable the Internet egress.
|
||||
|
||||
### VPNs
|
||||
|
@ -387,17 +372,12 @@ terraform apply
|
|||
|
||||
#### Private Google Access
|
||||
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment.
|
||||
[Private Google Access](https://cloud.google.com/vpc/docs/private-google-access) (or PGA) enables VMs and on-prem systems to consume Google APIs from within the Google network, and is already fully configured on this environment:
|
||||
|
||||
For PGA to work:
|
||||
- DNS response policies in the landing project implement rules for all supported domains reachable via PGA
|
||||
- routes for the private and restricted ranges are defined in all VPCs except untrusted
|
||||
|
||||
- Private Google Access should be enabled on the subnet. \
|
||||
Subnets created by the `net-vpc` module are PGA-enabled by default.
|
||||
|
||||
- 199.36.153.4/30 (`restricted.googleapis.com`) and 199.36.153.8/30 (`private.googleapis.com`) should be routed from on-prem to VPC, and from there to the `default-internet-gateway`. \
|
||||
Per variable `vpn_onprem_configs` such ranges are advertised to onprem - furthermore every VPC (e.g. see `landing-vpc` in [`landing.tf`](./landing.tf)) has explicit routes set in case the `0.0.0.0/0` route is changed.
|
||||
|
||||
- A private DNS zone for `googleapis.com` should be created and configured per [this article](https://cloud.google.com/vpc/docs/configure-private-google-access-hybrid#config-domain), as implemented in module `googleapis-private-zone` in [`dns-landing.tf`](./dns-landing.tf)
|
||||
To enable PGA access from on premises advertise the private/restricted ranges via the `vpn_onprem_primary_config` and `vpn_onprem_secondary_config` variables, using router or tunnel custom advertisements.
|
||||
|
||||
## Customizations
|
||||
|
||||
|
@ -515,12 +495,12 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
|
|||
| [ncc_asn](variables.tf#L126) | The NCC Cloud Routers ASN configuration. | <code>map(number)</code> | | <code title="{ nva_primary = 64513 nva_secondary = 64514 trusted = 64515 untrusted = 64512 }">{…}</code> | |
|
||||
| [onprem_cidr](variables.tf#L137) | Onprem addresses in name => range format. | <code>map(string)</code> | | <code title="{ main = "10.0.0.0/24" }">{…}</code> | |
|
||||
| [outputs_location](variables.tf#L155) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | <code>string</code> | | <code>null</code> | |
|
||||
| [psa_ranges](variables.tf#L172) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L189) | Region definitions. | <code title="object({ primary = string secondary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" secondary = "europe-west4" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L201) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_onprem_primary_config](variables.tf#L215) | VPN gateway configuration for onprem interconnection in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [vpn_onprem_secondary_config](variables.tf#L258) | VPN gateway configuration for onprem interconnection in the secondary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [zones](variables.tf#L301) | Zones in which NVAs are deployed. | <code>list(string)</code> | | <code>["b", "c"]</code> | |
|
||||
| [psa_ranges](variables.tf#L172) | IP ranges used for Private Service Access (e.g. CloudSQL). Ranges is in name => range format. | <code title="object({ dev = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) prod = object({ ranges = map(string) export_routes = optional(bool, false) import_routes = optional(bool, false) peered_domains = optional(list(string), []) }) })">object({…})</code> | | <code>null</code> | |
|
||||
| [regions](variables.tf#L191) | Region definitions. | <code title="object({ primary = string secondary = string })">object({…})</code> | | <code title="{ primary = "europe-west1" secondary = "europe-west4" }">{…}</code> | |
|
||||
| [service_accounts](variables.tf#L203) | Automation service accounts in name => email format. | <code title="object({ data-platform-dev = string data-platform-prod = string gke-dev = string gke-prod = string project-factory-dev = string project-factory-prod = string })">object({…})</code> | | <code>null</code> | <code>1-resman</code> |
|
||||
| [vpn_onprem_primary_config](variables.tf#L217) | VPN gateway configuration for onprem interconnection in the primary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [vpn_onprem_secondary_config](variables.tf#L260) | VPN gateway configuration for onprem interconnection in the secondary region. | <code title="object({ peer_external_gateways = map(object({ redundancy_type = string interfaces = list(string) })) router_config = object({ create = optional(bool, true) asn = number name = optional(string) keepalive = optional(number) custom_advertise = optional(object({ all_subnets = bool ip_ranges = map(string) })) }) tunnels = map(object({ bgp_peer = object({ address = string asn = number route_priority = optional(number, 1000) custom_advertise = optional(object({ all_subnets = bool all_vpc_subnets = bool all_peer_vpc_subnets = bool ip_ranges = map(string) })) }) bgp_session_range = string ike_version = optional(number, 2) peer_external_gateway_interface = optional(number) peer_gateway = optional(string, "default") router = optional(string) shared_secret = optional(string) vpn_gateway_interface = number })) })">object({…})</code> | | <code>null</code> | |
|
||||
| [zones](variables.tf#L303) | Zones in which NVAs are deployed. | <code>list(string)</code> | | <code>["b", "c"]</code> | |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -176,11 +176,13 @@ variable "psa_ranges" {
|
|||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
prod = object({
|
||||
ranges = map(string)
|
||||
export_routes = optional(bool, false)
|
||||
import_routes = optional(bool, false)
|
||||
peered_domains = optional(list(string), [])
|
||||
})
|
||||
})
|
||||
default = null
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
<!-- BEGIN TOC -->
|
||||
- [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)
|
||||
<!-- END TOC -->
|
||||
|
||||
## 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.
|
||||
|
||||
<!-- markdownlint-disable MD034 -->
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- BEGIN TFDOC -->
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [id](variables.tf#L165) | Billing account id. | <code>string</code> | ✓ | |
|
||||
| [budget_notification_channels](variables.tf#L17) | Notification channels used by budget alerts. | <code title="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)) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [budgets](variables.tf#L47) | Billing budgets. Notification channels are either keys in corresponding variable, or external ids. | <code title="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) })), {}) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [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. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam](variables.tf#L128) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L135) | 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.tf#L150) | 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> |
|
||||
| [logging_sinks](variables.tf#L170) | Logging sinks to create for the organization. | <code title="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) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [projects](variables.tf#L203) | Projects associated with this billing account. | <code>list(string)</code> | | <code>[]</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive |
|
||||
|---|---|:---:|
|
||||
| [billing_budget_ids](outputs.tf#L17) | Billing budget ids. | |
|
||||
| [monitoring_notification_channel_ids](outputs.tf#L25) | Monitoring notification channel ids. | |
|
||||
<!-- END TFDOC -->
|
|
@ -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
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}')"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 = []
|
||||
}
|
|
@ -25,5 +25,3 @@ terraform {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
```
|
||||
<!-- BEGIN TFDOC -->
|
||||
|
||||
## Variables
|
||||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [billing_account](variables.tf#L23) | Billing account id. | <code>string</code> | ✓ | |
|
||||
| [name](variables.tf#L50) | Budget name. | <code>string</code> | ✓ | |
|
||||
| [thresholds](variables.tf#L85) | Thresholds percentages at which alerts are sent. Must be a value between 0 and 1. | <code title="object({ current = list(number) forecasted = list(number) })">object({…})</code> | ✓ | |
|
||||
| [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. | <code>number</code> | | <code>0</code> |
|
||||
| [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. | <code>string</code> | | <code>"INCLUDE_ALL_CREDITS"</code> |
|
||||
| [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. | <code title="object({ project_id = string emails = list(string) })">object({…})</code> | | <code>null</code> |
|
||||
| [notification_channels](variables.tf#L55) | Monitoring notification channels where to send updates. | <code>list(string)</code> | | <code>null</code> |
|
||||
| [notify_default_recipients](variables.tf#L61) | Notify Billing Account Administrators and Billing Account Users IAM roles for the target account. | <code>bool</code> | | <code>false</code> |
|
||||
| [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. | <code>list(string)</code> | | <code>null</code> |
|
||||
| [pubsub_topic](variables.tf#L73) | The ID of the Cloud Pub/Sub topic where budget related messages will be published. | <code>string</code> | | <code>null</code> |
|
||||
| [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. | <code>list(string)</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
| name | description | sensitive |
|
||||
|---|---|:---:|
|
||||
| [budget](outputs.tf#L17) | Budget resource. | |
|
||||
| [id](outputs.tf#L22) | Fully qualified budget id. | |
|
||||
|
||||
<!-- END TFDOC -->
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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."
|
||||
}
|
||||
}
|
|
@ -683,9 +683,9 @@ module "instance" {
|
|||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [name](variables.tf#L235) | Instance name. | <code>string</code> | ✓ | |
|
||||
| [network_interfaces](variables.tf#L240) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | <code title="list(object({ nat = optional(bool, false) network = string subnetwork = string addresses = optional(object({ internal = optional(string) external = optional(string) }), null) alias_ips = optional(map(string), {}) nic_type = optional(string) }))">list(object({…}))</code> | ✓ | |
|
||||
| [project_id](variables.tf#L277) | Project id. | <code>string</code> | ✓ | |
|
||||
| [zone](variables.tf#L369) | Compute zone. | <code>string</code> | ✓ | |
|
||||
| [network_interfaces](variables.tf#L240) | Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed. | <code title="list(object({ network = string subnetwork = string alias_ips = optional(map(string), {}) nat = optional(bool, false) nic_type = optional(string) stack_type = optional(string) addresses = optional(object({ internal = optional(string) external = optional(string) }), null) }))">list(object({…}))</code> | ✓ | |
|
||||
| [project_id](variables.tf#L278) | Project id. | <code>string</code> | ✓ | |
|
||||
| [zone](variables.tf#L370) | Compute zone. | <code>string</code> | ✓ | |
|
||||
| [attached_disk_defaults](variables.tf#L17) | Defaults for attached disks options. | <code title="object({ auto_delete = optional(bool, false) mode = string replica_zone = string type = string })">object({…})</code> | | <code title="{ auto_delete = true mode = "READ_WRITE" replica_zone = null type = "pd-balanced" }">{…}</code> |
|
||||
| [attached_disks](variables.tf#L37) | Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null. | <code title="list(object({ name = string device_name = optional(string) size = string snapshot_schedule = optional(string) source = optional(string) source_type = optional(string) options = optional( object({ auto_delete = optional(bool, false) mode = optional(string, "READ_WRITE") replica_zone = optional(string) type = optional(string, "pd-balanced") }), { auto_delete = true mode = "READ_WRITE" replica_zone = null type = "pd-balanced" } ) }))">list(object({…}))</code> | | <code>[]</code> |
|
||||
| [boot_disk](variables.tf#L83) | Boot disk properties. | <code title="object({ auto_delete = optional(bool, true) snapshot_schedule = optional(string) source = optional(string) initialize_params = optional(object({ image = optional(string, "projects/debian-cloud/global/images/family/debian-11") size = optional(number, 10) type = optional(string, "pd-balanced") })) use_independent_disk = optional(bool, false) })">object({…})</code> | | <code title="{ initialize_params = {} }">{…}</code> |
|
||||
|
@ -703,13 +703,13 @@ module "instance" {
|
|||
| [labels](variables.tf#L217) | Instance labels. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [metadata](variables.tf#L223) | Instance metadata. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [min_cpu_platform](variables.tf#L229) | Minimum CPU platform. | <code>string</code> | | <code>null</code> |
|
||||
| [options](variables.tf#L255) | Instance options. | <code title="object({ allow_stopping_for_update = optional(bool, true) deletion_protection = optional(bool, false) spot = optional(bool, false) termination_action = optional(string) })">object({…})</code> | | <code title="{ allow_stopping_for_update = true deletion_protection = false spot = false termination_action = null }">{…}</code> |
|
||||
| [scratch_disks](variables.tf#L282) | Scratch disks configuration. | <code title="object({ count = number interface = string })">object({…})</code> | | <code title="{ count = 0 interface = "NVME" }">{…}</code> |
|
||||
| [service_account](variables.tf#L294) | Service account email and scopes. If email is null, the default Compute service account will be used unless auto_create is true, in which case a service account will be created. Set the variable to null to avoid attaching a service account. | <code title="object({ auto_create = optional(bool, false) email = optional(string) scopes = optional(list(string)) })">object({…})</code> | | <code>{}</code> |
|
||||
| [shielded_config](variables.tf#L304) | Shielded VM configuration of the instances. | <code title="object({ enable_secure_boot = bool enable_vtpm = bool enable_integrity_monitoring = bool })">object({…})</code> | | <code>null</code> |
|
||||
| [snapshot_schedules](variables.tf#L314) | Snapshot schedule resource policies that can be attached to disks. | <code title="map(object({ schedule = object({ daily = optional(object({ days_in_cycle = number start_time = string })) hourly = optional(object({ hours_in_cycle = number start_time = string })) weekly = optional(list(object({ day = string start_time = string }))) }) description = optional(string) retention_policy = optional(object({ max_retention_days = number on_source_disk_delete_keep = optional(bool) })) snapshot_properties = optional(object({ chain_name = optional(string) guest_flush = optional(bool) labels = optional(map(string)) storage_locations = optional(list(string)) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [tag_bindings](variables.tf#L357) | Tag bindings for this instance, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [tags](variables.tf#L363) | Instance network tags for firewall rule targets. | <code>list(string)</code> | | <code>[]</code> |
|
||||
| [options](variables.tf#L256) | Instance options. | <code title="object({ allow_stopping_for_update = optional(bool, true) deletion_protection = optional(bool, false) spot = optional(bool, false) termination_action = optional(string) })">object({…})</code> | | <code title="{ allow_stopping_for_update = true deletion_protection = false spot = false termination_action = null }">{…}</code> |
|
||||
| [scratch_disks](variables.tf#L283) | Scratch disks configuration. | <code title="object({ count = number interface = string })">object({…})</code> | | <code title="{ count = 0 interface = "NVME" }">{…}</code> |
|
||||
| [service_account](variables.tf#L295) | Service account email and scopes. If email is null, the default Compute service account will be used unless auto_create is true, in which case a service account will be created. Set the variable to null to avoid attaching a service account. | <code title="object({ auto_create = optional(bool, false) email = optional(string) scopes = optional(list(string)) })">object({…})</code> | | <code>{}</code> |
|
||||
| [shielded_config](variables.tf#L305) | Shielded VM configuration of the instances. | <code title="object({ enable_secure_boot = bool enable_vtpm = bool enable_integrity_monitoring = bool })">object({…})</code> | | <code>null</code> |
|
||||
| [snapshot_schedules](variables.tf#L315) | Snapshot schedule resource policies that can be attached to disks. | <code title="map(object({ schedule = object({ daily = optional(object({ days_in_cycle = number start_time = string })) hourly = optional(object({ hours_in_cycle = number start_time = string })) weekly = optional(list(object({ day = string start_time = string }))) }) description = optional(string) retention_policy = optional(object({ max_retention_days = number on_source_disk_delete_keep = optional(bool) })) snapshot_properties = optional(object({ chain_name = optional(string) guest_flush = optional(bool) labels = optional(map(string)) storage_locations = optional(list(string)) })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [tag_bindings](variables.tf#L358) | Tag bindings for this instance, in key => tag value id format. | <code>map(string)</code> | | <code>null</code> |
|
||||
| [tags](variables.tf#L364) | Instance network tags for firewall rule targets. | <code>list(string)</code> | | <code>[]</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -237,6 +237,8 @@ resource "google_compute_instance" "default" {
|
|||
network = config.value.network
|
||||
subnetwork = config.value.subnetwork
|
||||
network_ip = try(config.value.addresses.internal, null)
|
||||
nic_type = config.value.nic_type
|
||||
stack_type = config.value.stack_type
|
||||
dynamic "access_config" {
|
||||
for_each = config.value.nat ? [""] : []
|
||||
content {
|
||||
|
@ -251,7 +253,6 @@ resource "google_compute_instance" "default" {
|
|||
ip_cidr_range = config_alias.value
|
||||
}
|
||||
}
|
||||
nic_type = config.value.nic_type
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -374,6 +375,8 @@ resource "google_compute_instance_template" "default" {
|
|||
network = config.value.network
|
||||
subnetwork = config.value.subnetwork
|
||||
network_ip = try(config.value.addresses.internal, null)
|
||||
nic_type = config.value.nic_type
|
||||
stack_type = config.value.stack_type
|
||||
dynamic "access_config" {
|
||||
for_each = config.value.nat ? [""] : []
|
||||
content {
|
||||
|
@ -388,7 +391,6 @@ resource "google_compute_instance_template" "default" {
|
|||
ip_cidr_range = config_alias.value
|
||||
}
|
||||
}
|
||||
nic_type = config.value.nic_type
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -240,15 +240,16 @@ variable "name" {
|
|||
variable "network_interfaces" {
|
||||
description = "Network interfaces configuration. Use self links for Shared VPC, set addresses to null if not needed."
|
||||
type = list(object({
|
||||
nat = optional(bool, false)
|
||||
network = string
|
||||
subnetwork = string
|
||||
alias_ips = optional(map(string), {})
|
||||
nat = optional(bool, false)
|
||||
nic_type = optional(string)
|
||||
stack_type = optional(string)
|
||||
addresses = optional(object({
|
||||
internal = optional(string)
|
||||
external = optional(string)
|
||||
}), null)
|
||||
alias_ips = optional(map(string), {})
|
||||
nic_type = optional(string)
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -272,7 +272,7 @@ module "folder" {
|
|||
|
||||
| name | description | resources |
|
||||
|---|---|---|
|
||||
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | <code>google_folder_iam_binding</code> · <code>google_folder_iam_member</code> |
|
||||
| [iam.tf](./iam.tf) | IAM bindings. | <code>google_folder_iam_binding</code> · <code>google_folder_iam_member</code> |
|
||||
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_folder_iam_audit_config</code> · <code>google_logging_folder_exclusion</code> · <code>google_logging_folder_sink</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
|
||||
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_compute_firewall_policy_association</code> · <code>google_essential_contacts_contact</code> · <code>google_folder</code> |
|
||||
| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | <code>google_org_policy_policy</code> |
|
||||
|
@ -295,7 +295,7 @@ module "folder" {
|
|||
| [id](variables.tf#L83) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
|
||||
| [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. | <code>map(map(list(string)))</code> | | <code>{}</code> |
|
||||
| [logging_exclusions](variables.tf#L104) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L111) | Logging sinks to create for the organization. | <code title="map(object({ bq_partitioned_table = optional(bool) description = optional(string) destination = string disabled = optional(bool, false) exclusions = optional(map(string), {}) filter = string include_children = optional(bool, true) type = string }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [logging_sinks](variables.tf#L111) | Logging sinks to create for the folder. | <code title="map(object({ bq_partitioned_table = optional(bool) description = optional(string) destination = string disabled = optional(bool, false) exclusions = optional(map(string), {}) filter = string include_children = optional(bool, true) type = string }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [name](variables.tf#L141) | Folder name. | <code>string</code> | | <code>null</code> |
|
||||
| [org_policies](variables.tf#L147) | Organization policies applied to this folder keyed by policy name. | <code title="map(object({ inherit_from_parent = optional(bool) # for list policies only. reset = optional(bool) rules = optional(list(object({ allow = optional(object({ all = optional(bool) values = optional(list(string)) })) deny = optional(object({ all = optional(bool) values = optional(list(string)) })) enforce = optional(bool) # for boolean policies only. condition = optional(object({ description = optional(string) expression = optional(string) location = optional(string) title = optional(string) }), {}) })), []) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [org_policies_data_path](variables.tf#L174) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -178,29 +178,30 @@ module "bucket" {
|
|||
|
||||
| name | description | type | required | default |
|
||||
|---|---|:---:|:---:|:---:|
|
||||
| [name](variables.tf#L158) | Bucket name suffix. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L213) | Bucket project id. | <code>string</code> | ✓ | |
|
||||
| [cors](variables.tf#L17) | CORS configuration for the bucket. Defaults to null. | <code title="object({ origin = optional(list(string)) method = optional(list(string)) response_header = optional(list(string)) max_age_seconds = optional(number) })">object({…})</code> | | <code>null</code> |
|
||||
| [custom_placement_config](variables.tf#L28) | The bucket's custom location configuration, which specifies the individual regions that comprise a dual-region bucket. If the bucket is designated as REGIONAL or MULTI_REGIONAL, the parameters are empty. | <code>list(string)</code> | | <code>null</code> |
|
||||
| [default_event_based_hold](variables.tf#L34) | Enable event based hold to new objects added to specific bucket, defaults to false. | <code>bool</code> | | <code>null</code> |
|
||||
| [encryption_key](variables.tf#L40) | KMS key that will be used for encryption. | <code>string</code> | | <code>null</code> |
|
||||
| [force_destroy](variables.tf#L46) | Optional map to set force destroy keyed by name, defaults to false. | <code>bool</code> | | <code>false</code> |
|
||||
| [iam](variables.tf#L52) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L58) | 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.tf#L73) | 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> |
|
||||
| [labels](variables.tf#L88) | Labels to be attached to all buckets. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [lifecycle_rules](variables.tf#L94) | Bucket lifecycle rule. | <code title="map(object({ action = object({ type = string storage_class = optional(string) }) condition = object({ age = optional(number) created_before = optional(string) custom_time_before = optional(string) days_since_custom_time = optional(number) days_since_noncurrent_time = optional(number) matches_prefix = optional(list(string)) matches_storage_class = optional(list(string)) # STANDARD, MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, ARCHIVE, DURABLE_REDUCED_AVAILABILITY matches_suffix = optional(list(string)) noncurrent_time_before = optional(string) num_newer_versions = optional(number) with_state = optional(string) # "LIVE", "ARCHIVED", "ANY" }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [location](variables.tf#L143) | Bucket location. | <code>string</code> | | <code>"EU"</code> |
|
||||
| [logging_config](variables.tf#L149) | Bucket logging configuration. | <code title="object({ log_bucket = string log_object_prefix = optional(string) })">object({…})</code> | | <code>null</code> |
|
||||
| [notification_config](variables.tf#L163) | GCS Notification configuration. | <code title="object({ enabled = bool payload_format = string topic_name = string sa_email = string event_types = optional(list(string)) custom_attributes = optional(map(string)) object_name_prefix = optional(string) })">object({…})</code> | | <code>null</code> |
|
||||
| [objects_to_upload](variables.tf#L177) | Objects to be uploaded to bucket. | <code title="map(object({ name = string metadata = optional(map(string)) content = optional(string) source = optional(string) cache_control = optional(string) content_disposition = optional(string) content_encoding = optional(string) content_language = optional(string) content_type = optional(string) event_based_hold = optional(bool) temporary_hold = optional(bool) detect_md5hash = optional(string) storage_class = optional(string) kms_key_name = optional(string) customer_encryption = optional(object({ encryption_algorithm = optional(string) encryption_key = string })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [prefix](variables.tf#L203) | Optional prefix used to generate the bucket name. | <code>string</code> | | <code>null</code> |
|
||||
| [requester_pays](variables.tf#L218) | Enables Requester Pays on a storage bucket. | <code>bool</code> | | <code>null</code> |
|
||||
| [retention_policy](variables.tf#L224) | Bucket retention policy. | <code title="object({ retention_period = number is_locked = optional(bool) })">object({…})</code> | | <code>null</code> |
|
||||
| [storage_class](variables.tf#L233) | Bucket storage class. | <code>string</code> | | <code>"MULTI_REGIONAL"</code> |
|
||||
| [uniform_bucket_level_access](variables.tf#L243) | Allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API). | <code>bool</code> | | <code>true</code> |
|
||||
| [versioning](variables.tf#L249) | Enable versioning, defaults to false. | <code>bool</code> | | <code>false</code> |
|
||||
| [website](variables.tf#L255) | Bucket website. | <code title="object({ main_page_suffix = optional(string) not_found_page = optional(string) })">object({…})</code> | | <code>null</code> |
|
||||
| [name](variables.tf#L164) | Bucket name suffix. | <code>string</code> | ✓ | |
|
||||
| [project_id](variables.tf#L219) | Bucket project id. | <code>string</code> | ✓ | |
|
||||
| [autoclass](variables.tf#L17) | Enable autoclass to automatically transition objects to appropriate storage classes based on their access pattern. If set to true, storage_class must be set to STANDARD. Defaults to false. | <code>bool</code> | | <code>false</code> |
|
||||
| [cors](variables.tf#L23) | CORS configuration for the bucket. Defaults to null. | <code title="object({ origin = optional(list(string)) method = optional(list(string)) response_header = optional(list(string)) max_age_seconds = optional(number) })">object({…})</code> | | <code>null</code> |
|
||||
| [custom_placement_config](variables.tf#L34) | The bucket's custom location configuration, which specifies the individual regions that comprise a dual-region bucket. If the bucket is designated as REGIONAL or MULTI_REGIONAL, the parameters are empty. | <code>list(string)</code> | | <code>null</code> |
|
||||
| [default_event_based_hold](variables.tf#L40) | Enable event based hold to new objects added to specific bucket, defaults to false. | <code>bool</code> | | <code>null</code> |
|
||||
| [encryption_key](variables.tf#L46) | KMS key that will be used for encryption. | <code>string</code> | | <code>null</code> |
|
||||
| [force_destroy](variables.tf#L52) | Optional map to set force destroy keyed by name, defaults to false. | <code>bool</code> | | <code>false</code> |
|
||||
| [iam](variables.tf#L58) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map(list(string))</code> | | <code>{}</code> |
|
||||
| [iam_bindings](variables.tf#L64) | 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.tf#L79) | 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> |
|
||||
| [labels](variables.tf#L94) | Labels to be attached to all buckets. | <code>map(string)</code> | | <code>{}</code> |
|
||||
| [lifecycle_rules](variables.tf#L100) | Bucket lifecycle rule. | <code title="map(object({ action = object({ type = string storage_class = optional(string) }) condition = object({ age = optional(number) created_before = optional(string) custom_time_before = optional(string) days_since_custom_time = optional(number) days_since_noncurrent_time = optional(number) matches_prefix = optional(list(string)) matches_storage_class = optional(list(string)) # STANDARD, MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, ARCHIVE, DURABLE_REDUCED_AVAILABILITY matches_suffix = optional(list(string)) noncurrent_time_before = optional(string) num_newer_versions = optional(number) with_state = optional(string) # "LIVE", "ARCHIVED", "ANY" }) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [location](variables.tf#L149) | Bucket location. | <code>string</code> | | <code>"EU"</code> |
|
||||
| [logging_config](variables.tf#L155) | Bucket logging configuration. | <code title="object({ log_bucket = string log_object_prefix = optional(string) })">object({…})</code> | | <code>null</code> |
|
||||
| [notification_config](variables.tf#L169) | GCS Notification configuration. | <code title="object({ enabled = bool payload_format = string topic_name = string sa_email = string event_types = optional(list(string)) custom_attributes = optional(map(string)) object_name_prefix = optional(string) })">object({…})</code> | | <code>null</code> |
|
||||
| [objects_to_upload](variables.tf#L183) | Objects to be uploaded to bucket. | <code title="map(object({ name = string metadata = optional(map(string)) content = optional(string) source = optional(string) cache_control = optional(string) content_disposition = optional(string) content_encoding = optional(string) content_language = optional(string) content_type = optional(string) event_based_hold = optional(bool) temporary_hold = optional(bool) detect_md5hash = optional(string) storage_class = optional(string) kms_key_name = optional(string) customer_encryption = optional(object({ encryption_algorithm = optional(string) encryption_key = string })) }))">map(object({…}))</code> | | <code>{}</code> |
|
||||
| [prefix](variables.tf#L209) | Optional prefix used to generate the bucket name. | <code>string</code> | | <code>null</code> |
|
||||
| [requester_pays](variables.tf#L224) | Enables Requester Pays on a storage bucket. | <code>bool</code> | | <code>null</code> |
|
||||
| [retention_policy](variables.tf#L230) | Bucket retention policy. | <code title="object({ retention_period = number is_locked = optional(bool) })">object({…})</code> | | <code>null</code> |
|
||||
| [storage_class](variables.tf#L239) | Bucket storage class. | <code>string</code> | | <code>"MULTI_REGIONAL"</code> |
|
||||
| [uniform_bucket_level_access](variables.tf#L249) | Allow using object ACLs (false) or not (true, this is the recommended behavior) , defaults to true (which is the recommended practice, but not the behavior of storage API). | <code>bool</code> | | <code>true</code> |
|
||||
| [versioning](variables.tf#L255) | Enable versioning, defaults to false. | <code>bool</code> | | <code>false</code> |
|
||||
| [website](variables.tf#L261) | Bucket website. | <code title="object({ main_page_suffix = optional(string) not_found_page = optional(string) })">object({…})</code> | | <code>null</code> |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -33,6 +33,13 @@ resource "google_storage_bucket" "bucket" {
|
|||
enabled = var.versioning
|
||||
}
|
||||
|
||||
dynamic "autoclass" {
|
||||
for_each = var.autoclass == null ? [] : [""]
|
||||
content {
|
||||
enabled = var.autoclass
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "website" {
|
||||
for_each = var.website == null ? [] : [""]
|
||||
|
||||
|
|
|
@ -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,6 +14,12 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
variable "autoclass" {
|
||||
description = "Enable autoclass to automatically transition objects to appropriate storage classes based on their access pattern. If set to true, storage_class must be set to STANDARD. Defaults to false."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "cors" {
|
||||
description = "CORS configuration for the bucket. Defaults to null."
|
||||
type = object({
|
||||
|
|
|
@ -571,12 +571,12 @@ module "vpc" {
|
|||
| [project_id](outputs.tf#L58) | Project ID containing the network. Use this when you need to create resources *after* the VPC is fully set up (e.g. subnets created, shared VPC service projects attached, Private Service Networking configured). | |
|
||||
| [self_link](outputs.tf#L71) | Network self link. | |
|
||||
| [subnet_ids](outputs.tf#L83) | Map of subnet IDs keyed by name. | |
|
||||
| [subnet_ips](outputs.tf#L88) | Map of subnet address ranges keyed by name. | |
|
||||
| [subnet_ipv6_external_prefixes](outputs.tf#L95) | Map of subnet external IPv6 prefixes keyed by name. | |
|
||||
| [subnet_regions](outputs.tf#L103) | Map of subnet regions keyed by name. | |
|
||||
| [subnet_secondary_ranges](outputs.tf#L110) | Map of subnet secondary ranges keyed by name. | |
|
||||
| [subnet_self_links](outputs.tf#L121) | Map of subnet self links keyed by name. | |
|
||||
| [subnets](outputs.tf#L126) | Subnet resources. | |
|
||||
| [subnets_proxy_only](outputs.tf#L131) | L7 ILB or L7 Regional LB subnet resources. | |
|
||||
| [subnets_psc](outputs.tf#L136) | Private Service Connect subnet resources. | |
|
||||
| [subnet_ips](outputs.tf#L92) | Map of subnet address ranges keyed by name. | |
|
||||
| [subnet_ipv6_external_prefixes](outputs.tf#L99) | Map of subnet external IPv6 prefixes keyed by name. | |
|
||||
| [subnet_regions](outputs.tf#L107) | Map of subnet regions keyed by name. | |
|
||||
| [subnet_secondary_ranges](outputs.tf#L114) | Map of subnet secondary ranges keyed by name. | |
|
||||
| [subnet_self_links](outputs.tf#L125) | Map of subnet self links keyed by name. | |
|
||||
| [subnets](outputs.tf#L134) | Subnet resources. | |
|
||||
| [subnets_proxy_only](outputs.tf#L143) | L7 ILB or L7 Regional LB subnet resources. | |
|
||||
| [subnets_psc](outputs.tf#L148) | Private Service Connect subnet resources. | |
|
||||
<!-- END TFDOC -->
|
||||
|
|
|
@ -83,6 +83,10 @@ output "self_link" {
|
|||
output "subnet_ids" {
|
||||
description = "Map of subnet IDs keyed by name."
|
||||
value = { for k, v in google_compute_subnetwork.subnetwork : k => v.id }
|
||||
depends_on = [
|
||||
# allows correct destruction of internal application load balancers
|
||||
google_compute_subnetwork.proxy_only
|
||||
]
|
||||
}
|
||||
|
||||
output "subnet_ips" {
|
||||
|
@ -121,11 +125,19 @@ output "subnet_secondary_ranges" {
|
|||
output "subnet_self_links" {
|
||||
description = "Map of subnet self links keyed by name."
|
||||
value = { for k, v in google_compute_subnetwork.subnetwork : k => v.self_link }
|
||||
depends_on = [
|
||||
# allows correct destruction of internal application load balancers
|
||||
google_compute_subnetwork.proxy_only
|
||||
]
|
||||
}
|
||||
|
||||
output "subnets" {
|
||||
description = "Subnet resources."
|
||||
value = { for k, v in google_compute_subnetwork.subnetwork : k => v }
|
||||
depends_on = [
|
||||
# allows correct destruction of internal application load balancers
|
||||
google_compute_subnetwork.proxy_only
|
||||
]
|
||||
}
|
||||
|
||||
output "subnets_proxy_only" {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -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: {}
|
|
@ -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: {}
|
||||
|
|
@ -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: {}
|
|
@ -33,6 +33,8 @@ values:
|
|||
uniform_bucket_level_access: true
|
||||
versioning:
|
||||
- enabled: false
|
||||
autoclass:
|
||||
- enabled: false
|
||||
module.bucket.google_storage_bucket_iam_binding.authoritative["roles/storage.admin"]:
|
||||
bucket: my-bucket
|
||||
condition: []
|
||||
|
|
|
@ -33,6 +33,8 @@ values:
|
|||
uniform_bucket_level_access: true
|
||||
versioning:
|
||||
- enabled: false
|
||||
autoclass:
|
||||
- enabled: false
|
||||
module.bucket.google_storage_bucket_iam_member.bindings["storage-admin-with-delegated_roles"]:
|
||||
bucket: my-bucket
|
||||
condition:
|
||||
|
|
|
@ -33,6 +33,8 @@ values:
|
|||
uniform_bucket_level_access: true
|
||||
versioning:
|
||||
- enabled: false
|
||||
autoclass:
|
||||
- enabled: false
|
||||
module.bucket.google_storage_bucket_iam_binding.bindings["storage-admin-with-delegated_roles"]:
|
||||
bucket: my-bucket
|
||||
condition:
|
||||
|
|
|
@ -34,6 +34,8 @@ values:
|
|||
uniform_bucket_level_access: true
|
||||
versioning:
|
||||
- enabled: true
|
||||
autoclass:
|
||||
- enabled: false
|
||||
|
||||
counts:
|
||||
google_storage_bucket: 1
|
Loading…
Reference in New Issue