[#593] Add HTTP ILB module (net-ilb-l7)

This commit is contained in:
Luca Prete
2022-04-05 08:58:55 +02:00
committed by GitHub
parent 56b89211a7
commit cd0f09b748
15 changed files with 2272 additions and 1 deletions

View File

@@ -27,7 +27,7 @@ The current list of modules supports most of the core foundational and networkin
Currently available modules:
- **foundational** - [folder](./modules/folder), [organization](./modules/organization), [project](./modules/project), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [billing budget](./modules/billing-budget), [naming convention](./modules/naming-convention), [projects-data-source](./modules/projects-data-source)
- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/endpoints)
- **networking** - [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN static](./modules/net-vpn-static), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [NAT](./modules/net-cloudnat), [address reservation](./modules/net-address), [DNS](./modules/dns), [L4 ILB](./modules/net-ilb), [L7 ILB](./modules/net-ilb-l7), [Service Directory](./modules/service-directory), [Cloud Endpoints](./modules/endpoints)
- **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [GKE cluster](./modules/gke-cluster), [GKE nodepool](./modules/gke-nodepool), [GKE hub](./modules/gke-hub), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid)
- **data** - [GCS](./modules/gcs), [BigQuery dataset](./modules/bigquery-dataset), [Pub/Sub](./modules/pubsub), [Datafusion](./modules/datafusion), [Bigtable instance](./modules/bigtable-instance), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag)
- **development** - [Cloud Source Repository](./modules/source-repository), [Container Registry](./modules/container-registry), [Artifact Registry](./modules/artifact-registry), [Apigee Organization](./modules/apigee-organization), [Apigee X Instance](./modules/apigee-x-instance), [API Gateway](./modules/api-gateway)

View File

@@ -0,0 +1,432 @@
# 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.
## Examples
### Minimal Example
An HTTP ILB with a backend service pointing to a GCE instance group:
```hcl
module "ilb" {
source = "./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
}
}
}
# 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.
```hcl
module "ilb" {
source = "./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
}
}
}
# 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 = "./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 = "./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=5
```
-->
### 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 = "./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"
}
]
}
]
}
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 = "./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 = "./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
}
}
}
# tftest modules=1 resources=5
```
Otherwise, unmanaged certificates can also be contextually created:
```hcl
module "ilb" {
source = "./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
}
}
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
}
}
}
resource "tls_private_key" "self_signed_key" {
algorithm = "RSA"
rsa_bits = 2048
}
resource "tls_self_signed_cert" "self_signed_cert" {
key_algorithm = tls_private_key.self_signed_key.algorithm
private_key_pem = tls_private_key.self_signed_key.private_key_pem
validity_period_hours = 12
early_renewal_hours = 3
dns_names = ["example.com"]
allowed_uses = [
"key_encipherment",
"digital_signature",
"server_auth"
]
subject {
common_name = "example.com"
organization = "My Test Org"
}
}
# tftest modules=1 resources=6
```
## Components And Files Mapping
An 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 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.
<!-- TFDOC OPTS files:1 -->
<!-- BEGIN TFDOC -->
## Files
| name | description | resources |
|---|---|---|
| [backend-services.tf](./backend-services.tf) | Bucket and group backend services. | <code>google_compute_region_backend_service</code> |
| [forwarding-rule.tf](./forwarding-rule.tf) | IP Address and forwarding rule. | <code>google_compute_address</code> · <code>google_compute_forwarding_rule</code> |
| [health-checks.tf](./health-checks.tf) | Health checks. | <code>google_compute_region_health_check</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | |
| [ssl-certificates.tf](./ssl-certificates.tf) | SSL certificates. | <code>google_compute_region_ssl_certificate</code> |
| [target-proxy.tf](./target-proxy.tf) | HTTP and HTTPS target proxies. | <code>google_compute_region_target_http_proxy</code> · <code>google_compute_region_target_https_proxy</code> |
| [url-map.tf](./url-map.tf) | URL maps. | <code>google_compute_region_url_map</code> |
| [variables.tf](./variables.tf) | Module variables. | |
| [versions.tf](./versions.tf) | Version pins. | |
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [name](variables.tf#L17) | Load balancer name. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L22) | Project id. | <code>string</code> | ✓ | |
| [region](variables.tf#L157) | The region where to allocate the ILB resources. | <code>string</code> | ✓ | |
| [subnetwork](variables.tf#L187) | The subnetwork where the ILB VIP is allocated. | <code>string</code> | ✓ | |
| [backend_services_config](variables.tf#L27) | The backends services configuration. | <code title="map&#40;object&#40;&#123;&#10; backends &#61; list&#40;object&#40;&#123;&#10; group &#61; string &#35; The instance group link id&#10; options &#61; object&#40;&#123;&#10; balancing_mode &#61; string &#35; Can be UTILIZATION, RATE&#10; capacity_scaler &#61; number &#35; Valid range is &#91;0.0,1.0&#93;&#10; max_connections &#61; number&#10; max_connections_per_instance &#61; number&#10; max_connections_per_endpoint &#61; number&#10; max_rate &#61; number&#10; max_rate_per_instance &#61; number&#10; max_rate_per_endpoint &#61; number&#10; max_utilization &#61; number&#10; &#125;&#41;&#10; &#125;&#41;&#41;&#10; health_checks &#61; list&#40;string&#41;&#10;&#10;&#10; log_config &#61; object&#40;&#123;&#10; enable &#61; bool&#10; sample_rate &#61; number &#35; must be in &#91;0, 1&#93;&#10; &#125;&#41;&#10;&#10;&#10; options &#61; object&#40;&#123;&#10; affinity_cookie_ttl_sec &#61; number&#10; custom_request_headers &#61; list&#40;string&#41;&#10; custom_response_headers &#61; list&#40;string&#41;&#10; connection_draining_timeout_sec &#61; number&#10; locality_lb_policy &#61; string&#10; port_name &#61; string&#10; protocol &#61; string&#10; session_affinity &#61; string&#10; timeout_sec &#61; number&#10;&#10;&#10; circuits_breakers &#61; object&#40;&#123;&#10; max_requests_per_connection &#61; number &#35; Set to 1 to disable keep-alive&#10; max_connections &#61; number &#35; Defaults to 1024&#10; max_pending_requests &#61; number &#35; Defaults to 1024&#10; max_requests &#61; number &#35; Defaults to 1024&#10; max_retries &#61; number &#35; Defaults to 3&#10; &#125;&#41;&#10;&#10;&#10; consistent_hash &#61; object&#40;&#123;&#10; http_header_name &#61; string&#10; minimum_ring_size &#61; string&#10; http_cookie &#61; object&#40;&#123;&#10; name &#61; string&#10; path &#61; string&#10; ttl &#61; object&#40;&#123;&#10; seconds &#61; number&#10; nanos &#61; number&#10; &#125;&#41;&#10; &#125;&#41;&#10; &#125;&#41;&#10;&#10;&#10; iap &#61; object&#40;&#123;&#10; oauth2_client_id &#61; string&#10; oauth2_client_secret &#61; string&#10; oauth2_client_secret_sha256 &#61; string&#10; &#125;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [forwarding_rule_config](variables.tf#L98) | Forwarding rule configurations. | <code title="object&#40;&#123;&#10; ip_version &#61; string&#10; labels &#61; map&#40;string&#41;&#10; network_tier &#61; string&#10; port_range &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; allow_global_access &#61; true&#10; ip_version &#61; &#34;IPV4&#34;&#10; labels &#61; &#123;&#125;&#10; network_tier &#61; &#34;PREMIUM&#34;&#10; port_range &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
| [health_checks_config](variables.tf#L116) | Custom health checks configuration. | <code title="map&#40;object&#40;&#123;&#10; type &#61; string &#35; http https tcp ssl http2&#10; check &#61; map&#40;any&#41; &#35; actual health check block attributes&#10; options &#61; map&#40;number&#41; &#35; interval, thresholds, timeout&#10; logging &#61; bool&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [health_checks_config_defaults](variables.tf#L127) | Auto-created health check default configuration. | <code title="object&#40;&#123;&#10; check &#61; map&#40;any&#41; &#35; actual health check block attributes&#10; logging &#61; bool&#10; options &#61; map&#40;number&#41; &#35; interval, thresholds, timeout&#10; type &#61; string &#35; http https tcp ssl http2&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; type &#61; &#34;http&#34;&#10; logging &#61; false&#10; options &#61; &#123;&#125;&#10; check &#61; &#123;&#10; port_specification &#61; &#34;USE_SERVING_PORT&#34;&#10; &#125;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [https](variables.tf#L145) | Whether to enable HTTPS. | <code>bool</code> | | <code>false</code> |
| [network](variables.tf#L151) | The network where the ILB is created. | <code>string</code> | | <code>&#34;default&#34;</code> |
| [ssl_certificates_config](variables.tf#L162) | The SSL certificates configuration. | <code title="map&#40;object&#40;&#123;&#10; domains &#61; list&#40;string&#41;&#10; tls_private_key &#61; string&#10; tls_self_signed_cert &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [static_ip_config](variables.tf#L172) | Static IP address configuration. | <code title="object&#40;&#123;&#10; reserve &#61; bool&#10; options &#61; object&#40;&#123;&#10; address &#61; string&#10; subnetwork &#61; string &#35; The subnet id&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; reserve &#61; false&#10; options &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
| [target_proxy_https_config](variables.tf#L192) | The HTTPS target proxy configuration. | <code title="object&#40;&#123;&#10; ssl_certificates &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [url_map_config](variables.tf#L200) | The url-map configuration. | <code title="object&#40;&#123;&#10; default_service &#61; string&#10; default_url_redirect &#61; map&#40;any&#41;&#10; host_rules &#61; list&#40;any&#41;&#10; path_matchers &#61; list&#40;any&#41;&#10; tests &#61; list&#40;map&#40;string&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
## 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. | |
<!-- END TFDOC -->

View File

@@ -0,0 +1,133 @@
/**
* 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)
}
}
}

View File

@@ -0,0 +1,67 @@
/**
* 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)
subnetwork = try(var.subnetwork, null)
target = local.target
}

View File

@@ -0,0 +1,148 @@
/**
* 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
}
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
output "health_checks" {
description = "Health-check resources."
value = try(google_compute_region_health_check.health_check, [])
}
output "backend_services" {
description = "Backend service resources."
value = {
group = try(google_compute_region_backend_service.backend_service, [])
}
}
output "url_map" {
description = "The url-map."
value = try(google_compute_region_url_map.url_map, null)
}
output "ssl_certificate_link_ids" {
description = "The SSL certificate."
value = {
for k, v in google_compute_region_ssl_certificate.certificates : k => v.self_link
}
}
output "ip_address" {
description = "The reserved IP address."
value = try(google_compute_forwarding_rule.forwarding_rule.ip_address, null)
}
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)
}

View File

@@ -0,0 +1,26 @@
/**
* 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
}

View File

@@ -0,0 +1,48 @@
/**
* 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
}

View File

@@ -0,0 +1,708 @@
/**
* 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)
}
}
}

View File

@@ -0,0 +1,210 @@
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
variable "name" {
description = "Load balancer name."
type = string
}
variable "project_id" {
description = "Project id."
type = string
}
variable "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 "forwarding_rule_config" {
description = "Forwarding rule configurations."
type = object({
ip_version = string
labels = map(string)
network_tier = string
port_range = 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
}
}
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."
type = string
default = "default"
}
variable "region" {
description = "The region where to allocate the ILB resources."
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."
type = object({
reserve = bool
options = object({
address = string
subnetwork = string # The subnet id
})
})
default = {
reserve = false
options = null
}
}
variable "subnetwork" {
description = "The subnetwork where the ILB VIP is allocated."
type = string
}
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
}

View File

@@ -0,0 +1,27 @@
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
terraform {
required_version = ">= 1.0.0"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.0.0"
}
google-beta = {
source = "hashicorp/google-beta"
version = ">= 4.0.0"
}
}
}

View File

@@ -0,0 +1,13 @@
# 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.

View File

@@ -0,0 +1,33 @@
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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
}

View File

@@ -0,0 +1,184 @@
/**
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
variable "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
})
}))
# 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 "forwarding_rule_config" {
description = "Forwarding rule configurations."
type = object({
ip_version = string
labels = map(string)
network_tier = string
port_range = 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
}
}
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 "ssl_certificates_config" {
description = "The SSL certificate 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."
type = object({
reserve = bool
options = object({
address = string
subnetwork = string # The subnet id
})
})
default = {
reserve = false
options = null
}
}
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
}

View File

@@ -0,0 +1,184 @@
# 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.
_BACKEND_SVC_CONFIG = '''{
my-group = {
backends = [
{
group = "my_group",
options = null
}
],
health_checks = []
log_config = null
options = null
}
}'''
_BACKEND_SVC_CONFIG_HC = '''{
my-group = {
backends = [
{
group = "my_group",
options = null
}
],
health_checks = ["hc_1"]
log_config = null
options = null
}
}'''
_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"
}
}'''
_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_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_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_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(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