diff --git a/modules/net-ilb-l7/README.md b/modules/net-ilb-l7/README.md index 64a3f7761..c3373e2f1 100644 --- a/modules/net-ilb-l7/README.md +++ b/modules/net-ilb-l7/README.md @@ -1,7 +1,8 @@ # Internal (HTTP/S) Load Balancer Module -The module allows managing Internal HTTP/HTTPS Load Balancers (HTTP(S) ILBs), integrating the forwarding rule, the url-map, the backends, optional health checks and SSL certificates. -It's designed to be a simple match for the [`vpc`](../net-vpc) and the [`compute-mig`](../compute-mig) modules, which can be used to manage VPCs and instance groups. +This module allows managing Internal HTTP/HTTPS Load Balancers (L7 ILBs). It's designed to expose the full configuration of the underlying resources, and to facilitate common usage patterns by providing sensible defaults, and optionally managing prerequisite resources like health checks, instance groups, etc. + +Due to the complexity of the underlying resources, changes to the configuration that involve recreation of resources are best applied in stages, starting by disabling the configuration in the urlmap that references the resources that neeed recreation, then doing the same for the backend service, etc. ## Examples @@ -10,375 +11,478 @@ It's designed to be a simple match for the [`vpc`](../net-vpc) and the [`compute An HTTP ILB with a backend service pointing to a GCE instance group: ```hcl -module "ilb" { +module "ilb-l7" { source = "./fabric/modules/net-ilb-l7" name = "ilb-test" project_id = var.project_id region = "europe-west1" - network = var.vpc.self_link - subnetwork = var.subnet.self_link - - backend_services_config = { - my-backend-svc = { - backends = [ - { - group = "projects/my-project/zones/europe-west1-a/instanceGroups/my-ig" - options = null - } - ] - health_checks = [] - log_config = null - options = null + backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] } } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + } } # tftest modules=1 resources=5 ``` -Network and subnetwork can be entered using their name (if present in the same project) or leveraging their link id. The latter is mandatory if you're trying to deploy an ILB in a shared VPC environment. +An HTTPS ILB needs a few additional fields: ```hcl -module "ilb" { +module "ilb-l7" { source = "./fabric/modules/net-ilb-l7" name = "ilb-test" project_id = var.project_id region = "europe-west1" - network = "projects/my-host-project/global/networks/my-shared-vpc" - subnetwork = "projects/my-host-project/regions/europe-west1/subnetworks/my-shared-subnet" - - backend_services_config = { - my-backend-svc = { - backends = [ - { - group = "projects/my-project/zones/europe-west1-a/instanceGroups/my-ig" - options = null - } - ] - health_checks = [] - log_config = null - options = null + backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] } } -} -# tftest modules=1 resources=5 -``` - -### Defining Health Checks - -If no health checks are specified, a default health check is created and associated to each backend service without health checks already associated. The default health check configuration can be modified through the `health_checks_config_defaults` variable. - -If the `health_checks_config_defaults` variable is set to null, no default health checks will be automatically associted to backend services. - -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 with that id alredy exists. - -For example, to contextually create a health check and attach it to the backend service: - -```hcl -module "ilb" { - source = "./fabric/modules/net-ilb-l7" - name = "ilb-test" - project_id = var.project_id - region = "europe-west1" - network = var.vpc.self_link - subnetwork = var.subnet.self_link - - backend_services_config = { - my-backend-svc = { - backends = [ - { - group = "projects/my-project/zones/europe-west1-a/instanceGroups/my-ig" - options = null - } - ], - health_checks = ["hc-1"] - log_config = null - options = null - } - } - - health_checks_config = { - hc-1 = { - type = "http" - logging = true - options = { - timeout_sec = 5 - } - check = { - port_specification = "USE_SERVING_PORT" - } - } - } -} -# tftest modules=1 resources=5 -``` - -### Network Endpoint Groups (NEGs) - -Zonal Network Endpoint Groups (NEGs) can also be used, as shown in the example below. - -```hcl -module "ilb" { - source = "./fabric/modules/net-ilb-l7" - name = "ilb-test" - project_id = var.project_id - region = "europe-west1" - network = var.vpc.self_link - subnetwork = var.subnet.self_link - - backend_services_config = { - my-backend-svc = { - backends = [ - { - group = google_compute_network_endpoint_group.my-neg.id - options = { - balancing_mode = "RATE" - capacity_scaler = 1.0 - max_connections = null - max_connections_per_instance = null - max_connections_per_endpoint = null - max_rate = 100 - max_rate_per_endpoint = null - max_rate_per_instance = null - max_utilization = null - } - } - ], - health_checks = [] - log_config = null - options = null - } - } -} - -resource "google_compute_network_endpoint_group" "my-neg" { - name = "my-neg" - project = var.project_id - network = var.vpc.self_link - subnetwork = var.subnet.self_link - default_port = "90" - zone = "europe-west1-b" -} -# tftest modules=1 resources=6 -``` ---> - -### 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 a backend service as the default backend - -```hcl -module "ilb" { - source = "./fabric/modules/net-ilb-l7" - name = "ilb-test" - project_id = var.project_id - region = "europe-west1" - network = var.vpc.self_link - subnetwork = var.subnet.self_link - - url_map_config = { - default_service = "my-backend-svc" - default_url_redirect = null - tests = null - host_rules = [] - path_matchers = [ - { - name = "my-example-page" - path_rules = [ - { - paths = ["/my-example-page"] - service = "another-group-backend" - } - ] - } + protocol = "HTTPS" + ssl_certificates = { + certificate_ids = [ + "projects/myprj/regions/europe-west1/sslCertificates/my-cert" ] } - - backend_services_config = { - my-backend-svc = { - backends = [ - { - group = "projects/my-project/zones/europe-west1-a/instanceGroups/my-ig" - options = null - } - ], - health_checks = [] - log_config = null - options = null - }, - my-example-page = { - backends = [ - { - group = "projects/my-project/zones/europe-west1-a/instanceGroups/another-ig" - 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 "ilb" { - source = "./fabric/modules/net-ilb-l7" - name = "ilb-test" - project_id = var.project_id - region = "europe-west1" - network = var.vpc.self_link - subnetwork = var.subnet.self_link - - static_ip_config = { - reserve = true - options = null - } - - backend_services_config = { - my-backend-svc = { - backends = [ - { - group = "projects/my-project/zones/europe-west1-a/instanceGroups/my-ig" - 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. - -When HTTPS is enabled, 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 "ilb" { - source = "./fabric/modules/net-ilb-l7" - name = "ilb-test" - project_id = var.project_id - region = "europe-west1" - network = var.vpc.self_link - subnetwork = var.subnet.self_link - - https = true - - target_proxy_https_config = { - ssl_certificates = [ - "an-existing-cert" - ] - } - - backend_services_config = { - my-backend-svc = { - backends = [ - { - group = "projects/my-project/zones/europe-west1-a/instanceGroups/my-ig" - options = null - } - ] - health_checks = [] - log_config = null - options = null - } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link } } # tftest modules=1 resources=5 ``` -Otherwise, unmanaged certificates can also be contextually created: +### Health Checks + +You can leverage externally defined health checks for backend services, or have the module create them for you. By default a simple HTTP health check is created, and used in backend services. + +Health check configuration is controlled via the `health_check_configs` variable, which behaves in a similar way to other LB modules in this repository. + +Defining different health checks fromt he default is very easy. You can for example replace the default HTTP health check with a TCP one and reference it in you backend service: ```hcl -module "ilb" { +module "ilb-l7" { source = "./fabric/modules/net-ilb-l7" name = "ilb-test" project_id = var.project_id region = "europe-west1" - network = var.vpc.self_link - subnetwork = var.subnet.self_link - - https = true - - ssl_certificates_config = { - my-domain = { - domains = [ - "my-domain.com" - ], - tls_private_key = tls_private_key.self_signed_key.private_key_pem - tls_self_signed_cert = tls_self_signed_cert.self_signed_cert.cert_pem + backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] + health_checks = ["custom-tcp"] } } - - target_proxy_https_config = { - ssl_certificates = [ - "my-domain" - ] - } - - backend_services_config = { - my-backend-svc = { - backends = [ - { - group = "projects/my-project/zones/europe-west1-a/instanceGroups/my-ig" - options = null - } - ], - health_checks = [] - log_config = null - options = null + health_check_configs = { + custom-tcp = { + tcp = { port = 80 } } } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + } } +# tftest modules=1 resources=5 +``` -resource "tls_private_key" "self_signed_key" { +To leverage existing health checks without having the module create them, simply pass their self links to backend services and set the `health_check_configs` variable to an empty map: + +```hcl +module "ilb-l7" { + source = "./fabric/modules/net-ilb-l7" + name = "ilb-test" + project_id = var.project_id + region = "europe-west1" + backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] + health_checks = ["projects/myprj/global/healthChecks/custom"] + } + } + health_check_configs = {} + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + } +} +# tftest modules=1 resources=4 +``` + +### SSL Certificates + +Similarly to health checks, SSL certificates can also be created by the module. In this example we are using private key and certificate resources so that the example test only depends on Terraform providers, but in real use those can be replaced by external files. + +```hcl + +resource "tls_private_key" "default" { algorithm = "RSA" - rsa_bits = 2048 + rsa_bits = 4096 } -resource "tls_self_signed_cert" "self_signed_cert" { - private_key_pem = tls_private_key.self_signed_key.private_key_pem - validity_period_hours = 12 - early_renewal_hours = 3 - dns_names = ["example.com"] +resource "tls_self_signed_cert" "default" { + private_key_pem = tls_private_key.default.private_key_pem + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" + } + validity_period_hours = 720 allowed_uses = [ "key_encipherment", "digital_signature", - "server_auth" + "server_auth", ] - subject { - common_name = "example.com" - organization = "My Test Org" +} + +module "ilb-l7" { + source = "./fabric/modules/net-ilb-l7" + name = "ilb-test" + project_id = var.project_id + region = "europe-west1" + backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] + } + } + health_check_configs = { + default = { + https = { port = 443 } + } + } + protocol = "HTTPS" + ssl_certificates = { + create_configs = { + default = { + # certificate and key could also be read via file() from external files + certificate = tls_self_signed_cert.default.cert_pem + private_key = tls_private_key.default.private_key_pem + } + } + } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link } } # tftest modules=1 resources=8 ``` -## Components And Files Mapping +### Instance Groups -An Internal HTTP 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 module can optionally create unmanaged instance groups, which can then be referred to in backends via their key: -- The global load balancer [forwarding rule](forwarding-rule.tf) binds a frontend public Virtual IP (VIP) to an HTTP(S) [target proxy](target-proxy.tf). -- If the target proxy is HTTPS, it requires one or more unmanaged [SSL certificates](ssl-certificates.tf). -- Target proxies leverage [url-maps](url-map.tf): a set of L7 rules that create a mapping between specific hostnames, URIs (and more) to one or more [backends services](backend-services.tf). -- [Backend services](backend-services.tf) link to one or multiple infrastructure groups (GCE instance groups or NEGs). It is assumed in this module that groups have been previously created through other modules, and referenced in the input variables. -- Backend services support one or more [health checks](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. +```hcl +module "ilb-l7" { + source = "./fabric/modules/net-ilb-l7" + name = "ilb-test" + project_id = var.project_id + region = "europe-west1" + backend_service_configs = { + default = { + port_name = "http" + backends = [ + { group = "default" } + ] + } + } + group_configs = { + default = { + zone = "europe-west1-b" + instances = [ + "projects/myprj/zones/europe-west1-b/instances/vm-a" + ] + named_ports = { http = 80 } + } + } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + } +} +# tftest modules=1 resources=6 +``` + +### Network Endpoint Groups (NEGs) + +Zonal Network Endpoint Groups (NEGs) can be used as backends, by passing their id as the backend group in a backends service configuration: + +```hcl +module "ilb-l7" { + source = "./fabric/modules/net-ilb-l7" + name = "ilb-test" + project_id = var.project_id + region = "europe-west1" + backend_service_configs = { + default = { + backends = [{ + balancing_mode = "RATE" + group = "projects/myprj/zones/europe-west1-a/networkEndpointGroups/my-neg" + max_rate = { per_endpoint = 1 } + }] + } + } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + } +} +# tftest modules=1 resources=5 +``` + +Similarly to instance groups, NEGs can also be managed by this module: + +```hcl +module "ilb-l7" { + source = "./fabric/modules/net-ilb-l7" + name = "ilb-test" + project_id = var.project_id + region = "europe-west1" + backend_service_configs = { + default = { + backends = [{ + balancing_mode = "RATE" + group = "my-neg" + max_rate = { per_endpoint = 1 } + }] + } + } + neg_configs = { + my-neg = { + zone = "europe-west1-b" + endpoints = [{ + ip_address = "10.0.0.10" + instance = "test-1" + port = 80 + }] + } + } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + } +} +# tftest modules=1 resources=7 +``` + +Hybrid NEGs are also supported: + +```hcl +module "ilb-l7" { + source = "./fabric/modules/net-ilb-l7" + name = "ilb-test" + project_id = var.project_id + region = "europe-west1" + backend_service_configs = { + default = { + backends = [{ + balancing_mode = "RATE" + group = "my-neg" + max_rate = { per_endpoint = 1 } + }] + } + } + neg_configs = { + my-neg = { + zone = "europe-west1-b" + is_hybrid = true + endpoints = [{ + ip_address = "10.0.0.10" + port = 80 + }] + } + } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + } +} +# tftest modules=1 resources=7 +``` + +### URL Map + +The module exposes the full URL map resource configuration, with some minor changes to the interface to decrease verbosity, and support for aliasing backend services via keys. + +The default URL map configuration sets the `default` backend service as the default service for the load balancer as a convenience. Just override the `urlmap_config` variable to change the default behaviour: + +```hcl +module "ilb-l7" { + source = "./fabric/modules/net-ilb-l7" + name = "ilb-test" + project_id = var.project_id + region = "europe-west1" + backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] + } + video = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig-2" + }] + } + } + urlmap_config = { + default_service = "default" + host_rules = [{ + hosts = ["*"] + path_matcher = "pathmap" + }] + path_matchers = { + pathmap = { + default_service = "default" + path_rules = [{ + paths = ["/video", "/video/*"] + service = "video" + }] + } + } + } + vpc_config = { + network = var.vpc.self_link + subnetwork = var.subnet.self_link + } +} + +# tftest modules=1 resources=6 +``` + +### Complex example + +This example mixes group and NEG backends, and shows how to set HTTPS for specific backends. + +```hcl +module "ilb-l7" { + source = "./fabric/modules/net-ilb-l7" + name = "ilb-l7-test-0" + project_id = "prj-gce" + region = "europe-west8" + backend_service_configs = { + default = { + backends = [ + { group = "nginx-ew8-b" }, + { group = "nginx-ew8-c" }, + ] + } + gce-neg = { + backends = [{ + balancing_mode = "RATE" + group = "neg-nginx-ew8-c" + max_rate = { per_endpoint = 1 } + }] + } + home = { + backends = [{ + balancing_mode = "RATE" + group = "neg-home-hello" + max_rate = { + per_endpoint = 1 + } + }] + health_checks = ["neg"] + locality_lb_policy = "ROUND_ROBIN" + protocol = "HTTPS" + } + } + group_configs = { + nginx-ew8-b = { + zone = "europe-west8-b" + instances = [ + "projects/prj-gce/zones/europe-west8-b/instances/nginx-ew8-b" + ] + named_ports = { http = 80 } + } + nginx-ew8-c = { + zone = "europe-west8-c" + instances = [ + "projects/prj-gce/zones/europe-west8-c/instances/nginx-ew8-c" + ] + named_ports = { http = 80 } + } + } + health_check_configs = { + default = { + http = { + port = 80 + } + } + neg = { + https = { + host = "hello.home.example.com" + port = 443 + } + } + } + neg_configs = { + neg-nginx-ew8-c = { + zone = "europe-west8-c" + endpoints = [ + { + ip_address = "10.24.32.26" + instance = "nginx-ew8-c" + port = 80 + } + ] + } + neg-home-hello = { + zone = "europe-west8-b" + is_hybrid = true + endpoints = [ + { + ip_address = "192.168.0.3" + port = 443 + } + ] + } + } + urlmap_config = { + default_service = "default" + host_rules = [ + { + hosts = ["*"] + path_matcher = "gce" + }, + { + hosts = ["hello.home.example.com"] + path_matcher = "home" + } + ] + path_matchers = { + gce = { + default_service = "default" + path_rules = [ + { + paths = ["/gce-neg", "/gce-neg/*"] + service = "gce-neg" + } + ] + } + home = { + default_service = "home" + } + } + } + vpc_config = { + network = "projects/prj-host/global/networks/shared-vpc" + subnetwork = "projects/prj-host/regions/europe-west8/subnetworks/gce" + } +} +# tftest modules=1 resources=14 +``` @@ -387,13 +491,18 @@ An Internal HTTP Load Balancer is made of multiple components, that change depen | name | description | resources | |---|---|---| -| [backend-services.tf](./backend-services.tf) | Bucket and group backend services. | google_compute_region_backend_service | -| [forwarding-rule.tf](./forwarding-rule.tf) | IP Address and forwarding rule. | google_compute_address · google_compute_forwarding_rule | -| [health-checks.tf](./health-checks.tf) | Health checks. | google_compute_region_health_check | -| [outputs.tf](./outputs.tf) | Module outputs. | | -| [ssl-certificates.tf](./ssl-certificates.tf) | SSL certificates. | google_compute_region_ssl_certificate | -| [target-proxy.tf](./target-proxy.tf) | HTTP and HTTPS target proxies. | google_compute_region_target_http_proxy · google_compute_region_target_https_proxy | -| [url-map.tf](./url-map.tf) | URL maps. | google_compute_region_url_map | +| [backend-service.tf](./backend-service.tf) | Backend service resources. | google_compute_region_backend_service | +| [health-check.tf](./health-check.tf) | Health check resource. | google_compute_health_check | +| [main.tf](./main.tf) | Module-level locals and resources. | google_compute_forwarding_rule · google_compute_instance_group · google_compute_network_endpoint · google_compute_network_endpoint_group · google_compute_region_ssl_certificate · google_compute_region_target_http_proxy · google_compute_region_target_https_proxy | +| [outputs.tf](./outputs.tf) | Module outputs. | +# value = google_compute_forwarding_rule.default +# } + +output | +| [urlmap.tf](./urlmap.tf) | URL map resources. | google_compute_region_url_map | +| [variables-backend-service.tf](./variables-backend-service.tf) | Backend services variables. | | +| [variables-health-check.tf](./variables-health-check.tf) | Health check variable. | | +| [variables-urlmap.tf](./variables-urlmap.tf) | URLmap variable. | | | [variables.tf](./variables.tf) | Module variables. | | | [versions.tf](./versions.tf) | Version pins. | | @@ -401,31 +510,31 @@ An Internal HTTP Load Balancer is made of multiple components, that change depen | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L17) | Load balancer name. | string | ✓ | | -| [project_id](variables.tf#L22) | Project id. | string | ✓ | | -| [region](variables.tf#L159) | The region where to allocate the ILB resources. | string | ✓ | | -| [subnetwork](variables.tf#L189) | The subnetwork where the ILB VIP is allocated. | string | ✓ | | -| [backend_services_config](variables.tf#L27) | The backends services configuration. | map(object({…})) | | {} | -| [forwarding_rule_config](variables.tf#L98) | Forwarding rule configurations. | object({…}) | | {…} | -| [health_checks_config](variables.tf#L118) | Custom health checks configuration. | map(object({…})) | | {} | -| [health_checks_config_defaults](variables.tf#L129) | Auto-created health check default configuration. | object({…}) | | {…} | -| [https](variables.tf#L147) | Whether to enable HTTPS. | bool | | false | -| [network](variables.tf#L153) | The network where the ILB is created. | string | | "default" | -| [ssl_certificates_config](variables.tf#L164) | The SSL certificates configuration. | map(object({…})) | | {} | -| [static_ip_config](variables.tf#L174) | Static IP address configuration. | object({…}) | | {…} | -| [target_proxy_https_config](variables.tf#L194) | The HTTPS target proxy configuration. | object({…}) | | null | -| [url_map_config](variables.tf#L202) | The url-map configuration. | object({…}) | | null | +| [name](variables.tf#L46) | Load balancer name. | string | ✓ | | +| [project_id](variables.tf#L79) | Project id. | string | ✓ | | +| [region](variables.tf#L103) | The region where to allocate the ILB resources. | string | ✓ | | +| [vpc_config](variables.tf#L121) | VPC-level configuration. | object({…}) | ✓ | | +| [address](variables.tf#L17) | Optional IP address used for the forwarding rule. | string | | null | +| [backend_service_configs](variables-backend-service.tf#L19) | Backend service level configuration. | map(object({…})) | | {} | +| [description](variables.tf#L23) | Optional description used for resources. | string | | "Terraform managed." | +| [group_configs](variables.tf#L29) | Optional unmanaged groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} | +| [health_check_configs](variables-health-check.tf#L19) | Optional auto-created health check configurations, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | map(object({…})) | | {…} | +| [labels](variables.tf#L40) | Labels set on resources. | map(string) | | {} | +| [neg_configs](variables.tf#L51) | Optional network endpoint groups to create. Can be referenced in backends via key or outputs. | map(object({…})) | | {} | +| [network_tier_premium](variables.tf#L72) | Use premium network tier. Defaults to true. | bool | | true | +| [ports](variables.tf#L84) | Optional ports for HTTP load balancer, valid ports are 80 and 8080. | list(string) | | null | +| [protocol](variables.tf#L90) | Protocol supported by this load balancer. | string | | "HTTP" | +| [ssl_certificates](variables.tf#L108) | SSL target proxy certificates (only if protocol is HTTPS). | object({…}) | | {} | +| [urlmap_config](variables-urlmap.tf#L19) | The URL map configuration. | object({…}) | | {…} | ## Outputs | name | description | sensitive | |---|---|:---:| -| [backend_services](outputs.tf#L22) | Backend service resources. | | -| [forwarding_rule](outputs.tf#L55) | The forwarding rule. | | -| [health_checks](outputs.tf#L17) | Health-check resources. | | -| [ip_address](outputs.tf#L41) | The reserved IP address. | | -| [ssl_certificate_link_ids](outputs.tf#L34) | The SSL certificate. | | -| [target_proxy](outputs.tf#L46) | The target proxy. | | -| [url_map](outputs.tf#L29) | The url-map. | | +| [address](outputs.tf#L17) | Forwarding rule address. | | +| [backend_service_ids](outputs.tf#L22) | Backend service resources. | | +| [group_ids](outputs.tf#L34) | Autogenerated instance group ids. | | +| [health_check_ids](outputs.tf#L41) | Autogenerated health check ids. | | +| [neg_ids](outputs.tf#L48) | Autogenerated network endpoint group ids. | | diff --git a/modules/net-ilb-l7/backend-service.tf b/modules/net-ilb-l7/backend-service.tf new file mode 100644 index 000000000..87e618b5b --- /dev/null +++ b/modules/net-ilb-l7/backend-service.tf @@ -0,0 +1,227 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# tfdoc:file:description Backend service resources. + +locals { + group_ids = merge( + { + for k, v in google_compute_instance_group.default : k => v.id + }, + { + for k, v in google_compute_network_endpoint_group.default : k => v.id + } + ) + hc_ids = { + for k, v in google_compute_health_check.default : k => v.id + } +} + +resource "google_compute_region_backend_service" "default" { + provider = google-beta + for_each = var.backend_service_configs + project = var.project_id + region = var.region + name = "${var.name}-${each.key}" + description = var.description + affinity_cookie_ttl_sec = each.value.affinity_cookie_ttl_sec + connection_draining_timeout_sec = each.value.connection_draining_timeout_sec + health_checks = [ + for k in each.value.health_checks : lookup(local.hc_ids, k, k) + ] # not for internet / serverless NEGs + locality_lb_policy = each.value.locality_lb_policy + load_balancing_scheme = "INTERNAL_MANAGED" + port_name = each.value.port_name # defaults to http, not for NEGs + protocol = ( + each.value.protocol == null ? var.protocol : each.value.protocol + ) + session_affinity = each.value.session_affinity + timeout_sec = each.value.timeout_sec + + dynamic "backend" { + for_each = { for b in coalesce(each.value.backends, []) : b.group => b } + content { + group = lookup(local.group_ids, backend.key, backend.key) + balancing_mode = backend.value.balancing_mode + capacity_scaler = backend.value.capacity_scaler + description = backend.value.description + failover = backend.value.failover + max_connections = try( + backend.value.max_connections.per_group, null + ) + max_connections_per_endpoint = try( + backend.value.max_connections.per_endpoint, null + ) + max_connections_per_instance = try( + backend.value.max_connections.per_instance, null + ) + max_rate = try( + backend.value.max_rate.per_group, null + ) + max_rate_per_endpoint = try( + backend.value.max_rate.per_endpoint, null + ) + max_rate_per_instance = try( + backend.value.max_rate.per_instance, null + ) + max_utilization = backend.value.max_utilization + } + } + + dynamic "circuit_breakers" { + for_each = ( + each.value.circuit_breakers == null ? [] : [each.value.circuit_breakers] + ) + iterator = cb + content { + max_connections = cb.value.max_connections + max_pending_requests = cb.value.max_pending_requests + max_requests = cb.value.max_requests + max_requests_per_connection = cb.value.max_requests_per_connection + max_retries = cb.value.max_retries + dynamic "connect_timeout" { + for_each = ( + cb.value.connect_timeout == null ? [] : [cb.value.connect_timeout] + ) + content { + seconds = connect_timeout.value.seconds + nanos = connect_timeout.value.nanos + } + } + } + } + + dynamic "connection_tracking_policy" { + for_each = ( + each.value.connection_tracking == null + ? [] + : [each.value.connection_tracking] + ) + iterator = cb + content { + connection_persistence_on_unhealthy_backends = ( + cb.value.persist_conn_on_unhealthy != null + ? cb.value.persist_conn_on_unhealthy + : null + ) + idle_timeout_sec = cb.value.idle_timeout_sec + tracking_mode = ( + cb.value.track_per_session != null + ? cb.value.track_per_session + : null + ) + } + } + + dynamic "consistent_hash" { + for_each = ( + each.value.consistent_hash == null ? [] : [each.value.consistent_hash] + ) + iterator = ch + content { + http_header_name = ch.value.http_header_name + minimum_ring_size = ch.value.minimum_ring_size + dynamic "http_cookie" { + for_each = ch.value.http_cookie == null ? [] : [ch.value.http_cookie] + content { + name = http_cookie.value.name + path = http_cookie.value.path + dynamic "ttl" { + for_each = ( + http_cookie.value.ttl == null ? [] : [http_cookie.value.ttl] + ) + content { + seconds = ttl.value.seconds + nanos = ttl.value.nanos + } + } + } + } + } + } + + dynamic "failover_policy" { + for_each = ( + each.value.failover_config == null ? [] : [each.value.failover_config] + ) + iterator = fc + content { + disable_connection_drain_on_failover = fc.value.disable_conn_drain + drop_traffic_if_unhealthy = fc.value.drop_traffic_if_unhealthy + failover_ratio = fc.value.ratio + } + } + + dynamic "iap" { + for_each = each.value.iap_config == null ? [] : [each.value.iap_config] + content { + oauth2_client_id = iap.value.oauth2_client_id + oauth2_client_secret = iap.value.oauth2_client_secret + oauth2_client_secret_sha256 = iap.value.oauth2_client_secret_sha256 + } + } + + dynamic "log_config" { + for_each = each.value.log_sample_rate == null ? [] : [""] + content { + enable = true + sample_rate = each.value.log_sample_rate + } + } + + dynamic "outlier_detection" { + for_each = ( + each.value.outlier_detection == null ? [] : [each.value.outlier_detection] + ) + iterator = od + content { + consecutive_errors = od.value.consecutive_errors + consecutive_gateway_failure = od.value.consecutive_gateway_failure + enforcing_consecutive_errors = od.value.enforcing_consecutive_errors + enforcing_consecutive_gateway_failure = od.value.enforcing_consecutive_gateway_failure + enforcing_success_rate = od.value.enforcing_success_rate + max_ejection_percent = od.value.max_ejection_percent + success_rate_minimum_hosts = od.value.success_rate_minimum_hosts + success_rate_request_volume = od.value.success_rate_request_volume + success_rate_stdev_factor = od.value.success_rate_stdev_factor + dynamic "base_ejection_time" { + for_each = ( + od.value.base_ejection_time == null ? [] : [od.value.base_ejection_time] + ) + content { + seconds = base_ejection_time.value.seconds + nanos = base_ejection_time.value.nanos + } + } + dynamic "interval" { + for_each = ( + od.value.interval == null ? [] : [od.value.interval] + ) + content { + seconds = interval.value.seconds + nanos = interval.value.nanos + } + } + } + } + + dynamic "subsetting" { + for_each = each.value.enable_subsetting == true ? [""] : [] + content { + policy = "CONSISTENT_HASH_SUBSETTING" + } + } +} diff --git a/modules/net-ilb-l7/backend-services.tf b/modules/net-ilb-l7/backend-services.tf deleted file mode 100644 index 3e8ccc2fe..000000000 --- a/modules/net-ilb-l7/backend-services.tf +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -# tfdoc:file:description Bucket and group backend services. - -resource "google_compute_region_backend_service" "backend_service" { - for_each = var.backend_services_config - name = "${var.name}-${each.key}" - project = var.project_id - description = "Terraform managed." - affinity_cookie_ttl_sec = try(each.value.options.affinity_cookie_ttl_sec, null) - connection_draining_timeout_sec = try(each.value.options.connection_draining_timeout_sec, null) - load_balancing_scheme = "INTERNAL_MANAGED" - locality_lb_policy = try(each.value.options.locality_lb_policy, null) - port_name = try(each.value.options.port_name, null) - protocol = try(each.value.options.protocol, null) - region = var.region - session_affinity = try(each.value.options.session_affinity, null) - timeout_sec = try(each.value.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(length(each.value.health_checks), 0) == 0 - ? try( - [google_compute_region_health_check.health_check["default"].self_link], - null - ) - : [ - for hc in each.value.health_checks : - try(google_compute_region_health_check.health_check[hc].self_link, hc) - ] - ) - - dynamic "backend" { - for_each = try(each.value.backends, []) - content { - balancing_mode = try(backend.value.options.balancing_mode, "UTILIZATION") - capacity_scaler = try(backend.value.options.capacity_scaler, 1.0) - 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.options.circuit_breakers, null) == null - ? [] - : [each.value.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.options.consistent_hash, null) == null - ? [] - : [each.value.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 "iap" { - for_each = ( - try(each.value.options.iap, null) == null - ? [] - : [each.value.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.log_config, null) == null - ? [] - : [each.value.log_config] - ) - content { - enable = try(log_config.value.enable, null) - sample_rate = try(log_config.value.sample_rate, null) - } - } -} diff --git a/modules/net-ilb-l7/forwarding-rule.tf b/modules/net-ilb-l7/forwarding-rule.tf deleted file mode 100644 index 31ff65256..000000000 --- a/modules/net-ilb-l7/forwarding-rule.tf +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -# tfdoc:file:description IP Address and forwarding rule. - -locals { - ip_address = ( - var.static_ip_config.reserve - ? google_compute_address.static_ip.0.id - : null - ) - - port_range = coalesce( - var.forwarding_rule_config.port_range, - var.https ? "443" : "80" - ) - - target = ( - var.https - ? google_compute_region_target_https_proxy.https.0.id - : google_compute_region_target_http_proxy.http.0.id - ) -} - -resource "google_compute_address" "static_ip" { - count = var.static_ip_config.reserve ? 1 : 0 - provider = google-beta - name = var.name - project = var.project_id - description = "Terraform managed." - address_type = "INTERNAL" - address = try(var.static_ip_config.options.address, null) - purpose = "GCE_ENDPOINT" - region = try(var.region, null) - subnetwork = try(var.static_ip_config.options.subnet, var.subnetwork, null) -} - -resource "google_compute_forwarding_rule" "forwarding_rule" { - provider = google-beta - name = var.name - project = var.project_id - description = "Terraform managed." - ip_address = local.ip_address - ip_protocol = "TCP" - labels = try(var.forwarding_rule_config.labels, {}) - load_balancing_scheme = "INTERNAL_MANAGED" - network = try(var.forwarding_rule_config.network, null) - network_tier = var.forwarding_rule_config.network_tier - port_range = local.port_range - ports = [] - region = try(var.region, null) - service_label = try(var.forwarding_rule_config.service_label, null) - subnetwork = try(var.subnetwork, null) - target = local.target -} diff --git a/modules/net-ilb-l7/health-check.tf b/modules/net-ilb-l7/health-check.tf new file mode 100644 index 000000000..c9df52a22 --- /dev/null +++ b/modules/net-ilb-l7/health-check.tf @@ -0,0 +1,109 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# tfdoc:file:description Health check resource. + +resource "google_compute_health_check" "default" { + provider = google-beta + for_each = var.health_check_configs + project = var.project_id + name = "${var.name}-${each.key}" + description = each.value.description + check_interval_sec = each.value.check_interval_sec + healthy_threshold = each.value.healthy_threshold + timeout_sec = each.value.timeout_sec + unhealthy_threshold = each.value.unhealthy_threshold + + dynamic "grpc_health_check" { + for_each = try(each.value.grpc, null) != null ? [""] : [] + content { + port = each.value.grpc.port + port_name = each.value.grpc.port_name + port_specification = each.value.grpc.port_specification + grpc_service_name = each.value.grpc.service_name + } + } + + dynamic "http_health_check" { + for_each = try(each.value.http, null) != null ? [""] : [] + content { + host = each.value.http.host + port = each.value.http.port + port_name = each.value.http.port_name + port_specification = each.value.http.port_specification + proxy_header = each.value.http.proxy_header + request_path = each.value.http.request_path + response = each.value.http.response + } + } + + dynamic "http2_health_check" { + for_each = try(each.value.http2, null) != null ? [""] : [] + content { + host = each.value.http2.host + port = each.value.http2.port + port_name = each.value.http2.port_name + port_specification = each.value.http2.port_specification + proxy_header = each.value.http2.proxy_header + request_path = each.value.http2.request_path + response = each.value.http2.response + } + } + + dynamic "https_health_check" { + for_each = try(each.value.https, null) != null ? [""] : [] + content { + host = each.value.https.host + port = each.value.https.port + port_name = each.value.https.port_name + port_specification = each.value.https.port_specification + proxy_header = each.value.https.proxy_header + request_path = each.value.https.request_path + response = each.value.https.response + } + } + + dynamic "ssl_health_check" { + for_each = try(each.value.ssl, null) != null ? [""] : [] + content { + port = each.value.ssl.port + port_name = each.value.ssl.port_name + port_specification = each.value.ssl.port_specification + proxy_header = each.value.ssl.proxy_header + request = each.value.ssl.request + response = each.value.ssl.response + } + } + + dynamic "tcp_health_check" { + for_each = try(each.value.tcp, null) != null ? [""] : [] + content { + port = each.value.tcp.port + port_name = each.value.tcp.port_name + port_specification = each.value.tcp.port_specification + proxy_header = each.value.tcp.proxy_header + request = each.value.tcp.request + response = each.value.tcp.response + } + } + + dynamic "log_config" { + for_each = try(each.value.enable_logging, null) == true ? [""] : [] + content { + enable = true + } + } +} diff --git a/modules/net-ilb-l7/health-checks.tf b/modules/net-ilb-l7/health-checks.tf deleted file mode 100644 index d484e6e8f..000000000 --- a/modules/net-ilb-l7/health-checks.tf +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -# tfdoc:file:description Health checks. - -locals { - # Get backend services without health checks defined - _backends_without_hcs = [ - for k, v in coalesce(var.backend_services_config, {}) : - v if( - try(v.health_checks, null) == null - || length(try(v.health_checks, [])) == 0 - ) - ] - - health_checks_config_defaults = ( - try(var.health_checks_config_defaults, null) == null - ? null - : { default = var.health_checks_config_defaults } - ) - - # If at least one group backend service without HC is defined, - # create also a default HC (if default HC is not null) - health_checks_config = ( - length(local._backends_without_hcs) > 0 - ? merge( - coalesce(local.health_checks_config_defaults, {}), - coalesce(var.health_checks_config, {}) - ) - : coalesce(var.health_checks_config, {}) - ) -} - -resource "google_compute_region_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) - region = var.region - 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-ilb-l7/main.tf b/modules/net-ilb-l7/main.tf new file mode 100644 index 000000000..2845c3479 --- /dev/null +++ b/modules/net-ilb-l7/main.tf @@ -0,0 +1,135 @@ +/** + * 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 { + _neg_endpoints = flatten([ + for k, v in var.neg_configs : [ + for vv in v.endpoints : merge(vv, { neg = k, zone = v.zone }) + ] + ]) + fwd_rule_ports = ( + var.protocol == "HTTPS" ? [443] : coalesce(var.ports, [80]) + ) + fwd_rule_target = ( + var.protocol == "HTTPS" + ? google_compute_region_target_https_proxy.default.0.id + : google_compute_region_target_http_proxy.default.0.id + ) + neg_endpoints = { + for v in local._neg_endpoints : + "${v.neg}-${v.ip_address}-${coalesce(v.port, "none")}" => v + } + proxy_ssl_certificates = concat( + coalesce(var.ssl_certificates.certificate_ids, []), + [for k, v in google_compute_region_ssl_certificate.default : v.id] + ) +} + +resource "google_compute_forwarding_rule" "default" { + provider = google-beta + project = var.project_id + region = var.region + name = var.name + description = var.description + ip_address = var.address + ip_protocol = "TCP" + load_balancing_scheme = "INTERNAL_MANAGED" + network = var.vpc_config.network + network_tier = var.network_tier_premium ? "PREMIUM" : "STANDARD" + port_range = join(",", local.fwd_rule_ports) + subnetwork = var.vpc_config.subnetwork + labels = var.labels + target = local.fwd_rule_target + # service_directory_registrations +} + +resource "google_compute_region_ssl_certificate" "default" { + for_each = var.ssl_certificates.create_configs + project = var.project_id + region = var.region + name = "${var.name}-${each.key}" + certificate = each.value.certificate + private_key = each.value.private_key +} + +resource "google_compute_region_target_http_proxy" "default" { + count = var.protocol == "HTTPS" ? 0 : 1 + project = var.project_id + region = var.region + name = var.name + description = var.description + url_map = google_compute_region_url_map.default.id +} + +resource "google_compute_region_target_https_proxy" "default" { + count = var.protocol == "HTTPS" ? 1 : 0 + project = var.project_id + region = var.region + name = var.name + description = var.description + ssl_certificates = local.proxy_ssl_certificates + url_map = google_compute_region_url_map.default.id +} + +resource "google_compute_instance_group" "default" { + for_each = var.group_configs + project = var.project_id + zone = each.value.zone + name = "${var.name}-${each.key}" + description = var.description + instances = each.value.instances + dynamic "named_port" { + for_each = each.value.named_ports + content { + name = named_port.key + port = named_port.value + } + } +} + +resource "google_compute_network_endpoint_group" "default" { + for_each = var.neg_configs + project = var.project_id + zone = each.value.zone + name = "${var.name}-${each.key}" + # re-enable once provider properly supports this + # default_port = each.value.default_port + description = var.description + network_endpoint_type = ( + each.value.is_hybrid + ? "NON_GCP_PRIVATE_IP_PORT" + : "GCE_VM_IP_PORT" + ) + network = try(each.value.vpc_config.network, var.vpc_config.network) + subnetwork = ( + each.value.is_hybrid + ? null + : try(each.value.vpc_config.subnetwork, var.vpc_config.subnetwork) + ) +} + +resource "google_compute_network_endpoint" "default" { + for_each = local.neg_endpoints + project = var.project_id + network_endpoint_group = ( + google_compute_network_endpoint_group.default[each.value.neg].name + ) + instance = each.value.instance + ip_address = each.value.ip_address + port = each.value.port + zone = each.value.zone +} + diff --git a/modules/net-ilb-l7/outputs.tf b/modules/net-ilb-l7/outputs.tf index 3ffca2434..e940fb48e 100644 --- a/modules/net-ilb-l7/outputs.tf +++ b/modules/net-ilb-l7/outputs.tf @@ -14,45 +14,40 @@ * limitations under the License. */ -output "health_checks" { - description = "Health-check resources." - value = try(google_compute_region_health_check.health_check, []) +output "address" { + description = "Forwarding rule address." + value = google_compute_forwarding_rule.default.ip_address } -output "backend_services" { +output "backend_service_ids" { description = "Backend service resources." value = { - group = try(google_compute_region_backend_service.backend_service, []) + for k, v in google_compute_region_backend_service.default : k => v.id } } -output "url_map" { - description = "The url-map." - value = try(google_compute_region_url_map.url_map, null) -} +# output "forwarding_rule" { +# description = "Forwarding rule resource" +# value = google_compute_forwarding_rule.default +# } -output "ssl_certificate_link_ids" { - description = "The SSL certificate." +output "group_ids" { + description = "Autogenerated instance group ids." value = { - for k, v in google_compute_region_ssl_certificate.certificates : k => v.self_link + for k, v in google_compute_instance_group.default : k => v.id } } -output "ip_address" { - description = "The reserved IP address." - value = try(google_compute_forwarding_rule.forwarding_rule.ip_address, null) +output "health_check_ids" { + description = "Autogenerated health check ids." + value = { + for k, v in google_compute_health_check.default : k => v.id + } } -output "target_proxy" { - description = "The target proxy." - value = try( - google_compute_region_target_https_proxy.https.0, - google_compute_region_target_http_proxy.http.0, - [] - ) -} - -output "forwarding_rule" { - description = "The forwarding rule." - value = try(google_compute_forwarding_rule.forwarding_rule, null) +output "neg_ids" { + description = "Autogenerated network endpoint group ids." + value = { + for k, v in google_compute_network_endpoint_group.default : k => v.id + } } diff --git a/modules/net-ilb-l7/ssl-certificates.tf b/modules/net-ilb-l7/ssl-certificates.tf deleted file mode 100644 index 46fafa3d1..000000000 --- a/modules/net-ilb-l7/ssl-certificates.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. - */ - -# tfdoc:file:description SSL certificates. - -resource "google_compute_region_ssl_certificate" "certificates" { - for_each = var.ssl_certificates_config - project = var.project_id - name = "${var.name}-${each.key}" - certificate = try(each.value.tls_self_signed_cert, null) - private_key = try(each.value.tls_private_key, null) - region = var.region -} diff --git a/modules/net-ilb-l7/target-proxy.tf b/modules/net-ilb-l7/target-proxy.tf deleted file mode 100644 index b43a64179..000000000 --- a/modules/net-ilb-l7/target-proxy.tf +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -# tfdoc:file:description HTTP and HTTPS target proxies. - -locals { - # Look for the cert in the the ssl_certificates_config map. - # If not found, use the SSL certificate id as is (already existing). - ssl_certificates = [ - for cert in try(var.target_proxy_https_config.ssl_certificates, []) : - try( - google_compute_region_ssl_certificate.certificates[cert].self_link, - cert - ) - ] -} - -resource "google_compute_region_target_http_proxy" "http" { - count = var.https ? 0 : 1 - name = var.name - project = var.project_id - description = "Terraform managed." - region = var.region - url_map = google_compute_region_url_map.url_map.id -} - -resource "google_compute_region_target_https_proxy" "https" { - count = var.https ? 1 : 0 - name = var.name - project = var.project_id - description = "Terraform managed." - region = var.region - url_map = google_compute_region_url_map.url_map.id - ssl_certificates = local.ssl_certificates -} diff --git a/modules/net-ilb-l7/url-map.tf b/modules/net-ilb-l7/url-map.tf deleted file mode 100644 index 1d98b4978..000000000 --- a/modules/net-ilb-l7/url-map.tf +++ /dev/null @@ -1,708 +0,0 @@ -/** - * Copyright 2022 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -# tfdoc:file:description URL maps. - -locals { - # Look for a backend service 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_region_backend_service.backend_service[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_region_backend_service.backend_service[keys(google_compute_region_backend_service.backend_service)[0]].id, - null - ) - : null - ) -} - -resource "google_compute_region_url_map" "url_map" { - name = var.name - description = "Terraform managed." - project = var.project_id - region = var.region - default_service = local.default_service - - - 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_region_backend_service.backend_service[var.url_map_config.default_service].id, - path_matcher.value.default_service, - 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_region_backend_service.backend_service[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_region_backend_service.backend_service[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_region_backend_service.backend_service[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_region_backend_service.backend_service[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_region_backend_service.backend_service[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_region_backend_service.backend_service[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) - } - } - } - } - - # 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_region_backend_service.backend_service[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) - } - } -} diff --git a/modules/net-ilb-l7/urlmap.tf b/modules/net-ilb-l7/urlmap.tf new file mode 100644 index 000000000..21764ce09 --- /dev/null +++ b/modules/net-ilb-l7/urlmap.tf @@ -0,0 +1,576 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# tfdoc:file:description URL map resources. + +locals { + backend_ids = { + for k, v in google_compute_region_backend_service.default : k => v.id + } +} + +resource "google_compute_region_url_map" "default" { + provider = google-beta + project = var.project_id + region = var.region + name = var.name + description = var.description + default_service = ( + var.urlmap_config.default_service == null ? null : lookup( + local.backend_ids, + var.urlmap_config.default_service, + var.urlmap_config.default_service + ) + ) + + dynamic "default_url_redirect" { + for_each = ( + var.urlmap_config.default_url_redirect == null + ? [] + : [var.urlmap_config.default_url_redirect] + ) + iterator = r + content { + host_redirect = r.value.host + https_redirect = r.value.https + path_redirect = r.value.path + prefix_redirect = r.value.prefix + redirect_response_code = r.value.response_code + strip_query = r.value.strip_query + } + } + + dynamic "host_rule" { + for_each = coalesce(var.urlmap_config.host_rules, []) + iterator = r + content { + hosts = r.value.hosts + path_matcher = r.value.path_matcher + description = r.value.description + } + } + + dynamic "path_matcher" { + for_each = coalesce(var.urlmap_config.path_matchers, {}) + iterator = m + content { + default_service = m.value.default_service == null ? null : lookup( + local.backend_ids, m.value.default_service, m.value.default_service + ) + description = m.value.description + name = m.key + dynamic "default_url_redirect" { + for_each = ( + m.value.default_url_redirect == null + ? [] + : [m.value.default_url_redirect] + ) + content { + host_redirect = default_url_redirect.value.host + https_redirect = default_url_redirect.value.https + path_redirect = default_url_redirect.value.path + prefix_redirect = default_url_redirect.value.prefix + redirect_response_code = default_url_redirect.value.response_code + strip_query = default_url_redirect.value.strip_query + } + } + dynamic "path_rule" { + for_each = toset(coalesce(m.value.path_rules, [])) + content { + paths = path_rule.value.paths + service = path_rule.value.service == null ? null : lookup( + local.backend_ids, + path_rule.value.service, + path_rule.value.service + ) + dynamic "route_action" { + for_each = ( + path_rule.value.route_action == null + ? [] + : [path_rule.value.route_action] + ) + content { + dynamic "cors_policy" { + for_each = ( + route_action.value.cors_policy == null + ? [] + : [route_action.value.cors_policy] + ) + content { + allow_credentials = cors_policy.value.allow_credentials + allow_headers = cors_policy.value.allow_headers + allow_methods = cors_policy.value.allow_methods + allow_origin_regexes = cors_policy.value.allow_origin_regexes + allow_origins = cors_policy.value.allow_origins + disabled = cors_policy.value.disabled + expose_headers = cors_policy.value.expose_headers + max_age = cors_policy.value.max_age + } + } + dynamic "fault_injection_policy" { + for_each = ( + route_action.value.fault_injection_policy == null + ? [] + : [route_action.value.fault_injection_policy] + ) + content { + dynamic "abort" { + for_each = ( + fault_injection_policy.value.abort == null + ? [] + : [fault_injection_policy.value.abort] + ) + content { + http_status = abort.value.status + percentage = abort.value.percentage + } + } + dynamic "delay" { + for_each = ( + fault_injection_policy.value.delay == null + ? [] + : [fault_injection_policy.value.delay] + ) + content { + percentage = delay.value.percentage + fixed_delay { + nanos = delay.value.fixed.nanos + seconds = delay.value.fixed.seconds + } + } + } + } + } + dynamic "request_mirror_policy" { + for_each = ( + route_action.value.request_mirror_backend == null + ? [] + : [""] + ) + content { + backend_service = lookup( + local.backend_ids, + route_action.value.request_mirror_backend, + route_action.value.request_mirror_backend + ) + } + } + dynamic "retry_policy" { + for_each = ( + route_action.value.retry_policy == null + ? [] + : [route_action.value.retry_policy] + ) + content { + num_retries = retry_policy.value.num_retries + retry_conditions = retry_policy.value.retry_conditions + dynamic "per_try_timeout" { + for_each = ( + retry_policy.value.per_try_timeout == null + ? [] + : [retry_policy.value.per_try_timeout] + ) + content { + nanos = per_try_timeout.value.nanos + seconds = per_try_timeout.value.seconds + } + } + } + } + dynamic "timeout" { + for_each = ( + route_action.value.timeout == null + ? [] + : [route_action.value.timeout] + ) + content { + nanos = timeout.value.nanos + seconds = timeout.value.seconds + } + } + dynamic "url_rewrite" { + for_each = ( + route_action.value.url_rewrite == null + ? [] + : [route_action.value.url_rewrite] + ) + content { + host_rewrite = url_rewrite.value.host + path_prefix_rewrite = url_rewrite.value.path_prefix + } + } + dynamic "weighted_backend_services" { + for_each = coalesce( + route_action.value.weighted_backend_services, {} + ) + iterator = service + content { + backend_service = lookup( + local.backend_ids, service.key, service.key + ) + weight = service.value.weight + dynamic "header_action" { + for_each = ( + service.value.header_action == null + ? [] + : [service.value.header_action] + ) + iterator = h + content { + request_headers_to_remove = h.value.request_remove + response_headers_to_remove = h.value.response_remove + dynamic "request_headers_to_add" { + for_each = coalesce(h.value.request_add, {}) + content { + header_name = request_headers_to_add.key + header_value = request_headers_to_add.value.value + replace = request_headers_to_add.value.replace + } + } + dynamic "response_headers_to_add" { + for_each = coalesce(h.value.response_add, {}) + content { + header_name = response_headers_to_add.key + header_value = response_headers_to_add.value.value + replace = response_headers_to_add.value.replace + } + } + } + } + } + } + } + } + dynamic "url_redirect" { + for_each = ( + path_rule.value.url_redirect == null + ? [] + : [path_rule.value.url_redirect] + ) + content { + host_redirect = url_redirect.value.host + https_redirect = url_redirect.value.https + path_redirect = url_redirect.value.path + prefix_redirect = url_redirect.value.prefix + redirect_response_code = url_redirect.value.response_code + strip_query = url_redirect.value.strip_query + } + } + } + } + dynamic "route_rules" { + for_each = toset(coalesce(m.value.route_rules, [])) + content { + priority = route_rules.value.priority + service = route_rules.value.service == null ? null : lookup( + local.backend_ids, + route_rules.value.service, + route_rules.value.service + ) + dynamic "header_action" { + for_each = ( + route_rules.value.header_action == null + ? [] + : [route_rules.value.header_action] + ) + iterator = h + content { + request_headers_to_remove = h.value.request_remove + response_headers_to_remove = h.value.response_remove + dynamic "request_headers_to_add" { + for_each = coalesce(h.value.request_add, {}) + content { + header_name = request_headers_to_add.key + header_value = request_headers_to_add.value.value + replace = request_headers_to_add.value.replace + } + } + dynamic "response_headers_to_add" { + for_each = coalesce(h.value.response_add, {}) + content { + header_name = response_headers_to_add.key + header_value = response_headers_to_add.value.value + replace = response_headers_to_add.value.replace + } + } + } + } + dynamic "match_rules" { + for_each = toset(coalesce(route_rules.value.match_rules, [])) + content { + ignore_case = match_rules.value.ignore_case + full_path_match = ( + try(match_rules.value.path.type, null) == "full" + ? match_rules.value.path.value + : null + ) + prefix_match = ( + try(match_rules.value.path.type, null) == "prefix" + ? match_rules.value.path.value + : null + ) + regex_match = ( + try(match_rules.value.path.type, null) == "regex" + ? match_rules.value.path.value + : null + ) + dynamic "header_matches" { + for_each = toset(coalesce(match_rules.value.headers, [])) + iterator = h + content { + header_name = h.value.name + exact_match = h.value.type == "exact" ? h.value.value : null + invert_match = h.value.invert_match + prefix_match = h.value.type == "prefix" ? h.value.value : null + present_match = h.value.type == "present" ? h.value.value : null + regex_match = h.value.type == "regex" ? h.value.value : null + suffix_match = h.value.type == "suffix" ? h.value.value : null + dynamic "range_match" { + for_each = ( + h.value.type != "range" || h.value.range_value == null + ? [] + : [""] + ) + content { + range_end = h.value.range_value.end + range_start = h.value.range_value.start + } + } + } + } + dynamic "metadata_filters" { + for_each = toset(coalesce(match_rules.value.metadata_filters, [])) + iterator = m + content { + filter_match_criteria = ( + m.value.match_all ? "MATCH_ALL" : "MATCH_ANY" + ) + dynamic "filter_labels" { + for_each = m.value.labels + content { + name = filter_labels.key + value = filter_labels.value + } + } + } + } + dynamic "query_parameter_matches" { + for_each = toset(coalesce(match_rules.value.query_params, [])) + iterator = q + content { + name = q.value.name + exact_match = ( + q.value.type == "exact" ? q.value.value : null + ) + present_match = ( + q.value.type == "present" ? q.value.value : null + ) + regex_match = ( + q.value.type == "regex" ? q.value.value : null + ) + } + } + } + } + dynamic "route_action" { + for_each = ( + route_rules.value.route_action == null + ? [] + : [route_rules.value.route_action] + ) + content { + dynamic "cors_policy" { + for_each = ( + route_action.value.cors_policy == null + ? [] + : [route_action.value.cors_policy] + ) + content { + allow_credentials = cors_policy.value.allow_credentials + allow_headers = cors_policy.value.allow_headers + allow_methods = cors_policy.value.allow_methods + allow_origin_regexes = cors_policy.value.allow_origin_regexes + allow_origins = cors_policy.value.allow_origins + disabled = cors_policy.value.disabled + expose_headers = cors_policy.value.expose_headers + max_age = cors_policy.value.max_age + } + } + dynamic "fault_injection_policy" { + for_each = ( + route_action.value.fault_injection_policy == null + ? [] + : [route_action.value.fault_injection_policy] + ) + content { + dynamic "abort" { + for_each = ( + fault_injection_policy.value.abort == null + ? [] + : [fault_injection_policy.value.abort] + ) + content { + http_status = abort.value.status + percentage = abort.value.percentage + } + } + dynamic "delay" { + for_each = ( + fault_injection_policy.value.delay == null + ? [] + : [fault_injection_policy.value.delay] + ) + content { + percentage = delay.value.percentage + fixed_delay { + nanos = delay.value.fixed.nanos + seconds = delay.value.fixed.seconds + } + } + } + } + } + dynamic "request_mirror_policy" { + for_each = ( + route_action.value.request_mirror_backend == null + ? [] + : [""] + ) + content { + backend_service = lookup( + local.backend_ids, + route_action.value.request_mirror_backend, + route_action.value.request_mirror_backend + ) + } + } + dynamic "retry_policy" { + for_each = ( + route_action.value.retry_policy == null + ? [] + : [route_action.value.retry_policy] + ) + content { + num_retries = retry_policy.value.num_retries + retry_conditions = retry_policy.value.retry_conditions + dynamic "per_try_timeout" { + for_each = ( + retry_policy.value.per_try_timeout == null + ? [] + : [retry_policy.value.per_try_timeout] + ) + content { + nanos = per_try_timeout.value.nanos + seconds = per_try_timeout.value.seconds + } + } + } + } + dynamic "timeout" { + for_each = ( + route_action.value.timeout == null + ? [] + : [route_action.value.timeout] + ) + content { + nanos = timeout.value.nanos + seconds = timeout.value.seconds + } + } + dynamic "url_rewrite" { + for_each = ( + route_action.value.url_rewrite == null + ? [] + : [route_action.value.url_rewrite] + ) + content { + host_rewrite = url_rewrite.value.host + path_prefix_rewrite = url_rewrite.value.path_prefix + } + } + dynamic "weighted_backend_services" { + for_each = coalesce( + route_action.value.weighted_backend_services, {} + ) + iterator = service + content { + backend_service = lookup( + local.backend_ids, service.key, service.key + ) + weight = service.value.weight + dynamic "header_action" { + for_each = ( + service.value.header_action == null + ? [] + : [service.value.header_action] + ) + iterator = h + content { + request_headers_to_remove = h.value.request_remove + response_headers_to_remove = h.value.response_remove + dynamic "request_headers_to_add" { + for_each = coalesce(h.value.request_add, {}) + content { + header_name = request_headers_to_add.key + header_value = request_headers_to_add.value.value + replace = request_headers_to_add.value.replace + } + } + dynamic "response_headers_to_add" { + for_each = coalesce(h.value.response_add, {}) + content { + header_name = response_headers_to_add.key + header_value = response_headers_to_add.value.value + replace = response_headers_to_add.value.replace + } + } + } + } + } + } + } + } + dynamic "url_redirect" { + for_each = ( + route_rules.value.default_url_redirect == null + ? [] + : [route_rules.value.default_url_redirect] + ) + content { + host_redirect = url_redirect.value.host + https_redirect = url_redirect.value.https + path_redirect = url_redirect.value.path + prefix_redirect = url_redirect.value.prefix + redirect_response_code = url_redirect.value.response_code + strip_query = url_redirect.value.strip_query + } + } + } + } + } + } + + dynamic "test" { + for_each = toset(coalesce(var.urlmap_config.test, [])) + content { + host = test.value.host + path = test.value.path + service = test.value.service + description = test.value.description + } + } + +} diff --git a/modules/net-ilb-l7/variables-backend-service.tf b/modules/net-ilb-l7/variables-backend-service.tf new file mode 100644 index 000000000..30eee51e7 --- /dev/null +++ b/modules/net-ilb-l7/variables-backend-service.tf @@ -0,0 +1,130 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# tfdoc:file:description Backend services variables. + +variable "backend_service_configs" { + description = "Backend service level configuration." + type = map(object({ + affinity_cookie_ttl_sec = optional(number) + connection_draining_timeout_sec = optional(number) + health_checks = optional(list(string), ["default"]) + locality_lb_policy = optional(string) + log_sample_rate = optional(number) + port_name = optional(string) + protocol = optional(string) + session_affinity = optional(string) + timeout_sec = optional(number) + backends = list(object({ + group = string + balancing_mode = optional(string, "UTILIZATION") + capacity_scaler = optional(number, 1) + description = optional(string, "Terraform managed.") + failover = optional(bool, false) + max_connections = optional(object({ + per_endpoint = optional(number) + per_group = optional(number) + per_instance = optional(number) + })) + max_rate = optional(object({ + per_endpoint = optional(number) + per_group = optional(number) + per_instance = optional(number) + })) + max_utilization = optional(number) + })) + circuit_breakers = optional(object({ + max_connections = optional(number) + max_pending_requests = optional(number) + max_requests = optional(number) + max_requests_per_connection = optional(number) + max_retries = optional(number) + connect_timeout = optional(object({ + seconds = number + nanos = optional(number) + })) + })) + connection_tracking = optional(object({ + idle_timeout_sec = optional(number) + persist_conn_on_unhealthy = optional(string) + track_per_session = optional(bool) + })) + consistent_hash = optional(object({ + http_header_name = optional(string) + minimum_ring_size = optional(number) + http_cookie = optional(object({ + name = optional(string) + path = optional(string) + ttl = optional(object({ + seconds = number + nanos = optional(number) + })) + })) + })) + enable_subsetting = optional(bool) + failover_config = optional(object({ + disable_conn_drain = optional(bool) + drop_traffic_if_unhealthy = optional(bool) + ratio = optional(number) + })) + iap_config = optional(object({ + oauth2_client_id = string + oauth2_client_secret = string + oauth2_client_secret_sha256 = optional(string) + })) + outlier_detection = optional(object({ + consecutive_errors = optional(number) + consecutive_gateway_failure = optional(number) + enforcing_consecutive_errors = optional(number) + enforcing_consecutive_gateway_failure = optional(number) + enforcing_success_rate = optional(number) + max_ejection_percent = optional(number) + success_rate_minimum_hosts = optional(number) + success_rate_request_volume = optional(number) + success_rate_stdev_factor = optional(number) + base_ejection_time = optional(object({ + seconds = number + nanos = optional(number) + })) + interval = optional(object({ + seconds = number + nanos = optional(number) + })) + })) + })) + default = {} + nullable = false + validation { + condition = contains( + [ + "-", "ROUND_ROBIN", "LEAST_REQUEST", "RING_HASH", + "RANDOM", "ORIGINAL_DESTINATION", "MAGLEV" + ], + try(var.backend_service_configs.locality_lb_policy, "-") + ) + error_message = "Invalid locality lb policy value." + } + validation { + condition = contains( + [ + "NONE", "CLIENT_IP", "CLIENT_IP_NO_DESTINATION", + "CLIENT_IP_PORT_PROTO", "CLIENT_IP_PROTO" + ], + try(var.backend_service_configs.session_affinity, "NONE") + ) + error_message = "Invalid session affinity value." + } +} diff --git a/modules/net-ilb-l7/variables-health-check.tf b/modules/net-ilb-l7/variables-health-check.tf new file mode 100644 index 000000000..9b2066a5a --- /dev/null +++ b/modules/net-ilb-l7/variables-health-check.tf @@ -0,0 +1,95 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# tfdoc:file:description Health check variable. + +variable "health_check_configs" { + description = "Optional auto-created health check configurations, use the output self-link to set it in the auto healing policy. Refer to examples for usage." + type = map(object({ + check_interval_sec = optional(number) + description = optional(string, "Terraform managed.") + enable_logging = optional(bool, false) + healthy_threshold = optional(number) + timeout_sec = optional(number) + unhealthy_threshold = optional(number) + grpc = optional(object({ + port = optional(number) + port_name = optional(string) + port_specification = optional(string) # USE_FIXED_PORT USE_NAMED_PORT USE_SERVING_PORT + service_name = optional(string) + })) + http = optional(object({ + host = optional(string) + port = optional(number) + port_name = optional(string) + port_specification = optional(string) # USE_FIXED_PORT USE_NAMED_PORT USE_SERVING_PORT + proxy_header = optional(string) + request_path = optional(string) + response = optional(string) + })) + http2 = optional(object({ + host = optional(string) + port = optional(number) + port_name = optional(string) + port_specification = optional(string) # USE_FIXED_PORT USE_NAMED_PORT USE_SERVING_PORT + proxy_header = optional(string) + request_path = optional(string) + response = optional(string) + })) + https = optional(object({ + host = optional(string) + port = optional(number) + port_name = optional(string) + port_specification = optional(string) # USE_FIXED_PORT USE_NAMED_PORT USE_SERVING_PORT + proxy_header = optional(string) + request_path = optional(string) + response = optional(string) + })) + tcp = optional(object({ + port = optional(number) + port_name = optional(string) + port_specification = optional(string) # USE_FIXED_PORT USE_NAMED_PORT USE_SERVING_PORT + proxy_header = optional(string) + request = optional(string) + response = optional(string) + })) + ssl = optional(object({ + port = optional(number) + port_name = optional(string) + port_specification = optional(string) # USE_FIXED_PORT USE_NAMED_PORT USE_SERVING_PORT + proxy_header = optional(string) + request = optional(string) + response = optional(string) + })) + })) + default = { + default = { + http = { + port_specification = "USE_SERVING_PORT" + } + } + } + validation { + condition = alltrue([ + for k, v in var.health_check_configs : ( + (try(v.grpc, null) == null ? 0 : 1) + + (try(v.http, null) == null ? 0 : 1) + + (try(v.tcp, null) == null ? 0 : 1) <= 1 + ) + ]) + error_message = "Only one health check type can be configured at a time." + } +} diff --git a/modules/net-ilb-l7/variables-urlmap.tf b/modules/net-ilb-l7/variables-urlmap.tf new file mode 100644 index 000000000..cd1869f5a --- /dev/null +++ b/modules/net-ilb-l7/variables-urlmap.tf @@ -0,0 +1,234 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +# tfdoc:file:description URLmap variable. + +variable "urlmap_config" { + description = "The URL map configuration." + type = object({ + default_service = optional(string) + default_url_redirect = optional(object({ + host = optional(string) + https = optional(bool) + path = optional(string) + prefix = optional(string) + response_code = optional(string) + strip_query = optional(bool) + })) + host_rules = optional(list(object({ + hosts = list(string) + path_matcher = string + description = optional(string) + }))) + path_matchers = optional(map(object({ + description = optional(string) + default_service = optional(string) + default_url_redirect = optional(object({ + host = optional(string) + https = optional(bool) + path = optional(string) + prefix = optional(string) + response_code = optional(string) + strip_query = optional(bool) + })) + path_rules = optional(list(object({ + paths = list(string) + service = optional(string) + route_action = optional(object({ + request_mirror_backend = optional(string) + cors_policy = optional(object({ + allow_credentials = optional(bool) + allow_headers = optional(string) + allow_methods = optional(string) + allow_origin_regexes = list(string) + allow_origins = list(string) + disabled = optional(bool) + expose_headers = optional(string) + max_age = optional(string) + })) + fault_injection_policy = optional(object({ + abort = optional(object({ + percentage = number + status = number + })) + delay = optional(object({ + fixed = object({ + seconds = number + nanos = number + }) + percentage = number + })) + })) + retry_policy = optional(object({ + num_retries = number + retry_conditions = optional(list(string)) + per_try_timeout = optional(object({ + seconds = number + nanos = optional(number) + })) + })) + timeout = optional(object({ + seconds = number + nanos = optional(number) + })) + url_rewrite = optional(object({ + host = optional(string) + path_prefix = optional(string) + })) + weighted_backend_services = optional(map(object({ + weight = number + header_action = optional(object({ + request_add = optional(map(object({ + value = string + replace = optional(bool, true) + }))) + request_remove = optional(list(string)) + response_add = optional(map(object({ + value = string + replace = optional(bool, true) + }))) + response_remove = optional(list(string)) + })) + }))) + })) + url_redirect = optional(object({ + host = optional(string) + https = optional(bool) + path = optional(string) + prefix = optional(string) + response_code = optional(string) + strip_query = optional(bool) + })) + }))) + route_rules = optional(list(object({ + priority = number + service = optional(string) + header_action = optional(object({ + request_add = optional(map(object({ + value = string + replace = optional(bool, true) + }))) + request_remove = optional(list(string)) + response_add = optional(map(object({ + value = string + replace = optional(bool, true) + }))) + response_remove = optional(list(string)) + })) + match_rules = optional(list(object({ + ignore_case = optional(bool, false) + headers = optional(list(object({ + name = string + invert_match = optional(bool, false) + type = optional(string, "present") # exact, prefix, suffix, regex, present, range + value = optional(string) + range_value = optional(object({ + end = string + start = string + })) + }))) + metadata_filters = optional(list(object({ + labels = map(string) + match_all = bool # MATCH_ANY, MATCH_ALL + }))) + path = optional(object({ + value = string + type = optional(string, "prefix") # full, prefix, regex + })) + query_params = optional(list(object({ + name = string + value = string + type = optional(string, "present") # exact, present, regex + }))) + }))) + route_action = optional(object({ + request_mirror_backend = optional(string) + cors_policy = optional(object({ + allow_credentials = optional(bool) + allow_headers = optional(string) + allow_methods = optional(string) + allow_origin_regexes = list(string) + allow_origins = list(string) + disabled = optional(bool) + expose_headers = optional(string) + max_age = optional(string) + })) + fault_injection_policy = optional(object({ + abort = optional(object({ + percentage = number + status = number + })) + delay = optional(object({ + fixed = object({ + seconds = number + nanos = number + }) + percentage = number + })) + })) + retry_policy = optional(object({ + num_retries = number + retry_conditions = optional(list(string)) + per_try_timeout = optional(object({ + seconds = number + nanos = optional(number) + })) + })) + timeout = optional(object({ + seconds = number + nanos = optional(number) + })) + url_rewrite = optional(object({ + host = optional(string) + path_prefix = optional(string) + })) + weighted_backend_services = optional(map(object({ + weight = number + header_action = optional(object({ + request_add = optional(map(object({ + value = string + replace = optional(bool, true) + }))) + request_remove = optional(list(string)) + response_add = optional(map(object({ + value = string + replace = optional(bool, true) + }))) + response_remove = optional(list(string)) + })) + }))) + })) + url_redirect = optional(object({ + host = optional(string) + https = optional(bool) + path = optional(string) + prefix = optional(string) + response_code = optional(string) + strip_query = optional(bool) + })) + }))) + }))) + test = optional(list(object({ + host = string + path = string + service = string + description = optional(string) + }))) + }) + default = { + default_service = "default" + } +} diff --git a/modules/net-ilb-l7/variables.tf b/modules/net-ilb-l7/variables.tf index 7202e3fae..536fa7d08 100644 --- a/modules/net-ilb-l7/variables.tf +++ b/modules/net-ilb-l7/variables.tf @@ -14,146 +14,90 @@ * limitations under the License. */ +variable "address" { + description = "Optional IP address used for the forwarding rule." + type = string + default = null +} + +variable "description" { + description = "Optional description used for resources." + type = string + default = "Terraform managed." +} + +variable "group_configs" { + description = "Optional unmanaged groups to create. Can be referenced in backends via key or outputs." + type = map(object({ + zone = string + instances = optional(list(string), []) + named_ports = optional(map(number), {}) + })) + default = {} + nullable = false +} + +variable "labels" { + description = "Labels set on resources." + type = map(string) + default = {} +} + variable "name" { description = "Load balancer name." type = string } +variable "neg_configs" { + description = "Optional network endpoint groups to create. Can be referenced in backends via key or outputs." + type = map(object({ + zone = string + # re-enable once provider properly support this + # default_port = optional(number) + is_hybrid = optional(bool, false) + endpoints = optional(list(object({ + ip_address = string + instance = optional(string) + port = optional(number) + }))) + vpc_config = optional(object({ + network = optional(string) + subnetwork = optional(string) + })) + })) + default = {} + nullable = false +} + +variable "network_tier_premium" { + description = "Use premium network tier. Defaults to true." + type = bool + default = true + nullable = false +} + variable "project_id" { description = "Project id." type = string } -variable "backend_services_config" { - description = "The backends services configuration." - type = map(object({ - backends = list(object({ - group = string # The instance group link id - options = object({ - balancing_mode = string # Can be UTILIZATION, RATE - 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 - locality_lb_policy = string - port_name = string - protocol = 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 "ports" { + description = "Optional ports for HTTP load balancer, valid ports are 80 and 8080." + type = list(string) + default = null } -variable "forwarding_rule_config" { - description = "Forwarding rule configurations." - type = object({ - ip_version = string - labels = map(string) - network_tier = string - port_range = string - service_label = string - }) - default = { - allow_global_access = true - ip_version = "IPV4" - labels = {} - network_tier = "PREMIUM" - # If not specified, 443 if var.https = true; 80 otherwise - port_range = null - service_label = null - } -} - -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 "health_checks_config_defaults" { - description = "Auto-created health check default configuration." - type = object({ - check = map(any) # actual health check block attributes - logging = bool - options = map(number) # interval, thresholds, timeout - type = string # http https tcp ssl http2 - }) - default = { - type = "http" - logging = false - options = {} - check = { - port_specification = "USE_SERVING_PORT" - } - } -} - -variable "https" { - description = "Whether to enable HTTPS." - type = bool - default = false -} - -variable "network" { - description = "The network where the ILB is created." +variable "protocol" { + description = "Protocol supported by this load balancer." type = string - default = "default" + default = "HTTP" + nullable = false + validation { + condition = ( + var.protocol == null || var.protocol == "HTTP" || var.protocol == "HTTPS" + ) + error_message = "Protocol must be HTTP or HTTPS" + } } variable "region" { @@ -161,52 +105,24 @@ variable "region" { type = string } -variable "ssl_certificates_config" { - description = "The SSL certificates configuration." - type = map(object({ - domains = list(string) - tls_private_key = string - tls_self_signed_cert = string - })) - default = {} -} - -variable "static_ip_config" { - description = "Static IP address configuration." +variable "ssl_certificates" { + description = "SSL target proxy certificates (only if protocol is HTTPS)." type = object({ - reserve = bool - options = object({ - address = string - subnetwork = string # The subnet id - }) + certificate_ids = optional(list(string), []) + create_configs = optional(map(object({ + certificate = string + private_key = string + })), {}) }) - default = { - reserve = false - options = null - } + default = {} + nullable = false } -variable "subnetwork" { - description = "The subnetwork where the ILB VIP is allocated." - type = string -} - -variable "target_proxy_https_config" { - description = "The HTTPS target proxy configuration." +variable "vpc_config" { + description = "VPC-level configuration." type = object({ - ssl_certificates = list(string) + network = string + subnetwork = string }) - default = null -} - -variable "url_map_config" { - description = "The url-map configuration." - type = object({ - default_service = string - default_url_redirect = map(any) - host_rules = list(any) - path_matchers = list(any) - tests = list(map(string)) - }) - default = null + nullable = false } diff --git a/tests/modules/net_ilb_l7/fixture/main.tf b/tests/modules/net_ilb_l7/fixture/main.tf index 10d747c9d..a9981458a 100644 --- a/tests/modules/net_ilb_l7/fixture/main.tf +++ b/tests/modules/net_ilb_l7/fixture/main.tf @@ -15,19 +15,24 @@ */ module "test" { - source = "../../../../modules/net-ilb-l7" - project_id = "my-project" - name = "ilb-l7-test" - region = "europe-west1" - network = "projects/my-project/global/networks/default" - subnetwork = "projects/my-project/regions/europe-west1/subnetworks/default" - backend_services_config = var.backend_services_config - forwarding_rule_config = var.forwarding_rule_config - health_checks_config = var.health_checks_config - health_checks_config_defaults = var.health_checks_config_defaults - https = var.https - ssl_certificates_config = var.ssl_certificates_config - static_ip_config = var.static_ip_config - target_proxy_https_config = var.target_proxy_https_config - url_map_config = var.url_map_config + source = "../../../../modules/net-ilb-l7" + project_id = "my-project" + name = "ilb-l7-test" + region = "europe-west1" + vpc_config = { + network = "projects/my-project/global/networks/default" + subnetwork = "projects/my-project/regions/europe-west1/subnetworks/default" + } + address = var.address + backend_service_configs = var.backend_service_configs + description = var.description + group_configs = var.group_configs + health_check_configs = var.health_check_configs + labels = var.labels + neg_configs = var.neg_configs + network_tier_premium = var.network_tier_premium + ports = var.ports + protocol = var.protocol + ssl_certificates = var.ssl_certificates + urlmap_config = var.urlmap_config } diff --git a/tests/modules/net_ilb_l7/fixture/test.defaults.tfvars b/tests/modules/net_ilb_l7/fixture/test.defaults.tfvars new file mode 100644 index 000000000..f36b1062d --- /dev/null +++ b/tests/modules/net_ilb_l7/fixture/test.defaults.tfvars @@ -0,0 +1,7 @@ +backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] + } +} diff --git a/tests/modules/net_ilb_l7/fixture/test.groups.tfvars b/tests/modules/net_ilb_l7/fixture/test.groups.tfvars new file mode 100644 index 000000000..36e25ea55 --- /dev/null +++ b/tests/modules/net_ilb_l7/fixture/test.groups.tfvars @@ -0,0 +1,16 @@ +backend_service_configs = { + default = { + backends = [{ + group = "custom" + }] + } +} +group_configs = { + custom = { + zone = "europe-west1-b" + instances = [ + "projects/myprj/zones/europe-west1-b/instances/vm-a" + ] + named_ports = { http = 80 } + } +} diff --git a/tests/modules/net_ilb_l7/fixture/test.health-checks-custom.tfvars b/tests/modules/net_ilb_l7/fixture/test.health-checks-custom.tfvars new file mode 100644 index 000000000..4e267cd23 --- /dev/null +++ b/tests/modules/net_ilb_l7/fixture/test.health-checks-custom.tfvars @@ -0,0 +1,16 @@ +backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] + health_checks = ["custom"] + } +} +health_check_configs = { + custom = { + tcp = { + port_specification = "USE_SERVING_PORT" + } + } +} + diff --git a/tests/modules/net_ilb_l7/fixture/test.health-checks-external.tfvars b/tests/modules/net_ilb_l7/fixture/test.health-checks-external.tfvars new file mode 100644 index 000000000..57fafa93a --- /dev/null +++ b/tests/modules/net_ilb_l7/fixture/test.health-checks-external.tfvars @@ -0,0 +1,10 @@ +backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] + health_checks = ["projects/myprj/global/healthChecks/custom"] + } +} +health_check_configs = {} + diff --git a/tests/modules/net_ilb_l7/fixture/test.https.tfvars b/tests/modules/net_ilb_l7/fixture/test.https.tfvars new file mode 100644 index 000000000..7a5f61ddf --- /dev/null +++ b/tests/modules/net_ilb_l7/fixture/test.https.tfvars @@ -0,0 +1,6 @@ +protocol = "HTTPS" +ssl_certificates = { + certificate_ids = [ + "projects/myprj/regions/europe-west1/sslCertificates/my-cert" + ] +} diff --git a/tests/modules/net_ilb_l7/fixture/test.negs.tfvars b/tests/modules/net_ilb_l7/fixture/test.negs.tfvars new file mode 100644 index 000000000..6ffa232a1 --- /dev/null +++ b/tests/modules/net_ilb_l7/fixture/test.negs.tfvars @@ -0,0 +1,17 @@ +backend_service_configs = { + default = { + backends = [{ + group = "custom" + }] + } +} +neg_configs = { + custom = { + zone = "europe-west1-b" + endpoints = [{ + ip_address = "10.0.0.10" + instance = "test-1" + port = 80 + }] + } +} diff --git a/tests/modules/net_ilb_l7/fixture/test.ssl.tfvars b/tests/modules/net_ilb_l7/fixture/test.ssl.tfvars new file mode 100644 index 000000000..d81d0e6d2 --- /dev/null +++ b/tests/modules/net_ilb_l7/fixture/test.ssl.tfvars @@ -0,0 +1,9 @@ +protocol = "HTTPS" +ssl_certificates = { + create_configs = { + default = { + certificate = "FOO" + private_key = "FOO" + } + } +} diff --git a/tests/modules/net_ilb_l7/fixture/test.urlmaps.tfvars b/tests/modules/net_ilb_l7/fixture/test.urlmaps.tfvars new file mode 100644 index 000000000..110f65e7c --- /dev/null +++ b/tests/modules/net_ilb_l7/fixture/test.urlmaps.tfvars @@ -0,0 +1,28 @@ +backend_service_configs = { + default = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig" + }] + } + video = { + backends = [{ + group = "projects/myprj/zones/europe-west1-a/instanceGroups/my-ig-2" + }] + } +} +urlmap_config = { + default_service = "default" + host_rules = [{ + hosts = ["*"] + path_matcher = "pathmap" + }] + path_matchers = { + pathmap = { + default_service = "default" + path_rules = [{ + paths = ["/video", "/video/*"] + service = "video" + }] + } + } +} diff --git a/tests/modules/net_ilb_l7/fixture/variables.tf b/tests/modules/net_ilb_l7/fixture/variables.tf index d513a2a4c..4c8fff683 100644 --- a/tests/modules/net_ilb_l7/fixture/variables.tf +++ b/tests/modules/net_ilb_l7/fixture/variables.tf @@ -14,173 +14,71 @@ * limitations under the License. */ -variable "backend_services_config" { - description = "The backends services configuration." - type = map(object({ - backends = list(object({ - group = string # IG FQDN address - options = object({ - balancing_mode = string # Can be UTILIZATION, RATE - 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 - }) - })) +variable "address" { + description = "Optional IP address used for the forwarding rule." + type = string + default = null +} - # 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 - locality_lb_policy = string - port_name = string - protocol = 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 - }) - }) - })) +variable "backend_service_configs" { + type = any default = {} } -variable "forwarding_rule_config" { - description = "Forwarding rule configurations." - type = object({ - ip_version = string - labels = map(string) - network_tier = string - port_range = string - service_label = string - }) - default = { - allow_global_access = true - ip_version = "IPV4" - labels = {} - network_tier = "PREMIUM" - # If not specified, 443 if var.https = true; 80 otherwise - port_range = null - service_label = null - } +variable "description" { + type = string + default = "Terraform managed." } -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 - })) +variable "group_configs" { + type = any default = {} } -variable "health_checks_config_defaults" { - description = "Auto-created health check default configuration." - type = object({ - check = map(any) # actual health check block attributes - logging = bool - options = map(number) # interval, thresholds, timeout - type = string # http https tcp ssl http2 - }) +variable "health_check_configs" { + type = any default = { - type = "http" - logging = false - options = {} - check = { - port_specification = "USE_SERVING_PORT" + default = { + http = { + port_specification = "USE_SERVING_PORT" + } } } } -variable "https" { - description = "Whether to enable HTTPS." - type = bool - default = false -} - -variable "ssl_certificates_config" { - description = "The SSL certificate configuration." - type = map(object({ - domains = list(string) - tls_private_key = string - tls_self_signed_cert = string - })) +variable "labels" { + type = map(string) default = {} } -variable "static_ip_config" { - description = "Static IP address configuration." - type = object({ - reserve = bool - options = object({ - address = string - subnetwork = string # The subnet id - }) - }) +variable "neg_configs" { + type = any + default = {} +} + +variable "network_tier_premium" { + type = bool + default = true +} + +variable "ports" { + type = list(string) + default = null +} + +variable "protocol" { + type = string + default = "HTTP" +} + +variable "ssl_certificates" { + type = any + default = {} +} + +variable "urlmap_config" { + type = any default = { - reserve = false - options = null + default_service = "default" } } - -variable "target_proxy_https_config" { - description = "The HTTPS target proxy configuration." - type = object({ - ssl_certificates = list(string) - }) - default = null -} - -variable "url_map_config" { - description = "The url-map configuration." - type = object({ - default_service = string - default_url_redirect = map(any) - host_rules = list(any) - path_matchers = list(any) - tests = list(map(string)) - }) - default = null -} diff --git a/tests/modules/net_ilb_l7/test_plan.py b/tests/modules/net_ilb_l7/test_plan.py index 5e235a13a..91af3d2a2 100644 --- a/tests/modules/net_ilb_l7/test_plan.py +++ b/tests/modules/net_ilb_l7/test_plan.py @@ -12,173 +12,109 @@ # See the License for the specific language governing permissions and # limitations under the License. -_BACKEND_SVC_CONFIG = '''{ - my-group = { - backends = [ - { - group = "my_group", - options = null - } - ], - health_checks = [] - log_config = null - options = null +import collections + + +def test_defaults(plan_runner): + "Test with default values." + _, resources = plan_runner(tf_var_file='test.defaults.tfvars') + counts = collections.Counter(f'{r["type"]}.{r["name"]}' for r in resources) + assert counts == { + 'google_compute_forwarding_rule.default': 1, + 'google_compute_health_check.default': 1, + 'google_compute_region_backend_service.default': 1, + 'google_compute_region_target_http_proxy.default': 1, + 'google_compute_region_url_map.default': 1 } -}''' -_BACKEND_SVC_CONFIG_HC = '''{ - my-group = { - backends = [ - { - group = "my_group", - options = null - } - ], - health_checks = ["hc_1"] - log_config = null - options = null + +def test_groups(plan_runner): + "Test groups." + _, resources = plan_runner(tf_var_file='test.groups.tfvars') + counts = collections.Counter(f'{r["type"]}.{r["name"]}' for r in resources) + assert counts == { + 'google_compute_forwarding_rule.default': 1, + 'google_compute_health_check.default': 1, + 'google_compute_instance_group.default': 1, + 'google_compute_region_backend_service.default': 1, + 'google_compute_region_target_http_proxy.default': 1, + 'google_compute_region_url_map.default': 1 } -}''' -_NAME = 'ilb-l7-test' -_RESERVED_IP_CONFIG = '''{ - reserve = true - options = null -}''' - -_SSL_CERTIFICATES_CONFIG = '''{ - my-domain = { - domains = [ - "my-domain.com" - ], - tls_private_key = "my-key" - tls_self_signed_cert = "my-cert" +def test_health_checks_external(plan_runner): + "Test external health check." + _, resources = plan_runner(tf_var_file='test.health-checks-external.tfvars') + counts = collections.Counter(f'{r["type"]}.{r["name"]}' for r in resources) + assert counts == { + 'google_compute_forwarding_rule.default': 1, + 'google_compute_region_backend_service.default': 1, + 'google_compute_region_target_http_proxy.default': 1, + 'google_compute_region_url_map.default': 1 } -}''' - -_TARGET_PROXY_HTTPS_CONFIG = '''{ - ssl_certificates = [ - "my-domain" - ] -}''' -def test_group_default_hc(plan_runner): - "Tests a group backend service with no HC specified." - _, resources = plan_runner(backend_services_config=_BACKEND_SVC_CONFIG) - assert len(resources) == 5 - resources = dict((r['type'], r['values']) for r in resources) - - fwd_rule = resources['google_compute_forwarding_rule'] - assert fwd_rule['load_balancing_scheme'] == 'INTERNAL_MANAGED' - assert fwd_rule['port_range'] == '80' - assert fwd_rule['ip_protocol'] == 'TCP' - - group = resources['google_compute_region_backend_service'] - assert len(group['backend']) == 1 - assert group['backend'][0]['group'] == 'my_group' - - health_check = resources['google_compute_region_health_check'] - assert health_check['name'] == _NAME + '-default' - assert len(health_check['http_health_check']) > 0 - assert len(health_check['https_health_check']) == 0 - assert len(health_check['http2_health_check']) == 0 - assert len(health_check['tcp_health_check']) == 0 - assert health_check['http_health_check'][0]['port_specification'] == 'USE_SERVING_PORT' - assert health_check['http_health_check'][0]['proxy_header'] == 'NONE' - assert health_check['http_health_check'][0]['request_path'] == '/' - - assert 'google_compute_region_target_http_proxy' in resources - assert 'google_compute_region_target_https_proxy' not in resources - assert 'google_compute_region_url_map' in resources +def test_health_checks_custom(plan_runner): + "Test custom health check." + _, resources = plan_runner(tf_var_file='test.health-checks-custom.tfvars') + counts = collections.Counter(f'{r["type"]}.{r["name"]}' for r in resources) + assert counts == { + 'google_compute_forwarding_rule.default': 1, + 'google_compute_health_check.default': 1, + 'google_compute_region_backend_service.default': 1, + 'google_compute_region_target_http_proxy.default': 1, + 'google_compute_region_url_map.default': 1 + } -def test_group_no_hc(plan_runner): - "Tests a group backend service without HCs (including no default HC)." - _, resources = plan_runner(backend_services_config=_BACKEND_SVC_CONFIG, - health_checks_config_defaults='null') - - assert len(resources) == 4 - resources = dict((r['type'], r['values']) for r in resources) - - assert 'google_compute_region_backend_service' in resources - assert 'google_compute_region_health_check' not in resources - assert 'google_compute_region_target_http_proxy' in resources - assert 'google_compute_region_target_https_proxy' not in resources - assert 'google_compute_region_url_map' in resources - assert 'google_compute_forwarding_rule' in resources +def test_https(plan_runner): + "Test HTTPS load balancer." + _, resources = plan_runner(tf_var_file='test.https.tfvars') + counts = collections.Counter(f'{r["type"]}.{r["name"]}' for r in resources) + assert counts == { + 'google_compute_forwarding_rule.default': 1, + 'google_compute_health_check.default': 1, + 'google_compute_region_target_https_proxy.default': 1, + 'google_compute_region_url_map.default': 1 + } -def test_group_existing_hc(plan_runner): - "Tests a group backend service with referencing an existing HC." - _, resources = plan_runner(backend_services_config=_BACKEND_SVC_CONFIG_HC) - assert len(resources) == 4 - resources = dict((r['type'], r['values']) for r in resources) - - assert 'google_compute_region_backend_service' in resources - assert 'google_compute_region_health_check' not in resources - assert 'google_compute_region_target_http_proxy' in resources - assert 'google_compute_region_target_https_proxy' not in resources - assert 'google_compute_region_url_map' in resources - assert 'google_compute_forwarding_rule' in resources +def test_negs(plan_runner): + "Test negs." + _, resources = plan_runner(tf_var_file='test.negs.tfvars') + counts = collections.Counter(f'{r["type"]}.{r["name"]}' for r in resources) + assert counts == { + 'google_compute_forwarding_rule.default': 1, + 'google_compute_health_check.default': 1, + 'google_compute_network_endpoint.default': 1, + 'google_compute_network_endpoint_group.default': 1, + 'google_compute_region_backend_service.default': 1, + 'google_compute_region_target_http_proxy.default': 1, + 'google_compute_region_url_map.default': 1 + } -def test_reserved_ip(plan_runner): - "Tests an IP reservation with a group backend service." - _, resources = plan_runner( - backend_services_config=_BACKEND_SVC_CONFIG, - static_ip_config=_RESERVED_IP_CONFIG - ) - assert len(resources) == 6 - resources = dict((r['type'], r['values']) for r in resources) - - assert 'google_compute_region_backend_service' in resources - assert 'google_compute_region_target_http_proxy' in resources - assert 'google_compute_region_target_https_proxy' not in resources - assert 'google_compute_region_url_map' in resources - assert 'google_compute_address' in resources - assert 'google_compute_forwarding_rule' in resources +def test_ssl_certificates(plan_runner): + "Test HTTPS load balancer with SSL certificates." + _, resources = plan_runner(tf_var_file='test.ssl.tfvars') + counts = collections.Counter(f'{r["type"]}.{r["name"]}' for r in resources) + assert counts == { + 'google_compute_forwarding_rule.default': 1, + 'google_compute_health_check.default': 1, + 'google_compute_region_ssl_certificate.default': 1, + 'google_compute_region_target_https_proxy.default': 1, + 'google_compute_region_url_map.default': 1 + } -def test_ssl(plan_runner): - "Tests HTTPS and SSL certificates." - _, resources = plan_runner( - backend_services_config=_BACKEND_SVC_CONFIG, - https="true", - ssl_certificates_config=_SSL_CERTIFICATES_CONFIG, - target_proxy_https_config=_TARGET_PROXY_HTTPS_CONFIG - ) - assert len(resources) == 6 - resources = dict((r['type'], r['values']) for r in resources) - - fwd_rule = resources['google_compute_forwarding_rule'] - assert fwd_rule['port_range'] == '443' - - assert 'google_compute_region_backend_service' in resources - assert 'google_compute_region_ssl_certificate' in resources - assert 'google_compute_region_target_http_proxy' not in resources - assert 'google_compute_region_target_https_proxy' in resources - assert 'google_compute_region_url_map' in resources - assert 'google_compute_forwarding_rule' in resources - - -def test_ssl_existing_cert(plan_runner): - "Tests HTTPS and SSL existing certificate." - _, resources = plan_runner( - backend_services_config=_BACKEND_SVC_CONFIG, - https="true", - target_proxy_https_config=_TARGET_PROXY_HTTPS_CONFIG - ) - assert len(resources) == 5 - resources = dict((r['type'], r['values']) for r in resources) - - fwd_rule = resources['google_compute_forwarding_rule'] - assert fwd_rule['port_range'] == '443' - - assert 'google_compute_region_backend_service' in resources - assert 'google_compute_region_ssl_certificate' not in resources - assert 'google_compute_region_target_http_proxy' not in resources - assert 'google_compute_region_target_https_proxy' in resources - assert 'google_compute_region_url_map' in resources - assert 'google_compute_forwarding_rule' in resources +def test_urlmaps(plan_runner): + "Test URL maps." + _, resources = plan_runner(tf_var_file='test.urlmaps.tfvars') + counts = collections.Counter(f'{r["type"]}.{r["name"]}' for r in resources) + assert counts == { + 'google_compute_forwarding_rule.default': 1, + 'google_compute_health_check.default': 1, + 'google_compute_region_backend_service.default': 2, + 'google_compute_region_target_http_proxy.default': 1, + 'google_compute_region_url_map.default': 1 + }