diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcc71108..2599dd2b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
### BLUEPRINTS
+- [[#1024](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1024)] Fix Apigee PAYG environment node config ([g-greatdevaks](https://github.com/g-greatdevaks))
- [[#1019](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1019)] Added endpoint attachments to Apigee module ([apichick](https://github.com/apichick))
- [[#1000](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1000)] ADFS blueprint fixes ([apichick](https://github.com/apichick))
- [[#1001](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1001)] Binauthz blueprint fixes related to project creation ([apichick](https://github.com/apichick))
@@ -114,6 +115,12 @@ All notable changes to this project will be documented in this file.
### MODULES
+- [[#1026](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1026)] add lifecycle ignore_changes for apigee PAYG env ([g-greatdevaks](https://github.com/g-greatdevaks))
+- [[#1031](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1031)] Fix default_rules_config description in firewall module ([ludoo](https://github.com/ludoo))
+- [[#1028](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1028)] **incompatible change:** Align rest of vpn modules with #1027 ([juliocc](https://github.com/juliocc))
+- [[#1027](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1027)] **incompatible change:** Update VPN-HA module to tf1.3 ([juliocc](https://github.com/juliocc))
+- [[#1025](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1025)] fix apigee PAYG env node config dynamic block ([g-greatdevaks](https://github.com/g-greatdevaks))
+- [[#1024](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1024)] Fix Apigee PAYG environment node config ([g-greatdevaks](https://github.com/g-greatdevaks))
- [[#1019](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1019)] Added endpoint attachments to Apigee module ([apichick](https://github.com/apichick))
- [[#1018](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1018)] Apigee instance doc examples ([danistrebel](https://github.com/danistrebel))
- [[#1016](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1016)] Fix memory/cpu typo in gke cluster module ([joeheaton](https://github.com/joeheaton))
@@ -191,6 +198,8 @@ All notable changes to this project will be documented in this file.
### TOOLS
+- [[#1022](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1022)] Replace `set-output` with env variable and remove single quotes on labels ([kunzese](https://github.com/kunzese))
+- [[#1021](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1021)] Add OpenContainers annotations to published container images ([kunzese](https://github.com/kunzese))
- [[#1017](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1017)] Fix auto-labeling ([ludoo](https://github.com/ludoo))
- [[#1013](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1013)] Update labeler.yml ([ludoo](https://github.com/ludoo))
- [[#1010](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1010)] Enforce nonempty descriptions ending in a dot ([juliocc](https://github.com/juliocc))
diff --git a/blueprints/cloud-operations/apigee/README.md b/blueprints/cloud-operations/apigee/README.md
index 922f038e..0802c433 100644
--- a/blueprints/cloud-operations/apigee/README.md
+++ b/blueprints/cloud-operations/apigee/README.md
@@ -59,15 +59,15 @@ Do the following to verify that everything works as expected.
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [envgroups](variables.tf#L24) | Environment groups (NAME => [HOSTNAMES]). | map(list(string))
| ✓ | |
-| [environments](variables.tf#L30) | Environments. | map(object({…}))
| ✓ | |
-| [instances](variables.tf#L46) | Instance. | map(object({…}))
| ✓ | |
-| [project_id](variables.tf#L92) | Project ID. | string
| ✓ | |
-| [psc_config](variables.tf#L98) | PSC configuration. | map(string)
| ✓ | |
+| [environments](variables.tf#L30) | Environments. | map(object({…}))
| ✓ | |
+| [instances](variables.tf#L45) | Instance. | map(object({…}))
| ✓ | |
+| [project_id](variables.tf#L91) | Project ID. | string
| ✓ | |
+| [psc_config](variables.tf#L97) | PSC configuration. | map(string)
| ✓ | |
| [datastore_name](variables.tf#L17) | Datastore. | string
| | "gcs"
|
-| [organization](variables.tf#L60) | Apigee organization. | object({…})
| | {…}
|
-| [path](variables.tf#L76) | Bucket path. | string
| | "/analytics"
|
-| [project_create](variables.tf#L83) | Parameters for the creation of the new project. | object({…})
| | null
|
-| [vpc_create](variables.tf#L104) | Boolean flag indicating whether the VPC should be created or not. | bool
| | true
|
+| [organization](variables.tf#L59) | Apigee organization. | object({…})
| | {…}
|
+| [path](variables.tf#L75) | Bucket path. | string
| | "/analytics"
|
+| [project_create](variables.tf#L82) | Parameters for the creation of the new project. | object({…})
| | null
|
+| [vpc_create](variables.tf#L103) | Boolean flag indicating whether the VPC should be created or not. | bool
| | true
|
## Outputs
diff --git a/blueprints/cloud-operations/apigee/functions/export/package-lock.json b/blueprints/cloud-operations/apigee/functions/export/package-lock.json
index 77925575..737005be 100644
--- a/blueprints/cloud-operations/apigee/functions/export/package-lock.json
+++ b/blueprints/cloud-operations/apigee/functions/export/package-lock.json
@@ -869,9 +869,9 @@
}
},
"node_modules/dezalgo": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
- "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+ "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dependencies": {
"asap": "^2.0.0",
"wrappy": "1"
@@ -1224,30 +1224,19 @@
}
},
"node_modules/formidable": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
- "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
+ "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
"dependencies": {
- "dezalgo": "1.0.3",
- "hexoid": "1.0.0",
- "once": "1.4.0",
- "qs": "6.9.3"
+ "dezalgo": "^1.0.4",
+ "hexoid": "^1.0.0",
+ "once": "^1.4.0",
+ "qs": "^6.11.0"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
},
- "node_modules/formidable/node_modules/qs": {
- "version": "6.9.3",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
- "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -3827,9 +3816,9 @@
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
},
"dezalgo": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
- "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+ "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"requires": {
"asap": "^2.0.0",
"wrappy": "1"
@@ -4106,21 +4095,14 @@
}
},
"formidable": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
- "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz",
+ "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==",
"requires": {
- "dezalgo": "1.0.3",
- "hexoid": "1.0.0",
- "once": "1.4.0",
- "qs": "6.9.3"
- },
- "dependencies": {
- "qs": {
- "version": "6.9.3",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
- "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw=="
- }
+ "dezalgo": "^1.0.4",
+ "hexoid": "^1.0.0",
+ "once": "^1.4.0",
+ "qs": "^6.11.0"
}
},
"forwarded": {
diff --git a/blueprints/cloud-operations/apigee/variables.tf b/blueprints/cloud-operations/apigee/variables.tf
index 1c86621b..ba7f5d78 100644
--- a/blueprints/cloud-operations/apigee/variables.tf
+++ b/blueprints/cloud-operations/apigee/variables.tf
@@ -33,9 +33,8 @@ variable "environments" {
display_name = optional(string)
description = optional(string)
node_config = optional(object({
- min_node_count = optional(number)
- max_node_count = optional(number)
- current_aggregate_node_count = number
+ min_node_count = optional(number)
+ max_node_count = optional(number)
}))
iam = optional(map(list(string)))
envgroups = list(string)
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/main.py b/blueprints/cloud-operations/network-dashboard/cloud-function/main.py
index 83e93fbb..8e7640dd 100644
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/main.py
+++ b/blueprints/cloud-operations/network-dashboard/cloud-function/main.py
@@ -21,7 +21,7 @@ import time
from google.cloud import monitoring_v3, asset_v1
from google.protobuf import field_mask_pb2
from googleapiclient import discovery
-from metrics import ilb_fwrules, firewall_policies, instances, networks, metrics, limits, peerings, routes, subnets, vpc_firewalls
+from metrics import ilb_fwrules, firewall_policies, instances, networks, metrics, limits, peerings, routes, subnets, vpc_firewalls, secondarys
CF_VERSION = os.environ.get("CF_VERSION")
@@ -158,6 +158,9 @@ def main(event, context=None):
# IP utilization subnet level metrics
subnets.get_subnets(config, metrics_dict)
+ # IP utilization secondary range metrics
+ secondarys.get_secondaries(config, metrics_dict)
+
# Asset inventory queries
gce_instance_dict = instances.get_gce_instance_dict(config)
l4_forwarding_rules_dict = ilb_fwrules.get_forwarding_rules_dict(config, "L4")
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics.yaml b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics.yaml
index 0c5bb8cc..21759963 100644
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics.yaml
+++ b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics.yaml
@@ -25,6 +25,16 @@ metrics_per_subnet:
limit:
name: number_of_max_ip
description: Number of available IP addresses in the subnet.
+ ip_usage_per_secondaryRange:
+ usage:
+ name: number_of_sr_ip_used
+ description: Number of used IP addresses in the secondary range.
+ utilization:
+ name: ip_addresses_per_sr_utilization
+ description: Percentage of IP used in the secondary range.
+ limit:
+ name: number_of_max_sr_ip
+ description: Number of available IP addresses in the secondary range.
metrics_per_network:
instance_per_network:
usage:
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/metrics.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/metrics.py
index b26e30d4..8e0c4082 100644
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/metrics.py
+++ b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/metrics.py
@@ -53,7 +53,9 @@ def create_metrics(monitoring_project, config):
monitoring_project, config)
# Parse limits for network and peering group metrics
# Subnet level metrics have a different limit: the subnet IP range size
- if sub_metric_key == "limit" and metric_name != "ip_usage_per_subnet":
+ if sub_metric_key == "limit" and (
+ metric_name != "ip_usage_per_subnet" and
+ metric_name != "ip_usage_per_secondaryRange"):
limits_dict_for_metric = {}
if "values" in sub_metric:
for network_link, limit_value in sub_metric["values"].items():
@@ -262,4 +264,4 @@ def customize_quota_view(quota_results):
for val in result.points:
quotaViewJson.update({'value': val.value.int64_value})
quotaViewList.append(quotaViewJson)
- return quotaViewList
\ No newline at end of file
+ return quotaViewList
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/secondarys.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/secondarys.py
new file mode 100644
index 00000000..6030ddaf
--- /dev/null
+++ b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/secondarys.py
@@ -0,0 +1,266 @@
+#
+# 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.
+#
+
+import time
+
+from . import metrics
+from google.protobuf import field_mask_pb2
+from google.protobuf.json_format import MessageToDict
+import ipaddress
+
+
+def get_all_secondaryRange(config):
+ '''
+ Returns a dictionary with secondary range informations
+ Parameters:
+ config (dict): The dict containing config like clients and limits
+ Returns:
+ secondary_dict (dictionary of String: dictionary): Key is the project_id,
+ value is a nested dictionary with subnet_name/secondary_range_name as the key.
+ '''
+ secondary_dict = {}
+ 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/Subnetwork'],
+ "read_mask": read_mask,
+ "page_size": config["page_size"],
+ })
+
+ for asset in response:
+ for versioned in asset.versioned_resources:
+ subnet_name = versioned.resource.get('name')
+ # Network self link format:
+ # "https://www.googleapis.com/compute/v1/projects//global/networks/"
+ project_id = versioned.resource.get('network').split('/')[6]
+ network_name = versioned.resource.get('network').split('/')[-1]
+ subnet_region = versioned.resource.get('region').split('/')[-1]
+
+ # Check first if the subnet has any secondary ranges to begin with
+ if versioned.resource.get('secondaryIpRanges'):
+ for items in versioned.resource.get('secondaryIpRanges'):
+ # Each subnet can have multiple secondary ranges
+ secondaryRange_name = items.get('rangeName')
+ secondaryCidrBlock = items.get('ipCidrRange')
+
+ net = ipaddress.ip_network(secondaryCidrBlock)
+ total_ip_addresses = int(net.num_addresses)
+
+ if project_id not in secondary_dict:
+ secondary_dict[project_id] = {}
+ secondary_dict[project_id][f"{subnet_name}/{secondaryRange_name}"] = {
+ 'name': secondaryRange_name,
+ 'region': subnet_region,
+ 'subnetName': subnet_name,
+ 'ip_cidr_range': secondaryCidrBlock,
+ 'total_ip_addresses': total_ip_addresses,
+ 'used_ip_addresses': 0,
+ 'network_name': network_name
+ }
+ return secondary_dict
+
+
+def compute_GKE_secondaryIP_utilization(config, read_mask, all_secondary_dict):
+ '''
+ Counts the IP Addresses used by GKE (Pods and Services)
+ Parameters:
+ config (dict): The dict containing config like clients and limits
+ read_mask (FieldMask): read_mask to get additional metadata from Cloud Asset Inventory
+ all_secondary_dict (dict): Dict containing the secondary IP Range information for each subnets in the GCP organization
+ Returns:
+ all_secondary_dict (dict): Same dict but populated with GKE IP utilization information
+ '''
+ cluster_secondary_dict = {}
+ node_secondary_dict = {}
+
+ # Creating cluster dict
+ # Cluster dict has subnet information
+ response_cluster = config["clients"]["asset_client"].list_assets(
+ request={
+ "parent": f"organizations/{config['organization']}",
+ "asset_types": ['container.googleapis.com/Cluster'],
+ "content_type": 'RESOURCE',
+ "page_size": config["page_size"],
+ })
+
+ for asset in response_cluster:
+ cluster_project = asset.resource.data['selfLink'].split('/')[5]
+ cluster_parent = "/".join(asset.resource.data['selfLink'].split('/')[5:10])
+ cluster_subnetwork = asset.resource.data['subnetwork']
+ cluster_service_rangeName = asset.resource.data['ipAllocationPolicy'][
+ 'servicesSecondaryRangeName']
+
+ cluster_secondary_dict[f"{cluster_parent}/Service"] = {
+ "project": cluster_project,
+ "subnet": cluster_subnetwork,
+ "secondaryRange_name": cluster_service_rangeName,
+ 'used_ip_addresses': 0,
+ }
+
+ for node_pool in asset.resource.data['nodePools']:
+ nodepool_name = node_pool['name']
+ node_IPrange = node_pool['networkConfig']['podRange']
+ cluster_secondary_dict[f"{cluster_parent}/{nodepool_name}"] = {
+ "project": cluster_project,
+ "subnet": cluster_subnetwork,
+ "secondaryRange_name": node_IPrange,
+ 'used_ip_addresses': 0,
+ }
+
+ # Creating node dict
+ # Node dict allows 1:1 mapping of pod IP utilization, and which secondary Range it is using
+ response_node = config["clients"]["asset_client"].search_all_resources(
+ request={
+ "scope": f"organizations/{config['organization']}",
+ "asset_types": ['k8s.io/Node'],
+ "read_mask": read_mask,
+ "page_size": config["page_size"],
+ })
+
+ for asset in response_node:
+ # Node name link format:
+ # "//container.googleapis.com/projects////clusters//k8s/nodes/"
+ node_parent = "/".join(asset.name.split('/')[4:9])
+ node_name = asset.name.split('/')[-1]
+ node_full_name = f"{node_parent}/{node_name}"
+
+ for versioned in asset.versioned_resources:
+ node_secondary_dict[node_full_name] = {
+ 'node_parent':
+ node_parent,
+ 'this_node_pool':
+ versioned.resource['metadata']['labels']
+ ['cloud.google.com/gke-nodepool'],
+ 'used_ip_addresses':
+ 0
+ }
+
+ # Counting IP addresses used by pods in GKE
+ response_pods = config["clients"]["asset_client"].search_all_resources(
+ request={
+ "scope": f"organizations/{config['organization']}",
+ "asset_types": ['k8s.io/Pod'],
+ "read_mask": read_mask,
+ "page_size": config["page_size"],
+ })
+
+ for asset in response_pods:
+ # Pod name link format:
+ # "//container.googleapis.com/projects////clusters//k8s/namespaces//pods/"
+ pod_parent = "/".join(asset.name.split('/')[4:9])
+
+ for versioned in asset.versioned_resources:
+ cur_PodIP = versioned.resource['status']['podIP']
+ cur_HostIP = versioned.resource['status']['hostIP']
+ host_node_name = versioned.resource['spec']['nodeName']
+ pod_full_path = f"{pod_parent}/{host_node_name}"
+
+ # A check to make sure pod is not using node IP
+ if cur_PodIP != cur_HostIP:
+ node_secondary_dict[pod_full_path]['used_ip_addresses'] += 1
+
+ # Counting IP addresses used by Service in GKE
+ response_service = config["clients"]["asset_client"].search_all_resources(
+ request={
+ "scope": f"organizations/{config['organization']}",
+ "asset_types": ['k8s.io/Service'],
+ "read_mask": read_mask,
+ "page_size": config["page_size"],
+ })
+
+ for asset in response_service:
+ service_parent = "/".join(asset.name.split('/')[4:9])
+ service_fullpath = f"{service_parent}/Service"
+ cluster_secondary_dict[service_fullpath]['used_ip_addresses'] += 1
+
+ for item in node_secondary_dict.values():
+ itemKey = f"{item['node_parent']}/{item['this_node_pool']}"
+ cluster_secondary_dict[itemKey]['used_ip_addresses'] += item['used_ip_addresses']
+
+ for item in cluster_secondary_dict.values():
+ itemKey = f"{item['subnet']}/{item['secondaryRange_name']}"
+ all_secondary_dict[item['project']][itemKey]['used_ip_addresses'] += item[
+ 'used_ip_addresses']
+
+
+def compute_secondary_utilization(config, all_secondary_dict):
+ '''
+ Counts resources (GKE, GCE) using IPs in secondary ranges.
+ Parameters:
+ config (dict): Dict containing config like clients and limits
+ all_secondary_dict (dict): Dict containing the secondary IP Range information for each subnets in the GCP organization
+ Returns:
+ None
+ '''
+ read_mask = field_mask_pb2.FieldMask()
+ read_mask.FromJsonString('name,versionedResources')
+
+ compute_GKE_secondaryIP_utilization(config, read_mask, all_secondary_dict)
+ # TODO: Other Secondary IP like GCE VM using alias IPs
+
+
+def get_secondaries(config, metrics_dict):
+ '''
+ Writes all secondary rang IP address usage metrics to custom metrics.
+ Parameters:
+ config (dict): The dict containing config like clients and limits
+ Returns:
+ None
+ '''
+
+ secondaryRange_dict = get_all_secondaryRange(config)
+ # Updates all_subnets_dict with the IP utilization info
+ compute_secondary_utilization(config, secondaryRange_dict)
+
+ timestamp = time.time()
+ for project_id in config["monitored_projects"]:
+ if project_id not in secondaryRange_dict:
+ continue
+ for secondary_dict in secondaryRange_dict[project_id].values():
+ ip_utilization = 0
+ if secondary_dict['used_ip_addresses'] > 0:
+ ip_utilization = secondary_dict['used_ip_addresses'] / secondary_dict[
+ 'total_ip_addresses']
+
+ # Building unique identifier with subnet region/name
+ subnet_id = f"{secondary_dict['region']}/{secondary_dict['name']}"
+ metric_labels = {
+ 'project': project_id,
+ 'network_name': secondary_dict['network_name'],
+ 'region' : secondary_dict['region'],
+ 'subnet' : secondary_dict['subnetName'],
+ 'secondary_range' : secondary_dict['name']
+ }
+ metrics.append_data_to_series_buffer(
+ config, metrics_dict["metrics_per_subnet"]
+ ["ip_usage_per_secondaryRange"]["usage"]["name"],
+ secondary_dict['used_ip_addresses'], metric_labels,
+ timestamp=timestamp)
+ metrics.append_data_to_series_buffer(
+ config, metrics_dict["metrics_per_subnet"]
+ ["ip_usage_per_secondaryRange"]["limit"]["name"],
+ secondary_dict['total_ip_addresses'], metric_labels,
+ timestamp=timestamp)
+ metrics.append_data_to_series_buffer(
+ config, metrics_dict["metrics_per_subnet"]
+ ["ip_usage_per_secondaryRange"]["utilization"]["name"],
+ ip_utilization, metric_labels, timestamp=timestamp)
+
+ print("Buffered metrics for secondary ip utilization for VPCs in project",
+ project_id)
diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/subnets.py b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/subnets.py
index cb16c9c8..46fbc756 100644
--- a/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/subnets.py
+++ b/blueprints/cloud-operations/network-dashboard/cloud-function/metrics/subnets.py
@@ -149,7 +149,8 @@ def compute_subnet_utilization_ilbs(config, read_mask, all_subnets_dict):
for versioned in asset.versioned_resources:
for field_name, field_value in versioned.resource.items():
if 'loadBalancingScheme' in field_name and field_value in [
- 'INTERNAL', 'INTERNAL_MANAGED']:
+ 'INTERNAL', 'INTERNAL_MANAGED'
+ ]:
internal = True
# We want to count only accepted PSC endpoint Forwarding Rule
# If the PSC endpoint Forwarding Rule is pending, we will count it in the reserved IP addresses
diff --git a/blueprints/cloud-operations/network-dashboard/dashboards/quotas-utilization.json b/blueprints/cloud-operations/network-dashboard/dashboards/quotas-utilization.json
index c9eb8bd1..e26d6926 100644
--- a/blueprints/cloud-operations/network-dashboard/dashboards/quotas-utilization.json
+++ b/blueprints/cloud-operations/network-dashboard/dashboards/quotas-utilization.json
@@ -701,6 +701,49 @@
"width": 6,
"xPos": 6,
"yPos": 24
+ },
+ {
+ "height": 4,
+ "widget": {
+ "title": "secondary_ip_address_utilization",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "apiSource": "DEFAULT_CLOUD",
+ "timeSeriesFilter": {
+ "aggregation": {
+ "alignmentPeriod": "60s",
+ "crossSeriesReducer": "REDUCE_NONE",
+ "perSeriesAligner": "ALIGN_MEAN"
+ },
+ "filter": "metric.type=\"custom.googleapis.com/ip_addresses_per_sr_utilization\" resource.type=\"global\"",
+ "secondaryAggregation": {
+ "alignmentPeriod": "60s",
+ "crossSeriesReducer": "REDUCE_NONE",
+ "perSeriesAligner": "ALIGN_NONE"
+ }
+ }
+ }
+ }
+ ],
+ "thresholds": [],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ "width": 6,
+ "xPos": 0,
+ "yPos": 36
}
]
}
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/README.md b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/README.md
index 3af34289..9b292de6 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/README.md
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/README.md
@@ -13,6 +13,5 @@ The codebase provisions the following list of resources:
|---|---|:---:|:---:|:---:|
| [impersonate_service_account_email](variables.tf#L16) | Service account to be impersonated by workload identity. | string
| ✓ | |
| [project_id](variables.tf#L21) | GCP project ID. | string
| ✓ | |
-| [workload_identity_pool_provider_id](variables.tf#L26) | GCP workload identity pool provider ID. | string
| ✓ | |
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/provider.tf b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/provider.tf
index 47f24620..ae132fd4 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/provider.tf
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/provider.tf
@@ -16,8 +16,7 @@
module "tfe_oidc" {
source = "./tfc-oidc"
- workload_identity_pool_provider_id = var.workload_identity_pool_provider_id
- impersonate_service_account_email = var.impersonate_service_account_email
+ impersonate_service_account_email = var.impersonate_service_account_email
}
provider "google" {
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/terraform.auto.tfvars.template b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/terraform.auto.tfvars.template
index efea4cc9..fc2811db 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/terraform.auto.tfvars.template
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/terraform.auto.tfvars.template
@@ -13,5 +13,4 @@
# limitations under the License.
project_id = "tfe-oidc-workflow"
-workload_identity_pool_provider_id = "projects/683987109094/locations/global/workloadIdentityPools/tfe-pool/providers/tfe-provider"
impersonate_service_account_email = "sa-tfe@tfe-oidc-workflow2.iam.gserviceaccount.com"
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/README.md b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/README.md
index 240d6d02..534d6599 100644
--- a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/README.md
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/README.md
@@ -5,9 +5,8 @@ This is a helper module to prepare GCP Credentials from Terraform Enterprise wor
## Example
```hcl
module "tfe_oidc" {
- source = "./tfe_oidc"
+ source = "./tfc-oidc"
- workload_identity_pool_provider_id = "projects/683987109094/locations/global/workloadIdentityPools/tfe-pool/providers/tfe-provider"
impersonate_service_account_email = "tfe-test@tfe-test-wif.iam.gserviceaccount.com"
}
@@ -28,7 +27,6 @@ provider "google-beta" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [impersonate_service_account_email](variables.tf#L17) | Service account to be impersonated by workload identity federation. | string
| ✓ | |
-| [workload_identity_pool_provider_id](variables.tf#L28) | GCP workload identity pool provider ID. | string
| ✓ | |
| [tmp_oidc_token_path](variables.tf#L22) | Name of the temporary file where TFC OIDC token will be stored to authentificate terraform provider google. | string
| | ".oidc_token"
|
## Outputs
diff --git a/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/get_audience.sh b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/get_audience.sh
new file mode 100644
index 00000000..251fe321
--- /dev/null
+++ b/blueprints/cloud-operations/terraform-enterprise-wif/tfc-workflow-using-wif/tfc-oidc/get_audience.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+# 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.
+
+# Exit if any of the intermediate steps fail
+set -e
+
+cat <map(string) | | {…}
| |
| [router_configs](variables.tf#L192) | Configurations for CRs and onprem routers. | map(object({…}))
| | {…}
| |
| [service_accounts](variables.tf#L215) | Automation service accounts in name => email format. | object({…})
| | null
| 01-resman
|
-| [vpn_onprem_configs](variables.tf#L229) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [vpn_onprem_configs](variables.tf#L229) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking-nva/variables.tf b/fast/stages/02-networking-nva/variables.tf
index b55eaabc..ea6c0bf0 100644
--- a/fast/stages/02-networking-nva/variables.tf
+++ b/fast/stages/02-networking-nva/variables.tf
@@ -235,10 +235,7 @@ variable "vpn_onprem_configs" {
})
peer_external_gateway = object({
redundancy_type = string
- interfaces = list(object({
- id = number
- ip_address = string
- }))
+ interfaces = list(string)
})
tunnels = list(object({
peer_asn = number
@@ -258,9 +255,7 @@ variable "vpn_onprem_configs" {
}
peer_external_gateway = {
redundancy_type = "SINGLE_IP_INTERNALLY_REDUNDANT"
- interfaces = [
- { id = 0, ip_address = "8.8.8.8" },
- ]
+ interfaces = ["8.8.8.8"]
}
tunnels = [
{
@@ -288,9 +283,7 @@ variable "vpn_onprem_configs" {
}
peer_external_gateway = {
redundancy_type = "SINGLE_IP_INTERNALLY_REDUNDANT"
- interfaces = [
- { id = 0, ip_address = "8.8.8.8" },
- ]
+ interfaces = ["8.8.8.8"]
}
tunnels = [
{
diff --git a/fast/stages/02-networking-nva/vpn-onprem.tf b/fast/stages/02-networking-nva/vpn-onprem.tf
index c860c099..03658088 100644
--- a/fast/stages/02-networking-nva/vpn-onprem.tf
+++ b/fast/stages/02-networking-nva/vpn-onprem.tf
@@ -33,16 +33,19 @@ locals {
}
module "landing-to-onprem-ew1-vpn" {
- count = local.enable_onprem_vpn ? 1 : 0
- source = "../../../modules/net-vpn-ha"
- project_id = module.landing-project.project_id
- network = module.landing-trusted-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-onprem-ew1"
- router_create = true
- router_name = "landing-onprem-vpn-ew1"
- router_asn = var.router_configs.landing-trusted-ew1.asn
- peer_external_gateway = var.vpn_onprem_configs.landing-trusted-ew1.peer_external_gateway
+ count = local.enable_onprem_vpn ? 1 : 0
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.landing-project.project_id
+ network = module.landing-trusted-vpc.self_link
+ region = "europe-west1"
+ name = "vpn-to-onprem-ew1"
+ router_config = {
+ name = "landing-onprem-vpn-ew1"
+ asn = var.router_configs.landing-trusted-ew1.asn
+ }
+ peer_gateway = {
+ external = var.vpn_onprem_configs.landing-trusted-ew1.peer_external_gateway
+ }
tunnels = {
for t in var.vpn_onprem_configs.landing-trusted-ew1.tunnels :
"remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => {
@@ -52,9 +55,7 @@ module "landing-to-onprem-ew1-vpn" {
}
bgp_peer_options = local.bgp_peer_options_onprem.landing-trusted-ew1
bgp_session_range = "${cidrhost(t.session_range, 2)}/30"
- ike_version = 2
peer_external_gateway_interface = t.peer_external_gateway_interface
- router = null
shared_secret = t.secret
vpn_gateway_interface = t.vpn_gateway_interface
}
@@ -62,16 +63,19 @@ module "landing-to-onprem-ew1-vpn" {
}
module "landing-to-onprem-ew4-vpn" {
- count = local.enable_onprem_vpn ? 1 : 0
- source = "../../../modules/net-vpn-ha"
- project_id = module.landing-project.project_id
- network = module.landing-trusted-vpc.self_link
- region = "europe-west4"
- name = "vpn-to-onprem-ew4"
- router_create = true
- router_name = "landing-onprem-vpn-ew4"
- router_asn = var.router_configs.landing-trusted-ew4.asn
- peer_external_gateway = var.vpn_onprem_configs.landing-trusted-ew4.peer_external_gateway
+ count = local.enable_onprem_vpn ? 1 : 0
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.landing-project.project_id
+ network = module.landing-trusted-vpc.self_link
+ region = "europe-west4"
+ name = "vpn-to-onprem-ew4"
+ router_config = {
+ name = "landing-onprem-vpn-ew4"
+ asn = var.router_configs.landing-trusted-ew4.asn
+ }
+ peer_gateway = {
+ external = var.vpn_onprem_configs.landing-trusted-ew4.peer_external_gateway
+ }
tunnels = {
for t in var.vpn_onprem_configs.landing-trusted-ew4.tunnels :
"remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => {
@@ -81,9 +85,7 @@ module "landing-to-onprem-ew4-vpn" {
}
bgp_peer_options = local.bgp_peer_options_onprem.landing-trusted-ew4
bgp_session_range = "${cidrhost(t.session_range, 2)}/30"
- ike_version = 2
peer_external_gateway_interface = t.peer_external_gateway_interface
- router = null
shared_secret = t.secret
vpn_gateway_interface = t.vpn_gateway_interface
}
diff --git a/fast/stages/02-networking-peering/README.md b/fast/stages/02-networking-peering/README.md
index f6a82809..c7829f0f 100644
--- a/fast/stages/02-networking-peering/README.md
+++ b/fast/stages/02-networking-peering/README.md
@@ -311,7 +311,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [region_trigram](variables.tf#L166) | Short names for GCP regions. | map(string)
| | {…}
| |
| [router_onprem_configs](variables.tf#L175) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
| [service_accounts](variables.tf#L193) | Automation service accounts in name => email format. | object({…})
| | null
| 01-resman
|
-| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking-peering/variables.tf b/fast/stages/02-networking-peering/variables.tf
index 111633e6..faa91698 100644
--- a/fast/stages/02-networking-peering/variables.tf
+++ b/fast/stages/02-networking-peering/variables.tf
@@ -213,10 +213,7 @@ variable "vpn_onprem_configs" {
})
peer_external_gateway = object({
redundancy_type = string
- interfaces = list(object({
- id = number
- ip_address = string
- }))
+ interfaces = list(string)
})
tunnels = list(object({
peer_asn = number
@@ -236,9 +233,7 @@ variable "vpn_onprem_configs" {
}
peer_external_gateway = {
redundancy_type = "SINGLE_IP_INTERNALLY_REDUNDANT"
- interfaces = [
- { id = 0, ip_address = "8.8.8.8" },
- ]
+ interfaces = ["8.8.8.8"]
}
tunnels = [
{
diff --git a/fast/stages/02-networking-peering/vpn-onprem.tf b/fast/stages/02-networking-peering/vpn-onprem.tf
index 48cad54b..5237a05a 100644
--- a/fast/stages/02-networking-peering/vpn-onprem.tf
+++ b/fast/stages/02-networking-peering/vpn-onprem.tf
@@ -33,16 +33,19 @@ locals {
}
module "landing-to-onprem-ew1-vpn" {
- count = local.enable_onprem_vpn ? 1 : 0
- source = "../../../modules/net-vpn-ha"
- project_id = module.landing-project.project_id
- network = module.landing-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-onprem-ew1"
- router_create = true
- router_name = "landing-onprem-vpn-ew1"
- router_asn = var.router_onprem_configs.landing-ew1.asn
- peer_external_gateway = var.vpn_onprem_configs.landing-ew1.peer_external_gateway
+ count = local.enable_onprem_vpn ? 1 : 0
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.landing-project.project_id
+ network = module.landing-vpc.self_link
+ region = "europe-west1"
+ name = "vpn-to-onprem-ew1"
+ router_config = {
+ name = "landing-onprem-vpn-ew1"
+ asn = var.router_onprem_configs.landing-ew1.asn
+ }
+ peer_gateway = {
+ external = var.vpn_onprem_configs.landing-ew1.peer_external_gateway
+ }
tunnels = {
for t in var.vpn_onprem_configs.landing-ew1.tunnels :
"remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => {
@@ -52,9 +55,7 @@ module "landing-to-onprem-ew1-vpn" {
}
bgp_peer_options = local.bgp_peer_options_onprem.landing-ew1
bgp_session_range = "${cidrhost(t.session_range, 2)}/30"
- ike_version = 2
peer_external_gateway_interface = t.peer_external_gateway_interface
- router = null
shared_secret = t.secret
vpn_gateway_interface = t.vpn_gateway_interface
}
diff --git a/fast/stages/02-networking-separate-envs/README.md b/fast/stages/02-networking-separate-envs/README.md
index a874311a..66b31646 100644
--- a/fast/stages/02-networking-separate-envs/README.md
+++ b/fast/stages/02-networking-separate-envs/README.md
@@ -252,7 +252,7 @@ You're now ready to run `terraform init` and `apply`.
| [psa_ranges](variables.tf#L129) | IP ranges used for Private Service Access (e.g. CloudSQL). | object({…})
| | null
| |
| [router_onprem_configs](variables.tf#L166) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
| [service_accounts](variables.tf#L189) | Automation service accounts in name => email format. | object({…})
| | null
| 01-resman
|
-| [vpn_onprem_configs](variables.tf#L201) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [vpn_onprem_configs](variables.tf#L201) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking-separate-envs/variables.tf b/fast/stages/02-networking-separate-envs/variables.tf
index d71534db..019d0b2e 100644
--- a/fast/stages/02-networking-separate-envs/variables.tf
+++ b/fast/stages/02-networking-separate-envs/variables.tf
@@ -207,10 +207,7 @@ variable "vpn_onprem_configs" {
})
peer_external_gateway = object({
redundancy_type = string
- interfaces = list(object({
- id = number
- ip_address = string
- }))
+ interfaces = list(string)
})
tunnels = list(object({
peer_asn = number
@@ -230,9 +227,8 @@ variable "vpn_onprem_configs" {
}
peer_external_gateway = {
redundancy_type = "SINGLE_IP_INTERNALLY_REDUNDANT"
- interfaces = [
- { id = 0, ip_address = "8.8.8.8" },
- ]
+ interfaces = ["8.8.8.8"]
+
}
tunnels = [
{
@@ -260,9 +256,7 @@ variable "vpn_onprem_configs" {
}
peer_external_gateway = {
redundancy_type = "SINGLE_IP_INTERNALLY_REDUNDANT"
- interfaces = [
- { id = 0, ip_address = "8.8.8.8" },
- ]
+ interfaces = ["8.8.8.8"]
}
tunnels = [
{
diff --git a/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf b/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf
index 313a073d..1b0ff117 100644
--- a/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf
+++ b/fast/stages/02-networking-separate-envs/vpn-onprem-dev.tf
@@ -33,16 +33,19 @@ locals {
}
module "dev-to-onprem-ew1-vpn" {
- count = local.enable_onprem_vpn ? 1 : 0
- source = "../../../modules/net-vpn-ha"
- project_id = module.dev-spoke-project.project_id
- network = module.dev-spoke-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-onprem-ew1"
- router_create = true
- router_name = "dev-onprem-vpn-ew1"
- router_asn = var.router_onprem_configs.dev-ew1.asn
- peer_external_gateway = var.vpn_onprem_configs.dev-ew1.peer_external_gateway
+ count = local.enable_onprem_vpn ? 1 : 0
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.dev-spoke-project.project_id
+ network = module.dev-spoke-vpc.self_link
+ region = "europe-west1"
+ name = "vpn-to-onprem-ew1"
+ router_config = {
+ name = "dev-onprem-vpn-ew1"
+ asn = var.router_onprem_configs.dev-ew1.asn
+ }
+ peer_gateway = {
+ external = var.vpn_onprem_configs.dev-ew1.peer_external_gateway
+ }
tunnels = {
for t in var.vpn_onprem_configs.dev-ew1.tunnels :
"remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => {
@@ -52,9 +55,7 @@ module "dev-to-onprem-ew1-vpn" {
}
bgp_peer_options = local.bgp_peer_options_onprem.dev-ew1
bgp_session_range = "${cidrhost(t.session_range, 2)}/30"
- ike_version = 2
peer_external_gateway_interface = t.peer_external_gateway_interface
- router = null
shared_secret = t.secret
vpn_gateway_interface = t.vpn_gateway_interface
}
diff --git a/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf b/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf
index 0a8e9655..d4b2af24 100644
--- a/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf
+++ b/fast/stages/02-networking-separate-envs/vpn-onprem-prod.tf
@@ -17,16 +17,19 @@
# tfdoc:file:description VPN between prod and onprem.
module "prod-to-onprem-ew1-vpn" {
- count = local.enable_onprem_vpn ? 1 : 0
- source = "../../../modules/net-vpn-ha"
- project_id = module.prod-spoke-project.project_id
- network = module.prod-spoke-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-onprem-ew1"
- router_create = true
- router_name = "prod-onprem-vpn-ew1"
- router_asn = var.router_onprem_configs.prod-ew1.asn
- peer_external_gateway = var.vpn_onprem_configs.prod-ew1.peer_external_gateway
+ count = local.enable_onprem_vpn ? 1 : 0
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.prod-spoke-project.project_id
+ network = module.prod-spoke-vpc.self_link
+ region = "europe-west1"
+ name = "vpn-to-onprem-ew1"
+ router_config = {
+ name = "prod-onprem-vpn-ew1"
+ asn = var.router_onprem_configs.prod-ew1.asn
+ }
+ peer_gateway = {
+ external = var.vpn_onprem_configs.prod-ew1.peer_external_gateway
+ }
tunnels = {
for t in var.vpn_onprem_configs.prod-ew1.tunnels :
"remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => {
@@ -36,9 +39,7 @@ module "prod-to-onprem-ew1-vpn" {
}
bgp_peer_options = local.bgp_peer_options_onprem.prod-ew1
bgp_session_range = "${cidrhost(t.session_range, 2)}/30"
- ike_version = 2
peer_external_gateway_interface = t.peer_external_gateway_interface
- router = null
shared_secret = t.secret
vpn_gateway_interface = t.vpn_gateway_interface
}
diff --git a/fast/stages/02-networking-vpn/README.md b/fast/stages/02-networking-vpn/README.md
index 8a884c09..047a1189 100644
--- a/fast/stages/02-networking-vpn/README.md
+++ b/fast/stages/02-networking-vpn/README.md
@@ -336,7 +336,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [router_onprem_configs](variables.tf#L175) | Configurations for routers used for onprem connectivity. | map(object({…}))
| | {…}
| |
| [router_spoke_configs](variables-vpn.tf#L18) | Configurations for routers used for internal connectivity. | map(object({…}))
| | {…}
| |
| [service_accounts](variables.tf#L193) | Automation service accounts in name => email format. | object({…})
| | null
| 01-resman
|
-| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [vpn_onprem_configs](variables.tf#L207) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
| [vpn_spoke_configs](variables-vpn.tf#L37) | VPN gateway configuration for spokes. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking-vpn/variables.tf b/fast/stages/02-networking-vpn/variables.tf
index 111633e6..faa91698 100644
--- a/fast/stages/02-networking-vpn/variables.tf
+++ b/fast/stages/02-networking-vpn/variables.tf
@@ -213,10 +213,7 @@ variable "vpn_onprem_configs" {
})
peer_external_gateway = object({
redundancy_type = string
- interfaces = list(object({
- id = number
- ip_address = string
- }))
+ interfaces = list(string)
})
tunnels = list(object({
peer_asn = number
@@ -236,9 +233,7 @@ variable "vpn_onprem_configs" {
}
peer_external_gateway = {
redundancy_type = "SINGLE_IP_INTERNALLY_REDUNDANT"
- interfaces = [
- { id = 0, ip_address = "8.8.8.8" },
- ]
+ interfaces = ["8.8.8.8"]
}
tunnels = [
{
diff --git a/fast/stages/02-networking-vpn/vpn-onprem.tf b/fast/stages/02-networking-vpn/vpn-onprem.tf
index 48cad54b..5237a05a 100644
--- a/fast/stages/02-networking-vpn/vpn-onprem.tf
+++ b/fast/stages/02-networking-vpn/vpn-onprem.tf
@@ -33,16 +33,19 @@ locals {
}
module "landing-to-onprem-ew1-vpn" {
- count = local.enable_onprem_vpn ? 1 : 0
- source = "../../../modules/net-vpn-ha"
- project_id = module.landing-project.project_id
- network = module.landing-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-onprem-ew1"
- router_create = true
- router_name = "landing-onprem-vpn-ew1"
- router_asn = var.router_onprem_configs.landing-ew1.asn
- peer_external_gateway = var.vpn_onprem_configs.landing-ew1.peer_external_gateway
+ count = local.enable_onprem_vpn ? 1 : 0
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.landing-project.project_id
+ network = module.landing-vpc.self_link
+ region = "europe-west1"
+ name = "vpn-to-onprem-ew1"
+ router_config = {
+ name = "landing-onprem-vpn-ew1"
+ asn = var.router_onprem_configs.landing-ew1.asn
+ }
+ peer_gateway = {
+ external = var.vpn_onprem_configs.landing-ew1.peer_external_gateway
+ }
tunnels = {
for t in var.vpn_onprem_configs.landing-ew1.tunnels :
"remote-${t.vpn_gateway_interface}-${t.peer_external_gateway_interface}" => {
@@ -52,9 +55,7 @@ module "landing-to-onprem-ew1-vpn" {
}
bgp_peer_options = local.bgp_peer_options_onprem.landing-ew1
bgp_session_range = "${cidrhost(t.session_range, 2)}/30"
- ike_version = 2
peer_external_gateway_interface = t.peer_external_gateway_interface
- router = null
shared_secret = t.secret
vpn_gateway_interface = t.vpn_gateway_interface
}
diff --git a/fast/stages/02-networking-vpn/vpn-spoke-dev.tf b/fast/stages/02-networking-vpn/vpn-spoke-dev.tf
index 1ad329a0..317560af 100644
--- a/fast/stages/02-networking-vpn/vpn-spoke-dev.tf
+++ b/fast/stages/02-networking-vpn/vpn-spoke-dev.tf
@@ -39,11 +39,13 @@ module "landing-to-dev-ew1-vpn" {
network = module.landing-vpc.self_link
region = "europe-west1"
name = "vpn-to-dev-ew1"
- # The router used for this VPN is managed in vpn-prod.tf
- router_create = false
- router_name = "landing-vpn-ew1"
- router_asn = var.router_spoke_configs.landing-ew1.asn
- peer_gcp_gateway = module.dev-to-landing-ew1-vpn.self_link
+ router_config = {
+ # The router used for this VPN is managed in vpn-prod.tf
+ create = false
+ name = "landing-vpn-ew1"
+ asn = var.router_spoke_configs.landing-ew1.asn
+ }
+ peer_gateway = { gcp = module.dev-to-landing-ew1-vpn.self_link }
tunnels = {
0 = {
bgp_peer = {
@@ -54,11 +56,7 @@ module "landing-to-dev-ew1-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.0/27", 2)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = null
- vpn_gateway_interface = 0
+ vpn_gateway_interface = 0
}
1 = {
bgp_peer = {
@@ -69,11 +67,7 @@ module "landing-to-dev-ew1-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.0/27", 6)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = null
- vpn_gateway_interface = 1
+ vpn_gateway_interface = 1
}
}
depends_on = [
@@ -82,15 +76,16 @@ module "landing-to-dev-ew1-vpn" {
}
module "dev-to-landing-ew1-vpn" {
- source = "../../../modules/net-vpn-ha"
- project_id = module.dev-spoke-project.project_id
- network = module.dev-spoke-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-landing-ew1"
- router_create = true
- router_name = "dev-spoke-vpn-ew1"
- router_asn = var.router_spoke_configs.spoke-dev-ew1.asn
- peer_gcp_gateway = module.landing-to-dev-ew1-vpn.self_link
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.dev-spoke-project.project_id
+ network = module.dev-spoke-vpc.self_link
+ region = "europe-west1"
+ name = "vpn-to-landing-ew1"
+ router_config = {
+ name = "dev-spoke-vpn-ew1"
+ asn = var.router_spoke_configs.spoke-dev-ew1.asn
+ }
+ peer_gateway = { gcp = module.landing-to-dev-ew1-vpn.self_link }
tunnels = {
0 = {
bgp_peer = {
@@ -101,11 +96,8 @@ module "dev-to-landing-ew1-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.0/27", 1)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = module.landing-to-dev-ew1-vpn.random_secret
- vpn_gateway_interface = 0
+ shared_secret = module.landing-to-dev-ew1-vpn.random_secret
+ vpn_gateway_interface = 0
}
1 = {
bgp_peer = {
@@ -116,11 +108,8 @@ module "dev-to-landing-ew1-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.0/27", 5)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = module.landing-to-dev-ew1-vpn.random_secret
- vpn_gateway_interface = 1
+ shared_secret = module.landing-to-dev-ew1-vpn.random_secret
+ vpn_gateway_interface = 1
}
}
}
diff --git a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf b/fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf
index 9562e4ce..a215ad4e 100644
--- a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf
+++ b/fast/stages/02-networking-vpn/vpn-spoke-prod-ew1.tf
@@ -19,15 +19,16 @@
# local.vpn_spoke_bgp_peer_options is defined in the dev VPN file
module "landing-to-prod-ew1-vpn" {
- source = "../../../modules/net-vpn-ha"
- project_id = module.landing-project.project_id
- network = module.landing-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-prod-ew1"
- router_create = true
- router_name = "landing-vpn-ew1"
- router_asn = var.router_spoke_configs.landing-ew1.asn
- peer_gcp_gateway = module.prod-to-landing-ew1-vpn.self_link
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.landing-project.project_id
+ network = module.landing-vpc.self_link
+ region = "europe-west1"
+ name = "vpn-to-prod-ew1"
+ router_config = {
+ name = "landing-vpn-ew1"
+ asn = var.router_spoke_configs.landing-ew1.asn
+ }
+ peer_gateway = { gcp = module.prod-to-landing-ew1-vpn.self_link }
tunnels = {
0 = {
bgp_peer = {
@@ -38,11 +39,7 @@ module "landing-to-prod-ew1-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.64/27", 2)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = null
- vpn_gateway_interface = 0
+ vpn_gateway_interface = 0
}
1 = {
bgp_peer = {
@@ -53,25 +50,22 @@ module "landing-to-prod-ew1-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.64/27", 6)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = null
- vpn_gateway_interface = 1
+ vpn_gateway_interface = 1
}
}
}
module "prod-to-landing-ew1-vpn" {
- source = "../../../modules/net-vpn-ha"
- project_id = module.prod-spoke-project.project_id
- network = module.prod-spoke-vpc.self_link
- region = "europe-west1"
- name = "vpn-to-landing-ew1"
- router_create = true
- router_name = "prod-spoke-vpn-ew1"
- router_asn = var.router_spoke_configs.spoke-prod-ew1.asn
- peer_gcp_gateway = module.landing-to-prod-ew1-vpn.self_link
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.prod-spoke-project.project_id
+ network = module.prod-spoke-vpc.self_link
+ region = "europe-west1"
+ name = "vpn-to-landing-ew1"
+ router_config = {
+ name = "prod-spoke-vpn-ew1"
+ asn = var.router_spoke_configs.spoke-prod-ew1.asn
+ }
+ peer_gateway = { gcp = module.landing-to-prod-ew1-vpn.self_link }
tunnels = {
0 = {
bgp_peer = {
@@ -82,11 +76,8 @@ module "prod-to-landing-ew1-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.64/27", 1)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = module.landing-to-prod-ew1-vpn.random_secret
- vpn_gateway_interface = 0
+ shared_secret = module.landing-to-prod-ew1-vpn.random_secret
+ vpn_gateway_interface = 0
}
1 = {
bgp_peer = {
@@ -97,11 +88,8 @@ module "prod-to-landing-ew1-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.64/27", 5)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = module.landing-to-prod-ew1-vpn.random_secret
- vpn_gateway_interface = 1
+ shared_secret = module.landing-to-prod-ew1-vpn.random_secret
+ vpn_gateway_interface = 1
}
}
}
diff --git a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf b/fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf
index cbee0bef..994fba0b 100644
--- a/fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf
+++ b/fast/stages/02-networking-vpn/vpn-spoke-prod-ew4.tf
@@ -19,15 +19,16 @@
# local.vpn_spoke_bgp_peer_options is defined in the dev VPN file
module "landing-to-prod-ew4-vpn" {
- source = "../../../modules/net-vpn-ha"
- project_id = module.landing-project.project_id
- network = module.landing-vpc.self_link
- region = "europe-west4"
- name = "vpn-to-prod-ew4"
- router_create = true
- router_name = "landing-vpn-ew4"
- router_asn = var.router_spoke_configs.landing-ew4.asn
- peer_gcp_gateway = module.prod-to-landing-ew4-vpn.self_link
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.landing-project.project_id
+ network = module.landing-vpc.self_link
+ region = "europe-west4"
+ name = "vpn-to-prod-ew4"
+ router_config = {
+ name = "landing-vpn-ew4"
+ asn = var.router_spoke_configs.landing-ew4.asn
+ }
+ peer_gateway = { gcp = module.prod-to-landing-ew4-vpn.self_link }
tunnels = {
0 = {
bgp_peer = {
@@ -38,11 +39,7 @@ module "landing-to-prod-ew4-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.96/27", 2)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = null
- vpn_gateway_interface = 0
+ vpn_gateway_interface = 0
}
1 = {
bgp_peer = {
@@ -53,25 +50,22 @@ module "landing-to-prod-ew4-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.96/27", 6)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = null
- vpn_gateway_interface = 1
+ vpn_gateway_interface = 1
}
}
}
module "prod-to-landing-ew4-vpn" {
- source = "../../../modules/net-vpn-ha"
- project_id = module.prod-spoke-project.project_id
- network = module.prod-spoke-vpc.self_link
- region = "europe-west4"
- name = "vpn-to-landing-ew4"
- router_create = true
- router_name = "prod-spoke-vpn-ew4"
- router_asn = var.router_spoke_configs.spoke-prod-ew4.asn
- peer_gcp_gateway = module.landing-to-prod-ew4-vpn.self_link
+ source = "../../../modules/net-vpn-ha"
+ project_id = module.prod-spoke-project.project_id
+ network = module.prod-spoke-vpc.self_link
+ region = "europe-west4"
+ name = "vpn-to-landing-ew4"
+ router_config = {
+ name = "prod-spoke-vpn-ew4"
+ asn = var.router_spoke_configs.spoke-prod-ew4.asn
+ }
+ peer_gateway = { gcp = module.landing-to-prod-ew4-vpn.self_link }
tunnels = {
0 = {
bgp_peer = {
@@ -82,11 +76,8 @@ module "prod-to-landing-ew4-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.96/27", 1)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = module.landing-to-prod-ew4-vpn.random_secret
- vpn_gateway_interface = 0
+ shared_secret = module.landing-to-prod-ew4-vpn.random_secret
+ vpn_gateway_interface = 0
}
1 = {
bgp_peer = {
@@ -97,11 +88,8 @@ module "prod-to-landing-ew4-vpn" {
bgp_session_range = "${
cidrhost("169.254.0.96/27", 5)
}/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = module.landing-to-prod-ew4-vpn.random_secret
- vpn_gateway_interface = 1
+ shared_secret = module.landing-to-prod-ew4-vpn.random_secret
+ vpn_gateway_interface = 1
}
}
}
diff --git a/fast/stages/02-security/README.md b/fast/stages/02-security/README.md
index 72e93606..026ef771 100644
--- a/fast/stages/02-security/README.md
+++ b/fast/stages/02-security/README.md
@@ -224,17 +224,17 @@ vpc_sc_perimeters = {
ingress_policies = ["iac"]
resources = ["projects/1111111111"]
}
- dev = {
- egress_policies = ["iac-gcs"]
- ingress_policies = ["iac"]
- resources = ["projects/0000000000"]
- }
- dev = {
+ landing = {
access_levels = ["onprem"]
egress_policies = ["iac-gcs"]
ingress_policies = ["iac"]
resources = ["projects/2222222222"]
}
+ prod = {
+ egress_policies = ["iac-gcs"]
+ ingress_policies = ["iac"]
+ resources = ["projects/0000000000"]
+ }
}
```
diff --git a/modules/apigee/README.md b/modules/apigee/README.md
index 1425c8f9..62035e3c 100644
--- a/modules/apigee/README.md
+++ b/modules/apigee/README.md
@@ -169,12 +169,12 @@ module "apigee" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [project_id](variables.tf#L76) | Project ID. | string
| ✓ | |
+| [project_id](variables.tf#L75) | Project ID. | string
| ✓ | |
| [endpoint_attachments](variables.tf#L17) | Endpoint attachments. | map(object({…}))
| | null
|
| [envgroups](variables.tf#L26) | Environment groups (NAME => [HOSTNAMES]). | map(list(string))
| | null
|
-| [environments](variables.tf#L32) | Environments. | map(object({…}))
| | null
|
-| [instances](variables.tf#L48) | Instances. | map(object({…}))
| | null
|
-| [organization](variables.tf#L62) | Apigee organization. If set to null the organization must already exist. | object({…})
| | null
|
+| [environments](variables.tf#L32) | Environments. | map(object({…}))
| | null
|
+| [instances](variables.tf#L47) | Instances. | map(object({…}))
| | null
|
+| [organization](variables.tf#L61) | Apigee organization. If set to null the organization must already exist. | object({…})
| | null
|
## Outputs
diff --git a/modules/apigee/main.tf b/modules/apigee/main.tf
index bc5dab48..fe34a738 100644
--- a/modules/apigee/main.tf
+++ b/modules/apigee/main.tf
@@ -47,12 +47,16 @@ resource "google_apigee_environment" "environments" {
dynamic "node_config" {
for_each = try(each.value.node_config, null) != null ? [""] : []
content {
- min_node_count = node_config.min_node_count
- max_node_count = node_config.max_node_count
- current_aggregate_node_count = node_config.current_aggregate_node_count
+ min_node_count = each.value.node_config.min_node_count
+ max_node_count = each.value.node_config.max_node_count
}
}
org_id = local.org_id
+ lifecycle {
+ ignore_changes = [
+ node_config["current_aggregate_node_count"]
+ ]
+ }
}
resource "google_apigee_envgroup_attachment" "envgroup_attachments" {
diff --git a/modules/apigee/variables.tf b/modules/apigee/variables.tf
index 8cddf9a4..266f0d34 100644
--- a/modules/apigee/variables.tf
+++ b/modules/apigee/variables.tf
@@ -35,9 +35,8 @@ variable "environments" {
display_name = optional(string)
description = optional(string, "Terraform-managed")
node_config = optional(object({
- min_node_count = optional(number)
- max_node_count = optional(number)
- current_aggregate_node_count = number
+ min_node_count = optional(number)
+ max_node_count = optional(number)
}))
iam = optional(map(list(string)))
envgroups = list(string)
@@ -76,4 +75,4 @@ variable "organization" {
variable "project_id" {
description = "Project ID."
type = string
-}
\ No newline at end of file
+}
diff --git a/modules/cloud-config-container/onprem/README.md b/modules/cloud-config-container/onprem/README.md
index fa29e48c..1a668a8d 100644
--- a/modules/cloud-config-container/onprem/README.md
+++ b/modules/cloud-config-container/onprem/README.md
@@ -24,17 +24,15 @@ The test instance is optional, as described above.
```hcl
module "cloud-vpn" {
- source = "./fabric/modules/net-vpn-static"
- project_id = "my-project"
- region = "europe-west1"
- network = "my-vpc"
- name = "to-on-prem"
+ source = "./fabric/modules/net-vpn-static"
+ project_id = "my-project"
+ region = "europe-west1"
+ network = "my-vpc"
+ name = "to-on-prem"
remote_ranges = ["192.168.192.0/24"]
tunnels = {
remote-0 = {
- ike_version = 2
peer_ip = module.on-prem.external_address
- shared_secret = ""
traffic_selectors = { local = ["0.0.0.0/0"], remote = null }
}
}
diff --git a/modules/cloud-config-container/onprem/docker-images/strongswan/Dockerfile b/modules/cloud-config-container/onprem/docker-images/strongswan/Dockerfile
index 7a22d943..8bb6165b 100644
--- a/modules/cloud-config-container/onprem/docker-images/strongswan/Dockerfile
+++ b/modules/cloud-config-container/onprem/docker-images/strongswan/Dockerfile
@@ -12,10 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-FROM alpine:latest
+FROM debian:bullseye-slim
-RUN set -xe \
- && apk add --no-cache strongswan bash sudo
+ENV STRONGSWAN_VERSION=5.9
+
+RUN apt-get update \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -y sudo iptables procps strongswan=${STRONGSWAN_VERSION}* \
+ && rm -rf /var/lib/apt/lists/*
COPY entrypoint.sh /entrypoint.sh
RUN chmod 0755 /entrypoint.sh
diff --git a/modules/cloud-config-container/onprem/docker-images/strongswan/entrypoint.sh b/modules/cloud-config-container/onprem/docker-images/strongswan/entrypoint.sh
index e99d1ec8..bf596bc0 100644
--- a/modules/cloud-config-container/onprem/docker-images/strongswan/entrypoint.sh
+++ b/modules/cloud-config-container/onprem/docker-images/strongswan/entrypoint.sh
@@ -22,7 +22,7 @@ _stop_ipsec() {
echo "Shutting down strongSwan/ipsec..."
ipsec stop
}
-trap _stop_ipsec SIGTERM
+trap _stop_ipsec TERM
# Making the containter to work as a default gateway for LAN_NETWORKS
iptables -t nat -A POSTROUTING -s ${LAN_NETWORKS} -o ${VPN_DEVICE} -m policy --dir out --pol ipsec -j ACCEPT
diff --git a/modules/net-vpc-firewall/README.md b/modules/net-vpc-firewall/README.md
index eb6912a7..06cca4fb 100644
--- a/modules/net-vpc-firewall/README.md
+++ b/modules/net-vpc-firewall/README.md
@@ -176,7 +176,7 @@ healthchecks:
|---|---|:---:|:---:|:---:|
| [network](variables.tf#L109) | Name of the network this set of firewall rules applies to. | string
| ✓ | |
| [project_id](variables.tf#L114) | Project id of the project that holds the network. | string
| ✓ | |
-| [default_rules_config](variables.tf#L17) | Optionally created convenience rules. Set the variable or individual members to null to disable. | object({…})
| | {}
|
+| [default_rules_config](variables.tf#L17) | Optionally created convenience rules. Set the 'disabled' attribute to true, or individual rule attributes to empty lists to disable. | object({…})
| | {}
|
| [egress_rules](variables.tf#L37) | List of egress rule definitions, default to deny action. | map(object({…}))
| | {}
|
| [factories_config](variables.tf#L60) | Paths to data files and folders that enable factory functionality. | object({…})
| | null
|
| [ingress_rules](variables.tf#L69) | List of ingress rule definitions, default to allow action. | map(object({…}))
| | {}
|
diff --git a/modules/net-vpc-firewall/variables.tf b/modules/net-vpc-firewall/variables.tf
index 86aea9e2..3e458acd 100644
--- a/modules/net-vpc-firewall/variables.tf
+++ b/modules/net-vpc-firewall/variables.tf
@@ -15,7 +15,7 @@
*/
variable "default_rules_config" {
- description = "Optionally created convenience rules. Set the variable or individual members to null to disable."
+ description = "Optionally created convenience rules. Set the 'disabled' attribute to true, or individual rule attributes to empty lists to disable."
type = object({
admin_ranges = optional(list(string))
disabled = optional(bool, false)
diff --git a/modules/net-vpn-dynamic/README.md b/modules/net-vpn-dynamic/README.md
index 1c8fb7b6..378ba6b7 100644
--- a/modules/net-vpn-dynamic/README.md
+++ b/modules/net-vpn-dynamic/README.md
@@ -8,35 +8,50 @@ This example shows how to configure a single VPN tunnel using a couple of extra
- internally generated shared secret, which can be fetched from the module's `random_secret` output for reuse; a predefined secret can be used instead by assigning it to the `shared_secret` attribute
```hcl
+module "vm" {
+ source = "./fabric/modules/compute-vm"
+ project_id = "my-project"
+ zone = "europe-west1-b"
+ name = "my-vm"
+ network_interfaces = [{
+ nat = true
+ network = var.vpc.self_link
+ subnetwork = var.subnet.self_link
+ }]
+ service_account_create = true
+}
+
+
module "vpn-dynamic" {
source = "./fabric/modules/net-vpn-dynamic"
project_id = "my-project"
region = "europe-west1"
- network = "my-vpc"
+ network = var.vpc.name
name = "gateway-1"
+ router_config = {
+ asn = 64514
+ }
+
tunnels = {
remote-1 = {
bgp_peer = {
address = "169.254.139.134"
asn = 64513
+ custom_advertise = {
+ all_subnets = true
+ all_vpc_subnets = false
+ all_peer_vpc_subnets = false
+ ip_ranges = {
+ "192.168.0.0/24" = "Advertised range description"
+ }
+ }
}
bgp_session_range = "169.254.139.133/30"
- ike_version = 2
- peer_ip = "1.1.1.1"
- router = null
- shared_secret = null
- bgp_peer_options = {
- advertise_groups = ["ALL_SUBNETS"]
- advertise_ip_ranges = {
- "192.168.0.0/24" = "Advertised range description"
- }
- advertise_mode = "CUSTOM"
- route_priority = 1000
- }
+ peer_ip = module.vm.external_ip
}
}
}
-# tftest modules=1 resources=10
+# tftest modules=2 resources=12
```
@@ -48,14 +63,10 @@ module "vpn-dynamic" {
| [network](variables.tf#L34) | VPC used for the gateway and routes. | string
| ✓ | |
| [project_id](variables.tf#L39) | Project where resources will be created. | string
| ✓ | |
| [region](variables.tf#L44) | Region used for resources. | string
| ✓ | |
-| [gateway_address](variables.tf#L17) | Optional address assigned to the VPN gateway. Ignored unless gateway_address_create is set to false. | string
| | ""
|
+| [router_config](variables.tf#L49) | Cloud Router configuration for the VPN. If you want to reuse an existing router, set create to false and use name to specify the desired router. | object({…})
| ✓ | |
+| [gateway_address](variables.tf#L17) | Optional address assigned to the VPN gateway. Ignored unless gateway_address_create is set to false. | string
| | null
|
| [gateway_address_create](variables.tf#L23) | Create external address assigned to the VPN gateway. Needs to be explicitly set to false to use address in gateway_address variable. | bool
| | true
|
-| [route_priority](variables.tf#L49) | Route priority, defaults to 1000. | number
| | 1000
|
-| [router_advertise_config](variables.tf#L55) | Router custom advertisement configuration, ip_ranges is a map of address ranges and descriptions. | object({…})
| | null
|
-| [router_asn](variables.tf#L65) | Router ASN used for auto-created router. | number
| | 64514
|
-| [router_create](variables.tf#L71) | Create router. | bool
| | true
|
-| [router_name](variables.tf#L77) | Router name used for auto created router, or to specify existing router to use. Leave blank to use VPN name for auto created router. | string
| | ""
|
-| [tunnels](variables.tf#L83) | VPN tunnel configurations, bgp_peer_options is usually null. | map(object({…}))
| | {}
|
+| [tunnels](variables.tf#L64) | VPN tunnel configurations. | map(object({…}))
| | {}
|
## Outputs
diff --git a/modules/net-vpn-dynamic/main.tf b/modules/net-vpn-dynamic/main.tf
index 4da48c12..fcf26934 100644
--- a/modules/net-vpn-dynamic/main.tf
+++ b/modules/net-vpn-dynamic/main.tf
@@ -21,9 +21,9 @@ locals {
: var.gateway_address
)
router = (
- var.router_create
- ? google_compute_router.router[0].name
- : var.router_name
+ var.router_config.create
+ ? try(google_compute_router.router[0].name, null)
+ : var.router_config.name
)
secret = random_id.secret.b64_url
}
@@ -65,75 +65,56 @@ resource "google_compute_forwarding_rule" "udp-4500" {
}
resource "google_compute_router" "router" {
- count = var.router_create ? 1 : 0
- name = var.router_name == "" ? "vpn-${var.name}" : var.router_name
+ count = var.router_config.create ? 1 : 0
+ name = coalesce(var.router_config.name, "vpn-${var.name}")
project = var.project_id
region = var.region
network = var.network
bgp {
advertise_mode = (
- var.router_advertise_config == null
- ? null
- : var.router_advertise_config.mode
+ var.router_config.custom_advertise != null
+ ? "CUSTOM"
+ : "DEFAULT"
)
advertised_groups = (
- var.router_advertise_config == null ? null : (
- var.router_advertise_config.mode != "CUSTOM"
- ? null
- : var.router_advertise_config.groups
- )
+ try(var.router_config.custom_advertise.all_subnets, false)
+ ? ["ALL_SUBNETS"]
+ : []
)
dynamic "advertised_ip_ranges" {
- for_each = (
- var.router_advertise_config == null ? {} : (
- var.router_advertise_config.mode != "CUSTOM"
- ? null
- : var.router_advertise_config.ip_ranges
- )
- )
+ for_each = try(var.router_config.custom_advertise.ip_ranges, {})
iterator = range
content {
range = range.key
description = range.value
}
}
- asn = var.router_asn
+ keepalive_interval = try(var.router_config.keepalive, null)
+ asn = var.router_config.asn
}
}
resource "google_compute_router_peer" "bgp_peer" {
- for_each = var.tunnels
- region = var.region
- project = var.project_id
- name = "${var.name}-${each.key}"
- router = each.value.router == null ? local.router : each.value.router
- peer_ip_address = each.value.bgp_peer.address
- peer_asn = each.value.bgp_peer.asn
- advertised_route_priority = (
- each.value.bgp_peer_options == null ? var.route_priority : (
- each.value.bgp_peer_options.route_priority == null
- ? var.route_priority
- : each.value.bgp_peer_options.route_priority
- )
- )
+ for_each = var.tunnels
+ region = var.region
+ project = var.project_id
+ name = "${var.name}-${each.key}"
+ router = coalesce(each.value.router, local.router)
+ peer_ip_address = each.value.bgp_peer.address
+ peer_asn = each.value.bgp_peer.asn
+ advertised_route_priority = each.value.bgp_peer.route_priority
advertise_mode = (
- each.value.bgp_peer_options == null ? null : each.value.bgp_peer_options.advertise_mode
+ try(each.value.bgp_peer.custom_advertise, null) != null
+ ? "CUSTOM"
+ : "DEFAULT"
)
- advertised_groups = (
- each.value.bgp_peer_options == null ? null : (
- each.value.bgp_peer_options.advertise_mode != "CUSTOM"
- ? null
- : each.value.bgp_peer_options.advertise_groups
- )
+ advertised_groups = concat(
+ try(each.value.bgp_peer.custom_advertise.all_subnets, false) ? ["ALL_SUBNETS"] : [],
+ try(each.value.bgp_peer.custom_advertise.all_vpc_subnets, false) ? ["ALL_VPC_SUBNETS"] : [],
+ try(each.value.bgp_peer.custom_advertise.all_peer_vpc_subnets, false) ? ["ALL_PEER_VPC_SUBNETS"] : []
)
dynamic "advertised_ip_ranges" {
- for_each = (
- each.value.bgp_peer_options == null ? {} : (
- each.value.bgp_peer_options.advertise_mode != "CUSTOM"
- ? {}
- : each.value.bgp_peer_options.advertise_ip_ranges
- )
- )
+ for_each = try(each.value.bgp_peer.custom_advertise.ip_ranges, {})
iterator = range
content {
range = range.key
@@ -144,11 +125,12 @@ resource "google_compute_router_peer" "bgp_peer" {
}
resource "google_compute_router_interface" "router_interface" {
- for_each = var.tunnels
- project = var.project_id
- region = var.region
- name = "${var.name}-${each.key}"
- router = each.value.router == null ? local.router : each.value.router
+ for_each = var.tunnels
+ project = var.project_id
+ region = var.region
+ name = "${var.name}-${each.key}"
+ router = coalesce(each.value.router, local.router)
+ # FIXME: can bgp_session_range be null?
ip_range = each.value.bgp_session_range == "" ? null : each.value.bgp_session_range
vpn_tunnel = google_compute_vpn_tunnel.tunnels[each.key].name
}
@@ -161,18 +143,14 @@ resource "google_compute_vpn_gateway" "gateway" {
}
resource "google_compute_vpn_tunnel" "tunnels" {
- for_each = var.tunnels
- project = var.project_id
- region = var.region
- name = "${var.name}-${each.key}"
- router = each.value.router == null ? local.router : each.value.router
- peer_ip = each.value.peer_ip
- ike_version = each.value.ike_version
- shared_secret = (
- each.value.shared_secret == "" || each.value.shared_secret == null
- ? local.secret
- : each.value.shared_secret
- )
+ for_each = var.tunnels
+ project = var.project_id
+ region = var.region
+ name = "${var.name}-${each.key}"
+ router = coalesce(each.value.router, local.router)
+ peer_ip = each.value.peer_ip
+ ike_version = each.value.ike_version
+ shared_secret = coalesce(each.value.shared_secret, local.secret)
target_vpn_gateway = google_compute_vpn_gateway.gateway.self_link
depends_on = [google_compute_forwarding_rule.esp]
}
diff --git a/modules/net-vpn-dynamic/outputs.tf b/modules/net-vpn-dynamic/outputs.tf
index 09e5959e..f049df1d 100644
--- a/modules/net-vpn-dynamic/outputs.tf
+++ b/modules/net-vpn-dynamic/outputs.tf
@@ -37,7 +37,7 @@ output "random_secret" {
output "router" {
description = "Router resource (only if auto-created)."
- value = var.router_create ? google_compute_router.router[0] : null
+ value = one(google_compute_router.router[*])
}
output "router_name" {
@@ -54,7 +54,7 @@ output "tunnel_names" {
description = "VPN tunnel names."
value = {
for name in keys(var.tunnels) :
- name => google_compute_vpn_tunnel.tunnels[name].name
+ name => try(google_compute_vpn_tunnel.tunnels[name].name, null)
}
}
@@ -62,7 +62,7 @@ output "tunnel_self_links" {
description = "VPN tunnel self links."
value = {
for name in keys(var.tunnels) :
- name => google_compute_vpn_tunnel.tunnels[name].self_link
+ name => try(google_compute_vpn_tunnel.tunnels[name].self_link, null)
}
}
@@ -70,6 +70,6 @@ output "tunnels" {
description = "VPN tunnel resources."
value = {
for name in keys(var.tunnels) :
- name => google_compute_vpn_tunnel.tunnels[name]
+ name => try(google_compute_vpn_tunnel.tunnels[name], null)
}
}
diff --git a/modules/net-vpn-dynamic/variables.tf b/modules/net-vpn-dynamic/variables.tf
index 6da5c0ac..33d23a04 100644
--- a/modules/net-vpn-dynamic/variables.tf
+++ b/modules/net-vpn-dynamic/variables.tf
@@ -17,7 +17,7 @@
variable "gateway_address" {
description = "Optional address assigned to the VPN gateway. Ignored unless gateway_address_create is set to false."
type = string
- default = ""
+ default = null
}
variable "gateway_address_create" {
@@ -46,60 +46,43 @@ variable "region" {
type = string
}
-variable "route_priority" {
- description = "Route priority, defaults to 1000."
- type = number
- default = 1000
-}
-
-variable "router_advertise_config" {
- description = "Router custom advertisement configuration, ip_ranges is a map of address ranges and descriptions."
+variable "router_config" {
+ description = "Cloud Router configuration for the VPN. If you want to reuse an existing router, set create to false and use name to specify the desired router."
type = object({
- groups = list(string)
- ip_ranges = map(string)
- mode = string
+ create = optional(bool, true)
+ asn = number
+ name = optional(string)
+ keepalive = optional(number)
+ custom_advertise = optional(object({
+ all_subnets = bool
+ ip_ranges = map(string)
+ }))
})
- default = null
-}
-
-variable "router_asn" {
- description = "Router ASN used for auto-created router."
- type = number
- default = 64514
-}
-
-variable "router_create" {
- description = "Create router."
- type = bool
- default = true
-}
-
-variable "router_name" {
- description = "Router name used for auto created router, or to specify existing router to use. Leave blank to use VPN name for auto created router."
- type = string
- default = ""
+ nullable = false
}
variable "tunnels" {
- description = "VPN tunnel configurations, bgp_peer_options is usually null."
+ description = "VPN tunnel configurations."
type = map(object({
bgp_peer = object({
- address = string
- asn = number
- })
- bgp_peer_options = object({
- advertise_groups = list(string)
- advertise_ip_ranges = map(string)
- advertise_mode = string
- route_priority = number
+ address = string
+ asn = number
+ route_priority = optional(number, 1000)
+ custom_advertise = optional(object({
+ all_subnets = bool
+ all_vpc_subnets = bool
+ all_peer_vpc_subnets = bool
+ ip_ranges = map(string)
+ }))
})
# each BGP session on the same Cloud Router must use a unique /30 CIDR
# from the 169.254.0.0/16 block.
bgp_session_range = string
- ike_version = number
+ ike_version = optional(number, 2)
peer_ip = string
- router = string
- shared_secret = string
+ router = optional(string)
+ shared_secret = optional(string)
}))
- default = {}
+ default = {}
+ nullable = false
}
diff --git a/modules/net-vpn-ha/README.md b/modules/net-vpn-ha/README.md
index b79a25f1..e6cd752c 100644
--- a/modules/net-vpn-ha/README.md
+++ b/modules/net-vpn-ha/README.md
@@ -5,20 +5,21 @@ This module makes it easy to deploy either GCP-to-GCP or GCP-to-On-prem [Cloud H
### GCP to GCP
```hcl
-module "vpn_ha-1" {
- source = "./fabric/modules/net-vpn-ha"
- project_id = ""
- region = "europe-west4"
- network = "https://www.googleapis.com/compute/v1/projects//global/networks/network-1"
- name = "net1-to-net-2"
- peer_gcp_gateway = module.vpn_ha-2.self_link
- router_asn = 64514
- router_advertise_config = {
- groups = ["ALL_SUBNETS"]
- ip_ranges = {
- "10.0.0.0/8" = "default"
+module "vpn-1" {
+ source = "./fabric/modules/net-vpn-ha"
+ project_id = var.project_id
+ region = "europe-west4"
+ network = var.vpc1.self_link
+ name = "net1-to-net-2"
+ peer_gateway = { gcp = module.vpn-2.self_link }
+ router_config = {
+ asn = 64514
+ custom_advertise = {
+ all_subnets = true
+ ip_ranges = {
+ "10.0.0.0/8" = "default"
+ }
}
- mode = "CUSTOM"
}
tunnels = {
remote-0 = {
@@ -26,64 +27,46 @@ module "vpn_ha-1" {
address = "169.254.1.1"
asn = 64513
}
- bgp_peer_options = null
- bgp_session_range = "169.254.1.2/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = ""
- vpn_gateway_interface = 0
+ bgp_session_range = "169.254.1.2/30"
+ vpn_gateway_interface = 0
}
remote-1 = {
bgp_peer = {
address = "169.254.2.1"
asn = 64513
}
- bgp_peer_options = null
- bgp_session_range = "169.254.2.2/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = ""
- vpn_gateway_interface = 1
+ bgp_session_range = "169.254.2.2/30"
+ vpn_gateway_interface = 1
}
}
}
-module "vpn_ha-2" {
- source = "./fabric/modules/net-vpn-ha"
- project_id = ""
- region = "europe-west4"
- network = "https://www.googleapis.com/compute/v1/projects//global/networks/local-network"
- name = "net2-to-net1"
- router_asn = 64513
- peer_gcp_gateway = module.vpn_ha-1.self_link
+module "vpn-2" {
+ source = "./fabric/modules/net-vpn-ha"
+ project_id = var.project_id
+ region = "europe-west4"
+ network = var.vpc2.self_link
+ name = "net2-to-net1"
+ router_config = { asn = 64513 }
+ peer_gateway = { gcp = module.vpn-1.self_link}
tunnels = {
remote-0 = {
bgp_peer = {
address = "169.254.1.2"
asn = 64514
}
- bgp_peer_options = null
- bgp_session_range = "169.254.1.1/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = module.vpn_ha-1.random_secret
- vpn_gateway_interface = 0
+ bgp_session_range = "169.254.1.1/30"
+ shared_secret = module.vpn-1.random_secret
+ vpn_gateway_interface = 0
}
remote-1 = {
bgp_peer = {
address = "169.254.2.2"
asn = 64514
}
- bgp_peer_options = null
- bgp_session_range = "169.254.2.1/30"
- ike_version = 2
- peer_external_gateway_interface = null
- router = null
- shared_secret = module.vpn_ha-1.random_secret
- vpn_gateway_interface = 1
+ bgp_session_range = "169.254.2.1/30"
+ shared_secret = module.vpn-1.random_secret
+ vpn_gateway_interface = 1
}
}
}
@@ -101,25 +84,21 @@ module "vpn_ha" {
region = var.region
network = var.vpc.self_link
name = "mynet-to-onprem"
- peer_external_gateway = {
- redundancy_type = "SINGLE_IP_INTERNALLY_REDUNDANT"
- interfaces = [{
- id = 0
- ip_address = "8.8.8.8" # on-prem router ip address
- }]
+ peer_gateway = {
+ external = {
+ redundancy_type = "SINGLE_IP_INTERNALLY_REDUNDANT"
+ interfaces = ["8.8.8.8"] # on-prem router ip address
+ }
}
- router_asn = 64514
+ router_config = { asn = 64514 }
tunnels = {
remote-0 = {
bgp_peer = {
address = "169.254.1.1"
asn = 64513
}
- bgp_peer_options = null
bgp_session_range = "169.254.1.2/30"
- ike_version = 2
peer_external_gateway_interface = 0
- router = null
shared_secret = "mySecret"
vpn_gateway_interface = 0
}
@@ -128,11 +107,8 @@ module "vpn_ha" {
address = "169.254.2.1"
asn = 64513
}
- bgp_peer_options = null
bgp_session_range = "169.254.2.2/30"
- ike_version = 2
peer_external_gateway_interface = 0
- router = null
shared_secret = "mySecret"
vpn_gateway_interface = 1
}
@@ -148,18 +124,13 @@ module "vpn_ha" {
|---|---|:---:|:---:|:---:|
| [name](variables.tf#L17) | VPN Gateway name (if an existing VPN Gateway is not used), and prefix used for dependent resources. | string
| ✓ | |
| [network](variables.tf#L22) | VPC used for the gateway and routes. | string
| ✓ | |
-| [project_id](variables.tf#L45) | Project where resources will be created. | string
| ✓ | |
-| [region](variables.tf#L50) | Region used for resources. | string
| ✓ | |
-| [peer_external_gateway](variables.tf#L27) | Configuration of an external VPN gateway to which this VPN is connected. | object({…})
| | null
|
-| [peer_gcp_gateway](variables.tf#L39) | Self Link URL of the peer side HA GCP VPN gateway to which this VPN tunnel is connected. | string
| | null
|
-| [route_priority](variables.tf#L55) | Route priority, defaults to 1000. | number
| | 1000
|
-| [router_advertise_config](variables.tf#L61) | Router custom advertisement configuration, ip_ranges is a map of address ranges and descriptions. | object({…})
| | null
|
-| [router_asn](variables.tf#L71) | Router ASN used for auto-created router. | number
| | 64514
|
-| [router_create](variables.tf#L77) | Create router. | bool
| | true
|
-| [router_name](variables.tf#L83) | Router name used for auto created router, or to specify an existing router to use if `router_create` is set to `true`. Leave blank to use VPN name for auto created router. | string
| | ""
|
-| [tunnels](variables.tf#L89) | VPN tunnel configurations, bgp_peer_options is usually null. | map(object({…}))
| | {}
|
-| [vpn_gateway](variables.tf#L114) | HA VPN Gateway Self Link for using an existing HA VPN Gateway, leave empty if `vpn_gateway_create` is set to `true`. | string
| | null
|
-| [vpn_gateway_create](variables.tf#L120) | Create HA VPN Gateway. | bool
| | true
|
+| [peer_gateway](variables.tf#L27) | Configuration of the (external or GCP) peer gateway. | object({…})
| ✓ | |
+| [project_id](variables.tf#L43) | Project where resources will be created. | string
| ✓ | |
+| [region](variables.tf#L48) | Region used for resources. | string
| ✓ | |
+| [router_config](variables.tf#L53) | Cloud Router configuration for the VPN. If you want to reuse an existing router, set create to false and use name to specify the desired router. | object({…})
| ✓ | |
+| [tunnels](variables.tf#L68) | VPN tunnel configurations. | map(object({…}))
| | {}
|
+| [vpn_gateway](variables.tf#L95) | HA VPN Gateway Self Link for using an existing HA VPN Gateway. Ignored if `vpn_gateway_create` is set to `true`. | string
| | null
|
+| [vpn_gateway_create](variables.tf#L101) | Create HA VPN Gateway. | bool
| | true
|
## Outputs
@@ -167,14 +138,14 @@ module "vpn_ha" {
|---|---|:---:|
| [bgp_peers](outputs.tf#L18) | BGP peer resources. | |
| [external_gateway](outputs.tf#L25) | External VPN gateway resource. | |
-| [gateway](outputs.tf#L34) | VPN gateway resource (only if auto-created). | |
-| [name](outputs.tf#L43) | VPN gateway name (only if auto-created). . | |
-| [random_secret](outputs.tf#L52) | Generated secret. | |
-| [router](outputs.tf#L57) | Router resource (only if auto-created). | |
-| [router_name](outputs.tf#L66) | Router name. | |
-| [self_link](outputs.tf#L71) | HA VPN gateway self link. | |
-| [tunnel_names](outputs.tf#L76) | VPN tunnel names. | |
-| [tunnel_self_links](outputs.tf#L84) | VPN tunnel self links. | |
-| [tunnels](outputs.tf#L92) | VPN tunnel resources. | |
+| [gateway](outputs.tf#L30) | VPN gateway resource (only if auto-created). | |
+| [name](outputs.tf#L35) | VPN gateway name (only if auto-created). . | |
+| [random_secret](outputs.tf#L40) | Generated secret. | |
+| [router](outputs.tf#L45) | Router resource (only if auto-created). | |
+| [router_name](outputs.tf#L50) | Router name. | |
+| [self_link](outputs.tf#L55) | HA VPN gateway self link. | |
+| [tunnel_names](outputs.tf#L60) | VPN tunnel names. | |
+| [tunnel_self_links](outputs.tf#L68) | VPN tunnel self links. | |
+| [tunnels](outputs.tf#L76) | VPN tunnel resources. | |
diff --git a/modules/net-vpn-ha/main.tf b/modules/net-vpn-ha/main.tf
index 8c487031..9d53ee08 100644
--- a/modules/net-vpn-ha/main.tf
+++ b/modules/net-vpn-ha/main.tf
@@ -16,16 +16,10 @@
*/
locals {
- peer_external_gateway = (
- var.peer_external_gateway != null
- ? google_compute_external_vpn_gateway.external_gateway[0].self_link
- : null
-
- )
router = (
- var.router_create
+ var.router_config.create
? try(google_compute_router.router[0].name, null)
- : var.router_name
+ : var.router_config.name
)
vpn_gateway = (
var.vpn_gateway_create
@@ -36,100 +30,79 @@ locals {
}
resource "google_compute_ha_vpn_gateway" "ha_gateway" {
- provider = google-beta
- count = var.vpn_gateway_create ? 1 : 0
- name = var.name
- project = var.project_id
- region = var.region
- network = var.network
+ count = var.vpn_gateway_create ? 1 : 0
+ name = var.name
+ project = var.project_id
+ region = var.region
+ network = var.network
}
resource "google_compute_external_vpn_gateway" "external_gateway" {
- provider = google-beta
- count = var.peer_external_gateway != null ? 1 : 0
+ count = var.peer_gateway.external != null ? 1 : 0
name = "external-${var.name}"
project = var.project_id
- redundancy_type = var.peer_external_gateway.redundancy_type
+ redundancy_type = var.peer_gateway.external.redundancy_type
description = "Terraform managed external VPN gateway"
dynamic "interface" {
- for_each = var.peer_external_gateway.interfaces
+ for_each = var.peer_gateway.external.interfaces
content {
- id = interface.value.id
- ip_address = interface.value.ip_address
+ id = interface.key
+ ip_address = interface.value
}
}
}
resource "google_compute_router" "router" {
- count = var.router_create ? 1 : 0
- name = var.router_name == "" ? "vpn-${var.name}" : var.router_name
+ count = var.router_config.create ? 1 : 0
+ name = coalesce(var.router_config.name, "vpn-${var.name}")
project = var.project_id
region = var.region
network = var.network
bgp {
advertise_mode = (
- var.router_advertise_config == null
- ? null
- : var.router_advertise_config.mode
+ var.router_config.custom_advertise != null
+ ? "CUSTOM"
+ : "DEFAULT"
)
advertised_groups = (
- var.router_advertise_config == null ? null : (
- var.router_advertise_config.mode != "CUSTOM"
- ? null
- : var.router_advertise_config.groups
- )
+ try(var.router_config.custom_advertise.all_subnets, false)
+ ? ["ALL_SUBNETS"]
+ : []
)
dynamic "advertised_ip_ranges" {
- for_each = (
- var.router_advertise_config == null ? {} : (
- var.router_advertise_config.mode != "CUSTOM"
- ? null
- : var.router_advertise_config.ip_ranges
- )
- )
+ for_each = try(var.router_config.custom_advertise.ip_ranges, {})
iterator = range
content {
range = range.key
description = range.value
}
}
- asn = var.router_asn
+ keepalive_interval = try(var.router_config.keepalive, null)
+ asn = var.router_config.asn
}
}
resource "google_compute_router_peer" "bgp_peer" {
- for_each = var.tunnels
- region = var.region
- project = var.project_id
- name = "${var.name}-${each.key}"
- router = local.router
- peer_ip_address = each.value.bgp_peer.address
- peer_asn = each.value.bgp_peer.asn
- advertised_route_priority = (
- each.value.bgp_peer_options == null ? var.route_priority : (
- each.value.bgp_peer_options.route_priority == null
- ? var.route_priority
- : each.value.bgp_peer_options.route_priority
- )
- )
+ for_each = var.tunnels
+ region = var.region
+ project = var.project_id
+ name = "${var.name}-${each.key}"
+ router = coalesce(each.value.router, local.router)
+ peer_ip_address = each.value.bgp_peer.address
+ peer_asn = each.value.bgp_peer.asn
+ advertised_route_priority = each.value.bgp_peer.route_priority
advertise_mode = (
- each.value.bgp_peer_options == null ? null : each.value.bgp_peer_options.advertise_mode
+ try(each.value.bgp_peer.custom_advertise, null) != null
+ ? "CUSTOM"
+ : "DEFAULT"
)
- advertised_groups = (
- each.value.bgp_peer_options == null ? null : (
- each.value.bgp_peer_options.advertise_mode != "CUSTOM"
- ? null
- : each.value.bgp_peer_options.advertise_groups
- )
+ advertised_groups = concat(
+ try(each.value.bgp_peer.custom_advertise.all_subnets, false) ? ["ALL_SUBNETS"] : [],
+ try(each.value.bgp_peer.custom_advertise.all_vpc_subnets, false) ? ["ALL_VPC_SUBNETS"] : [],
+ try(each.value.bgp_peer.custom_advertise.all_peer_vpc_subnets, false) ? ["ALL_PEER_VPC_SUBNETS"] : []
)
dynamic "advertised_ip_ranges" {
- for_each = (
- each.value.bgp_peer_options == null ? {} : (
- each.value.bgp_peer_options.advertise_mode != "CUSTOM"
- ? {}
- : each.value.bgp_peer_options.advertise_ip_ranges
- )
- )
+ for_each = try(each.value.bgp_peer.custom_advertise.ip_ranges, {})
iterator = range
content {
range = range.key
@@ -140,33 +113,29 @@ resource "google_compute_router_peer" "bgp_peer" {
}
resource "google_compute_router_interface" "router_interface" {
- for_each = var.tunnels
- project = var.project_id
- region = var.region
- name = "${var.name}-${each.key}"
- router = local.router
+ for_each = var.tunnels
+ project = var.project_id
+ region = var.region
+ name = "${var.name}-${each.key}"
+ router = local.router
+ # FIXME: can bgp_session_range be null?
ip_range = each.value.bgp_session_range == "" ? null : each.value.bgp_session_range
vpn_tunnel = google_compute_vpn_tunnel.tunnels[each.key].name
}
resource "google_compute_vpn_tunnel" "tunnels" {
- provider = google-beta
for_each = var.tunnels
project = var.project_id
region = var.region
name = "${var.name}-${each.key}"
router = local.router
- peer_external_gateway = local.peer_external_gateway
+ peer_external_gateway = one(google_compute_external_vpn_gateway.external_gateway[*].self_link)
peer_external_gateway_interface = each.value.peer_external_gateway_interface
- peer_gcp_gateway = var.peer_gcp_gateway
+ peer_gcp_gateway = var.peer_gateway.gcp
vpn_gateway_interface = each.value.vpn_gateway_interface
ike_version = each.value.ike_version
- shared_secret = (
- each.value.shared_secret == "" || each.value.shared_secret == null
- ? local.secret
- : each.value.shared_secret
- )
- vpn_gateway = local.vpn_gateway
+ shared_secret = coalesce(each.value.shared_secret, local.secret)
+ vpn_gateway = local.vpn_gateway
}
resource "random_id" "secret" {
diff --git a/modules/net-vpn-ha/outputs.tf b/modules/net-vpn-ha/outputs.tf
index 94aac982..98b83394 100644
--- a/modules/net-vpn-ha/outputs.tf
+++ b/modules/net-vpn-ha/outputs.tf
@@ -24,29 +24,17 @@ output "bgp_peers" {
output "external_gateway" {
description = "External VPN gateway resource."
- value = (
- var.peer_external_gateway != null
- ? google_compute_external_vpn_gateway.external_gateway[0]
- : null
- )
+ value = one(google_compute_external_vpn_gateway.external_gateway[*])
}
output "gateway" {
description = "VPN gateway resource (only if auto-created)."
- value = (
- var.vpn_gateway_create
- ? google_compute_ha_vpn_gateway.ha_gateway[0]
- : null
- )
+ value = one(google_compute_ha_vpn_gateway.ha_gateway[*])
}
output "name" {
description = "VPN gateway name (only if auto-created). ."
- value = (
- var.vpn_gateway_create
- ? google_compute_ha_vpn_gateway.ha_gateway[0].name
- : null
- )
+ value = one(google_compute_ha_vpn_gateway.ha_gateway[*].name)
}
output "random_secret" {
@@ -56,11 +44,7 @@ output "random_secret" {
output "router" {
description = "Router resource (only if auto-created)."
- value = (
- var.router_name == ""
- ? google_compute_router.router[0]
- : null
- )
+ value = one(google_compute_router.router[*])
}
output "router_name" {
diff --git a/modules/net-vpn-ha/variables.tf b/modules/net-vpn-ha/variables.tf
index 4e8d17ac..a423eab1 100644
--- a/modules/net-vpn-ha/variables.tf
+++ b/modules/net-vpn-ha/variables.tf
@@ -24,22 +24,20 @@ variable "network" {
type = string
}
-variable "peer_external_gateway" {
- description = "Configuration of an external VPN gateway to which this VPN is connected."
+variable "peer_gateway" {
+ description = "Configuration of the (external or GCP) peer gateway."
type = object({
- redundancy_type = string
- interfaces = list(object({
- id = number
- ip_address = string
+ external = optional(object({
+ redundancy_type = string
+ interfaces = list(string)
}))
+ gcp = optional(string)
})
- default = null
-}
-
-variable "peer_gcp_gateway" {
- description = "Self Link URL of the peer side HA GCP VPN gateway to which this VPN tunnel is connected."
- type = string
- default = null
+ nullable = false
+ validation {
+ condition = (var.peer_gateway.external != null) != (var.peer_gateway.gcp != null)
+ error_message = "Peer gateway configuration must define exactly one between `external` and `gcp`."
+ }
}
variable "project_id" {
@@ -52,67 +50,50 @@ variable "region" {
type = string
}
-variable "route_priority" {
- description = "Route priority, defaults to 1000."
- type = number
- default = 1000
-}
-
-variable "router_advertise_config" {
- description = "Router custom advertisement configuration, ip_ranges is a map of address ranges and descriptions."
+variable "router_config" {
+ description = "Cloud Router configuration for the VPN. If you want to reuse an existing router, set create to false and use name to specify the desired router."
type = object({
- groups = list(string)
- ip_ranges = map(string)
- mode = string
+ create = optional(bool, true)
+ asn = number
+ name = optional(string)
+ keepalive = optional(number)
+ custom_advertise = optional(object({
+ all_subnets = bool
+ ip_ranges = map(string)
+ }))
})
- default = null
-}
-
-variable "router_asn" {
- description = "Router ASN used for auto-created router."
- type = number
- default = 64514
-}
-
-variable "router_create" {
- description = "Create router."
- type = bool
- default = true
-}
-
-variable "router_name" {
- description = "Router name used for auto created router, or to specify an existing router to use if `router_create` is set to `true`. Leave blank to use VPN name for auto created router."
- type = string
- default = ""
+ nullable = false
}
variable "tunnels" {
- description = "VPN tunnel configurations, bgp_peer_options is usually null."
+ description = "VPN tunnel configurations."
type = map(object({
bgp_peer = object({
- address = string
- asn = number
- })
- bgp_peer_options = object({
- advertise_groups = list(string)
- advertise_ip_ranges = map(string)
- advertise_mode = string
- route_priority = number
+ address = string
+ asn = number
+ route_priority = optional(number, 1000)
+ custom_advertise = optional(object({
+ all_subnets = bool
+ all_vpc_subnets = bool
+ all_peer_vpc_subnets = bool
+ ip_ranges = map(string)
+ }))
})
# each BGP session on the same Cloud Router must use a unique /30 CIDR
# from the 169.254.0.0/16 block.
bgp_session_range = string
- ike_version = number
- peer_external_gateway_interface = number
- router = string
- shared_secret = string
+ ike_version = optional(number, 2)
+ peer_external_gateway_interface = optional(number)
+ router = optional(string)
+ shared_secret = optional(string)
vpn_gateway_interface = number
}))
- default = {}
+ default = {}
+ nullable = false
}
variable "vpn_gateway" {
- description = "HA VPN Gateway Self Link for using an existing HA VPN Gateway, leave empty if `vpn_gateway_create` is set to `true`."
+ description = "HA VPN Gateway Self Link for using an existing HA VPN Gateway. Ignored if `vpn_gateway_create` is set to `true`."
type = string
default = null
}
diff --git a/modules/net-vpn-static/README.md b/modules/net-vpn-static/README.md
index 92745e2d..836746dc 100644
--- a/modules/net-vpn-static/README.md
+++ b/modules/net-vpn-static/README.md
@@ -12,17 +12,16 @@ module "addresses" {
}
module "vpn" {
- source = "./fabric/modules/net-vpn-static"
- project_id = var.project_id
- region = var.region
- network = var.vpc.self_link
- name = "remote"
+ source = "./fabric/modules/net-vpn-static"
+ project_id = var.project_id
+ region = var.region
+ network = var.vpc.self_link
+ name = "remote"
gateway_address_create = false
gateway_address = module.addresses.external_addresses["vpn"].address
- remote_ranges = ["10.10.0.0/24"]
+ remote_ranges = ["10.10.0.0/24"]
tunnels = {
remote-0 = {
- ike_version = 2
peer_ip = "1.1.1.1"
shared_secret = "mysecret"
traffic_selectors = { local = ["0.0.0.0/0"], remote = ["0.0.0.0/0"] }
@@ -41,11 +40,11 @@ module "vpn" {
| [network](variables.tf#L34) | VPC used for the gateway and routes. | string
| ✓ | |
| [project_id](variables.tf#L39) | Project where resources will be created. | string
| ✓ | |
| [region](variables.tf#L44) | Region used for resources. | string
| ✓ | |
-| [gateway_address](variables.tf#L17) | Optional address assigned to the VPN gateway. Ignored unless gateway_address_create is set to false. | string
| | ""
|
+| [gateway_address](variables.tf#L17) | Optional address assigned to the VPN gateway. Ignored unless gateway_address_create is set to false. | string
| | null
|
| [gateway_address_create](variables.tf#L23) | Create external address assigned to the VPN gateway. Needs to be explicitly set to false to use address in gateway_address variable. | bool
| | true
|
| [remote_ranges](variables.tf#L49) | Remote IP CIDR ranges. | list(string)
| | []
|
-| [route_priority](variables.tf#L55) | Route priority, defaults to 1000. | number
| | 1000
|
-| [tunnels](variables.tf#L61) | VPN tunnel configurations. | map(object({…}))
| | {}
|
+| [route_priority](variables.tf#L56) | Route priority, defaults to 1000. | number
| | 1000
|
+| [tunnels](variables.tf#L62) | VPN tunnel configurations. | map(object({…}))
| | {}
|
## Outputs
diff --git a/modules/net-vpn-static/main.tf b/modules/net-vpn-static/main.tf
index 3c8f5cb8..f05771c1 100644
--- a/modules/net-vpn-static/main.tf
+++ b/modules/net-vpn-static/main.tf
@@ -91,7 +91,7 @@ resource "google_compute_vpn_tunnel" "tunnels" {
local_traffic_selector = each.value.traffic_selectors.local
remote_traffic_selector = each.value.traffic_selectors.remote
ike_version = each.value.ike_version
- shared_secret = each.value.shared_secret == "" ? local.secret : each.value.shared_secret
+ shared_secret = coalesce(each.value.shared_secret, local.secret)
target_vpn_gateway = google_compute_vpn_gateway.gateway.self_link
depends_on = [google_compute_forwarding_rule.esp]
}
diff --git a/modules/net-vpn-static/variables.tf b/modules/net-vpn-static/variables.tf
index 90b15f53..935c543a 100644
--- a/modules/net-vpn-static/variables.tf
+++ b/modules/net-vpn-static/variables.tf
@@ -17,7 +17,7 @@
variable "gateway_address" {
description = "Optional address assigned to the VPN gateway. Ignored unless gateway_address_create is set to false."
type = string
- default = ""
+ default = null
}
variable "gateway_address_create" {
@@ -50,6 +50,7 @@ variable "remote_ranges" {
description = "Remote IP CIDR ranges."
type = list(string)
default = []
+ nullable = false
}
variable "route_priority" {
@@ -61,13 +62,14 @@ variable "route_priority" {
variable "tunnels" {
description = "VPN tunnel configurations."
type = map(object({
- ike_version = number
+ ike_version = optional(number, 2)
peer_ip = string
- shared_secret = string
+ shared_secret = optional(string)
traffic_selectors = object({
local = list(string)
remote = list(string)
})
}))
- default = {}
+ default = {}
+ nullable = false
}
diff --git a/modules/vpc-sc/README.md b/modules/vpc-sc/README.md
index a78158e1..8ba38c10 100644
--- a/modules/vpc-sc/README.md
+++ b/modules/vpc-sc/README.md
@@ -193,7 +193,7 @@ module "test" {
| [egress_policies](variables.tf#L70) | Egress policy definitions that can be referenced in perimeters. | map(object({…}))
| | {}
|
| [ingress_policies](variables.tf#L99) | Ingress policy definitions that can be referenced in perimeters. | map(object({…}))
| | {}
|
| [service_perimeters_bridge](variables.tf#L130) | Bridge service perimeters. | map(object({…}))
| | {}
|
-| [service_perimeters_regular](variables.tf#L140) | Regular service perimeters. | map(object({…}))
| | {}
|
+| [service_perimeters_regular](variables.tf#L140) | Regular service perimeters. | map(object({…}))
| | {}
|
## Outputs
diff --git a/modules/vpc-sc/service-perimeters-regular.tf b/modules/vpc-sc/service-perimeters-regular.tf
index 5ef41c32..5b87ca3f 100644
--- a/modules/vpc-sc/service-perimeters-regular.tf
+++ b/modules/vpc-sc/service-perimeters-regular.tf
@@ -28,20 +28,21 @@ resource "google_access_context_manager_service_perimeter" "regular" {
perimeter_type = "PERIMETER_TYPE_REGULAR"
use_explicit_dry_run_spec = each.value.use_explicit_dry_run_spec
dynamic "spec" {
- for_each = each.value.spec == null ? [] : [""]
+ for_each = each.value.spec == null ? [] : [each.value.spec]
+ iterator = spec
content {
access_levels = (
- each.value.spec.access_levels == null ? null : [
- for k in each.value.spec.access_levels :
+ spec.value.access_levels == null ? null : [
+ for k in spec.value.access_levels :
try(google_access_context_manager_access_level.basic[k].id, k)
]
)
- resources = each.value.spec.resources
- restricted_services = each.value.spec.restricted_services
+ resources = spec.value.resources
+ restricted_services = spec.value.restricted_services
dynamic "egress_policies" {
- for_each = each.value.spec.egress_policies == null ? {} : {
- for k in each.value.spec.egress_policies :
+ for_each = spec.value.egress_policies == null ? {} : {
+ for k in spec.value.egress_policies :
k => lookup(var.egress_policies, k, null)
if contains(keys(var.egress_policies), k)
}
@@ -77,8 +78,8 @@ resource "google_access_context_manager_service_perimeter" "regular" {
}
dynamic "ingress_policies" {
- for_each = each.value.spec.ingress_policies == null ? {} : {
- for k in each.value.spec.ingress_policies :
+ for_each = spec.value.ingress_policies == null ? {} : {
+ for k in spec.value.ingress_policies :
k => lookup(var.ingress_policies, k, null)
if contains(keys(var.ingress_policies), k)
}
@@ -129,30 +130,31 @@ resource "google_access_context_manager_service_perimeter" "regular" {
}
dynamic "vpc_accessible_services" {
- for_each = each.value.spec.vpc_accessible_services == null ? {} : { 1 = 1 }
+ for_each = spec.value.vpc_accessible_services == null ? {} : { 1 = 1 }
content {
- allowed_services = each.value.spec.vpc_accessible_services.allowed_services
- enable_restriction = each.value.spec.vpc_accessible_services.enable_restriction
+ allowed_services = spec.value.vpc_accessible_services.allowed_services
+ enable_restriction = spec.value.vpc_accessible_services.enable_restriction
}
}
}
}
dynamic "status" {
- for_each = each.value.status == null ? {} : { 1 = 1 }
+ for_each = each.value.status == null ? [] : [each.value.status]
+ iterator = status
content {
access_levels = (
- each.value.status.access_levels == null ? null : [
- for k in each.value.status.access_levels :
+ status.value.access_levels == null ? null : [
+ for k in status.value.access_levels :
try(google_access_context_manager_access_level.basic[k].id, k)
]
)
- resources = each.value.status.resources
- restricted_services = each.value.status.restricted_services
+ resources = status.value.resources
+ restricted_services = status.value.restricted_services
dynamic "egress_policies" {
- for_each = each.value.spec.egress_policies == null ? {} : {
- for k in each.value.spec.egress_policies :
+ for_each = status.value.egress_policies == null ? {} : {
+ for k in status.value.egress_policies :
k => lookup(var.egress_policies, k, null)
if contains(keys(var.egress_policies), k)
}
@@ -188,8 +190,8 @@ resource "google_access_context_manager_service_perimeter" "regular" {
}
dynamic "ingress_policies" {
- for_each = each.value.spec.ingress_policies == null ? {} : {
- for k in each.value.spec.ingress_policies :
+ for_each = status.value.ingress_policies == null ? {} : {
+ for k in status.value.ingress_policies :
k => lookup(var.ingress_policies, k, null)
if contains(keys(var.ingress_policies), k)
}
@@ -205,7 +207,8 @@ resource "google_access_context_manager_service_perimeter" "regular" {
iterator = s
content {
access_level = try(
- google_access_context_manager_access_level.basic[s.value].id, s.value
+ google_access_context_manager_access_level.basic[s.value].id,
+ s.value
)
}
}
@@ -240,10 +243,10 @@ resource "google_access_context_manager_service_perimeter" "regular" {
}
dynamic "vpc_accessible_services" {
- for_each = each.value.status.vpc_accessible_services == null ? {} : { 1 = 1 }
+ for_each = status.value.vpc_accessible_services == null ? {} : { 1 = 1 }
content {
- allowed_services = each.value.status.vpc_accessible_services.allowed_services
- enable_restriction = each.value.status.vpc_accessible_services.enable_restriction
+ allowed_services = status.value.vpc_accessible_services.allowed_services
+ enable_restriction = status.value.vpc_accessible_services.enable_restriction
}
}
diff --git a/modules/vpc-sc/variables.tf b/modules/vpc-sc/variables.tf
index 1be23ae3..a196cc52 100644
--- a/modules/vpc-sc/variables.tf
+++ b/modules/vpc-sc/variables.tf
@@ -92,7 +92,7 @@ variable "egress_policies" {
"ANY_USER", "ANY_SERVICE_ACCOUNT"
], v.from.identity_type)
])
- error_message = "Invalid `from.identity_type` value in eress policy."
+ error_message = "Invalid `from.identity_type` value in egress policy."
}
}
@@ -150,7 +150,7 @@ variable "service_perimeters_regular" {
allowed_services = list(string)
enable_restriction = bool
}))
- }), {})
+ }))
status = optional(object({
access_levels = optional(list(string))
resources = optional(list(string))
@@ -161,7 +161,7 @@ variable "service_perimeters_regular" {
allowed_services = list(string)
enable_restriction = bool
}))
- }), {})
+ }))
use_explicit_dry_run_spec = optional(bool, false)
}))
default = {}
diff --git a/tests/blueprints/cloud_operations/apigee/fixture/variables.tf b/tests/blueprints/cloud_operations/apigee/fixture/variables.tf
index fc79ae07..d66f3874 100644
--- a/tests/blueprints/cloud_operations/apigee/fixture/variables.tf
+++ b/tests/blueprints/cloud_operations/apigee/fixture/variables.tf
@@ -79,9 +79,8 @@ variable "environments" {
display_name = optional(string)
description = optional(string)
node_config = optional(object({
- min_node_count = optional(number)
- max_node_count = optional(number)
- current_aggregate_node_count = number
+ min_node_count = optional(number)
+ max_node_count = optional(number)
}))
iam = optional(map(list(string)))
envgroups = list(string)
diff --git a/tests/collectors.py b/tests/collectors.py
new file mode 100644
index 00000000..e69782f0
--- /dev/null
+++ b/tests/collectors.py
@@ -0,0 +1,92 @@
+# 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.
+"""Pytest plugin to discover tests specified in YAML files.
+
+This plugin uses the pytest_collect_file hook to collect all files
+matching tftest*.yaml and runs plan_validate for each test found.
+See FabricTestFile for details on the file structure.
+
+"""
+
+import pytest
+import yaml
+
+from .fixtures import plan_summary, plan_validator
+
+
+class FabricTestFile(pytest.File):
+
+ def collect(self):
+ """Read yaml test spec and yield test items for each test definition.
+
+ The test spec should contain a `module` key with the path of the
+ terraform module to test, relative to the root of the repository
+
+ Tests are defined within the top-level `tests` key, and should
+ have the following structure:
+
+ test-name:
+ tfvars:
+ - tfvars1.tfvars
+ - tfvars2.tfvars
+ inventory:
+ - inventory1.yaml
+ - inventory2.yaml
+
+ All paths specifications are relative to the location of the test
+ spec. The inventory key is optional, if omitted, the inventory
+ will be taken from the file test-name.yaml
+
+ """
+
+ try:
+ raw = yaml.safe_load(self.path.open())
+ module = raw.pop('module')
+ except (IOError, OSError, yaml.YAMLError) as e:
+ raise Exception(f'cannot read test spec {self.path}: {e}')
+ except KeyError as e:
+ raise Exception(f'`module` key not found in {self.path}: {e}')
+ common = raw.pop('common_tfvars', [])
+ for test_name, spec in raw.get('tests', {}).items():
+ spec = {} if spec is None else spec
+ inventories = spec.get('inventory', [f'{test_name}.yaml'])
+ tfvars = common + [f'{test_name}.tfvars'] + spec.get('tfvars', [])
+ for i in inventories:
+ name = test_name
+ if isinstance(inventories, list) and len(inventories) > 1:
+ name = f'{test_name}[{i}]'
+ yield FabricTestItem.from_parent(self, name=name, module=module,
+ inventory=[i], tfvars=tfvars)
+
+
+class FabricTestItem(pytest.Item):
+
+ def __init__(self, name, parent, module, inventory, tfvars):
+ super().__init__(name, parent)
+ self.module = module
+ self.inventory = inventory
+ self.tfvars = tfvars
+
+ def runtest(self):
+ s = plan_validator(self.module, self.inventory, self.parent.path.parent,
+ self.tfvars)
+
+ def reportinfo(self):
+ return self.path, None, self.name
+
+
+def pytest_collect_file(parent, file_path):
+ 'Collect tftest*.yaml files and run plan_validator from them.'
+ if file_path.suffix == '.yaml' and file_path.name.startswith('tftest'):
+ return FabricTestFile.from_parent(parent, path=file_path)
diff --git a/tests/conftest.py b/tests/conftest.py
index d676bbc0..167c74f7 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -11,144 +11,12 @@
# 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.
-"Shared fixtures"
-
-import inspect
-import os
-import shutil
-import tempfile
+'Pytest configuration.'
import pytest
-import tftest
-BASEDIR = os.path.dirname(os.path.dirname(__file__))
-
-
-@pytest.fixture(scope='session')
-def _plan_runner():
- "Returns a function to run Terraform plan on a fixture."
-
- def run_plan(fixture_path=None, extra_files=None, tf_var_file=None,
- targets=None, refresh=True, tmpdir=True, **tf_vars):
- "Runs Terraform plan and returns parsed output."
- if fixture_path is None:
- # find out the fixture directory from the caller's directory
- caller = inspect.stack()[2]
- fixture_path = os.path.join(os.path.dirname(caller.filename), "fixture")
-
- fixture_parent = os.path.dirname(fixture_path)
- fixture_prefix = os.path.basename(fixture_path) + "_"
- with tempfile.TemporaryDirectory(prefix=fixture_prefix,
- dir=fixture_parent) as tmp_path:
- # copy fixture to a temporary directory so we can execute
- # multiple tests in parallel
- if tmpdir:
- shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True)
- tf = tftest.TerraformTest(tmp_path if tmpdir else fixture_path, BASEDIR,
- os.environ.get('TERRAFORM', 'terraform'))
- tf.setup(extra_files=extra_files, upgrade=True)
- plan = tf.plan(output=True, refresh=refresh, tf_var_file=tf_var_file,
- tf_vars=tf_vars, targets=targets)
- return plan
-
- return run_plan
-
-
-@pytest.fixture(scope='session')
-def plan_runner(_plan_runner):
- "Returns a function to run Terraform plan on a module fixture."
-
- def run_plan(fixture_path=None, extra_files=None, tf_var_file=None,
- targets=None, **tf_vars):
- "Runs Terraform plan and returns plan and module resources."
- plan = _plan_runner(fixture_path, extra_files=extra_files,
- tf_var_file=tf_var_file, targets=targets, **tf_vars)
- # skip the fixture
- root_module = plan.root_module['child_modules'][0]
- return plan, root_module['resources']
-
- return run_plan
-
-
-@pytest.fixture(scope='session')
-def e2e_plan_runner(_plan_runner):
- "Returns a function to run Terraform plan on an end-to-end fixture."
-
- def run_plan(fixture_path=None, tf_var_file=None, targets=None,
- refresh=True, include_bare_resources=False, **tf_vars):
- "Runs Terraform plan on an end-to-end module using defaults, returns data."
- plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets,
- refresh=refresh, **tf_vars)
- # skip the fixture
- root_module = plan.root_module['child_modules'][0]
- modules = dict((mod['address'], mod['resources'])
- for mod in root_module['child_modules'])
- resources = [r for m in modules.values() for r in m]
- if include_bare_resources:
- bare_resources = root_module['resources']
- resources.extend(bare_resources)
- return modules, resources
-
- return run_plan
-
-
-@pytest.fixture(scope='session')
-def recursive_e2e_plan_runner(_plan_runner):
- """Plan runner for end-to-end root module, returns total number of
- (nested) modules and resources"""
-
- def walk_plan(node, modules, resources):
- # TODO(jccb): this would be better with node.get() but
- # TerraformPlanOutput objects don't have it
- new_modules = node.get('child_modules', [])
- resources += node.get('resources', [])
- modules += new_modules
- for module in new_modules:
- walk_plan(module, modules, resources)
-
- def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True,
- include_bare_resources=False, compute_sums=True, tmpdir=True,
- **tf_vars):
- "Runs Terraform plan on a root module using defaults, returns data."
- plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets,
- refresh=refresh, tmpdir=tmpdir, **tf_vars)
- modules = []
- resources = []
- walk_plan(plan.root_module, modules, resources)
- return len(modules), len(resources)
-
- return run_plan
-
-
-@pytest.fixture(scope='session')
-def apply_runner():
- "Returns a function to run Terraform apply on a fixture."
-
- def run_apply(fixture_path=None, **tf_vars):
- "Runs Terraform plan and returns parsed output."
- if fixture_path is None:
- # find out the fixture directory from the caller's directory
- caller = inspect.stack()[1]
- fixture_path = os.path.join(os.path.dirname(caller.filename), "fixture")
-
- fixture_parent = os.path.dirname(fixture_path)
- fixture_prefix = os.path.basename(fixture_path) + "_"
-
- with tempfile.TemporaryDirectory(prefix=fixture_prefix,
- dir=fixture_parent) as tmp_path:
- # copy fixture to a temporary directory so we can execute
- # multiple tests in parallel
- shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True)
- tf = tftest.TerraformTest(tmp_path, BASEDIR,
- os.environ.get('TERRAFORM', 'terraform'))
- tf.setup(upgrade=True)
- apply = tf.apply(tf_vars=tf_vars)
- output = tf.output(json_format=True)
- return apply, output
-
- return run_apply
-
-
-@pytest.fixture
-def basedir():
- return BASEDIR
+pytest_plugins = (
+ 'tests.fixtures',
+ 'tests.legacy_fixtures',
+ 'tests.collectors',
+)
diff --git a/tests/examples/variables.tf b/tests/examples/variables.tf
index 1924ac40..76c3770d 100644
--- a/tests/examples/variables.tf
+++ b/tests/examples/variables.tf
@@ -68,14 +68,21 @@ variable "subnet" {
variable "vpc" {
default = {
name = "vpc_name"
- self_link = "projects/xxx/global/networks/yyy"
+ self_link = "projects/xxx/global/networks/aaa"
+ }
+}
+
+variable "vpc1" {
+ default = {
+ name = "vpc_name"
+ self_link = "projects/xxx/global/networks/bbb"
}
}
variable "vpc2" {
default = {
name = "vpc2_name"
- self_link = "vpc2_self_link"
+ self_link = "projects/xxx/global/networks/ccc"
}
}
diff --git a/tests/fast/stages/s00_bootstrap/fixture/main.tf b/tests/fast/stages/s00_bootstrap/fixture/main.tf
deleted file mode 100644
index 1f07048a..00000000
--- a/tests/fast/stages/s00_bootstrap/fixture/main.tf
+++ /dev/null
@@ -1,29 +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.
- */
-
-module "stage" {
- source = "../../../../../fast/stages/00-bootstrap"
- prefix = "fast"
- organization = {
- domain = "fast.example.com"
- id = 123456789012
- customer_id = "C00000000"
- }
- billing_account = {
- id = "000000-111111-222222"
- organization_id = 123456789012
- }
-}
diff --git a/tests/fast/stages/s00_bootstrap/simple.tfvars b/tests/fast/stages/s00_bootstrap/simple.tfvars
new file mode 100644
index 00000000..f8ef5735
--- /dev/null
+++ b/tests/fast/stages/s00_bootstrap/simple.tfvars
@@ -0,0 +1,11 @@
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+billing_account = {
+ id = "000000-111111-222222"
+ organization_id = 123456789012
+}
+prefix = "fast"
+outputs_location = "/fast-config"
diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml
new file mode 100644
index 00000000..703b84b4
--- /dev/null
+++ b/tests/fast/stages/s00_bootstrap/simple.yaml
@@ -0,0 +1,49 @@
+# 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.
+
+counts:
+ google_bigquery_dataset: 2
+ google_bigquery_dataset_iam_member: 2
+ google_bigquery_default_service_account: 3
+ google_logging_organization_sink: 2
+ google_organization_iam_binding: 19
+ google_organization_iam_custom_role: 2
+ google_organization_iam_member: 16
+ google_project: 3
+ google_project_iam_binding: 9
+ google_project_iam_member: 1
+ google_project_service: 29
+ google_project_service_identity: 2
+ google_service_account: 3
+ google_service_account_iam_binding: 3
+ google_storage_bucket: 4
+ google_storage_bucket_iam_binding: 2
+ google_storage_bucket_iam_member: 3
+ google_storage_bucket_object: 5
+ google_storage_project_service_account: 3
+ local_file: 5
+
+outputs:
+ custom_roles:
+ organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin
+ service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin
+ outputs_bucket: fast-prod-iac-core-outputs-0
+ project_ids:
+ automation: fast-prod-iac-core-0
+ billing-export: fast-prod-billing-exp-0
+ log-export: fast-prod-audit-logs-0
+ service_accounts:
+ bootstrap: fast-prod-bootstrap-0@fast-prod-iac-core-0.iam.gserviceaccount.com
+ cicd: fast-prod-cicd-0@fast-prod-iac-core-0.iam.gserviceaccount.com
+ resman: fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com
diff --git a/tests/fast/stages/s00_bootstrap/simple_projects.yaml b/tests/fast/stages/s00_bootstrap/simple_projects.yaml
new file mode 100644
index 00000000..c4d359f3
--- /dev/null
+++ b/tests/fast/stages/s00_bootstrap/simple_projects.yaml
@@ -0,0 +1,33 @@
+# 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.
+
+values:
+ module.automation-project.google_project.project[0]:
+ auto_create_network: false
+ billing_account: 000000-111111-222222
+ name: fast-prod-iac-core-0
+ org_id: '123456789012'
+ project_id: fast-prod-iac-core-0
+ module.billing-export-project[0].google_project.project[0]:
+ auto_create_network: false
+ billing_account: 000000-111111-222222
+ name: fast-prod-billing-exp-0
+ org_id: '123456789012'
+ project_id: fast-prod-billing-exp-0
+ module.log-export-project.google_project.project[0]:
+ auto_create_network: false
+ billing_account: 000000-111111-222222
+ name: fast-prod-audit-logs-0
+ org_id: '123456789012'
+ project_id: fast-prod-audit-logs-0
diff --git a/tests/fast/stages/s00_bootstrap/simple_sas.yaml b/tests/fast/stages/s00_bootstrap/simple_sas.yaml
new file mode 100644
index 00000000..ba84948d
--- /dev/null
+++ b/tests/fast/stages/s00_bootstrap/simple_sas.yaml
@@ -0,0 +1,27 @@
+# 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.
+
+values:
+ module.automation-tf-bootstrap-sa.google_service_account.service_account[0]:
+ account_id: fast-prod-bootstrap-0
+ display_name: Terraform organization bootstrap service account.
+ project: fast-prod-iac-core-0
+ module.automation-tf-cicd-provisioning-sa.google_service_account.service_account[0]:
+ account_id: fast-prod-cicd-0
+ display_name: Terraform stage 1 CICD service account.
+ project: fast-prod-iac-core-0
+ module.automation-tf-resman-sa.google_service_account.service_account[0]:
+ account_id: fast-prod-resman-0
+ display_name: Terraform stage 1 resman service account.
+ project: fast-prod-iac-core-0
diff --git a/tests/fast/stages/s00_bootstrap/test_plan.py b/tests/fast/stages/s00_bootstrap/test_plan.py
deleted file mode 100644
index 78146970..00000000
--- a/tests/fast/stages/s00_bootstrap/test_plan.py
+++ /dev/null
@@ -1,33 +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.
-
-# _RESOURCE_COUNT = {
-# 'module.organization': 28,
-# 'module.automation-project': 23,
-# 'module.automation-tf-bootstrap-gcs': 1,
-# 'module.automation-tf-bootstrap-sa': 1,
-# 'module.automation-tf-resman-gcs': 2,
-# 'module.automation-tf-resman-sa': 1,
-# 'module.billing-export-dataset': 1,
-# 'module.billing-export-project': 7,
-# 'module.log-export-dataset': 1,
-# 'module.log-export-project': 7,
-# }
-
-
-def test_counts(recursive_e2e_plan_runner):
- "Test stage."
- # TODO: to re-enable per-module resource count check print _, then test
- num_modules, num_resources = recursive_e2e_plan_runner()
- assert num_modules > 0 and num_resources > 0
diff --git a/tests/fast/stages/s00_bootstrap/tftest.yaml b/tests/fast/stages/s00_bootstrap/tftest.yaml
new file mode 100644
index 00000000..4656859b
--- /dev/null
+++ b/tests/fast/stages/s00_bootstrap/tftest.yaml
@@ -0,0 +1,12 @@
+# skip boilerplate check
+
+module: fast/stages/00-bootstrap
+
+tests:
+ simple:
+ tfvars:
+ - simple.tfvars
+ inventory:
+ - simple.yaml
+ - simple_projects.yaml
+ - simple_sas.yaml
diff --git a/tests/fixtures.py b/tests/fixtures.py
new file mode 100644
index 00000000..c483063f
--- /dev/null
+++ b/tests/fixtures.py
@@ -0,0 +1,235 @@
+# 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.
+"""Common fixtures."""
+
+import collections
+import contextlib
+import itertools
+import os
+import shutil
+import tempfile
+from pathlib import Path
+
+import pytest
+import tftest
+import yaml
+
+PlanSummary = collections.namedtuple('PlanSummary', 'values counts outputs')
+
+
+@contextlib.contextmanager
+def _prepare_root_module(path):
+ """Context manager to prepare a terraform module to be tested.
+
+ If the TFTEST_COPY environment variable is set, `path` is copied to
+ a temporary directory and a few terraform files (e.g.
+ terraform.tfvars) are delete to ensure a clean test environment.
+ Otherwise, `path` is simply returned untouched.
+ """
+ if os.environ.get('TFTEST_COPY'):
+ # if the TFTEST_COPY is set, create temp dir and copy the root
+ # module there
+ with tempfile.TemporaryDirectory(dir=path.parent) as tmp_path:
+ tmp_path = Path(tmp_path)
+
+ # if we're copying the module, we might as well ignore files and
+ # directories that are automatically read by terraform. Useful
+ # to avoid surprises if, for example, you have an active fast
+ # deployment with links to configs)
+ ignore_patterns = shutil.ignore_patterns('*.auto.tfvars',
+ '*.auto.tfvars.json',
+ 'terraform.tfstate*',
+ 'terraform.tfvars', '.terraform')
+
+ shutil.copytree(path, tmp_path, dirs_exist_ok=True,
+ ignore=ignore_patterns)
+
+ yield tmp_path
+ else:
+ # if TFTEST_COPY is not set, just return the same path
+ yield path
+
+
+def plan_summary(module_path, basedir, tf_var_files=None, **tf_vars):
+ """
+ Run a Terraform plan on the module located at `module_path`.
+
+ - module_path: terraform root module to run. Can be an absolute
+ path or relative to the root of the repository
+
+ - basedir: directory root to use for relative paths in
+ tf_var_files.
+
+ - tf_var_files: set of terraform variable files (tfvars) to pass
+ in to terraform
+
+ Returns a PlanSummary object containing 3 attributes:
+ - values: dictionary where the keys are terraform plan addresses
+ and values are the JSON representation (converted to python
+ types) of the attribute values of the resource.
+
+ - counts: dictionary where the keys are the terraform resource
+ types and the values are the number of times that type appears
+ in the plan
+
+ - outputs: dictionary of the modules outputs that can be
+ determined at plan type.
+
+ Consult [1] for mode details on the structure of values and outputs
+
+ [1] https://developer.hashicorp.com/terraform/internals/json-format
+ """
+ # make the module_path relative to the root of the repo while still
+ # supporting absolute paths
+ module_path = Path(__file__).parents[1] / module_path
+ with _prepare_root_module(module_path) as test_path:
+ binary = os.environ.get('TERRAFORM', 'terraform')
+ tf = tftest.TerraformTest(test_path, binary=binary)
+ tf.setup(upgrade=True)
+ tf_var_files = [(basedir / x).resolve() for x in tf_var_files or []]
+ plan = tf.plan(output=True, tf_var_file=tf_var_files, tf_vars=tf_vars)
+
+ # compute resource type counts and address->values map
+ values = {}
+ counts = collections.defaultdict(int)
+ q = collections.deque([plan.root_module])
+ while q:
+ e = q.popleft()
+
+ if 'type' in e:
+ counts[e['type']] += 1
+ if 'values' in e:
+ values[e['address']] = e['values']
+
+ for x in e.get('resources', []):
+ q.append(x)
+ for x in e.get('child_modules', []):
+ q.append(x)
+
+ # extract planned outputs
+ outputs = plan.get('planned_values', {}).get('outputs', {})
+
+ return PlanSummary(values, dict(counts), outputs)
+
+
+@pytest.fixture(name='plan_summary')
+def plan_summary_fixture(request):
+ """Return a function to generate a PlanSummary.
+
+ In the returned function `basedir` becomes optional and it defaults
+ to the directory of the calling test
+ """
+
+ def inner(module_path, basedir=None, tf_var_files=None, **tf_vars):
+ if basedir is None:
+ basedir = Path(request.fspath).parent
+ return plan_summary(module_path=module_path, basedir=basedir,
+ tf_var_files=tf_var_files, **tf_vars)
+
+ return inner
+
+
+def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
+ **tf_vars):
+ summary = plan_summary(module_path=module_path, tf_var_files=tf_var_files,
+ basedir=basedir, **tf_vars)
+
+ # allow single single string for inventory_paths
+ if not isinstance(inventory_paths, list):
+ inventory_paths = [inventory_paths]
+
+ for path in inventory_paths:
+ # allow tfvars and inventory to be relative to the caller
+ path = basedir / path
+ try:
+ inventory = yaml.safe_load(path.read_text())
+ except (IOError, OSError, yaml.YAMLError) as e:
+ raise Exception(f'cannot read test inventory {path}: {e}')
+
+ # don't fail if the inventory is empty
+ inventory = inventory or {}
+
+ # If you add additional asserts to this function:
+ # - put the values coming from the plan on the left side of
+ # any comparison operators
+ # - put the values coming from user's inventory the right
+ # side of any comparison operators.
+ # - include a descriptive error message to the assert
+
+ # for values:
+ # - verify each address in the user's inventory exists in the plan
+ # - for those address that exist on both the user's inventory and
+ # the plan output, ensure the set of keys on the inventory are a
+ # subset of the keys in the plan, and compare their values by
+ # equality
+ if 'values' in inventory:
+ expected_values = inventory['values']
+ for address, expected_value in expected_values.items():
+ assert address in summary.values, \
+ f'{address} is not a valid address in the plan'
+ for k, v in expected_value.items():
+ assert k in summary.values[address], \
+ f'{k} not found at {address}'
+ plan_value = summary.values[address][k]
+ assert plan_value == v, \
+ f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`'
+
+ if 'counts' in inventory:
+ expected_counts = inventory['counts']
+ for type_, expected_count in expected_counts.items():
+ assert type_ in summary.counts, \
+ f'module does not create any resources of type `{type_}`'
+ plan_count = summary.counts[type_]
+ assert plan_count == expected_count, \
+ f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}'
+
+ if 'outputs' in inventory:
+ expected_outputs = inventory['outputs']
+ for output_name, expected_output in expected_outputs.items():
+ assert output_name in summary.outputs, \
+ f'module does not output `{output_name}`'
+ output = summary.outputs[output_name]
+ # assert 'value' in output, \
+ # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)'
+ plan_output = output.get('value', '__missing__')
+ assert plan_output == expected_output, \
+ f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`'
+
+ return summary
+
+
+@pytest.fixture(name='plan_validator')
+def plan_validator_fixture(request):
+ """Return a function to build a PlanSummary and compare it to a YAML inventory.
+
+ In the returned function `basedir` becomes optional and it defaults
+ to the directory of the calling test'
+
+ """
+
+ def inner(module_path, inventory_paths, basedir=None, tf_var_files=None,
+ **tf_vars):
+ if basedir is None:
+ basedir = Path(request.fspath).parent
+ return plan_validator(module_path=module_path,
+ inventory_paths=inventory_paths, basedir=basedir,
+ tf_var_files=tf_var_paths, **tf_vars)
+
+ return inner
+
+
+# @pytest.fixture
+# def repo_root():
+# 'Return a pathlib.Path to the root of the repository'
+# return Path(__file__).parents[1]
diff --git a/tests/legacy_fixtures.py b/tests/legacy_fixtures.py
new file mode 100644
index 00000000..5891d704
--- /dev/null
+++ b/tests/legacy_fixtures.py
@@ -0,0 +1,153 @@
+# 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.
+"""Legacy pytest fixtures.
+
+The fixtures contained in this file will eventually go away. Consider
+using one of the fixtures in fixtures.py
+"""
+
+import inspect
+import os
+import shutil
+import tempfile
+
+import pytest
+import tftest
+
+BASEDIR = os.path.dirname(os.path.dirname(__file__))
+
+
+@pytest.fixture(scope='session')
+def _plan_runner():
+ 'Return a function to run Terraform plan on a fixture.'
+
+ def run_plan(fixture_path=None, extra_files=None, tf_var_file=None,
+ targets=None, refresh=True, tmpdir=True, **tf_vars):
+ 'Run Terraform plan and returns parsed output.'
+ if fixture_path is None:
+ # find out the fixture directory from the caller's directory
+ caller = inspect.stack()[2]
+ fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture')
+
+ fixture_parent = os.path.dirname(fixture_path)
+ fixture_prefix = os.path.basename(fixture_path) + '_'
+ with tempfile.TemporaryDirectory(prefix=fixture_prefix,
+ dir=fixture_parent) as tmp_path:
+ # copy fixture to a temporary directory so we can execute
+ # multiple tests in parallel
+ if tmpdir:
+ shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True)
+ tf = tftest.TerraformTest(tmp_path if tmpdir else fixture_path, BASEDIR,
+ os.environ.get('TERRAFORM', 'terraform'))
+ tf.setup(extra_files=extra_files, upgrade=True)
+ plan = tf.plan(output=True, refresh=refresh, tf_var_file=tf_var_file,
+ tf_vars=tf_vars, targets=targets)
+ return plan
+
+ return run_plan
+
+
+@pytest.fixture(scope='session')
+def plan_runner(_plan_runner):
+ 'Return a function to run Terraform plan on a module fixture.'
+
+ def run_plan(fixture_path=None, extra_files=None, tf_var_file=None,
+ targets=None, **tf_vars):
+ 'Run Terraform plan and returns plan and module resources.'
+ plan = _plan_runner(fixture_path, extra_files=extra_files,
+ tf_var_file=tf_var_file, targets=targets, **tf_vars)
+ # skip the fixture
+ root_module = plan.root_module['child_modules'][0]
+ return plan, root_module['resources']
+
+ return run_plan
+
+
+@pytest.fixture(scope='session')
+def e2e_plan_runner(_plan_runner):
+ 'Return a function to run Terraform plan on an end-to-end fixture.'
+
+ def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True,
+ include_bare_resources=False, **tf_vars):
+ 'Run Terraform plan on an end-to-end module using defaults, returns data.'
+ plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets,
+ refresh=refresh, **tf_vars)
+ # skip the fixture
+ root_module = plan.root_module['child_modules'][0]
+ modules = dict((mod['address'], mod['resources'])
+ for mod in root_module['child_modules'])
+ resources = [r for m in modules.values() for r in m]
+ if include_bare_resources:
+ bare_resources = root_module['resources']
+ resources.extend(bare_resources)
+ return modules, resources
+
+ return run_plan
+
+
+@pytest.fixture(scope='session')
+def recursive_e2e_plan_runner(_plan_runner):
+ """
+ Plan runner for end-to-end root module, returns total number of
+ (nested) modules and resources
+ """
+
+ def walk_plan(node, modules, resources):
+ new_modules = node.get('child_modules', [])
+ resources += node.get('resources', [])
+ modules += new_modules
+ for module in new_modules:
+ walk_plan(module, modules, resources)
+
+ def run_plan(fixture_path=None, tf_var_file=None, targets=None, refresh=True,
+ include_bare_resources=False, compute_sums=True, tmpdir=True,
+ **tf_vars):
+ 'Run Terraform plan on a root module using defaults, returns data.'
+ plan = _plan_runner(fixture_path, tf_var_file=tf_var_file, targets=targets,
+ refresh=refresh, tmpdir=tmpdir, **tf_vars)
+ modules = []
+ resources = []
+ walk_plan(plan.root_module, modules, resources)
+ return len(modules), len(resources)
+
+ return run_plan
+
+
+@pytest.fixture(scope='session')
+def apply_runner():
+ 'Return a function to run Terraform apply on a fixture.'
+
+ def run_apply(fixture_path=None, **tf_vars):
+ 'Run Terraform plan and returns parsed output.'
+ if fixture_path is None:
+ # find out the fixture directory from the caller's directory
+ caller = inspect.stack()[1]
+ fixture_path = os.path.join(os.path.dirname(caller.filename), 'fixture')
+
+ fixture_parent = os.path.dirname(fixture_path)
+ fixture_prefix = os.path.basename(fixture_path) + '_'
+
+ with tempfile.TemporaryDirectory(prefix=fixture_prefix,
+ dir=fixture_parent) as tmp_path:
+ # copy fixture to a temporary directory so we can execute
+ # multiple tests in parallel
+ shutil.copytree(fixture_path, tmp_path, dirs_exist_ok=True)
+ tf = tftest.TerraformTest(tmp_path, BASEDIR,
+ os.environ.get('TERRAFORM', 'terraform'))
+ tf.setup(upgrade=True)
+ apply = tf.apply(tf_vars=tf_vars)
+ output = tf.output(json_format=True)
+ return apply, output
+
+ return run_apply
diff --git a/tests/modules/apigee/fixture/test.env_only.tfvars b/tests/modules/apigee/fixture/test.env_only.tfvars
index 5cc6f49d..0e4edc48 100644
--- a/tests/modules/apigee/fixture/test.env_only.tfvars
+++ b/tests/modules/apigee/fixture/test.env_only.tfvars
@@ -4,5 +4,9 @@ environments = {
display_name = "APIs test"
description = "APIs Test"
envgroups = ["test"]
+ node_config = {
+ min_node_count = 2
+ max_node_count = 5
+ }
}
-}
\ No newline at end of file
+}
diff --git a/tests/modules/apigee/fixture/variables.tf b/tests/modules/apigee/fixture/variables.tf
index 8cddf9a4..266f0d34 100644
--- a/tests/modules/apigee/fixture/variables.tf
+++ b/tests/modules/apigee/fixture/variables.tf
@@ -35,9 +35,8 @@ variable "environments" {
display_name = optional(string)
description = optional(string, "Terraform-managed")
node_config = optional(object({
- min_node_count = optional(number)
- max_node_count = optional(number)
- current_aggregate_node_count = number
+ min_node_count = optional(number)
+ max_node_count = optional(number)
}))
iam = optional(map(list(string)))
envgroups = list(string)
@@ -76,4 +75,4 @@ variable "organization" {
variable "project_id" {
description = "Project ID."
type = string
-}
\ No newline at end of file
+}
diff --git a/tests/modules/conftest.py b/tests/modules/conftest.py
index a34cef65..c199cff7 100644
--- a/tests/modules/conftest.py
+++ b/tests/modules/conftest.py
@@ -20,16 +20,11 @@ import pytest
import yaml
-def pytest_collection_modifyitems(config, items):
- for item in items:
- item.add_marker(pytest.mark.xdist_group(name=item.path.parent.name))
-
-
-@pytest.fixture(scope='session')
-def tfvars_to_yaml():
+@pytest.fixture()
+def tfvars_to_yaml(request):
def converter(source, dest, from_var, to_var=None):
- p_fixture = pathlib.Path(inspect.stack()[1].filename).parent / 'fixture'
+ p_fixture = pathlib.Path(request.path).parent
p_source = p_fixture / source
if not p_source.exists():
raise ValueError(f"tfvars '{source}' not found")
diff --git a/tests/modules/folder/test_plan_org_policies.py b/tests/modules/folder/test_plan_org_policies.py
index d4e4559d..8463761e 100644
--- a/tests/modules/folder/test_plan_org_policies.py
+++ b/tests/modules/folder/test_plan_org_policies.py
@@ -31,13 +31,14 @@ def test_policy_list(plan_runner):
def test_factory_policy_boolean(plan_runner, tfvars_to_yaml, tmp_path):
dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('test.orgpolicies-boolean.tfvars', dest, 'org_policies')
+ tfvars_to_yaml('fixture/test.orgpolicies-boolean.tfvars', dest,
+ 'org_policies')
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_boolean(resources)
def test_factory_policy_list(plan_runner, tfvars_to_yaml, tmp_path):
dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('test.orgpolicies-list.tfvars', dest, 'org_policies')
+ tfvars_to_yaml('fixture/test.orgpolicies-list.tfvars', dest, 'org_policies')
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_list(resources)
diff --git a/tests/modules/net_vpc/common.tfvars b/tests/modules/net_vpc/common.tfvars
new file mode 100644
index 00000000..8d9d8a95
--- /dev/null
+++ b/tests/modules/net_vpc/common.tfvars
@@ -0,0 +1,2 @@
+project_id = "test-project"
+name = "test"
diff --git a/tests/modules/net_vpc/fixture/data/factory-subnet.yaml b/tests/modules/net_vpc/data/factory-subnet.yaml
similarity index 100%
rename from tests/modules/net_vpc/fixture/data/factory-subnet.yaml
rename to tests/modules/net_vpc/data/factory-subnet.yaml
diff --git a/tests/modules/net_vpc/fixture/data/factory-subnet2.yaml b/tests/modules/net_vpc/data/factory-subnet2.yaml
similarity index 100%
rename from tests/modules/net_vpc/fixture/data/factory-subnet2.yaml
rename to tests/modules/net_vpc/data/factory-subnet2.yaml
diff --git a/tests/modules/net_vpc/factory.tfvars b/tests/modules/net_vpc/factory.tfvars
new file mode 100644
index 00000000..8c4d4a28
--- /dev/null
+++ b/tests/modules/net_vpc/factory.tfvars
@@ -0,0 +1 @@
+data_folder = "../../tests/modules/net_vpc/data"
diff --git a/tests/modules/net_vpc/factory.yaml b/tests/modules/net_vpc/factory.yaml
new file mode 100644
index 00000000..9cf628d0
--- /dev/null
+++ b/tests/modules/net_vpc/factory.yaml
@@ -0,0 +1,44 @@
+# 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.
+
+values:
+ google_compute_subnetwork.subnetwork["europe-west1/factory-subnet"]:
+ description: 'Sample description'
+ ip_cidr_range: '10.128.0.0/24'
+ ipv6_access_type: null
+ log_config: []
+ name: 'factory-subnet'
+ private_ip_google_access: false
+ project: 'test-project'
+ region: 'europe-west1'
+ role: null
+ secondary_ip_range:
+ - ip_cidr_range: '192.168.128.0/24'
+ range_name: 'secondary-range-a'
+ google_compute_subnetwork.subnetwork["europe-west4/factory-subnet2"]:
+ description: 'Sample description'
+ ip_cidr_range: '10.129.0.0/24'
+ log_config: []
+ name: 'factory-subnet2'
+ private_ip_google_access: true
+ project: 'test-project'
+ region: 'europe-west4'
+ role: null
+ secondary_ip_range: []
+
+ # FIXME: should we have some bindings here?
+
+counts:
+ google_compute_network: 1
+ google_compute_subnetwork: 2
diff --git a/tests/modules/net_vpc/peering.tfvars b/tests/modules/net_vpc/peering.tfvars
new file mode 100644
index 00000000..eccd7ae7
--- /dev/null
+++ b/tests/modules/net_vpc/peering.tfvars
@@ -0,0 +1,5 @@
+peering_config = {
+ peer_vpc_self_link = "projects/my-project/global/networks/peer"
+ export_routes = true
+ import_routes = null
+}
diff --git a/tests/modules/net_vpc/peering.yaml b/tests/modules/net_vpc/peering.yaml
new file mode 100644
index 00000000..8d0bbed7
--- /dev/null
+++ b/tests/modules/net_vpc/peering.yaml
@@ -0,0 +1,47 @@
+# 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.
+
+values:
+ google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ name: test
+ project: test-project
+ routing_mode: GLOBAL
+ google_compute_network_peering.local[0]:
+ export_custom_routes: true
+ import_custom_routes: false
+ name: test-peer
+ peer_network: projects/my-project/global/networks/peer
+ google_compute_network_peering.remote[0]:
+ export_custom_routes: false
+ import_custom_routes: true
+ name: peer-test
+ network: projects/my-project/global/networks/peer
+
+counts:
+ google_compute_network: 1
+ google_compute_network_peering: 2
+
+outputs:
+ bindings: {}
+ project_id: test-project
+ subnet_ips: {}
+ subnet_regions: {}
+ subnet_secondary_ranges: {}
+ subnet_self_links: {}
+ subnets: {}
+ subnets_proxy_only: {}
+ subnets_psc: {}
diff --git a/tests/modules/net_vpc/psa_routes_export.tfvars b/tests/modules/net_vpc/psa_routes_export.tfvars
new file mode 100644
index 00000000..9fbe4ddf
--- /dev/null
+++ b/tests/modules/net_vpc/psa_routes_export.tfvars
@@ -0,0 +1,7 @@
+psa_config = {
+ ranges = {
+ bar = "172.16.100.0/24"
+ }
+ export_routes = true
+ import_routes = false
+}
diff --git a/tests/modules/net_vpc/psa_routes_export.yaml b/tests/modules/net_vpc/psa_routes_export.yaml
new file mode 100644
index 00000000..c8cf631b
--- /dev/null
+++ b/tests/modules/net_vpc/psa_routes_export.yaml
@@ -0,0 +1,60 @@
+# 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.
+
+values:
+ google_compute_global_address.psa_ranges["bar"]:
+ address: 172.16.100.0
+ address_type: INTERNAL
+ description: null
+ ip_version: null
+ name: bar
+ prefix_length: 24
+ project: test-project
+ purpose: VPC_PEERING
+ google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ enable_ula_internal_ipv6: null
+ name: test
+ project: test-project
+ routing_mode: GLOBAL
+ google_compute_network_peering_routes_config.psa_routes["1"]:
+ export_custom_routes: true
+ import_custom_routes: false
+ project: test-project
+ google_service_networking_connection.psa_connection["1"]:
+ reserved_peering_ranges:
+ - bar
+ service: servicenetworking.googleapis.com
+
+counts:
+ google_compute_global_address: 1
+ google_compute_network: 1
+ google_compute_network_peering_routes_config: 1
+ google_service_networking_connection: 1
+
+outputs:
+ bindings: {}
+ name: __missing__
+ network: __missing__
+ project_id: test-project
+ self_link: __missing__
+ subnet_ips: {}
+ subnet_regions: {}
+ subnet_secondary_ranges: {}
+ subnet_self_links: {}
+ subnets: {}
+ subnets_proxy_only: {}
+ subnets_psc: {}
diff --git a/tests/modules/net_vpc/psa_routes_import.tfvars b/tests/modules/net_vpc/psa_routes_import.tfvars
new file mode 100644
index 00000000..beeaf433
--- /dev/null
+++ b/tests/modules/net_vpc/psa_routes_import.tfvars
@@ -0,0 +1,7 @@
+psa_config = {
+ ranges = {
+ bar = "172.16.100.0/24"
+ }
+ export_routes = false
+ import_routes = true
+}
diff --git a/tests/modules/net_vpc/psa_routes_import.yaml b/tests/modules/net_vpc/psa_routes_import.yaml
new file mode 100644
index 00000000..771d881f
--- /dev/null
+++ b/tests/modules/net_vpc/psa_routes_import.yaml
@@ -0,0 +1,60 @@
+# 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.
+
+values:
+ google_compute_global_address.psa_ranges["bar"]:
+ address: 172.16.100.0
+ address_type: INTERNAL
+ description: null
+ ip_version: null
+ name: bar
+ prefix_length: 24
+ project: test-project
+ purpose: VPC_PEERING
+ google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ enable_ula_internal_ipv6: null
+ name: test
+ project: test-project
+ routing_mode: GLOBAL
+ google_compute_network_peering_routes_config.psa_routes["1"]:
+ export_custom_routes: false
+ import_custom_routes: true
+ project: test-project
+ google_service_networking_connection.psa_connection["1"]:
+ reserved_peering_ranges:
+ - bar
+ service: servicenetworking.googleapis.com
+
+counts:
+ google_compute_global_address: 1
+ google_compute_network: 1
+ google_compute_network_peering_routes_config: 1
+ google_service_networking_connection: 1
+
+outputs:
+ bindings: {}
+ name: __missing__
+ network: __missing__
+ project_id: test-project
+ self_link: __missing__
+ subnet_ips: {}
+ subnet_regions: {}
+ subnet_secondary_ranges: {}
+ subnet_self_links: {}
+ subnets: {}
+ subnets_proxy_only: {}
+ subnets_psc: {}
diff --git a/tests/modules/net_vpc/psa_routes_import_export.tfvars b/tests/modules/net_vpc/psa_routes_import_export.tfvars
new file mode 100644
index 00000000..20592231
--- /dev/null
+++ b/tests/modules/net_vpc/psa_routes_import_export.tfvars
@@ -0,0 +1,7 @@
+psa_config = {
+ ranges = {
+ bar = "172.16.100.0/24"
+ }
+ export_routes = true
+ import_routes = true
+}
diff --git a/tests/modules/net_vpc/psa_routes_import_export.yaml b/tests/modules/net_vpc/psa_routes_import_export.yaml
new file mode 100644
index 00000000..561fa50a
--- /dev/null
+++ b/tests/modules/net_vpc/psa_routes_import_export.yaml
@@ -0,0 +1,60 @@
+# 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.
+
+values:
+ google_compute_global_address.psa_ranges["bar"]:
+ address: 172.16.100.0
+ address_type: INTERNAL
+ description: null
+ ip_version: null
+ name: bar
+ prefix_length: 24
+ project: test-project
+ purpose: VPC_PEERING
+ google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ enable_ula_internal_ipv6: null
+ name: test
+ project: test-project
+ routing_mode: GLOBAL
+ google_compute_network_peering_routes_config.psa_routes["1"]:
+ export_custom_routes: true
+ import_custom_routes: true
+ project: test-project
+ google_service_networking_connection.psa_connection["1"]:
+ reserved_peering_ranges:
+ - bar
+ service: servicenetworking.googleapis.com
+
+counts:
+ google_compute_global_address: 1
+ google_compute_network: 1
+ google_compute_network_peering_routes_config: 1
+ google_service_networking_connection: 1
+
+outputs:
+ bindings: {}
+ name: __missing__
+ network: __missing__
+ project_id: test-project
+ self_link: __missing__
+ subnet_ips: {}
+ subnet_regions: {}
+ subnet_secondary_ranges: {}
+ subnet_self_links: {}
+ subnets: {}
+ subnets_proxy_only: {}
+ subnets_psc: {}
diff --git a/tests/modules/net_vpc/psa_simple.tfvars b/tests/modules/net_vpc/psa_simple.tfvars
new file mode 100644
index 00000000..51289fe0
--- /dev/null
+++ b/tests/modules/net_vpc/psa_simple.tfvars
@@ -0,0 +1,7 @@
+psa_config = {
+ ranges = {
+ bar = "172.16.100.0/24"
+ foo = "172.16.101.0/24"
+ }
+ routes = null
+}
diff --git a/tests/modules/net_vpc/psa_simple.yaml b/tests/modules/net_vpc/psa_simple.yaml
new file mode 100644
index 00000000..019b443f
--- /dev/null
+++ b/tests/modules/net_vpc/psa_simple.yaml
@@ -0,0 +1,70 @@
+# Copyright 2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+values:
+ google_compute_global_address.psa_ranges["bar"]:
+ address: 172.16.100.0
+ address_type: INTERNAL
+ description: null
+ ip_version: null
+ name: bar
+ prefix_length: 24
+ project: test-project
+ purpose: VPC_PEERING
+ google_compute_global_address.psa_ranges["foo"]:
+ address: 172.16.101.0
+ address_type: INTERNAL
+ description: null
+ ip_version: null
+ name: foo
+ prefix_length: 24
+ project: test-project
+ purpose: VPC_PEERING
+ google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ enable_ula_internal_ipv6: null
+ name: test
+ project: test-project
+ routing_mode: GLOBAL
+ google_compute_network_peering_routes_config.psa_routes["1"]:
+ export_custom_routes: false
+ import_custom_routes: false
+ project: test-project
+ google_service_networking_connection.psa_connection["1"]:
+ reserved_peering_ranges:
+ - bar
+ - foo
+ service: servicenetworking.googleapis.com
+
+counts:
+ google_compute_global_address: 2
+ google_compute_network: 1
+ google_compute_network_peering_routes_config: 1
+ google_service_networking_connection: 1
+
+outputs:
+ bindings: {}
+ name: __missing__
+ network: __missing__
+ project_id: test-project
+ self_link: __missing__
+ subnet_ips: {}
+ subnet_regions: {}
+ subnet_secondary_ranges: {}
+ subnet_self_links: {}
+ subnets: {}
+ subnets_proxy_only: {}
+ subnets_psc: {}
diff --git a/tests/modules/net_vpc/shared_vpc.tfvars b/tests/modules/net_vpc/shared_vpc.tfvars
new file mode 100644
index 00000000..3171539a
--- /dev/null
+++ b/tests/modules/net_vpc/shared_vpc.tfvars
@@ -0,0 +1,2 @@
+shared_vpc_host = true
+shared_vpc_service_projects = ["tf-a", "tf-b"]
diff --git a/tests/modules/net_vpc/shared_vpc.yaml b/tests/modules/net_vpc/shared_vpc.yaml
new file mode 100644
index 00000000..0837dbc4
--- /dev/null
+++ b/tests/modules/net_vpc/shared_vpc.yaml
@@ -0,0 +1,46 @@
+# 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.
+
+values:
+ google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ name: test
+ project: test-project
+ routing_mode: GLOBAL
+ google_compute_shared_vpc_host_project.shared_vpc_host[0]:
+ project: test-project
+ google_compute_shared_vpc_service_project.service_projects["tf-a"]:
+ host_project: test-project
+ service_project: tf-a
+ google_compute_shared_vpc_service_project.service_projects["tf-b"]:
+ host_project: test-project
+ service_project: tf-b
+
+counts:
+ google_compute_network: 1
+ google_compute_shared_vpc_host_project: 1
+ google_compute_shared_vpc_service_project: 2
+
+outputs:
+ bindings: {}
+ project_id: test-project
+ subnet_ips: {}
+ subnet_regions: {}
+ subnet_secondary_ranges: {}
+ subnet_self_links: {}
+ subnets: {}
+ subnets_proxy_only: {}
+ subnets_psc: {}
diff --git a/tests/modules/net_vpc/simple.tfvars b/tests/modules/net_vpc/simple.tfvars
new file mode 100644
index 00000000..6f848aa9
--- /dev/null
+++ b/tests/modules/net_vpc/simple.tfvars
@@ -0,0 +1 @@
+# skip boilerplate check
diff --git a/tests/modules/net_vpc/simple.yaml b/tests/modules/net_vpc/simple.yaml
new file mode 100644
index 00000000..004be7ec
--- /dev/null
+++ b/tests/modules/net_vpc/simple.yaml
@@ -0,0 +1,36 @@
+# 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.
+
+values:
+ google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ name: test
+ project: test-project
+ routing_mode: GLOBAL
+
+counts:
+ google_compute_network: 1
+
+outputs:
+ bindings: {}
+ project_id: test-project
+ subnet_ips: {}
+ subnet_regions: {}
+ subnet_secondary_ranges: {}
+ subnet_self_links: {}
+ subnets: {}
+ subnets_proxy_only: {}
+ subnets_psc: {}
diff --git a/tests/modules/net_vpc/subnets.tfvars b/tests/modules/net_vpc/subnets.tfvars
new file mode 100644
index 00000000..499e498f
--- /dev/null
+++ b/tests/modules/net_vpc/subnets.tfvars
@@ -0,0 +1,44 @@
+subnet_iam = {
+ "europe-west1/a" = {
+ "roles/compute.networkUser" = [
+ "user:a@example.com", "group:g-a@example.com"
+ ]
+ }
+ "europe-west1/c" = {
+ "roles/compute.networkUser" = [
+ "user:c@example.com", "group:g-c@example.com"
+ ]
+ }
+}
+subnets = [
+ {
+ name = "a"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.0.0/24"
+ },
+ {
+ name = "b"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.1.0/24",
+ description = "Subnet b"
+ enable_private_access = false
+ },
+ {
+ name = "c"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.2.0/24"
+ secondary_ip_ranges = {
+ a = "192.168.0.0/24"
+ b = "192.168.1.0/24"
+ }
+ },
+ {
+ name = "d"
+ region = "europe-west1"
+ ip_cidr_range = "10.0.3.0/24"
+ flow_logs_config = {
+ flow_sampling = 0.5
+ aggregation_interval = "INTERVAL_10_MIN"
+ }
+ }
+]
diff --git a/tests/modules/net_vpc/subnets.yaml b/tests/modules/net_vpc/subnets.yaml
new file mode 100644
index 00000000..9ccf31e6
--- /dev/null
+++ b/tests/modules/net_vpc/subnets.yaml
@@ -0,0 +1,120 @@
+# 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.
+
+values:
+ google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ name: test
+ project: test-project
+ routing_mode: GLOBAL
+ google_compute_subnetwork.subnetwork["europe-west1/a"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.0.0/24
+ log_config: []
+ name: a
+ private_ip_google_access: true
+ project: test-project
+ region: europe-west1
+ role: null
+ secondary_ip_range: []
+ google_compute_subnetwork.subnetwork["europe-west1/b"]:
+ description: Subnet b
+ ip_cidr_range: 10.0.1.0/24
+ log_config: []
+ name: b
+ private_ip_google_access: false
+ project: test-project
+ region: europe-west1
+ role: null
+ secondary_ip_range: []
+ google_compute_subnetwork.subnetwork["europe-west1/c"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.2.0/24
+ ipv6_access_type: null
+ log_config: []
+ name: c
+ private_ip_google_access: true
+ project: test-project
+ region: europe-west1
+ role: null
+ secondary_ip_range:
+ - ip_cidr_range: 192.168.0.0/24
+ range_name: a
+ - ip_cidr_range: 192.168.1.0/24
+ range_name: b
+ google_compute_subnetwork.subnetwork["europe-west1/d"]:
+ description: Terraform-managed.
+ ip_cidr_range: 10.0.3.0/24
+ log_config:
+ - aggregation_interval: INTERVAL_10_MIN
+ filter_expr: 'true'
+ flow_sampling: 0.5
+ metadata: INCLUDE_ALL_METADATA
+ metadata_fields: null
+ name: d
+ private_ip_google_access: true
+ project: test-project
+ region: europe-west1
+ role: null
+ secondary_ip_range: []
+ google_compute_subnetwork_iam_binding.binding["europe-west1/a.roles/compute.networkUser"]:
+ condition: []
+ members:
+ - group:g-a@example.com
+ - user:a@example.com
+ project: test-project
+ region: europe-west1
+ role: roles/compute.networkUser
+ subnetwork: a
+ google_compute_subnetwork_iam_binding.binding["europe-west1/c.roles/compute.networkUser"]:
+ condition: []
+ members:
+ - group:g-c@example.com
+ - user:c@example.com
+ project: test-project
+ region: europe-west1
+ role: roles/compute.networkUser
+ subnetwork: c
+
+counts:
+ google_compute_network: 1
+ google_compute_subnetwork: 4
+ google_compute_subnetwork_iam_binding: 2
+
+outputs:
+ bindings: __missing__
+ project_id: test-project
+ subnet_ips:
+ europe-west1/a: 10.0.0.0/24
+ europe-west1/b: 10.0.1.0/24
+ europe-west1/c: 10.0.2.0/24
+ europe-west1/d: 10.0.3.0/24
+ subnet_regions:
+ europe-west1/a: europe-west1
+ europe-west1/b: europe-west1
+ europe-west1/c: europe-west1
+ europe-west1/d: europe-west1
+ subnet_secondary_ranges:
+ europe-west1/a: {}
+ europe-west1/b: {}
+ europe-west1/c:
+ a: 192.168.0.0/24
+ b: 192.168.1.0/24
+ europe-west1/d: {}
+ subnet_self_links: __missing__
+ subnets: __missing__
+ subnets_proxy_only: {}
+ subnets_psc: {}
diff --git a/tests/modules/net_vpc/test_plan.py b/tests/modules/net_vpc/test_plan.py
deleted file mode 100644
index 2f49f801..00000000
--- a/tests/modules/net_vpc/test_plan.py
+++ /dev/null
@@ -1,81 +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.
-
-_VAR_PEER_VPC_CONFIG = '''{
- peer_vpc_self_link="projects/my-project/global/networks/peer",
- export_routes=true, import_routes=null
-}'''
-_VAR_ROUTES_TEMPLATE = '''{
- next-hop = {
- dest_range="192.168.128.0/24", tags=null,
- next_hop_type="%s", next_hop="%s"},
- gateway = {
- dest_range="0.0.0.0/0", priority=100, tags=["tag-a"],
- next_hop_type="gateway",
- next_hop="global/gateways/default-internet-gateway"}
-}'''
-_VAR_ROUTES_NEXT_HOPS = {
- 'gateway': 'global/gateways/default-internet-gateway',
- 'instance': 'zones/europe-west1-b/test',
- 'ip': '192.168.0.128',
- 'ilb': 'regions/europe-west1/forwardingRules/test',
- 'vpn_tunnel': 'regions/europe-west1/vpnTunnels/foo'
-}
-
-
-def test_vpc_simple(plan_runner):
- "Test vpc with no extra options."
- _, resources = plan_runner()
- assert len(resources) == 1
- assert [r['type'] for r in resources] == ['google_compute_network']
- assert [r['values']['name'] for r in resources] == ['test']
- assert [r['values']['project'] for r in resources] == ['test-project']
-
-
-def test_vpc_shared(plan_runner):
- "Test shared vpc variables."
- _, resources = plan_runner(shared_vpc_host='true',
- shared_vpc_service_projects='["tf-a", "tf-b"]')
- assert len(resources) == 4
- assert set(r['type'] for r in resources) == set([
- 'google_compute_network', 'google_compute_shared_vpc_host_project',
- 'google_compute_shared_vpc_service_project'
- ])
-
-
-def test_vpc_peering(plan_runner):
- "Test vpc peering variables."
- _, resources = plan_runner(peering_config=_VAR_PEER_VPC_CONFIG)
- assert len(resources) == 3
- assert set(r['type'] for r in resources) == set(
- ['google_compute_network', 'google_compute_network_peering'])
- peerings = [
- r['values']
- for r in resources
- if r['type'] == 'google_compute_network_peering'
- ]
- assert [p['name'] for p in peerings] == ['test-peer', 'peer-test']
- assert [p['export_custom_routes'] for p in peerings] == [True, False]
- assert [p['import_custom_routes'] for p in peerings] == [False, True]
-
-
-def test_vpc_routes(plan_runner):
- "Test vpc routes."
- for next_hop_type, next_hop in _VAR_ROUTES_NEXT_HOPS.items():
- _var_routes = _VAR_ROUTES_TEMPLATE % (next_hop_type, next_hop)
- _, resources = plan_runner(routes=_var_routes)
- assert len(resources) == 3
- resource = [r for r in resources if r['values']['name'] == 'test-next-hop'
- ][0]
- assert resource['values']['next_hop_%s' % next_hop_type]
diff --git a/tests/modules/net_vpc/test_plan_psa.py b/tests/modules/net_vpc/test_plan_psa.py
deleted file mode 100644
index 359977d6..00000000
--- a/tests/modules/net_vpc/test_plan_psa.py
+++ /dev/null
@@ -1,79 +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.
-
-
-def test_single_range(plan_runner):
- "Test single PSA range."
- psa_config = '''{
- ranges = {
- bar = "172.16.100.0/24"
- foo = "172.16.101.0/24"
- },
- routes = null
- }'''
- _, resources = plan_runner(psa_config=psa_config)
- assert len(resources) == 5
- for r in resources:
- if r['type'] == 'google_compute_network_peering_routes_config':
- assert not r['values']['export_custom_routes']
- assert not r['values']['import_custom_routes']
-
-
-def test_routes_export(plan_runner):
- "Test routes export."
- psa_config = '''{
- ranges = {
- bar = "172.16.100.0/24"
- }
- export_routes = true
- import_routes = false
- }'''
- _, resources = plan_runner(psa_config=psa_config)
- assert len(resources) == 4
- for r in resources:
- if r['type'] == 'google_compute_network_peering_routes_config':
- assert r['values']['export_custom_routes']
- assert not r['values']['import_custom_routes']
-
-
-def test_routes_import(plan_runner):
- "Test routes import."
- psa_config = '''{
- ranges = {
- bar = "172.16.100.0/24"
- },
- export_routes = false
- import_routes = true
- }'''
- _, resources = plan_runner(psa_config=psa_config)
- for r in resources:
- if r['type'] == 'google_compute_network_peering_routes_config':
- assert not r['values']['export_custom_routes']
- assert r['values']['import_custom_routes']
-
-
-def test_routes_export_import(plan_runner):
- "Test routes export and import."
- psa_config = '''{
- ranges = {
- bar = "172.16.100.0/24"
- },
- export_routes = true
- import_routes = true
- }'''
- _, resources = plan_runner(psa_config=psa_config)
- for r in resources:
- if r['type'] == 'google_compute_network_peering_routes_config':
- assert r['values']['export_custom_routes']
- assert r['values']['import_custom_routes']
diff --git a/tests/modules/net_vpc/test_plan_subnets.py b/tests/modules/net_vpc/test_plan_subnets.py
deleted file mode 100644
index affea44c..00000000
--- a/tests/modules/net_vpc/test_plan_subnets.py
+++ /dev/null
@@ -1,71 +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.
-
-DATA_FOLDER = "data"
-
-
-def test_subnet_factory(plan_runner):
- "Test subnet factory."
- _, resources = plan_runner(data_folder=DATA_FOLDER)
- assert len(resources) == 3
- subnets = [
- r['values'] for r in resources if r['type'] == 'google_compute_subnetwork'
- ]
- assert {s['name'] for s in subnets} == {'factory-subnet', 'factory-subnet2'}
- assert {len(s['secondary_ip_range']) for s in subnets} == {0, 1}
- assert {s['private_ip_google_access'] for s in subnets} == {True, False}
-
-
-def test_subnets(plan_runner):
- "Test subnets variable."
- _, resources = plan_runner(tf_var_file='test.subnets.tfvars')
- assert len(resources) == 7
- subnets = [
- r['values'] for r in resources if r['type'] == 'google_compute_subnetwork'
- ]
- assert {s['name'] for s in subnets} == {'a', 'b', 'c', 'd'}
- assert {len(s['secondary_ip_range']) for s in subnets} == {0, 0, 2, 0}
- log_config = {s['name']: s['log_config'] for s in subnets if s['log_config']}
- assert log_config == {
- 'd': [{
- 'aggregation_interval': 'INTERVAL_10_MIN',
- 'filter_expr': 'true',
- 'flow_sampling': 0.5,
- 'metadata': 'INCLUDE_ALL_METADATA',
- 'metadata_fields': None
- }]
- }
- bindings = {
- r['index']: r['values']
- for r in resources
- if r['type'] == 'google_compute_subnetwork_iam_binding'
- }
- assert bindings == {
- 'europe-west1/a.roles/compute.networkUser': {
- 'condition': [],
- 'members': ['group:g-a@example.com', 'user:a@example.com'],
- 'project': 'test-project',
- 'region': 'europe-west1',
- 'role': 'roles/compute.networkUser',
- 'subnetwork': 'a'
- },
- 'europe-west1/c.roles/compute.networkUser': {
- 'condition': [],
- 'members': ['group:g-c@example.com', 'user:c@example.com'],
- 'project': 'test-project',
- 'region': 'europe-west1',
- 'role': 'roles/compute.networkUser',
- 'subnetwork': 'c'
- },
- }
diff --git a/tests/modules/net_vpc/test_routes.py b/tests/modules/net_vpc/test_routes.py
new file mode 100644
index 00000000..01d9673d
--- /dev/null
+++ b/tests/modules/net_vpc/test_routes.py
@@ -0,0 +1,47 @@
+# 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.
+
+import pytest
+
+_route_parameters = [('gateway', 'global/gateways/default-internet-gateway'),
+ ('instance', 'zones/europe-west1-b/test'),
+ ('ip', '192.168.0.128'),
+ ('ilb', 'regions/europe-west1/forwardingRules/test'),
+ ('vpn_tunnel', 'regions/europe-west1/vpnTunnels/foo')]
+
+
+@pytest.mark.parametrize('next_hop_type,next_hop', _route_parameters)
+def test_vpc_routes(plan_summary, next_hop_type, next_hop):
+ 'Test vpc routes.'
+
+ var_routes = '''{
+ next-hop = {
+ dest_range = "192.168.128.0/24"
+ tags = null
+ next_hop_type = "%s"
+ next_hop = "%s"
+ }
+ gateway = {
+ dest_range = "0.0.0.0/0",
+ priority = 100
+ tags = ["tag-a"]
+ next_hop_type = "gateway",
+ next_hop = "global/gateways/default-internet-gateway"
+ }
+ }''' % (next_hop_type, next_hop)
+ summary = plan_summary('modules/net-vpc', tf_var_files=['common.tfvars'],
+ routes=var_routes)
+ assert len(summary.values) == 3
+ route = summary.values[f'google_compute_route.{next_hop_type}["next-hop"]']
+ assert route[f'next_hop_{next_hop_type}'] == next_hop
diff --git a/tests/blueprints/conftest.py b/tests/modules/net_vpc/tftest.yaml
similarity index 73%
rename from tests/blueprints/conftest.py
rename to tests/modules/net_vpc/tftest.yaml
index ed29d5bb..b2b09798 100644
--- a/tests/blueprints/conftest.py
+++ b/tests/modules/net_vpc/tftest.yaml
@@ -12,10 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import pytest
+module: modules/net-vpc
+common_tfvars:
+ - common.tfvars
-
-def pytest_collection_modifyitems(config, items):
- for item in items:
- item.add_marker(
- pytest.mark.xdist_group(name='/'.join(item.path.parent.parts[-2:])))
+tests:
+ simple:
+ subnets:
+ peering:
+ shared_vpc:
+ factory:
+ psa_simple:
+ psa_routes_export:
+ psa_routes_import:
+ psa_routes_import_export:
diff --git a/tests/modules/organization/audit_config.tfvars b/tests/modules/organization/audit_config.tfvars
new file mode 100644
index 00000000..b071033a
--- /dev/null
+++ b/tests/modules/organization/audit_config.tfvars
@@ -0,0 +1,6 @@
+iam_audit_config = {
+ allServices = {
+ DATA_READ = [],
+ DATA_WRITE = ["user:me@example.org"]
+ }
+}
diff --git a/tests/fast/conftest.py b/tests/modules/organization/audit_config.yaml
similarity index 78%
rename from tests/fast/conftest.py
rename to tests/modules/organization/audit_config.yaml
index 3d559e6f..56e90b62 100644
--- a/tests/fast/conftest.py
+++ b/tests/modules/organization/audit_config.yaml
@@ -12,9 +12,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import pytest
-
-
-def pytest_collection_modifyitems(config, items):
- for item in items:
- item.add_marker(pytest.mark.xdist_group(name=item.path.parent.name))
+counts:
+ google_organization_iam_audit_config: 1
diff --git a/tests/modules/organization/common.tfvars b/tests/modules/organization/common.tfvars
new file mode 100644
index 00000000..d5f1c3d7
--- /dev/null
+++ b/tests/modules/organization/common.tfvars
@@ -0,0 +1 @@
+organization_id = "organizations/1234567890"
diff --git a/tests/modules/organization/fixture/data/firewall-cidrs.yaml b/tests/modules/organization/data/firewall-cidrs.yaml
similarity index 100%
rename from tests/modules/organization/fixture/data/firewall-cidrs.yaml
rename to tests/modules/organization/data/firewall-cidrs.yaml
diff --git a/tests/modules/organization/fixture/data/firewall-rules.yaml b/tests/modules/organization/data/firewall-rules.yaml
similarity index 100%
rename from tests/modules/organization/fixture/data/firewall-rules.yaml
rename to tests/modules/organization/data/firewall-rules.yaml
diff --git a/tests/modules/organization/firewall_policies.tfvars b/tests/modules/organization/firewall_policies.tfvars
new file mode 100644
index 00000000..603cd3a4
--- /dev/null
+++ b/tests/modules/organization/firewall_policies.tfvars
@@ -0,0 +1,45 @@
+firewall_policies = {
+ policy1 = {
+ allow-ingress = {
+ description = ""
+ direction = "INGRESS"
+ action = "allow"
+ priority = 100
+ ranges = ["10.0.0.0/8"]
+ ports = {
+ tcp = ["22"]
+ }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
+ deny-egress = {
+ description = ""
+ direction = "EGRESS"
+ action = "deny"
+ priority = 200
+ ranges = ["192.168.0.0/24"]
+ ports = {
+ tcp = ["443"]
+ }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
+ }
+ policy2 = {
+ allow-ingress = {
+ description = ""
+ direction = "INGRESS"
+ action = "allow"
+ priority = 100
+ ranges = ["10.0.0.0/8"]
+ ports = {
+ tcp = ["22"]
+ }
+ target_service_accounts = null
+ target_resources = null
+ logging = false
+ }
+ }
+}
diff --git a/tests/modules/organization/firewall_policies.yaml b/tests/modules/organization/firewall_policies.yaml
new file mode 100644
index 00000000..4ecc5c72
--- /dev/null
+++ b/tests/modules/organization/firewall_policies.yaml
@@ -0,0 +1,73 @@
+# 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.
+
+values:
+ google_compute_firewall_policy.policy["policy1"]:
+ parent: organizations/1234567890
+ short_name: policy1
+ google_compute_firewall_policy.policy["policy2"]:
+ parent: organizations/1234567890
+ short_name: policy2
+ google_compute_firewall_policy_rule.rule["policy1-allow-ingress"]:
+ action: allow
+ direction: INGRESS
+ disabled: null
+ enable_logging: false
+ match:
+ - dest_ip_ranges: null
+ layer4_configs:
+ - ip_protocol: tcp
+ ports:
+ - '22'
+ src_ip_ranges:
+ - 10.0.0.0/8
+ priority: 100
+ target_resources: null
+ target_service_accounts: null
+ google_compute_firewall_policy_rule.rule["policy1-deny-egress"]:
+ action: deny
+ direction: EGRESS
+ disabled: null
+ enable_logging: false
+ match:
+ - dest_ip_ranges:
+ - 192.168.0.0/24
+ layer4_configs:
+ - ip_protocol: tcp
+ ports:
+ - '443'
+ src_ip_ranges: null
+ priority: 200
+ target_resources: null
+ target_service_accounts: null
+ google_compute_firewall_policy_rule.rule["policy2-allow-ingress"]:
+ action: allow
+ direction: INGRESS
+ disabled: null
+ enable_logging: false
+ match:
+ - dest_ip_ranges: null
+ layer4_configs:
+ - ip_protocol: tcp
+ ports:
+ - '22'
+ src_ip_ranges:
+ - 10.0.0.0/8
+ priority: 100
+ target_resources: null
+ target_service_accounts: null
+
+counts:
+ google_compute_firewall_policy: 2
+ google_compute_firewall_policy_rule: 3
diff --git a/tests/modules/organization/firewall_policies_factory.tfvars b/tests/modules/organization/firewall_policies_factory.tfvars
new file mode 100644
index 00000000..3e1cf181
--- /dev/null
+++ b/tests/modules/organization/firewall_policies_factory.tfvars
@@ -0,0 +1,5 @@
+firewall_policy_factory = {
+ cidr_file = "../../tests/modules/organization/data/firewall-cidrs.yaml"
+ policy_name = "factory-1"
+ rules_file = "../../tests/modules/organization/data/firewall-rules.yaml"
+}
diff --git a/tests/modules/organization/firewall_policies_factory.yaml b/tests/modules/organization/firewall_policies_factory.yaml
new file mode 100644
index 00000000..85e565fd
--- /dev/null
+++ b/tests/modules/organization/firewall_policies_factory.yaml
@@ -0,0 +1,61 @@
+# 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.
+
+values:
+ google_compute_firewall_policy.policy["factory-1"]:
+ description: null
+ parent: organizations/1234567890
+ short_name: factory-1
+ timeouts: null
+ google_compute_firewall_policy_rule.rule["factory-1-allow-admins"]:
+ action: allow
+ description: Access from the admin subnet to all subnets
+ direction: INGRESS
+ disabled: null
+ enable_logging: null
+ match:
+ - dest_ip_ranges: null
+ layer4_configs:
+ - ip_protocol: all
+ ports: []
+ src_ip_ranges:
+ - 10.0.0.0/8
+ - 172.168.0.0/12
+ - 192.168.0.0/16
+ priority: 1000
+ target_resources: null
+ target_service_accounts: null
+ timeouts: null
+ google_compute_firewall_policy_rule.rule["factory-1-allow-ssh-from-iap"]:
+ action: allow
+ description: Enable SSH from IAP
+ direction: INGRESS
+ disabled: null
+ enable_logging: null
+ match:
+ - dest_ip_ranges: null
+ layer4_configs:
+ - ip_protocol: tcp
+ ports:
+ - '22'
+ src_ip_ranges:
+ - 35.235.240.0/20
+ priority: 1002
+ target_resources: null
+ target_service_accounts: null
+ timeouts: null
+
+counts:
+ google_compute_firewall_policy: 1
+ google_compute_firewall_policy_rule: 2
diff --git a/tests/modules/organization/firewall_policies_factory_combined.tfvars b/tests/modules/organization/firewall_policies_factory_combined.tfvars
new file mode 100644
index 00000000..6f848aa9
--- /dev/null
+++ b/tests/modules/organization/firewall_policies_factory_combined.tfvars
@@ -0,0 +1 @@
+# skip boilerplate check
diff --git a/tests/modules/organization/firewall_policies_factory_combined.yaml b/tests/modules/organization/firewall_policies_factory_combined.yaml
new file mode 100644
index 00000000..3b5cf6cc
--- /dev/null
+++ b/tests/modules/organization/firewall_policies_factory_combined.yaml
@@ -0,0 +1,27 @@
+# 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.
+
+values:
+ google_compute_firewall_policy.policy["factory-1"]: {}
+ google_compute_firewall_policy.policy["policy1"]: {}
+ google_compute_firewall_policy.policy["policy2"]: {}
+ google_compute_firewall_policy_rule.rule["factory-1-allow-admins"]: {}
+ google_compute_firewall_policy_rule.rule["factory-1-allow-ssh-from-iap"]: {}
+ google_compute_firewall_policy_rule.rule["policy1-allow-ingress"]: {}
+ google_compute_firewall_policy_rule.rule["policy1-deny-egress"]: {}
+ google_compute_firewall_policy_rule.rule["policy2-allow-ingress"]: {}
+
+counts:
+ google_compute_firewall_policy: 3
+ google_compute_firewall_policy_rule: 5
diff --git a/tests/modules/organization/fixture/test.network_tags.tfvars b/tests/modules/organization/fixture/test.network_tags.tfvars
deleted file mode 100644
index 6c5c3ccd..00000000
--- a/tests/modules/organization/fixture/test.network_tags.tfvars
+++ /dev/null
@@ -1,5 +0,0 @@
-network_tags = {
- net_environment = {
- network = "foobar"
- }
-}
diff --git a/tests/modules/organization/iam.tfvars b/tests/modules/organization/iam.tfvars
new file mode 100644
index 00000000..69963127
--- /dev/null
+++ b/tests/modules/organization/iam.tfvars
@@ -0,0 +1,18 @@
+group_iam = {
+ "owners@example.org" = [
+ "roles/owner",
+ "roles/resourcemanager.folderAdmin"
+ ],
+ "viewers@example.org" = [
+ "roles/viewer"
+ ]
+}
+iam = {
+ "roles/owner" = [
+ "user:one@example.org",
+ "user:two@example.org"
+ ],
+ "roles/browser" = [
+ "domain:example.org"
+ ]
+}
diff --git a/tests/modules/organization/iam.yaml b/tests/modules/organization/iam.yaml
new file mode 100644
index 00000000..7b1a8cb9
--- /dev/null
+++ b/tests/modules/organization/iam.yaml
@@ -0,0 +1,44 @@
+# 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.
+
+values:
+ google_organization_iam_binding.authoritative["roles/browser"]:
+ condition: []
+ members:
+ - domain:example.org
+ org_id: '1234567890'
+ role: roles/browser
+ google_organization_iam_binding.authoritative["roles/owner"]:
+ condition: []
+ members:
+ - group:owners@example.org
+ - user:one@example.org
+ - user:two@example.org
+ org_id: '1234567890'
+ role: roles/owner
+ google_organization_iam_binding.authoritative["roles/resourcemanager.folderAdmin"]:
+ condition: []
+ members:
+ - group:owners@example.org
+ org_id: '1234567890'
+ role: roles/resourcemanager.folderAdmin
+ google_organization_iam_binding.authoritative["roles/viewer"]:
+ condition: []
+ members:
+ - group:viewers@example.org
+ org_id: '1234567890'
+ role: roles/viewer
+
+counts:
+ google_organization_iam_binding: 4
diff --git a/tests/modules/organization/iam_additive.tfvars b/tests/modules/organization/iam_additive.tfvars
new file mode 100644
index 00000000..823d70ad
--- /dev/null
+++ b/tests/modules/organization/iam_additive.tfvars
@@ -0,0 +1,4 @@
+iam = {
+ "user:one@example.org" = ["roles/owner"],
+ "user:two@example.org" = ["roles/owner", "roles/editor"]
+}
diff --git a/tests/modules/organization/iam_additive.yaml b/tests/modules/organization/iam_additive.yaml
new file mode 100644
index 00000000..68eda8c2
--- /dev/null
+++ b/tests/modules/organization/iam_additive.yaml
@@ -0,0 +1,31 @@
+# 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.
+
+values:
+ google_organization_iam_binding.authoritative["user:one@example.org"]:
+ condition: []
+ members:
+ - roles/owner
+ org_id: '1234567890'
+ role: user:one@example.org
+ google_organization_iam_binding.authoritative["user:two@example.org"]:
+ condition: []
+ members:
+ - roles/editor
+ - roles/owner
+ org_id: '1234567890'
+ role: user:two@example.org
+
+counts:
+ google_organization_iam_binding: 2
diff --git a/tests/modules/organization/logging.tfvars b/tests/modules/organization/logging.tfvars
new file mode 100644
index 00000000..95a272e1
--- /dev/null
+++ b/tests/modules/organization/logging.tfvars
@@ -0,0 +1,29 @@
+logging_sinks = {
+ warning = {
+ destination = "mybucket"
+ type = "storage"
+ filter = "severity=WARNING"
+ }
+ info = {
+ destination = "projects/myproject/datasets/mydataset"
+ type = "bigquery"
+ filter = "severity=INFO"
+ disabled = true
+ }
+ notice = {
+ destination = "projects/myproject/topics/mytopic"
+ type = "pubsub"
+ filter = "severity=NOTICE"
+ include_children = false
+ }
+ debug = {
+ destination = "projects/myproject/locations/global/buckets/mybucket"
+ type = "logging"
+ filter = "severity=DEBUG"
+ include_children = false
+ exclusions = {
+ no-compute = "logName:compute"
+ no-container = "logName:container"
+ }
+ }
+}
diff --git a/tests/modules/organization/logging.yaml b/tests/modules/organization/logging.yaml
new file mode 100644
index 00000000..8038c9ab
--- /dev/null
+++ b/tests/modules/organization/logging.yaml
@@ -0,0 +1,86 @@
+# 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.
+
+values:
+ google_bigquery_dataset_iam_member.bq-sinks-binding["info"]:
+ condition: []
+ dataset_id: mydataset
+ project: myproject
+ role: roles/bigquery.dataEditor
+ google_logging_organization_sink.sink["debug"]:
+ description: debug (Terraform-managed).
+ destination: logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket
+ disabled: false
+ exclusions:
+ - description: null
+ disabled: false
+ filter: logName:compute
+ name: no-compute
+ - description: null
+ disabled: false
+ filter: logName:container
+ name: no-container
+ filter: severity=DEBUG
+ include_children: false
+ name: debug
+ org_id: '1234567890'
+ google_logging_organization_sink.sink["info"]:
+ description: info (Terraform-managed).
+ destination: bigquery.googleapis.com/projects/myproject/datasets/mydataset
+ disabled: true
+ exclusions: []
+ filter: severity=INFO
+ include_children: true
+ name: info
+ org_id: '1234567890'
+ google_logging_organization_sink.sink["notice"]:
+ description: notice (Terraform-managed).
+ destination: pubsub.googleapis.com/projects/myproject/topics/mytopic
+ disabled: false
+ exclusions: []
+ filter: severity=NOTICE
+ include_children: false
+ name: notice
+ org_id: '1234567890'
+ google_logging_organization_sink.sink["warning"]:
+ description: warning (Terraform-managed).
+ destination: storage.googleapis.com/mybucket
+ disabled: false
+ exclusions: []
+ filter: severity=WARNING
+ include_children: true
+ name: warning
+ org_id: '1234567890'
+ google_project_iam_member.bucket-sinks-binding["debug"]:
+ condition:
+ - expression: resource.name.endsWith('projects/myproject/locations/global/buckets/mybucket')
+ title: debug bucket writer
+ project: myproject
+ role: roles/logging.bucketWriter
+ google_pubsub_topic_iam_member.pubsub-sinks-binding["notice"]:
+ condition: []
+ project: myproject
+ role: roles/pubsub.publisher
+ topic: mytopic
+ google_storage_bucket_iam_member.storage-sinks-binding["warning"]:
+ bucket: mybucket
+ condition: []
+ role: roles/storage.objectCreator
+
+counts:
+ google_bigquery_dataset_iam_member: 1
+ google_logging_organization_sink: 4
+ google_project_iam_member: 1
+ google_pubsub_topic_iam_member: 1
+ google_storage_bucket_iam_member: 1
diff --git a/tests/modules/organization/logging_exclusions.tfvars b/tests/modules/organization/logging_exclusions.tfvars
new file mode 100644
index 00000000..75c35604
--- /dev/null
+++ b/tests/modules/organization/logging_exclusions.tfvars
@@ -0,0 +1,4 @@
+logging_exclusions = {
+ exclusion1 = "resource.type=gce_instance"
+ exclusion2 = "severity=NOTICE"
+}
diff --git a/tests/modules/organization/logging_exclusions.yaml b/tests/modules/organization/logging_exclusions.yaml
new file mode 100644
index 00000000..4d51dd7c
--- /dev/null
+++ b/tests/modules/organization/logging_exclusions.yaml
@@ -0,0 +1,30 @@
+# 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.
+
+values:
+ google_logging_organization_exclusion.logging-exclusion["exclusion1"]:
+ description: exclusion1 (Terraform-managed).
+ disabled: null
+ filter: resource.type=gce_instance
+ name: exclusion1
+ org_id: '1234567890'
+ google_logging_organization_exclusion.logging-exclusion["exclusion2"]:
+ description: exclusion2 (Terraform-managed).
+ disabled: null
+ filter: severity=NOTICE
+ name: exclusion2
+ org_id: '1234567890'
+
+counts:
+ google_logging_organization_exclusion: 2
diff --git a/tests/modules/organization/fixture/test.orgpolicies-boolean.tfvars b/tests/modules/organization/org_policies_boolean.tfvars
similarity index 100%
rename from tests/modules/organization/fixture/test.orgpolicies-boolean.tfvars
rename to tests/modules/organization/org_policies_boolean.tfvars
diff --git a/tests/modules/organization/org_policies_boolean.yaml b/tests/modules/organization/org_policies_boolean.yaml
new file mode 100644
index 00000000..310997a4
--- /dev/null
+++ b/tests/modules/organization/org_policies_boolean.yaml
@@ -0,0 +1,53 @@
+# 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.
+
+values:
+ google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]:
+ name: organizations/1234567890/policies/iam.disableServiceAccountKeyCreation
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ timeouts: null
+ google_org_policy_policy.default["iam.disableServiceAccountKeyUpload"]:
+ name: organizations/1234567890/policies/iam.disableServiceAccountKeyUpload
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: 'FALSE'
+ values: []
+ - allow_all: null
+ condition:
+ - description: test condition
+ expression: resource.matchTagId(aa, bb)
+ location: xxx
+ title: condition
+ deny_all: null
+ enforce: 'TRUE'
+ values: []
+ timeouts: null
+
+counts:
+ google_org_policy_policy: 2
diff --git a/tests/modules/organization/fixture/test.orgpolicy-custom-constraints.tfvars b/tests/modules/organization/org_policies_custom_constraints.tfvars
similarity index 100%
rename from tests/modules/organization/fixture/test.orgpolicy-custom-constraints.tfvars
rename to tests/modules/organization/org_policies_custom_constraints.tfvars
diff --git a/tests/modules/organization/org_policies_custom_constraints.yaml b/tests/modules/organization/org_policies_custom_constraints.yaml
new file mode 100644
index 00000000..c558c066
--- /dev/null
+++ b/tests/modules/organization/org_policies_custom_constraints.yaml
@@ -0,0 +1,37 @@
+# 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.
+
+values:
+ google_org_policy_custom_constraint.constraint["custom.dataprocNoMoreThan10Workers"]:
+ action_type: DENY
+ condition: resource.config.workerConfig.numInstances + resource.config.secondaryWorkerConfig.numInstances > 10
+ method_types:
+ - CREATE
+ - UPDATE
+ name: custom.dataprocNoMoreThan10Workers
+ parent: organizations/1234567890
+ resource_types:
+ - dataproc.googleapis.com/Cluster
+ google_org_policy_custom_constraint.constraint["custom.gkeEnableAutoUpgrade"]:
+ action_type: ALLOW
+ condition: resource.management.autoUpgrade == true
+ method_types:
+ - CREATE
+ name: custom.gkeEnableAutoUpgrade
+ parent: organizations/1234567890
+ resource_types:
+ - container.googleapis.com/NodePool
+
+counts:
+ google_org_policy_custom_constraint: 2
diff --git a/tests/modules/organization/fixture/test.orgpolicies-list.tfvars b/tests/modules/organization/org_policies_list.tfvars
similarity index 96%
rename from tests/modules/organization/fixture/test.orgpolicies-list.tfvars
rename to tests/modules/organization/org_policies_list.tfvars
index 73807173..f9de8dba 100644
--- a/tests/modules/organization/fixture/test.orgpolicies-list.tfvars
+++ b/tests/modules/organization/org_policies_list.tfvars
@@ -3,6 +3,7 @@ org_policies = {
deny = { all = true }
}
"iam.allowedPolicyMemberDomains" = {
+ inherit_from_parent = true
allow = {
values = ["C0xxxxxxx", "C0yyyyyyy"]
}
diff --git a/tests/modules/organization/org_policies_list.yaml b/tests/modules/organization/org_policies_list.yaml
new file mode 100644
index 00000000..39c3a389
--- /dev/null
+++ b/tests/modules/organization/org_policies_list.yaml
@@ -0,0 +1,85 @@
+# 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.
+
+values:
+ google_org_policy_policy.default["compute.restrictLoadBalancerCreationForTypes"]:
+ name: organizations/1234567890/policies/compute.restrictLoadBalancerCreationForTypes
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values: null
+ denied_values:
+ - in:EXTERNAL
+ - allow_all: null
+ condition:
+ - description: test condition
+ expression: resource.matchTagId(aa, bb)
+ location: xxx
+ title: condition
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - EXTERNAL_1
+ denied_values: null
+ - allow_all: 'TRUE'
+ condition:
+ - description: test condition2
+ expression: resource.matchTagId(cc, dd)
+ location: xxx
+ title: condition2
+ deny_all: null
+ enforce: null
+ values: []
+ timeouts: null
+ google_org_policy_policy.default["compute.vmExternalIpAccess"]:
+ name: organizations/1234567890/policies/compute.vmExternalIpAccess
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: null
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: 'TRUE'
+ enforce: null
+ values: []
+ timeouts: null
+ google_org_policy_policy.default["iam.allowedPolicyMemberDomains"]:
+ name: organizations/1234567890/policies/iam.allowedPolicyMemberDomains
+ parent: organizations/1234567890
+ spec:
+ - inherit_from_parent: true
+ reset: null
+ rules:
+ - allow_all: null
+ condition: []
+ deny_all: null
+ enforce: null
+ values:
+ - allowed_values:
+ - C0xxxxxxx
+ - C0yyyyyyy
+ denied_values: null
+ timeouts: null
+
+counts:
+ google_org_policy_policy: 3
diff --git a/tests/modules/organization/fixture/test.resource_tags.tfvars b/tests/modules/organization/tags.tfvars
similarity index 91%
rename from tests/modules/organization/fixture/test.resource_tags.tfvars
rename to tests/modules/organization/tags.tfvars
index 42cf7e0f..31c6764e 100644
--- a/tests/modules/organization/fixture/test.resource_tags.tfvars
+++ b/tests/modules/organization/tags.tfvars
@@ -1,3 +1,8 @@
+network_tags = {
+ net_environment = {
+ network = "foobar"
+ }
+}
tags = {
foo = {}
bar = {
diff --git a/tests/modules/organization/tags.yaml b/tests/modules/organization/tags.yaml
new file mode 100644
index 00000000..7da6f77b
--- /dev/null
+++ b/tests/modules/organization/tags.yaml
@@ -0,0 +1,76 @@
+# 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.
+
+values:
+ google_tags_tag_key.default["bar"]:
+ description: Managed by the Terraform organization module.
+ parent: organizations/1234567890
+ purpose: null
+ purpose_data: null
+ short_name: bar
+ google_tags_tag_key.default["foo"]:
+ description: Managed by the Terraform organization module.
+ parent: organizations/1234567890
+ purpose: null
+ purpose_data: null
+ short_name: foo
+ google_tags_tag_key.default["foobar"]:
+ description: Foobar tag.
+ parent: organizations/1234567890
+ purpose: null
+ purpose_data: null
+ short_name: foobar
+ google_tags_tag_key.default["net_environment"]:
+ description: Managed by the Terraform organization module.
+ parent: organizations/1234567890
+ purpose: GCE_FIREWALL
+ purpose_data:
+ network: foobar
+ short_name: net_environment
+ google_tags_tag_key_iam_binding.default["foobar:roles/resourcemanager.tagAdmin"]:
+ condition: []
+ members:
+ - user:user1@example.com
+ - user:user2@example.com
+ role: roles/resourcemanager.tagAdmin
+ google_tags_tag_value.default["foobar/one"]:
+ description: Managed by the Terraform organization module.
+ short_name: one
+ google_tags_tag_value.default["foobar/three"]:
+ description: Foobar 3.
+ short_name: three
+ google_tags_tag_value.default["foobar/two"]:
+ description: Foobar 2.
+ short_name: two
+ google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagAdmin"]:
+ condition: []
+ members:
+ - user:user4@example.com
+ role: roles/resourcemanager.tagAdmin
+ google_tags_tag_value_iam_binding.default["foobar/three:roles/resourcemanager.tagViewer"]:
+ condition: []
+ members:
+ - user:user3@example.com
+ role: roles/resourcemanager.tagViewer
+ google_tags_tag_value_iam_binding.default["foobar/two:roles/resourcemanager.tagViewer"]:
+ condition: []
+ members:
+ - user:user3@example.com
+ role: roles/resourcemanager.tagViewer
+
+counts:
+ google_tags_tag_key: 4
+ google_tags_tag_key_iam_binding: 1
+ google_tags_tag_value: 3
+ google_tags_tag_value_iam_binding: 3
diff --git a/tests/modules/organization/test_plan.py b/tests/modules/organization/test_plan.py
deleted file mode 100644
index 37860ab6..00000000
--- a/tests/modules/organization/test_plan.py
+++ /dev/null
@@ -1,62 +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.
-
-
-def test_audit_config(plan_runner):
- "Test audit config."
- iam_audit_config = '{allServices={DATA_READ=[], DATA_WRITE=["user:me@example.org"]}}'
- _, resources = plan_runner(iam_audit_config=iam_audit_config)
- assert len(resources) == 1
- log_types = set(
- r['log_type'] for r in resources[0]['values']['audit_log_config'])
- assert log_types == set(['DATA_READ', 'DATA_WRITE'])
-
-
-def test_iam(plan_runner):
- "Test IAM."
- group_iam = (
- '{'
- '"owners@example.org" = ["roles/owner", "roles/resourcemanager.folderAdmin"],'
- '"viewers@example.org" = ["roles/viewer"]'
- '}')
- iam = ('{'
- '"roles/owner" = ["user:one@example.org", "user:two@example.org"],'
- '"roles/browser" = ["domain:example.org"]'
- '}')
- _, resources = plan_runner(group_iam=group_iam, iam=iam)
- roles = sorted([(r['values']['role'], sorted(r['values']['members']))
- for r in resources
- if r['type'] == 'google_organization_iam_binding'])
- assert roles == [
- ('roles/browser', ['domain:example.org']),
- ('roles/owner', [
- 'group:owners@example.org', 'user:one@example.org',
- 'user:two@example.org'
- ]),
- ('roles/resourcemanager.folderAdmin', ['group:owners@example.org']),
- ('roles/viewer', ['group:viewers@example.org']),
- ]
-
-
-def test_iam_additive_members(plan_runner):
- "Test IAM additive members."
- iam = ('{"user:one@example.org" = ["roles/owner"],'
- '"user:two@example.org" = ["roles/owner", "roles/editor"]}')
- _, resources = plan_runner(iam_additive_members=iam)
- roles = set((r['values']['role'], r['values']['member'])
- for r in resources
- if r['type'] == 'google_organization_iam_member')
- assert roles == set([('roles/owner', 'user:one@example.org'),
- ('roles/owner', 'user:two@example.org'),
- ('roles/editor', 'user:two@example.org')])
diff --git a/tests/modules/organization/test_plan_firewall.py b/tests/modules/organization/test_plan_firewall.py
deleted file mode 100644
index 5925e98b..00000000
--- a/tests/modules/organization/test_plan_firewall.py
+++ /dev/null
@@ -1,130 +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.
-
-_FACTORY = '''
-{
- cidr_file = "data/firewall-cidrs.yaml"
- policy_name = "factory-1"
- rules_file = "data/firewall-rules.yaml"
-}
-'''
-_POLICIES = '''
-{
- policy1 = {
- allow-ingress = {
- description = ""
- direction = "INGRESS"
- action = "allow"
- priority = 100
- ranges = ["10.0.0.0/8"]
- ports = {
- tcp = ["22"]
- }
- target_service_accounts = null
- target_resources = null
- logging = false
- }
- deny-egress = {
- description = ""
- direction = "EGRESS"
- action = "deny"
- priority = 200
- ranges = ["192.168.0.0/24"]
- ports = {
- tcp = ["443"]
- }
- target_service_accounts = null
- target_resources = null
- logging = false
- }
- }
- policy2 = {
- allow-ingress = {
- description = ""
- direction = "INGRESS"
- action = "allow"
- priority = 100
- ranges = ["10.0.0.0/8"]
- ports = {
- tcp = ["22"]
- }
- target_service_accounts = null
- target_resources = null
- logging = false
- }
- }
- }
-'''
-
-
-def test_custom(plan_runner):
- 'Test custom firewall policies.'
- _, resources = plan_runner(firewall_policies=_POLICIES)
- assert len(resources) == 5
- policies = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy']
- rules = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy_rule']
- assert set(r['index'] for r in policies) == set([
- 'policy1', 'policy2'
- ])
- assert set(r['index'] for r in rules) == set([
- 'policy1-deny-egress', 'policy2-allow-ingress', 'policy1-allow-ingress'
- ])
-
-
-def test_factory(plan_runner):
- 'Test firewall policy factory.'
- _, resources = plan_runner(firewall_policy_factory=_FACTORY)
- assert len(resources) == 3
- policies = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy']
- rules = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy_rule']
- assert set(r['index'] for r in policies) == set([
- 'factory-1'
- ])
- assert set(r['index'] for r in rules) == set([
- 'factory-1-allow-admins', 'factory-1-allow-ssh-from-iap'
- ])
-
-
-def test_factory_name(plan_runner):
- 'Test firewall policy factory default name.'
- factory = _FACTORY.replace('"factory-1"', 'null')
- _, resources = plan_runner(firewall_policy_factory=factory)
- assert len(resources) == 3
- policies = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy']
- assert set(r['index'] for r in policies) == set([
- 'factory'
- ])
-
-
-def test_combined(plan_runner):
- 'Test combined rules.'
- _, resources = plan_runner(firewall_policies=_POLICIES,
- firewall_policy_factory=_FACTORY)
- assert len(resources) == 8
- policies = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy']
- rules = [r for r in resources
- if r['type'] == 'google_compute_firewall_policy_rule']
- assert set(r['index'] for r in policies) == set([
- 'factory-1', 'policy1', 'policy2'
- ])
- assert set(r['index'] for r in rules) == set([
- 'factory-1-allow-admins', 'factory-1-allow-ssh-from-iap',
- 'policy1-deny-egress', 'policy2-allow-ingress', 'policy1-allow-ingress'
- ])
diff --git a/tests/modules/organization/test_plan_logging.py b/tests/modules/organization/test_plan_logging.py
deleted file mode 100644
index 287a5a48..00000000
--- a/tests/modules/organization/test_plan_logging.py
+++ /dev/null
@@ -1,126 +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.
-
-from collections import Counter
-
-
-def test_sinks(plan_runner):
- "Test folder-level sinks."
- tfvars = 'test.logging-sinks.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- assert len(resources) == 8
-
- resource_types = Counter([r["type"] for r in resources])
- assert resource_types == {
- "google_logging_organization_sink": 4,
- "google_bigquery_dataset_iam_member": 1,
- "google_project_iam_member": 1,
- "google_pubsub_topic_iam_member": 1,
- "google_storage_bucket_iam_member": 1,
- }
-
- sinks = [
- r for r in resources if r["type"] == "google_logging_organization_sink"
- ]
- assert sorted([r["index"] for r in sinks]) == [
- "debug",
- "info",
- "notice",
- "warning",
- ]
- values = [(
- r["index"],
- r["values"]["filter"],
- r["values"]["destination"],
- r["values"]["include_children"],
- ) for r in sinks]
- assert sorted(values) == [
- (
- "debug",
- "severity=DEBUG",
- "logging.googleapis.com/projects/myproject/locations/global/buckets/mybucket",
- False,
- ),
- (
- "info",
- "severity=INFO",
- "bigquery.googleapis.com/projects/myproject/datasets/mydataset",
- True,
- ),
- (
- "notice",
- "severity=NOTICE",
- "pubsub.googleapis.com/projects/myproject/topics/mytopic",
- False,
- ),
- ("warning", "severity=WARNING", "storage.googleapis.com/mybucket", True),
- ]
-
- bindings = [r for r in resources if "member" in r["type"]]
- values = [(r["index"], r["type"], r["values"]["role"]) for r in bindings]
- assert sorted(values) == [
- ("debug", "google_project_iam_member", "roles/logging.bucketWriter"),
- ("info", "google_bigquery_dataset_iam_member",
- "roles/bigquery.dataEditor"),
- ("notice", "google_pubsub_topic_iam_member", "roles/pubsub.publisher"),
- ("warning", "google_storage_bucket_iam_member",
- "roles/storage.objectCreator"),
- ]
-
- exclusions = [(r["index"], r["values"]["exclusions"]) for r in sinks]
- assert sorted(exclusions) == [
- (
- "debug",
- [
- {
- "description": None,
- "disabled": False,
- "filter": "logName:compute",
- "name": "no-compute",
- },
- {
- "description": None,
- "disabled": False,
- "filter": "logName:container",
- "name": "no-container",
- },
- ],
- ),
- ("info", []),
- ("notice", []),
- ("warning", []),
- ]
-
-
-def test_exclusions(plan_runner):
- "Test folder-level logging exclusions."
- logging_exclusions = ("{"
- 'exclusion1 = "resource.type=gce_instance", '
- 'exclusion2 = "severity=NOTICE", '
- "}")
- _, resources = plan_runner(logging_exclusions=logging_exclusions)
- assert len(resources) == 2
- exclusions = [
- r for r in resources
- if r["type"] == "google_logging_organization_exclusion"
- ]
- assert sorted([r["index"] for r in exclusions]) == [
- "exclusion1",
- "exclusion2",
- ]
- values = [(r["index"], r["values"]["filter"]) for r in exclusions]
- assert sorted(values) == [
- ("exclusion1", "resource.type=gce_instance"),
- ("exclusion2", "severity=NOTICE"),
- ]
diff --git a/tests/modules/organization/test_plan_org_policies.py b/tests/modules/organization/test_plan_org_policies.py
index 05550832..1e041dbc 100644
--- a/tests/modules/organization/test_plan_org_policies.py
+++ b/tests/modules/organization/test_plan_org_policies.py
@@ -14,46 +14,32 @@
import pathlib
-from .validate_policies import validate_policy_boolean, validate_policy_list, validate_policy_custom_constraints
+import pytest
+
+_params = ['boolean', 'list']
-def test_policy_boolean(plan_runner):
- "Test boolean org policy."
- tfvars = 'test.orgpolicies-boolean.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- validate_policy_boolean(resources)
-
-
-def test_policy_list(plan_runner):
- "Test list org policy."
- tfvars = 'test.orgpolicies-list.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- validate_policy_list(resources)
-
-
-def test_policy_custom_constraints(plan_runner):
- "Test org policy custom constraints."
- tfvars = 'test.orgpolicy-custom-constraints.tfvars'
- _, resources = plan_runner(tf_var_file=tfvars)
- validate_policy_custom_constraints(resources)
-
-
-def test_factory_policy_boolean(plan_runner, tfvars_to_yaml, tmp_path):
+@pytest.mark.parametrize('policy_type', _params)
+def test_policy_factory(plan_summary, tfvars_to_yaml, tmp_path, policy_type):
dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('test.orgpolicies-boolean.tfvars', dest, 'org_policies')
- _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
- validate_policy_boolean(resources)
+ tfvars_to_yaml(f'org_policies_{policy_type}.tfvars', dest, 'org_policies')
+ tfvars_plan = plan_summary(
+ 'modules/organization',
+ tf_var_files=['common.tfvars', f'org_policies_{policy_type}.tfvars'])
+ yaml_plan = plan_summary('modules/organization',
+ tf_var_files=['common.tfvars'],
+ org_policies_data_path=f'{tmp_path}')
+ assert tfvars_plan.values == yaml_plan.values
-def test_factory_policy_list(plan_runner, tfvars_to_yaml, tmp_path):
- dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('test.orgpolicies-list.tfvars', dest, 'org_policies')
- _, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
- validate_policy_list(resources)
-
-
-def test_factory_policy_custom_constraints(plan_runner, tfvars_to_yaml, tmp_path):
+def test_custom_constraint_factory(plan_summary, tfvars_to_yaml, tmp_path):
dest = tmp_path / 'constraints.yaml'
- tfvars_to_yaml('test.orgpolicy-custom-constraints.tfvars', dest, 'org_policy_custom_constraints')
- _, resources = plan_runner(org_policy_custom_constraints_data_path=f'"{tmp_path}"')
- validate_policy_custom_constraints(resources)
+ tfvars_to_yaml(f'org_policies_custom_constraints.tfvars', dest,
+ 'org_policy_custom_constraints')
+ tfvars_plan = plan_summary(
+ 'modules/organization',
+ tf_var_files=['common.tfvars', f'org_policies_custom_constraints.tfvars'])
+ yaml_plan = plan_summary(
+ 'modules/organization', tf_var_files=['common.tfvars'],
+ org_policy_custom_constraints_data_path=f'{tmp_path}')
+ assert tfvars_plan.values == yaml_plan.values
diff --git a/tests/modules/organization/test_plan_tags.py b/tests/modules/organization/test_plan_tags.py
deleted file mode 100644
index bce2208a..00000000
--- a/tests/modules/organization/test_plan_tags.py
+++ /dev/null
@@ -1,52 +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.
-
-
-def test_resource_tags(plan_runner):
- 'Test resource tags.'
- _, resources = plan_runner(tf_var_file='test.resource_tags.tfvars')
- assert len(resources) == 10
- resource_values = {}
- for r in resources:
- resource_values.setdefault(r['type'], []).append(r['values'])
- assert len(resource_values['google_tags_tag_key']) == 3
- assert len(resource_values['google_tags_tag_value']) == 3
- result = [
- r['role'] for r in resource_values['google_tags_tag_value_iam_binding']
- ]
- expected = [
- 'roles/resourcemanager.tagAdmin',
- 'roles/resourcemanager.tagViewer',
- 'roles/resourcemanager.tagViewer'
- ]
- assert result == expected
-
-
-def test_network_tags(plan_runner):
- 'Test network tags.'
- _, resources = plan_runner(tf_var_file='test.network_tags.tfvars')
- assert len(resources) == 1
- resource_values = {}
- for r in resources:
- resource_values.setdefault(r['type'], []).append(r['values'])
- google_tags_tag_key = resource_values['google_tags_tag_key'][0]
- assert google_tags_tag_key['purpose'] == "GCE_FIREWALL"
- assert google_tags_tag_key['purpose_data']['network'] == "foobar"
-
-
-def test_bindings(plan_runner):
- 'Test tag bindings.'
- tag_bindings = '{foo = "tagValues/123456789012"}'
- _, resources = plan_runner(tag_bindings=tag_bindings)
- assert len(resources) == 1
diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml
new file mode 100644
index 00000000..c88e05c1
--- /dev/null
+++ b/tests/modules/organization/tftest.yaml
@@ -0,0 +1,35 @@
+# 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.
+
+module: modules/organization
+
+common_tfvars:
+ - common.tfvars
+
+tests:
+ audit_config:
+ iam:
+ iam_additive:
+ logging:
+ logging_exclusions:
+ org_policies_list:
+ org_policies_boolean:
+ org_policies_custom_constraints:
+ tags:
+ firewall_policies:
+ firewall_policies_factory:
+ firewall_policies_factory_combined:
+ tfvars:
+ - firewall_policies.tfvars
+ - firewall_policies_factory.tfvars
diff --git a/tests/modules/project/test_plan_org_policies.py b/tests/modules/project/test_plan_org_policies.py
index d4e4559d..8463761e 100644
--- a/tests/modules/project/test_plan_org_policies.py
+++ b/tests/modules/project/test_plan_org_policies.py
@@ -31,13 +31,14 @@ def test_policy_list(plan_runner):
def test_factory_policy_boolean(plan_runner, tfvars_to_yaml, tmp_path):
dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('test.orgpolicies-boolean.tfvars', dest, 'org_policies')
+ tfvars_to_yaml('fixture/test.orgpolicies-boolean.tfvars', dest,
+ 'org_policies')
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_boolean(resources)
def test_factory_policy_list(plan_runner, tfvars_to_yaml, tmp_path):
dest = tmp_path / 'policies.yaml'
- tfvars_to_yaml('test.orgpolicies-list.tfvars', dest, 'org_policies')
+ tfvars_to_yaml('fixture/test.orgpolicies-list.tfvars', dest, 'org_policies')
_, resources = plan_runner(org_policies_data_path=f'"{tmp_path}"')
validate_policy_list(resources)
diff --git a/tests/requirements.txt b/tests/requirements.txt
index 3eb583ab..a6f82d75 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -1,6 +1,6 @@
pytest>=6.2.5
PyYAML>=6.0
-tftest>=1.7.6
+tftest>=1.8.1
marko>=1.2.0
deepdiff>=5.7.0
python-hcl2>=3.0.5
diff --git a/tools/plan_summary.py b/tools/plan_summary.py
new file mode 100755
index 00000000..def79adb
--- /dev/null
+++ b/tools/plan_summary.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+
+# 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.
+
+import click
+import sys
+import yaml
+
+from pathlib import Path
+
+BASEDIR = Path(__file__).parents[1]
+sys.path.append(str(BASEDIR / 'tests'))
+
+import fixtures
+
+
+@click.command()
+@click.argument('module', type=click.Path(), nargs=1)
+@click.argument('tfvars', type=click.Path(exists=True), nargs=-1)
+def main(module, tfvars):
+ module = BASEDIR / module
+ summary = fixtures.plan_summary(module, Path(), tfvars)
+ print(yaml.dump({'values': summary.values}))
+ print(yaml.dump({'counts': summary.counts}))
+ outputs = {
+ k: v.get('value', '__missing__') for k, v in summary.outputs.items()
+ }
+ print(yaml.dump({'outputs': outputs}))
+
+
+if __name__ == '__main__':
+ main()