From 19565c5badf55567d600c47a504575e29457723a Mon Sep 17 00:00:00 2001 From: Luca Prete Date: Fri, 14 Jan 2022 08:19:02 +0100 Subject: [PATCH 1/3] [#411] XLB module - Initial commit (#416) * [#411] XLB module - Initial commit * formatting * Refactoring and examples tests * Update copyright to 2022 * Remove splat syntax from outputs * Fix linting --- modules/net-xlb/README.md | 451 ++++++++ modules/net-xlb/backend_services.tf | 212 ++++ modules/net-xlb/global_forwarding_rule.tf | 41 + modules/net-xlb/health_checks.tf | 142 +++ modules/net-xlb/ip_address.tf | 23 + modules/net-xlb/outputs.tf | 60 ++ modules/net-xlb/ssl_certificates.tf | 51 + modules/net-xlb/target_proxy.tf | 50 + modules/net-xlb/url_map.tf | 1180 +++++++++++++++++++++ modules/net-xlb/variables.tf | 218 ++++ modules/net-xlb/versions.tf | 27 + 11 files changed, 2455 insertions(+) create mode 100644 modules/net-xlb/README.md create mode 100644 modules/net-xlb/backend_services.tf create mode 100644 modules/net-xlb/global_forwarding_rule.tf create mode 100644 modules/net-xlb/health_checks.tf create mode 100644 modules/net-xlb/ip_address.tf create mode 100644 modules/net-xlb/outputs.tf create mode 100644 modules/net-xlb/ssl_certificates.tf create mode 100644 modules/net-xlb/target_proxy.tf create mode 100644 modules/net-xlb/url_map.tf create mode 100644 modules/net-xlb/variables.tf create mode 100644 modules/net-xlb/versions.tf diff --git a/modules/net-xlb/README.md b/modules/net-xlb/README.md new file mode 100644 index 00000000..726ef7bd --- /dev/null +++ b/modules/net-xlb/README.md @@ -0,0 +1,451 @@ +# External Global (HTTP/S) Load Balancer Module + +The module allows managing External Global HTTP/HTTPS Load Balancers (XGLB), integrating the global forwarding rule, the url-map, the backends (supporting buckets and groups), and optional health checks and SSL certificates (both managed and unmanaged). It's designed to be a simple match for the [`gcs`](../gcs) and the [`compute-vm`](../compute-vm) modules, which can be used to manage GCS buckets, instance templates and instance groups. + +## Examples + +### GCS Bucket Minimal Example + +This is a minimal example, which creates a global HTTP load balancer, pointing the path `/` to an existing GCS bucket called `my_test_bucket`. + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + backend_services_config = { + my-bucket-backend = { + bucket_config = { + bucket_name = "my_test_bucket" + options = null + } + group_config = null + enable_cdn = false + cdn_config = null + } + } +} +# tftest:modules=1:resources=4 +``` + +### Group Backend Service Minimal Example + +A very similar coniguration also applies to GCE instance groups: + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + backend_services_config = { + my-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_test_group" + options = null + } + ], + health_checks = [] + log_config = null + options = null + } + } + } +} +# tftest:modules=1:resources=5 +``` + +### Health Checks For Group Backend Services + +Group backend services support health checks. +If no health checks are specified, a default HTTP health check is created and associated to each group backend service. The default health check configuration can be modified through the `health_checks_config_defaults` variable. + +Alternatively, one or more health checks can be either contextually created or attached, if existing. If the id of the health checks specified is equal to one of the keys of the `health_checks_config` variable, the health check is contextually created; otherwise, the health check id is used as is, assuming an health check alredy exists. + +For example, to contextually create a health check and attach it to the backend service: + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + backend_services_config = { + my-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_test_group" + options = null + } + ], + health_checks = ["hc_1"] + log_config = null + options = null + } + } + } + + health_checks_config = { + hc_1 = { + type = "http" + logging = true + options = { + timeout_sec = 40 + } + check = { + port_specification = "USE_SERVING_PORT" + } + } + } +} +# tftest:modules=1:resources=5 +``` + +### Mixing Backends + +Backends can be multiple, group and bucket backends can be mixed and group backends support multiple groups. + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + backend_services_config = { + my-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_test_group" + options = null + } + ], + health_checks = [] + log_config = null + options = null + } + }, + another-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_other_test_group" + options = null + } + ], + health_checks = [] + log_config = null + options = null + } + }, + a-bucket-backend = { + bucket_config = { + bucket_name = "my_test_bucket" + options = null + } + group_config = null + enable_cdn = false + cdn_config = null + } + } +} +# tftest:modules=1:resources=7 +``` + +### Url-map + +The url-map can be customized with lots of different configurations. This includes leveraging multiple backends in different parts of the configuration. +Given its complexity, it's left to the user passing the right data structure. + +For simplicity, *if no configurations are given* the first backend service defined (in alphabetical order, with priority to bucket backend services, if any) is used as the *default_service*, thus answering to the root (*/*) path. + +Backend services can be specified as needed in the url-map configuration, referencing the id used to declare them in the backend services map. If a corresponding backend service is found, their object id is automatically used; otherwise, it is assumed that the string passed is the id of an already existing backend and it is given to the provider as it was passed. + +In this example, we're using one backend service as the default backend + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + url_map_config = { + default_service = "my-group-backend" + default_route_action = null + default_url_redirect = null + tests = null + header_action = null + host_rules = [] + path_matchers = [ + { + name = "my-example-page" + path_rules = [ + { + paths = ["/my-example-page"] + service = "another-group-backend" + } + ] + } + ] + } + + backend_services_config = { + my-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_test_group" + options = null + } + ], + health_checks = [] + log_config = null + options = null + } + }, + my-example-page = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_other_test_group" + options = null + } + ], + health_checks = [] + log_config = null + options = null + } + } + } +} +# tftest:modules=1:resources=6 +``` + +### Reserve a static IP address + +Optionally, a static IP address can be reserved: + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + reserve_ip_address = true + + backend_services_config = { + my-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_test_group" + options = null + } + ], + health_checks = [] + log_config = null + options = null + } + } + } +} +# tftest:modules=1:resources=6 +``` + +### HTTPS And SSL Certificates + +HTTPS is disabled by default but it can be optionally enabled. +The module supports both managed and unmanaged certificates, and they can be either created contextually with other resources or attached, if already existing. + +If no `ssl_certificates_config` variable is specified, a managed certificate for the domain *example.com* is automatically created. + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + https = true + + backend_services_config = { + my-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_test_group" + options = null + } + ], + health_checks = [] + log_config = null + options = null + } + } + } +} +# tftest:modules=1:resources=6 +``` + +Otherwise, SSL certificates can be explicitely defined. In this case, they'll need to be referenced from the `target_proxy_https_config.ssl_certificates` variable. + +If the ids specified in the `target_proxy_https_config` variable are not found in the `ssl_certificates_config` map, they are used as is, assuming the ssl certificates already exist. + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + https = true + + ssl_certificates_config = { + my-domain = { + domains = [ + "my-domain.com" + ], + unmanaged_config = null + } + } + + target_proxy_https_config = { + ssl_certificates = [ + "my-domain", + "an-existing-cert" + ] + } + + backend_services_config = { + my-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_test_group" + options = null + } + ] + health_checks = [] + log_config = null + options = null + } + } + } +} +# tftest:modules=1:resources=6 +``` + +Using unamanged certificates is also possible. Here is an example: + +```hcl +module "xlb" { + source = "./modules/net-xlb" + name = "xlb-test" + project_id = var.project_id + + https = true + + ssl_certificates_config = { + my-domain = { + domains = [ + "my-domain.com" + ], + unmanaged_config = { + tls_private_key = nonsensitive(tls_private_key.self_signed_key.private_key_pem) + tls_self_signed_cert = nonsensitive(tls_self_signed_cert.self_signed_cert.cert_pem) + } + } + } + + target_proxy_https_config = { + ssl_certificates = [ + "my-domain" + ] + } + + backend_services_config = { + my-group-backend = { + bucket_config = null + enable_cdn = false + cdn_config = null + group_config = { + backends = [ + { + group = "my_test_group" + options = null + } + ], + health_checks = [] + log_config = null + options = null + } + } + } +} + +resource "tls_private_key" "self_signed_key" { + algorithm = "RSA" + rsa_bits = 2048 +} + +resource "tls_self_signed_cert" "self_signed_cert" { + key_algorithm = tls_private_key.self_signed_key.algorithm + private_key_pem = tls_private_key.self_signed_key.private_key_pem + validity_period_hours = 12 + early_renewal_hours = 3 + dns_names = ["example.com"] + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth" + ] + subject { + common_name = "example.com" + organization = "My Test Org" + } +} +# tftest:modules=1:resources=6 +``` + +## Components And Files Mapping + +An External Global Load Balancer is made of multiple components, that change depending on the configurations. Sometimes, it may be tricky to understand what they are, and how they relate to each other. Following, we provide a very brief overview to become more familiar with them. + +* The global load balancer ([global_forwarding_rule.tf](global_forwarding_rule.tf)) binds a frontend public Virtual IP (VIP) (ip_address.tf)[ip_address.tf] to an HTTP(S) target proxy ((target_proxy.tf)[target_proxy.tf]). + +* If the target proxy ((target_proxy.tf)[target_proxy.tf]) is HTTPS, it requires one or more -managed or unmanaged- SSL certificates ((ssl_certificates.tf)[ssl_certificates.tf]). + +* Target proxies ((target_proxy.tf)[target_proxy.tf]) leverage url-maps ((url_map.tf)[url_map.tf]): set of L7 rules, which create a mapping between specific hostnames, URIs (and more) to one or more backends services ((backend_services.tf)[backend_services.tf]). + +* Backends services ((backend_services.tf)[backend_services.tf]) can either link to a bucket or -one or multiple- groups, which can be GCE instance groups or NEGs. It is assumed in this module that buckets and groups are previously created through other modules, and passed in as input variables. + +* Backends services ((backend_services.tf)[backend_services.tf]) support one or more health checks ((health_cheks.tf)[health_checks.tf]), used to verify that the backend is indeed healthy, so that traffic can be forwarded to it. Health checks currently supported in this module are HTTP, HTTPS, HTTP2, SSL, TCP. diff --git a/modules/net-xlb/backend_services.tf b/modules/net-xlb/backend_services.tf new file mode 100644 index 00000000..7ac818b9 --- /dev/null +++ b/modules/net-xlb/backend_services.tf @@ -0,0 +1,212 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + backend_services_bucket = { + for k, v in coalesce(var.backend_services_config, {}) : + k => v if v.bucket_config != null + } + backend_services_group = { + for k, v in coalesce(var.backend_services_config, {}) : + k => v if v.group_config != null + } +} + +resource "google_compute_backend_bucket" "bucket" { + for_each = local.backend_services_bucket + name = "${var.name}-${each.key}" + description = "Terraform managed." + project = var.project_id + bucket_name = try(each.value.bucket_config.bucket_name, null) + custom_response_headers = try(each.value.bucket_config.options.custom_response_headers, null) + enable_cdn = try(each.value.enable_cdn, null) + + dynamic "cdn_policy" { + for_each = try(each.value.cdn_policy, null) == null ? [] : [each.value.cdn_policy] + content { + cache_mode = try(cdn_policy.value.cache_mode, null) + client_ttl = try(cdn_policy.value.client_ttl, null) + default_ttl = try(cdn_policy.value.default_ttl, null) + max_ttl = try(cdn_policy.value.max_ttl, null) + negative_caching = try(cdn_policy.value.negative_caching, null) + serve_while_stale = try(cdn_policy.value.serve_while_stale, null) + signed_url_cache_max_age_sec = try(cdn_policy.value.signed_url_cache_max_age_sec, null) + + dynamic "negative_caching_policy" { + for_each = try(cdn_policy.value.negative_caching_policy, null) == null ? [] : [cdn_policy.value.negative_caching_policy] + iterator = ncp + content { + code = ncp.value.code + ttl = ncp.value.ttl + } + } + } + } +} + +resource "google_compute_backend_service" "group" { + for_each = local.backend_services_group + name = "${var.name}-${each.key}" + project = var.project_id + description = "Terraform managed." + affinity_cookie_ttl_sec = try(each.value.group_config.options.affinity_cookie_ttl_sec, null) + enable_cdn = try(each.value.enable_cdn, null) + custom_request_headers = try(each.value.group_config.options.custom_request_headers, null) + custom_response_headers = try(each.value.group_config.options.custom_response_headers, null) + connection_draining_timeout_sec = try(each.value.group_config.options.connection_draining_timeout_sec, null) + load_balancing_scheme = try(each.value.group_config.options.load_balancing_scheme, null) + locality_lb_policy = try(each.value.group_config.options.locality_lb_policy, null) + port_name = try(each.value.group_config.options.port_name, null) + protocol = try(each.value.group_config.options.protocol, null) + security_policy = try(each.value.group_config.options.security_policy, null) + session_affinity = try(each.value.group_config.options.session_affinity, null) + timeout_sec = try(each.value.group_config.options.timeout_sec, null) + + # If no health checks are defined, use the default one. + # Otherwise, look in the health_checks_config map. + # Otherwise, use the health_check id as is (already existing). + health_checks = ( + try(each.value.group_config.health_checks, null) == null + || length(try(each.value.group_config.health_checks, [])) == 0 + ? try( + [google_compute_health_check.health_check["default"].self_link], + null + ) + : [ + for hc in each.value.group_config.health_checks : + try( + google_compute_health_check.health_check[hc].id, + hc + ) + ] + ) + + dynamic "backend" { + for_each = try(each.value.group_config.backends, []) + content { + balancing_mode = try(backend.value.options.balancing_mode, null) + capacity_scaler = try(backend.value.options.capacity_scaler, null) + group = try(backend.value.group, null) + max_connections = try(backend.value.options.max_connections, null) + max_connections_per_instance = try(backend.value.options.max_connections_per_instance, null) + max_connections_per_endpoint = try(backend.value.options.max_connections_per_endpoint, null) + max_rate = try(backend.value.options.max_rate, null) + max_rate_per_instance = try(backend.value.options.max_rate_per_instance, null) + max_rate_per_endpoint = try(backend.value.options.max_rate_per_endpoint, null) + max_utilization = try(backend.value.options.max_utilization, null) + } + } + + dynamic "circuit_breakers" { + for_each = ( + try(each.value.group_config.options.circuit_breakers, null) == null + ? [] + : [each.value.group_config.options.circuit_breakers] + ) + iterator = cb + content { + max_requests_per_connection = try(cb.value.max_requests_per_connection, null) + max_connections = try(cb.value.max_connections, null) + max_pending_requests = try(cb.value.max_pending_requests, null) + max_requests = try(cb.value.max_requests, null) + max_retries = try(cb.value.max_retries, null) + } + } + + dynamic "consistent_hash" { + for_each = ( + try(each.value.group_config.options.consistent_hash, null) == null + ? [] + : [each.value.group_config.options.consistent_hash] + ) + content { + http_header_name = try(consistent_hash.value.http_header_name, null) + minimum_ring_size = try(consistent_hash.value.minimum_ring_size, null) + + dynamic "http_cookie" { + for_each = try(consistent_hash.value.http_cookie, null) == null ? [] : [consistent_hash.value.http_cookie] + content { + name = try(http_cookie.value.name, null) + path = try(http_cookie.value.path, null) + + dynamic "ttl" { + for_each = try(consistent_hash.value.ttl, null) == null ? [] : [consistent_hash.value.ttl] + content { + seconds = try(ttl.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + nanos = try(ttl.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + } + } + } + } + } + } + + dynamic "cdn_policy" { + for_each = ( + try(each.value.cdn_policy, null) == null + ? [] + : [each.value.cdn_policy] + ) + iterator = cdn_policy + content { + signed_url_cache_max_age_sec = try(cdn_policy.value.signed_url_cache_max_age_sec, null) + default_ttl = try(cdn_policy.value.default_ttl, null) + max_ttl = try(cdn_policy.value.max_ttl, null) + client_ttl = try(cdn_policy.value.client_ttl, null) + negative_caching = try(cdn_policy.value.negative_caching, null) + cache_mode = try(cdn_policy.value.cache_mode, null) + serve_while_stale = try(cdn_policy.value.serve_while_stale, null) + + dynamic "negative_caching_policy" { + for_each = ( + try(cdn_policy.value.negative_caching_policy, null) == null + ? [] + : [cdn_policy.value.negative_caching_policy] + ) + iterator = ncp + content { + code = try(ncp.value.code, null) + ttl = try(ncp.value.ttl, null) + } + } + } + } + + dynamic "iap" { + for_each = ( + try(each.value.group_config.options.iap, null) == null + ? [] + : [each.value.group_config.options.iap] + ) + content { + oauth2_client_id = try(iap.value.oauth2_client_id, null) + oauth2_client_secret = try(iap.value.oauth2_client_secret, null) # sensitive + oauth2_client_secret_sha256 = try(iap.value.oauth2_client_secret_sha256, null) # sensitive + } + } + + dynamic "log_config" { + for_each = ( + try(each.value.group_config.log_config, null) == null + ? [] + : [each.value.group_config.log_config] + ) + content { + enable = try(log_config.value.enable, null) + sample_rate = try(log_config.value.sample_rate, null) + } + } +} diff --git a/modules/net-xlb/global_forwarding_rule.tf b/modules/net-xlb/global_forwarding_rule.tf new file mode 100644 index 00000000..bac4cdab --- /dev/null +++ b/modules/net-xlb/global_forwarding_rule.tf @@ -0,0 +1,41 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + ip_address = ( + var.reserve_ip_address + ? google_compute_global_address.static_ip.0.id + : null + ) + + target = ( + var.https + ? google_compute_target_https_proxy.https.0.id + : google_compute_target_http_proxy.http.0.id + ) +} + +resource "google_compute_global_forwarding_rule" "forwarding_rule" { + provider = google-beta + name = var.name + project = var.project_id + description = "Terraform managed." + ip_protocol = var.global_forwarding_rule_config.ip_protocol + load_balancing_scheme = var.global_forwarding_rule_config.load_balancing_scheme + port_range = var.global_forwarding_rule_config.port_range + target = local.target + ip_address = local.ip_address +} diff --git a/modules/net-xlb/health_checks.tf b/modules/net-xlb/health_checks.tf new file mode 100644 index 00000000..10abb8bb --- /dev/null +++ b/modules/net-xlb/health_checks.tf @@ -0,0 +1,142 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + # Get group backend services without health checks defined + _backends_without_hcs = [ + for k, v in coalesce(var.backend_services_config, {}) : + v if( + v.group_config != null + && ( + try(v.group_config.health_checks, null) == null + || length(try(v.group_config.health_checks, [])) == 0 + ) + ) + ] + + # If at least one group backend service without + # HC is defined, create also a default HC + health_checks_config = ( + length(local._backends_without_hcs) > 0 + ? merge( + { default = var.health_checks_config_defaults }, + coalesce(var.health_checks_config, {}) + ) + : coalesce(var.health_checks_config, {}) + ) +} + +resource "google_compute_health_check" "health_check" { + for_each = local.health_checks_config + provider = google-beta + name = "${var.name}-${each.key}" + project = var.project_id + description = "Terraform managed." + check_interval_sec = try(each.value.options.check_interval_sec, null) + healthy_threshold = try(each.value.options.healthy_threshold, null) + timeout_sec = try(each.value.options.timeout_sec, null) + unhealthy_threshold = try(each.value.options.unhealthy_threshold, null) + + dynamic "http_health_check" { + for_each = ( + try(each.value.type, null) == "http" || try(each.value.type, null) == null + ? { 1 = 1 } + : {} + ) + content { + host = try(each.value.check.host, null) + port = try(each.value.check.port, null) + port_name = try(each.value.check.port_name, null) + port_specification = try(each.value.check.port_specification, null) + proxy_header = try(each.value.check.proxy_header, null) + request_path = try(each.value.check.request_path, null) + response = try(each.value.check.response, null) + } + } + + dynamic "https_health_check" { + for_each = ( + try(each.value.type, null) == "https" || try(each.value.type, null) == null + ? { 1 = 1 } + : {} + ) + content { + host = try(each.value.check.host, null) + port = try(each.value.check.port, null) + port_name = try(each.value.check.port_name, null) + port_specification = try(each.value.check.port_specification, null) + proxy_header = try(each.value.check.proxy_header, null) + request_path = try(each.value.check.request_path, null) + response = try(each.value.check.response, null) + } + } + + dynamic "tcp_health_check" { + for_each = ( + try(each.value.type, null) == "tcp" || try(each.value.type, null) == null + ? { 1 = 1 } + : {} + ) + content { + port = try(each.value.check.port, null) + port_name = try(each.value.check.port_name, null) + port_specification = try(each.value.check.port_specification, null) + proxy_header = try(each.value.check.proxy_header, null) + request = try(each.value.check.request, null) + response = try(each.value.check.response, null) + } + } + + dynamic "ssl_health_check" { + for_each = ( + try(each.value.type, null) == "ssl" || try(each.value.type, null) == null + ? { 1 = 1 } + : {} + ) + content { + port = try(each.value.check.port, null) + port_name = try(each.value.check.port_name, null) + port_specification = try(each.value.check.port_specification, null) + proxy_header = try(each.value.check.proxy_header, null) + request = try(each.value.check.request, null) + response = try(each.value.check.response, null) + } + } + + dynamic "http2_health_check" { + for_each = ( + try(each.value.type, null) == "http2" || try(each.value.type, null) == null + ? { 1 = 1 } + : {} + ) + content { + host = try(each.value.check.host, null) + port = try(each.value.check.port, null) + port_name = try(each.value.check.port_name, null) + port_specification = try(each.value.check.port_specification, null) + proxy_header = try(each.value.check.proxy_header, null) + request_path = try(each.value.check.request_path, null) + response = try(each.value.check.response, null) + } + } + + dynamic "log_config" { + for_each = try(each.value.logging, false) ? { 0 = 0 } : {} + content { + enable = true + } + } +} diff --git a/modules/net-xlb/ip_address.tf b/modules/net-xlb/ip_address.tf new file mode 100644 index 00000000..bb6750f8 --- /dev/null +++ b/modules/net-xlb/ip_address.tf @@ -0,0 +1,23 @@ +/** + * 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_compute_global_address" "static_ip" { + count = var.reserve_ip_address ? 1 : 0 + provider = google-beta + name = var.name + project = var.project_id + description = "Terraform managed." +} diff --git a/modules/net-xlb/outputs.tf b/modules/net-xlb/outputs.tf new file mode 100644 index 00000000..1f4754db --- /dev/null +++ b/modules/net-xlb/outputs.tf @@ -0,0 +1,60 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "health_checks" { + description = "Health-check resources." + value = try(google_compute_health_check.health_check, []) +} + +output "backend_services" { + description = "Backend service resources." + value = { + bucket = try(google_compute_backend_bucket.bucket, []) + group = try(google_compute_backend_service.group, []) + } +} + +output "url_map" { + description = "The url-map." + value = google_compute_url_map.url_map +} + +output "ssl_certificates" { + description = "The SSL certificate." + value = try( + google_compute_managed_ssl_certificate.managed, + google_compute_ssl_certificate.unmanaged, + null + ) +} + +output "ip_address" { + description = "The reserved global IP address." + value = try(google_compute_global_address.static_ip, null) +} + +output "target_proxy" { + description = "The target proxy." + value = try( + google_compute_target_http_proxy.http, + google_compute_target_https_proxy.https + ) +} + +output "global_forwarding_rule" { + description = "The global forwarding rule." + value = google_compute_global_forwarding_rule.forwarding_rule +} diff --git a/modules/net-xlb/ssl_certificates.tf b/modules/net-xlb/ssl_certificates.tf new file mode 100644 index 00000000..1f1dc2b8 --- /dev/null +++ b/modules/net-xlb/ssl_certificates.tf @@ -0,0 +1,51 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + managed = ( + var.https + ? { + for k, v in coalesce(var.ssl_certificates_config, {}) : + k => v if v.unmanaged_config == null + } + : {} + ) + unmanaged = ( + var.https + ? { + for k, v in coalesce(var.ssl_certificates_config, {}) : + k => v if v.unmanaged_config != null + } + : {} + ) +} + +resource "google_compute_managed_ssl_certificate" "managed" { + for_each = local.managed + project = var.project_id + name = "${var.name}-${each.key}" + managed { + domains = try(each.value.domains, null) + } +} + +resource "google_compute_ssl_certificate" "unmanaged" { + for_each = local.unmanaged + project = var.project_id + name = "${var.name}-${each.key}" + certificate = try(each.value.unmanaged_config.tls_self_signed_cert, null) + private_key = try(each.value.unmanaged_config.tls_private_key, null) +} diff --git a/modules/net-xlb/target_proxy.tf b/modules/net-xlb/target_proxy.tf new file mode 100644 index 00000000..dc9f9687 --- /dev/null +++ b/modules/net-xlb/target_proxy.tf @@ -0,0 +1,50 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + # If no SSL certificates are defined, use the default one. + # Otherwise, look in the ssl_certificates_config map. + # Otherwise, use the SSL certificate id as is (already existing). + ssl_certificates = ( + var.https == true + ? [ + for cert in try(var.target_proxy_https_config.ssl_certificates, ["default"]) : + try( + google_compute_managed_ssl_certificate.managed[cert].id, + google_compute_ssl_certificate.unmanaged[cert].id, + cert + ) + ] + : [] + ) +} + +resource "google_compute_target_http_proxy" "http" { + count = var.https ? 0 : 1 + name = var.name + project = var.project_id + description = "Terraform managed." + url_map = google_compute_url_map.url_map.id +} + +resource "google_compute_target_https_proxy" "https" { + count = var.https ? 1 : 0 + name = var.name + project = var.project_id + description = "Terraform managed." + url_map = google_compute_url_map.url_map.id + ssl_certificates = local.ssl_certificates +} diff --git a/modules/net-xlb/url_map.tf b/modules/net-xlb/url_map.tf new file mode 100644 index 00000000..dd0a6729 --- /dev/null +++ b/modules/net-xlb/url_map.tf @@ -0,0 +1,1180 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + # Look for a backend services in the config whose id is + # the default_service given in the url-map. + # If not found, use the default_service id as given + # (assuming it's already existing). + # If the variable is null, will be set to null. + _default_service = try( + google_compute_backend_bucket.bucket[var.url_map_config.default_service].id, + google_compute_backend_service.group[var.url_map_config.default_service].id, + var.url_map_config.default_service, + null + ) + + # If no backend services are specified, + # the first backend service defined is associated + default_service = ( + try(local._default_service, null) == null + && try(var.url_map_config.default_route_action.weighted_backend_services, null) == null + && try(var.url_map_config.default_url_redirect, null) == null + ? try( + google_compute_backend_bucket.bucket.0.id, + google_compute_backend_service.group.0.id, + null + ) + : null + ) +} + +resource "google_compute_url_map" "url_map" { + name = var.name + description = "Terraform managed." + project = var.project_id + default_service = local.default_service + + dynamic "header_action" { + for_each = ( + try(var.url_map_config.header_action, null) == null + ? [] + : [var.url_map_config.header_action] + ) + content { + request_headers_to_remove = try(header_action.value.request_headers_to_remove, null) + response_headers_to_remove = try(header_action.value.response_headers_to_remove, null) + + dynamic "request_headers_to_add" { + for_each = ( + try(header_action.value.request_headers_to_add, null) == null + ? [] + : [header_action.value.request_headers_to_add] + ) + content { + header_name = try(request_headers_to_add.value.header_name, null) + header_value = try(request_headers_to_add.value.header_value, null) + replace = try(request_headers_to_add.value.replace, null) + } + } + + dynamic "response_headers_to_add" { + for_each = ( + try(header_action.response_headers_to_add, null) == null + ? [] + : [header_action.response_headers_to_add] + ) + content { + header_name = try(response_headers_to_add.value.header_name, null) + header_value = try(response_headers_to_add.value.header_value, null) + replace = try(response_headers_to_add.value.replace, null) + } + } + } + } + + dynamic "host_rule" { + for_each = ( + try(var.url_map_config.host_rules, null) == null + ? [] + : var.url_map_config.host_rules + ) + content { + description = try(host_rule.value.description, null) + hosts = try(host_rule.value.hosts, null) + path_matcher = try(host_rule.value.path_matcher, null) + } + } + + dynamic "path_matcher" { + for_each = ( + try(var.url_map_config.path_matchers, null) == null + ? [] + : var.url_map_config.path_matchers + ) + content { + name = try(path_matcher.value.name, null) + description = try(path_matcher.value.description, null) + default_service = try( + google_compute_backend_bucket.bucket[var.url_map_config.default_service].id, + google_compute_backend_service.group[var.url_map_config.default_service].id, + path_matcher.value.default_service, + null + ) + + dynamic "header_action" { + for_each = ( + try(path_matcher.value.header_action, null) == null + ? [] + : [path_matcher.value.header_action] + ) + content { + request_headers_to_remove = try(header_action.value.request_headers_to_remove, null) + response_headers_to_remove = try(header_action.value.response_headers_to_remove, null) + + dynamic "request_headers_to_add" { + for_each = ( + try(header_action.value.request_headers_to_add, null) == null + ? [] + : [header_action.value.request_headers_to_add] + ) + content { + header_name = try(request_headers_to_add.value.header_name, null) + header_value = try(request_headers_to_add.value.header_value, null) + replace = try(request_headers_to_add.value.replace, null) + } + } + + dynamic "response_headers_to_add" { + for_each = ( + try(header_action.response_headers_to_add, null) == null + ? [] + : [header_action.response_headers_to_add] + ) + content { + header_name = try(response_headers_to_add.value.header_name, null) + header_value = try(response_headers_to_add.value.header_value, null) + replace = try(response_headers_to_add.value.replace, null) + } + } + } + } + + dynamic "path_rule" { + for_each = ( + try(path_matcher.value.path_rules, null) == null + ? [] + : path_matcher.value.path_rules + ) + content { + paths = try(path_rule.value.paths, null) + service = try( + google_compute_backend_bucket.bucket[path_rule.value.service].id, + google_compute_backend_service.group[path_rule.value.service].id, + path_rule.value.service, + null + ) + + dynamic "route_action" { + for_each = ( + try(path_rule.value.route_action, null) == null + ? [] + : [path_rule.value.route_action] + ) + content { + + dynamic "cors_policy" { + for_each = ( + try(route_action.value.cors_policy, null) == null + ? [] + : [route_action.value.cors_policy] + ) + content { + allow_credentials = try(cors_policy.value.allow_credentials, null) + allow_headers = try(cors_policy.value.allow_headers, null) + allow_methods = try(cors_policy.value.allow_methods, null) + allow_origin_regexes = try(cors_policy.value.allow_origin_regexes, null) + allow_origins = try(cors_policy.value.allow_origins, null) + disabled = try(cors_policy.value.disabled, null) + expose_headers = try(cors_policy.value.expose_headers, null) + max_age = try(cors_policy.value.max_age, null) + } + } + + dynamic "fault_injection_policy" { + for_each = ( + try(route_action.value.fault_injection_policy, null) == null + ? [] + : [route_action.value.fault_injection_policy] + ) + iterator = policy + content { + + dynamic "abort" { + for_each = ( + try(policy.value.abort, null) == null + ? [] + : [policy.value.abort] + ) + content { + http_status = try(abort.value.http_status, null) # Must be between 200 and 599 inclusive + percentage = try(abort.value.percentage, null) # Must be between 0.0 and 100.0 inclusive + } + } + + dynamic "delay" { + for_each = ( + try(policy.value.delay, null) == null + ? [] + : [policy.value.delay] + ) + content { + percentage = try(delay.value.percentage, null) # Must be between 0.0 and 100.0 inclusive + + dynamic "fixed_delay" { + for_each = ( + try(delay.value.fixed_delay, null) == null + ? [] + : [delay.value.fixed_delay] + ) + content { + nanos = try(fixed_delay.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(fixed_delay.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + } + } + } + } + + dynamic "request_mirror_policy" { + for_each = ( + try(route_action.value.request_mirror_policy, null) == null + ? [] + : [route_action.value.request_mirror_policy] + ) + iterator = policy + content { + backend_service = try( + google_compute_backend_bucket.bucket[policy.value.backend_service].id, + google_compute_backend_service.group[policy.value.backend_service].id, + policy.value.backend_service, + null + ) + } + } + + dynamic "retry_policy" { + for_each = ( + try(route_action.value.retry_policy, null) == null + ? [] + : [route_action.value.retry_policy] + ) + iterator = policy + content { + num_retries = try(policy.num_retries, null) # Must be > 0 + # Valid values at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#retry_conditions + retry_conditions = try(policy.retry_conditions, null) + + dynamic "per_try_timeout" { + for_each = ( + try(policy.value.per_try_timeout, null) == null + ? [] + : [policy.value.per_try_timeout] + ) + iterator = timeout + content { + nanos = try(timeout.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(timeout.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + } + } + + dynamic "timeout" { + for_each = ( + try(route_action.value.timeout, null) == null + ? [] + : [route_action.value.timeout] + ) + content { + nanos = try(timeout.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(timeout.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + + dynamic "url_rewrite" { + for_each = ( + try(route_action.value.url_rewrite, null) == null + ? [] + : [route_action.value.url_rewrite] + ) + content { + host_rewrite = try(url_rewrite.value.host_rewrite, null) # Must be between 1 and 255 characters + path_prefix_rewrite = try(url_rewrite.value.path_prefix_rewrite, null) # Must be between 1 and 1024 characters + } + } + + dynamic "weighted_backend_services" { + for_each = ( + try(route_action.value.weighted_backend_services, null) == null + ? [] + : route_action.value.weighted_backend_services + ) + iterator = weighted + content { + weight = try(weighted.value.weigth, null) + backend_service = try( + google_compute_backend_bucket.bucket[weighted.value.backend_service].id, + google_compute_backend_service.group[weighted.value.backend_service].id, + policy.value.backend_service, + null + ) + dynamic "header_action" { + for_each = ( + try(path_matcher.value.header_action, null) == null + ? [] + : [path_matcher.value.header_action] + ) + content { + request_headers_to_remove = try(header_action.value.request_headers_to_remove, null) + response_headers_to_remove = try(header_action.value.response_headers_to_remove, null) + + dynamic "request_headers_to_add" { + for_each = ( + try(header_action.value.request_headers_to_add, null) == null + ? [] : + [header_action.value.request_headers_to_add] + ) + content { + header_name = try(request_headers_to_add.value.header_name, null) + header_value = try(request_headers_to_add.value.header_value, null) + replace = try(request_headers_to_add.value.replace, null) + } + } + + dynamic "response_headers_to_add" { + for_each = ( + try(header_action.response_headers_to_add, null) == null + ? [] + : [header_action.response_headers_to_add] + ) + content { + header_name = try(response_headers_to_add.value.header_name, null) + header_value = try(response_headers_to_add.value.header_value, null) + replace = try(response_headers_to_add.value.replace, null) + } + } + } + } + } + } + } + } + + dynamic "url_redirect" { + for_each = ( + try(path_rule.value.url_redirect, null) == null + ? [] + : path_rule.value.url_redirect + ) + content { + host_redirect = try(url_redirect.value.host_redirect, null) # Must be between 1 and 255 characters + https_redirect = try(url_redirect.value.https_redirect, null) + path_redirect = try(url_redirect.value.path_redirect, null) + prefix_redirect = try(url_redirect.value.prefix_redirect, null) # Must be between 1 and 1024 characters + # Valid valus at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#redirect_response_code + redirect_response_code = try(url_redirect.value.redirect_response_code, null) + strip_query = try(url_redirect.value.strip_query, null) + } + } + } + } + + dynamic "route_rules" { + for_each = ( + try(path_matcher.value.route_rules, null) == null + ? [] + : path_matcher.value.route_rules + ) + content { + priority = try(route_rules.value.priority, null) + service = try( + google_compute_backend_bucket.bucket[route_rules.value.service].id, + google_compute_backend_service.group[route_rules.value.service].id, + route_rules.value.service, + null + ) + + dynamic "header_action" { + for_each = ( + try(path_matcher.value.header_action, null) == null + ? [] + : [path_matcher.value.header_action] + ) + content { + request_headers_to_remove = try(header_action.value.request_headers_to_remove, null) + response_headers_to_remove = try(header_action.value.response_headers_to_remove, null) + + dynamic "request_headers_to_add" { + for_each = ( + try(header_action.value.request_headers_to_add, null) == null + ? [] + : [header_action.value.request_headers_to_add] + ) + content { + header_name = try(request_headers_to_add.value.header_name, null) + header_value = try(request_headers_to_add.value.header_value, null) + replace = try(request_headers_to_add.value.replace, null) + } + } + + dynamic "response_headers_to_add" { + for_each = ( + try(header_action.response_headers_to_add, null) == null + ? [] + : [header_action.response_headers_to_add] + ) + content { + header_name = try(response_headers_to_add.value.header_name, null) + header_value = try(response_headers_to_add.value.header_value, null) + replace = try(response_headers_to_add.value.replace, null) + } + } + } + } + + dynamic "match_rules" { + for_each = ( + try(path_matcher.value.match_rules, null) == null + ? [] + : path_matcher.value.match_rules + ) + content { + full_path_match = try(match_rules.value.full_path_match, null) # Must be between 1 and 1024 characters + ignore_case = try(match_rules.value.ignore_case, null) + prefix_match = try(match_rules.value.prefix_match, null) + regex_match = try(match_rules.value.regex_match, null) + + dynamic "header_matches" { + for_each = ( + try(match_rules.value.header_matches, null) == null + ? [] + : [match_rules.value.header_matches] + ) + content { + exact_match = try(header_matches.value.exact_match, null) + header_name = try(header_matches.value.header_name, null) + invert_match = try(header_matches.value.invert_match, null) + prefix_match = try(header_matches.value.prefix_match, null) + present_match = try(header_matches.value.present_match, null) + regex_match = try(header_matches.value.regex_match, null) + suffix_match = try(header_matches.value, null) + + dynamic "range_match" { + for_each = try(header_matches.value.range_match, null) == null ? [] : [header_matches.value.range_match] + content { + range_end = try(range_match.value.range_end, null) + range_start = try(range_match.value.range_start, null) + } + } + } + } + + dynamic "metadata_filters" { + for_each = ( + try(match_rules.value.metadata_filters, null) == null + ? [] + : [match_rules.value.metadata_filters] + ) + content { + # Valid values at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#filter_match_criteria + filter_match_criteria = try(metadata_filters.value.filter_match_criteria, null) + + dynamic "filter_labels" { + for_each = ( + try(metadata_filters.value.filter_labels, null) == null + ? [] + : metadata_filters.value.filter_labels + ) + content { + name = try(filter_labels.value.name, null) # Must be between 1 and 1024 characters + value = try(filter_labels.value.value, null) # Must be between 1 and 1024 characters + } + } + } + } + + dynamic "query_parameter_matches" { + for_each = ( + try(match_rules.value.query_parameter_matches, null) == null + ? [] + : [match_rules.value.query_parameter_matches] + ) + iterator = query + content { + exact_match = try(query.value.exact_match, null) + name = try(query.value.name, null) + present_match = try(query.value.present_match, null) + regex_match = try(query.value.regex_match, null) + } + } + } + } + + dynamic "route_action" { + for_each = ( + try(route_rules.value.route_action, null) == null + ? [] + : [route_rules.value.route_action] + ) + content { + + dynamic "cors_policy" { + for_each = ( + try(route_action.value.cors_policy, null) == null + ? [] + : [route_action.value.cors_policy] + ) + content { + allow_credentials = try(cors_policy.value.allow_credentials, null) + allow_headers = try(cors_policy.value.allow_headers, null) + allow_methods = try(cors_policy.value.allow_methods, null) + allow_origin_regexes = try(cors_policy.value.allow_origin_regexes, null) + allow_origins = try(cors_policy.value.allow_origins, null) + disabled = try(cors_policy.value.disabled, null) + expose_headers = try(cors_policy.value.expose_headers, null) + max_age = try(cors_policy.value.max_age, null) + } + } + + dynamic "fault_injection_policy" { + for_each = ( + try(route_action.value.fault_injection_policy, null) == null + ? [] + : [route_action.value.fault_injection_policy] + ) + iterator = policy + content { + + dynamic "abort" { + for_each = ( + try(policy.value.abort, null) == null + ? [] + : [policy.value.abort] + ) + content { + http_status = try(abort.value.http_status, null) # Must be between 200 and 599 inclusive + percentage = try(abort.value.percentage, null) # Must be between 0.0 and 100.0 inclusive + } + } + + dynamic "delay" { + for_each = ( + try(policy.value.delay, null) == null + ? [] + : [policy.value.delay] + ) + content { + percentage = try(delay.value.percentage, null) # Must be between 0.0 and 100.0 inclusive + + dynamic "fixed_delay" { + for_each = ( + try(delay.value.fixed_delay, null) == null + ? [] + : [delay.value.fixed_delay] + ) + content { + nanos = try(fixed_delay.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(fixed_delay.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + } + } + } + } + + dynamic "request_mirror_policy" { + for_each = ( + try(route_action.value.request_mirror_policy, null) == null + ? [] + : [route_action.value.request_mirror_policy] + ) + iterator = policy + content { + backend_service = try( + google_compute_backend_bucket.bucket[policy.value.backend_service].id, + google_compute_backend_service.group[policy.value.backend_service].id, + policy.value.backend_service, + null + ) + } + } + + dynamic "retry_policy" { + for_each = ( + try(route_action.value.retry_policy, null) == null + ? [] + : [route_action.value.retry_policy] + ) + iterator = policy + content { + num_retries = try(policy.num_retries, null) # Must be > 0 + # Valid values at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#retry_conditions + retry_conditions = try(policy.retry_conditions, null) + + dynamic "per_try_timeout" { + for_each = ( + try(policy.value.per_try_timeout, null) == null + ? [] + : [policy.value.per_try_timeout] + ) + iterator = timeout + content { + nanos = try(timeout.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(timeout.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + } + } + + dynamic "timeout" { + for_each = ( + try(route_action.value.timeout, null) == null + ? [] + : [route_action.value.timeout] + ) + content { + nanos = try(timeout.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(timeout.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + + dynamic "url_rewrite" { + for_each = ( + try(route_action.value.url_rewrite, null) == null + ? [] + : [route_action.value.url_rewrite] + ) + content { + host_rewrite = try(url_rewrite.value.host_rewrite, null) # Must be between 1 and 255 characters + path_prefix_rewrite = try(url_rewrite.value.path_prefix_rewrite, null) # Must be between 1 and 1024 characters + } + } + + dynamic "weighted_backend_services" { + for_each = ( + try(route_action.value.weighted_backend_services, null) == null + ? [] + : [route_action.value.url_rewrite] + ) + iterator = weighted + content { + weight = try(weighted.value.weigth, null) + backend_service = try( + google_compute_backend_bucket.bucket[weighted.value.backend_service].id, + google_compute_backend_service.group[weighted.value.backend_service].id, + weighted.value.backend_service, + null + ) + + dynamic "header_action" { + for_each = ( + try(path_matcher.value.header_action, null) == null + ? [] : + [path_matcher.value.header_action] + ) + content { + request_headers_to_remove = try(header_action.value.request_headers_to_remove, null) + response_headers_to_remove = try(header_action.value.response_headers_to_remove, null) + + dynamic "request_headers_to_add" { + for_each = ( + try(header_action.value.request_headers_to_add, null) == null + ? [] + : [header_action.value.request_headers_to_add] + ) + content { + header_name = try(request_headers_to_add.value.header_name, null) + header_value = try(request_headers_to_add.value.header_value, null) + replace = try(request_headers_to_add.value.replace, null) + } + } + + dynamic "response_headers_to_add" { + for_each = ( + try(header_action.response_headers_to_add, null) == null + ? [] + : [header_action.response_headers_to_add] + ) + content { + header_name = try(response_headers_to_add.value.header_name, null) + header_value = try(response_headers_to_add.value.header_value, null) + replace = try(response_headers_to_add.value.replace, null) + } + } + } + } + } + } + } + } + + dynamic "url_redirect" { + for_each = ( + try(route_rules.value.url_redirect, null) == null + ? [] + : route_rules.value.url_redirect + ) + content { + host_redirect = try(url_redirect.value.host_redirect, null) # Must be between 1 and 255 characters + https_redirect = try(url_redirect.value.https_redirect, null) + path_redirect = try(url_redirect.value.path_redirect, null) + prefix_redirect = try(url_redirect.value.prefix_redirect, null) # Must be between 1 and 1024 characters + # Valid valus at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#redirect_response_code + redirect_response_code = try(url_redirect.value.redirect_response_code, null) + strip_query = try(url_redirect.value.strip_query, null) + } + } + } + } + + dynamic "default_url_redirect" { + for_each = ( + try(path_matcher.value.default_url_redirect, null) == null + ? [] + : path_matcher.value.default_url_redirect + ) + content { + host_redirect = try(default_url_redirect.value.host_redirect, null) # Must be between 1 and 255 characters + https_redirect = try(default_url_redirect.value.https_redirect, null) + path_redirect = try(default_url_redirect.value.path_redirect, null) # Must be between 1 and 1024 characters + prefix_redirect = try(default_url_redirect.value.prefix_redirect, null) # Must be between 1 and 1024 characters + # Valid values at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#redirect_response_code + redirect_response_code = try(default_url_redirect.value.redirect_response_code, null) + strip_query = try(default_url_redirect.value.strip_query, null) + } + } + + dynamic "default_route_action" { + for_each = ( + try(path_matcher.value.default_route_action, null) == null + ? [] + : path_matcher.value.default_route_action + ) + content { + dynamic "cors_policy" { + for_each = ( + try(default_route_action.value.cors_policy, null) == null + ? [] + : [default_route_action.value.cors_policy] + ) + content { + allow_credentials = try(cors_policy.value.allow_credentials, null) + allow_headers = try(cors_policy.value.allow_headers, null) + allow_methods = try(cors_policy.value.allow_methods, null) + allow_origin_regexes = try(cors_policy.value.allow_origin_regexes, null) + allow_origins = try(cors_policy.value.allow_origins, null) + disabled = try(cors_policy.value.disabled, null) + expose_headers = try(cors_policy.value.expose_headers, null) + max_age = try(cors_policy.value.max_age, null) + } + } + + dynamic "fault_injection_policy" { + for_each = ( + try(default_route_action.value.fault_injection_policy, null) == null + ? [] + : [default_route_action.value.fault_injection_policy] + ) + iterator = policy + content { + + dynamic "abort" { + for_each = ( + try(policy.value.abort, null) == null + ? [] + : [policy.value.abort] + ) + content { + http_status = try(abort.value.http_status, null) # Must be between 200 and 599 inclusive + percentage = try(abort.value.percentage, null) # Must be between 0.0 and 100.0 inclusive + } + } + + dynamic "delay" { + for_each = ( + try(policy.value.delay, null) == null + ? [] + : [policy.value.delay] + ) + content { + percentage = try(delay.value.percentage, null) # Must be between 0.0 and 100.0 inclusive + + dynamic "fixed_delay" { + for_each = ( + try(delay.value.fixed_delay, null) == null + ? [] + : [delay.value.fixed_delay] + ) + content { + nanos = try(fixed_delay.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(fixed_delay.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + } + } + } + } + + dynamic "request_mirror_policy" { + for_each = ( + try(default_route_action.value.request_mirror_policy, null) == null + ? [] + : [default_route_action.value.request_mirror_policy] + ) + iterator = policy + content { + backend_service = try( + google_compute_backend_bucket.bucket[policy.value.backend_service].id, + google_compute_backend_service.group[policy.value.backend_service].id, + policy.value.backend_service, + null + ) + } + } + + dynamic "retry_policy" { + for_each = ( + try(default_route_action.value.retry_policy, null) == null + ? [] + : [default_route_action.value.retry_policy] + ) + iterator = policy + content { + num_retries = try(policy.num_retries, null) # Must be > 0 + # Valid values at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#retry_conditions + retry_conditions = try(policy.retry_conditions, null) + + dynamic "per_try_timeout" { + for_each = ( + try(policy.value.per_try_timeout, null) == null + ? [] + : [policy.value.per_try_timeout] + ) + iterator = timeout + content { + nanos = try(timeout.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(timeout.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + } + } + + dynamic "timeout" { + for_each = ( + try(default_route_action.value.timeout, null) == null + ? [] + : [default_route_action.value.timeout] + ) + content { + nanos = try(timeout.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(timeout.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + + dynamic "url_rewrite" { + for_each = ( + try(default_route_action.value.url_rewrite, null) == null + ? [] + : [default_route_action.value.url_rewrite] + ) + content { + host_rewrite = try(url_rewrite.value.host_rewrite, null) # Must be between 1 and 255 characters + path_prefix_rewrite = try(url_rewrite.value.path_prefix_rewrite, null) # Must be between 1 and 1024 characters + } + } + + dynamic "weighted_backend_services" { + for_each = ( + try(default_route_action.value.weighted_backend_services, null) == null + ? [] + : default_route_action.value.weighted_backend_services + ) + iterator = weighted + content { + weight = try(weighted.value.weigth, null) + backend_service = try( + google_compute_backend_bucket.bucket[weighted.value.backend_service].id, + google_compute_backend_service.group[weighted.value.backend_service].id, + weighted.value.backend_service, + null + ) + + dynamic "header_action" { + for_each = ( + try(path_matcher.value.header_action, null) == null + ? [] + : [path_matcher.value.header_action] + ) + content { + request_headers_to_remove = try(header_action.value.request_headers_to_remove, null) + response_headers_to_remove = try(header_action.value.response_headers_to_remove, null) + + dynamic "request_headers_to_add" { + for_each = ( + try(header_action.value.request_headers_to_add, null) == null + ? [] + : [header_action.value.request_headers_to_add] + ) + content { + header_name = try(request_headers_to_add.value.header_name, null) + header_value = try(request_headers_to_add.value.header_value, null) + replace = try(request_headers_to_add.value.replace, null) + } + } + + dynamic "response_headers_to_add" { + for_each = ( + try(header_action.response_headers_to_add, null) == null + ? [] + : [header_action.response_headers_to_add] + ) + content { + header_name = try(response_headers_to_add.value.header_name, null) + header_value = try(response_headers_to_add.value.header_value, null) + replace = try(response_headers_to_add.value.replace, null) + } + } + } + } + } + } + } + } + } + } + + # Up to 100 tests per url_map + dynamic "test" { + for_each = ( + try(var.url_map_config.tests, null) == null + ? [] + : var.url_map_config.tests + ) + content { + description = try(test.value.description, null) + host = try(test.value.host, null) + path = try(test.value.path, null) + service = try( + google_compute_backend_bucket.bucket[test.value.service].id, + google_compute_backend_service.group[test.value.service].id, + test.value.service, + null + ) + } + } + + dynamic "default_url_redirect" { + for_each = ( + try(var.url_map_config.default_url_redirect, null) == null + ? [] + : [var.url_map_config.default_url_redirect] + ) + content { + host_redirect = try(default_url_redirect.value.host_redirect, null) # Must be between 1 and 255 characters + https_redirect = try(default_url_redirect.value.https_redirect, null) + path_redirect = try(default_url_redirect.value.path_redirect, null) # Must be between 1 and 1024 characters + prefix_redirect = try(default_url_redirect.value.prefix_redirect, null) # Must be between 1 and 1024 characters + # Valid values at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#redirect_response_code + redirect_response_code = try(default_url_redirect.value.redirect_response_code, null) + strip_query = try(default_url_redirect.value.strip_query, null) + } + } + + dynamic "default_route_action" { + for_each = ( + try(var.url_map_config.default_route_action, null) == null + ? [] + : [var.url_map_config.default_route_action] + ) + content { + dynamic "cors_policy" { + for_each = ( + try(default_route_action.value.cors_policy, null) == null + ? [] + : [default_route_action.value.cors_policy] + ) + content { + allow_credentials = try(cors_policy.value.allow_credentials, null) + allow_headers = try(cors_policy.value.allow_headers, null) + allow_methods = try(cors_policy.value.allow_methods, null) + allow_origin_regexes = try(cors_policy.value.allow_origin_regexes, null) + allow_origins = try(cors_policy.value.allow_origins, null) + disabled = try(cors_policy.value.disabled, null) + expose_headers = try(cors_policy.value.expose_headers, null) + max_age = try(cors_policy.value.max_age, null) + } + } + + dynamic "fault_injection_policy" { + for_each = ( + try(default_route_action.value.fault_injection_policy, null) == null + ? [] + : [default_route_action.value.fault_injection_policy] + ) + iterator = policy + content { + + dynamic "abort" { + for_each = ( + try(policy.value.abort, null) == null + ? [] + : [policy.value.abort] + ) + content { + http_status = try(abort.value.http_status, null) # Must be between 200 and 599 inclusive + percentage = try(abort.value.percentage, null) # Must be between 0.0 and 100.0 inclusive + } + } + + dynamic "delay" { + for_each = ( + try(policy.value.delay, null) == null + ? [] + : [policy.value.delay] + ) + content { + percentage = try(delay.value.percentage, null) # Must be between 0.0 and 100.0 inclusive + + dynamic "fixed_delay" { + for_each = try(delay.value.fixed_delay, null) == null ? [] : [delay.value.fixed_delay] + content { + nanos = try(fixed_delay.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(fixed_delay.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + } + } + } + } + + dynamic "request_mirror_policy" { + for_each = ( + try(default_route_action.value.request_mirror_policy, null) == null + ? [] + : [default_route_action.value.request_mirror_policy] + ) + iterator = policy + content { + backend_service = try( + google_compute_backend_bucket.bucket[policy.value.backend_service].id, + google_compute_backend_service.group[policy.value.backend_service].id, + policy.value.backend_service, + null + ) + } + } + + dynamic "retry_policy" { + for_each = ( + try(default_route_action.value.retry_policy, null) == null + ? [] + : [default_route_action.value.retry_policy] + ) + iterator = policy + content { + num_retries = try(policy.num_retries, null) # Must be > 0 + # Valid values at https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_url_map#retry_conditions + retry_conditions = try(policy.retry_conditions, null) + + dynamic "per_try_timeout" { + for_each = ( + try(policy.value.per_try_timeout, null) == null + ? [] + : [policy.value.per_try_timeout] + ) + iterator = timeout + content { + nanos = try(timeout.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(timeout.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + } + } + + dynamic "timeout" { + for_each = ( + try(default_route_action.value.timeout, null) == null + ? [] + : [default_route_action.value.timeout] + ) + content { + nanos = try(timeout.value.nanos, null) # Must be from 0 to 999,999,999 inclusive + seconds = try(timeout.value.seconds, null) # Must be from 0 to 315,576,000,000 inclusive + } + } + + dynamic "url_rewrite" { + for_each = ( + try(default_route_action.value.url_rewrite, null) == null + ? [] + : [default_route_action.value.url_rewrite] + ) + content { + host_rewrite = try(url_rewrite.value.host_rewrite, null) # Must be between 1 and 255 characters + path_prefix_rewrite = try(url_rewrite.value.path_prefix_rewrite, null) # Must be between 1 and 1024 characters + } + } + + dynamic "weighted_backend_services" { + for_each = ( + try(default_route_action.value.weighted_backend_services, null) == null + ? [] + : default_route_action.value.weighted_backend_services + ) + iterator = weighted + content { + weight = try(weighted.value.weigth, null) + backend_service = try( + google_compute_backend_bucket.bucket[weighted.value.backend_service].id, + google_compute_backend_service.group[weighted.value.backend_service].id, + weighted.value.backend_service, + null + ) + + dynamic "header_action" { + for_each = ( + try(weighted.value.header_action, null) == null + ? [] + : [weighted.value.header_action] + ) + content { + request_headers_to_remove = try(header_action.value.request_headers_to_remove, null) + response_headers_to_remove = try(header_action.value.response_headers_to_remove, null) + + dynamic "request_headers_to_add" { + for_each = ( + try(header_action.value.request_headers_to_add, null) == null + ? [] + : [header_action.value.request_headers_to_add] + ) + content { + header_name = try(request_headers_to_add.value.header_name, null) + header_value = try(request_headers_to_add.value.header_value, null) + replace = try(request_headers_to_add.value.replace, null) + } + } + + dynamic "response_headers_to_add" { + for_each = ( + try(header_action.response_headers_to_add, null) == null + ? [] + : [header_action.response_headers_to_add] + ) + content { + header_name = try(response_headers_to_add.value.header_name, null) + header_value = try(response_headers_to_add.value.header_value, null) + replace = try(response_headers_to_add.value.replace, null) + } + } + } + } + } + } + } + } +} diff --git a/modules/net-xlb/variables.tf b/modules/net-xlb/variables.tf new file mode 100644 index 00000000..1d7050f9 --- /dev/null +++ b/modules/net-xlb/variables.tf @@ -0,0 +1,218 @@ +/** + * 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 "name" { + description = "Load balancer name." + type = string +} + +variable "project_id" { + description = "Project id." + type = string +} + +variable "health_checks_config_defaults" { + description = "Auto-created health check default configuration." + type = object({ + type = string # http https tcp ssl http2 + check = map(any) # actual health check block attributes + options = map(number) # interval, thresholds, timeout + logging = bool + }) + default = { + type = "http" + logging = false + options = {} + check = { + port_specification = "USE_SERVING_PORT" + } + } +} + +variable "health_checks_config" { + description = "Custom health checks configuration." + type = map(object({ + type = string # http https tcp ssl http2 + check = map(any) # actual health check block attributes + options = map(number) # interval, thresholds, timeout + logging = bool + })) + default = {} +} + +variable "backend_services_config" { + description = "The backends services configuration." + type = map(object({ + enable_cdn = bool + + cdn_config = object({ + cache_mode = string + client_ttl = number + default_ttl = number + max_ttl = number + negative_caching = bool + negative_caching_policy = map(number) + serve_while_stale = bool + signed_url_cache_max_age_sec = string + }) + + bucket_config = object({ + bucket_name = string + options = object({ + custom_response_headers = list(string) + }) + }) + + group_config = object({ + backends = list(object({ + group = string # IG or NEG FQDN address + options = object({ + balancing_mode = string # Can be UTILIZATION, RATE, CONNECTION + capacity_scaler = number # Valid range is [0.0,1.0] + max_connections = number + max_connections_per_instance = number + max_connections_per_endpoint = number + max_rate = number + max_rate_per_instance = number + max_rate_per_endpoint = number + max_utilization = number + }) + })) + + # Optional health check ids for backend service groups. + # Will lookup for ids in health_chacks_config first, + # then will use the id as is. If no ids are defined + # at all (null, []) health_checks_config_defaults is used + health_checks = list(string) + + log_config = object({ + enable = bool + sample_rate = number # must be in [0, 1] + }) + + options = object({ + affinity_cookie_ttl_sec = number + custom_request_headers = list(string) + custom_response_headers = list(string) + connection_draining_timeout_sec = number + load_balancing_scheme = string # only EXTERNAL (default) makes sense here + locality_lb_policy = string + port_name = string + protocol = string + security_policy = string + session_affinity = string + timeout_sec = number + + circuits_breakers = object({ + max_requests_per_connection = number # Set to 1 to disable keep-alive + max_connections = number # Defaults to 1024 + max_pending_requests = number # Defaults to 1024 + max_requests = number # Defaults to 1024 + max_retries = number # Defaults to 3 + }) + + consistent_hash = object({ + http_header_name = string + minimum_ring_size = string + http_cookie = object({ + name = string + path = string + ttl = object({ + seconds = number + nanos = number + }) + }) + }) + + iap = object({ + oauth2_client_id = string + oauth2_client_secret = string + oauth2_client_secret_sha256 = string + }) + }) + }) + })) + default = {} +} + +variable "url_map_config" { + description = "The url-map configuration." + type = object({ + default_service = string + default_route_action = any + default_url_redirect = map(any) + header_action = any + host_rules = list(any) + path_matchers = list(any) + tests = list(map(string)) + }) + default = null +} + +variable "ssl_certificates_config" { + description = "The SSL certificate configuration." + type = map(object({ + domains = list(string) + # If unmanaged_config is null, the certificate will be managed + unmanaged_config = object({ + tls_private_key = string + tls_self_signed_cert = string + }) + })) + default = { + default = { + domains = ["example.com"], + unmanaged_config = null + } + } +} + +variable "target_proxy_https_config" { + description = "The HTTPS target proxy configuration." + type = object({ + ssl_certificates = list(string) + }) + default = null +} + +variable "global_forwarding_rule_config" { + description = "Global forwarding rule configurations." + type = object({ + ip_protocol = string + ip_version = string + load_balancing_scheme = string + port_range = string + + }) + default = { + load_balancing_scheme = "EXTERNAL" + ip_protocol = "TCP" + ip_version = "IPV4" + port_range = "80" # 80, 8080, 443 + } +} + +variable "https" { + description = "Whether to enable HTTPS." + type = bool + default = false +} + +variable "reserve_ip_address" { + description = "Whether to reserve a static global IP address." + type = bool + default = false +} diff --git a/modules/net-xlb/versions.tf b/modules/net-xlb/versions.tf new file mode 100644 index 00000000..76419788 --- /dev/null +++ b/modules/net-xlb/versions.tf @@ -0,0 +1,27 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_version = ">= 1.0.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 4.0.0" + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 4.0.0" + } + } +} From 952d86c2ffe517c7656b472ca7b2118aa813f735 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 14 Jan 2022 08:21:39 +0100 Subject: [PATCH 2/3] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f47b83c9..e132fe29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## Unreleased + +- new `net-xlb` module for Global External Load balancer + ## [12.0.0] - 2022-01-11 - new repo structure. All end-to-end examples moved to the top level `examples` folder From baa31e82d991bfb97b9c8174d13e3823d3d751ef Mon Sep 17 00:00:00 2001 From: Simone Ruffilli Date: Fri, 14 Jan 2022 08:30:06 +0100 Subject: [PATCH 3/3] Resource Factory update (#430) * Resource Factory update * Pleasing the linting gods * Poking the linting gods Co-authored-by: Ludovico Magnocavallo --- examples/README.md | 6 +- examples/factories/README.md | 54 +++--- .../factories/example-environments/README.md | 43 ----- .../vpc-alpha/subnet-alpha-a.yaml | 8 - .../vpc-alpha/subnet-alpha-b.yaml | 6 - .../project-dev-b/vpc-beta/subnet-beta-a.yaml | 7 - .../vpc-gamma/subnet-gamma-a.yaml | 5 - .../example-environments/dev/main.tf | 21 --- .../vpc-alpha/subnet-alpha-a.yaml | 8 - .../vpc-alpha/subnet-alpha-b.yaml | 6 - .../example-environments/prod/main.tf | 21 --- .../example-environments/prod/versions.tf | 29 --- .../firewall-hierarchical-policies/README.md | 168 ------------------ .../firewall-hierarchical-policies/main.tf | 99 ----------- .../firewall-hierarchical-policies/outputs.tf | 26 --- .../variables.tf | 25 --- .../versions.tf | 29 --- .../factories/firewall-vpc-rules/README.md | 6 - .../firewall-vpc-rules/flat/versions.tf | 29 --- .../firewall-vpc-rules/nested/README.md | 144 --------------- .../firewall-vpc-rules/nested/main.tf | 143 --------------- .../firewall-vpc-rules/nested/outputs.tf | 20 --- .../firewall-vpc-rules/nested/variables.tf | 25 --- .../firewall-vpc-rules/nested/versions.tf | 29 --- .../flat => net-vpc-firewall-yaml}/README.md | 26 +-- .../flat => net-vpc-firewall-yaml}/main.tf | 0 .../flat => net-vpc-firewall-yaml}/outputs.tf | 0 .../variables.tf | 0 .../dev => net-vpc-firewall-yaml}/versions.tf | 0 examples/factories/subnets/README.md | 71 -------- examples/factories/subnets/main.tf | 72 -------- examples/factories/subnets/outputs.tf | 30 ---- examples/factories/subnets/variables.tf | 20 --- examples/factories/subnets/versions.tf | 29 --- examples/networking/README.md | 2 +- .../decentralized-firewall/README.md | 28 +-- .../networking/decentralized-firewall/main.tf | 4 +- .../rules/1234567890/0987654321-foobar.yaml | 13 -- .../fixture/conf/rules/1234567890/org.yaml | 13 -- .../fixture/conf/templates/cidrs.yaml | 10 -- .../conf/templates/service_accounts.yaml | 4 - .../fixture/main.tf | 21 --- .../test_plan.py | 47 ----- .../__init__.py | 0 .../fixture/main.tf | 2 +- .../fixture/rules/common.yaml | 0 .../fixture/variables.tf | 0 .../test_plan.py | 0 tests/examples/factories/subnets/__init__.py | 13 -- .../conf/project-a/vpc-a/subnet-a.yaml | 8 - .../conf/project-a/vpc-a/subnet-b.yaml | 6 - .../conf/project-a/vpc-b/subnet-one.yaml | 7 - .../conf/project-b/vpc-x/subnet-alpha.yaml | 7 - .../factories/subnets/fixture/main.tf | 20 --- tests/examples/factories/subnets/test_plan.py | 60 ------- .../factories/vpc_firewall/__init__.py | 13 -- .../factories/vpc_firewall/flat/__init__.py | 13 -- .../factories/vpc_firewall/nested/__init__.py | 13 -- .../vpc-a/ingress-loadbalancers.yaml | 25 --- .../nested/fixture/conf/templates/cidrs.yaml | 10 -- .../conf/templates/service_accounts.yaml | 4 - .../vpc_firewall/nested/fixture/main.tf | 21 --- .../vpc_firewall/nested/test_plan.py | 38 ---- 63 files changed, 56 insertions(+), 1551 deletions(-) delete mode 100644 examples/factories/example-environments/README.md delete mode 100644 examples/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-a.yaml delete mode 100644 examples/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-b.yaml delete mode 100644 examples/factories/example-environments/dev/conf/project-dev-b/vpc-beta/subnet-beta-a.yaml delete mode 100644 examples/factories/example-environments/dev/conf/project-dev-b/vpc-gamma/subnet-gamma-a.yaml delete mode 100644 examples/factories/example-environments/dev/main.tf delete mode 100644 examples/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml delete mode 100644 examples/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-b.yaml delete mode 100644 examples/factories/example-environments/prod/main.tf delete mode 100644 examples/factories/example-environments/prod/versions.tf delete mode 100644 examples/factories/firewall-hierarchical-policies/README.md delete mode 100644 examples/factories/firewall-hierarchical-policies/main.tf delete mode 100644 examples/factories/firewall-hierarchical-policies/outputs.tf delete mode 100644 examples/factories/firewall-hierarchical-policies/variables.tf delete mode 100644 examples/factories/firewall-hierarchical-policies/versions.tf delete mode 100644 examples/factories/firewall-vpc-rules/README.md delete mode 100644 examples/factories/firewall-vpc-rules/flat/versions.tf delete mode 100644 examples/factories/firewall-vpc-rules/nested/README.md delete mode 100644 examples/factories/firewall-vpc-rules/nested/main.tf delete mode 100644 examples/factories/firewall-vpc-rules/nested/outputs.tf delete mode 100644 examples/factories/firewall-vpc-rules/nested/variables.tf delete mode 100644 examples/factories/firewall-vpc-rules/nested/versions.tf rename examples/factories/{firewall-vpc-rules/flat => net-vpc-firewall-yaml}/README.md (63%) rename examples/factories/{firewall-vpc-rules/flat => net-vpc-firewall-yaml}/main.tf (100%) rename examples/factories/{firewall-vpc-rules/flat => net-vpc-firewall-yaml}/outputs.tf (100%) rename examples/factories/{firewall-vpc-rules/flat => net-vpc-firewall-yaml}/variables.tf (100%) rename examples/factories/{example-environments/dev => net-vpc-firewall-yaml}/versions.tf (100%) delete mode 100644 examples/factories/subnets/README.md delete mode 100644 examples/factories/subnets/main.tf delete mode 100644 examples/factories/subnets/outputs.tf delete mode 100644 examples/factories/subnets/variables.tf delete mode 100644 examples/factories/subnets/versions.tf delete mode 100644 tests/examples/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/0987654321-foobar.yaml delete mode 100644 tests/examples/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/org.yaml delete mode 100644 tests/examples/factories/firewall_hierarchical_policies/fixture/conf/templates/cidrs.yaml delete mode 100644 tests/examples/factories/firewall_hierarchical_policies/fixture/conf/templates/service_accounts.yaml delete mode 100644 tests/examples/factories/firewall_hierarchical_policies/fixture/main.tf delete mode 100644 tests/examples/factories/firewall_hierarchical_policies/test_plan.py rename tests/examples/factories/{firewall_hierarchical_policies => net-vpc-firewall-yaml}/__init__.py (100%) rename tests/examples/factories/{vpc_firewall/flat => net-vpc-firewall-yaml}/fixture/main.tf (90%) rename tests/examples/factories/{vpc_firewall/flat => net-vpc-firewall-yaml}/fixture/rules/common.yaml (100%) rename tests/examples/factories/{vpc_firewall/flat => net-vpc-firewall-yaml}/fixture/variables.tf (100%) rename tests/examples/factories/{vpc_firewall/flat => net-vpc-firewall-yaml}/test_plan.py (100%) delete mode 100644 tests/examples/factories/subnets/__init__.py delete mode 100644 tests/examples/factories/subnets/fixture/conf/project-a/vpc-a/subnet-a.yaml delete mode 100644 tests/examples/factories/subnets/fixture/conf/project-a/vpc-a/subnet-b.yaml delete mode 100644 tests/examples/factories/subnets/fixture/conf/project-a/vpc-b/subnet-one.yaml delete mode 100644 tests/examples/factories/subnets/fixture/conf/project-b/vpc-x/subnet-alpha.yaml delete mode 100644 tests/examples/factories/subnets/fixture/main.tf delete mode 100644 tests/examples/factories/subnets/test_plan.py delete mode 100644 tests/examples/factories/vpc_firewall/__init__.py delete mode 100644 tests/examples/factories/vpc_firewall/flat/__init__.py delete mode 100644 tests/examples/factories/vpc_firewall/nested/__init__.py delete mode 100644 tests/examples/factories/vpc_firewall/nested/fixture/conf/rules/resource-factory-playground/vpc-a/ingress-loadbalancers.yaml delete mode 100644 tests/examples/factories/vpc_firewall/nested/fixture/conf/templates/cidrs.yaml delete mode 100644 tests/examples/factories/vpc_firewall/nested/fixture/conf/templates/service_accounts.yaml delete mode 100644 tests/examples/factories/vpc_firewall/nested/fixture/main.tf delete mode 100644 tests/examples/factories/vpc_firewall/nested/test_plan.py diff --git a/examples/README.md b/examples/README.md index c4fdbcf2..70df2320 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,11 +4,11 @@ This section contains **[foundational examples](./foundations/)** that bootstrap Currently available examples: +- **cloud operations** - [Resource tracking and remediation via Cloud Asset feeds](./cloud-operations/asset-inventory-feed-remediation), [Granular Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Granular Cloud DNS IAM for Shared VPC](./cloud-operations/dns-shared-vpc), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Packer image builder](./cloud-operations/packer-image-builder), [On-prem SA key management](./cloud-operations/onprem-sa-key-management) +- **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms/), [Cloud Storage to Bigquery with Cloud Dataflow](./data-solutions/gcs-to-bq-with-dataflow/) +- **factories** - [The why and the how of resource factories](./factories/README.md) - **foundations** - [single level hierarchy](./foundations/environments/) (environments), [multiple level hierarchy](./foundations/business-units/) (business units + environments) - **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop), [PSC for on-premises Cloud Function invocation](./networking/private-cloud-function-from-onprem/), [decentralized firewall](./networking/decentralized-firewall) -- **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms/), [Cloud Storage to Bigquery with Cloud Dataflow](./data-solutions/gcs-to-bq-with-dataflow/) -- **cloud operations** - [Resource tracking and remediation via Cloud Asset feeds](./cloud-operations/asset-inventory-feed-remediation), [Granular Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Granular Cloud DNS IAM for Shared VPC](./cloud-operations/dns-shared-vpc), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq), [Packer image builder](./cloud-operations/packer-image-builder), [On-prem SA key management](./cloud-operations/onprem-sa-key-management) - **third party solutions** - [OpenShift cluster on Shared VPC](./third-party-solutions/openshift) -- **factories** - [Example environments](./factories/example-environments), [Hierarchical Firewall Policies](./factories/firewall-hierarchical-policies), [VPC Firewall Rules](./factories/firewall-vpc-rules), [Subnets](./factories/subnets) For more information see the README files in the [foundations](./foundations/), [networking](./networking/), [data solutions](./data-solutions/), [cloud operations](./cloud-operations/) and [factories](./factories/) folders. diff --git a/examples/factories/README.md b/examples/factories/README.md index bb33bd74..8c2491e7 100644 --- a/examples/factories/README.md +++ b/examples/factories/README.md @@ -1,25 +1,16 @@ -# Resource Factories +# The why and the how of Resource Factories -This set of modules creates specialized resource factories, made of two distinct components: +Terraform modules can be designed - where it makes sense - to implement a resource factory, which is a configuration-driven approach to resource creation meant to: -- a module, which implements the factory logic in Terraform syntax, and -- a set of directories, which hold the configuration for the factory in YAML syntax. +- accelerate and rationalize the repetitive creation of common resources, such as firewall rules and subnets +- enable teams without Terraform specific knowledge to leverage IaC via human-friendly and machine-parseable YAML files +- make it simple to implement specific requirements and best practices (e.g. "always enable PGA for GCP subnets", or "only allow using regions `europe-west1` and `europe-west3`") +- codify and centralise business logics and policies (e.g. labels and naming conventions) +- allow to easily parse and understand sets of specific resources, for documentation purposes -## Available modules - -- [Hierarchical Firewall policies](./firewall-hierarchical-policies) -- [VPC Firewall rules](./firewall-vpc-rules) -- [Subnets](./subnets) - -## Using the modules - -Each module specialises on a single resource type, and comes with a `README.md` file which describes the module interface, and the directory/file structure each module requires. - -All modules consume specialized `yaml` configurations - located on a well-defined directory structure that carries metadata. Let's observe an example which leverages the `subnet` module, taken from the [Example environments](example-environments/) directory: +Generally speaking, the configurations for a resource factory consists in one or more YaML files, optionally grouped in folders, that describe resources following a well defined, validable schema, such as in the example below for the subnet factory of the [`net-vpc`](../../modules/net-vpc) module, which allows for the massive creation of subnets for a given VPC. ```yaml -# ../example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml - region: europe-west3 ip_cidr_range: 10.0.0.0/24 description: Sample Subnet in project project-prod-a, vpc-alpha @@ -28,21 +19,7 @@ secondary_ip_ranges: secondary-range-b: 192.168.1.0/24 ``` -This configuration creates the `subnet-alpha-a` subnet, located in VPC `vpc-alpha`, inside project `project-prod-a`. - -## Rationale - -This approach is based on modules implementing the factory logic using Terraform code, and a set of directories having a well-defined, semantic structure holding the configuration for the resources in YaML syntax. - -Resource factories are designed to: - -- accelerate and rationalize the repetitive creation of common resources, such as firewall rules and subnets -- enable teams without Terraform specific knowledge to leverage IaC via human-friendly and machine-parseable YAML files -- make it simple to implement specific requirements and best practices (e.g. "always enable PGA for GCP subnets", or "only allow using regions `europe-west1` and `europe-west3`") -- codify and centralise business logics and policies (e.g. labels and naming conventions) -- allow to easily parse and understand sets of specific resources, for documentation purposes - -Terraform natively supports YaML, JSON and CSV parsing - however we've decided to embrace YaML for the following reasons: +Terraform natively supports YaML, JSON and CSV parsing - however Fabric has decided to embrace YaML for the following reasons: - YaML is easier to parse for a human, and allows for comments and nested, complex structures - JSON and CSV can't include comments, which can be used to document configurations, but are often useful to bridge from other systems in automated pipelines @@ -50,3 +27,16 @@ Terraform natively supports YaML, JSON and CSV parsing - however we've decided t - CSV isn't often expressive enough (e.g. dit oesn't allow for nested structures) If needed, converting factories to consume JSON is a matter of switching from `yamldecode()` to `jsondecode()` in the right place on each module. + +## Resource factories in Fabric + +### Fabric Modules + +- [folder](../../modules/folder/README.md#firewall-policy-factory) and [organization](../../modules/organization/README.md#firewall-policy-factory) implement factories for [hierarchical firewall policies](https://cloud.google.com/vpc/docs/firewall-policies) +- [net-vpc](../../modules/net-vpc/README.md#subnet-factory) for subnets creation +- [net-vpc-firewall](../../modules/net-vpc-firewall/README.md#rules-factory) for massive rules creation + +### Dedicated Factories + +A dedicated resource factory for the massive creation of VPC firewall rules across different projects/VPCs is also provided as a reference: [net-vpc-firewall-yaml](net-vpc-firewall-yaml/README.md) + diff --git a/examples/factories/example-environments/README.md b/examples/factories/example-environments/README.md deleted file mode 100644 index 3bac9098..00000000 --- a/examples/factories/example-environments/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Resource Factories - -The examples in this folder are derived from actual production use cases, and show how to use a factory module, and how to structure a codebase for multiple environments. - -## Resource Factories usage - Managing subnets - -At the top level of this directory, besides the `README.md` your're reading now, you'll find - -- `dev/`, a directory which holds all configurations for the *development* environment -- `prod/`, a directory which holds all configurations for the *production* environment - -and on each directory, `main.tf`, a simple terraform file which consumes the [`subnets`](../subnets/) module and the configurations. - -Each environment directory structure is meant to mimic your GCP resource structure - -``` -. -├── dev # Environment -│ ├── conf # Configuration directory -│ │ ├── project-dev-a # Project id -│ │ │ └── vpc-alpha # VPC name -│ │ │ ├── subnet-alpha-a.yaml # Subnet name (one file per subnet) -│ │ │ └── subnet-alpha-b.yaml -│ │ └── project-dev-b -│ │ ├── vpc-beta -│ │ │ └── subnet-beta-a.yaml -│ │ └── vpc-gamma -│ │ └── subnet-gamma-a.yaml -│ └── main.tf -└── prod - ├── conf - │ └── project-prod-a - │ └── vpc-alpha - │ ├── subnet-alpha-a.yaml - │ └── subnet-alpha-b.yaml - └── main.tf -``` - -Since this resource factory only creates subnets, projects and VPCs are expected to exist. - -In this example, each environment is implemented as a distinct factory, and each has its own `main.tf` file (and hence a dedicated state). -Another option you might want to consider, in line with the CI/CD pipeline or processes you have in place, might be to leverage a single `main.tf` consuming both environment directories. - diff --git a/examples/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-a.yaml b/examples/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-a.yaml deleted file mode 100644 index 13973106..00000000 --- a/examples/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-a.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# skip boilerplate check - -region: europe-west3 -ip_cidr_range: 10.0.0.0/24 -description: Sample Subnet in project project-dev-a, vpc-alpha -secondary_ip_ranges: - secondary-range-a: 192.168.0.0/24 - secondary-range-b: 192.168.1.0/24 diff --git a/examples/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-b.yaml b/examples/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-b.yaml deleted file mode 100644 index fb859e72..00000000 --- a/examples/factories/example-environments/dev/conf/project-dev-a/vpc-alpha/subnet-alpha-b.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# skip boilerplate check - -region: europe-west3 -ip_cidr_range: 10.0.1.0/24 -description: Sample Subnet in project project-dev-a, vpc-alpha -private_ip_google_access: false diff --git a/examples/factories/example-environments/dev/conf/project-dev-b/vpc-beta/subnet-beta-a.yaml b/examples/factories/example-environments/dev/conf/project-dev-b/vpc-beta/subnet-beta-a.yaml deleted file mode 100644 index c3512f8c..00000000 --- a/examples/factories/example-environments/dev/conf/project-dev-b/vpc-beta/subnet-beta-a.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# skip boilerplate check - -region: europe-west4 -ip_cidr_range: 10.0.2.0/24 -description: Sample Subnet in project project-dev-b, vpc-beta -iam_users: ["sruffilli@google.com"] -iam_groups: [] diff --git a/examples/factories/example-environments/dev/conf/project-dev-b/vpc-gamma/subnet-gamma-a.yaml b/examples/factories/example-environments/dev/conf/project-dev-b/vpc-gamma/subnet-gamma-a.yaml deleted file mode 100644 index 9a9b8e17..00000000 --- a/examples/factories/example-environments/dev/conf/project-dev-b/vpc-gamma/subnet-gamma-a.yaml +++ /dev/null @@ -1,5 +0,0 @@ -# skip boilerplate check - -region: europe-west4 -ip_cidr_range: 10.0.3.0/24 -description: Sample Subnet in project project-dev-b, vpc-gamma diff --git a/examples/factories/example-environments/dev/main.tf b/examples/factories/example-environments/dev/main.tf deleted file mode 100644 index 36233f7b..00000000 --- a/examples/factories/example-environments/dev/main.tf +++ /dev/null @@ -1,21 +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 "subnets" { - source = "../../subnets" - config_folder = "conf" -} diff --git a/examples/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml b/examples/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml deleted file mode 100644 index 26bb4bdd..00000000 --- a/examples/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-a.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# skip boilerplate check - -region: europe-west3 -ip_cidr_range: 10.0.0.0/24 -description: Sample Subnet in project project-prod-a, vpc-alpha -secondary_ip_ranges: - secondary-range-a: 192.168.0.0/24 - secondary-range-b: 192.168.1.0/24 diff --git a/examples/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-b.yaml b/examples/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-b.yaml deleted file mode 100644 index cb1a77a1..00000000 --- a/examples/factories/example-environments/prod/conf/project-prod-a/vpc-alpha/subnet-alpha-b.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# skip boilerplate check - -region: europe-west3 -ip_cidr_range: 10.0.1.0/24 -description: Sample Subnet in project project-prod-a, vpc-alpha -private_ip_google_access: false diff --git a/examples/factories/example-environments/prod/main.tf b/examples/factories/example-environments/prod/main.tf deleted file mode 100644 index 36233f7b..00000000 --- a/examples/factories/example-environments/prod/main.tf +++ /dev/null @@ -1,21 +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 "subnets" { - source = "../../subnets" - config_folder = "conf" -} diff --git a/examples/factories/example-environments/prod/versions.tf b/examples/factories/example-environments/prod/versions.tf deleted file mode 100644 index 29041268..00000000 --- a/examples/factories/example-environments/prod/versions.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 -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -terraform { - required_version = ">= 1.0.0" - required_providers { - google = { - source = "hashicorp/google" - version = ">= 4.0.0" - } - google-beta = { - source = "hashicorp/google-beta" - version = ">= 4.0.0" - } - } -} - - diff --git a/examples/factories/firewall-hierarchical-policies/README.md b/examples/factories/firewall-hierarchical-policies/README.md deleted file mode 100644 index 1590e9b8..00000000 --- a/examples/factories/firewall-hierarchical-policies/README.md +++ /dev/null @@ -1,168 +0,0 @@ -# Google Cloud Resource Factories - Hierarchical Firewall Policies - -This module implements a resource factory which allows the creation and management of [hierarchical firewall policies](https://cloud.google.com/vpc/docs/firewall-policies) through properly formatted `yaml` files. - -`yaml` configurations are stored on a well-defined folder structure, whose entry point can be customized, and which allows for simple grouping of policies by Organization ID. - -This module also allows defining custom template variables, to centralize common CIDRs or Service Account lists, which enables re-using them across different policies. - -## Example - -### Terraform code - -```hcl -module "hierarchical" { - source = "./factories/hierarchical-firewall-policies" - config_folder = "firewall/hierarchical" - templates_folder = "firewall/templates" -} -# tftest:skip -``` - -### Configuration Structure - -The naming convention for the `config_folder` variable requires - -- the first directory layer to be named after the organization ID we're creating the policies for -- each file to be named either `$folder_id-$description.yaml` (e.g. `1234567890-sharedinfra.yaml`) for policies applying to regular folders or `org.yaml` for the root folder. - -Organizations and folders should exist prior to running this module, or set as an explicit dependency to this module, leveraging `depends_on`. - -The optional `templates_folder` variable can have two files. - -- `cidrs.yaml` - a YAML map defining lists of CIDRs -- `service_accounts.yaml` - a YAML map defining lists of Service Accounts - -Examples for both files are shown in the following section. - -```bash -└── firewall - ├── hierarchical - │ ├── 31415926535 - │ │ ├── 1234567890-sharedinfra.yaml # Maps to folders/1234567890 - │ │ └── org.yaml # Maps to organizations/31415926535 - │ └── 27182818284 - │ └── 1234567891-sharedinfra.yaml # Maps to folders/1234567891 - └── templates - ├── cidrs.yaml - └── service_accounts.yaml -``` - -### Hierarchical firewall policies format and structure - -The following syntax applies for both `$folder_id-$description.yaml` and for `org.yaml` files, with the former applying at the `$folder_id` level and the latter at the Organization level. - -Each file can contain an arbitrary number of policies. - -```yaml -# Policy name -allow-icmp: - # Description - description: Sample policy - # Direction {INGRESS, EGRESS} - direction: INGRESS - # Action {allow, deny} - action: allow - # Priority (must be unique on a node) - priority: 1000 - # List of CIDRs this rule applies to (for INGRESS rules) - source_ranges: - - 0.0.0.0/0 - # List of CIDRs this rule applies to (for EGRESS rules) - destination_ranges: - - 0.0.0.0/0 - # List of ports this rule applies to (empty array means all ports) - ports: - tcp: [] - udp: [] - icmp: [] - # List of VPCs this rule applies to - a null value implies all VPCs - target_resources: null - # Opt - List of target Service Accounts this rule applies to - target_service_accounts: - - example-service-account@foobar.iam.gserviceaccount.com - # Opt - Whether to enable logs - defaults to false - enable_logging: true -``` - -A sample configuration file might look like the following one: - -```yaml -allow-icmp: - description: Enable ICMP for all hosts - direction: INGRESS - action: allow - priority: 1000 - source_ranges: - - 0.0.0.0/0 - ports: - icmp: [] - target_resources: null - enable_logging: false - -allow-ssh-from-onprem: - description: Enable SSH for on prem hosts - direction: INGRESS - action: allow - priority: 1001 - source_ranges: - - $onprem - ports: - tcp: ["22"] - target_resources: null - enable_logging: false - -allow-443-from-clients: - description: Enable HTTPS for web clients - direction: INGRESS - action: allow - priority: 1001 - source_ranges: - - $web_clients - ports: - tcp: ["443"] - target_resources: null - target_service_accounts: - - $web_frontends - enable_logging: false -``` - -with `firewall/templates/cidrs.yaml` defined as follows: - -```yaml -onprem: - - 10.0.0.0/8 - - 192.168.0.0/16 - -web_clients: - - 172.16.0.0/16 - - 10.0.10.0/24 - - 10.0.250.0/24 -``` - -and `firewall/templates/service_accounts.yaml`: - -```yaml -web_frontends: - - web-frontends@project-wf1.iam.gserviceaccount.com - - web-frontends@project-wf2.iam.gserviceaccount.com -``` - - - - -## Variables - -| name | description | type | required | default | -|---|---|:---:|:---:|:---:| -| config_folder | Relative path of the folder containing the hierarchical firewall configuration | string | ✓ | | -| templates_folder | Relative path of the folder containing the cidr/service account templates | string | ✓ | | - -## Outputs - -| name | description | sensitive | -|---|---|:---:| -| hierarchical-firewall-rules | Generated Hierarchical Firewall Rules | | - - - diff --git a/examples/factories/firewall-hierarchical-policies/main.tf b/examples/factories/firewall-hierarchical-policies/main.tf deleted file mode 100644 index 499a2a5b..00000000 --- a/examples/factories/firewall-hierarchical-policies/main.tf +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -locals { - cidrs = try({ - for name, cidrs in yamldecode(file("${var.templates_folder}/cidrs.yaml")) : - name => cidrs - }, {}) - - service_accounts = try({ for name, service_accounts in yamldecode(file("${var.templates_folder}/service_accounts.yaml")) : - name => service_accounts - }, {}) - - org_paths = { - for file in fileset(var.config_folder, "**/*.yaml") : - file => split("/", file)[1] == "org.yaml" - ? "organizations/${split("/", file)[0]}" - : "folders/${split("-", split("/", file)[1])[0]}" - } - - rules = flatten([ - for file in fileset(var.config_folder, "**/*.yaml") : [ - for key, ruleset in yamldecode(file("${var.config_folder}/${file}")) : - merge(ruleset, { - parent_id = local.org_paths[file] - name = "${replace(local.org_paths[file], "/", "-")}-${key}" - source_ranges = try(ruleset.source_ranges, null) == null ? null : flatten( - [for cidr in ruleset.source_ranges : - can(regex("^\\$", cidr)) - ? local.cidrs[trimprefix(cidr, "$")] - : [cidr] - ]) - destination_ranges = try(ruleset.destination_ranges, null) == null ? null : flatten( - [for cidr in ruleset.destination_ranges : - can(regex("^\\$", cidr)) - ? local.cidrs[trimprefix(cidr, "$")] - : [cidr] - ]) - target_service_accounts = try(ruleset.target_service_accounts, null) == null ? null : flatten( - [for service_account in ruleset.target_service_accounts : - can(regex("^\\$", service_account)) - ? local.service_accounts[trimprefix(service_account, "$")] - : [service_account] - ]) - }) - ] - ]) -} - -resource "google_compute_firewall_policy" "default" { - for_each = { for rule in local.rules : rule.parent_id => rule.name... } - short_name = replace("hierarchical-fw-policy-${each.key}", "/", "-") - description = replace("hierarchical-fw-policy-${each.key}", "/", "-") - parent = each.key -} - -resource "google_compute_firewall_policy_rule" "default" { - for_each = { for rule in local.rules : "${rule.parent_id}-${rule.name}" => rule } - firewall_policy = google_compute_firewall_policy.default[each.value.parent_id].id - action = each.value.action - direction = each.value.direction - priority = each.value.priority - target_resources = each.value.target_resources - target_service_accounts = each.value.target_service_accounts - enable_logging = try(each.value.enable_logging, false) - # preview = each.value.preview - match { - src_ip_ranges = each.value.direction == "INGRESS" ? each.value.source_ranges : null - dest_ip_ranges = each.value.direction == "EGRESS" ? each.value.destination_ranges : null - dynamic "layer4_configs" { - for_each = each.value.ports - iterator = port - content { - ip_protocol = port.key - ports = port.value - } - } - } -} - -resource "google_compute_firewall_policy_association" "default" { - for_each = { for rule in local.rules : rule.parent_id => rule.name... } - name = replace("hierarchical-fw-policy-${each.key}", "/", "-") - attachment_target = google_compute_firewall_policy.default[each.key].parent - firewall_policy = google_compute_firewall_policy.default[each.key].id -} diff --git a/examples/factories/firewall-hierarchical-policies/outputs.tf b/examples/factories/firewall-hierarchical-policies/outputs.tf deleted file mode 100644 index 4ead20e3..00000000 --- a/examples/factories/firewall-hierarchical-policies/outputs.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. - */ - -output "hierarchical-firewall-rules" { - description = "Generated Hierarchical Firewall Rules" - value = { - for k, v in google_compute_firewall_policy_rule.default : - k => { - parent_id = split("-", k)[0] - id = v.id - } - } -} diff --git a/examples/factories/firewall-hierarchical-policies/variables.tf b/examples/factories/firewall-hierarchical-policies/variables.tf deleted file mode 100644 index 1fafc45f..00000000 --- a/examples/factories/firewall-hierarchical-policies/variables.tf +++ /dev/null @@ -1,25 +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 "config_folder" { - description = "Relative path of the folder containing the hierarchical firewall configuration" - type = string -} - -variable "templates_folder" { - description = "Relative path of the folder containing the cidr/service account templates" - type = string -} diff --git a/examples/factories/firewall-hierarchical-policies/versions.tf b/examples/factories/firewall-hierarchical-policies/versions.tf deleted file mode 100644 index 29041268..00000000 --- a/examples/factories/firewall-hierarchical-policies/versions.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 -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -terraform { - required_version = ">= 1.0.0" - required_providers { - google = { - source = "hashicorp/google" - version = ">= 4.0.0" - } - google-beta = { - source = "hashicorp/google-beta" - version = ">= 4.0.0" - } - } -} - - diff --git a/examples/factories/firewall-vpc-rules/README.md b/examples/factories/firewall-vpc-rules/README.md deleted file mode 100644 index fd65bf21..00000000 --- a/examples/factories/firewall-vpc-rules/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Google Cloud VPC Firewall Factories - -This collection of modules implement two different metodologies for the creation of VPC firewall rules, both based on leveraging well-defined `yaml` configuration files. - -- The [flat module](flat/) delegates the definition of all firewall rules metadata (project, network amongst other) to the individual `yaml` configuration. This module allows for maximum flexibility, and a custom logical grouping of resources which can be trasversal to the traditional resource hierarchy, and could be useful in scenarios where networking is not managed centrally by a single team. -- The [nested module](nested/) requires and enforces a semantical folder structure that carries some of the rules metadata (project and network), and leaves the rest to each `yaml` configuration. This solution allows for the definition of a resource hierarchy that is aligned with the organisational resource structure. diff --git a/examples/factories/firewall-vpc-rules/flat/versions.tf b/examples/factories/firewall-vpc-rules/flat/versions.tf deleted file mode 100644 index 29041268..00000000 --- a/examples/factories/firewall-vpc-rules/flat/versions.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 -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -terraform { - required_version = ">= 1.0.0" - required_providers { - google = { - source = "hashicorp/google" - version = ">= 4.0.0" - } - google-beta = { - source = "hashicorp/google-beta" - version = ">= 4.0.0" - } - } -} - - diff --git a/examples/factories/firewall-vpc-rules/nested/README.md b/examples/factories/firewall-vpc-rules/nested/README.md deleted file mode 100644 index ae6be494..00000000 --- a/examples/factories/firewall-vpc-rules/nested/README.md +++ /dev/null @@ -1,144 +0,0 @@ -# Google Cloud VPC Firewall Factory - Nested hierarchy - -This module implements a resource factory which allows the creation and management of [VPC firewall rules](https://cloud.google.com/vpc/docs/firewalls) via properly formatted `yaml` files. - -`yaml` configurations are stored in a well-defined folder structure, whose entry point can be customized, and which represents and forces the resource hierarchy a firewall rule belongs to (Project > VPC > Firewall Rule). - -This module also allows for the definition of template variables, allowing to centralize common CIDRs or Service Account lists, which enables re-using them across different policies. - -## Example - -### Terraform code - -```hcl -module "vpc-firewall" { - source = "./factories/firewall-vpc-rules/nested" - config_folder = "firewall/vpc" - templates_folder = "firewall/templates" -} - -# tftest:skip -``` - -### Configuration Structure - -The naming convention for the `config_folder` folder **requires** - -- the first directory layer to be named after the project ID which contains the VPC we're creating the firewall rules for -- the second directory layer to be named after the VPC we're creating the firewall rules for -- `yaml` files contained in the "VPC" directory can be arbitrarily named, to allow for an easier logical grouping. - -Projects and VPCs should exist prior to running this module, or set as an explicit dependency to this module, leveraging `depends_on`. - -The optional `templates_folder` folder can have two files. - -- `cidrs.yaml` - a YAML map defining lists of CIDRs -- `service_accounts.yaml` - a YAML map definint lists of Service Accounts - -```bash -└── firewall - ├── vpc - │ ├── project-resource-factory-dev - │ │ └── vpc-resource-factory-dev-one - │ │ │ ├── frontend.yaml - │ │ │ └── backend.yaml - │ │ └── vpc-resource-factory-dev-two - │ │ ├── foo.yaml - │ │ └── bar.yaml - │ └── project-resource-factory-prod - │ │ └── vpc-resource-factory-prod-alpha - │ │ ├── lorem.yaml - │ │ └── ipsum.yaml - └── templates - ├── cidrs.yaml - └── service_accounts.yaml -``` - -### Rule definition format and structure - -Firewall rules configuration should be placed in a set of yaml files in a folder/s. Firewall rule entry structure is following: - -```yaml -rule-name: # descriptive name, naming convention is adjusted by the module - description: "Allow icmp" # rule description - action: allow # `allow` or `deny` - direction: INGRESS # EGRESS or INGRESS - ports: - icmp: [] # {tcp, udp, icmp, all}: [ports], use [] for any port - priority: 1000 # rule priority value, default value is 1000 - source_ranges: # list of source ranges - - 0.0.0.0/0 - destination_ranges: # list of destination ranges - - 0.0.0.0/0 - source_tags: ['some-tag'] # list of source tags - source_service_accounts: # list of source service accounts - - myapp@myproject-id.iam.gserviceaccount.com - target_tags: ['some-tag'] # list of target tags - target_service_accounts: # list of target service accounts - - myapp@myproject-id.iam.gserviceaccount.com - enable_logging: true # `false` or `true`, logging is enabled when `true` -``` - -A sample configuration file might look like the following one: - -```yaml -allow-healthchecks: - description: "Allow traffic from healthcheck" - direction: INGRESS - action: allow - priority: 1000 - source_ranges: - - $healthcheck - ports: - tcp: ["80"] - enable_logging: false - -allow-http: - description: "Allow traffic to LB backend" - direction: INGRESS - action: allow - priority: 1000 - source_ranges: - - 0.0.0.0/0 - target_service_accounts: - - $web_frontends - ports: - tcp: ["80", "443"] - enable_logging: false - -``` - -with `firewall/templates/cidrs.yaml` defined as follows: - -```yaml -healthcheck: - - 35.191.0.0/16 - - 130.211.0.0/22 -``` - -and `firewall/templates/service_accounts.yaml`: - -```yaml -web_frontends: - - web-frontends@project-wf1.iam.gserviceaccount.com - - web-frontends@project-wf2.iam.gserviceaccount.com -``` - - - - -## Variables - -| name | description | type | required | default | -|---|---|:---:|:---:|:---:| -| config_folder | Relative path of the folder containing the hierarchical firewall configuration | string | ✓ | | -| templates_folder | Relative path of the folder containing the cidr/service account templates | string | ✓ | | - -## Outputs - -| name | description | sensitive | -|---|---|:---:| -| vpc-firewall-rules | Generated VPC Firewall Rules | | - - - diff --git a/examples/factories/firewall-vpc-rules/nested/main.tf b/examples/factories/firewall-vpc-rules/nested/main.tf deleted file mode 100644 index afbf4473..00000000 --- a/examples/factories/firewall-vpc-rules/nested/main.tf +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -locals { - cidrs = try({ for name, cidrs in yamldecode(file("${var.templates_folder}/cidrs.yaml")) : - name => cidrs - }, {}) - - service_accounts = try({ for name, service_accounts in yamldecode(file("${var.templates_folder}/service_accounts.yaml")) : - name => service_accounts - }, {}) - rules = flatten([ - for file in fileset(var.config_folder, "**/*.yaml") : [ - for key, ruleset in yamldecode(file("${var.config_folder}/${file}")) : - merge(ruleset, { - project_id = split("/", file)[0] - network = split("/", file)[1] - name = "${key}-${split("/", file)[1]}" - - source_ranges = try(ruleset.source_ranges, null) == null ? null : flatten( - [for cidr in ruleset.source_ranges : - can(regex("^\\$", cidr)) - ? local.cidrs[trimprefix(cidr, "$")] - : [cidr] - ]) - destination_ranges = try(ruleset.destination_ranges, null) == null ? null : flatten( - [for cidr in ruleset.destination_ranges : - can(regex("^\\$", cidr)) - ? local.cidrs[trimprefix(cidr, "$")] - : [cidr] - ]) - source_service_accounts = try(ruleset.source_service_accounts, null) == null ? null : flatten( - [for service_account in ruleset.source_service_accounts : - can(regex("^\\$", service_account)) - ? local.service_accounts[trimprefix(service_account, "$")] - : [service_account] - ]) - target_service_accounts = try(ruleset.target_service_accounts, null) == null ? null : flatten( - [for service_account in ruleset.target_service_accounts : - can(regex("^\\$", service_account)) - ? local.service_accounts[trimprefix(service_account, "$")] - : [service_account] - ]) - }) - ] - ]) - - rules_allow = [for item in local.rules : item if item.action == "allow"] - rules_deny = [for item in local.rules : item if item.action == "deny"] - -} - - -resource "google_compute_firewall" "rules-allow" { - for_each = { for rule in local.rules_allow : "${rule.network}-${rule.name}" => rule } - project = each.value.project_id - name = each.value.name - description = each.value.description - network = each.value.network - direction = each.value.direction - priority = each.value.priority - - source_ranges = try(each.value.source_ranges, each.value.direction == "INGRESS" ? [] : null) - source_tags = try(each.value.source_tags, null) - source_service_accounts = try(each.value.source_service_accounts, null) - - destination_ranges = try(each.value.destination_ranges, each.value.direction == "EGRESS" ? [] : null) - target_tags = try(each.value.target_tags, null) - target_service_accounts = try(each.value.target_service_accounts, null) - - dynamic "allow" { - for_each = { for proto, ports in try(each.value.ports, []) : - "${proto}-${join("-", ports)}" => { - ports = [for port in ports : tostring(port)] - protocol = proto - } - } - content { - protocol = allow.value.protocol - ports = allow.value.ports - } - } - - dynamic "log_config" { - for_each = (each.value.enable_logging == null) || (each.value.enable_logging == false) ? [] : [""] - content { - metadata = "INCLUDE_ALL_METADATA" - } - } -} - - -resource "google_compute_firewall" "rules-deny" { - for_each = { for rule in local.rules_deny : "${rule.network}-${rule.name}" => rule } - project = each.value.project_id - name = each.value.name - description = each.value.description - network = each.value.network - direction = each.value.direction - priority = each.value.priority - - source_ranges = try(each.value.source_ranges, each.value.direction == "INGRESS" ? [] : null) - source_tags = try(each.value.source_tags, null) - source_service_accounts = try(each.value.source_service_accounts, null) - - destination_ranges = try(each.value.destination_ranges, each.value.direction == "EGRESS" ? [] : null) - target_tags = try(each.value.target_tags, null) - target_service_accounts = try(each.value.target_service_accounts, null) - - dynamic "deny" { - for_each = { for proto, ports in try(each.value.ports, []) : - "${proto}-${join("-", ports)}" => { - ports = [for port in ports : tostring(port)] - protocol = proto - } - } - content { - protocol = deny.value.protocol - ports = deny.value.ports - } - } - - dynamic "log_config" { - for_each = (each.value.enable_logging == null) || (each.value.enable_logging == false) ? [] : [""] - content { - metadata = "INCLUDE_ALL_METADATA" - } - } -} diff --git a/examples/factories/firewall-vpc-rules/nested/outputs.tf b/examples/factories/firewall-vpc-rules/nested/outputs.tf deleted file mode 100644 index e6f5dc3f..00000000 --- a/examples/factories/firewall-vpc-rules/nested/outputs.tf +++ /dev/null @@ -1,20 +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. - */ - -output "vpc-firewall-rules" { - description = "Generated VPC Firewall Rules" - value = merge(google_compute_firewall.rules-allow, google_compute_firewall.rules-deny) -} diff --git a/examples/factories/firewall-vpc-rules/nested/variables.tf b/examples/factories/firewall-vpc-rules/nested/variables.tf deleted file mode 100644 index 1fafc45f..00000000 --- a/examples/factories/firewall-vpc-rules/nested/variables.tf +++ /dev/null @@ -1,25 +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 "config_folder" { - description = "Relative path of the folder containing the hierarchical firewall configuration" - type = string -} - -variable "templates_folder" { - description = "Relative path of the folder containing the cidr/service account templates" - type = string -} diff --git a/examples/factories/firewall-vpc-rules/nested/versions.tf b/examples/factories/firewall-vpc-rules/nested/versions.tf deleted file mode 100644 index 29041268..00000000 --- a/examples/factories/firewall-vpc-rules/nested/versions.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 -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -terraform { - required_version = ">= 1.0.0" - required_providers { - google = { - source = "hashicorp/google" - version = ">= 4.0.0" - } - google-beta = { - source = "hashicorp/google-beta" - version = ">= 4.0.0" - } - } -} - - diff --git a/examples/factories/firewall-vpc-rules/flat/README.md b/examples/factories/net-vpc-firewall-yaml/README.md similarity index 63% rename from examples/factories/firewall-vpc-rules/flat/README.md rename to examples/factories/net-vpc-firewall-yaml/README.md index 6baa5862..064ee30c 100644 --- a/examples/factories/firewall-vpc-rules/flat/README.md +++ b/examples/factories/net-vpc-firewall-yaml/README.md @@ -1,4 +1,4 @@ -# Google Cloud VPC Firewall Factory - Flat hierarchy +# Google Cloud VPC Firewall Factory This module allows creation and management of different types of firewall rules by defining them in well formatted `yaml` files. @@ -140,21 +140,21 @@ web-app-a-ingress: ## Variables -| name | description | type | required | default | -|---|---|:---:|:---:|:---:| -| config_directories | List of paths to folders where firewall configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml` | list(string) | ✓ | | -| network | Name of the network this set of firewall rules applies to. | string | ✓ | | -| project_id | Project Id. | string | ✓ | | -| log_config | Log configuration. Possible values for `metadata` are `EXCLUDE_ALL_METADATA` and `INCLUDE_ALL_METADATA`. Set to `null` for disabling firewall logging. | object({…}) | | null | +| name | description | type | required | default | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------: | :------: | :---------------: | +| config_directories | List of paths to folders where firewall configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml` | list(string) | ✓ | | +| network | Name of the network this set of firewall rules applies to. | string | ✓ | | +| project_id | Project Id. | string | ✓ | | +| log_config | Log configuration. Possible values for `metadata` are `EXCLUDE_ALL_METADATA` and `INCLUDE_ALL_METADATA`. Set to `null` for disabling firewall logging. | object({…}) | | null | ## Outputs -| name | description | sensitive | -|---|---|:---:| -| egress_allow_rules | Egress rules with allow blocks. | | -| egress_deny_rules | Egress rules with allow blocks. | | -| ingress_allow_rules | Ingress rules with allow blocks. | | -| ingress_deny_rules | Ingress rules with deny blocks. | | +| name | description | sensitive | +| ------------------- | -------------------------------- | :-------: | +| egress_allow_rules | Egress rules with allow blocks. | | +| egress_deny_rules | Egress rules with allow blocks. | | +| ingress_allow_rules | Ingress rules with allow blocks. | | +| ingress_deny_rules | Ingress rules with deny blocks. | | diff --git a/examples/factories/firewall-vpc-rules/flat/main.tf b/examples/factories/net-vpc-firewall-yaml/main.tf similarity index 100% rename from examples/factories/firewall-vpc-rules/flat/main.tf rename to examples/factories/net-vpc-firewall-yaml/main.tf diff --git a/examples/factories/firewall-vpc-rules/flat/outputs.tf b/examples/factories/net-vpc-firewall-yaml/outputs.tf similarity index 100% rename from examples/factories/firewall-vpc-rules/flat/outputs.tf rename to examples/factories/net-vpc-firewall-yaml/outputs.tf diff --git a/examples/factories/firewall-vpc-rules/flat/variables.tf b/examples/factories/net-vpc-firewall-yaml/variables.tf similarity index 100% rename from examples/factories/firewall-vpc-rules/flat/variables.tf rename to examples/factories/net-vpc-firewall-yaml/variables.tf diff --git a/examples/factories/example-environments/dev/versions.tf b/examples/factories/net-vpc-firewall-yaml/versions.tf similarity index 100% rename from examples/factories/example-environments/dev/versions.tf rename to examples/factories/net-vpc-firewall-yaml/versions.tf diff --git a/examples/factories/subnets/README.md b/examples/factories/subnets/README.md deleted file mode 100644 index 5b922632..00000000 --- a/examples/factories/subnets/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Google Cloud Resource Factories - VPC Subnets - -This module implements a resource factory which allows the creation and management of subnets through properly formatted `yaml` files. - -`yaml` configurations are stored on a well-defined folder structure, whose entry point can be customized, and which allows for simple grouping of subnets by Project > VPC. - -## Example - -### Terraform code - -```hcl -module "subnets" { - source = "./factories/subnets" - config_folder = "subnets" -} -# tftest:skip -``` - -### Configuration Structure - -The directory structure implies the project and the VPC each subnet belongs to. -Per the structure below, a subnet named `subnet-a` (after filename `subnet-a.yaml`) will be created on VPC `vpc-alpha-one` which belongs to project `project-alpha`. - -Projects and VPCs should exist prior to running this module, or set as an explicit dependency to this module, leveraging `depends_on`. - -```bash -└── subnets - ├── project-alpha - │ ├── vpc-alpha-one - │ │ ├── subnet-a.yaml - │ │ └── subnet-b.yaml - │ └── vpc-alpha-two - │ └── subnet-c.yaml - └── project-bravo - └── vpc-bravo-one - └── subnet-d.yaml -``` - -### Subnet definition format and structure - -```yaml -region: europe-west1 # Region where the subnet will be creted -description: Sample description # Description -ip_cidr_range: 10.0.0.0/24 # Primary IP range for the subnet -private_ip_google_access: false # Opt- Enables PGA. Defaults to true -iam_users: ["foobar@example.com"] # Opt- Users to grant compute/networkUser to -iam_groups: ["lorem@example.com"] # Opt- Groups to grant compute/networkUser to -iam_service_accounts: ["foobar@project-id.iam.gserviceaccount.com"] - # Opt- SAs to grant compute/networkUser to -secondary_ip_ranges: # Opt- List of secondary IP ranges - - secondary-range-a: 192.168.0.0/24 - # Secondary ranges in name: cidr format -``` - - - - -## Variables - -| name | description | type | required | default | -|---|---|:---:|:---:|:---:| -| config_folder | Relative path of the folder containing the subnet configuration | string | ✓ | | - -## Outputs - -| name | description | sensitive | -|---|---|:---:| -| subnet | Generated subnets | | - - - diff --git a/examples/factories/subnets/main.tf b/examples/factories/subnets/main.tf deleted file mode 100644 index e837d807..00000000 --- a/examples/factories/subnets/main.tf +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -locals { - _data = { - for f in fileset(var.config_folder, "**/*.yaml") : - trimsuffix(split("/", f)[2], ".yaml") => merge( - yamldecode(file("${var.config_folder}/${f}")), - { - project_id = split("/", f)[0] - network = split("/", f)[1] - } - ) - } - - data = { - for k, v in local._data : k => merge(v, - { - network_users : concat( - formatlist("group:%s", try(v.iam_groups, [])), - formatlist("user:%s", try(v.iam_users, [])), - formatlist("serviceAccount:%s", try(v.iam_service_accounts, [])) - ) - } - ) - } -} - -resource "google_compute_subnetwork" "default" { - for_each = local.data - project = each.value.project_id - network = each.value.network - name = each.key - region = each.value.region - description = each.value.description - ip_cidr_range = each.value.ip_cidr_range - private_ip_google_access = try(each.value.private_ip_google_access, true) - - dynamic "secondary_ip_range" { - for_each = try(each.value.secondary_ip_ranges, []) - iterator = secondary_range - content { - range_name = secondary_range.key - ip_cidr_range = secondary_range.value - } - } -} - - -resource "google_compute_subnetwork_iam_binding" "default" { - for_each = { - for k, v in local.data : k => v if length(v.network_users) > 0 - } - project = each.value.project_id - subnetwork = google_compute_subnetwork.default[each.key].name - region = each.value.region - role = "roles/compute.networkUser" - members = each.value.network_users -} diff --git a/examples/factories/subnets/outputs.tf b/examples/factories/subnets/outputs.tf deleted file mode 100644 index b068c1cc..00000000 --- a/examples/factories/subnets/outputs.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. - */ - -output "subnet" { - description = "Generated subnets" - value = { - for k, v in google_compute_subnetwork.default : - k => { - id = v.id - network = v.network - project = v.project - range = v.ip_cidr_range - region = v.region - self_link = v.self_link - } - } -} diff --git a/examples/factories/subnets/variables.tf b/examples/factories/subnets/variables.tf deleted file mode 100644 index c6d3e731..00000000 --- a/examples/factories/subnets/variables.tf +++ /dev/null @@ -1,20 +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 "config_folder" { - description = "Relative path of the folder containing the subnet configuration" - type = string -} diff --git a/examples/factories/subnets/versions.tf b/examples/factories/subnets/versions.tf deleted file mode 100644 index 29041268..00000000 --- a/examples/factories/subnets/versions.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 -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -terraform { - required_version = ">= 1.0.0" - required_providers { - google = { - source = "hashicorp/google" - version = ">= 4.0.0" - } - google-beta = { - source = "hashicorp/google-beta" - version = ">= 4.0.0" - } - } -} - - diff --git a/examples/networking/README.md b/examples/networking/README.md index 6b7cf720..6585aef1 100644 --- a/examples/networking/README.md +++ b/examples/networking/README.md @@ -46,5 +46,5 @@ It is meant to be used as a starting point for most Shared VPC configurations, a ### Decentralized firewall management - This [example](./decentralized-firewall/) shows how a decentralized firewall management can be organized using the [firewall factory](../factories/firewall-vpc-rules/). + This [example](./decentralized-firewall/) shows how a decentralized firewall management can be organized using the [firewall factory](../factories/net-vpc-firewall-yaml/).
diff --git a/examples/networking/decentralized-firewall/README.md b/examples/networking/decentralized-firewall/README.md index 08a81144..8bf40135 100644 --- a/examples/networking/decentralized-firewall/README.md +++ b/examples/networking/decentralized-firewall/README.md @@ -1,6 +1,6 @@ # Decentralized firewall management -This sample shows how a decentralized firewall management can be organized using the [firewall factory](../../factories/firewall-vpc-rules/). +This sample shows how a decentralized firewall management can be organized using the [firewall factory](../../factories/net-vpc-firewall-yaml/README.md). This approach is a good fit when Shared VPCs are used across multiple application/infrastructure teams. A central repository keeps environment/team specific folders with firewall definitions in `yaml` format. @@ -25,22 +25,22 @@ in the [`validator/`](validator/) subdirectory, which can be integrated as part ## Variables -| name | description | type | required | default | -|---|---|:---:|:---:|:---:| -| billing_account_id | Billing account id used as default for new projects. | string | ✓ | | -| prefix | Prefix used for resources that need unique names. | string | ✓ | | -| root_node | Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'. | string | ✓ | | -| ip_ranges | Subnet IP CIDR ranges. | map(string) | | {…} | -| project_services | Service APIs enabled by default in new projects. | list(string) | | […] | -| region | Region used. | string | | "europe-west1" | +| name | description | type | required | default | +| ------------------ | --------------------------------------------------------------------------------------------- | :-------------------------------: | :------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| billing_account_id | Billing account id used as default for new projects. | string | ✓ | | +| prefix | Prefix used for resources that need unique names. | string | ✓ | | +| root_node | Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'. | string | ✓ | | +| ip_ranges | Subnet IP CIDR ranges. | map(string) | | {…} | +| project_services | Service APIs enabled by default in new projects. | list(string) | | […] | +| region | Region used. | string | | "europe-west1" | ## Outputs -| name | description | sensitive | -|---|---|:---:| -| fw_rules | Firewall rules. | | -| projects | Project ids. | | -| vpc | Shared VPCs. | | +| name | description | sensitive | +| -------- | --------------- | :-------: | +| fw_rules | Firewall rules. | | +| projects | Project ids. | | +| vpc | Shared VPCs. | | diff --git a/examples/networking/decentralized-firewall/main.tf b/examples/networking/decentralized-firewall/main.tf index 69174a82..fbc5d973 100644 --- a/examples/networking/decentralized-firewall/main.tf +++ b/examples/networking/decentralized-firewall/main.tf @@ -109,7 +109,7 @@ module "dns-api-dev" { ############################################################################### module "vpc-firewall-prod" { - source = "../../factories/firewall-vpc-rules/flat" + source = "../../factories/net-vpc-firewall-yaml" project_id = module.project-host-prod.project_id network = module.vpc-prod.name @@ -125,7 +125,7 @@ module "vpc-firewall-prod" { } module "vpc-firewall-dev" { - source = "../../factories/firewall-vpc-rules/flat" + source = "../../factories/net-vpc-firewall-yaml" project_id = module.project-host-dev.project_id network = module.vpc-dev.name diff --git a/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/0987654321-foobar.yaml b/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/0987654321-foobar.yaml deleted file mode 100644 index 49385718..00000000 --- a/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/0987654321-foobar.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# skip boilerplate check - -allow-ssh-from-onprem: - description: Enable SSH for onprem ranges - direction: INGRESS - action: allow - priority: 1001 - source_ranges: - - $example - ports: - tcp: ["22"] - target_resources: null - enable_logging: false diff --git a/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/org.yaml b/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/org.yaml deleted file mode 100644 index 649561b9..00000000 --- a/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/rules/1234567890/org.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# skip boilerplate check - -allow-icmp: - description: Enable ICMP for all hosts - direction: INGRESS - action: allow - priority: 1000 - source_ranges: - - 0.0.0.0/0 - ports: - icmp: [] - target_resources: null - enable_logging: false diff --git a/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/templates/cidrs.yaml b/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/templates/cidrs.yaml deleted file mode 100644 index 789ea960..00000000 --- a/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/templates/cidrs.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# skip boilerplate check - -example: - - 10.0.0.0/24 - - 10.0.10.0/24 - - 192.168.1.1/32 - -healthcheck: - - 35.191.0.0/16 - - 130.211.0.0/22 \ No newline at end of file diff --git a/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/templates/service_accounts.yaml b/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/templates/service_accounts.yaml deleted file mode 100644 index e4c68edd..00000000 --- a/tests/examples/factories/firewall_hierarchical_policies/fixture/conf/templates/service_accounts.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# skip boilerplate check - -example: - - example-service-account@resource-factory-playground.iam.gserviceaccount.com diff --git a/tests/examples/factories/firewall_hierarchical_policies/fixture/main.tf b/tests/examples/factories/firewall_hierarchical_policies/fixture/main.tf deleted file mode 100644 index 89cc9a6d..00000000 --- a/tests/examples/factories/firewall_hierarchical_policies/fixture/main.tf +++ /dev/null @@ -1,21 +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 "hierarchical-firewall-rules" { - source = "../../../../../examples/factories/firewall-hierarchical-policies/" - config_folder = "conf/rules" - templates_folder = "conf/templates" -} diff --git a/tests/examples/factories/firewall_hierarchical_policies/test_plan.py b/tests/examples/factories/firewall_hierarchical_policies/test_plan.py deleted file mode 100644 index 1cf86271..00000000 --- a/tests/examples/factories/firewall_hierarchical_policies/test_plan.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. - -def test_firewall(plan_runner): - "Test hierarchical firewall rules from conf/rules" - _, resources = plan_runner() - assert len(resources) == 6 - assert set(r["type"] for r in resources) == set([ - "google_compute_firewall_policy_rule", "google_compute_firewall_policy_association", "google_compute_firewall_policy" - ]) - rule_ssh = [r["values"] for r in resources if r["type"] == - "google_compute_firewall_policy_rule" - and r["values"]["priority"] == 1001] - rule_icmp = [r["values"] for r in resources if r["type"] == - "google_compute_firewall_policy_rule" - and r["values"]["priority"] == 1000] - association_org = [r["values"] for r in resources if r["type"] == - "google_compute_firewall_policy_association" - and r["values"]["attachment_target"] == "organizations/1234567890"] - association_folder = [r["values"] for r in resources if r["type"] == - "google_compute_firewall_policy_association" - and r["values"]["attachment_target"] == "folders/0987654321"] - policies_org = [r["values"] for r in resources if r["type"] == - "google_compute_firewall_policy" - and r["values"]["parent"] == "organizations/1234567890"] - policies_folder = [r["values"] for r in resources if r["type"] == - "google_compute_firewall_policy" - and r["values"]["parent"] == "folders/0987654321"] - - assert set(rule_ssh[0]["match"][0]["src_ip_ranges"]) == set( - ["10.0.0.0/24", "10.0.10.0/24", "192.168.1.1/32"]) - assert rule_icmp[0]["match"][0]["layer4_configs"][0]["ip_protocol"] == "icmp" - assert association_org[0]["name"] == "hierarchical-fw-policy-organizations-1234567890" - assert association_folder[0]["name"] == "hierarchical-fw-policy-folders-0987654321" - assert policies_org[0]["short_name"] == "hierarchical-fw-policy-organizations-1234567890" - assert policies_folder[0]["short_name"] == "hierarchical-fw-policy-folders-0987654321" diff --git a/tests/examples/factories/firewall_hierarchical_policies/__init__.py b/tests/examples/factories/net-vpc-firewall-yaml/__init__.py similarity index 100% rename from tests/examples/factories/firewall_hierarchical_policies/__init__.py rename to tests/examples/factories/net-vpc-firewall-yaml/__init__.py diff --git a/tests/examples/factories/vpc_firewall/flat/fixture/main.tf b/tests/examples/factories/net-vpc-firewall-yaml/fixture/main.tf similarity index 90% rename from tests/examples/factories/vpc_firewall/flat/fixture/main.tf rename to tests/examples/factories/net-vpc-firewall-yaml/fixture/main.tf index 5838e223..724ff9d2 100644 --- a/tests/examples/factories/vpc_firewall/flat/fixture/main.tf +++ b/tests/examples/factories/net-vpc-firewall-yaml/fixture/main.tf @@ -15,7 +15,7 @@ */ module "firewall" { - source = "../../../../../../examples/factories/firewall-vpc-rules/flat" + source = "../../../../../examples/factories/net-vpc-firewall-yaml" project_id = "my-project" network = "my-network" config_directories = [ diff --git a/tests/examples/factories/vpc_firewall/flat/fixture/rules/common.yaml b/tests/examples/factories/net-vpc-firewall-yaml/fixture/rules/common.yaml similarity index 100% rename from tests/examples/factories/vpc_firewall/flat/fixture/rules/common.yaml rename to tests/examples/factories/net-vpc-firewall-yaml/fixture/rules/common.yaml diff --git a/tests/examples/factories/vpc_firewall/flat/fixture/variables.tf b/tests/examples/factories/net-vpc-firewall-yaml/fixture/variables.tf similarity index 100% rename from tests/examples/factories/vpc_firewall/flat/fixture/variables.tf rename to tests/examples/factories/net-vpc-firewall-yaml/fixture/variables.tf diff --git a/tests/examples/factories/vpc_firewall/flat/test_plan.py b/tests/examples/factories/net-vpc-firewall-yaml/test_plan.py similarity index 100% rename from tests/examples/factories/vpc_firewall/flat/test_plan.py rename to tests/examples/factories/net-vpc-firewall-yaml/test_plan.py diff --git a/tests/examples/factories/subnets/__init__.py b/tests/examples/factories/subnets/__init__.py deleted file mode 100644 index 6d6d1266..00000000 --- a/tests/examples/factories/subnets/__init__.py +++ /dev/null @@ -1,13 +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. diff --git a/tests/examples/factories/subnets/fixture/conf/project-a/vpc-a/subnet-a.yaml b/tests/examples/factories/subnets/fixture/conf/project-a/vpc-a/subnet-a.yaml deleted file mode 100644 index 9cf4a1e2..00000000 --- a/tests/examples/factories/subnets/fixture/conf/project-a/vpc-a/subnet-a.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# skip boilerplate check - -region: europe-west1 -ip_cidr_range: 10.0.0.0/24 -description: Sample Subnet in project project-a, vpc-a -secondary_ip_ranges: - secondary-range-a: 192.168.0.0/24 - secondary-range-b: 192.168.1.0/24 diff --git a/tests/examples/factories/subnets/fixture/conf/project-a/vpc-a/subnet-b.yaml b/tests/examples/factories/subnets/fixture/conf/project-a/vpc-a/subnet-b.yaml deleted file mode 100644 index f8dab01a..00000000 --- a/tests/examples/factories/subnets/fixture/conf/project-a/vpc-a/subnet-b.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# skip boilerplate check - -region: europe-west3 -ip_cidr_range: 10.0.1.0/24 -description: Sample Subnet in project project-a, vpc-a -private_ip_google_access: false diff --git a/tests/examples/factories/subnets/fixture/conf/project-a/vpc-b/subnet-one.yaml b/tests/examples/factories/subnets/fixture/conf/project-a/vpc-b/subnet-one.yaml deleted file mode 100644 index 85b5607b..00000000 --- a/tests/examples/factories/subnets/fixture/conf/project-a/vpc-b/subnet-one.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# skip boilerplate check - -region: europe-west4 -ip_cidr_range: 10.0.2.0/24 -description: Sample Subnet in project project-a, vpc-b -iam_users: ["sruffilli@google.com"] -iam_groups: [] diff --git a/tests/examples/factories/subnets/fixture/conf/project-b/vpc-x/subnet-alpha.yaml b/tests/examples/factories/subnets/fixture/conf/project-b/vpc-x/subnet-alpha.yaml deleted file mode 100644 index 0af68295..00000000 --- a/tests/examples/factories/subnets/fixture/conf/project-b/vpc-x/subnet-alpha.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# skip boilerplate check - -region: europe-west4 -ip_cidr_range: 172.16.0.0/24 -description: Sample Subnet in project project-b, vpc-x -iam_users: ["sruffilli@google.com"] -iam_groups: [] diff --git a/tests/examples/factories/subnets/fixture/main.tf b/tests/examples/factories/subnets/fixture/main.tf deleted file mode 100644 index 2eedceda..00000000 --- a/tests/examples/factories/subnets/fixture/main.tf +++ /dev/null @@ -1,20 +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 "subnets" { - source = "../../../../../examples/factories/subnets" - config_folder = "conf" -} diff --git a/tests/examples/factories/subnets/test_plan.py b/tests/examples/factories/subnets/test_plan.py deleted file mode 100644 index de0a28dc..00000000 --- a/tests/examples/factories/subnets/test_plan.py +++ /dev/null @@ -1,60 +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 - -def test_firewall(plan_runner): - "Test hierarchical firewall rules from conf/rules" - _, resources = plan_runner() - assert len(resources) == 6 - assert set(r["type"] for r in resources) == set( - ["google_compute_subnetwork", "google_compute_subnetwork_iam_binding"]) - subnets = [ - r["values"] for r in resources - if r["type"] == "google_compute_subnetwork" - ] - iam_bindings = [ - r["values"] for r in resources - if r["type"] == "google_compute_subnetwork_iam_binding" - ] - - subnet_a_a = [ - s for s in subnets if s["project"] == "project-a" - and s["network"] == "vpc-a" and s["name"] == "subnet-a" - ][0] - assert subnet_a_a["ip_cidr_range"] == "10.0.0.0/24" - assert subnet_a_a["private_ip_google_access"] == True - assert subnet_a_a["region"] == "europe-west1" - assert subnet_a_a["secondary_ip_range"] == [{ - "ip_cidr_range": - "192.168.0.0/24", - "range_name": - "secondary-range-a" - }, { - "ip_cidr_range": - "192.168.1.0/24", - "range_name": - "secondary-range-b" - }] - - subnet_a_b = [ - s for s in subnets if s["project"] == "project-a" - and s["network"] == "vpc-a" and s["name"] == "subnet-b" - ][0] - assert subnet_a_b["private_ip_google_access"] == False - - iam_binding_b_alpha = [ - b for b in iam_bindings if b["project"] == "project-b" - ][0] - assert set(iam_binding_b_alpha["members"]) == set( - ["user:sruffilli@google.com"]) - assert iam_binding_b_alpha["role"] == "roles/compute.networkUser" - assert iam_binding_b_alpha["subnetwork"] == "subnet-alpha" diff --git a/tests/examples/factories/vpc_firewall/__init__.py b/tests/examples/factories/vpc_firewall/__init__.py deleted file mode 100644 index 6d6d1266..00000000 --- a/tests/examples/factories/vpc_firewall/__init__.py +++ /dev/null @@ -1,13 +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. diff --git a/tests/examples/factories/vpc_firewall/flat/__init__.py b/tests/examples/factories/vpc_firewall/flat/__init__.py deleted file mode 100644 index 6d6d1266..00000000 --- a/tests/examples/factories/vpc_firewall/flat/__init__.py +++ /dev/null @@ -1,13 +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. diff --git a/tests/examples/factories/vpc_firewall/nested/__init__.py b/tests/examples/factories/vpc_firewall/nested/__init__.py deleted file mode 100644 index 6d6d1266..00000000 --- a/tests/examples/factories/vpc_firewall/nested/__init__.py +++ /dev/null @@ -1,13 +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. diff --git a/tests/examples/factories/vpc_firewall/nested/fixture/conf/rules/resource-factory-playground/vpc-a/ingress-loadbalancers.yaml b/tests/examples/factories/vpc_firewall/nested/fixture/conf/rules/resource-factory-playground/vpc-a/ingress-loadbalancers.yaml deleted file mode 100644 index 46acd4ea..00000000 --- a/tests/examples/factories/vpc_firewall/nested/fixture/conf/rules/resource-factory-playground/vpc-a/ingress-loadbalancers.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# skip boilerplate check - -allow-healthchecks: - description: "Allow traffic from healthcheck" - direction: INGRESS - action: allow - priority: 1000 - source_ranges: - - $healthcheck - ports: - tcp: ["80"] - enable_logging: false - -allow-http: - description: "Allow traffic to LB backend" - direction: INGRESS - action: allow - priority: 1000 - source_ranges: - - 0.0.0.0/0 - target_service_accounts: - - example-service-account@resource-factory-playground.iam.gserviceaccount.com - ports: - tcp: ["80", "443"] - enable_logging: true diff --git a/tests/examples/factories/vpc_firewall/nested/fixture/conf/templates/cidrs.yaml b/tests/examples/factories/vpc_firewall/nested/fixture/conf/templates/cidrs.yaml deleted file mode 100644 index 789ea960..00000000 --- a/tests/examples/factories/vpc_firewall/nested/fixture/conf/templates/cidrs.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# skip boilerplate check - -example: - - 10.0.0.0/24 - - 10.0.10.0/24 - - 192.168.1.1/32 - -healthcheck: - - 35.191.0.0/16 - - 130.211.0.0/22 \ No newline at end of file diff --git a/tests/examples/factories/vpc_firewall/nested/fixture/conf/templates/service_accounts.yaml b/tests/examples/factories/vpc_firewall/nested/fixture/conf/templates/service_accounts.yaml deleted file mode 100644 index 0a28b28b..00000000 --- a/tests/examples/factories/vpc_firewall/nested/fixture/conf/templates/service_accounts.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# skip boilerplate check - -couchbase: - - example-service-account@resource-factory-playground.iam.gserviceaccount.com diff --git a/tests/examples/factories/vpc_firewall/nested/fixture/main.tf b/tests/examples/factories/vpc_firewall/nested/fixture/main.tf deleted file mode 100644 index d80d4a50..00000000 --- a/tests/examples/factories/vpc_firewall/nested/fixture/main.tf +++ /dev/null @@ -1,21 +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 "vpc-firewall-rules" { - source = "../../../../../../examples/factories/firewall-vpc-rules/nested" - config_folder = "conf/rules" - templates_folder = "conf/templates" -} diff --git a/tests/examples/factories/vpc_firewall/nested/test_plan.py b/tests/examples/factories/vpc_firewall/nested/test_plan.py deleted file mode 100644 index 5234ee54..00000000 --- a/tests/examples/factories/vpc_firewall/nested/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 - -def test_firewall(plan_runner): - "Test hierarchical firewall rules from conf/rules" - _, resources = plan_runner() - assert len(resources) == 2 - - assert set(r["type"] - for r in resources) == set(["google_compute_firewall"]) - - rule_hc = [ - r["values"] for r in resources - if r["values"]["name"] == "allow-healthchecks-vpc-a" - ][0] - rule_be = [ - r["values"] for r in resources - if r["values"]["description"] == "Allow traffic to LB backend" - ][0] - - assert set(rule_hc["source_ranges"]) == set( - ["130.211.0.0/22", "35.191.0.0/16"]) - assert rule_hc["direction"] == "INGRESS" - assert rule_hc["network"] == "vpc-a" - assert rule_hc["priority"] == 1000 - assert rule_hc["project"] == "resource-factory-playground" - assert rule_hc["allow"][0] == {'ports': ['80'], 'protocol': 'tcp'} - assert rule_be["log_config"][0] == {'metadata': 'INCLUDE_ALL_METADATA'}