From 720213593e018cb799e039ce568124f9b3b9b011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Wed, 18 Jan 2023 14:56:12 +0100 Subject: [PATCH 01/34] Use google_gkehub_feature_membership Use google_gkehub_feature_membership to provision ASM on clusters. Ensure that the cluster membership is refreshed on cluster recreation. --- .../ansible/roles/install/tasks/install.yaml | 19 ------------------- modules/gke-hub/main.tf | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml b/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml index b81c4962..f59f03e3 100644 --- a/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml +++ b/blueprints/gke/multi-cluster-mesh-gke-fleet-api/ansible/roles/install/tasks/install.yaml @@ -23,25 +23,6 @@ set_fact: context: "gke_{{ project_id }}_{{ region }}_{{ cluster }}" -- name: Install ASM in cluster - shell: > - gcloud container fleet mesh update \ - --control-plane automatic \ - --memberships {{ cluster }} \ - --project {{ project_id }} - -- name: Wait until MCP is provisioned - shell: > - for i in $(seq 12); do - result=$(gcloud container fleet mesh describe --project {{ project_id }} --format json \ - | jq -r '.membershipStates | to_entries[] | select(.key | endswith("{{ cluster }}")) | .value.servicemesh.controlPlaneManagement.state') - if [ "$result" = "ACTIVE" ]; then - break - fi - echo "ASM control plane is not ready yet..." - sleep 60 - done - - name: Get endpoint IP shell: > gcloud container clusters describe "{{ cluster }}" \ diff --git a/modules/gke-hub/main.tf b/modules/gke-hub/main.tf index f433d322..aa89c1dc 100644 --- a/modules/gke-hub/main.tf +++ b/modules/gke-hub/main.tf @@ -38,7 +38,7 @@ resource "google_gke_hub_membership" "default" { provider = google-beta for_each = var.clusters project = var.project_id - membership_id = each.key + membership_id = reverse(split("/", each.value))[0] # forces re-enrollment of the cluster in the fleet in case when cluster is recreated endpoint { gke_cluster { resource_link = each.value @@ -70,6 +70,20 @@ resource "google_gke_hub_feature" "default" { } } +resource "google_gke_hub_feature_membership" "servicemesh" { + provider = google-beta + for_each = var.features.servicemesh ? var.clusters : {} + project = var.project_id + location = "global" + feature = google_gke_hub_feature.default["servicemesh"].name + membership = google_gke_hub_membership.default[each.key].membership_id + + mesh { + management = "MANAGEMENT_AUTOMATIC" + control_plane = "AUTOMATIC" + } +} + resource "google_gke_hub_feature_membership" "default" { provider = google-beta for_each = local.cluster_cm_config From 10e462d5941ddcc88066bcdb9df385713f304928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Wed, 18 Jan 2023 15:29:02 +0100 Subject: [PATCH 02/34] Fix tests for servicemesh --- modules/gke-hub/README.md | 2 +- tests/modules/gke_hub/fixture/variables.tf | 2 +- tests/modules/gke_hub/test_plan.py | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/gke-hub/README.md b/modules/gke-hub/README.md index 0f4c5ae8..de17b520 100644 --- a/modules/gke-hub/README.md +++ b/modules/gke-hub/README.md @@ -295,7 +295,7 @@ module "hub" { ] } -# tftest modules=8 resources=28 +# tftest modules=8 resources=38 ``` diff --git a/tests/modules/gke_hub/fixture/variables.tf b/tests/modules/gke_hub/fixture/variables.tf index 5c5c106f..1d76d4f9 100644 --- a/tests/modules/gke_hub/fixture/variables.tf +++ b/tests/modules/gke_hub/fixture/variables.tf @@ -31,7 +31,7 @@ variable "features" { configmanagement = true identityservice = false multiclusteringress = null - servicemesh = false + servicemesh = true multiclusterservicediscovery = false } } diff --git a/tests/modules/gke_hub/test_plan.py b/tests/modules/gke_hub/test_plan.py index 35521813..8a71d12b 100644 --- a/tests/modules/gke_hub/test_plan.py +++ b/tests/modules/gke_hub/test_plan.py @@ -23,11 +23,14 @@ def resources(plan_runner): def test_resource_count(resources): "Test number of resources created." - assert len(resources) == 5 + assert len(resources) == 8 assert sorted(r['address'] for r in resources) == [ 'module.hub.google_gke_hub_feature.default["configmanagement"]', + 'module.hub.google_gke_hub_feature.default["servicemesh"]', 'module.hub.google_gke_hub_feature_membership.default["cluster-1"]', 'module.hub.google_gke_hub_feature_membership.default["cluster-2"]', + 'module.hub.google_gke_hub_feature_membership.servicemesh["cluster-1"]', + 'module.hub.google_gke_hub_feature_membership.servicemesh["cluster-2"]', 'module.hub.google_gke_hub_membership.default["cluster-1"]', 'module.hub.google_gke_hub_membership.default["cluster-2"]' ] @@ -58,6 +61,7 @@ def test_configmanagement_setup(resources): 'sync_wait_secs': None }], + 'oci': [], 'prevent_drift': False, 'source_format': 'hierarchy' }], From b38ef22572afd18182ac78c38a37dea7f7b2090a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Wed, 18 Jan 2023 16:10:20 +0100 Subject: [PATCH 03/34] Even more test fixes --- blueprints/gke/multitenant-fleet/README.md | 2 +- modules/gke-hub/README.md | 2 +- .../gke/multi_cluster_mesh_gke_fleet_api/test_plan.py | 2 +- tests/modules/gke_hub/test_plan.py | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/blueprints/gke/multitenant-fleet/README.md b/blueprints/gke/multitenant-fleet/README.md index 52f26ced..ce14b5a0 100644 --- a/blueprints/gke/multitenant-fleet/README.md +++ b/blueprints/gke/multitenant-fleet/README.md @@ -224,7 +224,7 @@ module "gke" { } } -# tftest modules=8 resources=35 +# tftest modules=8 resources=37 ``` diff --git a/modules/gke-hub/README.md b/modules/gke-hub/README.md index de17b520..17d7b427 100644 --- a/modules/gke-hub/README.md +++ b/modules/gke-hub/README.md @@ -295,7 +295,7 @@ module "hub" { ] } -# tftest modules=8 resources=38 +# tftest modules=8 resources=30 ``` diff --git a/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py b/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py index 270a142d..2379849d 100644 --- a/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py +++ b/tests/blueprints/gke/multi_cluster_mesh_gke_fleet_api/test_plan.py @@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner() assert len(modules) == 12 - assert len(resources) == 53 + assert len(resources) == 55 diff --git a/tests/modules/gke_hub/test_plan.py b/tests/modules/gke_hub/test_plan.py index 8a71d12b..51258c83 100644 --- a/tests/modules/gke_hub/test_plan.py +++ b/tests/modules/gke_hub/test_plan.py @@ -61,7 +61,6 @@ def test_configmanagement_setup(resources): 'sync_wait_secs': None }], - 'oci': [], 'prevent_drift': False, 'source_format': 'hierarchy' }], From 0ea769e70ff4a16942e29c5e397a0d4f9e3a99b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiktor=20Niesiob=C4=99dzki?= Date: Thu, 19 Jan 2023 10:54:27 +0100 Subject: [PATCH 04/34] Revert to use each.key for membership It's not needed to force recreation of membership when workload identity is configured. --- modules/gke-hub/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gke-hub/main.tf b/modules/gke-hub/main.tf index aa89c1dc..ddd35a46 100644 --- a/modules/gke-hub/main.tf +++ b/modules/gke-hub/main.tf @@ -38,7 +38,7 @@ resource "google_gke_hub_membership" "default" { provider = google-beta for_each = var.clusters project = var.project_id - membership_id = reverse(split("/", each.value))[0] # forces re-enrollment of the cluster in the fleet in case when cluster is recreated + membership_id = each.key endpoint { gke_cluster { resource_link = each.value From de704110c9519fe52ead9e6a8c5873bf281a05e5 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Wed, 4 Jan 2023 09:52:06 +0100 Subject: [PATCH 05/34] Update api-gateway tests --- modules/api-gateway/README.md | 28 +++--- tests/modules/api_gateway/examples/basic.yaml | 42 +++++++++ .../api_gateway/examples/create-sa.yaml | 90 +++++++++++++++++++ .../api_gateway/examples/existing-sa.yaml | 71 +++++++++++++++ tests/modules/api_gateway/fixture/main.tf | 26 ------ .../modules/api_gateway/fixture/variables.tf | 55 ------------ tests/modules/api_gateway/test_plan.py | 19 ---- .../modules/organization/examples/basic.yaml | 2 +- 8 files changed, 218 insertions(+), 115 deletions(-) create mode 100644 tests/modules/api_gateway/examples/basic.yaml create mode 100644 tests/modules/api_gateway/examples/create-sa.yaml create mode 100644 tests/modules/api_gateway/examples/existing-sa.yaml delete mode 100644 tests/modules/api_gateway/fixture/main.tf delete mode 100644 tests/modules/api_gateway/fixture/variables.tf delete mode 100644 tests/modules/api_gateway/test_plan.py diff --git a/modules/api-gateway/README.md b/modules/api-gateway/README.md index 0b5fc928..d3c16d38 100644 --- a/modules/api-gateway/README.md +++ b/modules/api-gateway/README.md @@ -1,4 +1,4 @@ -# Api Gateway +# API Gateway This module allows creating an API with its associated API config and API gateway. It also allows you grant IAM roles on the created resources. # Examples @@ -15,46 +15,46 @@ module "gateway" { # ... EOT } -# tftest modules=1 resources=4 +# tftest modules=1 resources=4 inventory=basic.yaml ``` -## Basic example + customer service account +## Use existing service account ```hcl module "gateway" { source = "./fabric/modules/api-gateway" project_id = "my-project" api_id = "api" region = "europe-west1" - spec = < diff --git a/tests/modules/api_gateway/examples/basic.yaml b/tests/modules/api_gateway/examples/basic.yaml new file mode 100644 index 00000000..a17fc3ca --- /dev/null +++ b/tests/modules/api_gateway/examples/basic.yaml @@ -0,0 +1,42 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.gateway.google_api_gateway_api.api: + api_id: api + display_name: api + project: my-project + module.gateway.google_api_gateway_api_config.api_config: + api: api + gateway_config: [] + grpc_services: [] + labels: null + managed_service_configs: [] + project: my-project + module.gateway.google_api_gateway_gateway.gateway: + display_name: gw-api + gateway_id: gw-api + labels: null + project: my-project + region: europe-west1 + module.gateway.google_project_service.service: + disable_dependent_services: true + disable_on_destroy: true + project: my-project + +counts: + google_api_gateway_api: 1 + google_api_gateway_api_config: 1 + google_api_gateway_gateway: 1 + google_project_service: 1 diff --git a/tests/modules/api_gateway/examples/create-sa.yaml b/tests/modules/api_gateway/examples/create-sa.yaml new file mode 100644 index 00000000..2c8d7c76 --- /dev/null +++ b/tests/modules/api_gateway/examples/create-sa.yaml @@ -0,0 +1,90 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.gateway.google_api_gateway_api.api: + api_id: api + display_name: api + labels: null + project: my-project + module.gateway.google_api_gateway_api_config.api_config: + api: api + grpc_services: [] + labels: null + managed_service_configs: [] + project: my-project + module.gateway.google_api_gateway_api_config_iam_binding.api_config_iam_bindings["roles/apigateway.admin"]: + api: api + condition: [] + members: + - user:mirene@google.com + project: my-project + role: roles/apigateway.admin + module.gateway.google_api_gateway_api_config_iam_binding.api_config_iam_bindings["roles/apigateway.viewer"]: + api: api + condition: [] + members: + - user:mirene@google.com + project: my-project + role: roles/apigateway.viewer + module.gateway.google_api_gateway_api_iam_binding.api_iam_bindings["roles/apigateway.admin"]: + api: api + condition: [] + members: + - user:mirene@google.com + project: my-project + role: roles/apigateway.admin + module.gateway.google_api_gateway_api_iam_binding.api_iam_bindings["roles/apigateway.viewer"]: + api: api + condition: [] + members: + - user:mirene@google.com + project: my-project + role: roles/apigateway.viewer + module.gateway.google_api_gateway_gateway.gateway: + display_name: gw-api + gateway_id: gw-api + labels: null + project: my-project + region: europe-west1 + module.gateway.google_api_gateway_gateway_iam_binding.gateway_iam_bindings["roles/apigateway.admin"]: + condition: [] + gateway: gw-api + members: + - user:mirene@google.com + project: my-project + region: europe-west1 + role: roles/apigateway.admin + module.gateway.google_api_gateway_gateway_iam_binding.gateway_iam_bindings["roles/apigateway.viewer"]: + condition: [] + gateway: gw-api + members: + - user:mirene@google.com + project: my-project + region: europe-west1 + role: roles/apigateway.viewer + module.gateway.google_project_service.service: {} + module.gateway.google_service_account.service_account[0]: + account_id: sa-api-cfg-api + project: my-project + +counts: + google_api_gateway_api: 1 + google_api_gateway_api_config: 1 + google_api_gateway_api_config_iam_binding: 2 + google_api_gateway_api_iam_binding: 2 + google_api_gateway_gateway: 1 + google_api_gateway_gateway_iam_binding: 2 + google_project_service: 1 + google_service_account: 1 diff --git a/tests/modules/api_gateway/examples/existing-sa.yaml b/tests/modules/api_gateway/examples/existing-sa.yaml new file mode 100644 index 00000000..f0befa79 --- /dev/null +++ b/tests/modules/api_gateway/examples/existing-sa.yaml @@ -0,0 +1,71 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.gateway.google_api_gateway_api.api: + api_id: api + display_name: api + labels: null + project: my-project + module.gateway.google_api_gateway_api_config.api_config: + api: api + gateway_config: + - backend_config: + - google_service_account: sa@my-project.iam.gserviceaccount.com + grpc_services: [] + labels: null + managed_service_configs: [] + project: my-project + module.gateway.google_api_gateway_api_config_iam_binding.api_config_iam_bindings["roles/apigateway.admin"]: + api: api + api_config: api-cfg-api-8656c6040d6d9ba18a8b9b5f3955c223 + condition: [] + members: + - user:user@example.com + project: my-project + role: roles/apigateway.admin + module.gateway.google_api_gateway_api_iam_binding.api_iam_bindings["roles/apigateway.admin"]: + api: api + condition: [] + members: + - user:user@example.com + project: my-project + role: roles/apigateway.admin + module.gateway.google_api_gateway_gateway.gateway: + display_name: gw-api + gateway_id: gw-api + labels: null + project: my-project + region: europe-west1 + module.gateway.google_api_gateway_gateway_iam_binding.gateway_iam_bindings["roles/apigateway.admin"]: + condition: [] + gateway: gw-api + members: + - user:user@example.com + project: my-project + region: europe-west1 + role: roles/apigateway.admin + module.gateway.google_project_service.service: + disable_dependent_services: true + disable_on_destroy: true + project: my-project + +counts: + google_api_gateway_api: 1 + google_api_gateway_api_config: 1 + google_api_gateway_api_config_iam_binding: 1 + google_api_gateway_api_iam_binding: 1 + google_api_gateway_gateway: 1 + google_api_gateway_gateway_iam_binding: 1 + google_project_service: 1 diff --git a/tests/modules/api_gateway/fixture/main.tf b/tests/modules/api_gateway/fixture/main.tf deleted file mode 100644 index d4cd134f..00000000 --- a/tests/modules/api_gateway/fixture/main.tf +++ /dev/null @@ -1,26 +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 "gateway" { - source = "../../../../modules/api-gateway" - api_id = var.api_id - project_id = var.project_id - labels = var.labels - iam = var.iam - region = var.region - spec = var.spec - service_account_create = true -} diff --git a/tests/modules/api_gateway/fixture/variables.tf b/tests/modules/api_gateway/fixture/variables.tf deleted file mode 100644 index 977af921..00000000 --- a/tests/modules/api_gateway/fixture/variables.tf +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -variable "api_id" { - type = string - default = "my-api" -} - -variable "iam" { - type = map(list(string)) - default = null -} - -variable "labels" { - type = map(string) - default = null -} - -variable "project_id" { - type = string - default = "my-project" -} - -variable "region" { - type = string - default = "europe-west1" -} - -variable "service_account_create" { - type = bool - default = true -} - -variable "service_account_email" { - type = string - default = null -} - -variable "spec" { - type = string - default = "Spec contents" -} diff --git a/tests/modules/api_gateway/test_plan.py b/tests/modules/api_gateway/test_plan.py deleted file mode 100644 index 18ecdd32..00000000 --- a/tests/modules/api_gateway/test_plan.py +++ /dev/null @@ -1,19 +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_count(plan_runner): - "Test number of resources created." - _, resources = plan_runner() - assert len(resources) == 5 diff --git a/tests/modules/organization/examples/basic.yaml b/tests/modules/organization/examples/basic.yaml index 2ba70f40..f7b63a1d 100644 --- a/tests/modules/organization/examples/basic.yaml +++ b/tests/modules/organization/examples/basic.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 4897aa7109becfd592b5fd9efc6f392379489f69 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Wed, 18 Jan 2023 12:18:41 +0100 Subject: [PATCH 06/34] bump test suite versions --- tests/requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index a6f82d75..1e0921c1 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,6 +1,6 @@ -pytest>=6.2.5 +pytest>=7.2.1 PyYAML>=6.0 tftest>=1.8.1 -marko>=1.2.0 -deepdiff>=5.7.0 -python-hcl2>=3.0.5 +marko>=1.2.2 +deepdiff>=6.2.3 +python-hcl2>=4.3.0 From 410b7f5ba3c30ac551d90ca9b0ab036f247fde3c Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 00:00:31 +0100 Subject: [PATCH 07/34] Fix typo in net-vpc DNS policies --- modules/net-vpc/main.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/net-vpc/main.tf b/modules/net-vpc/main.tf index 7eedc95a..d1505801 100644 --- a/modules/net-vpc/main.tf +++ b/modules/net-vpc/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,7 +109,7 @@ resource "google_dns_policy" "default" { ) iterator = ns content { - ipv4_address = ns.key + ipv4_address = ns.value forwarding_path = "private" } } @@ -121,7 +121,7 @@ resource "google_dns_policy" "default" { ) iterator = ns content { - ipv4_address = ns.key + ipv4_address = ns.value } } } From fd19e4a923205deff9775494c7c8b96d799b3878 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 00:00:58 +0100 Subject: [PATCH 08/34] add inventories net-vpc examples --- modules/net-vpc/README.md | 14 ++--- .../net_vpc/examples/dns-policies.yaml | 42 +++++++++++++++ tests/modules/net_vpc/examples/peering.yaml | 34 +++++++++++++ .../net_vpc/examples/proxy-only-subnets.yaml | 40 +++++++++++++++ .../modules/net_vpc/examples/psc-routes.yaml | 47 +++++++++++++++++ tests/modules/net_vpc/examples/psc.yaml | 46 +++++++++++++++++ .../modules/net_vpc/examples/shared-vpc.yaml | 51 +++++++++++++++++++ tests/modules/net_vpc/examples/simple.yaml | 50 ++++++++++++++++++ 8 files changed, 317 insertions(+), 7 deletions(-) create mode 100644 tests/modules/net_vpc/examples/dns-policies.yaml create mode 100644 tests/modules/net_vpc/examples/peering.yaml create mode 100644 tests/modules/net_vpc/examples/proxy-only-subnets.yaml create mode 100644 tests/modules/net_vpc/examples/psc-routes.yaml create mode 100644 tests/modules/net_vpc/examples/psc.yaml create mode 100644 tests/modules/net_vpc/examples/shared-vpc.yaml create mode 100644 tests/modules/net_vpc/examples/simple.yaml diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 53361009..71884671 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -30,7 +30,7 @@ module "vpc" { } ] } -# tftest modules=1 resources=3 +# tftest modules=1 resources=3 inventory=simple.yaml ``` ### Peering @@ -65,7 +65,7 @@ module "vpc-spoke-1" { import_routes = true } } -# tftest modules=2 resources=6 +# tftest modules=2 resources=6 inventory=peering.yaml ``` ### Shared VPC @@ -116,7 +116,7 @@ module "vpc-host" { } } } -# tftest modules=1 resources=7 +# tftest modules=1 resources=7 inventory=shared-vpc.yaml ``` ### Private Service Networking @@ -137,7 +137,7 @@ module "vpc" { ranges = { myrange = "10.0.1.0/24" } } } -# tftest modules=1 resources=5 +# tftest modules=1 resources=5 inventory=psc.yaml ``` ### Private Service Networking with peering routes @@ -162,7 +162,7 @@ module "vpc" { import_routes = true } } -# tftest modules=1 resources=5 +# tftest modules=1 resources=5 inventory=psc-routes.yaml ``` ### Subnets for Private Service Connect, Proxy-only subnets @@ -194,7 +194,7 @@ module "vpc" { } ] } -# tftest modules=1 resources=3 +# tftest modules=1 resources=3 inventory=proxy-only-subnets.yaml ``` ### DNS Policies @@ -219,7 +219,7 @@ module "vpc" { } ] } -# tftest modules=1 resources=3 +# tftest modules=1 resources=3 inventory=dns-policies.yaml ``` ### Subnet Factory diff --git a/tests/modules/net_vpc/examples/dns-policies.yaml b/tests/modules/net_vpc/examples/dns-policies.yaml new file mode 100644 index 00000000..a30d6408 --- /dev/null +++ b/tests/modules/net_vpc/examples/dns-policies.yaml @@ -0,0 +1,42 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_network.network[0]: + name: my-network + project: my-project + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]: {} + module.vpc.google_dns_policy.default[0]: + alternative_name_server_config: + - target_name_servers: + - forwarding_path: '' + ipv4_address: '8.8.8.8' + - forwarding_path: private + ipv4_address: '10.0.0.1' + description: Managed by Terraform + enable_inbound_forwarding: true + enable_logging: null + name: my-network + networks: + - {} + project: my-project + +counts: + google_compute_network: 1 + google_compute_subnetwork: 1 + google_dns_policy: 1 + modules: 1 + resources: 3 + +outputs: {} diff --git a/tests/modules/net_vpc/examples/peering.yaml b/tests/modules/net_vpc/examples/peering.yaml new file mode 100644 index 00000000..937ce144 --- /dev/null +++ b/tests/modules/net_vpc/examples/peering.yaml @@ -0,0 +1,34 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc-hub.google_compute_network.network[0]: {} + module.vpc-spoke-1.google_compute_network.network[0]: {} + module.vpc-hub.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]: {} + module.vpc-spoke-1.google_compute_subnetwork.subnetwork["europe-west1/subnet-2"]: {} + module.vpc-spoke-1.google_compute_network_peering.local[0]: + export_custom_routes: false + export_subnet_routes_with_public_ip: true + import_custom_routes: true + import_subnet_routes_with_public_ip: null + module.vpc-spoke-1.google_compute_network_peering.remote[0]: + export_custom_routes: true + export_subnet_routes_with_public_ip: true + import_custom_routes: false + import_subnet_routes_with_public_ip: null + +counts: + google_compute_network: 2 + google_compute_network_peering: 2 + google_compute_subnetwork: 2 diff --git a/tests/modules/net_vpc/examples/proxy-only-subnets.yaml b/tests/modules/net_vpc/examples/proxy-only-subnets.yaml new file mode 100644 index 00000000..6e2069aa --- /dev/null +++ b/tests/modules/net_vpc/examples/proxy-only-subnets.yaml @@ -0,0 +1,40 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_network.network[0]: + name: my-network + project: my-project + module.vpc.google_compute_subnetwork.proxy_only["europe-west1/regional-proxy"]: + description: Terraform-managed proxy-only subnet for Regional HTTPS or Internal HTTPS LB. + ip_cidr_range: 10.0.1.0/24 + log_config: [] + name: regional-proxy + project: my-project + purpose: REGIONAL_MANAGED_PROXY + region: europe-west1 + role: ACTIVE + module.vpc.google_compute_subnetwork.psc["europe-west1/psc"]: + description: Terraform-managed subnet for Private Service Connect (PSC NAT). + ip_cidr_range: 10.0.3.0/24 + log_config: [] + name: psc + project: my-project + purpose: PRIVATE_SERVICE_CONNECT + region: europe-west1 + role: null + +counts: + google_compute_network: 1 + google_compute_subnetwork: 2 diff --git a/tests/modules/net_vpc/examples/psc-routes.yaml b/tests/modules/net_vpc/examples/psc-routes.yaml new file mode 100644 index 00000000..6f459f4b --- /dev/null +++ b/tests/modules/net_vpc/examples/psc-routes.yaml @@ -0,0 +1,47 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_global_address.psa_ranges["myrange"]: + address: 10.0.1.0 + address_type: INTERNAL + description: null + ip_version: null + name: myrange + prefix_length: 24 + project: my-project + purpose: VPC_PEERING + module.vpc.google_compute_network.network[0]: + name: my-network + project: my-project + routing_mode: GLOBAL + module.vpc.google_compute_network_peering_routes_config.psa_routes["1"]: + export_custom_routes: true + import_custom_routes: true + project: my-project + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]: + ip_cidr_range: 10.0.0.0/24 + name: production + project: my-project + module.vpc.google_service_networking_connection.psa_connection["1"]: + reserved_peering_ranges: + - myrange + service: servicenetworking.googleapis.com + +counts: + google_compute_global_address: 1 + google_compute_network: 1 + google_compute_network_peering_routes_config: 1 + google_compute_subnetwork: 1 + google_service_networking_connection: 1 diff --git a/tests/modules/net_vpc/examples/psc.yaml b/tests/modules/net_vpc/examples/psc.yaml new file mode 100644 index 00000000..c08fcb45 --- /dev/null +++ b/tests/modules/net_vpc/examples/psc.yaml @@ -0,0 +1,46 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_global_address.psa_ranges["myrange"]: + address: 10.0.1.0 + address_type: INTERNAL + name: myrange + prefix_length: 24 + project: my-project + purpose: VPC_PEERING + module.vpc.google_compute_network.network[0]: + name: my-network + project: my-project + module.vpc.google_compute_network_peering_routes_config.psa_routes["1"]: + export_custom_routes: false + import_custom_routes: false + project: my-project + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]: + ip_cidr_range: 10.0.0.0/24 + name: production + project: my-project + module.vpc.google_service_networking_connection.psa_connection["1"]: + reserved_peering_ranges: + - myrange + service: servicenetworking.googleapis.com + +counts: + google_compute_global_address: 1 + google_compute_network: 1 + google_compute_network_peering_routes_config: 1 + google_compute_subnetwork: 1 + google_service_networking_connection: 1 + +outputs: {} diff --git a/tests/modules/net_vpc/examples/shared-vpc.yaml b/tests/modules/net_vpc/examples/shared-vpc.yaml new file mode 100644 index 00000000..b004e315 --- /dev/null +++ b/tests/modules/net_vpc/examples/shared-vpc.yaml @@ -0,0 +1,51 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc-host.google_compute_network.network[0]: + name: my-host-network + project: my-project + module.vpc-host.google_compute_shared_vpc_host_project.shared_vpc_host[0]: + project: my-project + module.vpc-host.google_compute_shared_vpc_service_project.service_projects["project1"]: + host_project: my-project + service_project: project1 + module.vpc-host.google_compute_shared_vpc_service_project.service_projects["project2"]: + host_project: my-project + service_project: project2 + module.vpc-host.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]: {} + module.vpc-host.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-1.roles/compute.networkUser"]: + condition: [] + members: + - serviceAccount:cloudsvc + - serviceAccount:gke + project: my-project + region: europe-west1 + role: roles/compute.networkUser + subnetwork: subnet-1 + module.vpc-host.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-1.roles/compute.securityAdmin"]: + condition: [] + members: + - serviceAccount:gke + project: my-project + region: europe-west1 + role: roles/compute.securityAdmin + subnetwork: subnet-1 + +counts: + google_compute_network: 1 + google_compute_shared_vpc_host_project: 1 + google_compute_shared_vpc_service_project: 2 + google_compute_subnetwork: 1 + google_compute_subnetwork_iam_binding: 2 diff --git a/tests/modules/net_vpc/examples/simple.yaml b/tests/modules/net_vpc/examples/simple.yaml new file mode 100644 index 00000000..799852c0 --- /dev/null +++ b/tests/modules/net_vpc/examples/simple.yaml @@ -0,0 +1,50 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + name: my-network + project: my-project + routing_mode: GLOBAL + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/production"]: + description: Terraform-managed. + ip_cidr_range: 10.0.0.0/24 + log_config: [] + name: production + private_ip_google_access: true + project: my-project + region: europe-west1 + role: null + secondary_ip_range: + - ip_cidr_range: 172.16.0.0/20 + range_name: pods + - ip_cidr_range: 192.168.0.0/24 + range_name: services + module.vpc.google_compute_subnetwork.subnetwork["europe-west2/production"]: + description: Terraform-managed. + ip_cidr_range: 10.0.16.0/24 + log_config: [] + name: production + private_ip_google_access: true + project: my-project + region: europe-west2 + role: null + secondary_ip_range: [] + +counts: + google_compute_network: 1 + google_compute_subnetwork: 2 From 12f07ebeac446e618e5696664556daf0eb4b2392 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 00:29:16 +0100 Subject: [PATCH 09/34] Extend net-vpc README with more tested examples --- modules/net-vpc/README.md | 81 ++++++++++++ tests/examples/test_plan.py | 10 +- .../modules/net_vpc/examples/subnet-iam.yaml | 54 ++++++++ .../net_vpc/examples/subnet-options.yaml | 70 ++++++++++ tests/modules/net_vpc/fixture/main.tf | 30 ----- .../net_vpc/fixture/test.subnets.tfvars | 44 ------- tests/modules/net_vpc/fixture/variables.tf | 101 --------------- tests/modules/net_vpc/peering.tfvars | 5 - tests/modules/net_vpc/peering.yaml | 47 ------- tests/modules/net_vpc/psa_simple.tfvars | 7 - tests/modules/net_vpc/psa_simple.yaml | 70 ---------- tests/modules/net_vpc/simple.tfvars | 1 - tests/modules/net_vpc/simple.yaml | 36 ------ tests/modules/net_vpc/subnets.tfvars | 44 ------- tests/modules/net_vpc/subnets.yaml | 120 ------------------ tests/modules/net_vpc/tftest.yaml | 6 +- 16 files changed, 211 insertions(+), 515 deletions(-) create mode 100644 tests/modules/net_vpc/examples/subnet-iam.yaml create mode 100644 tests/modules/net_vpc/examples/subnet-options.yaml delete mode 100644 tests/modules/net_vpc/fixture/main.tf delete mode 100644 tests/modules/net_vpc/fixture/test.subnets.tfvars delete mode 100644 tests/modules/net_vpc/fixture/variables.tf delete mode 100644 tests/modules/net_vpc/peering.tfvars delete mode 100644 tests/modules/net_vpc/peering.yaml delete mode 100644 tests/modules/net_vpc/psa_simple.tfvars delete mode 100644 tests/modules/net_vpc/psa_simple.yaml delete mode 100644 tests/modules/net_vpc/simple.tfvars delete mode 100644 tests/modules/net_vpc/simple.yaml delete mode 100644 tests/modules/net_vpc/subnets.tfvars delete mode 100644 tests/modules/net_vpc/subnets.yaml diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 71884671..5a901378 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -33,6 +33,87 @@ module "vpc" { # tftest modules=1 resources=3 inventory=simple.yaml ``` +### Subnet Options +```hcl +module "vpc" { + source = "./fabric/modules/net-vpc" + project_id = "my-project" + name = "my-network" + subnets = [ + # simple subnet + { + name = "simple" + region = "europe-west1" + ip_cidr_range = "10.0.0.0/24" + }, + # custom description and PGA disabled + { + name = "no-pga" + region = "europe-west1" + ip_cidr_range = "10.0.1.0/24", + description = "Subnet b" + enable_private_access = false + }, + # secondary ranges + { + name = "with-secondary-ranges" + 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" + } + }, + # enable flow logs + { + name = "with-flow-logs" + region = "europe-west1" + ip_cidr_range = "10.0.3.0/24" + flow_logs_config = { + flow_sampling = 0.5 + aggregation_interval = "INTERVAL_10_MIN" + } + } + ] +} +# tftest modules=1 resources=5 inventory=subnet-options.yaml +``` + +### Subnet IAM + +```hcl +module "vpc" { + source = "./fabric/modules/net-vpc" + project_id = "my-project" + name = "my-network" + subnets = [ + { + name = "subnet-1" + region = "europe-west1" + ip_cidr_range = "10.0.1.0/24" + }, + { + name = "subnet-2" + region = "europe-west1" + ip_cidr_range = "10.0.1.0/24" + } + ] + subnet_iam = { + "europe-west1/subnet-1" = { + "roles/compute.networkUser" = [ + "user:user1@example.com", "group:group1@example.com" + ] + } + "europe-west1/subnet-2" = { + "roles/compute.networkUser" = [ + "user:user2@example.com", "group:group2@example.com" + ] + } + } +} +# tftest modules=1 resources=5 inventory=subnet-iam.yaml +``` + ### Peering A single peering can be configured for the VPC, so as to allow management of simple scenarios, and more complex configurations like hub and spoke by defining the peering configuration on the spoke VPCs. Care must be taken so as a single peering is created/changed/destroyed at a time, due to the specific behaviour of the peering API calls. diff --git a/tests/examples/test_plan.py b/tests/examples/test_plan.py index 5f902cbe..2dd42a7a 100644 --- a/tests/examples/test_plan.py +++ b/tests/examples/test_plan.py @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -49,10 +49,10 @@ def test_example(plan_validator, tmp_path, example): summary = plan_validator(module_path=tmp_path, inventory_paths=inventory, tf_var_files=[]) - import yaml - print(yaml.dump({"values": summary.values})) - print(yaml.dump({"counts": summary.counts})) - print(yaml.dump({"outputs": summary.outputs})) + # import yaml + # print(yaml.dump({"values": summary.values})) + # print(yaml.dump({"counts": summary.counts})) + # print(yaml.dump({"outputs": summary.outputs})) counts = summary.counts num_modules, num_resources = counts['modules'], counts['resources'] diff --git a/tests/modules/net_vpc/examples/subnet-iam.yaml b/tests/modules/net_vpc/examples/subnet-iam.yaml new file mode 100644 index 00000000..cb53ecd8 --- /dev/null +++ b/tests/modules/net_vpc/examples/subnet-iam.yaml @@ -0,0 +1,54 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_network.network[0]: + name: my-network + project: my-project + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]: + name: subnet-1 + project: my-project + region: europe-west1 + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/subnet-2"]: + name: subnet-2 + private_ip_google_access: true + project: my-project + region: europe-west1 + module.vpc.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-1.roles/compute.networkUser"]: + condition: [] + members: + - group:group1@example.com + - user:user1@example.com + project: my-project + region: europe-west1 + role: roles/compute.networkUser + subnetwork: subnet-1 + module.vpc.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-2.roles/compute.networkUser"]: + condition: [] + members: + - group:group2@example.com + - user:user2@example.com + project: my-project + region: europe-west1 + role: roles/compute.networkUser + subnetwork: subnet-2 + +counts: + google_compute_network: 1 + google_compute_subnetwork: 2 + google_compute_subnetwork_iam_binding: 2 + modules: 1 + resources: 5 + +outputs: {} diff --git a/tests/modules/net_vpc/examples/subnet-options.yaml b/tests/modules/net_vpc/examples/subnet-options.yaml new file mode 100644 index 00000000..e3cea5ca --- /dev/null +++ b/tests/modules/net_vpc/examples/subnet-options.yaml @@ -0,0 +1,70 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_network.network[0]: + name: my-network + project: my-project + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/no-pga"]: + description: Subnet b + ip_cidr_range: 10.0.1.0/24 + log_config: [] + name: no-pga + private_ip_google_access: false + project: my-project + region: europe-west1 + secondary_ip_range: [] + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/simple"]: + description: Terraform-managed. + ip_cidr_range: 10.0.0.0/24 + log_config: [] + name: simple + private_ip_google_access: true + project: my-project + region: europe-west1 + secondary_ip_range: [] + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/with-flow-logs"]: + description: Terraform-managed. + ip_cidr_range: 10.0.3.0/24 + ipv6_access_type: null + log_config: + - aggregation_interval: INTERVAL_10_MIN + filter_expr: 'true' + flow_sampling: 0.5 + metadata: INCLUDE_ALL_METADATA + metadata_fields: null + name: with-flow-logs + private_ip_google_access: true + project: my-project + region: europe-west1 + role: null + secondary_ip_range: [] + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/with-secondary-ranges"]: + description: Terraform-managed. + ip_cidr_range: 10.0.2.0/24 + log_config: [] + name: with-secondary-ranges + private_ip_google_access: true + project: my-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 + +counts: + google_compute_network: 1 + google_compute_subnetwork: 4 diff --git a/tests/modules/net_vpc/fixture/main.tf b/tests/modules/net_vpc/fixture/main.tf deleted file mode 100644 index f0e4696e..00000000 --- a/tests/modules/net_vpc/fixture/main.tf +++ /dev/null @@ -1,30 +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 "test" { - source = "../../../../modules/net-vpc" - project_id = "test-project" - name = "test" - peering_config = var.peering_config - routes = var.routes - shared_vpc_host = var.shared_vpc_host - shared_vpc_service_projects = var.shared_vpc_service_projects - subnet_iam = var.subnet_iam - subnets = var.subnets - auto_create_subnetworks = var.auto_create_subnetworks - psa_config = var.psa_config - data_folder = var.data_folder -} diff --git a/tests/modules/net_vpc/fixture/test.subnets.tfvars b/tests/modules/net_vpc/fixture/test.subnets.tfvars deleted file mode 100644 index 499e498f..00000000 --- a/tests/modules/net_vpc/fixture/test.subnets.tfvars +++ /dev/null @@ -1,44 +0,0 @@ -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/fixture/variables.tf b/tests/modules/net_vpc/fixture/variables.tf deleted file mode 100644 index 868966c8..00000000 --- a/tests/modules/net_vpc/fixture/variables.tf +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -variable "auto_create_subnetworks" { - type = bool - default = false -} - -variable "data_folder" { - type = string - default = null -} - -variable "delete_default_routes_on_create" { - type = bool - default = false -} - -variable "description" { - type = string - default = "Terraform-managed." -} - -variable "dns_policy" { - type = any - default = null -} - -variable "mtu" { - type = number - default = null -} - -variable "peering_config" { - type = any - default = null -} - -variable "psa_config" { - type = any - default = null -} - -variable "routes" { - type = any - default = {} - nullable = false -} - -variable "routing_mode" { - type = string - default = "GLOBAL" -} - -variable "shared_vpc_host" { - type = bool - default = false -} - -variable "shared_vpc_service_projects" { - type = list(string) - default = [] -} - -variable "subnets" { - type = any - default = [] -} - -variable "subnet_iam" { - type = map(map(list(string))) - default = {} -} - -variable "subnets_proxy_only" { - type = any - default = [] -} - -variable "subnets_psc" { - type = any - default = [] -} - -variable "vpc_create" { - type = bool - default = true -} diff --git a/tests/modules/net_vpc/peering.tfvars b/tests/modules/net_vpc/peering.tfvars deleted file mode 100644 index eccd7ae7..00000000 --- a/tests/modules/net_vpc/peering.tfvars +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 8d0bbed7..00000000 --- a/tests/modules/net_vpc/peering.yaml +++ /dev/null @@ -1,47 +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. - -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_simple.tfvars b/tests/modules/net_vpc/psa_simple.tfvars deleted file mode 100644 index 51289fe0..00000000 --- a/tests/modules/net_vpc/psa_simple.tfvars +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 019b443f..00000000 --- a/tests/modules/net_vpc/psa_simple.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -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/simple.tfvars b/tests/modules/net_vpc/simple.tfvars deleted file mode 100644 index 6f848aa9..00000000 --- a/tests/modules/net_vpc/simple.tfvars +++ /dev/null @@ -1 +0,0 @@ -# skip boilerplate check diff --git a/tests/modules/net_vpc/simple.yaml b/tests/modules/net_vpc/simple.yaml deleted file mode 100644 index 004be7ec..00000000 --- a/tests/modules/net_vpc/simple.yaml +++ /dev/null @@ -1,36 +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. - -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 deleted file mode 100644 index 499e498f..00000000 --- a/tests/modules/net_vpc/subnets.tfvars +++ /dev/null @@ -1,44 +0,0 @@ -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 deleted file mode 100644 index 9ccf31e6..00000000 --- a/tests/modules/net_vpc/subnets.yaml +++ /dev/null @@ -1,120 +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. - -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/tftest.yaml b/tests/modules/net_vpc/tftest.yaml index b2b09798..c0dfa0ad 100644 --- a/tests/modules/net_vpc/tftest.yaml +++ b/tests/modules/net_vpc/tftest.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,12 +17,8 @@ common_tfvars: - common.tfvars tests: - simple: - subnets: - peering: shared_vpc: factory: - psa_simple: psa_routes_export: psa_routes_import: psa_routes_import_export: From a0cb67e1f4498d42198fa5a64c43c7d2c9fc5e5e Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 00:44:30 +0100 Subject: [PATCH 10/34] Add inventories to gcs examples --- modules/gcs/README.md | 37 ++++++--------- tests/modules/gcs/common.tfvars | 13 ------ .../gcs/{tftest.yaml => examples/cmek.yaml} | 17 ++++--- .../{prefix.yaml => examples/lifecycle.yaml} | 46 ++++++++----------- tests/modules/gcs/examples/notification.yaml | 31 +++++++++++++ .../retention-logging.yaml} | 22 ++++----- tests/modules/gcs/examples/simple.yaml | 46 +++++++++++++++++++ tests/modules/gcs/iam.tfvars | 3 -- tests/modules/gcs/prefix.tfvars | 1 - 9 files changed, 130 insertions(+), 86 deletions(-) delete mode 100644 tests/modules/gcs/common.tfvars rename tests/modules/gcs/{tftest.yaml => examples/cmek.yaml} (71%) rename tests/modules/gcs/{prefix.yaml => examples/lifecycle.yaml} (51%) create mode 100644 tests/modules/gcs/examples/notification.yaml rename tests/modules/gcs/{iam.yaml => examples/retention-logging.yaml} (65%) create mode 100644 tests/modules/gcs/examples/simple.yaml delete mode 100644 tests/modules/gcs/iam.tfvars delete mode 100644 tests/modules/gcs/prefix.tfvars diff --git a/modules/gcs/README.md b/modules/gcs/README.md index 439b4522..07c5a6d7 100644 --- a/modules/gcs/README.md +++ b/modules/gcs/README.md @@ -8,50 +8,46 @@ module "bucket" { project_id = "myproject" prefix = "test" name = "my-bucket" + versioning = true iam = { "roles/storage.admin" = ["group:storage@example.com"] } + labels = { + cost-center = "devops" + } } -# tftest modules=1 resources=2 +# tftest modules=1 resources=2 inventory=simple.yaml ``` ### Example with Cloud KMS ```hcl module "bucket" { - source = "./fabric/modules/gcs" - project_id = "myproject" - prefix = "test" - name = "my-bucket" - iam = { - "roles/storage.admin" = ["group:storage@example.com"] - } + source = "./fabric/modules/gcs" + project_id = "myproject" + name = "my-bucket" encryption_key = "my-encryption-key" } -# tftest modules=1 resources=2 +# tftest modules=1 resources=1 inventory=cmek.yaml ``` -### Example with retention policy +### Example with retention policy and logging ```hcl module "bucket" { source = "./fabric/modules/gcs" project_id = "myproject" - prefix = "test" name = "my-bucket" - iam = { - "roles/storage.admin" = ["group:storage@example.com"] - } retention_policy = { retention_period = 100 is_locked = true } logging_config = { - log_bucket = var.bucket + log_bucket = "log-bucket" log_object_prefix = null } } -# tftest modules=1 resources=2 +# tftest modules=1 resources=1 inventory=retention-logging.yaml ``` ### Example with lifecycle rule @@ -60,11 +56,7 @@ module "bucket" { module "bucket" { source = "./fabric/modules/gcs" project_id = "myproject" - prefix = "test" name = "my-bucket" - iam = { - "roles/storage.admin" = ["group:storage@example.com"] - } lifecycle_rules = { lr-0 = { action = { @@ -77,7 +69,7 @@ module "bucket" { } } } -# tftest modules=1 resources=2 +# tftest modules=1 resources=1 inventory=lifecycle.yaml ``` ### Minimal example with GCS notifications @@ -86,7 +78,6 @@ module "bucket" { module "bucket-gcs-notification" { source = "./fabric/modules/gcs" project_id = "myproject" - prefix = "test" name = "my-bucket" notification_config = { enabled = true @@ -97,7 +88,7 @@ module "bucket-gcs-notification" { custom_attributes = {} } } -# tftest modules=1 resources=4 +# tftest modules=1 resources=4 inventory=notification.yaml ``` diff --git a/tests/modules/gcs/common.tfvars b/tests/modules/gcs/common.tfvars deleted file mode 100644 index 5bab53b2..00000000 --- a/tests/modules/gcs/common.tfvars +++ /dev/null @@ -1,13 +0,0 @@ -force_destroy = true -labels = { environment = "test" } -logging_config = { - log_bucket = "foo" -} -name = "test" -project_id = "test-project" -retention_policy = { - retention_period = 5 - is_locked = false -} -storage_class = "MULTI_REGIONAL" -versioning = true diff --git a/tests/modules/gcs/tftest.yaml b/tests/modules/gcs/examples/cmek.yaml similarity index 71% rename from tests/modules/gcs/tftest.yaml rename to tests/modules/gcs/examples/cmek.yaml index 22337d18..ee92a5d2 100644 --- a/tests/modules/gcs/tftest.yaml +++ b/tests/modules/gcs/examples/cmek.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,9 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -module: modules/gcs -common_tfvars: - - common.tfvars -tests: - prefix: - iam: +values: + module.bucket.google_storage_bucket.bucket: + encryption: + - default_kms_key_name: my-encryption-key + name: my-bucket + project: myproject + +counts: + google_storage_bucket: 1 diff --git a/tests/modules/gcs/prefix.yaml b/tests/modules/gcs/examples/lifecycle.yaml similarity index 51% rename from tests/modules/gcs/prefix.yaml rename to tests/modules/gcs/examples/lifecycle.yaml index 6baee4a1..69eeea41 100644 --- a/tests/modules/gcs/prefix.yaml +++ b/tests/modules/gcs/examples/lifecycle.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,32 +13,26 @@ # limitations under the License. values: - google_storage_bucket.bucket: - force_destroy: true - labels: - environment: test - location: EU - logging: - - log_bucket: foo - name: foo-test - project: test-project - retention_policy: - - is_locked: false - retention_period: 5 - storage_class: MULTI_REGIONAL - uniform_bucket_level_access: true - versioning: - - enabled: true + module.bucket.google_storage_bucket.bucket: + lifecycle_rule: + - action: + - storage_class: STANDARD + type: SetStorageClass + condition: + - age: 30 + created_before: '' + custom_time_before: '' + days_since_custom_time: null + days_since_noncurrent_time: null + matches_prefix: [] + matches_storage_class: [] + matches_suffix: [] + noncurrent_time_before: '' + num_newer_versions: null + name: my-bucket + project: myproject counts: google_storage_bucket: 1 - modules: 0 - resources: 1 -outputs: - bucket: __missing__ - id: foo-test - name: foo-test - notification: null - topic: null - url: __missing__ +outputs: {} diff --git a/tests/modules/gcs/examples/notification.yaml b/tests/modules/gcs/examples/notification.yaml new file mode 100644 index 00000000..9536e89b --- /dev/null +++ b/tests/modules/gcs/examples/notification.yaml @@ -0,0 +1,31 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.bucket-gcs-notification.google_pubsub_topic.topic[0]: {} + module.bucket-gcs-notification.google_pubsub_topic_iam_binding.binding[0]: {} + module.bucket-gcs-notification.google_storage_bucket.bucket: + name: my-bucket + project: myproject + module.bucket-gcs-notification.google_storage_notification.notification[0]: + bucket: my-bucket + event_types: + - OBJECT_FINALIZE + payload_format: JSON_API_V1 + +counts: + google_pubsub_topic: 1 + google_pubsub_topic_iam_binding: 1 + google_storage_bucket: 1 + google_storage_notification: 1 diff --git a/tests/modules/gcs/iam.yaml b/tests/modules/gcs/examples/retention-logging.yaml similarity index 65% rename from tests/modules/gcs/iam.yaml rename to tests/modules/gcs/examples/retention-logging.yaml index 8a85a4bd..96241420 100644 --- a/tests/modules/gcs/iam.yaml +++ b/tests/modules/gcs/examples/retention-logging.yaml @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,18 +13,14 @@ # limitations under the License. values: - google_storage_bucket.bucket: - name: test - - google_storage_bucket_iam_binding.bindings["roles/storage.admin"]: - bucket: test - condition: [] - members: - - user:a@example.org - role: roles/storage.admin + module.bucket.google_storage_bucket.bucket: + logging: + - log_bucket: log-bucket + name: my-bucket + project: myproject + retention_policy: + - is_locked: true + retention_period: 100 counts: google_storage_bucket: 1 - google_storage_bucket_iam_binding: 1 - modules: 0 - resources: 2 diff --git a/tests/modules/gcs/examples/simple.yaml b/tests/modules/gcs/examples/simple.yaml new file mode 100644 index 00000000..bc2630b8 --- /dev/null +++ b/tests/modules/gcs/examples/simple.yaml @@ -0,0 +1,46 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.bucket.google_storage_bucket.bucket: + autoclass: [] + cors: [] + custom_placement_config: [] + default_event_based_hold: null + encryption: [] + force_destroy: false + labels: + cost-center: devops + lifecycle_rule: [] + location: EU + logging: [] + name: test-my-bucket + project: myproject + requester_pays: null + retention_policy: [] + storage_class: MULTI_REGIONAL + timeouts: null + uniform_bucket_level_access: true + versioning: + - enabled: true + module.bucket.google_storage_bucket_iam_binding.bindings["roles/storage.admin"]: + bucket: test-my-bucket + condition: [] + members: + - group:storage@example.com + role: roles/storage.admin + +counts: + google_storage_bucket: 1 + google_storage_bucket_iam_binding: 1 diff --git a/tests/modules/gcs/iam.tfvars b/tests/modules/gcs/iam.tfvars deleted file mode 100644 index cfb3a014..00000000 --- a/tests/modules/gcs/iam.tfvars +++ /dev/null @@ -1,3 +0,0 @@ -iam = { - "roles/storage.admin" = ["user:a@example.org"] -} diff --git a/tests/modules/gcs/prefix.tfvars b/tests/modules/gcs/prefix.tfvars deleted file mode 100644 index 0031d561..00000000 --- a/tests/modules/gcs/prefix.tfvars +++ /dev/null @@ -1 +0,0 @@ -prefix = "foo" From 2aad7845a4f868bdee1b5d55a2f82e5f74e04515 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 11:20:00 +0100 Subject: [PATCH 11/34] Allow dashes and underscores in tftest file ids --- tests/examples/conftest.py | 4 ++-- tests/examples/test_plan.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/examples/conftest.py b/tests/examples/conftest.py index 16863e26..4d3d85ee 100644 --- a/tests/examples/conftest.py +++ b/tests/examples/conftest.py @@ -1,4 +1,4 @@ -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import marko FABRIC_ROOT = Path(__file__).parents[2] -FILE_TEST_RE = re.compile(r'# tftest-file +id=(\w+) +path=([\S]+)') +FILE_TEST_RE = re.compile(r'# tftest-file +id=([\w_.-]+) +path=([\S]+)') Example = collections.namedtuple('Example', 'name code module files') File = collections.namedtuple('File', 'path content') diff --git a/tests/examples/test_plan.py b/tests/examples/test_plan.py index 2dd42a7a..261276f7 100644 --- a/tests/examples/test_plan.py +++ b/tests/examples/test_plan.py @@ -18,7 +18,7 @@ from pathlib import Path BASE_PATH = Path(__file__).parent COUNT_TEST_RE = re.compile(r'# tftest +modules=(\d+) +resources=(\d+)' + - r'(?: +files=([\w,-.]+))?' + + r'(?: +files=([\w,_-]+))?' + r'(?: +inventory=([\w\-.]+))?') @@ -49,10 +49,10 @@ def test_example(plan_validator, tmp_path, example): summary = plan_validator(module_path=tmp_path, inventory_paths=inventory, tf_var_files=[]) - # import yaml - # print(yaml.dump({"values": summary.values})) - # print(yaml.dump({"counts": summary.counts})) - # print(yaml.dump({"outputs": summary.outputs})) + import yaml + print(yaml.dump({"values": summary.values})) + print(yaml.dump({"counts": summary.counts})) + print(yaml.dump({"outputs": summary.outputs})) counts = summary.counts num_modules, num_resources = counts['modules'], counts['resources'] From a12089ef8cc68b9e0dce58114c3befafe019e942 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 11:20:38 +0100 Subject: [PATCH 12/34] Move VPC factory and route tests to examples. --- modules/net-vpc/README.md | 49 +++++- .../modules/net_vpc/data/factory-subnet.yaml | 23 --- .../modules/net_vpc/data/factory-subnet2.yaml | 17 -- tests/modules/net_vpc/examples/factory.yaml | 50 ++++++ tests/modules/net_vpc/examples/routes.yaml | 146 ++++++++++++++++++ tests/modules/net_vpc/factory.tfvars | 1 - tests/modules/net_vpc/factory.yaml | 44 ------ tests/modules/net_vpc/test_routes.py | 47 ------ tests/modules/net_vpc/tftest.yaml | 1 - 9 files changed, 243 insertions(+), 135 deletions(-) delete mode 100644 tests/modules/net_vpc/data/factory-subnet.yaml delete mode 100644 tests/modules/net_vpc/data/factory-subnet2.yaml create mode 100644 tests/modules/net_vpc/examples/factory.yaml create mode 100644 tests/modules/net_vpc/examples/routes.yaml delete mode 100644 tests/modules/net_vpc/factory.tfvars delete mode 100644 tests/modules/net_vpc/factory.yaml delete mode 100644 tests/modules/net_vpc/test_routes.py diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 5a901378..002c738d 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -314,11 +314,17 @@ module "vpc" { name = "my-network" data_folder = "config/subnets" } -# tftest modules=1 resources=2 files=subnets +# tftest modules=1 resources=3 files=subnet-simple,subnet-detailed inventory=factory.yaml ``` ```yaml -# tftest-file id=subnets path=config/subnets/subnet-name.yaml +# tftest-file id=subnet-simple path=config/subnets/subnet-simple.yaml +region: europe-west4 +ip_cidr_range: 10.0.1.0/24 +``` + +```yaml +# tftest-file id=subnet-detailed path=config/subnets/subnet-detailed.yaml region: europe-west1 description: Sample description ip_cidr_range: 10.0.0.0/24 @@ -337,6 +343,45 @@ flow_logs: # enable, set to empty map to use defaults ``` +### Custom Routes + +VPC routes can be configured through the `routes` variable. + +```hcl +locals { + route_types = { + 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" + } +} +module "vpc" { + source = "./fabric/modules/net-vpc" + for_each = local.route_types + project_id = "my-project" + name = "my-network-with-route-${replace(each.key, "_", "-")}" + routes = { + next-hop = { + dest_range = "192.168.128.0/24" + tags = null + next_hop_type = each.key + next_hop = each.value + } + gateway = { + dest_range = "0.0.0.0/0", + priority = 100 + tags = ["tag-a"] + next_hop_type = "gateway", + next_hop = "global/gateways/default-internet-gateway" + } + } +} +# tftest modules=5 resources=15 inventory=routes.yaml +``` + + ## Variables | name | description | type | required | default | diff --git a/tests/modules/net_vpc/data/factory-subnet.yaml b/tests/modules/net_vpc/data/factory-subnet.yaml deleted file mode 100644 index d0f4bd8f..00000000 --- a/tests/modules/net_vpc/data/factory-subnet.yaml +++ /dev/null @@ -1,23 +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. - -region: europe-west1 -description: Sample description -ip_cidr_range: 10.128.0.0/24 -enable_private_access: false -iam_users: ["foobar@example.com"] -iam_groups: ["lorem@example.com"] -iam_service_accounts: ["foobar@project-id.iam.gserviceaccount.com"] -secondary_ip_ranges: - secondary-range-a: 192.168.128.0/24 diff --git a/tests/modules/net_vpc/data/factory-subnet2.yaml b/tests/modules/net_vpc/data/factory-subnet2.yaml deleted file mode 100644 index e110c162..00000000 --- a/tests/modules/net_vpc/data/factory-subnet2.yaml +++ /dev/null @@ -1,17 +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. - -region: europe-west4 -description: Sample description -ip_cidr_range: 10.129.0.0/24 diff --git a/tests/modules/net_vpc/examples/factory.yaml b/tests/modules/net_vpc/examples/factory.yaml new file mode 100644 index 00000000..48671c29 --- /dev/null +++ b/tests/modules/net_vpc/examples/factory.yaml @@ -0,0 +1,50 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc.google_compute_network.network[0]: + name: my-network + project: my-project + routing_mode: GLOBAL + module.vpc.google_compute_subnetwork.subnetwork["europe-west1/subnet-detailed"]: + description: Sample description + ip_cidr_range: 10.0.0.0/24 + log_config: + - aggregation_interval: INTERVAL_5_SEC + filter_expr: 'true' + flow_sampling: 0.5 + metadata: INCLUDE_ALL_METADATA + metadata_fields: null + name: subnet-detailed + private_ip_google_access: false + project: my-project + region: europe-west1 + role: null + secondary_ip_range: + - ip_cidr_range: 192.168.0.0/24 + range_name: secondary-range-a + module.vpc.google_compute_subnetwork.subnetwork["europe-west4/subnet-simple"]: + description: Terraform-managed. + ip_cidr_range: 10.0.1.0/24 + log_config: [] + name: subnet-simple + private_ip_google_access: true + project: my-project + region: europe-west4 + role: null + secondary_ip_range: [] + +counts: + google_compute_network: 1 + google_compute_subnetwork: 2 diff --git a/tests/modules/net_vpc/examples/routes.yaml b/tests/modules/net_vpc/examples/routes.yaml new file mode 100644 index 00000000..205197c8 --- /dev/null +++ b/tests/modules/net_vpc/examples/routes.yaml @@ -0,0 +1,146 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.vpc["gateway"].google_compute_network.network[0]: + name: my-network-with-route-gateway + project: my-project + routing_mode: GLOBAL + module.vpc["gateway"].google_compute_route.gateway["gateway"]: + dest_range: 0.0.0.0/0 + name: my-network-with-route-gateway-gateway + next_hop_gateway: global/gateways/default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 100 + project: my-project + tags: + - tag-a + module.vpc["gateway"].google_compute_route.gateway["next-hop"]: + dest_range: 192.168.128.0/24 + name: my-network-with-route-gateway-next-hop + next_hop_gateway: global/gateways/default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 1000 + project: my-project + tags: null + module.vpc["ilb"].google_compute_network.network[0]: + name: my-network-with-route-ilb + project: my-project + routing_mode: GLOBAL + module.vpc["ilb"].google_compute_route.gateway["gateway"]: + dest_range: 0.0.0.0/0 + name: my-network-with-route-ilb-gateway + next_hop_gateway: global/gateways/default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 100 + project: my-project + tags: + - tag-a + module.vpc["ilb"].google_compute_route.ilb["next-hop"]: + dest_range: 192.168.128.0/24 + name: my-network-with-route-ilb-next-hop + next_hop_gateway: null + next_hop_ilb: regions/europe-west1/forwardingRules/test + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 1000 + project: my-project + tags: null + module.vpc["instance"].google_compute_network.network[0]: + name: my-network-with-route-instance + project: my-project + routing_mode: GLOBAL + module.vpc["instance"].google_compute_route.gateway["gateway"]: + dest_range: 0.0.0.0/0 + name: my-network-with-route-instance-gateway + next_hop_gateway: global/gateways/default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 100 + project: my-project + tags: + - tag-a + module.vpc["instance"].google_compute_route.instance["next-hop"]: + dest_range: 192.168.128.0/24 + name: my-network-with-route-instance-next-hop + next_hop_gateway: null + next_hop_ilb: null + next_hop_instance: zones/europe-west1-b/test + next_hop_instance_zone: europe-west1-b + next_hop_vpn_tunnel: null + priority: 1000 + project: my-project + tags: null + module.vpc["ip"].google_compute_network.network[0]: + name: my-network-with-route-ip + project: my-project + routing_mode: GLOBAL + module.vpc["ip"].google_compute_route.gateway["gateway"]: + dest_range: 0.0.0.0/0 + name: my-network-with-route-ip-gateway + next_hop_gateway: global/gateways/default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 100 + project: my-project + tags: + - tag-a + module.vpc["ip"].google_compute_route.ip["next-hop"]: + dest_range: 192.168.128.0/24 + name: my-network-with-route-ip-next-hop + next_hop_gateway: null + next_hop_ilb: null + next_hop_instance: null + next_hop_ip: 192.168.0.128 + next_hop_vpn_tunnel: null + priority: 1000 + project: my-project + tags: null + module.vpc["vpn_tunnel"].google_compute_network.network[0]: + name: my-network-with-route-vpn-tunnel + project: my-project + routing_mode: GLOBAL + module.vpc["vpn_tunnel"].google_compute_route.gateway["gateway"]: + dest_range: 0.0.0.0/0 + name: my-network-with-route-vpn-tunnel-gateway + next_hop_gateway: global/gateways/default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 100 + project: my-project + tags: + - tag-a + module.vpc["vpn_tunnel"].google_compute_route.vpn_tunnel["next-hop"]: + dest_range: 192.168.128.0/24 + name: my-network-with-route-vpn-tunnel-next-hop + next_hop_gateway: null + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: regions/europe-west1/vpnTunnels/foo + priority: 1000 + project: my-project + tags: null + +counts: + google_compute_network: 5 + google_compute_route: 10 diff --git a/tests/modules/net_vpc/factory.tfvars b/tests/modules/net_vpc/factory.tfvars deleted file mode 100644 index 8c4d4a28..00000000 --- a/tests/modules/net_vpc/factory.tfvars +++ /dev/null @@ -1 +0,0 @@ -data_folder = "../../tests/modules/net_vpc/data" diff --git a/tests/modules/net_vpc/factory.yaml b/tests/modules/net_vpc/factory.yaml deleted file mode 100644 index 9cf628d0..00000000 --- a/tests/modules/net_vpc/factory.yaml +++ /dev/null @@ -1,44 +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. - -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/test_routes.py b/tests/modules/net_vpc/test_routes.py deleted file mode 100644 index 01d9673d..00000000 --- a/tests/modules/net_vpc/test_routes.py +++ /dev/null @@ -1,47 +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. - -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/modules/net_vpc/tftest.yaml b/tests/modules/net_vpc/tftest.yaml index c0dfa0ad..5e9668ea 100644 --- a/tests/modules/net_vpc/tftest.yaml +++ b/tests/modules/net_vpc/tftest.yaml @@ -18,7 +18,6 @@ common_tfvars: tests: shared_vpc: - factory: psa_routes_export: psa_routes_import: psa_routes_import_export: From 1e0d7776e18a7f11497161a4a68d9fdd9bbdd12e Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 12:21:48 +0100 Subject: [PATCH 13/34] Update DNS tests --- modules/dns/README.md | 32 +++- modules/dns/main.tf | 2 +- .../modules/dns/examples/forwarding-zone.yaml | 34 +++++ tests/modules/dns/examples/peering-zone.yaml | 34 +++++ tests/modules/dns/examples/private-zone.yaml | 50 +++++++ tests/modules/dns/examples/public-zone.yaml | 38 +++++ tests/modules/dns/examples/reverse-zone.yaml | 27 ++++ .../dns/examples/routing-policies.yaml | 80 ++++++++++ tests/modules/dns/fixture/main.tf | 27 ---- tests/modules/dns/fixture/variables.tf | 62 -------- tests/modules/dns/no_clients.tfvars | 5 + tests/modules/dns/no_clients.yaml | 25 ++++ tests/modules/dns/null_forwarders.tfvars | 4 + tests/modules/dns/null_forwarders.yaml | 20 +++ tests/modules/dns/test_plan.py | 138 ------------------ tests/modules/dns/tftest.yaml | 19 +++ 16 files changed, 363 insertions(+), 234 deletions(-) create mode 100644 tests/modules/dns/examples/forwarding-zone.yaml create mode 100644 tests/modules/dns/examples/peering-zone.yaml create mode 100644 tests/modules/dns/examples/private-zone.yaml create mode 100644 tests/modules/dns/examples/public-zone.yaml create mode 100644 tests/modules/dns/examples/reverse-zone.yaml create mode 100644 tests/modules/dns/examples/routing-policies.yaml delete mode 100644 tests/modules/dns/fixture/main.tf delete mode 100644 tests/modules/dns/fixture/variables.tf create mode 100644 tests/modules/dns/no_clients.tfvars create mode 100644 tests/modules/dns/no_clients.yaml create mode 100644 tests/modules/dns/null_forwarders.tfvars create mode 100644 tests/modules/dns/null_forwarders.yaml delete mode 100644 tests/modules/dns/test_plan.py create mode 100644 tests/modules/dns/tftest.yaml diff --git a/modules/dns/README.md b/modules/dns/README.md index 9e461f0e..4803a13c 100644 --- a/modules/dns/README.md +++ b/modules/dns/README.md @@ -21,7 +21,7 @@ module "private-dns" { "A myhost" = { ttl = 600, records = ["10.0.0.120"] } } } -# tftest modules=1 resources=3 +# tftest modules=1 resources=3 inventory=private-zone.yaml ``` ### Forwarding Zone @@ -36,7 +36,7 @@ module "private-dns" { client_networks = [var.vpc.self_link] forwarders = { "10.0.1.1" = null, "1.2.3.4" = "private" } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=forwarding-zone.yaml ``` ### Peering Zone @@ -47,11 +47,12 @@ module "private-dns" { project_id = "myproject" type = "peering" name = "test-example" - domain = "test.example." + domain = "." + description = "Forwarding zone for ." client_networks = [var.vpc.self_link] peer_network = var.vpc2.self_link } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=peering-zone.yaml ``` ### Routing Policies @@ -84,7 +85,7 @@ module "private-dns" { } } } -# tftest modules=1 resources=4 +# tftest modules=1 resources=4 inventory=routing-policies.yaml ``` ### Reverse Lookup Zone @@ -98,10 +99,29 @@ module "private-dns" { domain = "0.0.10.in-addr.arpa." client_networks = [var.vpc.self_link] } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=reverse-zone.yaml ``` +### Public Zone + +```hcl +module "public-dns" { + source = "./fabric/modules/dns" + project_id = "myproject" + type = "public" + name = "example" + domain = "example.com." + recordsets = { + "A myhost" = { ttl = 300, records = ["127.0.0.1"] } + } +} +# tftest modules=1 resources=3 inventory=public-zone.yaml +``` + + + + ## Variables | name | description | type | required | default | diff --git a/modules/dns/main.tf b/modules/dns/main.tf index ca30c7d0..edf342ef 100644 --- a/modules/dns/main.tf +++ b/modules/dns/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/modules/dns/examples/forwarding-zone.yaml b/tests/modules/dns/examples/forwarding-zone.yaml new file mode 100644 index 00000000..4a09114e --- /dev/null +++ b/tests/modules/dns/examples/forwarding-zone.yaml @@ -0,0 +1,34 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.private-dns.google_dns_managed_zone.non-public[0]: + dns_name: test.example. + forwarding_config: + - target_name_servers: + - forwarding_path: '' + ipv4_address: 10.0.1.1 + - forwarding_path: private + ipv4_address: 1.2.3.4 + name: test-example + private_visibility_config: + - gke_clusters: [] + networks: + - network_url: projects/xxx/global/networks/aaa + project: myproject + visibility: private + +counts: + google_dns_managed_zone: 1 + diff --git a/tests/modules/dns/examples/peering-zone.yaml b/tests/modules/dns/examples/peering-zone.yaml new file mode 100644 index 00000000..9f16adab --- /dev/null +++ b/tests/modules/dns/examples/peering-zone.yaml @@ -0,0 +1,34 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.private-dns.google_dns_managed_zone.non-public[0]: + description: Forwarding zone for . + dns_name: . + forwarding_config: [] + name: test-example + peering_config: + - target_network: + - network_url: projects/xxx/global/networks/ccc + private_visibility_config: + - gke_clusters: [] + networks: + - network_url: projects/xxx/global/networks/aaa + project: myproject + visibility: private + +counts: + google_dns_managed_zone: 1 + +outputs: {} diff --git a/tests/modules/dns/examples/private-zone.yaml b/tests/modules/dns/examples/private-zone.yaml new file mode 100644 index 00000000..f6426645 --- /dev/null +++ b/tests/modules/dns/examples/private-zone.yaml @@ -0,0 +1,50 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.private-dns.google_dns_managed_zone.non-public[0]: + description: Terraform managed. + dns_name: test.example. + force_destroy: false + forwarding_config: [] + name: test-example + peering_config: [] + private_visibility_config: + - gke_clusters: [] + networks: + - network_url: projects/xxx/global/networks/aaa + project: myproject + visibility: private + module.private-dns.google_dns_record_set.cloud-static-records["A localhost"]: + managed_zone: test-example + name: localhost.test.example. + project: myproject + routing_policy: [] + rrdatas: + - 127.0.0.1 + ttl: 300 + type: A + module.private-dns.google_dns_record_set.cloud-static-records["A myhost"]: + managed_zone: test-example + name: myhost.test.example. + project: myproject + routing_policy: [] + rrdatas: + - 10.0.0.120 + ttl: 600 + type: A + +counts: + google_dns_managed_zone: 1 + google_dns_record_set: 2 diff --git a/tests/modules/dns/examples/public-zone.yaml b/tests/modules/dns/examples/public-zone.yaml new file mode 100644 index 00000000..0f8067a7 --- /dev/null +++ b/tests/modules/dns/examples/public-zone.yaml @@ -0,0 +1,38 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.public-dns.google_dns_managed_zone.public[0]: + dns_name: example.com. + name: example + project: myproject + visibility: public + module.public-dns.google_dns_record_set.cloud-static-records["A myhost"]: + managed_zone: example + name: myhost.example.com. + project: myproject + routing_policy: [] + rrdatas: + - 127.0.0.1 + ttl: 300 + type: A + +counts: + google_dns_keys: 1 + google_dns_managed_zone: 1 + google_dns_record_set: 1 + modules: 1 + resources: 3 + +outputs: {} diff --git a/tests/modules/dns/examples/reverse-zone.yaml b/tests/modules/dns/examples/reverse-zone.yaml new file mode 100644 index 00000000..17e76a12 --- /dev/null +++ b/tests/modules/dns/examples/reverse-zone.yaml @@ -0,0 +1,27 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.private-dns.google_dns_managed_zone.non-public[0]: + description: Terraform managed. + dns_name: 0.0.10.in-addr.arpa. + name: test-example + project: myproject + reverse_lookup: true + visibility: private + +counts: + google_dns_managed_zone: 1 + +outputs: {} diff --git a/tests/modules/dns/examples/routing-policies.yaml b/tests/modules/dns/examples/routing-policies.yaml new file mode 100644 index 00000000..45b19276 --- /dev/null +++ b/tests/modules/dns/examples/routing-policies.yaml @@ -0,0 +1,80 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.private-dns.google_dns_managed_zone.non-public[0]: + dns_name: test.example. + name: test-example + project: myproject + module.private-dns.google_dns_record_set.cloud-geo-records["A geo"]: + managed_zone: test-example + name: geo.test.example. + project: myproject + routing_policy: + - enable_geo_fencing: null + geo: + - health_checked_targets: [] + location: europe-west1 + rrdatas: + - 10.0.0.1 + - health_checked_targets: [] + location: europe-west2 + rrdatas: + - 10.0.0.2 + - health_checked_targets: [] + location: europe-west3 + rrdatas: + - 10.0.0.3 + primary_backup: [] + wrr: [] + rrdatas: null + ttl: 300 + type: A + module.private-dns.google_dns_record_set.cloud-static-records["A regular"]: + managed_zone: test-example + name: regular.test.example. + project: myproject + routing_policy: [] + rrdatas: + - 10.20.0.1 + ttl: 300 + type: A + module.private-dns.google_dns_record_set.cloud-wrr-records["A wrr"]: + managed_zone: test-example + name: wrr.test.example. + project: myproject + routing_policy: + - enable_geo_fencing: null + geo: [] + primary_backup: [] + wrr: + - health_checked_targets: [] + rrdatas: + - 10.10.0.1 + weight: 0.6 + - health_checked_targets: [] + rrdatas: + - 10.10.0.2 + weight: 0.2 + - health_checked_targets: [] + rrdatas: + - 10.10.0.3 + weight: 0.2 + rrdatas: null + ttl: 600 + type: A + +counts: + google_dns_managed_zone: 1 + google_dns_record_set: 3 diff --git a/tests/modules/dns/fixture/main.tf b/tests/modules/dns/fixture/main.tf deleted file mode 100644 index bab31920..00000000 --- a/tests/modules/dns/fixture/main.tf +++ /dev/null @@ -1,27 +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 "test" { - source = "../../../../modules/dns" - project_id = "my-project" - name = "test" - domain = "test.example." - client_networks = var.client_networks - type = var.type - forwarders = var.forwarders - peer_network = var.peer_network - recordsets = var.recordsets -} diff --git a/tests/modules/dns/fixture/variables.tf b/tests/modules/dns/fixture/variables.tf deleted file mode 100644 index 8e55a287..00000000 --- a/tests/modules/dns/fixture/variables.tf +++ /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. - */ - -variable "client_networks" { - type = list(string) - default = [ - "https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default" - ] -} - -variable "forwarders" { - type = map(string) - default = {} -} - -variable "peer_network" { - type = string - default = null -} - -variable "recordsets" { - type = any - default = { - "A localhost" = { ttl = 300, records = ["127.0.0.1"] } - "A local-host.test.example." = { ttl = 300, records = ["127.0.0.2"] } - "CNAME *" = { ttl = 300, records = ["localhost.example.org."] } - "A " = { ttl = 300, records = ["127.0.0.3"] } - "A geo" = { - geo_routing = [ - { location = "europe-west1", records = ["127.0.0.4"] }, - { location = "europe-west2", records = ["127.0.0.5"] }, - { location = "europe-west3", records = ["127.0.0.6"] } - ] - } - "A wrr" = { - ttl = 600 - wrr_routing = [ - { weight = 0.6, records = ["127.0.0.7"] }, - { weight = 0.2, records = ["127.0.0.8"] }, - { weight = 0.2, records = ["127.0.0.9"] } - ] - } - } -} - -variable "type" { - type = string - default = "private" -} diff --git a/tests/modules/dns/no_clients.tfvars b/tests/modules/dns/no_clients.tfvars new file mode 100644 index 00000000..97b72273 --- /dev/null +++ b/tests/modules/dns/no_clients.tfvars @@ -0,0 +1,5 @@ +type = "private" +domain = "test.example." +name = "test" +project_id = "my-project" +client_networks = [] diff --git a/tests/modules/dns/no_clients.yaml b/tests/modules/dns/no_clients.yaml new file mode 100644 index 00000000..42f628c9 --- /dev/null +++ b/tests/modules/dns/no_clients.yaml @@ -0,0 +1,25 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + google_dns_managed_zone.non-public[0]: + dns_name: test.example. + name: test + private_visibility_config: [] + visibility: private + +counts: + google_dns_managed_zone: 1 + modules: 0 + resources: 1 diff --git a/tests/modules/dns/null_forwarders.tfvars b/tests/modules/dns/null_forwarders.tfvars new file mode 100644 index 00000000..4514d639 --- /dev/null +++ b/tests/modules/dns/null_forwarders.tfvars @@ -0,0 +1,4 @@ +type = "forwarding" +domain = "test.example." +name = "test" +project_id = "my-project" diff --git a/tests/modules/dns/null_forwarders.yaml b/tests/modules/dns/null_forwarders.yaml new file mode 100644 index 00000000..bbe637fc --- /dev/null +++ b/tests/modules/dns/null_forwarders.yaml @@ -0,0 +1,20 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + google_dns_managed_zone.non-public[0]: + forwarding_config: [] + +counts: + google_dns_managed_zone: 1 diff --git a/tests/modules/dns/test_plan.py b/tests/modules/dns/test_plan.py deleted file mode 100644 index 5cc1ba70..00000000 --- a/tests/modules/dns/test_plan.py +++ /dev/null @@ -1,138 +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_private(plan_runner): - "Test private zone with three recordsets." - _, resources = plan_runner() - assert len(resources) == 7 - assert set(r['type'] for r in resources) == { - 'google_dns_record_set', 'google_dns_managed_zone' - } - for r in resources: - if r['type'] != 'google_dns_managed_zone': - continue - assert r['values']['visibility'] == 'private' - assert len(r['values']['private_visibility_config']) == 1 - - -def test_private_recordsets(plan_runner): - "Test recordsets in private zone." - _, resources = plan_runner() - recordsets = [ - r['values'] for r in resources if r['type'] == 'google_dns_record_set' - ] - - assert set(r['name'] for r in recordsets) == { - 'localhost.test.example.', 'local-host.test.example.', '*.test.example.', - "test.example.", "geo.test.example.", "wrr.test.example." - } - - for r in recordsets: - if r['name'] not in ['wrr.test.example.', 'geo.test.example.']: - assert r['routing_policy'] == [] - assert r['rrdatas'] != [] - - -def test_routing_policies(plan_runner): - "Test recordsets with routing policies." - _, resources = plan_runner() - recordsets = [ - r['values'] for r in resources if r['type'] == 'google_dns_record_set' - ] - geo_zone = [ - r['values'] for r in resources if r['address'] == - 'module.test.google_dns_record_set.cloud-geo-records["A geo"]' - ][0] - assert geo_zone['name'] == 'geo.test.example.' - assert geo_zone['routing_policy'][0]['wrr'] == [] - geo_policy = geo_zone['routing_policy'][0]['geo'] - assert geo_policy[0]['location'] == 'europe-west1' - assert geo_policy[0]['rrdatas'] == ['127.0.0.4'] - assert geo_policy[1]['location'] == 'europe-west2' - assert geo_policy[1]['rrdatas'] == ['127.0.0.5'] - assert geo_policy[2]['location'] == 'europe-west3' - assert geo_policy[2]['rrdatas'] == ['127.0.0.6'] - - wrr_zone = [ - r['values'] for r in resources if r['address'] == - 'module.test.google_dns_record_set.cloud-wrr-records["A wrr"]' - ][0] - assert wrr_zone['name'] == 'wrr.test.example.' - wrr_policy = wrr_zone['routing_policy'][0]['wrr'] - assert wrr_policy[0]['weight'] == 0.6 - assert wrr_policy[0]['rrdatas'] == ['127.0.0.7'] - assert wrr_policy[1]['weight'] == 0.2 - assert wrr_policy[1]['rrdatas'] == ['127.0.0.8'] - assert wrr_policy[2]['weight'] == 0.2 - assert wrr_policy[2]['rrdatas'] == ['127.0.0.9'] - assert wrr_zone['routing_policy'][0]['geo'] == [] - - -def test_private_no_networks(plan_runner): - "Test private zone not exposed to any network." - _, resources = plan_runner(client_networks='[]') - for r in resources: - if r['type'] != 'google_dns_managed_zone': - continue - assert r['values']['visibility'] == 'private' - assert len(r['values']['private_visibility_config']) == 0 - - -def test_forwarding_recordsets_null_forwarders(plan_runner): - "Test forwarding zone with wrong set of attributes does not break." - _, resources = plan_runner(type='forwarding') - assert len(resources) == 1 - resource = resources[0] - assert resource['type'] == 'google_dns_managed_zone' - assert resource['values']['forwarding_config'] == [] - - -def test_forwarding(plan_runner): - "Test forwarding zone with single forwarder." - _, resources = plan_runner(type='forwarding', recordsets='null', - forwarders='{ "1.2.3.4" = null }') - assert len(resources) == 1 - resource = resources[0] - assert resource['type'] == 'google_dns_managed_zone' - assert resource['values']['forwarding_config'] == [{ - 'target_name_servers': [{ - 'forwarding_path': '', - 'ipv4_address': '1.2.3.4' - }] - }] - - -def test_peering(plan_runner): - "Test peering zone." - _, resources = plan_runner(type='peering', recordsets='null', - peer_network='dummy-vpc-self-link') - assert len(resources) == 1 - resource = resources[0] - assert resource['type'] == 'google_dns_managed_zone' - assert resource['values']['peering_config'] == [{ - 'target_network': [{ - 'network_url': 'dummy-vpc-self-link' - }] - }] - - -def test_public(plan_runner): - "Test public zone with two recordsets." - _, resources = plan_runner(type='public') - for r in resources: - if r['type'] != 'google_dns_managed_zone': - continue - assert r['values']['visibility'] == 'public' - assert r['values']['private_visibility_config'] == [] diff --git a/tests/modules/dns/tftest.yaml b/tests/modules/dns/tftest.yaml new file mode 100644 index 00000000..5172a013 --- /dev/null +++ b/tests/modules/dns/tftest.yaml @@ -0,0 +1,19 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module: modules/dns + +tests: + no_clients: + null_forwarders: From 9c9aafb3f1a23e224c1ca85bf2375abad689ff67 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 13:03:30 +0100 Subject: [PATCH 14/34] Update gke-cluster tests --- modules/gke-cluster/README.md | 34 ++++++++++++-- .../gke_cluster/examples/autopilot.yaml | 32 +++++++++++++ tests/modules/gke_cluster/examples/basic.yaml | 42 +++++++++++++++++ .../gke_cluster/examples/dataplane-v2.yaml | 45 +++++++++++++++++++ tests/modules/gke_cluster/fixture/main.tf | 29 ------------ .../modules/gke_cluster/fixture/variables.tf | 43 ------------------ tests/modules/gke_cluster/test_plan.py | 38 ---------------- 7 files changed, 150 insertions(+), 113 deletions(-) create mode 100644 tests/modules/gke_cluster/examples/autopilot.yaml create mode 100644 tests/modules/gke_cluster/examples/basic.yaml create mode 100644 tests/modules/gke_cluster/examples/dataplane-v2.yaml delete mode 100644 tests/modules/gke_cluster/fixture/main.tf delete mode 100644 tests/modules/gke_cluster/fixture/variables.tf delete mode 100644 tests/modules/gke_cluster/test_plan.py diff --git a/modules/gke-cluster/README.md b/modules/gke-cluster/README.md index caf1fec9..0ba75cd6 100644 --- a/modules/gke-cluster/README.md +++ b/modules/gke-cluster/README.md @@ -33,7 +33,7 @@ module "cluster-1" { environment = "dev" } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=basic.yaml ``` ### GKE Cluster with Dataplane V2 enabled @@ -42,7 +42,7 @@ module "cluster-1" { module "cluster-1" { source = "./fabric/modules/gke-cluster" project_id = "myproject" - name = "cluster-1" + name = "cluster-dataplane-v2" location = "europe-west1-b" vpc_config = { network = var.vpc.self_link @@ -68,8 +68,36 @@ module "cluster-1" { environment = "dev" } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=dataplane-v2.yaml ``` +### Autopilot Cluster + +```hcl +module "cluster-autopilot" { + source = "./fabric/modules/gke-cluster" + project_id = "myproject" + name = "cluster-autopilot" + location = "europe-west1-b" + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + secondary_range_names = { + pods = "pods" + services = "services" + } + master_authorized_ranges = { + internal-vms = "10.0.0.0/8" + } + master_ipv4_cidr_block = "192.168.0.0/28" + } + enable_features = { + autopilot = true + } +} +# tftest modules=1 resources=1 inventory=autopilot.yaml +``` + + ## Variables diff --git a/tests/modules/gke_cluster/examples/autopilot.yaml b/tests/modules/gke_cluster/examples/autopilot.yaml new file mode 100644 index 00000000..0a5380db --- /dev/null +++ b/tests/modules/gke_cluster/examples/autopilot.yaml @@ -0,0 +1,32 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.cluster-autopilot.google_container_cluster.cluster: + enable_autopilot: true + ip_allocation_policy: + - cluster_secondary_range_name: pods + services_secondary_range_name: services + location: europe-west1-b + master_authorized_networks_config: + - cidr_blocks: + - cidr_block: 10.0.0.0/8 + display_name: internal-vms + name: cluster-autopilot + network: projects/xxx/global/networks/aaa + project: myproject + subnetwork: subnet_self_link + +counts: + google_container_cluster: 1 diff --git a/tests/modules/gke_cluster/examples/basic.yaml b/tests/modules/gke_cluster/examples/basic.yaml new file mode 100644 index 00000000..fe6648c8 --- /dev/null +++ b/tests/modules/gke_cluster/examples/basic.yaml @@ -0,0 +1,42 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.cluster-1.google_container_cluster.cluster: + default_max_pods_per_node: 32 + ip_allocation_policy: + - cluster_secondary_range_name: pods + services_secondary_range_name: services + location: europe-west1-b + master_authorized_networks_config: + - cidr_blocks: + - cidr_block: 10.0.0.0/8 + display_name: internal-vms + name: cluster-1 + network: projects/xxx/global/networks/aaa + private_cluster_config: + - enable_private_endpoint: true + enable_private_nodes: true + master_global_access_config: + - enabled: false + master_ipv4_cidr_block: 192.168.0.0/28 + private_endpoint_subnetwork: null + project: myproject + remove_default_node_pool: true + resource_labels: + environment: dev + subnetwork: subnet_self_link + +counts: + google_container_cluster: 1 diff --git a/tests/modules/gke_cluster/examples/dataplane-v2.yaml b/tests/modules/gke_cluster/examples/dataplane-v2.yaml new file mode 100644 index 00000000..ef7ca642 --- /dev/null +++ b/tests/modules/gke_cluster/examples/dataplane-v2.yaml @@ -0,0 +1,45 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.cluster-1.google_container_cluster.cluster: + datapath_provider: ADVANCED_DATAPATH + ip_allocation_policy: + - cluster_secondary_range_name: pods + services_secondary_range_name: services + location: europe-west1-b + master_authorized_networks_config: + - cidr_blocks: + - cidr_block: 10.0.0.0/8 + display_name: internal-vms + min_master_version: null + name: cluster-dataplane-v2 + network: projects/xxx/global/networks/aaa + private_cluster_config: + - enable_private_endpoint: true + enable_private_nodes: true + master_global_access_config: + - enabled: false + master_ipv4_cidr_block: 192.168.0.0/28 + private_endpoint_subnetwork: null + project: myproject + remove_default_node_pool: true + resource_labels: + environment: dev + subnetwork: subnet_self_link + workload_identity_config: + - workload_pool: myproject.svc.id.goog + +counts: + google_container_cluster: 1 diff --git a/tests/modules/gke_cluster/fixture/main.tf b/tests/modules/gke_cluster/fixture/main.tf deleted file mode 100644 index 5e11fbd7..00000000 --- a/tests/modules/gke_cluster/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 "test" { - source = "../../../../modules/gke-cluster" - project_id = "my-project" - name = "cluster-1" - location = "europe-west1-b" - vpc_config = { - network = "mynetwork" - subnetwork = "mysubnet" - } - enable_addons = var.enable_addons - enable_features = var.enable_features - tags = var.tags -} diff --git a/tests/modules/gke_cluster/fixture/variables.tf b/tests/modules/gke_cluster/fixture/variables.tf deleted file mode 100644 index 2104e452..00000000 --- a/tests/modules/gke_cluster/fixture/variables.tf +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -variable "enable_addons" { - type = any - default = { - horizontal_pod_autoscaling = true - http_load_balancing = true - } -} - -variable "enable_features" { - type = any - default = { - workload_identity = true - } -} - -variable "monitoring_config" { - type = any - default = { - managed_prometheus = true - } -} - -variable "tags" { - description = "Network tags applied to nodes." - type = list(string) - default = null -} diff --git a/tests/modules/gke_cluster/test_plan.py b/tests/modules/gke_cluster/test_plan.py deleted file mode 100644 index acd97bed..00000000 --- a/tests/modules/gke_cluster/test_plan.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -def test_standard(plan_runner): - "Test resources created with variable defaults." - _, resources = plan_runner() - assert len(resources) == 1 - - cluster_config = resources[0]['values'] - assert cluster_config['name'] == "cluster-1" - assert cluster_config['network'] == "mynetwork" - assert cluster_config['subnetwork'] == "mysubnet" - assert cluster_config['enable_autopilot'] is None - # assert 'service_account' not in node_config - - -def test_autopilot(plan_runner): - "Test resources created with variable defaults." - _, resources = plan_runner(enable_features='{ autopilot=true }') - assert len(resources) == 1 - cluster_config = resources[0]['values'] - assert cluster_config['name'] == "cluster-1" - assert cluster_config['network'] == "mynetwork" - assert cluster_config['subnetwork'] == "mysubnet" - assert cluster_config['enable_autopilot'] == True - # assert 'service_account' not in node_config From 44724f3839e118c8676978b7b62153f64d716067 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 17:46:06 +0100 Subject: [PATCH 15/34] Update plan_summary to support running documentation examples --- tools/plan_summary.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/tools/plan_summary.py b/tools/plan_summary.py index def79adb..78c5f939 100755 --- a/tools/plan_summary.py +++ b/tools/plan_summary.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright 2022 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ import click import sys +import tempfile import yaml from pathlib import Path @@ -27,17 +28,32 @@ import fixtures @click.command() +@click.option('--example', default=False, is_flag=True) @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})) +def main(example, module, tfvars): + try: + if example: + tmp_dir = tempfile.TemporaryDirectory() + tmp_path = Path(tmp_dir.name) + common_vars = BASEDIR / 'tests' / 'examples' / 'variables.tf' + (tmp_path / 'main.tf').symlink_to(module) + (tmp_path / 'variables.tf').symlink_to(common_vars) + (tmp_path / 'fabric').symlink_to(BASEDIR) + module = tmp_path + else: + 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})) + finally: + if example: + tmp_dir.cleanup() if __name__ == '__main__': From 1820269680b8847ca1241dfa34b377c7940af844 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 18:19:34 +0100 Subject: [PATCH 16/34] Add inventories to gke-nodepool examples --- modules/gke-nodepool/README.md | 67 ++++++++++----- modules/gke-nodepool/variables.tf | 6 +- .../modules/gke_nodepool/examples/basic.yaml | 23 +++++ .../modules/gke_nodepool/examples/config.yaml | 59 +++++++++++++ .../gke_nodepool/examples/create-sa.yaml | 52 +++++++++++ .../gke_nodepool/examples/external-sa.yaml | 43 ++++++++++ tests/modules/gke_nodepool/fixture/main.tf | 45 ---------- .../modules/gke_nodepool/fixture/variables.tf | 86 ------------------- tests/modules/gke_nodepool/test_plan.py | 67 --------------- 9 files changed, 227 insertions(+), 221 deletions(-) create mode 100644 tests/modules/gke_nodepool/examples/basic.yaml create mode 100644 tests/modules/gke_nodepool/examples/config.yaml create mode 100644 tests/modules/gke_nodepool/examples/create-sa.yaml create mode 100644 tests/modules/gke_nodepool/examples/external-sa.yaml delete mode 100644 tests/modules/gke_nodepool/fixture/main.tf delete mode 100644 tests/modules/gke_nodepool/fixture/variables.tf delete mode 100644 tests/modules/gke_nodepool/test_plan.py diff --git a/modules/gke-nodepool/README.md b/modules/gke-nodepool/README.md index 50e9d08c..9b42c9b9 100644 --- a/modules/gke-nodepool/README.md +++ b/modules/gke-nodepool/README.md @@ -16,7 +16,7 @@ module "cluster-1-nodepool-1" { location = "europe-west1-b" name = "nodepool-1" } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=basic.yaml ``` ### Internally managed service account @@ -27,22 +27,11 @@ If you create a new service account, its resource and email (in both plain and I #### GCE default service account -To use the GCE default service account, you can ignore the variable which is equivalent to `{ create = null, email = null }`. - -```hcl -module "cluster-1-nodepool-1" { - source = "./fabric/modules/gke-nodepool" - project_id = "myproject" - cluster_name = "cluster-1" - location = "europe-west1-b" - name = "nodepool-1" -} -# tftest modules=1 resources=1 -``` +To use the GCE default service account, you can ignore the variable which is equivalent to `{ create = null, email = null }`. This is what the first example of this document does. #### Externally defined service account -To use an existing service account, pass in just the `email` attribute. +To use an existing service account, pass in just the `email` attribute. If you do this, will most likely want to use the `cloud-platform` scope. ```hcl module "cluster-1-nodepool-1" { @@ -52,10 +41,11 @@ module "cluster-1-nodepool-1" { location = "europe-west1-b" name = "nodepool-1" service_account = { - email = "foo-bar@myproject.iam.gserviceaccount.com" + email = "foo-bar@myproject.iam.gserviceaccount.com" + oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"] } } -# tftest modules=1 resources=1 +# tftest modules=1 resources=1 inventory=external-sa.yaml ``` #### Auto-created service account @@ -70,13 +60,50 @@ module "cluster-1-nodepool-1" { location = "europe-west1-b" name = "nodepool-1" service_account = { - create = true - # optional - email = "spam-eggs" + create = true + email = "spam-eggs" # optional + oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"] } } -# tftest modules=1 resources=2 +# tftest modules=1 resources=2 inventory=create-sa.yaml ``` +### Node & node pool configuration + +```hcl +module "cluster-1-nodepool-1" { + source = "./fabric/modules/gke-nodepool" + project_id = "myproject" + cluster_name = "cluster-1" + location = "europe-west1-b" + name = "nodepool-1" + labels = { environment = "dev" } + service_account = { + create = true + email = "nodepool-1" # optional + oauth_scopes = ["https://www.googleapis.com/auth/cloud-platform"] + } + node_config = { + machine_type = "n2-standard-2" + disk_size_gb = 50 + disk_type = "pd-ssd" + ephemeral_ssd_count = 1 + gvnic = true + spot = true + } + nodepool_config = { + autoscaling = { + max_node_count = 10 + min_node_count = 1 + } + management = { + auto_repair = true + auto_upgrade = false + } + } +} +# tftest modules=1 resources=2 inventory=config.yaml +``` + ## Variables diff --git a/modules/gke-nodepool/variables.tf b/modules/gke-nodepool/variables.tf index e0d3e967..1166c34f 100644 --- a/modules/gke-nodepool/variables.tf +++ b/modules/gke-nodepool/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2023 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -165,8 +165,8 @@ variable "service_account" { description = "Nodepool service account. If this variable is set to null, the default GCE service account will be used. If set and email is null, a service account will be created. If scopes are null a default will be used." type = object({ create = optional(bool, false) - email = optional(string, null) - oauth_scopes = optional(list(string), null) + email = optional(string) + oauth_scopes = optional(list(string)) }) default = {} nullable = false diff --git a/tests/modules/gke_nodepool/examples/basic.yaml b/tests/modules/gke_nodepool/examples/basic.yaml new file mode 100644 index 00000000..010b98cd --- /dev/null +++ b/tests/modules/gke_nodepool/examples/basic.yaml @@ -0,0 +1,23 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.cluster-1-nodepool-1.google_container_node_pool.nodepool: + cluster: cluster-1 + location: europe-west1-b + name: nodepool-1 + project: myproject + +counts: + google_container_node_pool: 1 diff --git a/tests/modules/gke_nodepool/examples/config.yaml b/tests/modules/gke_nodepool/examples/config.yaml new file mode 100644 index 00000000..858e5ca5 --- /dev/null +++ b/tests/modules/gke_nodepool/examples/config.yaml @@ -0,0 +1,59 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.cluster-1-nodepool-1.google_container_node_pool.nodepool: + autoscaling: + - max_node_count: 10 + min_node_count: 1 + total_max_node_count: null + total_min_node_count: null + cluster: cluster-1 + initial_node_count: 1 + location: europe-west1-b + management: + - auto_repair: true + auto_upgrade: false + name: nodepool-1 + node_config: + - boot_disk_kms_key: null + disk_size_gb: 50 + disk_type: pd-ssd + ephemeral_storage_config: + - local_ssd_count: 1 + gcfs_config: [] + gvnic: [] + kubelet_config: [] + labels: + environment: dev + linux_node_config: [] + logging_variant: DEFAULT + machine_type: n2-standard-2 + node_group: null + oauth_scopes: + - https://www.googleapis.com/auth/cloud-platform + preemptible: false + reservation_affinity: [] + resource_labels: null + sandbox_config: [] + spot: true + tags: null + taint: [] + placement_policy: [] + project: myproject + module.cluster-1-nodepool-1.google_service_account.service_account[0]: {} + +counts: + google_container_node_pool: 1 + google_service_account: 1 diff --git a/tests/modules/gke_nodepool/examples/create-sa.yaml b/tests/modules/gke_nodepool/examples/create-sa.yaml new file mode 100644 index 00000000..df1f2f70 --- /dev/null +++ b/tests/modules/gke_nodepool/examples/create-sa.yaml @@ -0,0 +1,52 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.cluster-1-nodepool-1.google_container_node_pool.nodepool: + cluster: cluster-1 + location: europe-west1-b + name: nodepool-1 + node_config: + - boot_disk_kms_key: null + disk_type: pd-balanced + ephemeral_storage_config: [] + gcfs_config: [] + gvnic: [] + kubelet_config: [] + linux_node_config: [] + logging_variant: DEFAULT + node_group: null + oauth_scopes: + - https://www.googleapis.com/auth/cloud-platform + preemptible: false + reservation_affinity: [] + resource_labels: null + sandbox_config: [] + spot: false + tags: null + taint: [] + placement_policy: [] + project: myproject + timeouts: null + module.cluster-1-nodepool-1.google_service_account.service_account[0]: + account_id: spam-eggs + description: null + disabled: false + display_name: Terraform GKE cluster-1 nodepool-1. + project: myproject + timeouts: null + +counts: + google_container_node_pool: 1 + google_service_account: 1 diff --git a/tests/modules/gke_nodepool/examples/external-sa.yaml b/tests/modules/gke_nodepool/examples/external-sa.yaml new file mode 100644 index 00000000..05959321 --- /dev/null +++ b/tests/modules/gke_nodepool/examples/external-sa.yaml @@ -0,0 +1,43 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.cluster-1-nodepool-1.google_container_node_pool.nodepool: + cluster: cluster-1 + location: europe-west1-b + name: nodepool-1 + node_config: + - boot_disk_kms_key: null + disk_type: pd-balanced + ephemeral_storage_config: [] + gcfs_config: [] + gvnic: [] + kubelet_config: [] + linux_node_config: [] + logging_variant: DEFAULT + node_group: null + oauth_scopes: + - https://www.googleapis.com/auth/cloud-platform + preemptible: false + reservation_affinity: [] + resource_labels: null + sandbox_config: [] + service_account: foo-bar@myproject.iam.gserviceaccount.com + spot: false + tags: null + taint: [] + project: myproject + +counts: + google_container_node_pool: 1 diff --git a/tests/modules/gke_nodepool/fixture/main.tf b/tests/modules/gke_nodepool/fixture/main.tf deleted file mode 100644 index 4ee27482..00000000 --- a/tests/modules/gke_nodepool/fixture/main.tf +++ /dev/null @@ -1,45 +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 "google_service_account" "test" { - project = "my-project" - account_id = "gke-nodepool-test" - display_name = "Test Service Account" -} - -module "test" { - source = "../../../../modules/gke-nodepool" - project_id = "my-project" - cluster_name = "cluster-1" - location = "europe-west1-b" - name = "nodepool-1" - gke_version = var.gke_version - labels = var.labels - max_pods_per_node = var.max_pods_per_node - node_config = var.node_config - node_count = var.node_count - node_locations = var.node_locations - nodepool_config = var.nodepool_config - pod_range = var.pod_range - reservation_affinity = var.reservation_affinity - service_account = { - create = var.service_account_create - email = google_service_account.test.email - } - sole_tenant_nodegroup = var.sole_tenant_nodegroup - tags = var.tags - taints = var.taints -} diff --git a/tests/modules/gke_nodepool/fixture/variables.tf b/tests/modules/gke_nodepool/fixture/variables.tf deleted file mode 100644 index 18376ec5..00000000 --- a/tests/modules/gke_nodepool/fixture/variables.tf +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -variable "gke_version" { - type = string - default = null -} - -variable "labels" { - type = map(string) - default = {} - nullable = false -} - -variable "max_pods_per_node" { - type = number - default = null -} - -variable "node_config" { - type = any - default = { - disk_type = "pd-balanced" - } -} - -variable "node_count" { - type = any - default = { - initial = 1 - } - nullable = false -} - -variable "node_locations" { - type = list(string) - default = null -} - -variable "nodepool_config" { - type = any - default = null -} - -variable "pod_range" { - type = any - default = null -} - -variable "reservation_affinity" { - type = any - default = null -} - -variable "service_account_create" { - type = bool - default = false -} - -variable "sole_tenant_nodegroup" { - type = string - default = null -} - -variable "tags" { - type = list(string) - default = null -} - -variable "taints" { - type = any - default = null -} diff --git a/tests/modules/gke_nodepool/test_plan.py b/tests/modules/gke_nodepool/test_plan.py deleted file mode 100644 index 75d1cc14..00000000 --- a/tests/modules/gke_nodepool/test_plan.py +++ /dev/null @@ -1,67 +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_defaults(plan_runner): - "Test resources created with variable defaults." - _, resources = plan_runner() - assert len(resources) == 1 - assert resources[0]['values']['autoscaling'] == [] - - -def test_service_account(plan_runner): - _, resources = plan_runner() - assert len(resources) == 1 - _, resources = plan_runner(service_account_create='true') - assert len(resources) == 2 - assert 'google_service_account' in [r['type'] for r in resources] - - -def test_nodepool_config(plan_runner): - nodepool_config = '''{ - autoscaling = { use_total_nodes = true, max_node_count = 3} - management = {} - upgrade_settings = { max_surge = 3, max_unavailable = 3 } - }''' - _, resources = plan_runner(nodepool_config=nodepool_config) - assert resources[0]['values']['autoscaling'] == [{ - 'location_policy': None, - 'max_node_count': None, - 'min_node_count': None, - 'total_max_node_count': 3, - 'total_min_node_count': None - }] - nodepool_config = '{ autoscaling = { max_node_count = 3} }' - _, resources = plan_runner(nodepool_config=nodepool_config) - assert resources[0]['values']['autoscaling'] == [{ - 'location_policy': None, - 'max_node_count': 3, - 'min_node_count': None, - 'total_max_node_count': None, - 'total_min_node_count': None - }] - - -def test_node_config(plan_runner): - node_config = '''{ - gcfs = true - metadata = { foo = "bar" } - }''' - _, resources = plan_runner(node_config=node_config) - values = resources[0]['values']['node_config'][0] - assert values['gcfs_config'] == [{'enabled': True}] - assert values['metadata'] == { - 'disable-legacy-endpoints': 'true', - 'foo': 'bar' - } From f014ee57948f5651ee5d3dbb65925860952dc757 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 18:35:00 +0100 Subject: [PATCH 17/34] Fix linting --- modules/dns/README.md | 3 --- modules/gke-nodepool/README.md | 3 +-- modules/net-vpc/README.md | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/dns/README.md b/modules/dns/README.md index 4803a13c..a405ff75 100644 --- a/modules/dns/README.md +++ b/modules/dns/README.md @@ -101,7 +101,6 @@ module "private-dns" { } # tftest modules=1 resources=1 inventory=reverse-zone.yaml ``` - ### Public Zone @@ -120,8 +119,6 @@ module "public-dns" { ``` - - ## Variables | name | description | type | required | default | diff --git a/modules/gke-nodepool/README.md b/modules/gke-nodepool/README.md index 9b42c9b9..2f632c9c 100644 --- a/modules/gke-nodepool/README.md +++ b/modules/gke-nodepool/README.md @@ -103,7 +103,6 @@ module "cluster-1-nodepool-1" { } # tftest modules=1 resources=2 inventory=config.yaml ``` - ## Variables @@ -124,7 +123,7 @@ module "cluster-1-nodepool-1" { | [nodepool_config](variables.tf#L115) | Nodepool-level configuration. | object({…}) | | null | | [pod_range](variables.tf#L137) | Pod secondary range configuration. | object({…}) | | null | | [reservation_affinity](variables.tf#L154) | Configuration of the desired reservation which instances could take capacity from. | object({…}) | | null | -| [service_account](variables.tf#L164) | Nodepool service account. If this variable is set to null, the default GCE service account will be used. If set and email is null, a service account will be created. If scopes are null a default will be used. | object({…}) | | {} | +| [service_account](variables.tf#L164) | Nodepool service account. If this variable is set to null, the default GCE service account will be used. If set and email is null, a service account will be created. If scopes are null a default will be used. | object({…}) | | {} | | [sole_tenant_nodegroup](variables.tf#L175) | Sole tenant node group. | string | | null | | [tags](variables.tf#L181) | Network tags applied to nodes. | list(string) | | null | | [taints](variables.tf#L187) | Kubernetes taints applied to all nodes. | list(object({…})) | | null | diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 002c738d..dbd85502 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -341,7 +341,6 @@ flow_logs: # enable, set to empty map to use defaults metadata: "INCLUDE_ALL_METADATA" filter_expression: null ``` - ### Custom Routes From 2aee1dd1c70387cb480d58dcb1d002a6d9a1eafb Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 18:43:13 +0100 Subject: [PATCH 18/34] Fix broken link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18a42f59..d34ad2d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -754,7 +754,7 @@ def test_name(plan_summary, tfvars_to_yaml, tmp_path): assert s.values[address]['project'] == 'my-project' ``` -For more examples on how to write python tests, the tests for [`organization`](./tests/modules/organization/test_plan_org_policies.py) and [`net-vpc`](./tests/modules/net_vpc/test_routes.py) modules. +For more examples on how to write python tests, check the tests for the [`organization`](./tests/modules/organization/test_plan_org_policies.py) module. #### Testing documentation examples From 13352779acfb5333972f1e57353b021aabaa8ec6 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 19 Jan 2023 18:55:30 +0100 Subject: [PATCH 19/34] Fix nodepool test --- tests/modules/gke_nodepool/examples/config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/modules/gke_nodepool/examples/config.yaml b/tests/modules/gke_nodepool/examples/config.yaml index 858e5ca5..fc1682a8 100644 --- a/tests/modules/gke_nodepool/examples/config.yaml +++ b/tests/modules/gke_nodepool/examples/config.yaml @@ -15,7 +15,8 @@ values: module.cluster-1-nodepool-1.google_container_node_pool.nodepool: autoscaling: - - max_node_count: 10 + - location_policy: null + max_node_count: 10 min_node_count: 1 total_max_node_count: null total_min_node_count: null From 8945165bc3e35fff827e78acaa81d3a225620249 Mon Sep 17 00:00:00 2001 From: Miren Esnaola Date: Fri, 13 Jan 2023 12:45:18 +0100 Subject: [PATCH 20/34] Improvements in apigee hybrid-gke: now using workload identity and GLB --- blueprints/apigee/hybrid-gke/README.md | 16 +- blueprints/apigee/hybrid-gke/ansible.tf | 13 +- .../tasks/k8s_service_accounts.yaml | 28 ++ .../roles/apigee-hybrid/tasks/main.yaml | 284 +++++++++++++++--- .../apigee-hybrid/templates/overrides.yaml.j2 | 45 +-- blueprints/apigee/hybrid-gke/apigee.tf | 67 ++++- blueprints/apigee/hybrid-gke/diagram.png | Bin 35836 -> 35869 bytes blueprints/apigee/hybrid-gke/glb.tf | 25 ++ blueprints/apigee/hybrid-gke/main.tf | 9 +- blueprints/apigee/hybrid-gke/mgmt.tf | 10 +- blueprints/apigee/hybrid-gke/outputs.tf | 20 ++ .../templates/deploy-apiproxy.sh.tpl | 7 +- tests/blueprints/apigee/hybrid-gke/basic.yaml | 4 +- 13 files changed, 422 insertions(+), 106 deletions(-) create mode 100644 blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml create mode 100644 blueprints/apigee/hybrid-gke/glb.tf create mode 100644 blueprints/apigee/hybrid-gke/outputs.tf diff --git a/blueprints/apigee/hybrid-gke/README.md b/blueprints/apigee/hybrid-gke/README.md index cee4aec1..30589716 100644 --- a/blueprints/apigee/hybrid-gke/README.md +++ b/blueprints/apigee/hybrid-gke/README.md @@ -25,20 +25,20 @@ The diagram below depicts the architecture. terraform apply ``` +Create an A record in your DNS registrar to point the environment group hostname to the public IP address returned after the terraform configuration was applied. You might need to wait some time until the certificate is provisioned. + ## Testing the blueprint 2. Deploy an api proxy ``` - ./deploy-apiproxy.sh + ./deploy-apiproxy.sh apis-test ``` -3. In the console check the IP address that has been allocated to the Apigee ingress gateway and send some traffic to the deployed API proxy. +3. Send a request ``` - curl -k -v -H "Host:HOSTNAME" \ - --resolve HOSTNAME:443:IP_ADDRESS \ - https://HOSTNAME/httpbin/headers + curl -v https://HOSTNAME/httpbin/headers ``` @@ -56,4 +56,10 @@ The diagram below depicts the architecture. | [region](variables.tf#L84) | Region. | string | | "europe-west1" | | [zone](variables.tf#L90) | Zone. | string | | "europe-west1-c" | +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [ip_address](outputs.tf#L17) | GLB IP address. | | + diff --git a/blueprints/apigee/hybrid-gke/ansible.tf b/blueprints/apigee/hybrid-gke/ansible.tf index e5a491a3..b7694ab1 100644 --- a/blueprints/apigee/hybrid-gke/ansible.tf +++ b/blueprints/apigee/hybrid-gke/ansible.tf @@ -18,12 +18,13 @@ resource "local_file" "vars_file" { content = yamlencode({ - cluster = module.cluster.name - region = var.region - project_id = module.project.project_id - envgroup = local.envgroup - env = local.environment - hostname = var.hostname + cluster = module.cluster.name + region = var.region + project_id = module.project.project_id + envgroups = local.envgroups + environments = local.environments + service_accounts = local.google_sas + ingress_ip_name = local.ingress_ip_name }) filename = "${path.module}/ansible/vars/vars.yaml" file_permission = "0666" diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml new file mode 100644 index 00000000..e74ca159 --- /dev/null +++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/k8s_service_accounts.yaml @@ -0,0 +1,28 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- name: Create and annotate k8s service account + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: ServiceAccount + metadata: + name: "{{ k8s_service_account }}" + namespace: apigee + annotations: + iam.gke.io/gcp-service-account: "{{ google_service_account }}@{{ project_id }}.iam.gserviceaccount.com" + with_items: "{{ k8s_service_accounts }}" + loop_control: + loop_var: k8s_service_account \ No newline at end of file diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml index 4b72039b..0907846f 100644 --- a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml +++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/tasks/main.yaml @@ -1,11 +1,11 @@ # Copyright 2023 Google LLC -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,18 +19,27 @@ --project {{ project_id }} \ --internal-ip -- name: Install cert-manager - shell: > - kubectl apply \ - --validate=false \ - -f https://github.com/jetstack/cert-manager/releases/download/v1.7.2/cert-manager.yaml +- name: Download cert-manager + uri: + url: https://github.com/jetstack/cert-manager/releases/download/v1.7.2/cert-manager.yaml + dest: ~/cert-manager.yaml -- name: Wait until pods are ready in cert-manager namespace - shell: > - kubectl wait --for=condition=ready pods \ - -l app.kubernetes.io/instance=cert-manager \ - -n cert-manager \ - --timeout=90s +- name: Apply metrics-server manifest to the cluster. + kubernetes.core.k8s: + state: present + src: ~/cert-manager.yaml + +- name: + kubernetes.core.k8s_info: + kind: Pod + wait: yes + label_selectors: + - "app.kubernetes.io/instance=cert-manager" + namespace: cert-manager + wait_timeout: 90 + wait_condition: + type: Ready + status: True - name: Fetch apigeectl version uri: @@ -48,7 +57,7 @@ unarchive: src: "~/apigeectl.tar.gz" dest: "~" - remote_src: yes + remote_src: yes - name: Move apigeectl folder shell: > @@ -66,25 +75,69 @@ file: src: ~/apigeectl/{{ item }} dest: "~/hybrid-files/{{ item }}" - state: link + state: link with_items: - tools - config - templates - - plugins + - plugins -- name: Create service accounts - shell: > - ~/hybrid-files/tools/create-service-account -i {{ project_id }} -e non-prod -d ~/hybrid-files/service-accounts +- name: Create apigee namespace + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: Namespace + metadata: + name: apigee -- name: Create certificates +- name: Create k8s service accounts + include_tasks: k8s_service_accounts.yaml + vars: + google_service_account: "{{ item.key }}" + k8s_service_accounts: "{{ item.value }}" + with_dict: "{{ service_accounts }}" + +- name: Set hostnames + set_fact: + hostnames: "{{ hostnames | default([]) + item.value }}" + with_dict: "{{ envgroups }}" + +- name: Create certificate and private key shell: > openssl req \ -nodes \ -new \ -x509 \ - -keyout ~/hybrid-files/certs/{{ envgroup }}.key \ - -out ~/hybrid-files/certs/{{ envgroup }}.cert -subj '/CN='{{ hostname }}'' -days 3650 + -keyout ~/hybrid-files/certs/server.key \ + -out ~/hybrid-files/certs/server.crt \ + -subj "/CN=apigee.com' \ + -addext "subjectAltName={{ hostnames | map('regex_replace', '^', 'DNS:') | join(',') }}"" + -days 3650 + +- name: Read certificate + slurp: + src: ~/hybrid-files/certs/server.crt + register: certificate_output + +- name: Read private ket + slurp: + src: ~/hybrid-files/certs/server.key + register: privatekey_output + +- name: Create secret + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: Secret + metadata: + name: tls-hybrid-ingress + namespace: apigee + type: kubernetes.io/tls + data: + tls.crt: "{{ certificate_output.content }}" + tls.key: "{{ privatekey_output.content }}" - name: Create overrides.yaml template: @@ -96,48 +149,185 @@ curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type:application/json" \ "https://apigee.googleapis.com/v1/organizations/{{ project_id }}:setSyncAuthorization" \ - -d '{"identities":["'"serviceAccount:apigee-non-prod@{{ project_id }}.iam.gserviceaccount.com"'"]}' + -d '{"identities":["'"serviceAccount:apigee-synchronizer@{{ project_id }}.iam.gserviceaccount.com"'"]}' - name: Dry-run (init) shell: > - ~/apigeectl/apigeectl init -f overrides/overrides.yaml --dry-run=client + ~/apigeectl/apigeectl init -f overrides/overrides.yaml --dry-run=client args: chdir: ~/hybrid-files - name: Install the Apigee deployment services Apigee Deployment Controller and Apigee Admission Webhook. shell: > - ~/apigeectl/apigeectl init -f overrides/overrides.yaml + ~/apigeectl/apigeectl init -f overrides/overrides.yaml args: - chdir: ~/hybrid-files + chdir: ~/hybrid-files -- name: Wait until pods are ready in apigee-system namespace - shell: > - kubectl wait --for=condition=ready pods \ - -l app=apigee-controller \ - -n apigee-system \ - --timeout=300s +- name: Wait for apigee-controller pod to be ready + kubernetes.core.k8s_info: + kind: Pod + wait: yes + label_selectors: + - "app=apigee-controller" + namespace: apigee-system + wait_timeout: 600 + wait_condition: + type: Ready + status: True -- name: Wait until pods are ready in apigee namespace - shell: > - kubectl wait --for=condition=ready pods \ - -l app=apigee-ingressgateway-manager \ - -n apigee \ - --timeout=300s +- name: Wait for apigee-selfsigned-issuer issuer to be ready + kubernetes.core.k8s_info: + kind: Issuer + wait: yes + name: apigee-selfsigned-issuer + namespace: apigee-system + wait_timeout: 600 + wait_condition: + type: Ready + status: True + +- name: Wait for apigee-serving-cert certificate to be ready + kubernetes.core.k8s_info: + kind: Certificate + wait: yes + name: apigee-serving-cert + namespace: apigee-system + wait_timeout: 600 + wait_condition: + type: Ready + status: True + +- name: Wait for apigee-resources-install job to be complete + kubernetes.core.k8s_info: + kind: Job + wait: yes + name: apigee-resources-install + namespace: apigee-system + wait_timeout: 360 + wait_condition: + type: Complete + status: True - name: Dry-run (apply) shell: > - ~/apigeectl/apigeectl apply -f overrides/overrides.yaml --dry-run=client + ~/apigeectl/apigeectl apply -f overrides/overrides.yaml --dry-run=client args: chdir: ~/hybrid-files - name: Install the Apigee runtime components shell: > - ~/apigeectl/apigeectl apply -f overrides/overrides.yaml + ~/apigeectl/apigeectl apply -f overrides/overrides.yaml args: - chdir: ~/hybrid-files + chdir: ~/hybrid-files -- name: Check status of the deployment - shell: > - while [ -n "$(kubectl get pods -n apigee | tail -n +2 | grep -v Running | grep -v Completed)" ]; do sleep 1; done - args: - chdir: ~/hybrid-files \ No newline at end of file +- name: Wait for apigee-runtime pod to be ready + kubernetes.core.k8s_info: + kind: Pod + wait: yes + label_selectors: + - "app=apigee-runtime" + namespace: apigee + wait_timeout: 360 + wait_condition: + type: Ready + status: True + +- name: + kubernetes.core.k8s: + state: present + definition: + apiVersion: apigee.cloud.google.com/v1alpha1 + kind: ApigeeRoute + metadata: + name: apigee-wildcard + namespace: apigee + spec: + hostnames: + - '*' + ports: + - number: 443 + protocol: HTTPS + tls: + credentialName: tls-hybrid-ingress + mode: SIMPLE + selector: + app: apigee-ingressgateway + enableNonSniClient: true + +- name: Create google-managed certificate + kubernetes.core.k8s: + state: present + definition: + apiVersion: networking.gke.io/v1 + kind: ManagedCertificate + metadata: + name: "apigee-cert-hybrid" + namespace: apigee + spec: + domains: "{{ hostnames }}" + +- name: Create backend config + kubernetes.core.k8s: + state: present + definition: + apiVersion: cloud.google.com/v1 + kind: BackendConfig + metadata: + name: apigee-ingress-backendconfig + namespace: apigee + spec: + healthCheck: + requestPath: /healthz/ready + port: 15021 + type: HTTP + logging: + enable: true + sampleRate: 0.5 + +- name: Create service + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: Service + metadata: + name: apigee-ingressgateway-hybrid + namespace: apigee + annotations: + cloud.google.com/backend-config: '{"default": "apigee-ingress-backendconfig"}' + cloud.google.com/neg: '{"ingress": true}' + cloud.google.com/app-protocols: '{"https":"HTTPS", "status-port": "HTTP"}' + labels: + app: apigee-ingressgateway-hybrid + spec: + ports: + - name: status-port + port: 15021 + targetPort: 15021 + - name: https + port: 443 + targetPort: 8443 + selector: + app: apigee-ingressgateway + ingress_name: ingress + type: ClusterIP + +- name: Create ingress + kubernetes.core.k8s: + state: present + definition: + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + annotations: + networking.gke.io/managed-certificates: "apigee-cert-hybrid" + kubernetes.io/ingress.global-static-ip-name: "{{ ingress_ip_name }}" + kubernetes.io/ingress.allow-http: "false" + name: xlb-apigee + namespace: apigee + spec: + defaultBackend: + service: + name: apigee-ingressgateway-hybrid + port: + number: 443 \ No newline at end of file diff --git a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 index 1c2c09ed..691cc6d5 100644 --- a/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 +++ b/blueprints/apigee/hybrid-gke/ansible/roles/apigee-hybrid/templates/overrides.yaml.j2 @@ -1,29 +1,26 @@ gcp: region: {{ region }} projectID: {{ project_id }} + workloadIdentityEnabled: true k8sCluster: name: {{ cluster }} - region: CLUSTER_LOCATION # Must be the closest Google Cloud region to your cluster. + region: {{ region }} # Must be the closest Google Cloud region to your cluster. org: {{ project_id }} -instanceID: "instance-1" +instanceID: "{{ cluster }}-{{ region }}" cassandra: hostNetwork: false - # Set to false for single region installations and multi-region installations - # with connectivity between pods in different clusters, for example GKE installations. - # Set to true for multi-region installations with no communication between - # pods in different clusters, for example GKE On-prem, GKE on AWS, Anthos on bare metal, - # AKS, EKS, and OpenShift installations. - # See Multi-region deployment: Prerequisites virtualhosts: - - name: {{ envgroup }} +{% for k in envgroups %} + - name: {{ k }} + sslSecret: tls-hybrid-ingress + additionalGateways: ["apigee-wildcard"] selector: app: apigee-ingressgateway - sslCertPath: ./certs/{{ envgroup }}.cert - sslKeyPath: ./certs/{{ envgroup }}.key +{% endfor %} ao: args: @@ -37,27 +34,9 @@ ingressGateways: replicaCountMax: 10 envs: - - name: {{ env }} - serviceAccountPaths: - synchronizer: ./service-accounts/{{ project_id }}-apigee-non-prod.json - udca: ./service-accounts/{{ project_id }}-apigee-non-prod.json - runtime: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -mart: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -connectAgent: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -metrics: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -udca: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json - -watcher: - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json +{% for k in environments %} + - name: {{ k }} +{% endfor %} logger: - enabled: true - serviceAccountPath: ./service-accounts/{{ project_id }}-apigee-non-prod.json + enabled: false diff --git a/blueprints/apigee/hybrid-gke/apigee.tf b/blueprints/apigee/hybrid-gke/apigee.tf index e3dc6b2e..b92592aa 100644 --- a/blueprints/apigee/hybrid-gke/apigee.tf +++ b/blueprints/apigee/hybrid-gke/apigee.tf @@ -15,8 +15,51 @@ */ locals { - envgroup = "test" - environment = "apis-test" + envgroups = { + test = [var.hostname] + } + environments = { + apis-test = { + envgroups = ["test"] + } + } + org_short_name = (length(module.project.project_id) < 16 ? + module.project.project_id : + substr(module.project.project_id, 0, 15)) + org_hash = format("%s-%s", local.org_short_name, substr(sha256(module.project.project_id), 0, 7)) + org_env_hashes = { + for k, v in local.environments : + k => format("%s-%s-%s", local.org_short_name, length(k) < 16 ? k : substr(k, 0, 15), substr(sha256("${module.project.project_id}:${k}"), 0, 7)) + } + google_sas = { + apigee-metrics = [ + "apigee-metrics-sa" + ] + apigee-cassandra = [ + "apigee-cassandra-schema-setup-${local.org_hash}-sa", + "apigee-cassandra-user-setup-${local.org_hash}-sa" + ] + apigee-mart = [ + "apigee-mart-${local.org_hash}-sa", + "apigee-connect-agent-${local.org_hash}-sa" + ] + apigee-watcher = [ + "apigee-watcher-${local.org_hash}-sa" + ] + apigee-udca = concat([ + "apigee-udca-${local.org_hash}-sa" + ], + [for k, v in local.org_env_hashes : + "apigee-udca-${local.org_env_hashes[k]}-sa" + ]) + apigee-synchronizer = [ + for k, v in local.org_env_hashes : + "apigee-synchronizer-${local.org_env_hashes[k]}-sa" + ] + apigee-runtime = [for k, v in local.org_env_hashes : + "apigee-runtime-${local.org_env_hashes[k]}-sa" + ] + } } module "apigee" { @@ -26,20 +69,24 @@ module "apigee" { analytics_region = var.region runtime_type = "HYBRID" } - envgroups = { - (local.envgroup) = [var.hostname] - } - environments = { - (local.environment) = { - envgroups = [local.envgroup] - } + envgroups = local.envgroups + environments = local.environments +} + +module "sas" { + for_each = local.google_sas + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = each.key + # authoritative roles granted *on* the service accounts to other identities + iam = { + "roles/iam.workloadIdentityUser" = [for v in each.value : "serviceAccount:${module.project.project_id}.svc.id.goog[apigee/${v}]"] } } resource "local_file" "deploy_apiproxy_file" { content = templatefile("${path.module}/templates/deploy-apiproxy.sh.tpl", { org = module.project.project_id - env = local.environment }) filename = "${path.module}/deploy-apiproxy.sh" file_permission = "0777" diff --git a/blueprints/apigee/hybrid-gke/diagram.png b/blueprints/apigee/hybrid-gke/diagram.png index 6d5c2d6bc9f38f962c7ed2967981b0f81a485cd7..57e07ca307ee61b9ce335db27d800b68b9b995ff 100644 GIT binary patch literal 35869 zcmcG$cT`hZ)HY5>P>PE5qN9Q!MWuHX1?e56t4JplDUp_71Er4Adyo!Nq<0hq1Ox)1 z1`rV`p_kAJ{7!TR8E4-2Tfg$1efIXBWJDJ{ zDUi3HAefbC`Y=_deb3$8^$0QQIa;GieYNF_mD8-lEoBd=%BDP{cn#MaTABIULi`s;LP9G1r}Ob+Huz6JV>vlu{Ff0TB4+&O zSNfwvR0vK_Pb;LlK%r1;>*=pwzkdJzYz*9=zya?5>d4B@ZYLDi-QC^U*}1W?@%S-e zq;hF}J>13R!xE5M=Ls;GGkI3h^wgA{y?qd0M07L_HMO?*12o)Jc6N4kwYP(V0|c`0 z{X5A;DuT0AV1xa{l&wpRMn^{W_Hnzf1$Ylmp5x}@%gfJCiir`ElKRd8CQ&~IM0VE3 z$*BPRy?r}xT>o+Sl%<=mFRs6z9!>{VA%fS4ljP*&x?Lk(dDYDB9uP>LdgeqJDfl%h zekbi27#PCC!#QMO-&wXL=tdwA2!6Xx5)!=k#Os$M1PU#$t;KN~I6BXqix_2LVL5uy z(lWiSE~#K5p8N9U_=JS-VaI@q5dRn$KlMetK8&D&HUX6Y3b)JW?%@#{9L&nfx~xb- zKrsCn=yXMCP*4z%+w51|upo2DRyq$@92FP?{QXm>Ukswr%&e?J2Xa-fU!U5^($5VG z3xk(&5D?rZ|1l>YAK&=+ct}VH_FZvtxRsQ&G{(Z!b*;0Lx_+~Zjg|HL^z>RWFbFBU zL6p0Cc_~+_a>&NoiSh8vqN4p47Zygx#;jQ|7z{Bnaf$TRt5?OuViFTg&CNALg8~Ch zOig!pc6h@mzye9|6G{Sc@W%1I^9v0N8?X_gYhhwyl1Sv|Zc z`?H`&h&WI7*4raCxHlm{rVC@)@O^Bfx$pe?-rr7l$7dbRAi)<@!a|j z5~;a34ivyAY!TkEjnwAk@W8+V9{f-q3!uPwReOndE{lv{O>&NS0ZV$oU}mPK9bH5O ze!O@ILW7B_GBW&=!8nF^&0ZWF8p4~n-2G!Ve|{u=|I`Jj10nwYnB@avVy^vjGo~A_xbtY1304?3yIL}C+5intk!UN{m&tMn`YS5x*}#i?|3vg3|KbX}<|gIZ+{@WuV%e$80Fh zBCem>+adWXCMhWiM6pGGbdFsTEHEM<%}#J*JvaGxuqcv7lSzTRSS!r9RG#t@6<(DB{URL&b zGwxjg;|c%B$TK^iI5C>$qeH>$mfO9|Ay#nsn2ki<;>5&SH7>Y$X2vE`xv2~jgN@@` zBK31RD$8!W(sIg~*RNk!R8$le7S7Jvt#yEy1p)yjYPu_#>%s*RQqsui=wff%cq`c6 ziX!CmdV6)1$I)o? zt5>grgHNF0_wWC4@#1rlhpw&|%@S90a}KtIP}Q(>c|KlVWOJ%w7!ME6ppKBJDC&4n zNJx4iKQC`^U|`^j7nk_?Me51^Dd&V?vXb(0(K9@xmo4X-o5|L5yRnUFwKX-D5SY|% zH+6}#XZNqc+y{{DW%s9kB?;!Z!4O^AYg^*bhIh)FD!GvCq8rbJFnS;01j=@}S+ zSCGGPd#3%_Vz!Jrh;yvkfGS@^M)k8DV8Wg5cC!0hDKV zcbA`^zr@(Z<*M|b?2BNYI^+;5a_LL0MSYK@ySrX)pSE4GAnM}9i*!i3>XnrhxA30W zKVs~}fFzTigccVT%5`;irizM)^b{k*f#-$8nmBc*OIRco)e8d5PSnD@=-Yh>-ZLLH zOkqnEmE*Fz5Mg2Al#~?UDtW89?{LRqtV~VW)@@8o61uTr2ZQTpKv<-EOILuGdR}kc z{JY+dQ^ZkGQPeavENTql$Rsx>Rn=gHR6}FqU_y{fv0Bi+h#}gr;6~Y$MBV_IR~0Gr_ZFh z{^1Eurw@&cZy@c5zPRK33Et9HO| zyc-Q~&RmOrx2JJX-dqpU(vm8wu&R5DWiCn9&onmC4be~y4GvXPR|DYVOG`nln3xy{ z(ET?3^V=#yU-j8xa>Rbb=l=FmsB$I`=sHJW`N2XmymJ!1Q}2iy5-5S=;P&;q(Bb-> zpauAP%Vjurw-mlFDl22Vvv$l^A^hGNv|i_1e3qpik2}Ov-p%Am%jGc!cC#)9AX zX=&f6`h#!kCi^c+qWYh_d0krSzF+5rn97Jtj!9rN9cc$%|=C3p5){ zxmNUl(GT`5;H^Xq$u-J!4A&(C685|CWi5(JcwGD$kT*s~k1$!p`8FT6s z`@*-4YgaDZceZ&3_k#$o z_vM>qt1IgvG&>FE)s;(r83KbcK3jL|hJX)q&<0@+_VO;k zeZD@M!~}~ZhZ^qtbo?%~fEPBfzPTZ_pZoOd!QR>VdJ%nGPld_7-dyfHHE%}$>5b8~ zjf1nxendMoPfon+YtGfUq4P4s>H%(f%k#eS&i7vIhYyo3B@(6vP$xb2h1nc`@Dz?` zkh(_FwK(|}QetS8nl#reGP#br68W+c{=I%`# zc&?6*$>!}2CoAH{#BC3j!mMg&pqzK)Sxz+PT%R!KWW0p1-IG{Hcnl(N&6pb8)2GcD zQOQZwP0d_6x_0}E$*8$?`?)!~5p4G=$vNS2{ajN+!}RN;7IpJ+B3XU|{r%C=1|p*6 zSx}4_y5$;Tb8~ZGKy)T%ZfCor>3TC4nBSQ z1pH1IhiuOSYiq7y0>A1fhq%K0o)~&jf_*C-6&%c^-G=i>PTM3;_84Nqnhwr6h?$s(@)#n(b3Uk9cwi`)l{$4 z=@~zU8dg+u5W2{n?tDwHBmR5I$M4wHFg*aQOJpax)9o~z;8q*M&pt*xd2@J>Rz01(gT0(?eMmx2l4>cX6xQc{T`R1278Vf@_L zp`onQ^{I1VcCpV+K%<7{#0kBD{`mMLYCU~3rEE1plwdLG^NX$7OipXZ!B`x)@7g9t zR-QghZv?E+!^7J{l5e_gvb<~?ySl<~E;_V21!2L#y1Kg0osoCpKS2qpnx4T8pR#cRHMh$m~o_%oFIi~H=UFT1-mm+$~YEyG7 zu#HX}(j6U_2L}gDjLkEpxy{10ds>>gE@g8k2KXypx`WvgkgDM&uF5Qsy|yDv3iY)( zEg68DTVj8Ht4W)x*r2Gi6vxReeC7M?a>d6} zvttY;io@J4&Dwh8sT&y?!Qrwvm)-5{=l=dZ^1&HFqC`YQyzuq21S1r`wDn<{mut`d zm@oBAH{7~66jo>9J){`Gr857)kcm|%Fld)mBAm0P}PyD_{S4rOFoLk|l$!^YR@ zk0rz>*E`rd9puDSjqNR8STa2sKQ@Bq;ek0ZA^9K+VM$fm)u`f+b?R;|-WV1}?TvTZ zo0B}D3lKib{9%=3C!8D^J#ytG_wnOF2TKC=bBh>@HLPb&?`-|v0Zc7qKhJOD(PVsF z(yioU@tIw5W+#Z^0OqRB^aEL`lB%lESlXaow(bBK7L}>t`tTtUF|oa!U9dS(oRE+Z z2|0OYMg|an4~Lhk>SvHefV5JLS#@CSV7ZZoH-qpi^&waZk4)YE6y9IoPPFrAD&6H^ zXRKGP(qzzbFZyJ1!L!f;Vb(jOLTV)?WwM@&3T#rM^@Rr)g+ihu-#S6&9*vK$OPs@4 zSPHLOw(ior{Q%tk97}vXd~e?U#=|=gg2Uj2UbZi#?O^w=Uf>N}Eh%}sx2xC{sM0nl zUsh@qXwdHG=mH0;q{4cz%t5zaRLwNY*# zOoliy=9;AFfkFK~ZE!jvvc$c!lXq5_KBc`aECBn0f1`>%kf|Lom!QYfxHDBK2K z_Q3;DK!#Ck;4dTdtA2hMdGx9ETDcYu8m9%3=MS0-N1=BI1x*x+JWcuwJvA>E*wR?! zo~S=oRdZq|?8XUOHT`XeqN1X>*tLxoE`G4v6Q_0T?dRx(a&zPI7tPvqC}*0dqQ2C@ zW>o^`Z$;YKdwbh?ab6WqT+GRO+u7EU%A22`Jv=y)Mqb1k78t_8$hw26^uBZRb5GAT z=?C{825JvqLsWZt5kF~!@6On|L-2f<^u+u3wCRPF2{up7f-!WDX6EOmNChu;FD@>k zcf2&5%q{i!Zk?%qW?^r?+pQCwP(y?cXnWaKm61OlUBB((qNS&)yV@8&jcu?=+6eQ- z`R61O?X1T?;HW=4yAQ0}v=;X00oDn&TT#epjS$d!`9(+Wcwr6M&J;a8{b$)yfXnC) zahaj`h>ZjW#}$3xQ@H5F!hC>=O-Uw;l29DTR2aw{Vl?klYk4sLu8sn_|$eA@je9 zRIs^;H9zRsOeU5PZfwH!4Q`R7i?g8`+XV^`m#&I0tmq63ZrxlHMk=5*GEzsSYWHSh zu*x8hFjAEz-&rYU&|T}DJazxlUD53MKPb&~%>2biP8vgCP8x@{1I(Cr3}T zPIHAEynDAXHkY^81=HE9KM?RdScSLk>a1N|`1b@02F zg#MUnbrn_a`Ez-ubJ1+$PrVTTo>=O2lq;0ytKZk1nV)iXb}p>QQdVkDYta@(71yj%UGg>0M`1kHcY1^8q0gfYtWfQj zF7c%EXZp^x69hVJSjB>1vzJ>Y4oam@K62|wFBi4!6ciQ1s zm3EEcMauquWPXEHQ9Grcy#&Dh7 zVXydXBgb>rJ*AHyf~4f9dkyxtzxwLrM&CE9cUi+O&MxX@_fe_wX_!j=@y6V>-jS9a z0a#DKoBSpTh&8<2H(S>6%5WavH&6Q)o)vn1wY)vGyh4TJIu0-LM1(?_M7N6Hc@*Ui zKs`34px)baV>0{WE3P{;3k*U+QVmPwG&G%nayxUFV=GZcM+@Zka`5ClT%*jfpNoz% z*pmKwz2X1J{K*rJqEL-^lJQU)fWG4u5I7H%-1u;Oz?cA-@8vCfaK(v$fcnsJ{0El^ zMjRMu`F+H!K0ClB`W!#{OHwxg8f1?X-2Ig@04h>D8?d9xe23g#+M}hhdidHckY2>6 zQK_QuU6YioE-YN;^uFKI+=|7zi4fx{>A$EQrIjqcE$+f-0Qo?v2u8p7SUj6B;n?A{ z-0Vc?$CnU*<@slptt~}TLJWvCz{DPTY^9auJP7#>xnZg=(_e!RCjru!nw}2u_un$C zAm36yE_VJ9;sDk(;c*#YWRLuxwv>VJG2x! z8o~uRS6A1@#>PEf0zZm>E*;3mne=cr?V8JVrFjE$igkcI9$(rQe}*6+DH%0cks*In z_-na!5L?^UzP{4I?|@C!_1BNn+_+gOyY;WW7^mM7# z^UU;gj9b;1bU;xzJiWVH3v3A?5p%-HUyt?W%ow`t#7slO4ZF$aW`(%e`1sUSKwI_L z2mu-_qPyg2W+s=fs=B%(1Oiw$Gcz+l2!qrVkQ;8O;bCuk`^%i1{x|l%eto}c7~x=L zMMenWIA-Ila72X+o{^GbX=L|k9bP9V7YxjCDI*fwOgLiqqW{P_Sw+8Huxqg_%GBx}!9Jc1lkXlSc z`+2yy7=lG!P5HX9ynL5tWN=VU&N%ge8So0fZD`URkbHxO0f{}R!$n6$G1rs0;gemD zjjvztDl1c-5%f*NRaaF3EInfd{yaUm7Z>vs!!0vAe*z`n6pm6g#Syg)kG*--+y z8Ps$geaa`*NF)TxH926tMdhienmh~Nkp0xk~>lvh@^g3Fv-TwI)-coq6h_FMgB zfOT5{C82+4`Mq&D-L78b9`6Y8e;9xmF%^M<++h);NnTEp_BWUJKiXzC+IOmTNo?!AHoZ*OPO-d?G zYN@beRCjMaywlH7^?iM z^b^Rnmj@v!S$In1MN7yhM2kd_9R4!xQNz8*er3LH=MlGBAjC#n$8Qk;U-)-j3BM)I zZd4&u9oc!)SM%A*scZVyL;*|%7uTUfZOlR`mvj#eXv`t z-}afRW6jOZM8CeyQolDTVC>HoAHcaPqbuXNaIr5)%cgqI*(X;%%0Z=%hbgrPxJ_fV5=%U^lF#oOVr7Gb93!QRfQ^)xowYG0x zY!$9^3zw`nMqWYh`YY(={!qeyZ&(494RgdN;Y6HcQh|T8$j}K=AxIdnla@qrY(raB z+Pu`7JTrOo=YtB(9k3Rmq8EPjOkg$Ro8m|K_R}w_l;lLILaXanH>fDDcHJ=}?r28X z;j)9~up5LB4akD5*D7iY9lD;9#!81wZECXps#+oXGP(j_gkOw{r+K zF(V0YPW|$?|NqT-Ds)k)qwH|Q{_UTDq(AZy89n%)98^^2qEblzf9=G6zX%a}qkE+L zIneR{e4tB+%zih#>V|?M=Q2Ye0YFuM*S3GSAsf4;n{q)R!(>8!N8LN#feDpE)&D>| zC;QEce|lX-9pjeXozVaNv9Qhghd2DDsL#Q62$hwcM>PHS`2V$ujW@&)(Nvl70p7*| zUcnjtQ|WoabDqybYR^o(T;KgZi`SO@8z+zWM5UE^4kWsf4cARZ782FZ*-_T!WoBzJ zJ=0&`wO2++^$*AiZmp8fbHBmuDpW3%Z`;?urp!_u5Z}KPp8LBe`z!Jg#ZgHDU%bnD zWJ-#g=MWGMwT_TE36c_NBBL!yzN11v3y{RFoLn(5sx6+Q`sV~W4}KS-M#Unjf}i=A zjbFi$h;o`56#W*62|N7Rz?i=|L;P-O$~i6a8+ToX41(^8qZks8>QhtRu@mMg$`Shz zA6gRk-|=S0zlskRxL(uqW9Z+)O@a`@pq!zK+$e9F?V2i~Zy(c4MwwCKbqi^Mp+fn8 z$DmOixq9E|$?2ujjPo2!Y$CN{cD2N!r%p`>p>NG?7le*BuE$h-N?&K1t7g5%%sgAZ z!QUm9J7C3WMQ;1_VP#(RRUS?sTZ<@1YK;x6dbM&G;sFTD%X&vc?-;r+(@$O9SMUn@ z7R!VnaUJZ7!$U{g1hU~TSMx(u*R_NS96C^HJGTv0S&Vlq+tfU67>yvw&7~#?Ne6fM{>Aqp8Azh1`Toq<|Nq#{^G+746eOZ+60`Ch> z+k>LOZv#&EXN0G}TgD_mH)A=o5vRUB>QrxGm>l=&rYI)8NWS}WngGp=)va5kwI_Da zXZg>?Iriujo+k+)izBYeS(LYg#BP7prpBAXZysiofLMa}k7vr!ZKcHpmY%Mi!vjHW z?)NNvMjIhsE+AhYj4+u4RVBi<*aVF-2n z>y3=u`X&iPJlYOp{D*)F(qyWpOSd*BqNv|DDKPY)Ys zEHRtCZX0h@pI7}Z?X4fDO8fDkvHDc$+`0BO%Hp}W%b46J=&HEZ?UR8H=n&410@O9c zuD|4^p$@2$_&i7_kAm_gLNYg3@6nN-t#$);-6)`T5jnjmp)9fN;BcMOgGs$_cLV?c z;V#8+p7BsMxv-!hIj3eFm;JJXJoA$Ez$49wdf4ekYDuXfdp0D*l*&!;9CxVWtXi~j zVQ6NBb-?Pfn=v}ZcW>*YREILH#B47Vy0L|e69Qf zmhSpF!YD#hGd-g_uN7HB@`QEY(IF$cC7XqLEYJv?p#q8;>)hV;ht#zQqBxN^7UyS_ zY@eE$DR|_3aqGao^?F-W7A%v9M#nu%8_->5tL>=Vj-3iHCKGZO@l}W-k9Gp?>-pI9 zKV0T)5>ecJmxqqGovKuyFh>|w0#$kPT0y17asaf1oYg9zvL1lkCZ9)7Lrz7}!hC5>_0P{|_ zR(BZtCpshW@w@gHneyF)7=8({TT-P3(<+ydi@jCS{ zz_Lk)9H6G0;lo{9w}0RkvtDaJV>kIC>&iyBD2e~%MONrFJ3D(fgF41p2MKG()0NIKJZ^K!QK4~k&#pMf5W(( z*;Ub;J3nLAf8}PtY5pq?{=_`j2_e$i@Bbx?!auJeB)#Hu?PLcby87^vq?-EfpcK%gtCDWY7_PGTQTs}`o2p8NUSdk4&uK$4M&;2|kl&NjT zmr6!hnc3bBZRP)h;I#OQ$7Z}O12PFREhegoj_lAD2?Y@va~81_$(BTv5E`;Xc@#FR zv;8%j`K2!j%jOsVjQJmK@VFbGK>pjWOsZMAmGKy z)GU{#rsy3`F)#o^WqZvR&UxX&$GcaSi)9tfx!%5gyIkCfK|`DRjv{`IKQHkhs1zCK z+DcP19ct!eDDuR-46&Ycn@RJmCd*--kMOh4X5KL}T3uNg8XitvXlrZx_U*oiEad)| zkOr3r4?ZRg(51S$+S{w-=^K+_8yXsVdn2~B95g@^U$`5?nc45(FF*>bv0lx^uh@kJ1ubOgr1jIayB&3#-NLC=XsioHY`kW-w>{_ltcKax*g!wI zQC?Ki6Bz+7x=~3P8AsUFspt-Bm4cAbG@Pv{8V-T*UA_8hfA^D%|L!cbsr2ac4M1;^ zV)w^0Lt}L)Ek(WvvC6hf?#&Lq-XsEYj$0lOji71^kWlz47Epws@(H^ANCBbver9H-r$<|YPWqW+ z&*^Ti*Wux76JfI2n{U$qGuyqPEs;GYG_4FVBYw&+2weU4FGw zf^-hpMTlQrE)YW5(_EI?o@XL$?d{p|*)|3S2H6ZlwRG7$k!>w4*5F)(V}Di3kg?W3ifYD0!P{K=aqsNTCs}phMn69cpJc+ZaF- z(Nxlr6p_vcL4F47DiwAYSx{|xV4Z>6+q;Cwwghw{rt*?A=6s*HT2C$ANE9Isw>g#> z^T^Zl-2fO+0or{JbD5+pth!p#_W_}r#%O=PLbVozhK2@}3VLvC>Z+^b%@|1~eEB|5 zFV5$~3`~yyat?R{t~-Nlo(SUZFdP_}v|*GD$&py)l?u#~3M`P#=)U4o1^p!Zoe#l@Ri)WB@ku^o2mmnYFd!jor_`JQC*=p8nFL_}EfWes--d@i=qU(;$>!!ee?4 zk7IyS*)~mKO!qJ`GZTsHYzI_laa`J;mQakFL|oczIveOruBxuiemUf6l4IPiB4$?W z)x>X8_V%+AU=Y8aWeaiasWmh-1T7o`a-^!QbKsnVkm>#FY^nnOaWQ6h0N=fg^q-!!w}D5%Eb&ODp2# z+q5C!6A@u0_k>LKuS2^#R#zXUVv;u}zkf$kW4~qlatjSj(ypzo(G`^#gSMXSuext~ zLA@6<>+es7MqJ3t%S*3P)6lSH;Ma)(WrBC7$EQ;Dbaj!nz)a`ewzjs!Mpf0+(m6AH zZQy~XURBVgFWo6wd6@MY7%pxnMuu&pSEwt0Tm)YSX)Ia*I{V_qv7L|Ltig(koK78} zwo&)M^qBF8b?p39*9&%aniP#SsG^(EK;7kh|HIIDR#}{|)}rE0Pf~8VY*>C}F~fF1 zUAiJJ$a<>v%xKg8)A~SSLf?YLJ?=}F)9soCkpxfTQ1+iGO!F zW!o_OAHlN;gsV3RevXL}A=%>8d0SGK66@Qq?8msz9PEFR+OArxfUhLLzcchwfreyo zUIa7v&G*TX^02DC0t`TlYKJmBYTI6)mRKYtNQ zQ>TN63<7jc>RSb;ZvPX4BrWgcXZdY-!+WoKXGm7;Z9WN)TlKZu|B2%j^9jt`!=`%PWOO*smuaWq3WT&KD|44Y?A7Y!3>gx@~KZz?^aA7A8r?5 zhssz@w%mjh2>{nR#b^NH9;|9?S{OI!ym6Z3Y6=bdv(F z9gNzEXV#OpqS&`1*!x10LU|KK)e^L&y`x!g>9Lh|C8)nm@@15w*nafg@X?)K?<%|X zRPl_A3~e19TZ>1JDzdUZwX{s528iNDSs58`tEdQejq%~}%5M}6_c?^9M;8m1`f+dd zn!vDqiKdt#j}a+P{r-b((UUT5GCpJQ9$~2di1#TY7i_Ms#e=?;Ju(htKtKR!LQzyy zEP-5M%%`52oSe+cy0o@8s{Ig8g-lK%XYO_!#fgBf#8MH*VK+C)$;dJ?Gl9SC@9p)u zM(PH-XR9b~36ctpswyk5uCIgE=W!0WBGA1yY7Ij5mZPHps)phe3ATecE+HYo!O4k( zgJTUkc#>$xY-pVOJ!bGIlx#X}HWo>XtZoi^Hz}z69Xgsxv98;dIdOTq_P8~GLPHDD zvR(Ps`wmtM4+DxGw;xY!C)XJ8sAosgJbsO@WHiN}-p?gDRs0WS;Hs&+d=8<}C9}Yx zr2sb?FyFs+W8p*7{y3@L`o{iqtLgjh(+`K8!3ix$jK^TFIjb>_E;K;Cl@LPiVdLg3 zAyRkw2za4IMor|(k{dA}n;}<|M(dFNuRXl4gzgc)jv58!onE zHWd^<4*}6>Jn~iiiI3TiiAoTmm0HvQS7-X+NP=+=!ADMnJ|`dQNAK_F(c=>CS9YY_ z``7GRK$+^h76=Fz(g61*3bGvh$)OiVu(x_e_erF>gySiYW&A&Tx&bmb-z8@gWrZ7F z9$mrcoiFx^h8D&!&&c%}dH>w%OixL^0vmnn2g9J~WJ#5mcOK=^iV=G4x@zk`+ILM# zDH-Nt6y-Vf_ymd22+7amRDnQ`&La{-UlXILpRwR({TydFs7?eW+!w8&~7GT=Dffe_mo&tXE3$#&97axFPw zPq-Pu+i33nBvd?{-Q{*k1E>Tb+TCTLs@*Yn#?s%acD80#XK`lxTbGc5?q&^i3u-vp zJgwdU^}&`G1k#fIxWY+7JSBmjl%BvZS zFnEZ;j)Cl0x;mk339-z0r6ErMc13)rcAh+mr#?l`qS40^onVF<(y08bKG$*DIIon|S3#hGnqE#S|y&+s-}LAmV7H(&(Q2c)WU(wSx-uBCU;eZ$s7 z(Lm=blz`!1W<1RAf0nP^-TOjM67qp-vQ2%2YU%CjxE=%*r43JK!fP6Qao2ltPWD~h zZcj_t!dEt0p`w~w6p}S=+sP5ygRDqUYt{YiAj+e)wo-ov`O?q{bIvWCy-!N$1F73B zdj?DXQQmCG8$U-@#vqkeM11PyVeGt)@h;dJ!;dIeEj{mT@*a^6DfqY*%#IoR&6nsY zrueQXpTPLtr(SSLb$OJJ0m?4-N{5fx1qR*;@*Oir$O=6;s|at z%&~j#R!UghbGEm@3AU=FbF-_3F$0SNwK95O@p!5F*jsbd!qPQq~koKOCEL)`d>V4caX%h=O4G5(8B zaU(SC94qHGe^;X)4P1(*rVAoOMp1tB9@B&&_mclJ^em*-)j8TbHGv{oBQ?I6qMVqd zB%Jz}1OHLJ%5usY>lOH?*@ga$X7n(FD~F|;pZyj7%{zS3opLH5R%tx`kGnuz<+_b; z#Awd&4@m@l59w4SCF@)-R+d54?QeU>qZ)i~M8=c?Ny!&}u(apjIw%hGKur91hz1qW zo{D^(zZSS4_us)1-%R%YI$>?btsiTn{TWY1er0I*yyBampUlXJG_p;|$jDfTrM-Vm zU9xOTwkZB*;ctZ6krCPMkJ6hs>5n7FMVIeSXr4!mTm5xRSZjmECQ-e>RIp7bF5W1b zK_qWF5jxLYmUr+qpADa}0xmEC?C2E)H02IO%2cK8wt0|W| z-H%_wSp?}>1jiO=`r!My(w~G0WY@+XquG&X*d@92sPKr4WtRa4G5!Y(+UV{_XUFy3 z`FSgZydfmi5B9YYsMk>q_^TJ7EWasMZ_aa?4Wt_P0Opf%_*eY84yx}G=qm8~3l9pA z$yc)dbAdlSp|$ts9u`z8*FwpEl>YH>Pf<>G`JT#u`AuuZuD#wOa5ptR0|0oBW2^Au zf8xD`)ZA34qg|`l7f(=kCLr2*44CJ^zd{XO_0BLNaibRf;bp;#opnP>rR#egisl1Z z>uJO-PeC)s&;0DQ4hc5c&cV|Di`PD3=mM_qRkqBW=lDNGw<~|#rthuR`AP+Fh3EEfrob+D`e>`eJ$J({fDr z2g7!pZ8Gxf@^?{N=E%;6u=|hBUB>ZAKGHBa`1~wTP2twZ9v3pN<_>TDNKvF~2vuj3 zLJO)kF^_nvnp2Vf;k7l})QmT;l0L0&g|g9zAn^9FbOF(LULI6LEWZ6As~a^Bg6h&; z@qYK5;CbD-Z6_*aYl&;mv)4^S*&^0KMHM}GHQ#-slrHK!Mqo^Rt~J{!gF!KGlYyn| zcMulNiD;BR?=9(XoBFomHDzS=04tK^vUk*)aV~k3sHnllkivB{C3&@YPnfaAvE}1$ zy!%|^u{XP4xhw@;9yAq@HqW*ewc?7ZHQSEac5_QuGkDO6lr@ zs~Ots*>TWStyp*Y=+E~rc8i7&ksUT9Zj|%+-G|D>vJWK@ym^>E0>I|dAvNG$3gf$N zsNABOq$zH_35(MhEjNKt*2VZYr$UYewW+qJ2Ou4FKc8+qKK~UcXm)Gt)b!3B{dOWR zTiE6IvN3Afn7Fx`9Sh>&#|?4H=sh`+npz)Oa<|Q|*exQ`tI|=UXBcUV#C*4_DdOiO zfv$C%p2wl)`Ms2Q5tvWzdL51eQiGscVrv1B+@uR-2I>Nea~{qCglehzmDdWc#GewJ za@W0~odEX}=u11LK> z>AO+g#VAKtm5B_&j}&g4nS`v;~ zDP|;Q$E;^Grx#`@b|du5sBLyW6~#Y^e{Jd{aaEN(Z7?rqS56cZ%rT#&pF*&&9D4+{ zFKcvCQH8qeOzKplQf>TeMP{cKY9kR}395oTGBlI)!n!XkXlR%k8fTP?5$AzdKYZ^M zg}PrX;RBH)|L$SBPjOSa#LvnCC?ub_pyc^32}H+$T>UEcO|S_0rQt`|nX8ffEI@<7 z3s3kB;0{;Dgvf=2p_+GRrW@<9BIe!@>cs*x@bAoiQcCN)?T$#_H71)jXy9s7Z0WOs z)+yX@oL6?{P&ymdP54ZRZ{qo`BpD92n3UL!DVaGHeO6(7El$G)SUDD>5s^aIA@0fK zCb(%zFU%i#DWr#nRJR!vwuLlS?5Jnj!>&h9r`Xa()IjWV(?{=!PET2TY?KyO>&DqC z8>8=-B<`$q)6B;RW!2;n$Bp`oPqm5I#q(s3awG8V1XH~Yw?EHi1;%Zb5KG?P7VZEhik@OI%AGj%#uo0i@oA==%Xeo!dz*>Niaa|dyHLL2$((eC3y56&nAou1RtZ%lULU;Fc5yhFNs_s@)d)o1Jel4O3L|3-9kJ&cJ z&`(A8>y^iZXil!^eT z`AJgPcu=$~j#rfzFkEy}Yiy}$aHz%P*icTc=4ewI)?!$%K*{1vk*xG5Z(Q8z^ZW9} z_#T6S!%{(2W&Pb~F@d+LvuY`-UAGnQHaSq+cu}daL}!NR^n5|OTzbJBx}I@aE^EG; z+~{=p1SX>sp;gdP7*J?YRp*>ypw1F8HGhuqF^sOmOQ#OyTI(BqOCa8i*t>b_ck(@QOzBq5T;``g`~eY2-z&Z{y3Pjf4$RP&AT>!=E&Oz*ygsRWDSDR6BJc@ zT8!Ym6>ayPFi?)Un`Bmp%R}#q+ti7s}I-L$+XM@R&yk`R;9tM@0j&@1q?vEID=7i|i5EHn!H3 z_HjzUgLq>+xidPtZk)gJ3GV3{DsrRYNmh*Q)1oBmTfQ0sF;$gSbqO*$Uc7AKi|J_C zcH2|j(@*Y+fo2qv)VnvnO`nf(_In%QO7ZJwX~F3I}TH+ABCbo9MDp_9)SLZe`F ziS-gT(=Q4y&3P(@0I272_JU%USwAasU>TDh+`?Zlj?akJ(@jJw>J-2?0I@9fptKWPH8tyn{jE0gott!HUz zT!A>T>6!-C_I5r;;hed4W68O^=_Okv*X`t~P?wG!{&&DWhmRad0ZH!59wH@keRd zMuH{g_#H)o?tn7i;1lYhC)DwH8^2;%QangC{+(W=NIr`P(pgE_DgP_ZXJmDhLe`ZPfv>^qD9hx z#+48BKi~cYX!e~@AZpc+j!keX10<|}HC5oB<)02j(n79Wbkm17*Hdi5)a;&>LfLu^ zv7(A^`8vEjKI|`*1+6`x!AY-_<4Qi`Lakc=!RXt6FN^LBvRKe{kA(>4STU9{7Mz8L zq#A_Mf|9KVn1-&9t`o)w&|-F=6aNxr`UU&HiQ-FOdBi_yXh3lrDHnlmU(loRgC`EX z4_aT2W@Y%iT>FOH@zXHSN|O46AN)559szolo&ixV92xp2!>yv!|Fe|_@F1Ww8oZm% z@gd~cc4S`D4_)uMd%LLF%BZM7HEe0E!hSIq1SH#c=*9Oq=J2+ z{yUFuqnMdM1?i1$_iR*-&wSf^Qg3kW|L}-uG&4a#8eG;4peA*1-Ed*hcKws>*temg zjfO`)F6W#S&Lf^v05kfrkbkh~c;4#e2%)dXzLK}|)k?2^DNiFbRC~l*+$I$^@Ad%W zBM*1+@iH6)ZxzJL@FEFzeEgj3I>E0MsThs zVCH)bEvnBszw`V4^>t21XYhXC>vdiC^?Kc}Yc2o|${5-k(<7BK9*3EF zQlcXsJBR6hf5WV1D&<9NNLXLj1AHGoz4VqeTt>Tj^XAgn)^z<+H}@lt+?i$9?CS;k zu=g+wnXd{7+9y-^OH_xFdoDlrG8`}eG~GJcFu?5kN>IVI8#8S^8D}&5{M3JS4mVo` zH9=&ILRlmVJztPjeHmpdvqQnu9;N}%jsiIbazE+vIAIvyQAQMd61=B#UTX2!o8cCR zrG>8ff<6Ur@ui!%cY@4{gq^mLZPl)X@pzf=Ft%ms1-R&kh2PM z2T?n-JRsAA!6h*%DJd-N?i_GtOifM4^uiM1K5Y2dplkO3SYI|SvnH-{F6iM`7E9A% zKGpJ3vf+S0ME>>3J3OzM&{QqCQknIdyLIgN9Gc&W12c4as?kBeI z=+TL&+k6Of%pC+UGm;_`nZUlTQ=Pnu6zY+mpQX7usC8f?g|a{|3}m!CJUl@9QYZ_WHf)4My1VkWvHx}8^k*XV4oVkx zER?-HKib$DH|SC^xnSU(J)@yCf~S;cd&E|6=UqG({W3KvL-?V3*POmypJ=924Rwf} zg9B(gCLX;Zl+pT!etvaiGI@A7DLT3vWa=HH~tQpwRzG%5QAog z-|GX$jD>}-(*x(nvb$D|*4rpVWRu-e`;)S;dVE;`yx$w2tLTd_QZfsTVve&1dYFTYOw|?f!S_Hg@rpAkEkT4{2s^A&qLEM?1geh; zuIo@v$49?8wH*YOlKatv2M>yfB&PFKtAdUJ$e<4Iufcz;i|)}|-K|uNvwd%KBj~;^ zVNynhqlQP4Ml{vxRP8l7(}tbCb+qeB;G4S2M@u|*KhDSIXE-QwGb!F+-15}5d(O3c zo$0KtTcwT9)H(x)N0pUCm+tiL(XM{ZO^pGb8vaUj`}@DGyCS-t(6-A`?(%hXos?!C z#;s5$xV_kLR`pfBFnPy--0VBY#CHU{dG;3U^R8nBo;*54=Av`x3rZLl*DTO41--k+ z9-bV6*EBVqT@dDZ&e1K-K#_{iW+l9M@dC7%{D}K3*$AL_25cn#DQUa0ZCAhfDNz1N z6pF2jls(XS`nuId)PSbesl*P#vkip%2BR@%Gt*x@x2mEv60-Fbj#01P8@Al!<1v%= zR>?p2^MpdjacbWYyr-Jt!D&=poU8w=Y8y>Wa|to-7+u?2nuCArs>dkaxs`C*gJXP> zWO;h~5ui%dGIzWW-gMXfT9+^*qc{UA>fcj((a0^??rxHVJ`;t!@j zeR{-7069tP(-j{+Fq$VO|D&e1rTOcfH_Xh;FQu1^F(o4zr;x&zOT7MJM&Ah?SI9c- z%?EyFWU}LM-g48vK9SxhrB5WIL^3SGR^%nJ zmmZN?q{+&PV&=rXyFMp}v75_J*ZOaoHF)tzwN68mR?l zL=VT0U!B^iq3(Luujkv1hf&9T_#{C(>L4~dD{Fds`emZf_0ve;y+ljfBmmP*S~`?I zEVsMFb+JD79Hazde7I|JA`KqMDDm&2rIQV-B)KZx6C#&1whtcgVV@GuZkjmn*e5r! zfJqF~IGOFX{B}uFQ*}zjtapB2*a5X!eSHaC=YhrTWg6Iu`*$sODixA?dw0tjxGc?! ze#@NCJk{nbS27TMqQN)P-@l=`d1$KGz9LfA9yOGhoKU)ILgkBC)e}wDGl>sOx3q*f2!x$zGwV_Xxfnh{!Nd?ZGASv4iA2hwZZUuS1Xz}cYnrJkeT2Qv2WTaE3)-Qbe_x?yHgtpx(ggV ztU1})HsfutNhH$!`;1e$d|V$TuzQei>i2BNsRtXIwQixlCy(!`9(EbEObeqdq=h!z z;LlQ|wu=>FK4mVElZOk>1yQZbNE&%HKC$X4%VoO+V~+9qa92%V^YawgUE(~KmY+Xg zEl1xG%*vQEt+#6@b~}izZk{H`IkTb^*-*+umJOe|xBKz&bJyHbsh!%VS@%jf_T%j0 zl^K0K@r0|b&2~l6FZprKtS$vI?>TYLwpP0DXHPW#w{~H%aF80YliexzAKN8tmGeL1 z8wYH!mIh1_)BKP~K~DNG&^uinm?g$_i|!Pe8dat3u7|oCWJme+tT55HVg~2WMA(<0 zkmoMys`Ux~8$<522&p_z?Q zsw@8Uj1b|E5oAoxXU@S7#E2aoLAwM{D%~lD5u!1lPDl}#T{p85-qzb7g}YDhWe9t* zK=fCmQgA1;2wBn{#m8$gYLZm>rqii9jW;zg#*6!XYQy**?l1~>wk~sB^cgSYU*9R) zFU$t^>dTie^nNofU0w2@ZvMmWd8?*^I(ov`$ms03a|$I_ww>OI4V+^`AZkE?HsopZ zfzHcr_wMT>x7y3dBu3tHO|W+(`xwHR4QFrHVoVX89rmS#!#$`l$$fJ3oj z5_c`-CaNEkmTv9pI*OUER{CxjNdpGN@Gr1v%lSfOb)vd%5PK-4=f#$n)i#g!=y#rw zcp{Zr5^WK`S3nExN^SAi$!%_L0m`V$^0#J?Ip}>gKkgx-U$QiR$vH-97E@3$mwOz& z5%df|t$VA8UYzr3B_$=`P`)+34=1{{_F#!VtYE<;yYIHww~NKD7M-uN#%s;9b~xW& zxSa4Nr&T=Q}LUn)?xlSeG?Q z&unUTzvhPBCNofYX)go0PT>YcJIlmyw5+{BT4!RyWT<Lr}iRo)|?nH$I46ZoUS>tXpST021KW-L-65pmTJJ?>e|>f|9IkSM)y_TB)GJ z3^KN_uglLAR)F~X>7SFR*B#1)u6epAy*#M>zjR)!kXH)SLzd*amMrg0p(n19WYveX zyEjVtTq9Nx=#2^s-9DUuy&P9Ro1p2YBvT366qa2@=OvIa|B5?d4hw^tL_-V~>#nXd z7egdBZanA95Blthjac^U+S;)iz~_{+8QfraT3J~cMA0=hx4OzKO9}}IfrR5^QlUp5 zFOCbl5)8z?>GFtjHdbJSRqxn3F+a-Km!8f`wW`x;vB+aSxh>+l_43nO()#nE?j?5P zyyHF)ccX)5FWuH*L$P&$au*2TUb%KHO3M0u-=gb%^j6SnTAC;%DyyhCJ2@SEJ)CI( zI@loE@a0QT{`T`MMW2}Tv-v1$o^4h2){!<^0 zSAWQ=9weECb=GO`C@nOn=nEw@%lYP?8ujz55osGwk+pDgkt!ZhcQswSoUm|XM$eB! z2y2pVVPX=~0%GNfAr`l9zo2QNap{gaQd5dd1bK{wUX>GhpZNJDR``$}iB7U^Y->-- zFsVySiT4afBcP$cw}tf(5sOu^7RB|L1voewpyMB3w zDvhVd)}xa9r9BFra8p^GdPubn6Jo?2i?HIlIwrS03GQ8)PRv8a6>|9UgA#`pckJ;# z8bCA|mV4wrImUy769xK&q-0)cse%-?y!G{KiK*(hAWC*rzoZBRXU6lo^`@j9r{k#C z*nLfr!o(pi&PsQ^mPrBssg%=rYPEQ@NQOZ5Yl_F5oTRF)61#Wj@uIH<^9@e(nFF_z z#=n>fM^fgZ53f7(Ww@!>ZS$@`;cvoBqD{wavmMw&)N#I%fr0yuW_P;>MV;b791+lH zsMWYDuno6AV7MV(1>`ubt*y(5yReFTR>QBmhl~*8Q#&$lW`Z+~up~!Z`N)(ytEp?J z%w~O>#Z@-9tur7c?Xlaro8fa_Ls=H*h_6hm`QJ;9xVtALA8m$ey-rJgYTv;Kn`g!Ctax;iyVZa7k>oFAj6evLk}%hd)_1vSzTKwHB^`@% zlP~D6GRcT`v{>lL96*=sE37?hk684~d(-}a6(#d5IM}}b6W5_zT)1l~fu|hN=$!cY zf%&PvC=C|z@o^Amq%Z_4Y{7vf{FTq7dXV!e=mW80dlqYVd`E{4bopx{Mj50n9Md^D zaT|7G@2+!nrN6pFufI`>&hrJ$%zRKfBa#rO{FY@opi|$W%%huf{{Hc z&W9th%Wq<1J=8zf);=x{Q$OiE`zk5POMqD43KViLjilt{J8z2Yf!C#V$Rxbx+V$&F z5)%95l?@O3noiG@`s{e^0Hy(?yCbo(>mNv%=d_Rat_Qc6;Ji`SUEmS38#(cR`a(E9 zTI3nKR&+{@95kCdfmR%?6IDa#9`6s)w0f1e_kui8 z)pN_;Ul^P?z@U$fj5v`=LT6H!L^mRlN{p)m*C03WEz%yj{%D@Db(ya8SCafx$88a! z^^^4c)Y%KZX-}^Hia4JzIKjOU${?7yehw7F@1D+y73D|FJ0%q{F=g0{yH{R z(YtDLpD<|k?S|GkKX7+9`VlVg!tUavmzn0o)x+sYe&^4#6Ix>dXuskr@M1>=KRQNq z&Q9zu$XU`3774`?!G6XxiEhmtiMGvjEEYV@9wJGLxT;d(Z@}14oKMtB#4>OAos`y30xnO_E1>~*w5-jUb z1I4G1$m5Wngp>IBx#zU>_^o>^h*Ua6>Xag~O!3#;!+oK30yS$o%`3<#&JVoR)gX*U zKMMI;Z-zYQekXPhRzm)-hjZdWj`_mdx6^C&LF4|O$Ndhp4lO#uf|HPEMM<%)#?n}} zA8!^u5_1_4fSiv*JAXe-(k6Sq1BoA31!Uka^Za}N4iOn?4`Iv!NsEtAUxNj}men3Z z4j=xM_F@ihr9>CaQ%F-6PMj6zYKV>2a0JTot$XLPzfb4?c(@V_rIZsp%8(W1Q>REz zhFg2zZ4GQ08gpx9SZF5S9on`N+q3ig3QhjRLi#vcaI^JM{n)E?-n`E#@2FPMk+k)) z(o+&AgHw5yd4}1)yA_0kBEMpMhjD)zvu7Nr4b44V&~YvU1!D?l*UtVSy}u>If9z`wnHrS0y!b6qp?8=m$IuXudW&+nHh{^(L4yQ8__p5jdLvp%ebVFk z%S;I=C~jJC*VFG7I~*pe6{UvPnpEg8K0wQSL^GV``e|FRhd|>3i8OURVK2VwE7^V6 z-?8Uw{yeBMCaKQ) z>u=JqAtuWw6FCy!m@S|5!qsT{9lv+7**=wt)cEOATk&V_sbHI|=p%Ji!4cc^@*}Jy z=CkD`W&%dN@qBl+N0PPf*hJyI-keu5sc$c^Pc?AX9R2z_j<52(q_gZf1`&>VXCmK#nMiskKrDB#kCg@W2rSMzsh(W7_eW#n>CNuu{i50uYbnu@_E z)~fm)^<)1WC1=xxS)im@CmWLwS9Kiln_h0eY>{xH*|x!_;hd~C^HO`?Q62u_=$KJy z>m_d2pm%e&dTK@c(=d1bd!|e#)c0MfV{$A<CF3G~6eA}lAI&?}aVayD_Z zeRtVeGwuV8KNnq`v;7-J@o$+_>9&$u(UHa{Ifp%nJ3U$Kj~~Qh<|)S>5JN8;>u=G- z*(o>4=1tTa*Pk0cTz)W+@H(X#Q}roEBgXrpCoi^<31RG-!Tx)!24U|2(9MXV+vrUC>6qfoivQ$bU>-2$5oBavNamH{Lm4TyR zl7%#nD{_bmd6?EsRkd1oHI4Y`@o>E%=H{ZOL8NjgCMfH=i#c=m3}Mgmar8YcjN5eC zOcmO@H`GjdmRP#jGZs2SIhvki#^&NLTR_{3U3G{TzkK|K_}A_{O0J<(UlYeXHSRIA zVK$IpScPK!1Z9P4A8+<#cU6fOHo`ieVnbC8z-Uo#V4%>^0iA;BnWiZDA%)pC!H#zo zdVVCe3jKU>+04Q2EU~;k40G6Y2msHY)7luPB&f+ag>r;AuG!IiJ0!-hpEdC=Q_s}tS81EWWJO3AcmTG5c4194Mb96kEH_JD>%`ZZNkrMcopv`qc=_GesHV-+OAbo_kv^G}o`22e|8 zee5V#b5Qi+DgHY1$z^N*w7rY?r7u3Ry;$FSMF!M5t_G7~wpa-e6gYN@7PI(r-ZmT= zgjy${gAGA6nFv*+Mn;m$dY+1~DX)Yx`fXpXAo*N~{#=-zOYh@_xAtv1C>lJJ+gGa} zm+iH#EAKtlJ>f!0n8SEuCB8SwPzZ=DbD+IzF@+i!!k$AMHzlN}Jf-YV z&T*|pe^7zB`TbWf@cY41qPJRAP4Vc~j+ivMt?7vo4IHT;!=ma@PLQE&@v@7gHiC1aM zML~v1(y|M54mye)FH>47qE0$aCwXozmY@DI*Z*z!mT_HFXIqax-!&2t z9$uK4*`uIBy$+*-5ad|bnFh;$Gt`07)zzK$e9f{FE{Y?!t)vJr))skioxdfueZxa7L_rxGhi@fD5q zP6h_={}WrNOJ+jDlhoDOBd>$dr?z&0!czIc(wrUM2wnZoOViU55Z))DR>J$AL%U-g zT5~%uFVt-E9|_HcyOI?j@O4phh~<%{l*KtB0N8T7cD*ymG_$bi%D3JlB;<9XVb5LD z=Cnw8*D!yyiHVmw-m{^hfw1d7HxBd-NJ3s-UXZc4E2+3k1+z;tU2lu=u+8z~$Kej_ zL}%eS%}XG8(AaqTfWS!yPC-Ep!J1E>u-^3_g*DS~LB781S|QLB$ZV(@Ck-yAY+*vf zReAF`VI9cURO8QjyhG~mb$v~ezA|8yn-BH}6uU&^74H=5&30FjT})!Pt?*rLB8U z>*)zZCI|~c6)BClQXX>Eu#AZ%m!<<})lDvMgq(~RPyQaojEULkZnBp7YE=K-{K}jn zbcuCl45JH;l=?s$Ebs?@HSnfdH?$Sg671c=R$tt^E7}y%Lx6^{P#ag!))0B?(Fu4o)cR(Jx1=izJxD4Miidb@3!_gzi3 z99UUgUq7!ku8rU_K7;hu5XHE#vgq>ZCQ9w#$B~9!Y$grVe`8@GL21t6!>&mrL%i3! zckfs@1afk7FLijQM^KDDY!QGXDm_TP<$yLE9X*)S!@5U<@fBR|wGFe^D+#&`VUHbw zx=?#*hNfB23W*~b6%(lwgXX+dDDVew%57R2OC&8iCiSU-!kRCIJY5go92W7Uce1q& zlz$!=*wo%GOdobsNluwh=u*-JsM*!q+e;um4ce;=J**BE@!oQ1()|`d@Z#mx1_m!$ z?KhB?7w3iZuwb)SPJ&$f3f~X}8q1Iz6&O6Li4>SBZ8KK6JLZ}l?J9gMpS-3RE z$KA+lC2#n{ei~kEsb9W7@_4s@mDe=f)KAXJ@~5aI3Z=djOKKsAwben-oQ#YHA^itA zWA4~1Pa6vXyG;;}Q{c57Pu0#lcs9v3-WZF=ee>C@V-F}q$$Xn!qCIhfcBc0 zB(-kEU3rUsKnlc{UbI49Zfjtg=qgsx%rZqPU?>qKr<{&WcG`D`f^aN|`P--MKS8P#t_n1VgWT$H77BpC}m9^0;vVu=naRd-tv}~ZM<4OG*tY~b$BD>!r(;y!)`5o z&`t(j|LVK^_kf`hAHQ#0kRw%b1BS>+Yr|bsKJU zriB(tNMhBv$yx%cAyD< zosybgJ8bF6StvY_67g}DshQaZ`N^&FDtggyT8}qp6sMP2&GATopIOi7%Kw0K5>P1h zAGlst6ca5B0Q(f5SvDZrda_B$SHUQRx_rQ<{eA%1p_3pVXt{hjL3+R^P#qss#Iraj zJxH`U9C`VucV{2kaU1`YatUmpz6s*dZrZ_?Fc~XLiwwcxmQm`BkBEq1W|up6hS0Ku zu-%4F7JVIyv<tQ4%C8V3?QrXzV~4mIPw8=sSN-1AAjJ3`=M85y9R2zX3yZN$ zl$=&sCUZNP#TT7X&AoEr-6L{p+EICJ(Gt#yIQzQ=H-`#D2cz$ug|)^~QvWLHM?P(yTs#sv#{6*Ct5s08G9m9D7?6XyF+^8?5N?F`@4xJU->k7NdIyT z?4?kM7&MPeVBZKGITCn+`F;8Y>79FdRMOr*T*#tjO~}wCY1rYW3ix zxUbB?{L5V}6wz{$x73~*Ns~-6PXK6HfPOOciMDL&!SHL@(KC|VabE?H#`K($nO#Cpc-3m?NM$Xo+x=&a+#+>MUPcuW8-{XgAz&$@xg@5<4hu4 z46m^x;9{z(l&OXw8A9xM)2F}(TglySB}?Gh3VwsUxCjza8j&(*x_Py=w4iF}Nrb1> zBc1&a6L20N_(uUmq!E)1${$dtw3mKSLPmysTM`DAIP3AERQf-X;oKO@@)4hj3SfYA zMD3S9>!-?dDi>;^mI%VZWctoaxhfI!cX*2S@=)^i8$<(7FLPKMUpHE!QIZlKS z)C+7~!M4sCza6qCjvd>X=2Ma?Knv*RC@&4*Bw}QgP?2{{2NsBq`>b5SN%}Sp#Kz3k zBUl6=d&Ang33c%*!CKndvNAGJ3YZBmGbD!1)i0R%!TH4{@%vEpT1~x8?jmQkPtgH3 zi#P8S_8XM@(Z`Ga2XV#K#VLq@7V0ab2-DM7$Gl1e7F3qR$pOk3yImq88eRQLH%??E zmfU@pdzXuf_EERh`20|;ETAm*GQ)B2TiGgoF}2h4RfA)_@2lREoDOD-7dqc2a^a}Y z3=^cEn@dMp)|hw5VP~|*lD2$FpKusm*lK+T*`1oPT53f{{ZCuw?;u^00p`rqBCxnGKQ-k0{0MFhw zIhj$Bhr;=b$;+!Js;H<8LzdFbC;6ABihxb^((S`A6lex2CIptHeIR?;XEaJ0Z0DVDHVQFUUf#)WpX68gQ6I61F z<*SXhytjo@4jnpw=IX(j*_sIs-LUYmfgQcY#5;$@yN9mV%IEbnFLA~wV3VTo$>!Xx z)NhZcf~N|Z(DCEp;xm>3YU14rZTqmp(O&_&Mw37K@S#O`Jgsm$bX@%6*m)40O-%z+ zJNguBhNg~eMGNL>r2=?(SMwjgBXC$5x08*}omYGWQKp8v`Xwd&je=v5V5mQculN|N z5h77CRF#qqvPqaue4`pXQ!m3po2>G zv6OBV7*qW2E{r^_ict1A?p#Mq>dt{IZrqi+VWOF5^!1YSl2rT-eEO#1jYpVBcq1a8 zb)06qiC#`6kwFbn47LH_N2n9Sh6=CQPfzZz4Ctl5ge-Mg$a>QnzDU{`dhOv?_mf8! z&d|}VZ#jQj@ydSCpCj!mh?AmicR%984@%aD%oxRys!D5MApJHY9zf>CZ?r$dJj$^C z9D31QcBy6Bhgkf=e7(H<=t?o4&A?w~B_OL7e}n=wf)-Zdd*Z&5!$BOi4?-*fsm8%~3YR%oiD1J}a%267wOwDnZpid?dGHrkjr zyUuv>uvtF8Aa!@(`UhY3XdD)otR7I3l8;YFc&O!QWi`39Fw?O4^F@H1aG(_Mo%gvC z5E&ts(}#`1FxAHdD_@^zh@~G{VQYKh*Tqnr9DX-)gaL zW|wAb8#B3@OBB38v1(SeIic^i)%W^42$%#9(yxIh3g9+G?h%VHwQOgn8r{ZW*FEgF z3I4TD6Ed3gaJSP5Hma!wCsZxMj%2%RK)?FRi=$gLldI5+5|U*T@%_a%Ly^hiP+dK5 zdqPf#OLx%HoVL6Uw#hly=#$B@kGCZkf5wYHNL(i2hR8x!jWhPD(&1+#nap$V?>05? zdL38YB{uj<(5~xXw%*ZPc?aWIe9nM%Y~Sr9iKGwYQ#3Vyd9;8SI#{?Rw^U86zR^!= z90FPE6)rH{+MI2<9JcdJq_2Jv2dXX0;tus_IUlZqo}MguKB0RjmI11ha6-YDUwLSi zc2*SNZ9j8(-@C}Zz6)V)ldpY1=Rnkrx_<3NBi$Usir$iIw`He3`|BESt&j<}mF?4S z>19i0uu=CsKIrGhvjQ`p`5sKHVwS)Fd}IptXC=n{5$J7%LKoiyQX0Sp5ZB*_M%oWn zVDArWqvY>2WE$B0!_lvjCBY5vSv~%+tU;Ohd47Q3|DUbW&_r~0v``9O(PvjQUXx&< z7C!)7_j~sCJN~NQ=3DOm(Pj9z?BI`vt^luV4uY#Tf`*MLA{p-gLeywU#uETVp8WIa zW%NJLD~3N?3t+Sn1Mg0rH5?#-1h)Y={P$$4PSRkXIh!@puoaGY=imHFF!#ixn;{An`Z@fPF?zaNQ*zQ z^DN)*t@U<6)`gofS2HvBMKjHm%)xdgAl5t^^6Ynzw~nzyF9S*AZ?N_;u*3dW)45^A zJFOwxtk{i@r>&u$4^(6NY1C+m3DJuYv}galm9E3*KXQscY*ff~{=XVX+oR>JmTPc; zpF919)Z*WU_ksK04Vk7{SJl5-?Frcb>a+hk1N(T<-uZ{Ugbx*w9s+C7PndaP2#{D_ z);x5?tT{}s5W21Cyp4>lTK_$!?=+}-G@x}i(bxIX?+f9cN9ze~NwsaN{YKSh5|xEL zL%>^FYhD~o_((u$`psKjcNh42clRHroDK`AkRKxt6bm|J_?m55lmqj%%)gq&S!o;{ zmcb#O>GuzSI<$6k>a{ULg({`hwD0#R;=!^wU|D}_4w8WmwfWbY@^ojW!|ngItT{x? zJp}$$@LUClR_2b5K&NMNcg+X$Q4xiUQB*G(vkzz0`2bWXzu~^^-jA)jnU2nV|Ih8l zNJob~|Ff$IIy%&*pD(AQqYGPsidJ9q|KOk9ga$uiT4_EsmX2-_XjHWCEi6#35We2Z zgVmz_XBL6jNc*0_0jUu9a(4x;80|k}5W>;E|0@WViT3q>@rW`i3%5~B4U=zFCG834 N�eHJN56q{{vnS9n=5- literal 35836 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 Date: Fri, 20 Jan 2023 13:55:53 +0100 Subject: [PATCH 21/34] Updated hybrid GKE readme --- blueprints/apigee/hybrid-gke/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blueprints/apigee/hybrid-gke/README.md b/blueprints/apigee/hybrid-gke/README.md index 30589716..ae5c0364 100644 --- a/blueprints/apigee/hybrid-gke/README.md +++ b/blueprints/apigee/hybrid-gke/README.md @@ -25,7 +25,11 @@ The diagram below depicts the architecture. terraform apply ``` -Create an A record in your DNS registrar to point the environment group hostname to the public IP address returned after the terraform configuration was applied. You might need to wait some time until the certificate is provisioned. + Create an A record in your DNS registrar to point the environment group hostname to the public IP address returned after the terraform configuration was applied. You might need to wait some time until the certificate is provisioned. + +5. Install Apigee hybrid using de ansible playbook that is in the ansible folder by running this command + + ansible-playbook playbook.yaml -vvvß ## Testing the blueprint From 3cca689792c16079a991a91c1c85be78764fec18 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Sat, 21 Jan 2023 17:17:51 +0100 Subject: [PATCH 22/34] Check linting for Python dashboard files (#1107) * enable Python lint check for network dashboard * fix linting for network dashboard --- .github/workflows/linting.yml | 5 ++++- blueprints/cloud-operations/network-dashboard/src/main.py | 4 ++-- .../network-dashboard/src/plugins/discover-cai.py | 8 ++++---- .../network-dashboard/src/plugins/series-psa.py | 3 ++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 6d773f9b..a73d8ae2 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -69,4 +69,7 @@ jobs: - name: Check python formatting id: yapf run: | - yapf --style="{based_on_style: google, indent_width: 2, SPLIT_BEFORE_NAMED_ASSIGNS: false}" -p -d tools/*.py + yapf --style="{based_on_style: google, indent_width: 2, SPLIT_BEFORE_NAMED_ASSIGNS: false}" -p -d \ + tools/*.py \ + blueprints/cloud-operations/network-dashboard/src/*py \ + blueprints/cloud-operations/network-dashboard/src/plugins/*py diff --git a/blueprints/cloud-operations/network-dashboard/src/main.py b/blueprints/cloud-operations/network-dashboard/src/main.py index 6db262a6..bd57f18e 100755 --- a/blueprints/cloud-operations/network-dashboard/src/main.py +++ b/blueprints/cloud-operations/network-dashboard/src/main.py @@ -84,8 +84,8 @@ def do_discovery(resources): {k: len(v) for k, v in resources.items() if not isinstance(v, str)})) -def do_init(resources, discovery_root, monitoring_project, folders=None, projects=None, - custom_quota=None): +def do_init(resources, discovery_root, monitoring_project, folders=None, + projects=None, custom_quota=None): '''Calls init plugins to configure keys in the shared resource map. Args: diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py index 1d22cd8e..3041df38 100644 --- a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py +++ b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py @@ -62,8 +62,8 @@ def _handle_discovery(resources, response, data): 'Processes the asset API response and returns parsed resources or next URL.' LOGGER.info('discovery handle request') for result in parse_cai_results(data, 'cai-compute', method='list'): - resource = _handle_resource( - resources, result['assetType'], result['resource']) + resource = _handle_resource(resources, result['assetType'], + result['resource']) if not resource: continue yield resource @@ -214,6 +214,7 @@ def _handle_sql_instances(resource, data): 'availabilityType': data['settings']['availabilityType'], } + def _handle_subnetworks(resource, data): 'Handles subnetwork type resource data.' secondary_ranges = [{ @@ -237,8 +238,7 @@ def _self_link(s): def _url(resources): 'Returns discovery URL' discovery_root = resources['config:discovery_root'] - asset_types = '&'.join( - f'assetTypes={t}' for t in TYPES.values()) + asset_types = '&'.join(f'assetTypes={t}' for t in TYPES.values()) return CAI_URL.format(root=discovery_root, asset_types=asset_types) diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py index 0ea088f9..e9993e07 100644 --- a/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py +++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py @@ -47,7 +47,8 @@ def timeseries(resources): dtype.endswith('ratio')) psa_nets = { k: ipaddress.ip_network('{}/{}'.format(v['address'], v['prefixLength'])) - for k, v in resources['global_addresses'].items() if v['prefixLength'] + for k, v in resources['global_addresses'].items() + if v['prefixLength'] } psa_counts = {} for address, ip_count in _sql_addresses(resources.get('sql_instances', {})): From e780b7f98ccb8c1daa33b3a4f5bb61e360f93136 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jan 2023 15:07:11 +0000 Subject: [PATCH 23/34] Bump cookiejar in /blueprints/apigee/bigquery-analytics/functions/export (#1110) Bumps [cookiejar](https://github.com/bmeck/node-cookiejar) from 2.1.3 to 2.1.4. - [Release notes](https://github.com/bmeck/node-cookiejar/releases) - [Commits](https://github.com/bmeck/node-cookiejar/commits) --- updated-dependencies: - dependency-name: cookiejar dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../functions/export/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json b/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json index 737005be..9ccbef77 100644 --- a/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json +++ b/blueprints/apigee/bigquery-analytics/functions/export/package-lock.json @@ -826,9 +826,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" }, "node_modules/debug": { "version": "2.6.9", @@ -3783,9 +3783,9 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "cookiejar": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", - "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" }, "debug": { "version": "2.6.9", From 7f5c177cfec1769b6fd91bbb748548862fb34d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Legrand?= Date: Wed, 25 Jan 2023 16:02:30 +0100 Subject: [PATCH 24/34] Network Dashboard: PSA support for Filestore and Memorystore (#1106) * Support for Filestore and Memorystore PSA ranges Co-authored-by: Ludovico Magnocavallo --- .../dashboards/quotas-utilization.json | 271 ++++++++++-------- .../deploy-cloud-function/README.md | 2 +- .../deploy-cloud-function/variables.tf | 2 +- .../src/plugins/discover-cai.py | 50 +++- .../src/plugins/series-psa.py | 43 ++- 5 files changed, 231 insertions(+), 137 deletions(-) diff --git a/blueprints/cloud-operations/network-dashboard/dashboards/quotas-utilization.json b/blueprints/cloud-operations/network-dashboard/dashboards/quotas-utilization.json index 1c11bdb7..361eb821 100644 --- a/blueprints/cloud-operations/network-dashboard/dashboards/quotas-utilization.json +++ b/blueprints/cloud-operations/network-dashboard/dashboards/quotas-utilization.json @@ -1,5 +1,4 @@ { - "category": "CUSTOM", "displayName": "quotas_utilization", "mosaicLayout": { "columns": 12, @@ -18,7 +17,6 @@ "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { "alignmentPeriod": "3600s", @@ -40,9 +38,7 @@ } } }, - "width": 6, - "xPos": 0, - "yPos": 0 + "width": 6 }, { "height": 4, @@ -58,7 +54,6 @@ "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { "alignmentPeriod": "3600s", @@ -81,7 +76,6 @@ } }, "width": 6, - "xPos": 0, "yPos": 12 }, { @@ -98,7 +92,6 @@ "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { "alignmentPeriod": "3600s", @@ -121,7 +114,6 @@ } }, "width": 6, - "xPos": 0, "yPos": 8 }, { @@ -138,7 +130,6 @@ "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { "alignmentPeriod": "3600s", @@ -178,7 +169,6 @@ "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { "alignmentPeriod": "3600s", @@ -201,7 +191,6 @@ } }, "width": 6, - "xPos": 0, "yPos": 4 }, { @@ -218,7 +207,6 @@ "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { "alignmentPeriod": "3600s", @@ -241,8 +229,7 @@ } }, "width": 6, - "xPos": 6, - "yPos": 0 + "xPos": 6 }, { "height": 4, @@ -258,7 +245,6 @@ "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { "alignmentPeriod": "3600s", @@ -298,7 +284,6 @@ "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { "alignmentPeriod": "3600s", @@ -330,17 +315,19 @@ }, "dataSets": [ { - "minAlignmentPeriod": "60s", + "minAlignmentPeriod": "3600s", "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { - "alignmentPeriod": "60s", - "perSeriesAligner": "ALIGN_MEAN" + "alignmentPeriod": "3600s", + "perSeriesAligner": "ALIGN_NEXT_OLDER" }, - "filter": "metric.type=\"custom.googleapis.com/netmon/peering_group/routes_dynamic_used_ratio\" resource.type=\"global\"" + "filter": "metric.type=\"custom.googleapis.com/netmon/peering_group/routes_dynamic_used_ratio\" resource.type=\"global\"", + "secondaryAggregation": { + "alignmentPeriod": "60s" + } } } } @@ -353,7 +340,6 @@ } }, "width": 6, - "xPos": 0, "yPos": 20 }, { @@ -366,21 +352,23 @@ }, "dataSets": [ { - "minAlignmentPeriod": "60s", + "minAlignmentPeriod": "3600s", "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { - "alignmentPeriod": "60s", + "alignmentPeriod": "3600s", "crossSeriesReducer": "REDUCE_SUM", "groupByFields": [ "metric.label.\"project\"" ], - "perSeriesAligner": "ALIGN_MEAN" + "perSeriesAligner": "ALIGN_NEXT_OLDER" }, - "filter": "metric.type=\"custom.googleapis.com/netmon/project/firewall_rules_used_ratio\" resource.type=\"global\"" + "filter": "metric.type=\"custom.googleapis.com/netmon/project/firewall_rules_used_ratio\" resource.type=\"global\"", + "secondaryAggregation": { + "alignmentPeriod": "60s" + } } } } @@ -393,8 +381,7 @@ } }, "width": 6, - "xPos": 0, - "yPos": 32 + "yPos": 28 }, { "height": 4, @@ -406,17 +393,19 @@ }, "dataSets": [ { - "minAlignmentPeriod": "60s", + "minAlignmentPeriod": "3600s", "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { - "alignmentPeriod": "60s", - "perSeriesAligner": "ALIGN_MEAN" + "alignmentPeriod": "3600s", + "perSeriesAligner": "ALIGN_NEXT_OLDER" }, - "filter": "metric.type=\"custom.googleapis.com/netmon/firewall_policy/tuples_used_ratio\" resource.type=\"global\"" + "filter": "metric.type=\"custom.googleapis.com/netmon/firewall_policy/tuples_used_ratio\" resource.type=\"global\"", + "secondaryAggregation": { + "alignmentPeriod": "60s" + } } } } @@ -430,7 +419,7 @@ }, "width": 6, "xPos": 6, - "yPos": 28 + "yPos": 24 }, { "height": 4, @@ -442,17 +431,135 @@ }, "dataSets": [ { - "minAlignmentPeriod": "60s", + "minAlignmentPeriod": "3600s", "plotType": "LINE", "targetAxis": "Y1", "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", "timeSeriesFilter": { "aggregation": { - "alignmentPeriod": "60s", - "perSeriesAligner": "ALIGN_MEAN" + "alignmentPeriod": "3600s", + "perSeriesAligner": "ALIGN_NEXT_OLDER" }, - "filter": "metric.type=\"custom.googleapis.com/netmon/subnetwork/addresses_used_ratio\" resource.type=\"global\"" + "filter": "metric.type=\"custom.googleapis.com/netmon/subnetwork/addresses_used_ratio\" resource.type=\"global\"", + "secondaryAggregation": { + "alignmentPeriod": "60s" + } + } + } + } + ], + "timeshiftDuration": "0s", + "yAxis": { + "label": "y1Axis", + "scale": "LINEAR" + } + } + }, + "width": 6, + "yPos": 16 + }, + { + "height": 4, + "widget": { + "title": "Project static routes used", + "xyChart": { + "chartOptions": { + "mode": "COLOR" + }, + "dataSets": [ + { + "minAlignmentPeriod": "3600s", + "plotType": "LINE", + "targetAxis": "Y1", + "timeSeriesQuery": { + "timeSeriesFilter": { + "aggregation": { + "alignmentPeriod": "3600s", + "crossSeriesReducer": "REDUCE_SUM", + "groupByFields": [ + "metric.label.\"project\"" + ], + "perSeriesAligner": "ALIGN_NEXT_OLDER" + }, + "filter": "metric.type=\"custom.googleapis.com/netmon/project/routes_static_used_ratio\" resource.type=\"global\"", + "secondaryAggregation": { + "alignmentPeriod": "60s" + } + } + } + } + ], + "timeshiftDuration": "0s", + "yAxis": { + "label": "y1Axis", + "scale": "LINEAR" + } + } + }, + "width": 6, + "xPos": 6, + "yPos": 20 + }, + { + "height": 4, + "widget": { + "title": "Peering group static routes used", + "xyChart": { + "chartOptions": { + "mode": "COLOR" + }, + "dataSets": [ + { + "minAlignmentPeriod": "3600s", + "plotType": "LINE", + "targetAxis": "Y1", + "timeSeriesQuery": { + "timeSeriesFilter": { + "aggregation": { + "alignmentPeriod": "3600s", + "perSeriesAligner": "ALIGN_NEXT_OLDER" + }, + "filter": "metric.type=\"custom.googleapis.com/netmon/peering_group/routes_static_used_ratio\" resource.type=\"global\"", + "secondaryAggregation": { + "alignmentPeriod": "60s" + } + } + } + } + ], + "timeshiftDuration": "0s", + "yAxis": { + "label": "y1Axis", + "scale": "LINEAR" + } + } + }, + "width": 6, + "yPos": 24 + }, + { + "height": 4, + "widget": { + "title": "Addresses used ratio per psa range [NEXT OLDER]", + "xyChart": { + "chartOptions": { + "mode": "COLOR" + }, + "dataSets": [ + { + "minAlignmentPeriod": "3600s", + "plotType": "LINE", + "targetAxis": "Y1", + "timeSeriesQuery": { + "timeSeriesFilter": { + "aggregation": { + "alignmentPeriod": "3600s", + "perSeriesAligner": "ALIGN_NEXT_OLDER" + }, + "filter": "metric.type=\"custom.googleapis.com/netmon/network/psa/addresses_used_ratio\" resource.type=\"global\"", + "secondaryAggregation": { + "alignmentPeriod": "60s" + } } } } @@ -467,88 +574,6 @@ "width": 6, "xPos": 6, "yPos": 16 - }, - { - "height": 4, - "widget": { - "title": "Project static routes used", - "xyChart": { - "chartOptions": { - "mode": "COLOR" - }, - "dataSets": [ - { - "minAlignmentPeriod": "60s", - "plotType": "LINE", - "targetAxis": "Y1", - "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", - "timeSeriesFilter": { - "aggregation": { - "alignmentPeriod": "60s", - "crossSeriesReducer": "REDUCE_SUM", - "groupByFields": [ - "metric.label.\"project\"" - ], - "perSeriesAligner": "ALIGN_MEAN" - }, - "filter": "metric.type=\"custom.googleapis.com/netmon/project/routes_static_used_ratio\" resource.type=\"global\"", - "secondaryAggregation": { - "alignmentPeriod": "60s", - "perSeriesAligner": "ALIGN_NONE" - } - } - } - } - ], - "thresholds": [], - "timeshiftDuration": "0s", - "yAxis": { - "label": "y1Axis", - "scale": "LINEAR" - } - } - }, - "width": 6, - "xPos": 0, - "yPos": 24 - }, - { - "height": 4, - "widget": { - "title": "Peering group static routes used", - "xyChart": { - "chartOptions": { - "mode": "COLOR" - }, - "dataSets": [ - { - "minAlignmentPeriod": "60s", - "plotType": "LINE", - "targetAxis": "Y1", - "timeSeriesQuery": { - "apiSource": "DEFAULT_CLOUD", - "timeSeriesFilter": { - "aggregation": { - "alignmentPeriod": "60s", - "perSeriesAligner": "ALIGN_MEAN" - }, - "filter": "metric.type=\"custom.googleapis.com/netmon/peering_group/routes_static_used_ratio\" resource.type=\"global\"" - } - } - } - ], - "thresholds": [], - "timeshiftDuration": "0s", - "yAxis": { - "label": "y1Axis", - "scale": "LINEAR" - } - } - }, - "width": 6, - "xPos": 0, - "yPos": 28 } ] } diff --git a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/README.md b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/README.md index bf1d7b5c..aa0bdf42 100644 --- a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/README.md +++ b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/README.md @@ -74,7 +74,7 @@ dashboard_json_path = "../dashboards/quotas-utilization.json" | [name](variables.tf#L75) | Name used to create Cloud Function related resources. | string | | "net-dash" | | [project_create_config](variables.tf#L81) | Optional configuration if project creation is required. | object({…}) | | null | | [region](variables.tf#L95) | Compute region where the Cloud Function will be deployed. | string | | "europe-west1" | -| [schedule_config](variables.tf#L101) | Schedule timer configuration in crontab format. | string | | "0/30 * * * *" | +| [schedule_config](variables.tf#L101) | Schedule timer configuration in crontab format. | string | | "*/30 * * * *" | ## Outputs diff --git a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf index ab59f91f..680b689d 100644 --- a/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf +++ b/blueprints/cloud-operations/network-dashboard/deploy-cloud-function/variables.tf @@ -101,5 +101,5 @@ variable "region" { variable "schedule_config" { description = "Schedule timer configuration in crontab format." type = string - default = "0/30 * * * *" + default = "*/30 * * * *" } diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py index 3041df38..1ac62d06 100644 --- a/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py +++ b/blueprints/cloud-operations/network-dashboard/src/plugins/discover-cai.py @@ -39,7 +39,9 @@ TYPES = { 'subnetworks': 'compute.googleapis.com/Subnetwork', 'routers': 'compute.googleapis.com/Router', 'routes': 'compute.googleapis.com/Route', - 'sql_instances': 'sqladmin.googleapis.com/Instance' + 'sql_instances': 'sqladmin.googleapis.com/Instance', + 'filestore_instances': 'file.googleapis.com/Instance', + 'memorystore_instances': 'redis.googleapis.com/Instance', } NAMES = {v: k for k, v in TYPES.items()} @@ -82,10 +84,16 @@ def _handle_resource(resources, asset_type, data): # e.g. assetType = GlobalAddress but discoveryName = Address resource_name = NAMES[asset_type] resource = { - 'id': attrs.get('id'), - 'name': attrs['name'], - 'self_link': _self_link(attrs['selfLink']), - 'assetType': asset_type + 'id': + attrs.get('id'), + 'name': + attrs['name'], + # Some resources (ex: Filestore) don't have a self_link, using parent + name in that case + 'self_link': + f'{data["parent"]}/{attrs["name"]}' + if not 'selfLink' in attrs else _self_link(attrs['selfLink']), + 'assetType': + asset_type } # derive parent type and id and skip if parent is not within scope parent_data = _get_parent(data['parent'], resources) @@ -212,6 +220,38 @@ def _handle_sql_instances(resource, data): ], 'region': data['region'], 'availabilityType': data['settings']['availabilityType'], + 'network': data['settings']['ipConfiguration']['privateNetwork'] + } + + +def _handle_filestore_instances(resource, data): + 'Handles filestore instance type resource data.' + return { + # Getting only the instance name, removing the rest + 'name': data['name'].split('/')[-1], + # Is a list but for now, only one network is supported for Filestore + 'network': data['networks'][0]['network'], + 'reservedIpRange': data['networks'][0]['reservedIpRange'], + 'ipAddresses': data['networks'][0]['ipAddresses'] + } + + +def _handle_memorystore_instances(resource, data): + 'Handles Memorystore (Redis) instance type resource data.' + return { + # Getting only the instance name, removing the rest + 'name': + data['name'].split('/')[-1], + 'locationId': + data['locationId'], + 'replicaCount': + 0 if not 'replicaCount' in data else data['replicaCount'], + 'network': + data['authorizedNetwork'], + 'reservedIpRange': + '' if not 'reservedIpRange' in data else data['reservedIpRange'], + 'host': + '' if not 'host' in data else data['host'], } diff --git a/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py b/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py index e9993e07..82e06009 100644 --- a/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py +++ b/blueprints/cloud-operations/network-dashboard/src/plugins/series-psa.py @@ -34,7 +34,28 @@ def _sql_addresses(sql_instances): if not v['ipAddresses']: continue # 1 IP for the instance + 1 IP for the ILB + 1 IP if HA - yield v['ipAddresses'][0], 2 if v['availabilityType'] != 'REGIONAL' else 3 + yield v['ipAddresses'][ + 0], 2 if v['availabilityType'] != 'REGIONAL' else 3, v['network'] + + +def _filestore_addresses(filestore_instances): + 'Returns counts of Filestore instances per PSA range.' + for v in filestore_instances.values(): + if not v['ipAddresses'] or not v['reservedIpRange']: + continue + # Subnet size (reservedIpRange) can be /29, /26 or /24 + yield v['ipAddresses'][0], ipaddress.ip_network( + v['reservedIpRange']).num_addresses, v['network'] + + +def _memorystore_addresses(memorystore_instances): + 'Returns counts of Memorystore (Redis) instances per PSA range.' + for v in memorystore_instances.values(): + if not v['reservedIpRange'] or v['reservedIpRange'] == '': + continue + # Subnet size (reservedIpRange) can be minimum /28 or /29 + yield v['host'], ipaddress.ip_network( + v['reservedIpRange']).num_addresses, v['network'] @register_timeseries @@ -46,26 +67,34 @@ def timeseries(resources): ('project', 'network', 'subnetwork'), dtype.endswith('ratio')) psa_nets = { - k: ipaddress.ip_network('{}/{}'.format(v['address'], v['prefixLength'])) - for k, v in resources['global_addresses'].items() - if v['prefixLength'] + k: { + 'network_link': + v['network'], + 'network_prefix': + ipaddress.ip_network('{}/{}'.format(v['address'], + v['prefixLength'])) + } for k, v in resources['global_addresses'].items() if v['prefixLength'] } psa_counts = {} - for address, ip_count in _sql_addresses(resources.get('sql_instances', {})): + for address, ip_count, network in itertools.chain( + _sql_addresses(resources.get('sql_instances', {})), + _filestore_addresses(resources.get('filestore_instances', {})), + _memorystore_addresses(resources.get('memorystore_instances', {}))): ip_address = ipaddress.ip_address(address) for k, v in psa_nets.items(): - if ip_address in v: + if network == v['network_link'] and ip_address in v['network_prefix']: psa_counts[k] = psa_counts.get(k, 0) + ip_count break for k, v in psa_counts.items(): - max_ips = psa_nets[k].num_addresses - 4 + max_ips = psa_nets[k]['network_prefix'].num_addresses - 4 psa_range = resources['global_addresses'][k] labels = { 'network': psa_range['network'], 'project': psa_range['project_id'], 'psa_range': psa_range['name'] } + yield TimeSeries('network/psa/addresses_available', max_ips, labels) yield TimeSeries('network/psa/addresses_used', v, labels) yield TimeSeries('network/psa/addresses_used_ratio', From 793596c61dd7836a0aded6bf1797d35b911e3e0e Mon Sep 17 00:00:00 2001 From: Julio Diez Date: Thu, 26 Jan 2023 12:25:54 +0100 Subject: [PATCH 25/34] Add HTTPS frontend with SNEG example --- modules/net-glb/README.md | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/modules/net-glb/README.md b/modules/net-glb/README.md index 4b6d2435..12584b46 100644 --- a/modules/net-glb/README.md +++ b/modules/net-glb/README.md @@ -438,6 +438,46 @@ module "glb-0" { # tftest modules=1 resources=5 ``` +Serverless NEGs don't use the port name but it should be set to `http`. An HTTPS frontend requires the protocol to be set to `HTTPS`, and the port name field will infer this value if omitted so you need to set it explicitly: + +```hcl +module "glb-0" { + source = "./fabric/modules/net-glb" + project_id = "myprj" + name = "glb-test-0" + backend_service_configs = { + default = { + backends = [ + { backend = "neg-0" } + ] + health_checks = [] + port_name = "http" + } + } + # with a single serverless NEG the implied default health check is not needed + health_check_configs = {} + neg_configs = { + neg-0 = { + cloudrun = { + region = "europe-west8" + target_service = { + name = "hello" + } + } + } + } + protocol = "HTTPS" + ssl_certificates = { + managed_configs = { + default = { + domains = ["glb-test-0.example.org"] + } + } + } +} +# tftest ... +``` + ### URL Map The module exposes the full URL map resource configuration, with some minor changes to the interface to decrease verbosity, and support for aliasing backend services via keys. From 4d6561712ec67f3f4e100fc30bdcd7d6a68f7e31 Mon Sep 17 00:00:00 2001 From: Julio Diez Date: Thu, 26 Jan 2023 17:56:20 +0100 Subject: [PATCH 26/34] Fix tftest and format --- modules/net-glb/README.md | 4 +-- .../modules/net_glb/examples/https-sneg.yaml | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 tests/modules/net_glb/examples/https-sneg.yaml diff --git a/modules/net-glb/README.md b/modules/net-glb/README.md index 12584b46..cbf47588 100644 --- a/modules/net-glb/README.md +++ b/modules/net-glb/README.md @@ -451,7 +451,7 @@ module "glb-0" { { backend = "neg-0" } ] health_checks = [] - port_name = "http" + port_name = "http" } } # with a single serverless NEG the implied default health check is not needed @@ -475,7 +475,7 @@ module "glb-0" { } } } -# tftest ... +# tftest modules=1 resources=6 inventory=https-sneg.yaml ``` ### URL Map diff --git a/tests/modules/net_glb/examples/https-sneg.yaml b/tests/modules/net_glb/examples/https-sneg.yaml new file mode 100644 index 00000000..0876484c --- /dev/null +++ b/tests/modules/net_glb/examples/https-sneg.yaml @@ -0,0 +1,35 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.glb-0.google_compute_backend_service.default["default"]: + port_name: http + protocol: HTTPS + module.glb-0.google_compute_global_forwarding_rule.default: + load_balancing_scheme: EXTERNAL + port_range: '443' + module.glb-0.google_compute_region_network_endpoint_group.serverless["neg-0"]: + cloud_run: + - service: hello + tag: null + url_mask: null + +counts: + google_compute_backend_service: 1 + google_compute_global_forwarding_rule: 1 + google_compute_managed_ssl_certificate: 1 + google_compute_region_network_endpoint_group: 1 + google_compute_target_https_proxy: 1 + google_compute_url_map: 1 + From d537897cbf247388776ef5c737d2da64e45898f3 Mon Sep 17 00:00:00 2001 From: Julio Castillo Date: Thu, 26 Jan 2023 17:08:20 +0000 Subject: [PATCH 27/34] Remove trailing whitespaces --- tests/modules/net_glb/examples/https-sneg.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/modules/net_glb/examples/https-sneg.yaml b/tests/modules/net_glb/examples/https-sneg.yaml index 0876484c..fa0823cb 100644 --- a/tests/modules/net_glb/examples/https-sneg.yaml +++ b/tests/modules/net_glb/examples/https-sneg.yaml @@ -1,4 +1,4 @@ -# Copyright 2023 Google LLC +# Copyright 2023 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -9,7 +9,7 @@ # 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 +# See the License for the specific language governing permissions and # limitations under the License. values: From bed7e05d09a563d699266c832ed53fe95eaffb24 Mon Sep 17 00:00:00 2001 From: fdhaussy Date: Fri, 27 Jan 2023 15:37:57 +0100 Subject: [PATCH 28/34] add support for deployment_type and api_proxy_type in google_apigee_environment resource --- modules/apigee/README.md | 24 +++++++++++-------- modules/apigee/main.tf | 10 ++++---- modules/apigee/variables.tf | 6 +++-- .../test.env_only_with_api_proxy_type.tfvars | 13 ++++++++++ .../test.env_only_with_deployment_type.tfvars | 13 ++++++++++ tests/modules/apigee/fixture/variables.tf | 6 +++-- tests/modules/apigee/test_plan.py | 12 ++++++++++ 7 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars create mode 100644 tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars diff --git a/modules/apigee/README.md b/modules/apigee/README.md index fb26c1e7..02b1d13f 100644 --- a/modules/apigee/README.md +++ b/modules/apigee/README.md @@ -25,14 +25,18 @@ module "apigee" { } environments = { apis-test = { - display_name = "APIs test" - description = "APIs Test" - envgroups = ["test"] + display_name = "APIs test" + description = "APIs Test" + deployment_type = "ARCHIVE" + api_proxy_type = "PROGRAMMABLE" + envgroups = ["test"] } apis-prod = { - display_name = "APIs prod" - description = "APIs prod" - envgroups = ["prod"] + display_name = "APIs prod" + description = "APIs prod" + deployment_type = "PROXY" + api_proxy_type = "CONFIGURABLE" + envgroups = ["prod"] iam = { "roles/viewer" = ["group:devops@myorg.com"] } @@ -169,12 +173,12 @@ module "apigee" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [project_id](variables.tf#L75) | Project ID. | string | ✓ | | +| [project_id](variables.tf#L77) | 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#L47) | Instances. | map(object({…})) | | null | -| [organization](variables.tf#L61) | Apigee organization. If set to null the organization must already exist. | object({…}) | | null | +| [environments](variables.tf#L32) | Environments. | map(object({…})) | | null | +| [instances](variables.tf#L49) | Instances. | map(object({…})) | | null | +| [organization](variables.tf#L63) | 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 fe34a738..ec5781d1 100644 --- a/modules/apigee/main.tf +++ b/modules/apigee/main.tf @@ -40,10 +40,12 @@ resource "google_apigee_envgroup" "envgroups" { } resource "google_apigee_environment" "environments" { - for_each = local.environments - name = each.key - display_name = each.value.display_name - description = each.value.description + for_each = local.environments + name = each.key + display_name = each.value.display_name + description = each.value.description + deployment_type = each.value.deployment_type != null ? each.value.deployment_type : null + api_proxy_type = each.value.api_proxy_type != null ? each.value.api_proxy_type : null dynamic "node_config" { for_each = try(each.value.node_config, null) != null ? [""] : [] content { diff --git a/modules/apigee/variables.tf b/modules/apigee/variables.tf index 266f0d34..81cf77f6 100644 --- a/modules/apigee/variables.tf +++ b/modules/apigee/variables.tf @@ -32,8 +32,10 @@ variable "envgroups" { variable "environments" { description = "Environments." type = map(object({ - display_name = optional(string) - description = optional(string, "Terraform-managed") + display_name = optional(string) + description = optional(string, "Terraform-managed") + deployment_type = optional(string) + api_proxy_type = optional(string) node_config = optional(object({ min_node_count = optional(number) max_node_count = optional(number) diff --git a/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars b/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars new file mode 100644 index 00000000..cbb40463 --- /dev/null +++ b/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars @@ -0,0 +1,13 @@ +project_id = "my-project" +environments = { + apis-test = { + display_name = "APIs test" + description = "APIs Test" + api_proxy_type = "PROGRAMMABLE" + envgroups = ["test"] + node_config = { + min_node_count = 2 + max_node_count = 5 + } + } +} diff --git a/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars b/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars new file mode 100644 index 00000000..48ef24e6 --- /dev/null +++ b/tests/modules/apigee/fixture/test.env_only_with_deployment_type.tfvars @@ -0,0 +1,13 @@ +project_id = "my-project" +environments = { + apis-test = { + display_name = "APIs test" + description = "APIs Test" + deployment_type = "ARCHIVE" + envgroups = ["test"] + node_config = { + min_node_count = 2 + max_node_count = 5 + } + } +} diff --git a/tests/modules/apigee/fixture/variables.tf b/tests/modules/apigee/fixture/variables.tf index 266f0d34..81cf77f6 100644 --- a/tests/modules/apigee/fixture/variables.tf +++ b/tests/modules/apigee/fixture/variables.tf @@ -32,8 +32,10 @@ variable "envgroups" { variable "environments" { description = "Environments." type = map(object({ - display_name = optional(string) - description = optional(string, "Terraform-managed") + display_name = optional(string) + description = optional(string, "Terraform-managed") + deployment_type = optional(string) + api_proxy_type = optional(string) node_config = optional(object({ min_node_count = optional(number) max_node_count = optional(number) diff --git a/tests/modules/apigee/test_plan.py b/tests/modules/apigee/test_plan.py index e693ddbb..d16ef296 100644 --- a/tests/modules/apigee/test_plan.py +++ b/tests/modules/apigee/test_plan.py @@ -54,6 +54,18 @@ def test_env_only(plan_runner): 'google_apigee_envgroup_attachment.envgroup_attachments': 1, } +def test_env_only_with_deployment_type(plan_runner): + "Test that creates an environment in an existing environment group, with deployment_type set." + _, resources = plan_runner(tf_var_file='test.env_only_with_deployment_type.tfvars') + assert [r['values'].get('deployment_type') for r in resources + ] == [None, 'ARCHIVE'] + +def test_env_only_with_api_proxy_type(plan_runner): + "Test that creates an environment in an existing environment group, with api_proxy_type set." + _, resources = plan_runner(tf_var_file='test.env_only_with_api_proxy_type.tfvars') + assert [r['values'].get('api_proxy_type') for r in resources + ] == [None, 'PROGRAMMABLE'] + def test_instance_only(plan_runner): "Test that creates only an instance." _, resources = plan_runner(tf_var_file='test.instance_only.tfvars') From a291dca63b93e524cf08011930bdca292f2b9262 Mon Sep 17 00:00:00 2001 From: fdhaussy Date: Fri, 27 Jan 2023 16:20:33 +0100 Subject: [PATCH 29/34] chore: linting issue in tfvars file --- .../fixture/test.env_only_with_api_proxy_type.tfvars | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars b/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars index cbb40463..2a9164bf 100644 --- a/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars +++ b/tests/modules/apigee/fixture/test.env_only_with_api_proxy_type.tfvars @@ -1,10 +1,10 @@ project_id = "my-project" environments = { apis-test = { - display_name = "APIs test" - description = "APIs Test" - api_proxy_type = "PROGRAMMABLE" - envgroups = ["test"] + display_name = "APIs test" + description = "APIs Test" + api_proxy_type = "PROGRAMMABLE" + envgroups = ["test"] node_config = { min_node_count = 2 max_node_count = 5 From 22c26e319fe4c72f1df219608e5ebcb5edff45c3 Mon Sep 17 00:00:00 2001 From: fdhaussy Date: Fri, 27 Jan 2023 17:08:35 +0100 Subject: [PATCH 30/34] fix: remove unuseful ternary --- modules/apigee/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/apigee/main.tf b/modules/apigee/main.tf index ec5781d1..f1c71ec1 100644 --- a/modules/apigee/main.tf +++ b/modules/apigee/main.tf @@ -44,8 +44,8 @@ resource "google_apigee_environment" "environments" { name = each.key display_name = each.value.display_name description = each.value.description - deployment_type = each.value.deployment_type != null ? each.value.deployment_type : null - api_proxy_type = each.value.api_proxy_type != null ? each.value.api_proxy_type : null + deployment_type = each.value.deployment_type + api_proxy_type = each.value.api_proxy_type dynamic "node_config" { for_each = try(each.value.node_config, null) != null ? [""] : [] content { From edd3a824539c2616e1d1fd531a2e517e38776123 Mon Sep 17 00:00:00 2001 From: Ayman Farhat <823713+aymanfarhat@users.noreply.github.com> Date: Fri, 27 Jan 2023 21:38:01 +0100 Subject: [PATCH 31/34] Include cloudbuild API in project module (#1116) * Include cloudbuild API in project module * Increase number of resources --- blueprints/data-solutions/data-platform-foundations/README.md | 2 +- modules/project/service-accounts.tf | 3 ++- tests/blueprints/apigee/bigquery-analytics/basic.yaml | 2 +- .../asset_inventory_feed_remediation/test_plan.py | 2 +- .../scheduled_asset_inventory_export_bq/test_plan.py | 2 +- .../unmanaged_instances_healthcheck/test_plan.py | 2 +- .../data_solutions/data_platform_foundations/test_plan.py | 2 +- tests/blueprints/gke/binauthz/test_plan.py | 2 +- .../networking/private_cloud_function_from_onprem/test_plan.py | 2 +- tests/blueprints/serverless/api_gateway/test_plan.py | 2 +- tests/fast/stages/s00_bootstrap/simple.yaml | 2 +- 11 files changed, 12 insertions(+), 11 deletions(-) diff --git a/blueprints/data-solutions/data-platform-foundations/README.md b/blueprints/data-solutions/data-platform-foundations/README.md index 30cdab83..b038cfe4 100644 --- a/blueprints/data-solutions/data-platform-foundations/README.md +++ b/blueprints/data-solutions/data-platform-foundations/README.md @@ -219,7 +219,7 @@ module "data-platform" { prefix = "myprefix" } -# tftest modules=39 resources=286 +# tftest modules=39 resources=287 ``` ## Customizations diff --git a/modules/project/service-accounts.tf b/modules/project/service-accounts.tf index abf34cae..b25c6126 100644 --- a/modules/project/service-accounts.tf +++ b/modules/project/service-accounts.tf @@ -75,7 +75,8 @@ locals { "gkehub.googleapis.com", "pubsub.googleapis.com", "secretmanager.googleapis.com", - "sqladmin.googleapis.com" + "sqladmin.googleapis.com", + "cloudbuild.googleapis.com", ] service_accounts_cmek_service_keys = distinct(flatten([ for s in keys(var.service_encryption_key_ids) : [ diff --git a/tests/blueprints/apigee/bigquery-analytics/basic.yaml b/tests/blueprints/apigee/bigquery-analytics/basic.yaml index 2b044dcb..d89eaef5 100644 --- a/tests/blueprints/apigee/bigquery-analytics/basic.yaml +++ b/tests/blueprints/apigee/bigquery-analytics/basic.yaml @@ -14,4 +14,4 @@ counts: modules: 9 - resources: 60 + resources: 61 diff --git a/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py b/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py index df03e144..497af6be 100644 --- a/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py +++ b/tests/blueprints/cloud_operations/asset_inventory_feed_remediation/test_plan.py @@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner() assert len(modules) == 6 - assert len(resources) == 18 + assert len(resources) == 19 diff --git a/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py b/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py index c5394839..3bcc6344 100644 --- a/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py +++ b/tests/blueprints/cloud_operations/scheduled_asset_inventory_export_bq/test_plan.py @@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner() assert len(modules) == 7 - assert len(resources) == 29 + assert len(resources) == 30 diff --git a/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py b/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py index c79be049..b1f0fba3 100644 --- a/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py +++ b/tests/blueprints/cloud_operations/unmanaged_instances_healthcheck/test_plan.py @@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner() assert len(modules) == 10 - assert len(resources) == 31 + assert len(resources) == 32 diff --git a/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py b/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py index 17563647..93961b5e 100644 --- a/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py +++ b/tests/blueprints/data_solutions/data_platform_foundations/test_plan.py @@ -22,4 +22,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner(FIXTURES_DIR) assert len(modules) == 38 - assert len(resources) == 285 + assert len(resources) == 286 diff --git a/tests/blueprints/gke/binauthz/test_plan.py b/tests/blueprints/gke/binauthz/test_plan.py index cf012c06..b4437b6f 100644 --- a/tests/blueprints/gke/binauthz/test_plan.py +++ b/tests/blueprints/gke/binauthz/test_plan.py @@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner() assert len(modules) == 13 - assert len(resources) == 43 + assert len(resources) == 44 diff --git a/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py b/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py index 2b3f8d7f..81225db3 100644 --- a/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py +++ b/tests/blueprints/networking/private_cloud_function_from_onprem/test_plan.py @@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner() assert len(modules) == 10 - assert len(resources) == 38 + assert len(resources) == 39 diff --git a/tests/blueprints/serverless/api_gateway/test_plan.py b/tests/blueprints/serverless/api_gateway/test_plan.py index 6cf48a87..9d658398 100644 --- a/tests/blueprints/serverless/api_gateway/test_plan.py +++ b/tests/blueprints/serverless/api_gateway/test_plan.py @@ -16,4 +16,4 @@ def test_resources(e2e_plan_runner): "Test that plan works and the numbers of resources is as expected." modules, resources = e2e_plan_runner() assert len(modules) == 7 - assert len(resources) == 31 + assert len(resources) == 32 diff --git a/tests/fast/stages/s00_bootstrap/simple.yaml b/tests/fast/stages/s00_bootstrap/simple.yaml index 703b84b4..ed0d7738 100644 --- a/tests/fast/stages/s00_bootstrap/simple.yaml +++ b/tests/fast/stages/s00_bootstrap/simple.yaml @@ -24,7 +24,7 @@ counts: google_project_iam_binding: 9 google_project_iam_member: 1 google_project_service: 29 - google_project_service_identity: 2 + google_project_service_identity: 3 google_service_account: 3 google_service_account_iam_binding: 3 google_storage_bucket: 4 From 83a0916bff99c4e7c0457c1412c7a5b57d3facd4 Mon Sep 17 00:00:00 2001 From: Ludo Date: Sat, 28 Jan 2023 09:27:31 +0100 Subject: [PATCH 32/34] add missing newline --- fast/stages/00-bootstrap/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/fast/stages/00-bootstrap/README.md b/fast/stages/00-bootstrap/README.md index 6e0e1b55..889b0bce 100644 --- a/fast/stages/00-bootstrap/README.md +++ b/fast/stages/00-bootstrap/README.md @@ -264,6 +264,7 @@ terraform init terraform apply \ -var bootstrap_user=$(gcloud config list --format 'value(core.account)') ``` + > If you see an error related to project name already exists, please make sure the project name is unique or the project was not deleted recently Once the initial `apply` completes successfully, configure a remote backend using the new GCS bucket, and impersonation on the automation service account for this stage. To do this you can use the generated `providers.tf` file if you have configured output files as described above, or extract its contents from Terraform's output, then migrate state with `terraform init`: From 7b96ed429c956165dfce19d8c214d578574a59f3 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Sat, 28 Jan 2023 09:41:22 +0100 Subject: [PATCH 33/34] add missing role for initial user (#1118) --- fast/stages/00-bootstrap/organization.tf | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fast/stages/00-bootstrap/organization.tf b/fast/stages/00-bootstrap/organization.tf index 0700d564..33b87820 100644 --- a/fast/stages/00-bootstrap/organization.tf +++ b/fast/stages/00-bootstrap/organization.tf @@ -23,9 +23,10 @@ locals { "roles/browser" = [ "domain:${var.organization.domain}" ] - "roles/logging.admin" = [ - module.automation-tf-bootstrap-sa.iam_email - ] + "roles/logging.admin" = concat( + [module.automation-tf-bootstrap-sa.iam_email], + local._iam_bootstrap_user + ) "roles/owner" = local._iam_bootstrap_user "roles/resourcemanager.folderAdmin" = [ module.automation-tf-resman-sa.iam_email From ac8698b3dfb4332f857fc123717f911d023261e8 Mon Sep 17 00:00:00 2001 From: Ludo Date: Sun, 29 Jan 2023 13:36:54 +0100 Subject: [PATCH 34/34] update changelog --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd8b0c9..59e4d371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ All notable changes to this project will be documented in this file. ### BLUEPRINTS +- [[#1106](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1106)] Network Dashboard: PSA support for Filestore and Memorystore ([aurelienlegrand](https://github.com/aurelienlegrand)) +- [[#1110](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1110)] Bump cookiejar from 2.1.3 to 2.1.4 in /blueprints/apigee/bigquery-analytics/functions/export ([dependabot[bot]](https://github.com/dependabot[bot])) +- [[#1097](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1097)] Use terraform resource to activate Anthos Service Mesh ([wiktorn](https://github.com/wiktorn)) +- [[#1104](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1104)] Updated apigee hybrid for gke README ([apichick](https://github.com/apichick)) +- [[#1107](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1107)] Check linting for Python dashboard files ([ludoo](https://github.com/ludoo)) +- [[#1102](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1102)] Improvements in apigee hybrid-gke: now using workload identity and GLB ([apichick](https://github.com/apichick)) +- [[#1098](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1098)] Add shared-vpc support on data-playground blueprint ([lcaggio](https://github.com/lcaggio)) +- [[#1095](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1095)] [Data Platform] Fix Table in readme ([lcaggio](https://github.com/lcaggio)) +- [[#1089](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1089)] Update Data Platform ([lcaggio](https://github.com/lcaggio)) - [[#1081](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1081)] Apigee hybrid on GKE ([apichick](https://github.com/apichick)) - [[#1082](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1082)] Fixes in Apigee Bigquery Analytics blueprint ([apichick](https://github.com/apichick)) - [[#1071](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1071)] Moved apigee bigquery analytics blueprint, added apigee network patterns ([apichick](https://github.com/apichick)) @@ -20,6 +29,8 @@ All notable changes to this project will be documented in this file. ### DOCUMENTATION +- [[#1101](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1101)] First batch of testing updates to core modules ([juliocc](https://github.com/juliocc)) +- [[#1089](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1089)] Update Data Platform ([lcaggio](https://github.com/lcaggio)) - [[#1084](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1084)] Fixes in Apigee blueprints README files ([apichick](https://github.com/apichick)) - [[#1081](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1081)] Apigee hybrid on GKE ([apichick](https://github.com/apichick)) - [[#1074](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1074)] Adding new section for Authentication issues ([agutta](https://github.com/agutta)) @@ -28,6 +39,9 @@ All notable changes to this project will be documented in this file. ### FAST +- [[#1118](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1118)] Add missing logging admin role for initial user ([ludoo](https://github.com/ludoo)) +- [[#1099](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1099)] Fix destroy in stage 1 outputs ([ludoo](https://github.com/ludoo)) +- [[#1089](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1089)] Update Data Platform ([lcaggio](https://github.com/lcaggio)) - [[#1085](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1085)] fix restricted services not being added to the perimeter configurations ([drebes](https://github.com/drebes)) - [[#1057](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1057)] Adding new file FAQ and an image ([agutta](https://github.com/agutta)) - [[#1054](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1054)] FAST: fix typo in bootstrap stage README ([agutta](https://github.com/agutta)) @@ -35,6 +49,14 @@ All notable changes to this project will be documented in this file. ### MODULES +- [[#1116](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1116)] Include cloudbuild API in project module ([aymanfarhat](https://github.com/aymanfarhat)) +- [[#1115](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1115)] add new parameters support in apigee module ([blackillzone](https://github.com/blackillzone)) +- [[#1112](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1112)] Add HTTPS frontend with SNEG example ([juliodiez](https://github.com/juliodiez)) +- [[#1097](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1097)] Use terraform resource to activate Anthos Service Mesh ([wiktorn](https://github.com/wiktorn)) +- [[#1101](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1101)] First batch of testing updates to core modules ([juliocc](https://github.com/juliocc)) +- [[#1098](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1098)] Add shared-vpc support on data-playground blueprint ([lcaggio](https://github.com/lcaggio)) +- [[#1096](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1096)] [VPC-SC] Add support for scoped Policies ([lcaggio](https://github.com/lcaggio)) +- [[#1093](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1093)] Added tags to gke-cluster module ([apichick](https://github.com/apichick)) - [[#1078](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1078)] Fixed delete_rule in compute-mig module for stateful disks ([rosmo](https://github.com/rosmo)) - [[#1080](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1080)] Added device_name field to compute-vm attached_disks parameter ([rosmo](https://github.com/rosmo)) - [[#1079](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1079)] Reorder org policy rules ([juliocc](https://github.com/juliocc)) @@ -53,6 +75,8 @@ All notable changes to this project will be documented in this file. ### TOOLS +- [[#1107](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1107)] Check linting for Python dashboard files ([ludoo](https://github.com/ludoo)) +- [[#1101](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1101)] First batch of testing updates to core modules ([juliocc](https://github.com/juliocc)) - [[#1091](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1091)] Fix check_documentation output ([juliocc](https://github.com/juliocc)) - [[#1053](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1053)] Extend inventory-based testing to examples ([juliocc](https://github.com/juliocc))