Merge branch 'master' into averbuks-tfe-oidc

This commit is contained in:
Aleksandr Averbukh 2022-10-25 20:54:54 +02:00
commit fbbded31e9
163 changed files with 1563 additions and 1053 deletions

View File

@ -33,7 +33,7 @@ env:
TF_VERSION: 1.3.2
jobs:
doc-examples:
examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@ -68,7 +68,7 @@ jobs:
pip install -r tests/requirements.txt
pytest -vv tests/examples
examples:
blueprints:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

1
.gitignore vendored
View File

@ -36,3 +36,4 @@ examples/cloud-operations/adfs/ansible/vars/vars.yaml
examples/cloud-operations/adfs/ansible/gssh.sh
examples/cloud-operations/multi-cluster-mesh-gke-fleet-api/ansible/vars.yaml
examples/cloud-operations/multi-cluster-mesh-gke-fleet-api/ansible/gssh.sh
blueprints/cloud-operations/network-dashboard/cloud-function.zip

View File

@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.
### BLUEPRINTS
- [[#899](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/899)] Static routes monitoring metrics added to network dashboard BP ([maunope](https://github.com/maunope)) <!-- 2022-10-25 11:36:39+00:00 -->
- [[#909](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/909)] GCS2BQ: Move images and templates in sub-folders ([lcaggio](https://github.com/lcaggio)) <!-- 2022-10-25 08:31:25+00:00 -->
- [[#907](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/907)] Fix CloudSQL blueprint ([lcaggio](https://github.com/lcaggio)) <!-- 2022-10-25 07:08:08+00:00 -->
- [[#897](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/897)] Project-factory: allow folder_id to be defined in defaults_file ([Malet](https://github.com/Malet)) <!-- 2022-10-21 08:20:06+00:00 -->
- [[#900](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/900)] Improve net dashboard variables ([juliocc](https://github.com/juliocc)) <!-- 2022-10-20 20:59:31+00:00 -->
- [[#896](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/896)] Network Dashboard: CFv2 and performance improvements ([aurelienlegrand](https://github.com/aurelienlegrand)) <!-- 2022-10-19 16:59:29+00:00 -->
@ -44,6 +47,8 @@ All notable changes to this project will be documented in this file.
### FAST
- [[#911](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/911)] FAST: Additional PGA DNS records ([sruffilli](https://github.com/sruffilli)) <!-- 2022-10-25 12:28:29+00:00 -->
- [[#903](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/903)] Initial replacement for CI/CD stage ([ludoo](https://github.com/ludoo)) <!-- 2022-10-23 17:52:46+00:00 -->
- [[#898](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/898)] Update FAST bootstrap README.md ([juliocc](https://github.com/juliocc)) <!-- 2022-10-19 15:15:36+00:00 -->
- [[#880](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/880)] **incompatible change:** Refactor net-vpc module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) <!-- 2022-10-14 09:02:34+00:00 -->
- [[#875](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/875)] **incompatible change:** Refactor GKE nodepool for Terraform 1.3, refactor GKE blueprints and FAST stage ([ludoo](https://github.com/ludoo)) <!-- 2022-10-12 10:59:37+00:00 -->
@ -62,6 +67,10 @@ All notable changes to this project will be documented in this file.
### MODULES
- [[#916](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/916)] Add support for DNS routing policies ([juliocc](https://github.com/juliocc)) <!-- 2022-10-25 14:20:53+00:00 -->
- [[#918](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/918)] Fix race condition in SimpleNVA ([sruffilli](https://github.com/sruffilli)) <!-- 2022-10-25 13:04:38+00:00 -->
- [[#914](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/914)] **incompatible change:** Update DNS module ([juliocc](https://github.com/juliocc)) <!-- 2022-10-25 10:31:11+00:00 -->
- [[#904](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/904)] Add missing description field ([dsbutler101](https://github.com/dsbutler101)) <!-- 2022-10-21 15:05:11+00:00 -->
- [[#891](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/891)] Add internal_ips output to compute-vm module ([LucaPrete](https://github.com/LucaPrete)) <!-- 2022-10-21 08:38:27+00:00 -->
- [[#890](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/890)] Add auto_delete and instance_redistribution_type to compute-vm and compute-mig modules. ([giovannibaratta](https://github.com/giovannibaratta)) <!-- 2022-10-16 19:19:46+00:00 -->
- [[#883](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/883)] Fix csi-driver, logging and monitoring default values when autopilot … ([danielmarzini](https://github.com/danielmarzini)) <!-- 2022-10-14 15:30:54+00:00 -->

View File

@ -5,7 +5,7 @@ This section **[networking blueprints](./networking/)** that implement core patt
Currently available blueprints:
- **cloud operations** - [Resource tracking and remediation via Cloud Asset feeds](./cloud-operations/asset-inventory-feed-remediation), [Granular Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Granular Cloud DNS IAM for Shared VPC](./cloud-operations/dns-shared-vpc), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Packer image builder](./cloud-operations/packer-image-builder), [On-prem SA key management](./cloud-operations/onprem-sa-key-management), [TCP healthcheck for unmanaged GCE instances](./cloud-operations/unmanaged-instances-healthcheck), [HTTP Load Balancer with Cloud Armor](./cloud-operations/glb_and_armor)
- **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/gcs-to-bq-with-least-privileges/), [Cloud Storage to Bigquery with Cloud Dataflow with least privileges](./data-solutions/gcs-to-bq-with-least-privileges/), [Data Platform Foundations](./data-solutions/data-platform-foundations/), [SQL Server AlwaysOn availability groups blueprint](./data-solutions/sqlserver-alwayson), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion/), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2/)
- **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms/), [Cloud Storage to Bigquery with Cloud Dataflow with least privileges](./data-solutions/gcs-to-bq-with-least-privileges/), [Data Platform Foundations](./data-solutions/data-platform-foundations/), [SQL Server AlwaysOn availability groups blueprint](./data-solutions/sqlserver-alwayson), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion/), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2/)
- **factories** - [The why and the how of resource factories](./factories/README.md)
- **GKE** - [GKE multitenant fleet](./gke/multitenant-fleet/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [Binary Authorization Pipeline](./gke/binauthz/), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api/)
- **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop), [Connecting to on-premise services leveraging PSC and hybrid NEGs](./networking/psc-hybrid/), [decentralized firewall](./networking/decentralized-firewall)

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -46,22 +46,32 @@ The Cloud Function currently tracks usage, limit and utilization of:
- internal forwarding rules for internal L7 load balancers per VPC
- internal forwarding rules for internal L4 load balancers per VPC peering group
- internal forwarding rules for internal L7 load balancers per VPC peering group
- Dynamic routes per VPC
- Dynamic routes per VPC peering group
- Dynamic routes per VPC
- Dynamic routes per VPC peering group
- Static routes per project (VPC drill down is available for usage)
- Static routes per VPC peering group
- IP utilization per subnet (% of IP addresses used in a subnet)
- VPC firewall rules per project (VPC drill down is available for usage)
- Tuples per Firewall Policy
It writes this values to custom metrics in Cloud Monitoring and creates a dashboard to visualize the current utilization of these metrics in Cloud Monitoring.
Note that metrics are created in the cloud-function/metrics.yaml file.
Note that metrics are created in the cloud-function/metrics.yaml file. You can also edit default limits for a specific network in that file. See the example for `vpc_peering_per_network`.
## Assumptions and limitations
- The CF assumes that all VPCs in peering groups are within the same organization, except for PSA peerings
- The CF will only fetch subnet utilization data from the PSA peerings (not the VMs, ILB or routes usage)
- The CF assumes global routing is ON, this impacts dynamic routes usage calculation
- The CF assumes custom routes importing/exporting is ON, this impacts static and dynamic routes usage calculation
- The CF assumes all networks in peering groups have the same global routing and custom routes sharing configuration
You can also edit default limits for a specific network in that file. See the example for `vpc_peering_per_network`.
## Next steps and ideas
In a future release, we could support:
- Static routes per VPC / per VPC peering group
- Google managed VPCs that are peered with PSA (such as Cloud SQL or Memorystore)
- Dynamic routes calculation for VPCs/PPGs with "global routing" set to OFF
- Static routes calculation for projects/PPGs with "custom routes importing/exporting" set to OFF
- Calculations for cross Organization peering groups
If you are interested in this and/or would like to contribute, please contact legranda@google.com.
<!-- BEGIN TFDOC -->

View File

@ -163,6 +163,9 @@ def main(event, context=None):
l4_forwarding_rules_dict = ilb_fwrules.get_forwarding_rules_dict(config, "L4")
l7_forwarding_rules_dict = ilb_fwrules.get_forwarding_rules_dict(config, "L7")
subnet_range_dict = networks.get_subnet_ranges_dict(config)
static_routes_dict = routes.get_static_routes_dict(config)
dynamic_routes_dict = routes.get_dynamic_routes(
config, metrics_dict, limits_dict['dynamic_routes_per_network_limit'])
try:
@ -181,10 +184,12 @@ def main(event, context=None):
ilb_fwrules.get_forwarding_rules_data(
config, metrics_dict, l7_forwarding_rules_dict,
limits_dict['internal_forwarding_rules_l7_limit'], "L7")
routes.get_static_routes_data(config, metrics_dict, static_routes_dict,
project_quotas_dict)
peerings.get_vpc_peering_data(config, metrics_dict,
limits_dict['number_of_vpc_peerings_limit'])
dynamic_routes_dict = routes.get_dynamic_routes(
config, metrics_dict, limits_dict['dynamic_routes_per_network_limit'])
# Per VPC peering group metrics
metrics.get_pgg_data(
@ -207,7 +212,13 @@ def main(event, context=None):
["subnet_ranges_per_peering_group"], subnet_range_dict,
config["limit_names"]["SUBNET_RANGES"],
limits_dict['number_of_subnet_IP_ranges_ppg_limit'])
routes.get_dynamic_routes_ppg(
#static
routes.get_routes_ppg(
config, metrics_dict["metrics_per_peering_group"]
["static_routes_per_peering_group"], static_routes_dict,
limits_dict['static_routes_per_peering_group_limit'])
#dynamic
routes.get_routes_ppg(
config, metrics_dict["metrics_per_peering_group"]
["dynamic_routes_per_peering_group"], dynamic_routes_dict,
limits_dict['dynamic_routes_per_peering_group_limit'])

View File

@ -99,6 +99,19 @@ metrics_per_network:
utilization:
name: dynamic_routes_per_network_utilization
description: Number of Dynamic routes per network - utilization.
#static routes limit is per project, but usage is per network
static_routes_per_project:
usage:
name: static_routes_per_project_vpc_usage
description: Number of Static routes per project and network - usage.
limit:
name: static_routes_per_project_limit
description: Number of Static routes per project - limit.
values:
default_value: 250
utilization:
name: static_routes_per_project_utilization
description: Number of Static routes per project - utilization.
metrics_per_peering_group:
l4_forwarding_rules_per_peering_group:
usage:
@ -160,6 +173,18 @@ metrics_per_peering_group:
utilization:
name: dynamic_routes_per_peering_group_utilization
description: Number of Dynamic routes per peering group - utilization.
static_routes_per_peering_group:
usage:
name: static_routes_per_peering_group_usage
description: Number of Static routes per peering group - usage.
limit:
name: static_routes_per_peering_group_limit
description: Number of Static routes per peering group - limit.
values:
default_value: 300
utilization:
name: static_routes_per_peering_group_utilization
description: Number of Static routes per peering group - utilization.
metrics_per_project:
firewalls:
usage:

View File

@ -26,8 +26,8 @@ from . import metrics, networks, limits
def get_firewall_policies_dict(config: dict):
'''
Calls the Asset Inventory API to get all Firewall Policies under the GCP organization
Calls the Asset Inventory API to get all Firewall Policies under the GCP organization, including children
Ignores monitored projects list: returns all policies regardless of their parent resource
Parameters:
config (dict): The dict containing config like clients and limits
Returns:
@ -55,8 +55,8 @@ def get_firewall_policies_dict(config: dict):
def get_firewal_policies_data(config, metrics_dict, firewall_policies_dict):
'''
Gets the data for VPC Firewall lorem ipsum
Gets the data for VPC Firewall Policies in an organization, including children. All folders are considered,
only projects in the monitored projects list are considered.
Parameters:
config (dict): The dict containing config like clients and limits
metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions.
@ -91,6 +91,9 @@ def get_firewal_policies_data(config, metrics_dict, firewall_policies_dict):
parent_type = re.search("(^\w+)", firewall_policy["parent"]).group(
1) if "parent" in firewall_policy else "projects"
if parent_type == "projects" and parent not in config["monitored_projects"]:
continue
metric_labels = {'parent': parent, 'parent_type': parent_type}
metric_labels["name"] = firewall_policy[

View File

@ -42,7 +42,7 @@ def get_quotas_dict(quotas_list):
def get_quota_project_limit(config, regions=["global"]):
'''
Retrieves limit for a specific project quota
Retrieves quotas for all monitored project in selected regions, default 'global'
Parameters:
project_link (string): Project link.
Returns:
@ -158,7 +158,7 @@ def get_quota_current_limit(config, project_link, metric_name):
def count_effective_limit(config, project_id, network_dict, usage_metric_name,
limit_metric_name, utilization_metric_name,
limit_dict):
limit_dict, timestamp=None):
'''
Calculates the effective limits (using algorithm in the link below) for peering groups and writes data (usage, limit, utilization) to the custom metrics.
Source: https://cloud.google.com/vpc/docs/quota#vpc-peering-effective-limit
@ -171,11 +171,13 @@ def count_effective_limit(config, project_id, network_dict, usage_metric_name,
limit_metric_name (string): Name of the custom metric to be populated for limit per VPC peering group.
utilization_metric_name (string): Name of the custom metric to be populated for utilization per VPC peering group.
limit_dict (dictionary of string:int): Dictionary containing the limit per peering group (either VPC specific or default limit).
timestamp (time): timestamp to be recorded for all points
Returns:
None
'''
timestamp = time.time()
if timestamp == None:
timestamp = time.time()
if network_dict['peerings'] == []:
return

View File

@ -91,7 +91,8 @@ def create_metric(metric_name, description, monitoring_project, config):
def append_data_to_series_buffer(config, metric_name, metric_value,
metric_labels, timestamp=None):
'''
Writes data to Cloud Monitoring custom metrics.
Appends data to Cloud Monitoring custom metrics, using a buffer. buffer is flushed every BUFFER_LEN elements,
any unflushed series is discarded upon function closure
Parameters:
config (dict): The dict containing config like clients and limits
metric_name (string): Name of the metric
@ -139,7 +140,7 @@ def append_data_to_series_buffer(config, metric_name, metric_value,
def flush_series_buffer(config):
'''
writes buffered metrics to Google Cloud Monitoring, empties buffer upon failure
writes buffered metrics to Google Cloud Monitoring, empties buffer upon both failure/success
config (dict): The dict containing config like clients and limits
'''
try:
@ -188,6 +189,7 @@ def get_pgg_data(config, metric_dict, usage_dict, limit_metric, limit_dict):
current_quota_limit_view = customize_quota_view(current_quota_limit)
timestamp = time.time()
# For each network in this GCP project
for network_dict in network_dict_list:
if network_dict['network_id'] == 0:
@ -238,7 +240,7 @@ def get_pgg_data(config, metric_dict, usage_dict, limit_metric, limit_dict):
metric_dict["usage"]["name"],
metric_dict["limit"]["name"],
metric_dict["utilization"]["name"],
limit_dict)
limit_dict, timestamp)
print(
f"Buffered {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project_id}"
)

View File

@ -17,6 +17,7 @@
import time
from collections import defaultdict
from google.protobuf import field_mask_pb2
from . import metrics, networks, limits, peerings, routers
@ -78,8 +79,8 @@ def get_routes_for_network(config, network_link, project_id, routers_dict):
def get_dynamic_routes(config, metrics_dict, limits_dict):
'''
Writes all dynamic routes per VPC to custom metrics.
This function gets the usage, limit and utilization for the dynamic routes per VPC
note: assumes global routing is ON for all VPCs
Parameters:
config (dict): The dict containing config like clients and limits
metrics_dict (dictionary of dictionary of string: string): metrics names and descriptions.
@ -128,10 +129,10 @@ def get_dynamic_routes(config, metrics_dict, limits_dict):
return dynamic_routes_dict
def get_dynamic_routes_ppg(config, metric_dict, usage_dict, limit_dict):
def get_routes_ppg(config, metric_dict, usage_dict, limit_dict):
'''
This function gets the usage, limit and utilization for the dynamic routes per VPC peering group.
This function gets the usage, limit and utilization for the static or dynamic routes per VPC peering group.
note: assumes global routing is ON for all VPCs for dynamic routes, assumes share custom routes is on for all peered networks
Parameters:
config (dict): The dict containing config like clients and limits
metric_dict (dictionary of string: string): Dictionary with the metric names and description, that will be used to populate the metrics
@ -140,11 +141,12 @@ def get_dynamic_routes_ppg(config, metric_dict, usage_dict, limit_dict):
Returns:
None
'''
for project in config["monitored_projects"]:
network_dict_list = peerings.gather_peering_data(config, project)
timestamp = time.time()
for project_id in config["monitored_projects"]:
network_dict_list = peerings.gather_peering_data(config, project_id)
for network_dict in network_dict_list:
network_link = f"https://www.googleapis.com/compute/v1/projects/{project}/global/networks/{network_dict['network_name']}"
network_link = f"https://www.googleapis.com/compute/v1/projects/{project_id}/global/networks/{network_dict['network_name']}"
limit = limits.get_ppg(network_link, limit_dict)
@ -169,11 +171,119 @@ def get_dynamic_routes_ppg(config, metric_dict, usage_dict, limit_dict):
peered_network_dict["usage"] = peered_usage
peered_network_dict["limit"] = peered_limit
limits.count_effective_limit(config, project, network_dict,
limits.count_effective_limit(config, project_id, network_dict,
metric_dict["usage"]["name"],
metric_dict["limit"]["name"],
metric_dict["utilization"]["name"],
limit_dict)
limit_dict, timestamp)
print(
f"Wrote {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project}"
f"Buffered {metric_dict['usage']['name']} for peering group {network_dict['network_name']} in {project_id}"
)
def get_static_routes_dict(config):
'''
Calls the Asset Inventory API to get all static custom routes under the GCP organization.
Parameters:
config (dict): The dict containing config like clients and limits
Returns:
routes_per_vpc_dict (dictionary of string: int): Keys are the network links and values are the number of custom static routes per network.
'''
routes_per_vpc_dict = defaultdict()
usage_dict = defaultdict()
read_mask = field_mask_pb2.FieldMask()
read_mask.FromJsonString('name,versionedResources')
response = config["clients"]["asset_client"].search_all_resources(
request={
"scope": f"organizations/{config['organization']}",
"asset_types": ["compute.googleapis.com/Route"],
"read_mask": read_mask
})
for resource in response:
for versioned in resource.versioned_resources:
static_route = dict()
for field_name, field_value in versioned.resource.items():
static_route[field_name] = field_value
static_route["project_id"] = static_route["network"].split('/')[6]
static_route["network_name"] = static_route["network"].split('/')[-1]
network_link = f"https://www.googleapis.com/compute/v1/projects/{static_route['project_id']}/global/networks/{static_route['network_name']}"
#exclude default vpc and peering routes, dynamic routes are not in Cloud Asset Inventory
if "nextHopPeering" not in static_route and "nextHopNetwork" not in static_route:
if network_link not in routes_per_vpc_dict:
routes_per_vpc_dict[network_link] = dict()
routes_per_vpc_dict[network_link]["project_id"] = static_route[
"project_id"]
routes_per_vpc_dict[network_link]["network_name"] = static_route[
"network_name"]
if static_route["destRange"] not in routes_per_vpc_dict[network_link]:
routes_per_vpc_dict[network_link][static_route["destRange"]] = {}
if "usage" not in routes_per_vpc_dict[network_link]:
routes_per_vpc_dict[network_link]["usage"] = 0
routes_per_vpc_dict[network_link][
"usage"] = routes_per_vpc_dict[network_link]["usage"] + 1
#output a dict with network links and usage only
return {
network_link_out: routes_per_vpc_dict[network_link_out]["usage"]
for network_link_out in routes_per_vpc_dict
}
def get_static_routes_data(config, metrics_dict, static_routes_dict,
project_quotas_dict):
'''
Determines and writes the number of static routes for each VPC in monitored projects, the per project limit and the per project utilization
note: assumes custom routes sharing is ON for all VPCs
Parameters:
config (dict): The dict containing config like clients and limits
metric_dict (dictionary of string: string): Dictionary with the metric names and description, that will be used to populate the metrics
static_routes_dict (dictionary of dictionary: int): Keys are the network links and values are the number of custom static routes per network.
project_quotas_dict (dictionary of string:int): Dictionary with the network link as key and the limit as value.
Returns:
None
'''
timestamp = time.time()
project_usage = {project: 0 for project in config["monitored_projects"]}
#usage is drilled down by network
for network_link in static_routes_dict:
project_id = network_link.split('/')[6]
if (project_id not in config["monitored_projects"]):
continue
network_name = network_link.split('/')[-1]
project_usage[project_id] = project_usage[project_id] + static_routes_dict[
network_link]
metric_labels = {"project": project_id, "network_name": network_name}
metrics.append_data_to_series_buffer(
config, metrics_dict["metrics_per_network"]["static_routes_per_project"]
["usage"]["name"], static_routes_dict[network_link], metric_labels,
timestamp=timestamp)
#limit and utilization are calculated by project
for project_id in project_usage:
current_quota_limit = project_quotas_dict[project_id]['global']["routes"][
"limit"]
if current_quota_limit is None:
print(
f"Could not determine static routes metric for projects/{project_id} due to missing quotas"
)
continue
# limit and utilization are calculted by project
metric_labels = {"project": project_id}
metrics.append_data_to_series_buffer(
config, metrics_dict["metrics_per_network"]["static_routes_per_project"]
["limit"]["name"], current_quota_limit, metric_labels,
timestamp=timestamp)
metrics.append_data_to_series_buffer(
config, metrics_dict["metrics_per_network"]["static_routes_per_project"]
["utilization"]["name"],
project_usage[project_id] / current_quota_limit, metric_labels,
timestamp=timestamp)
return

View File

@ -84,7 +84,7 @@ def get_firewalls_data(config, metrics_dict, project_quotas_dict,
current_quota_limit = project_quotas_dict[project_id]['global']["firewalls"]
if current_quota_limit is None:
print(
f"Could not determine VPC firewal rules to metric for projects/{project_id} due to missing quotas"
f"Could not determine VPC firewal rules metric for projects/{project_id} due to missing quotas"
)
continue

View File

@ -1,6 +1,6 @@
{
"category": "CUSTOM",
"displayName": "quotas_utilization_updated",
"displayName": "quotas_utilization",
"mosaicLayout": {
"columns": 12,
"tiles": [
@ -22,13 +22,11 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l4_utilization\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "1800s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
}
}
@ -64,13 +62,11 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l7_utilization\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
}
}
@ -106,13 +102,11 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/number_of_instances_utilization\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
}
}
@ -148,13 +142,11 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/number_of_vpc_peerings_utilization\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
}
}
@ -190,13 +182,11 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/number_of_active_vpc_peerings_utilization\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_INTERPOLATE"
}
}
@ -232,13 +222,11 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/number_of_subnet_IP_ranges_ppg_utilization\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
}
}
@ -274,13 +262,11 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l4_ppg_utilization\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
}
}
@ -316,13 +302,11 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/internal_forwarding_rules_l7_ppg_utilization\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
}
}
@ -358,7 +342,6 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "3600s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_NEXT_OLDER"
},
"filter": "metric.type=\"custom.googleapis.com/number_of_instances_ppg_utilization\" resource.type=\"global\""
@ -395,7 +378,6 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
},
"filter": "metric.type=\"custom.googleapis.com/dynamic_routes_per_network_utilization\" resource.type=\"global\""
@ -452,7 +434,7 @@
},
"width": 6,
"xPos": 0,
"yPos": 24
"yPos": 32
},
{
"height": 4,
@ -492,7 +474,7 @@
},
"width": 6,
"xPos": 6,
"yPos": 24
"yPos": 32
},
{
"height": 4,
@ -512,7 +494,6 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
},
"filter": "metric.type=\"custom.googleapis.com/firewall_policy_tuples_per_policy_utilization\" resource.type=\"global\""
@ -528,7 +509,7 @@
}
},
"width": 6,
"xPos": 0,
"xPos": 6,
"yPos": 28
},
{
@ -549,7 +530,6 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
},
"filter": "metric.type=\"custom.googleapis.com/ip_addresses_per_subnet_utilization\" resource.type=\"global\""
@ -586,7 +566,6 @@
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_NONE",
"perSeriesAligner": "ALIGN_MEAN"
},
"filter": "metric.type=\"custom.googleapis.com/dynamic_routes_per_peering_group_utilization\" resource.type=\"global\""
@ -604,6 +583,124 @@
"width": 6,
"xPos": 6,
"yPos": 20
},
{
"height": 4,
"widget": {
"title": "static_routes_per_project_vpc_usage",
"xyChart": {
"chartOptions": {
"mode": "COLOR"
},
"dataSets": [
{
"minAlignmentPeriod": "60s",
"plotType": "LINE",
"targetAxis": "Y1",
"timeSeriesQuery": {
"apiSource": "DEFAULT_CLOUD",
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "60s",
"crossSeriesReducer": "REDUCE_SUM",
"groupByFields": [
"metric.label.\"project\""
],
"perSeriesAligner": "ALIGN_MEAN"
},
"filter": "metric.type=\"custom.googleapis.com/static_routes_per_project_vpc_usage\" resource.type=\"global\"",
"secondaryAggregation": {
"alignmentPeriod": "60s",
"perSeriesAligner": "ALIGN_NONE"
}
}
}
}
],
"thresholds": [],
"timeshiftDuration": "0s",
"yAxis": {
"label": "y1Axis",
"scale": "LINEAR"
}
}
},
"width": 6,
"xPos": 0,
"yPos": 24
},
{
"height": 4,
"widget": {
"title": "static_routes_per_ppg_utilization",
"xyChart": {
"chartOptions": {
"mode": "COLOR"
},
"dataSets": [
{
"minAlignmentPeriod": "60s",
"plotType": "LINE",
"targetAxis": "Y1",
"timeSeriesQuery": {
"apiSource": "DEFAULT_CLOUD",
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "60s",
"perSeriesAligner": "ALIGN_MEAN"
},
"filter": "metric.type=\"custom.googleapis.com/static_routes_per_peering_group_utilization\" resource.type=\"global\""
}
}
}
],
"thresholds": [],
"timeshiftDuration": "0s",
"yAxis": {
"label": "y1Axis",
"scale": "LINEAR"
}
}
},
"width": 6,
"xPos": 0,
"yPos": 28
},
{
"height": 4,
"widget": {
"title": "static_routes_per_project_utilization",
"xyChart": {
"chartOptions": {
"mode": "COLOR"
},
"dataSets": [
{
"minAlignmentPeriod": "60s",
"plotType": "LINE",
"targetAxis": "Y1",
"timeSeriesQuery": {
"apiSource": "DEFAULT_CLOUD",
"timeSeriesFilter": {
"aggregation": {
"alignmentPeriod": "60s",
"perSeriesAligner": "ALIGN_MEAN"
},
"filter": "metric.type=\"custom.googleapis.com/static_routes_per_project_utilization\" resource.type=\"global\""
}
}
}
],
"timeshiftDuration": "0s",
"yAxis": {
"label": "y1Axis",
"scale": "LINEAR"
}
}
},
"width": 6,
"xPos": 6,
"yPos": 24
}
]
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -13,7 +13,7 @@ They are meant to be used as minimal but complete starting points to create actu
### Cloud Storage to Bigquery with Cloud Dataflow with least privileges
<a href="./gcs-to-bq-with-least-privileges/" title="Cloud Storage to Bigquery with Cloud Dataflow with least privileges"><img src="./gcs-to-bq-with-least-privileges/diagram.png" align="left" width="280px"></a> This [blueprint](./gcs-to-bq-with-least-privileges/) implements resources required to run GCS to BigQuery Dataflow pipelines. The solution rely on a set of Services account created with the least privileges principle.
<a href="./gcs-to-bq-with-least-privileges/" title="Cloud Storage to Bigquery with Cloud Dataflow with least privileges"><img src="./gcs-to-bq-with-least-privileges/images/diagram.png" align="left" width="280px"></a> This [blueprint](./gcs-to-bq-with-least-privileges/) implements resources required to run GCS to BigQuery Dataflow pipelines. The solution rely on a set of Services account created with the least privileges principle.
<br clear="left">
### Data Platform Foundations

View File

@ -50,7 +50,7 @@ resource "google_sql_user" "service-account" {
for_each = toset(var.data_eng_principals)
project = module.project.project_id
# Omit the .gserviceaccount.com suffix in the email
name = regex("(.+)(gserviceaccount)", module.service-account-sql.email)[0]
name = regex("(.+)(.gserviceaccount)", module.service-account-sql.email)[0]
instance = module.db.name
type = "CLOUD_IAM_SERVICE_ACCOUNT"
}

View File

@ -38,8 +38,8 @@ output "demo_commands" {
description = "Demo commands."
value = {
"01_ssh" = "gcloud compute ssh ${module.test-vm.instance.name} --project ${module.project.name} --zone ${var.regions.primary}-b"
"02_cloud_sql_proxy" = "cloud_sql_proxy -instances=${module.db.connection_name}=tcp:5432 &"
"03_psql" = "psql 'host=127.0.0.1 port=5432 sslmode=disable dbname=${var.postgres_database} user=postgres'"
"02_cloud_sql_proxy" = "cloud_sql_proxy -enable_iam_login -instances=${module.db.connection_name}=tcp:5432 &"
"03_psql" = "psql 'host=127.0.0.1 port=5432 sslmode=disable dbname=${var.postgres_database} user=postgres password=PASSWORD'"
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -23,7 +23,7 @@ Whether youre transferring from another Cloud Service Provider or youre ta
## Architecture
![GCS to BigQuery High-level diagram](diagram.png "GCS to BigQuery High-level diagram")
![GCS to BigQuery High-level diagram](images/diagram.png "GCS to BigQuery High-level diagram")
The main components that we would be setting up are (to learn more about these products, click on the hyperlinks):
@ -61,11 +61,11 @@ __Note__: To grant a user a role, take a look at the [Granting and Revoking Acce
Click on the button below, sign in if required and when the prompt appears, click on “confirm”.
[![Open Cloudshell](shell_button.png)](https://goo.gle/GoDataPipe)
[![Open Cloudshell](images/shell_button.png)](https://goo.gle/GoDataPipe)
This will clone the repository to your cloud shell and a screen like this one will appear:
![cloud_shell](cloud_shell.png)
![cloud_shell](images/cloud_shell.png)
Before you deploy the architecture, make sure you run the following command to move your cloudshell session into your service project:
@ -87,7 +87,7 @@ Before we deploy the architecture, you will need the following information:
2. In the editor, edit the terraform.tfvars.sample file with the variables you gathered in the step above.
![editor](editor.png)
![editor](images/editor.png)
* a. Fill in __data_eng_principals__ with the list of Users or Groups to impersonate service accounts.
@ -105,7 +105,7 @@ Before we deploy the architecture, you will need the following information:
The resource creation will take a few minutes, at the end this is the output you should expect for successful completion along with a list of the created resources:
![output](output.png)
![output](images/output.png)
__Congratulations!__ You have successfully deployed the foundation for running your first ETL pipeline on Google Cloud.
@ -168,16 +168,16 @@ This command will start a dataflow job called test_batch_01 that uses a Dataflow
The expected output is the following:
![second_output](second_output.png)
![second_output](images/second_output.png)
Then, if you navigate to Dataflow on the console, you will see the following:
![dataflow_console](dataflow_console.png)
![dataflow_console](images/dataflow_console.png)
This shows the job you started from the cloudshell is currently running in Dataflow.
If you click on the job name, you can see the job graph created and how every step of the Dataflow pipeline is moving along:
![dataflow_execution](dataflow_execution.png)
![dataflow_execution](images/dataflow_execution.png)
Once the job completes, you can navigate to BigQuery in the console and under __SERVICE_PROJECT_ID__ → datalake → person, you can see the data that was successfully imported into BigQuery through the Dataflow job.

View File

@ -47,7 +47,7 @@ output "command_01_gcs" {
output "command_02_dataflow" {
description = "Command to run Dataflow template impersonating the service account."
value = templatefile("${path.module}/dataflow.tftpl", {
value = templatefile("${path.module}/templates/dataflow.tftpl", {
sa_orch_email = module.service-account-orch.email
project_id = module.project.project_id
region = var.region
@ -68,7 +68,7 @@ output "command_02_dataflow" {
output "command_03_bq" {
description = "BigQuery command to query imported data."
value = templatefile("${path.module}/bigquery.tftpl", {
value = templatefile("${path.module}/templates/bigquery.tftpl", {
project_id = module.project.project_id
bigquery_dataset = module.bigquery-dataset.dataset_id
bigquery_table = module.bigquery-dataset.tables["person"].table_id

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -84,7 +84,7 @@ module "dns-api-prod" {
domain = "googleapis.com."
client_networks = [module.vpc-prod.self_link]
recordsets = {
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
"CNAME *" = { records = ["private.googleapis.com."] }
}
}
@ -96,7 +96,7 @@ module "dns-api-dev" {
domain = "googleapis.com."
client_networks = [module.vpc-dev.self_link]
recordsets = {
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
"CNAME *" = { records = ["private.googleapis.com."] }
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -63,7 +63,7 @@ module "dev-dns-zone" {
domain = "dev.example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { ttl = 300, records = ["127.0.0.1"] }
"A test-r2" = { ttl = 300, records = [module.dev-r2-vm.internal_ip] }
"A localhost" = { records = ["127.0.0.1"] }
"A test-r2" = { records = [module.dev-r2-vm.internal_ip] }
}
}

View File

@ -53,7 +53,7 @@ module "landing-dns-zone" {
domain = "example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { ttl = 300, records = ["127.0.0.1"] }
"A test-r1" = { ttl = 300, records = [module.landing-r1-vm.internal_ip] }
"A localhost" = { records = ["127.0.0.1"] }
"A test-r1" = { records = [module.landing-r1-vm.internal_ip] }
}
}

View File

@ -63,7 +63,7 @@ module "prod-dns-zone" {
domain = "prd.example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { ttl = 300, records = ["127.0.0.1"] }
"A test-r1" = { ttl = 300, records = [module.prod-r1-vm.internal_ip] }
"A localhost" = { records = ["127.0.0.1"] }
"A test-r1" = { records = [module.prod-r1-vm.internal_ip] }
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -169,9 +169,9 @@ module "dns-gcp" {
domain = "gcp.example.org."
client_networks = [module.vpc.self_link]
recordsets = {
"A localhost" = { ttl = 300, records = ["127.0.0.1"] }
"A test-1" = { ttl = 300, records = [module.vm-test1.internal_ip] }
"A test-2" = { ttl = 300, records = [module.vm-test2.internal_ip] }
"A localhost" = { records = ["127.0.0.1"] }
"A test-1" = { records = [module.vm-test1.internal_ip] }
"A test-2" = { records = [module.vm-test2.internal_ip] }
}
}
@ -183,9 +183,9 @@ module "dns-api" {
domain = "googleapis.com."
client_networks = [module.vpc.self_link]
recordsets = {
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
"A private" = { ttl = 300, records = local.vips.private }
"A restricted" = { ttl = 300, records = local.vips.restricted }
"CNAME *" = { records = ["private.googleapis.com."] }
"A private" = { records = local.vips.private }
"A restricted" = { records = local.vips.restricted }
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -218,7 +218,7 @@ module "private-dns-onprem" {
domain = "${var.region}-${module.project.project_id}.cloudfunctions.net."
client_networks = [module.vpc-onprem.self_link]
recordsets = {
"A " = { ttl = 300, records = [module.addresses.psc_addresses[local.psc_name].address] }
"A " = { records = [module.addresses.psc_addresses[local.psc_name].address] }
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -157,8 +157,8 @@ module "host-dns" {
domain = "example.com."
client_networks = [module.vpc-shared.self_link]
recordsets = {
"A localhost" = { ttl = 300, records = ["127.0.0.1"] }
"A bastion" = { ttl = 300, records = [module.vm-bastion.internal_ip] }
"A localhost" = { records = ["127.0.0.1"] }
"A bastion" = { records = [module.vm-bastion.internal_ip] }
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0"
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0"
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -36,7 +36,7 @@ FAST uses YAML-based factories to deploy subnets and firewall rules and, as its
### CI/CD
One of our objectives with FAST is to provide a lightweight reference design for the IaC repositories, and a built-in implementation for running our code in automated pipelines. Our CI/CD approach leverages [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation), and provides sample workflow configurations for several major providers. Refer to the [CI/CD section in the bootstrap stage](stages/00-bootstrap/README.md#cicd) for more details.
One of our objectives with FAST is to provide a lightweight reference design for the IaC repositories, and a built-in implementation for running our code in automated pipelines. Our CI/CD approach leverages [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation), and provides sample workflow configurations for several major providers. Refer to the [CI/CD section in the bootstrap stage](stages/00-bootstrap/README.md#cicd) for more details. We also provide separate [optional small stages](./extras/) to help you configure your CI/CD provider.
## Implementation

View File

@ -0,0 +1,92 @@
# FAST GitHub repository management
This small extra stage allows creation and management of GitHub repositories used to host FAST stage code, including initial population of files and rewriting of module sources.
This stage is designed for quick repository creation in a GitHub organization, and is not suited for medium or long-term repository management especially if you enable initial population of files.
## Initial population caveats
Initial file population of repositories is controlled via the `populate_from` attribute, and needs a bit of care:
- never run this stage gain with the same variables used for population once the repository starts being used, as **Terraform will manage file state and revert any changes at each apply**, which is probably not what you want.
- be mindful when enabling initial population of the modules repository, as the number of resulting files to manage is very close to the GitHub hourly limit for their API
The scenario for which this stage has been designed is one-shot creation and/or population of stage repositories, running it multiple times with different variables and Terraform states if incremental creation is needed for subsequent FAST stages (e.g. GKE, data platform, etc.).
## GitHub provider credentials
A [GitHub token](https://github.com/settings/tokens) is needed to authenticate against their API. The token needs organization-level permissions, like shown in this screenshot:
<p align="center">
<img src="github_token.png" alt="GitHub token scopes.">
</p>
## Variable configuration
The `organization` required variable sets the GitHub organization where repositories will be created, and is used to configure the Terraform provider.
The `repositories` variable is where you configure which repositories to create, whether initial population of files is desired, and which repository is used to host modules.
This is an example that creates repositories for stages 00 and 01, defines an existing repositories as the source for modules, and populates initial files for stages 00, 01, and 02:
```hcl
organization = "ludomagno"
repositories = {
fast_00_bootstrap = {
create_options = {
description = "FAST bootstrap."
features = {
issues = true
}
}
populate_from = "../../stages/00-bootstrap"
}
fast_01_resman = {
create_options = {
description = "FAST resource management."
features = {
issues = true
}
}
populate_from = "../../stages/01-resman"
}
fast_02_networking = {
populate_from = "../../stages/02-networking-peering"
}
fast_modules = {
has_modules = true
}
}
```
The `create_options` repository attribute controls creation: if the attribute is not present, the repository is assumed to be already existing.
Initial population depends on a modules repository being configured, identified by the `has_modules` attribute, and on `populate_from` attributes in each repository where population is required, pointing to the folder holding the files to be committed.
Finally, a `commit_config` variable is optional: it can be used to configure author, email and message used in commits for initial population of files, its defaults are probably fine for most use cases.
## Modules secret
When initial population is configured for a repository, this stage also adds a secret with the private key used to authenticate against the modules repository. This matches the configuration of the GitHub workflow files created for each FAST stage when CI/CD is enabled.
<!-- TFDOC OPTS files:1 -->
<!-- BEGIN TFDOC -->
## Files
| name | description | resources |
|---|---|---|
| [cicd-versions.tf](./cicd-versions.tf) | Provider version. | |
| [main.tf](./main.tf) | Module-level locals and resources. | <code>github_actions_secret</code> · <code>github_repository</code> · <code>github_repository_file</code> · <code>tls_private_key</code> |
| [providers.tf](./providers.tf) | Provider configuration. | |
| [variables.tf](./variables.tf) | Module variables. | |
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [organization](variables.tf#L28) | GitHub organization. | <code>string</code> | ✓ | |
| [commmit_config](variables.tf#L17) | Configure commit metadata. | <code title="object&#40;&#123;&#10; author &#61; optional&#40;string, &#34;FAST loader&#34;&#41;&#10; email &#61; optional&#40;string, &#34;fast-loader&#64;fast.gcp.tf&#34;&#41;&#10; message &#61; optional&#40;string, &#34;FAST initial loading&#34;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [repositories](variables.tf#L33) | Repositories to create. | <code title="map&#40;object&#40;&#123;&#10; create_options &#61; optional&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; auto_merge &#61; optional&#40;bool&#41;&#10; merge_commit &#61; optional&#40;bool&#41;&#10; rebase_merge &#61; optional&#40;bool&#41;&#10; squash_merge &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#10; auto_init &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; features &#61; optional&#40;object&#40;&#123;&#10; issues &#61; optional&#40;bool&#41;&#10; projects &#61; optional&#40;bool&#41;&#10; wiki &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#10; templates &#61; optional&#40;object&#40;&#123;&#10; gitignore &#61; optional&#40;string, &#34;Terraform&#34;&#41;&#10; license &#61; optional&#40;string&#41;&#10; repository &#61; optional&#40;object&#40;&#123;&#10; name &#61; string&#10; owner &#61; string&#10; &#125;&#41;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; visibility &#61; optional&#40;string, &#34;private&#34;&#41;&#10; &#125;&#41;&#41;&#10; has_modules &#61; optional&#40;bool, false&#41;&#10; populate_from &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
<!-- END TFDOC -->

View File

@ -14,10 +14,16 @@
* limitations under the License.
*/
# tfdoc:file:description Output files persistence to automation GCS bucket.
# tfdoc:file:description Provider version.
resource "google_storage_bucket_object" "tfvars" {
bucket = var.automation.outputs_bucket
name = "tfvars/00-bootstrap.auto.tfvars.json"
content = jsonencode(local.tfvars)
terraform {
required_version = ">= 1.3.1"
required_providers {
github = {
source = "integrations/github"
version = "~> 4.0"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,124 @@
/**
* 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 {
_modules_repository = [
for k, v in var.repositories : local.repositories[k] if v.has_modules
]
_repository_files = flatten([
for k, v in var.repositories : [
for f in concat(
[for f in fileset(path.module, "${v.populate_from}/*.md") : f],
[for f in fileset(path.module, "${v.populate_from}/*.tf") : f]
) : {
repository = k
file = f
name = replace(f, "${v.populate_from}/", "")
}
] if v.populate_from != null
])
modules_repository = (
length(local._modules_repository) > 0
? local._modules_repository.0
: null
)
repositories = {
for k, v in var.repositories :
k => v.create_options == null ? k : github_repository.default[k].name
}
repository_files = {
for k in local._repository_files :
"${k.repository}/${k.name}" => k
if !endswith(k.name, ".tf") || (
!startswith(k.name, "0") && k.name != "globals.tf"
)
}
}
resource "github_repository" "default" {
for_each = {
for k, v in var.repositories : k => v if v.create_options != null
}
name = each.key
description = (
each.value.create_options.description != null
? each.value.create_options.description
: "FAST stage ${each.key}."
)
visibility = each.value.create_options.visibility
auto_init = each.value.create_options.auto_init
allow_auto_merge = try(each.value.create_options.allow.auto_merge, null)
allow_merge_commit = try(each.value.create_options.allow.merge_commit, null)
allow_rebase_merge = try(each.value.create_options.allow.rebase_merge, null)
allow_squash_merge = try(each.value.create_options.allow.squash_merge, null)
has_issues = try(each.value.create_options.features.issues, null)
has_projects = try(each.value.create_options.features.projects, null)
has_wiki = try(each.value.create_options.features.wiki, null)
gitignore_template = try(each.value.create_options.templates.gitignore, null)
license_template = try(each.value.create_options.templates.license, null)
dynamic "template" {
for_each = (
try(each.value.create_options.templates.repository, null) != null
? [""]
: []
)
content {
owner = each.value.create_options.templates.repository.owner
repository = each.value.create_options.templates.repository.name
}
}
}
resource "tls_private_key" "default" {
count = local.modules_repository != null ? 1 : 0
algorithm = "ED25519"
}
resource "github_actions_secret" "default" {
for_each = local.modules_repository == null ? {} : {
for k, v in local.repositories :
k => v if(
k != local.modules_repository &&
var.repositories[k].populate_from != null
)
}
repository = local.repositories[local.modules_repository]
secret_name = "CICD_MODULES_KEY"
plaintext_value = tls_private_key.default.0.private_key_openssh
}
resource "github_repository_file" "default" {
for_each = (
local.modules_repository == null ? {} : local.repository_files
)
repository = local.repositories[each.value.repository]
branch = "main"
file = each.value.name
content = (
endswith(each.value.name, ".tf") && local.modules_repository != null
? replace(
file(each.value.file),
"/source\\s*=\\s*\"../../../",
"source = \"git@github.com:${var.organization}/${local.modules_repository}.git/"
)
: file(each.value.file)
)
commit_message = "${var.commmit_config.message} (${each.value.name})"
commit_author = var.commmit_config.author
commit_email = var.commmit_config.email
overwrite_on_create = true
}

View File

@ -14,3 +14,8 @@
* limitations under the License.
*/
# tfdoc:file:description Provider configuration.
provider "github" {
owner = var.organization
}

View File

@ -0,0 +1,72 @@
/**
* 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 "commmit_config" {
description = "Configure commit metadata."
type = object({
author = optional(string, "FAST loader")
email = optional(string, "fast-loader@fast.gcp.tf")
message = optional(string, "FAST initial loading")
})
default = {}
nullable = false
}
variable "organization" {
description = "GitHub organization."
type = string
}
variable "repositories" {
description = "Repositories to create."
type = map(object({
create_options = optional(object({
allow = optional(object({
auto_merge = optional(bool)
merge_commit = optional(bool)
rebase_merge = optional(bool)
squash_merge = optional(bool)
}))
auto_init = optional(bool)
description = optional(string)
features = optional(object({
issues = optional(bool)
projects = optional(bool)
wiki = optional(bool)
}))
templates = optional(object({
gitignore = optional(string, "Terraform")
license = optional(string)
repository = optional(object({
name = string
owner = string
}))
}), {})
visibility = optional(string, "private")
}))
has_modules = optional(bool, false)
populate_from = optional(string)
}))
default = {}
nullable = true
validation {
condition = alltrue([
for k, v in var.repositories :
try(regex("^[a-zA-Z0-9_.]+$", k), null) != null
])
error_message = "Repository names must match '^[a-zA-Z0-9_.]+$'."
}
}

5
fast/extras/README.md Normal file
View File

@ -0,0 +1,5 @@
# FAST extra stages
This folder contains additional helper stages for FAST, which can be used to simplify specific operational tasks:
- [GitHub repository management](./00-cicd-github/)

View File

@ -87,7 +87,7 @@ This stage also implements initial support for two interrelated features
Workload Identity Federation support allows configuring external providers independently from CI/CD, and offers predefined attributes for a few well known ones (more can be easily added by editing the `identity-providers.tf` file). Once providers have been configured their names are passed to the following stages via interface outputs, and can be leveraged to set up access or impersonation in IAM bindings.
CI/CD support is fully implemented for GitHub, Gitlab support is almost complete and will be published soon, and Cloud Source Repositories / Cloud Build will follow.
CI/CD support is fully implemented for GitHub, Gitlab, and Cloud Source Repositories / Cloud Build. For GitHub, we also offer a [separate supporting setup](../../extras/00-cicd-github/) to quickly create / configure repositories.
<!-- TODO: add a general overview of our design -->
@ -176,7 +176,7 @@ Before the first run, the following IAM groups must exist to allow IAM bindings
- `gcp-organization-admins`
- `gcp-security-admins`
You can refer to [this animated image](./groups.gif) for a step by step on group creation.
You can refer to [this animated image](./groups.gif) for a step by step on group creation.
Please note that FAST also supports an additional group for users with permissions to create support tickets and view logging and monitoring data. To remain consistent with the [Google Cloud Enterprise Checklist](https://cloud.google.com/docs/enterprise/setup-checklist) we map these permissions to the `gcp-devops` by default. However, we recommend creating a dedicated `gcp-support` group and updating the `groups` variable with the right value.

View File

@ -1,206 +0,0 @@
# CI/CD bootstrap
The primary purpose of this stage is to set up your CI/CD project structure automatically, with most of the necessary configuration to run the pipelines out of the box.
## How to run this stage
This stage is meant to be executed after the [bootstrap](../00-bootstrap) stage has run, as it leverages the automation service account and bucket created there.
The entire stage is optional, you may also choose to create your repositories manually.
### Providers configuration
The default way of making sure you have the right permissions, is to use the identity of the service account pre-created for this stage during bootstrap, and that you are a member of the group that can impersonate it via provider-level configuration (`gcp-devops` or `organization-admins`).
To simplify setup, the previous stage pre-configures a valid providers file in its output, and optionally writes it to a local file if the `outputs_location` variable is set to a valid path.
If you have set a valid value for `outputs_location` in the bootstrap stage (see the [bootstrap stage README](../00-bootstrap/#output-files-and-cross-stage-variables) for more details), simply link the relevant `providers.tf` file from this stage's folder in the path you specified:
```bash
# `outputs_location` is set to `~/fast-config`
ln -s ~/fast-config/providers/00-cicd-providers.tf .
```
If you have not configured `outputs_location` in bootstrap, you can derive the providers file from that stage's outputs:
```bash
cd ../00-bootstrap
terraform output -json providers | jq -r '.["00-cicd"]' \
> ../00-cicd/providers.tf
```
If you want to continue to rely on `outputs_location` logic, create a `terraform.tfvars` file and configure it as described [here](../00-bootstrap/#output-files-and-cross-stage-variables).
### Variable configuration
There are two broad sets of variables you will need to fill in:
- variables shared by other stages (org id, billing account id, etc.), or derived from a resource managed by a different stage (folder id, automation project id, etc.)
- variables specific to resources managed by this stage
To avoid the tedious job of filling in the first group of variable with values derived from other stages' outputs, the same mechanism used above for the provider configuration can be used to leverage pre-configured `.tfvars` files.
If you configured a valid path for `outputs_location` in the bootstrap stage, simply link the relevant `terraform-*.auto.tfvars.json` files from the outputs folder. For this stage, you need the `.tfvars` file compiled manually for the bootstrap stage, and the one generated by it:
```bash
# `outputs_location` is set to `~/fast-config`
ln -s ~/fast-config/tfvars/00-bootstrap.auto.tfvars.json .
# also copy the tfvars file used for the bootstrap stage
cp ../00-bootstrap/terraform.tfvars .
```
A second set of variables is specific to this stage, they are all optional so if you need to customize them, create an extra `terraform.tfvars` file or add them to the file copied from bootstrap.
Refer to the [Variables](#variables) table at the bottom of this document, for a full list of variables, their origin (e.g. a stage or specific to this one), and descriptions explaining their meaning. The sections below also describe some of the possible customizations.
### CI/CD systems
#### Gitlab
To configure Gitlab, add the following variable:
```hcl
gitlab = {
url = "https://gitlab.com" # Or self-hosted URL
project_visibility = "private"
shared_runners_enabled = true
}
```
Also set `GITLAB_TOKEN` to a token that has appropriate permissions.
#### GitHub
To configure GitHub, add the following variable:
```hcl
github = {
url = null # Or GitHub Enterprise base URL
visibility = "private"
}
```
Also set `GITHUB_TOKEN` to a token that has appropriate permissions.
### CI/CD repositories
While the other stages create the necessary supporting structure for their CI/CD pipelines, like service accounts
and such, the `00-cicd` stage creates all the repositories in your CI/CD system through automation. Its
configuration is essentially a combination of all the `cicd_repositories` variables of the other stages
plus additional CI/CD system specific configuration information.
This is an example of configuring the repositories in this stage.
```hcl
cicd_repositories = {
bootstrap = {
branch = null
identity_provider = "github-sample"
name = "my-gh-org/fast-bootstrap"
description = "Google Cloud bootstrapping"
type = "github"
create = true
create_group = true
}
cicd = {
branch = null
identity_provider = "github-sample"
name = "my-gh-org/fast-cicd"
description = "Fabric FAST CI/CD setup"
type = "github"
create = true
create_group = true
}
resman = {
branch = "main"
identity_provider = "github-sample"
name = "my-gh-org/fast-resman"
description = "Google Cloud resource management"
type = "github"
create = true
create_group = true
}
networking = {
branch = "main"
identity_provider = "github-sample"
name = "my-gh-org/fast-networking"
description = "Google Cloud networking setup"
type = "github"
create = true
create_group = true
}
security = {
branch = "main"
identity_provider = "github-sample"
description = "Google Cloud security settings"
name = "my-gh-org/fast-security"
type = "github"
create = true
create_group = true
}
data-platform = {
branch = "main"
identity_provider = "github-sample"
name = "my-gh-org/fast-data-platform"
description = "Google Cloud data platform"
type = "github"
create = true
create_group = true
}
project-factory = {
branch = "main"
identity_provider = "github-sample"
name = "my-gh-org/fast-project-factory"
description = "Google Cloud project factory"
type = "github"
create = true
create_group = true
}
}
```
The `type` attribute can be set to one of the supported repository types: `github` or `gitlab`.
Once the stage is applied the generated output files will contain pre-configured workflow files for each repository, that will use Workload Identity Federation via a dedicated service account for each repository to impersonate the automation service account for the stage.
Once done, you can run this stage:
```bash
terraform init
terraform apply
```
<!-- TFDOC OPTS files:1 show_extra:1 -->
<!-- BEGIN TFDOC -->
## Files
| name | description | resources |
|---|---|---|
| [cicd.tf](./cicd.tf) | None | <code>tls_private_key</code> |
| [github.tf](./github.tf) | None | <code>github_actions_secret</code> · <code>github_repository</code> |
| [gitlab.tf](./gitlab.tf) | None | <code>gitlab_group</code> · <code>gitlab_project</code> · <code>gitlab_project_variable</code> |
| [main.tf](./main.tf) | Module-level locals and resources. | |
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | <code>local_file</code> |
| [outputs-gcs.tf](./outputs-gcs.tf) | Output files persistence to automation GCS bucket. | <code>google_storage_bucket_object</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [variables.tf](./variables.tf) | Module variables. | |
| [versions.tf](./versions.tf) | Version pins. | |
## Variables
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
| [automation](variables.tf#L17) | Automation resources created by the bootstrap stage. | <code title="object&#40;&#123;&#10; outputs_bucket &#61; string&#10; project_id &#61; string&#10; project_number &#61; string&#10; federated_identity_pool &#61; string&#10; federated_identity_providers &#61; map&#40;object&#40;&#123;&#10; issuer &#61; string&#10; issuer_uri &#61; string&#10; name &#61; string&#10; principal_tpl &#61; string&#10; principalset_tpl &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | <code>00-bootstrap</code> |
| [cicd_repositories](variables.tf#L35) | CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | <code title="object&#40;&#123;&#10; bootstrap &#61; object&#40;&#123;&#10; branch &#61; string&#10; name &#61; string&#10; description &#61; string&#10; type &#61; string&#10; create &#61; bool&#10; create_group &#61; bool&#10; &#125;&#41;&#10; resman &#61; object&#40;&#123;&#10; branch &#61; string&#10; name &#61; string&#10; description &#61; string&#10; type &#61; string&#10; create &#61; bool&#10; create_group &#61; bool&#10; &#125;&#41;&#10; networking &#61; object&#40;&#123;&#10; branch &#61; string&#10; name &#61; string&#10; description &#61; string&#10; type &#61; string&#10; create &#61; bool&#10; create_group &#61; bool&#10; &#125;&#41;&#10; security &#61; object&#40;&#123;&#10; branch &#61; string&#10; name &#61; string&#10; description &#61; string&#10; type &#61; string&#10; create &#61; bool&#10; create_group &#61; bool&#10; &#125;&#41;&#10; data-platform &#61; object&#40;&#123;&#10; branch &#61; string&#10; name &#61; string&#10; description &#61; string&#10; type &#61; string&#10; create &#61; bool&#10; create_group &#61; bool&#10; &#125;&#41;&#10; project-factory &#61; object&#40;&#123;&#10; branch &#61; string&#10; name &#61; string&#10; description &#61; string&#10; type &#61; string&#10; create &#61; bool&#10; create_group &#61; bool&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | |
| [custom_roles](variables.tf#L132) | Custom roles defined at the org level, in key => id format. | <code title="object&#40;&#123;&#10; service_project_network_admin &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | <code>00-bootstrap</code> |
| [github](variables.tf#L120) | GitHub settings | <code title="object&#40;&#123;&#10; url &#61; string&#10; visibility &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; url &#61; null&#10; visibility &#61; &#34;private&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [gitlab](variables.tf#L106) | Gitlab settings | <code title="object&#40;&#123;&#10; url &#61; string&#10; project_visibility &#61; string&#10; shared_runners_enabled &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; url &#61; &#34;https:&#47;&#47;gitlab.com&#34;&#10; project_visibility &#61; &#34;private&#34;&#10; shared_runners_enabled &#61; true&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [outputs_location](variables.tf#L141) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable | <code>string</code> | | <code>null</code> | |
## Outputs
| name | description | sensitive | consumers |
|---|---|:---:|---|
| [tfvars](outputs.tf#L30) | Terraform variable files for the following stages. | ✓ | |
<!-- END TFDOC -->

View File

@ -1,38 +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 {
supported_cicd_systems = ["gitlab", "github", "sourcerepo"]
cicd_repositories = {
for k, v in coalesce(var.cicd_repositories, {}) : k => merge(v,
{ group = join("/", slice(split("/", v.name), 0, length(split("/", v.name)) - 1)) },
{ name = element(split("/", v.name), length(split("/", v.name)) - 1) },
{ create_group = try(v.create_group, true) })
if(
v != null
&&
contains(local.supported_cicd_systems, try(v.type, ""))
)
}
cicd_repositories_by_system = { for system in local.supported_cicd_systems : system => {
for k, v in local.cicd_repositories : k => v if v.type == system
}
}
}
resource "tls_private_key" "cicd-modules-key" {
algorithm = "ED25519"
}

View File

@ -1,51 +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 {
github_groups = distinct([for k, v in local.cicd_repositories_by_system["github"] : v.group])
}
provider "github" {
base_url = var.github.url
owner = local.github_groups[0]
}
data "github_organization" "organization" {
for_each = toset(local.github_groups)
name = each.value
}
data "github_repository" "repositories" {
for_each = { for name, repo in local.cicd_repositories_by_system["github"] : name => repo if !try(repo.create, true) }
full_name = format("%s/%s", each.value.group, each.value.name)
}
resource "github_repository" "repositories" {
for_each = { for name, repo in local.cicd_repositories_by_system["github"] : name => repo if try(repo.create, true) }
name = each.value.name
description = each.value.description
visibility = var.github.visibility
}
resource "github_actions_secret" "actions-modules-key" {
for_each = { for name, repo in local.cicd_repositories_by_system["github"] : name => repo }
repository = try(each.value.create, true) ? github_repository.repositories[each.key].name : data.github_repository.repositories[each.key].name
secret_name = "CICD_MODULES_KEY"
plaintext_value = tls_private_key.cicd-modules-key.private_key_openssh
}

View File

@ -1,70 +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 {
gitlab_create_groups = distinct([for k, v in local.cicd_repositories_by_system["gitlab"] : v.group if try(v.create_group, false)])
gitlab_existing_groups = distinct([for k, v in local.cicd_repositories_by_system["gitlab"] : v.group if !try(v.create_group, false)])
}
provider "gitlab" {
base_url = var.gitlab.url
}
data "gitlab_group" "group" {
for_each = toset(local.gitlab_existing_groups)
full_path = each.value
}
data "gitlab_project" "projects" {
for_each = { for name, repo in local.cicd_repositories_by_system["gitlab"] : name => repo if !try(repo.create, true) }
id = format("%s/%s", each.value.group, each.value.name)
}
resource "gitlab_group" "group" {
for_each = toset(local.gitlab_create_groups)
name = each.value
path = each.value
description = "Cloud Foundation Fabric FAST: github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/fast/"
}
resource "gitlab_project" "projects" {
for_each = { for name, repo in local.cicd_repositories_by_system["gitlab"] : name => repo if try(repo.create, true) }
name = each.value.name
namespace_id = each.value.create_group ? gitlab_group.group[each.value.group].id : data.gitlab_group.group[each.value.group].id
description = each.value.description
visibility_level = var.gitlab.project_visibility
shared_runners_enabled = var.gitlab.shared_runners_enabled
auto_devops_enabled = false
}
resource "gitlab_project_variable" "project-modules-key" {
for_each = { for name, repo in local.cicd_repositories_by_system["gitlab"] : name => repo }
project = try(each.value.create, true) ? gitlab_project.projects[each.key].id : data.gitlab_project.projects[each.key].id
key = "CICD_MODULES_KEY"
value = base64encode(tls_private_key.cicd-modules-key.private_key_openssh)
protected = false
masked = true
variable_type = "env_var"
}

View File

@ -1,24 +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.
*/
# tfdoc:file:description Output files persistence to local filesystem.
resource "local_file" "tfvars" {
for_each = var.outputs_location == null ? {} : { 1 = 1 }
file_permission = "0644"
filename = "${pathexpand(var.outputs_location)}/tfvars/00-cicd.auto.tfvars.json"
content = jsonencode(local.tfvars)
}

View File

@ -1,34 +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 {
gitlab_cicd_https = { for k, v in local.cicd_repositories_by_system["gitlab"] : k => v.create ? gitlab_project.projects[k].http_url_to_repo : data.gitlab_project.projects[k].http_url_to_repo }
gitlab_cicd_ssh = { for k, v in local.cicd_repositories_by_system["gitlab"] : k => v.create ? gitlab_project.projects[k].ssh_url_to_repo : data.gitlab_project.projects[k].ssh_url_to_repo }
github_cicd_https = { for k, v in local.cicd_repositories_by_system["github"] : k => v.create ? github_repository.repositories[k].http_clone_url : data.github_repository.repositories[k].http_clone_url }
github_cicd_ssh = { for k, v in local.cicd_repositories_by_system["github"] : k => v.create ? github_repository.repositories[k].git_clone_url : data.github_repository.repositories[k].git_clone_url }
tfvars = {
cicd_repositories = merge(local.cicd_repositories_by_system["gitlab"], local.cicd_repositories_by_system["github"])
cicd_ssh_urls = merge(local.gitlab_cicd_ssh, local.github_cicd_ssh)
cicd_https_urls = merge(local.gitlab_cicd_https, local.github_cicd_https)
}
}
output "tfvars" {
description = "Terraform variable files for the following stages."
sensitive = true
value = local.tfvars
}

View File

@ -1,145 +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 "automation" {
# tfdoc:variable:source 00-bootstrap
description = "Automation resources created by the bootstrap stage."
type = object({
outputs_bucket = string
project_id = string
project_number = string
federated_identity_pool = string
federated_identity_providers = map(object({
issuer = string
issuer_uri = string
name = string
principal_tpl = string
principalset_tpl = string
}))
})
}
variable "cicd_repositories" {
description = "CI/CD repository configuration. Identity providers reference keys in the `federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed."
type = object({
bootstrap = object({
branch = string
name = string
description = string
type = string
create = bool
create_group = bool
})
resman = object({
branch = string
name = string
description = string
type = string
create = bool
create_group = bool
})
networking = object({
branch = string
name = string
description = string
type = string
create = bool
create_group = bool
})
security = object({
branch = string
name = string
description = string
type = string
create = bool
create_group = bool
})
data-platform = object({
branch = string
name = string
description = string
type = string
create = bool
create_group = bool
})
project-factory = object({
branch = string
name = string
description = string
type = string
create = bool
create_group = bool
})
})
default = null
validation {
condition = alltrue([
for k, v in coalesce(var.cicd_repositories, {}) :
v == null || try(v.name, null) != null
])
error_message = "Non-null repositories need a non-null name."
}
validation {
condition = alltrue([
for k, v in coalesce(var.cicd_repositories, {}) :
v == null || (
contains(["github", "gitlab", "sourcerepo"], coalesce(try(v.type, null), "null"))
)
])
error_message = "Invalid repository type, supported types: 'github' 'gitlab' or 'sourcerepo'."
}
}
variable "gitlab" {
description = "Gitlab settings"
type = object({
url = string
project_visibility = string
shared_runners_enabled = bool
})
default = {
url = "https://gitlab.com"
project_visibility = "private"
shared_runners_enabled = true
}
}
variable "github" {
description = "GitHub settings"
type = object({
url = string
visibility = string
})
default = {
url = null
visibility = "private"
}
}
variable "custom_roles" {
# tfdoc:variable:source 00-bootstrap
description = "Custom roles defined at the org level, in key => id format."
type = object({
service_project_network_admin = string
})
default = null
}
variable "outputs_location" {
description = "Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable"
type = string
default = null
}

View File

@ -1,38 +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
#
# https://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.
terraform {
required_version = ">= 1.3.1"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
}
github = {
source = "integrations/github"
version = "~> 4.0"
}
gitlab = {
source = "gitlabhq/gitlab"
version = ">= 3.16.1"
}
}
}

View File

@ -172,6 +172,13 @@ DNS configuration is further centralized by leveraging peering zones, so that
- the hub/landing Cloud DNS hosts configurations for on-prem forwarding, Google API domains, and the top-level private zone/s (e.g. gcp.example.com)
- the spokes Cloud DNS host configurations for the environment-specific domains (e.g. prod.gcp.example.com), which are bound to the hub/landing leveraging [cross-project binding](https://cloud.google.com/dns/docs/zones/zones-overview#cross-project_binding); a peering zone for the `.` (root) zone is then created on each spoke, delegating all DNS resolution to hub/landing.
- Private Google Access is enabled for a selection of the [supported domains](https://cloud.google.com/vpc/docs/configure-private-google-access#domain-options), namely
- `private.googleapis.com`
- `restricted.googleapis.com`
- `gcr.io`
- `packages.cloud.google.com`
- `pkg.dev`
- `pki.goog`
To complete the configuration, the 35.199.192.0/19 range should be routed to the VPN tunnels from on-premises, and the following names should be configured for DNS forwarding to cloud:

View File

@ -26,7 +26,7 @@ module "dev-dns-private-zone" {
domain = "dev.gcp.example.com."
client_networks = [module.landing-trusted-vpc.self_link, module.landing-untrusted-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}

View File

@ -55,11 +55,11 @@ module "gcp-example-dns-private-zone" {
module.landing-trusted-vpc.self_link
]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}
# Google API zone to trigger Private Access
# Google APIs
module "googleapis-private-zone" {
source = "../../../modules/dns"
@ -72,12 +72,84 @@ module "googleapis-private-zone" {
module.landing-trusted-vpc.self_link
]
recordsets = {
"A private" = { type = "A", ttl = 300, records = [
"A private" = { records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"A restricted" = { type = "A", ttl = 300, records = [
"A restricted" = { records = [
"199.36.153.4", "199.36.153.5", "199.36.153.6", "199.36.153.7"
] }
"CNAME *" = { type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }
"CNAME *" = { records = ["private.googleapis.com."] }
}
}
module "gcrio-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "gcr-io"
domain = "gcr.io."
client_networks = [
module.landing-untrusted-vpc.self_link,
module.landing-trusted-vpc.self_link
]
recordsets = {
"A gcr.io." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "packages-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "packages-cloud"
domain = "packages.cloud.google.com."
client_networks = [
module.landing-untrusted-vpc.self_link,
module.landing-trusted-vpc.self_link
]
recordsets = {
"A packages.cloud.google.com." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "pkgdev-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "pkg-dev"
domain = "pkg.dev."
client_networks = [
module.landing-untrusted-vpc.self_link,
module.landing-trusted-vpc.self_link
]
recordsets = {
"A pkg.dev." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "pkigoog-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "pki-goog"
domain = "pki.goog."
client_networks = [
module.landing-untrusted-vpc.self_link,
module.landing-trusted-vpc.self_link
]
recordsets = {
"A pki.goog." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}

View File

@ -26,7 +26,7 @@ module "prod-dns-private-zone" {
domain = "prod.gcp.example.com."
client_networks = [module.landing-trusted-vpc.self_link, module.landing-untrusted-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}

View File

@ -102,6 +102,13 @@ DNS configuration is further centralized by leveraging peering zones, so that
- the hub/landing Cloud DNS hosts configurations for on-prem forwarding, Google API domains, and the top-level private zone/s (e.g. gcp.example.com)
- the spokes Cloud DNS host configurations for the environment-specific domains (e.g. prod.gcp.example.com), which are bound to the hub/landing leveraging [cross-project binding](https://cloud.google.com/dns/docs/zones/zones-overview#cross-project_binding); a peering zone for the `.` (root) zone is then created on each spoke, delegating all DNS resolution to hub/landing.
- Private Google Access is enabled for a selection of the [supported domains](https://cloud.google.com/vpc/docs/configure-private-google-access#domain-options), namely
- `private.googleapis.com`
- `restricted.googleapis.com`
- `gcr.io`
- `packages.cloud.google.com`
- `pkg.dev`
- `pki.goog`
To complete the configuration, the 35.199.192.0/19 range should be routed on the VPN tunnels from on-prem, and the following names configured for DNS forwarding to cloud:

View File

@ -26,7 +26,7 @@ module "dev-dns-private-zone" {
domain = "dev.gcp.example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}

View File

@ -46,11 +46,11 @@ module "gcp-example-dns-private-zone" {
domain = "gcp.example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}
# Google API zone to trigger Private Access
# Google APIs
module "googleapis-private-zone" {
source = "../../../modules/dns"
@ -60,12 +60,72 @@ module "googleapis-private-zone" {
domain = "googleapis.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A private" = { type = "A", ttl = 300, records = [
"A private" = { records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"A restricted" = { type = "A", ttl = 300, records = [
"A restricted" = { records = [
"199.36.153.4", "199.36.153.5", "199.36.153.6", "199.36.153.7"
] }
"CNAME *" = { type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }
"CNAME *" = { records = ["private.googleapis.com."] }
}
}
module "gcrio-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "gcr-io"
domain = "gcr.io."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A gcr.io." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "packages-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "packages-cloud"
domain = "packages.cloud.google.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A packages.cloud.google.com." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "pkgdev-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "pkg-dev"
domain = "pkg.dev."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A pkg.dev." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "pkigoog-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "pki-goog"
domain = "pki.goog."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A pki.goog." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}

View File

@ -26,7 +26,7 @@ module "prod-dns-private-zone" {
domain = "prod.gcp.example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}

View File

@ -69,6 +69,13 @@ DNS often goes hand in hand with networking, especially on GCP where Cloud DNS z
- on-prem to cloud via private zones for cloud-managed domains, and an [inbound policy](https://cloud.google.com/dns/docs/server-policies-overview#dns-server-policy-in) used as forwarding target or via delegation (requires some extra configuration) from on-prem DNS resolvers
- cloud to on-prem via forwarding zones for the on-prem managed domains
- Private Google Access is enabled for a selection of the [supported domains](https://cloud.google.com/vpc/docs/configure-private-google-access#domain-options), namely
- `private.googleapis.com`
- `restricted.googleapis.com`
- `gcr.io`
- `packages.cloud.google.com`
- `pkg.dev`
- `pki.goog`
To complete the configuration, the 35.199.192.0/19 range should be routed on the VPN tunnels from on-prem, and the following names configured for DNS forwarding to cloud:

View File

@ -26,7 +26,7 @@ module "dev-dns-private-zone" {
domain = "dev.gcp.example.com."
client_networks = [module.dev-spoke-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}
@ -50,6 +50,8 @@ module "dev-reverse-10-dns-forwarding" {
forwarders = { for ip in var.dns.dev : ip => null }
}
# Google APIs
module "dev-googleapis-private-zone" {
source = "../../../modules/dns"
project_id = module.dev-spoke-project.project_id
@ -58,12 +60,72 @@ module "dev-googleapis-private-zone" {
domain = "googleapis.com."
client_networks = [module.dev-spoke-vpc.self_link]
recordsets = {
"A private" = { type = "A", ttl = 300, records = [
"A private" = { records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"A restricted" = { type = "A", ttl = 300, records = [
"A restricted" = { records = [
"199.36.153.4", "199.36.153.5", "199.36.153.6", "199.36.153.7"
] }
"CNAME *" = { type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }
"CNAME *" = { records = ["private.googleapis.com."] }
}
}
module "dev-gcrio-private-zone" {
source = "../../../modules/dns"
project_id = module.dev-spoke-project.project_id
type = "private"
name = "gcr-io"
domain = "gcr.io."
client_networks = [module.dev-spoke-vpc.self_link]
recordsets = {
"A gcr.io." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "dev-packages-private-zone" {
source = "../../../modules/dns"
project_id = module.dev-spoke-project.project_id
type = "private"
name = "packages-cloud"
domain = "packages.cloud.google.com."
client_networks = [module.dev-spoke-vpc.self_link]
recordsets = {
"A packages.cloud.google.com." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "dev-pkgdev-private-zone" {
source = "../../../modules/dns"
project_id = module.dev-spoke-project.project_id
type = "private"
name = "pkg-dev"
domain = "pkg.dev."
client_networks = [module.dev-spoke-vpc.self_link]
recordsets = {
"A pkg.dev." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "dev-pkigoog-private-zone" {
source = "../../../modules/dns"
project_id = module.dev-spoke-project.project_id
type = "private"
name = "pki-goog"
domain = "pki.goog."
client_networks = [module.dev-spoke-vpc.self_link]
recordsets = {
"A pki.goog." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}

View File

@ -26,7 +26,7 @@ module "prod-dns-private-zone" {
domain = "prod.gcp.example.com."
client_networks = [module.prod-spoke-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}
@ -50,6 +50,7 @@ module "prod-reverse-10-dns-forwarding" {
forwarders = { for ip in var.dns.prod : ip => null }
}
# Google APIs
module "prod-googleapis-private-zone" {
source = "../../../modules/dns"
@ -59,12 +60,72 @@ module "prod-googleapis-private-zone" {
domain = "googleapis.com."
client_networks = [module.prod-spoke-vpc.self_link]
recordsets = {
"A private" = { type = "A", ttl = 300, records = [
"A private" = { records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"A restricted" = { type = "A", ttl = 300, records = [
"A restricted" = { records = [
"199.36.153.4", "199.36.153.5", "199.36.153.6", "199.36.153.7"
] }
"CNAME *" = { type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }
"CNAME *" = { records = ["private.googleapis.com."] }
}
}
module "prod-gcrio-private-zone" {
source = "../../../modules/dns"
project_id = module.prod-spoke-project.project_id
type = "private"
name = "gcr-io"
domain = "gcr.io."
client_networks = [module.prod-spoke-vpc.self_link]
recordsets = {
"A gcr.io." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "prod-packages-private-zone" {
source = "../../../modules/dns"
project_id = module.prod-spoke-project.project_id
type = "private"
name = "packages-cloud"
domain = "packages.cloud.google.com."
client_networks = [module.prod-spoke-vpc.self_link]
recordsets = {
"A packages.cloud.google.com." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "prod-pkgdev-private-zone" {
source = "../../../modules/dns"
project_id = module.prod-spoke-project.project_id
type = "private"
name = "pkg-dev"
domain = "pkg.dev."
client_networks = [module.prod-spoke-vpc.self_link]
recordsets = {
"A pkg.dev." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "prod-pkigoog-private-zone" {
source = "../../../modules/dns"
project_id = module.prod-spoke-project.project_id
type = "private"
name = "pki-goog"
domain = "pki.goog."
client_networks = [module.prod-spoke-vpc.self_link]
recordsets = {
"A pki.goog." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}

View File

@ -108,6 +108,13 @@ DNS configuration is further centralized by leveraging peering zones, so that
- the hub/landing Cloud DNS hosts configurations for on-prem forwarding, Google API domains, and the top-level private zone/s (e.g. gcp.example.com)
- the spokes Cloud DNS host configurations for the environment-specific domains (e.g. prod.gcp.example.com), which are bound to the hub/landing leveraging [cross-project binding](https://cloud.google.com/dns/docs/zones/zones-overview#cross-project_binding); a peering zone for the `.` (root) zone is then created on each spoke, delegating all DNS resolution to hub/landing.
- Private Google Access is enabled for a selection of the [supported domains](https://cloud.google.com/vpc/docs/configure-private-google-access#domain-options), namely
- `private.googleapis.com`
- `restricted.googleapis.com`
- `gcr.io`
- `packages.cloud.google.com`
- `pkg.dev`
- `pki.goog`
To complete the configuration, the 35.199.192.0/19 range should be routed on the VPN tunnels from on-prem, and the following names configured for DNS forwarding to cloud:

View File

@ -26,7 +26,7 @@ module "dev-dns-private-zone" {
domain = "dev.gcp.example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}

View File

@ -46,11 +46,11 @@ module "gcp-example-dns-private-zone" {
domain = "gcp.example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}
# Google API zone to trigger Private Access
# Google APIs
module "googleapis-private-zone" {
source = "../../../modules/dns"
@ -60,12 +60,72 @@ module "googleapis-private-zone" {
domain = "googleapis.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A private" = { type = "A", ttl = 300, records = [
"A private" = { records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"A restricted" = { type = "A", ttl = 300, records = [
"A restricted" = { records = [
"199.36.153.4", "199.36.153.5", "199.36.153.6", "199.36.153.7"
] }
"CNAME *" = { type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }
"CNAME *" = { records = ["private.googleapis.com."] }
}
}
module "gcrio-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "gcr-io"
domain = "gcr.io."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A gcr.io." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "packages-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "packages-cloud"
domain = "packages.cloud.google.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A packages.cloud.google.com." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "pkgdev-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "pkg-dev"
domain = "pkg.dev."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A pkg.dev." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}
module "pkigoog-private-zone" {
source = "../../../modules/dns"
project_id = module.landing-project.project_id
type = "private"
name = "pki-goog"
domain = "pki.goog."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A pki.goog." = { ttl = 300, records = [
"199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
] }
"CNAME *" = { ttl = 300, records = ["private.googleapis.com."] }
}
}

View File

@ -26,7 +26,7 @@ module "prod-dns-private-zone" {
domain = "prod.gcp.example.com."
client_networks = [module.landing-vpc.self_link]
recordsets = {
"A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
"A localhost" = { records = ["127.0.0.1"] }
}
}

View File

@ -24,8 +24,6 @@ To destroy a previous FAST deployment follow the instructions detailed in [clean
- [Bootstrap](00-bootstrap/README.md)
Enables critical organization-level functionality that depends on broad permissions. It has two primary purposes. The first is to bootstrap the resources needed for automation of this and the following stages (service accounts, GCS buckets). And secondly, it applies the minimum amount of configuration needed at the organization level, to avoid the need of broad permissions later on, and to implement a minimum of security features like sinks and exports from the start.\
Exports: automation variables, organization-level custom roles
- [CI/CD Bootstrap](00-cicd/README.md)
Optionally set up CI/CD repositories and project structure automatically for GitHub and Gitlab. This stage is not needed if repository are created manually.
- [Resource Management](01-resman/README.md)
Creates the base resource hierarchy (folders) and the automation resources required later to delegate deployment of each part of the hierarchy to separate stages. This stage also configures organization-level policies and any exceptions needed by different branches of the resource hierarchy.\
Exports: folder ids, automation service account emails

View File

@ -18,7 +18,7 @@ These modules are used in the examples included in this repository. If you are u
- Sync from the upstream repository to get all the updates.
- Use GitHub sources with refs to reference the modules. See an example below:
```
```terraform
module "project" {
source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/project?ref=v13.0.0"
name = "my-project"

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

View File

@ -17,11 +17,11 @@ terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.36.0" # tftest
version = ">= 4.40.0" # tftest
}
}
}

Some files were not shown because too many files have changed in this diff Show More