CloudSQL PSC Endpoints support (#2242)

* Add PSC endpoints consumers to net-address
* Cloud SQL E2E tests
This commit is contained in:
Wiktor Niesiobędzki
2024-05-12 12:00:39 +02:00
committed by GitHub
parent 35a17a46ba
commit 6a3c7fe444
19 changed files with 464 additions and 178 deletions

View File

@@ -1,4 +1,4 @@
# Cloud SQL instance with read replicas
# Cloud SQL instance module
This module manages the creation of Cloud SQL instances with potential read replicas in other regions. It can also create an initial set of users and databases via the `users` and `databases` parameters.
@@ -6,7 +6,24 @@ Note that this module assumes that some options are the same for both the primar
*Warning:* if you use the `users` field, you terraform state will contain each user's password in plain text.
## Simple example
<!-- BEGIN TOC -->
- [Examples](#examples)
- [Simple example](#simple-example)
- [Cross-regional read replica](#cross-regional-read-replica)
- [Custom flags, databases and users](#custom-flags-databases-and-users)
- [CMEK encryption](#cmek-encryption)
- [Instance with PSC enabled](#instance-with-psc-enabled)
- [Enable public IP](#enable-public-ip)
- [Query Insights](#query-insights)
- [Maintenance Config](#maintenance-config)
- [SSL Config](#ssl-config)
- [Variables](#variables)
- [Outputs](#outputs)
- [Fixtures](#fixtures)
<!-- END TOC -->
## Examples
### Simple example
This example shows how to setup a project, VPC and a standalone Cloud SQL instance.
@@ -14,10 +31,12 @@ This example shows how to setup a project, VPC and a standalone Cloud SQL instan
module "project" {
source = "./fabric/modules/project"
billing_account = var.billing_account_id
parent = var.organization_id
name = "my-db-project"
parent = var.folder_id
name = "db-prj"
prefix = var.prefix
services = [
"servicenetworking.googleapis.com"
"servicenetworking.googleapis.com",
"sqladmin.googleapis.com",
]
}
@@ -25,9 +44,18 @@ module "vpc" {
source = "./fabric/modules/net-vpc"
project_id = module.project.project_id
name = "my-network"
# need only one - psa_config or subnets_psc
psa_configs = [{
ranges = { cloud-sql = "10.60.0.0/16" }
ranges = { cloud-sql = "10.60.0.0/16" }
deletion_policy = "ABANDON"
}]
subnets_psc = [
{
ip_cidr_range = "10.0.3.0/24"
name = "psc"
region = var.region
}
]
}
module "db" {
@@ -38,17 +66,20 @@ module "db" {
psa_config = {
private_network = module.vpc.self_link
}
# psc_allowed_consumer_projects = [var.project_id]
}
}
name = "db"
region = "europe-west1"
database_version = "POSTGRES_13"
tier = "db-g1-small"
name = "db"
region = var.region
database_version = "POSTGRES_13"
tier = "db-g1-small"
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=3 resources=11 inventory=simple.yaml
# tftest modules=3 resources=14 inventory=simple.yaml e2e
```
## Cross-regional read replica
### Cross-regional read replica
```hcl
module "db" {
@@ -61,21 +92,23 @@ module "db" {
}
}
}
prefix = "myprefix"
name = "db"
region = "europe-west1"
prefix = "myprefix"
region = var.region
database_version = "POSTGRES_13"
tier = "db-g1-small"
replicas = {
replica1 = { region = "europe-west3", encryption_key_name = null }
replica2 = { region = "us-central1", encryption_key_name = null }
replica1 = { region = "europe-west3" }
replica2 = { region = "us-central1" }
}
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=1 resources=3 inventory=replicas.yaml
# tftest modules=1 resources=3 inventory=replicas.yaml e2e
```
## Custom flags, databases and users
### Custom flags, databases and users
```hcl
module "db" {
@@ -89,7 +122,7 @@ module "db" {
}
}
name = "db"
region = "europe-west1"
region = var.region
database_version = "MYSQL_8_0"
tier = "db-g1-small"
@@ -112,47 +145,19 @@ module "db" {
password = "mypassword"
}
}
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=1 resources=6 inventory=custom.yaml
# tftest modules=1 resources=6 inventory=custom.yaml e2e
```
### CMEK encryption
```hcl
module "project" {
source = "./fabric/modules/project"
billing_account = var.billing_account_id
parent = var.organization_id
name = "my-db-project"
services = [
"servicenetworking.googleapis.com",
"sqladmin.googleapis.com",
]
}
module "kms" {
source = "./fabric/modules/kms"
project_id = module.project.project_id
keyring = {
name = "keyring"
location = var.region
}
keys = {
key-sql = {
iam = {
"roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
"serviceAccount:${module.project.service_accounts.robots.sqladmin}"
]
}
}
}
}
module "db" {
source = "./fabric/modules/cloudsql-instance"
project_id = module.project.project_id
encryption_key_name = module.kms.keys["key-sql"].id
project_id = var.project_id
encryption_key_name = var.kms_key.id
network_config = {
connectivity = {
psa_config = {
@@ -160,13 +165,15 @@ module "db" {
}
}
}
name = "db"
region = var.region
database_version = "POSTGRES_13"
tier = "db-g1-small"
name = "db"
region = var.region
database_version = "POSTGRES_13"
tier = "db-g1-small"
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=3 resources=10
# tftest modules=1 resources=2 fixtures=fixtures/cloudsql-kms-iam-grant.tf e2e
```
### Instance with PSC enabled
@@ -177,22 +184,25 @@ module "db" {
project_id = var.project_id
network_config = {
connectivity = {
psc_allowed_consumer_projects = ["my-project-id"]
psc_allowed_consumer_projects = [var.project_id]
}
}
prefix = "myprefix"
name = "db"
region = "europe-west1"
region = var.region
availability_type = "REGIONAL"
database_version = "POSTGRES_13"
tier = "db-g1-small"
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=1 resources=1
# tftest modules=1 resources=1 inventory=psc.yaml e2e
```
### Enable public IP
Use `ipv_enabled` to create instances with a public IP.
Use `public_ipv4` to create instances with a public IP.
```hcl
module "db" {
@@ -206,15 +216,14 @@ module "db" {
}
}
}
name = "db"
region = "europe-west1"
tier = "db-g1-small"
database_version = "MYSQL_8_0"
replicas = {
replica1 = { region = "europe-west3", encryption_key_name = null }
}
name = "db"
region = var.region
tier = "db-g1-small"
database_version = "MYSQL_8_0"
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=1 resources=2 inventory=public-ip.yaml
# tftest modules=1 resources=1 inventory=public-ip.yaml e2e
```
### Query Insights
@@ -233,15 +242,17 @@ module "db" {
}
}
name = "db"
region = "europe-west1"
region = var.region
database_version = "POSTGRES_13"
tier = "db-g1-small"
insights_config = {
query_string_length = 2048
}
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=1 resources=1 inventory=insights.yaml
# tftest modules=1 resources=1 inventory=insights.yaml e2e
```
### Maintenance Config
@@ -260,13 +271,15 @@ module "db" {
}
}
name = "db"
region = "europe-west1"
region = var.region
database_version = "POSTGRES_13"
tier = "db-g1-small"
maintenance_config = {}
maintenance_config = {}
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=1 resources=1
# tftest modules=1 resources=1 e2e
```
### SSL Config
@@ -285,13 +298,15 @@ module "db" {
}
}
name = "db"
region = "europe-west1"
region = var.region
database_version = "POSTGRES_13"
tier = "db-g1-small"
ssl = {}
ssl = {}
gcp_deletion_protection = false
terraform_deletion_protection = false
}
# tftest modules=1 resources=1
# tftest modules=1 resources=1 e2e
```
<!-- BEGIN TFDOC -->
## Variables
@@ -322,7 +337,7 @@ module "db" {
| [labels](variables.tf#L140) | Labels to be attached to all instances. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [maintenance_config](variables.tf#L146) | Set maintenance window configuration and maintenance deny period (up to 90 days). Date format: 'yyyy-mm-dd'. | <code title="object&#40;&#123;&#10; maintenance_window &#61; optional&#40;object&#40;&#123;&#10; day &#61; number&#10; hour &#61; number&#10; update_track &#61; optional&#40;string, null&#41;&#10; &#125;&#41;, null&#41;&#10; deny_maintenance_period &#61; optional&#40;object&#40;&#123;&#10; start_date &#61; string&#10; end_date &#61; string&#10; start_time &#61; optional&#40;string, &#34;00:00:00&#34;&#41;&#10; &#125;&#41;, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [prefix](variables.tf#L207) | Optional prefix used to generate instance names. | <code>string</code> | | <code>null</code> |
| [replicas](variables.tf#L227) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | <code title="map&#40;object&#40;&#123;&#10; region &#61; string&#10; encryption_key_name &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [replicas](variables.tf#L227) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | <code title="map&#40;object&#40;&#123;&#10; region &#61; string&#10; encryption_key_name &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [root_password](variables.tf#L236) | Root password of the Cloud SQL instance. Required for MS SQL Server. | <code>string</code> | | <code>null</code> |
| [ssl](variables.tf#L242) | Setting to enable SSL, set config and certificates. | <code title="object&#40;&#123;&#10; client_certificates &#61; optional&#40;list&#40;string&#41;&#41;&#10; require_ssl &#61; optional&#40;bool&#41;&#10; ssl_mode &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [terraform_deletion_protection](variables.tf#L258) | Prevent terraform from deleting instances. | <code>bool</code> | | <code>true</code> |
@@ -350,4 +365,8 @@ module "db" {
| [self_link](outputs.tf#L114) | Self link of the primary instance. | |
| [self_links](outputs.tf#L119) | Self links of all instances. | |
| [user_passwords](outputs.tf#L127) | Map of containing the password of all users created through terraform. | ✓ |
## Fixtures
- [cloudsql-kms-iam-grant.tf](../../tests/fixtures/cloudsql-kms-iam-grant.tf)
<!-- END TFDOC -->

View File

@@ -228,7 +228,7 @@ variable "replicas" {
description = "Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation."
type = map(object({
region = string
encryption_key_name = string
encryption_key_name = optional(string)
}))
default = {}
}

View File

@@ -122,6 +122,26 @@ module "addresses" {
# tftest modules=1 resources=1 inventory=psc.yaml e2e
```
To create PSC address targeting a service regional provider use the `service_attachment` property.
```hcl
module "addresses" {
source = "./fabric/modules/net-address"
project_id = var.project_id
psc_addresses = {
cloudsql-one = {
address = "10.0.16.32"
subnet_self_link = var.subnet.self_link
region = var.region
service_attachment = {
psc_service_attachment_link = module.cloudsql-instance.psc_service_attachment_link
}
}
}
}
# tftest modules=2 resources=3 fixtures=fixtures/cloudsql-instance.tf inventory=psc-service-attachment.yaml e2e
```
### IPSec Interconnect addresses
```hcl
@@ -176,8 +196,8 @@ module "addresses" {
| [internal_addresses](variables.tf#L50) | Map of internal addresses to create, keyed by name. | <code title="map&#40;object&#40;&#123;&#10; region &#61; string&#10; subnetwork &#61; string&#10; address &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string, &#34;Terraform managed.&#34;&#41;&#10; ipv6 &#61; optional&#40;map&#40;string&#41;&#41; &#35; To be left empty for ipv6&#10; labels &#61; optional&#40;map&#40;string&#41;&#41;&#10; name &#61; optional&#40;string&#41;&#10; purpose &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [ipsec_interconnect_addresses](variables.tf#L65) | Map of internal addresses used for HPA VPN over Cloud Interconnect. | <code title="map&#40;object&#40;&#123;&#10; region &#61; string&#10; address &#61; string&#10; network &#61; string&#10; description &#61; optional&#40;string, &#34;Terraform managed.&#34;&#41;&#10; name &#61; optional&#40;string&#41;&#10; prefix_length &#61; number&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [network_attachments](variables.tf#L84) | PSC network attachments, names as keys. | <code title="map&#40;object&#40;&#123;&#10; subnet_self_link &#61; string&#10; automatic_connection &#61; optional&#40;bool, false&#41;&#10; description &#61; optional&#40;string, &#34;Terraform-managed.&#34;&#41;&#10; producer_accept_lists &#61; optional&#40;list&#40;string&#41;&#41;&#10; producer_reject_lists &#61; optional&#40;list&#40;string&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [psa_addresses](variables.tf#L102) | Map of internal addresses used for Private Service Access. | <code title="map&#40;object&#40;&#123;&#10; address &#61; string&#10; network &#61; string&#10; prefix_length &#61; number&#10; description &#61; optional&#40;string, &#34;Terraform managed.&#34;&#41;&#10; name &#61; optional&#40;string&#41;&#10;&#10;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [psc_addresses](variables.tf#L115) | Map of internal addresses used for Private Service Connect. | <code title="map&#40;object&#40;&#123;&#10; address &#61; string&#10; network &#61; string&#10; description &#61; optional&#40;string, &#34;Terraform managed.&#34;&#41;&#10; name &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [psa_addresses](variables.tf#L102) | Map of internal addresses used for Private Service Access. | <code title="map&#40;object&#40;&#123;&#10; address &#61; string&#10; network &#61; string&#10; prefix_length &#61; number&#10; description &#61; optional&#40;string, &#34;Terraform managed.&#34;&#41;&#10; name &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [psc_addresses](variables.tf#L114) | Map of internal addresses used for Private Service Connect. | <code title="map&#40;object&#40;&#123;&#10; address &#61; string&#10; description &#61; optional&#40;string, &#34;Terraform managed.&#34;&#41;&#10; name &#61; optional&#40;string&#41;&#10; network &#61; optional&#40;string&#41;&#10; region &#61; optional&#40;string&#41;&#10; subnet_self_link &#61; optional&#40;string&#41;&#10; service_attachment &#61; optional&#40;object&#40;&#123; &#35; so we can safely check if service_attachemnt &#33;&#61; null in for_each&#10; psc_service_attachment_link &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs
@@ -193,5 +213,6 @@ module "addresses" {
## Fixtures
- [cloudsql-instance.tf](../../tests/fixtures/cloudsql-instance.tf)
- [net-vpc-ipv6.tf](../../tests/fixtures/net-vpc-ipv6.tf)
<!-- END TFDOC -->

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2022 Google LLC
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,20 +14,6 @@
* limitations under the License.
*/
locals {
network_attachments = {
for k, v in var.network_attachments : k => merge(v, {
region = regex("regions/([^/]+)", v.subnet_self_link)[0]
# not using the full self link generates a permadiff
subnet_self_link = (
startswith(v.subnet_self_link, "https://")
? v.subnet_self_link
: "https://www.googleapis.com/compute/v1/${v.subnet_self_link}"
)
})
}
}
resource "google_compute_global_address" "global" {
for_each = var.global_addresses
project = var.project_id
@@ -66,18 +52,6 @@ resource "google_compute_address" "internal" {
subnetwork = each.value.subnetwork
}
resource "google_compute_global_address" "psc" {
for_each = var.psc_addresses
project = var.project_id
name = coalesce(each.value.name, each.key)
description = each.value.description
address = try(each.value.address, null)
address_type = "INTERNAL"
network = each.value.network
purpose = "PRIVATE_SERVICE_CONNECT"
# labels = lookup(var.internal_address_labels, each.key, {})
}
resource "google_compute_global_address" "psa" {
for_each = var.psa_addresses
project = var.project_id
@@ -104,17 +78,3 @@ resource "google_compute_address" "ipsec_interconnect" {
purpose = "IPSEC_INTERCONNECT"
}
resource "google_compute_network_attachment" "default" {
provider = google-beta
for_each = local.network_attachments
project = var.project_id
region = each.value.region
name = each.key
description = each.value.description
connection_preference = (
each.value.automatic_connection ? "ACCEPT_AUTOMATIC" : "ACCEPT_MANUAL"
)
subnetworks = [each.value.subnet_self_link]
producer_accept_lists = each.value.producer_accept_lists
producer_reject_lists = each.value.producer_reject_lists
}

102
modules/net-address/psc.tf Normal file
View File

@@ -0,0 +1,102 @@
/**
* Copyright 2024 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 {
network_attachments = {
for k, v in var.network_attachments : k => merge(v, {
region = regex("regions/([^/]+)", v.subnet_self_link)[0]
# not using the full self link generates a permadiff
subnet_self_link = (
startswith(v.subnet_self_link, "https://")
? v.subnet_self_link
: "https://www.googleapis.com/compute/v1/${v.subnet_self_link}"
)
})
}
regional_psc = {
for name, psc in var.psc_addresses : name => psc if psc.region != null
}
global_psc = {
for name, psc in var.psc_addresses : name => psc if psc.region == null
}
}
resource "google_compute_network_attachment" "default" {
provider = google-beta
for_each = local.network_attachments
project = var.project_id
region = each.value.region
name = each.key
description = each.value.description
connection_preference = (
each.value.automatic_connection ? "ACCEPT_AUTOMATIC" : "ACCEPT_MANUAL"
)
subnetworks = [each.value.subnet_self_link]
producer_accept_lists = each.value.producer_accept_lists
producer_reject_lists = each.value.producer_reject_lists
}
# global PSC services
resource "google_compute_global_address" "psc" {
for_each = local.global_psc
project = var.project_id
name = coalesce(each.value.name, each.key)
description = each.value.description
address = try(each.value.address, null)
address_type = "INTERNAL"
network = each.value.network
purpose = "PRIVATE_SERVICE_CONNECT"
# labels = lookup(var.internal_address_labels, each.key, {})
}
resource "google_compute_global_forwarding_rule" "psc_consumer" {
for_each = { for name, psc in local.global_psc : name => psc if psc.service_attachment != null }
name = coalesce(each.value.name, each.key)
project = var.project_id
subnetwork = each.value.subnet_self_link
ip_address = google_compute_global_address.psc[each.key].self_link
load_balancing_scheme = ""
target = each.value.service_attachment.psc_service_attachment_link
}
# regional PSC services
resource "google_compute_address" "psc" {
for_each = local.regional_psc
project = var.project_id
name = coalesce(each.value.name, each.key)
address = try(each.value.address, null)
address_type = "INTERNAL"
description = each.value.description
network = each.value.network
# purpose not applicable for regional address
# purpose = "PRIVATE_SERVICE_CONNECT"
region = each.value.region
subnetwork = each.value.subnet_self_link
# labels = lookup(var.internal_address_labels, each.key, {})
}
resource "google_compute_forwarding_rule" "psc_consumer" {
for_each = { for name, psc in local.regional_psc : name => psc if psc.service_attachment != null }
name = coalesce(each.value.name, each.key)
project = var.project_id
region = each.value.region
subnetwork = each.value.subnet_self_link
ip_address = google_compute_address.psc[each.key].self_link
load_balancing_scheme = ""
recreate_closed_psc = true
target = each.value.service_attachment.psc_service_attachment_link
}

View File

@@ -107,7 +107,6 @@ variable "psa_addresses" {
prefix_length = number
description = optional(string, "Terraform managed.")
name = optional(string)
}))
default = {}
}
@@ -115,10 +114,27 @@ variable "psa_addresses" {
variable "psc_addresses" {
description = "Map of internal addresses used for Private Service Connect."
type = map(object({
address = string
network = string
description = optional(string, "Terraform managed.")
name = optional(string)
address = string
description = optional(string, "Terraform managed.")
name = optional(string)
network = optional(string)
region = optional(string)
subnet_self_link = optional(string)
service_attachment = optional(object({ # so we can safely check if service_attachemnt != null in for_each
psc_service_attachment_link = string
}))
}))
default = {}
validation {
condition = alltrue([for key, value in var.psc_addresses : (value.region != null || (value.region == null && value.network != null))])
error_message = "Provide network if creating global PSC addresses / endpoints."
}
validation {
condition = alltrue([for key, value in var.psc_addresses : (value.region == null || (value.region != null && value.subnet_self_link != null))])
error_message = "Provide subnet_self_link if creating regional PSC addresses / endpoints."
}
validation {
condition = alltrue([for key, value in var.psc_addresses : !(value.subnet_self_link != null && value.network != null)])
error_message = "Do not provide network and subnet_self_link at the same time"
}
}

View File

@@ -87,8 +87,15 @@ class FabricTestItem(pytest.Item):
self.extra_files = extra_files
def runtest(self):
s = plan_validator(self.module, self.inventory, self.parent.path.parent,
self.tf_var_files, self.extra_files)
try:
summary = plan_validator(self.module, self.inventory, self.parent.path.parent,
self.tf_var_files, self.extra_files)
except AssertionError:
def full_paths(x):
return [(self.parent.path.parent / x ) for x in x]
print(f'Error in inventory file: {" ".join(full_paths(self.inventory))}')
print(f'To regenerate inventory run: python tools/plan_summary.py {self.module} {" ".join(full_paths(self.tf_var_files))}')
raise
def reportinfo(self):
return self.path, None, self.name

View File

@@ -84,7 +84,7 @@ variable "subnet_psc_1" {
name = "subnet_name"
region = "subnet_region"
cidr = "subnet_cidr"
self_link = "subnet_self_link"
self_link = "https://www.googleapis.com/compute/v1/projects/my-project/regions/europe-west8/subnetworks/subnet"
}
}

View File

@@ -37,6 +37,12 @@ subnet = {
cidr = "${subnet.ip_cidr_range}"
self_link = "${subnet.self_link}"
}
subnet_psc_1 = {
name = "${subnet_psc_1.name}"
region = "${subnet_psc_1.region}"
cidr = "${subnet_psc_1.ip_cidr_range}"
self_link = "${subnet_psc_1.self_link}"
}
vpc = {
name = "${vpc.name}"
self_link = "${vpc.self_link}"

View File

@@ -15,7 +15,8 @@
locals {
prefix = "${var.prefix}-${var.timestamp}${var.suffix}"
jit_services = [
"storage.googleapis.com", # no permissions granted by default
"storage.googleapis.com", # no permissions granted by default
"sqladmin.googleapis.com", # roles/cloudsql.serviceAgent
]
services = [
# trimmed down list of services, to be extended as needed
@@ -35,6 +36,7 @@ locals {
"secretmanager.googleapis.com",
"servicenetworking.googleapis.com",
"serviceusage.googleapis.com",
"sqladmin.googleapis.com",
"stackdriver.googleapis.com",
"storage-component.googleapis.com",
"storage.googleapis.com",
@@ -114,6 +116,36 @@ resource "google_compute_subnetwork" "proxy_only_regional" {
role = "ACTIVE"
}
resource "google_compute_subnetwork" "psc" {
project = google_project.project.project_id
network = google_compute_network.network.name
name = "psc-regional"
region = var.region
ip_cidr_range = "10.0.19.0/24"
purpose = "PRIVATE_SERVICE_CONNECT"
}
### PSA ###
resource "google_compute_global_address" "psa_ranges" {
project = google_project.project.project_id
network = google_compute_network.network.id
name = "psa-range"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
address = "10.0.20.0"
prefix_length = 22
}
resource "google_service_networking_connection" "psa_connection" {
network = google_compute_network.network.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.psa_ranges.name]
deletion_policy = "ABANDON"
}
### END OF PSA
resource "google_service_account" "service_account" {
account_id = "e2e-service-account"
project = google_project.project.project_id
@@ -141,6 +173,12 @@ resource "google_project_service_identity" "jit_si" {
depends_on = [google_project_service.project_service]
}
resource "google_project_iam_binding" "cloudsql_agent" {
members = ["serviceAccount:service-${google_project.project.number}@gcp-sa-cloud-sql.iam.gserviceaccount.com"]
project = google_project.project.project_id
role = "roles/cloudsql.serviceAgent"
depends_on = [google_project_service_identity.jit_si]
}
resource "local_file" "terraform_tfvars" {
filename = "e2e_tests.tfvars"
@@ -168,6 +206,12 @@ resource "local_file" "terraform_tfvars" {
ip_cidr_range = google_compute_subnetwork.subnetwork.ip_cidr_range
self_link = google_compute_subnetwork.subnetwork.self_link
}
subnet_psc_1 = {
name = google_compute_subnetwork.psc.name
region = google_compute_subnetwork.psc.region
ip_cidr_range = google_compute_subnetwork.psc.ip_cidr_range
self_link = google_compute_subnetwork.psc.self_link
}
vpc = {
name = google_compute_network.network.name
self_link = google_compute_network.network.self_link

View File

@@ -186,8 +186,13 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
# print(yaml.dump({'counts': summary.counts}))
if 'values' in inventory:
validate_plan_object(inventory['values'], summary.values, relative_path,
"")
try:
validate_plan_object(inventory['values'], summary.values, relative_path,
"")
except AssertionError:
print(f'\n{path}')
print(yaml.dump({'values': summary.values}))
raise
if 'counts' in inventory:
try:
@@ -199,6 +204,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
assert plan_count == expected_count, \
f'{relative_path}: count of {type_} resources failed. Got {plan_count}, expected {expected_count}'
except AssertionError:
print(f'\n{path}')
print(yaml.dump({'counts': summary.counts}))
raise
@@ -218,6 +224,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
f'{relative_path}: output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`'
except AssertionError:
if _buffer:
print(f'\n{path}')
print(yaml.dump(_buffer))
raise
return summary

33
tests/fixtures/cloudsql-instance.tf vendored Normal file
View File

@@ -0,0 +1,33 @@
/**
* Copyright 2024 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 "cloudsql-instance" {
source = "./fabric/modules/cloudsql-instance"
project_id = var.project_id
network_config = {
connectivity = {
psc_allowed_consumer_projects = [var.project_id]
}
}
## define a consumer project with an endpoint within the project
name = "db"
region = var.region
availability_type = "REGIONAL"
database_version = "POSTGRES_13"
tier = "db-g1-small"
gcp_deletion_protection = false
terraform_deletion_protection = false
}

View File

@@ -0,0 +1,23 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
resource "google_kms_crypto_key_iam_binding" "encrypt_decrypt" {
crypto_key_id = var.kms_key.id
members = [
"serviceAccount:service-${var.project_number}@gcp-sa-cloud-sql.iam.gserviceaccount.com"
]
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
}

View File

@@ -17,11 +17,11 @@ values:
database_version: POSTGRES_13
name: db
project: project-id
region: europe-west1
region: europe-west8
settings:
- activation_policy: ALWAYS
availability_type: ZONAL
deletion_protection_enabled: true
deletion_protection_enabled: false
disk_autoresize: true
disk_type: PD_SSD
insights_config:

View File

@@ -0,0 +1,38 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
values:
module.db.google_sql_database_instance.primary:
database_version: POSTGRES_13
deletion_protection: false
name: myprefix-db
project: project-id
region: europe-west8
settings:
- activation_policy: ALWAYS
availability_type: REGIONAL
deletion_protection_enabled: false
ip_configuration:
- ipv4_enabled: false
private_network: null
psc_config:
- allowed_consumer_projects:
- project-id
psc_enabled: true
tier: db-g1-small
counts:
google_sql_database_instance: 1
modules: 1
resources: 1

View File

@@ -17,30 +17,11 @@ values:
database_version: MYSQL_8_0
name: db
project: project-id
region: europe-west1
region: europe-west8
settings:
- activation_policy: ALWAYS
availability_type: ZONAL
deletion_protection_enabled: true
disk_autoresize: true
disk_type: PD_SSD
insights_config: []
ip_configuration:
- allocated_ip_range: null
authorized_networks: []
ipv4_enabled: true
private_network: projects/xxx/global/networks/aaa
tier: db-g1-small
module.db.google_sql_database_instance.replicas["replica1"]:
database_version: MYSQL_8_0
master_instance_name: db
name: replica1
project: project-id
region: europe-west3
settings:
- activation_policy: ALWAYS
availability_type: ZONAL
deletion_protection_enabled: true
deletion_protection_enabled: false
disk_autoresize: true
disk_type: PD_SSD
insights_config: []
@@ -52,4 +33,4 @@ values:
tier: db-g1-small
counts:
google_sql_database_instance: 2
google_sql_database_instance: 1

View File

@@ -18,7 +18,7 @@ values:
database_version: POSTGRES_13
name: myprefix-db
project: project-id
region: europe-west1
region: europe-west8
module.db.google_sql_database_instance.replicas["replica1"]:
clone: []
database_version: POSTGRES_13

View File

@@ -16,10 +16,10 @@ values:
module.db.google_sql_database_instance.primary:
clone: []
database_version: POSTGRES_13
deletion_protection: true
deletion_protection: false
name: db
project: my-db-project
region: europe-west1
project: test-db-prj
region: europe-west8
restore_backup_context: []
root_password: null
settings:
@@ -30,7 +30,7 @@ values:
collation: null
data_cache_config: []
database_flags: []
deletion_protection_enabled: true
deletion_protection_enabled: false
deny_maintenance_period: []
disk_autoresize: true
disk_autoresize_limit: 0
@@ -54,25 +54,25 @@ values:
module.project.google_project.project[0]:
auto_create_network: false
billing_account: 123456-123456-123456
folder_id: null
folder_id: '1122334455'
labels: null
name: my-db-project
org_id: '1122334455'
project_id: my-db-project
name: test-db-prj
org_id: null
project_id: test-db-prj
skip_delete: false
timeouts: null
module.project.google_project_iam_member.servicenetworking[0]:
condition: []
project: my-db-project
project: test-db-prj
role: roles/servicenetworking.serviceAgent
module.project.google_project_service.project_services["servicenetworking.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: my-db-project
project: test-db-prj
service: servicenetworking.googleapis.com
timeouts: null
module.project.google_project_service_identity.servicenetworking[0]:
project: my-db-project
project: test-db-prj
service: servicenetworking.googleapis.com
timeouts: null
module.vpc.google_compute_global_address.psa_ranges["servicenetworking-googleapis-com-cloud-sql"]:
@@ -82,7 +82,7 @@ values:
ip_version: null
name: servicenetworking-googleapis-com-cloud-sql
prefix_length: 16
project: my-db-project
project: test-db-prj
purpose: VPC_PEERING
timeouts: null
module.vpc.google_compute_network.network[0]:
@@ -92,14 +92,14 @@ values:
enable_ula_internal_ipv6: null
name: my-network
network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL
project: my-db-project
project: test-db-prj
routing_mode: GLOBAL
timeouts: null
module.vpc.google_compute_network_peering_routes_config.psa_routes["servicenetworking.googleapis.com"]:
export_custom_routes: false
import_custom_routes: false
network: my-network
project: my-db-project
project: test-db-prj
timeouts: null
module.vpc.google_compute_route.gateway["private-googleapis"]:
description: Terraform-managed.
@@ -111,7 +111,7 @@ values:
next_hop_instance: null
next_hop_vpn_tunnel: null
priority: 1000
project: my-db-project
project: test-db-prj
tags: null
timeouts: null
module.vpc.google_compute_route.gateway["restricted-googleapis"]:
@@ -124,11 +124,11 @@ values:
next_hop_instance: null
next_hop_vpn_tunnel: null
priority: 1000
project: my-db-project
project: test-db-prj
tags: null
timeouts: null
module.vpc.google_service_networking_connection.psa_connection["servicenetworking.googleapis.com"]:
deletion_policy: null
deletion_policy: ABANDON
reserved_peering_ranges:
- servicenetworking-googleapis-com-cloud-sql
service: servicenetworking.googleapis.com
@@ -141,11 +141,11 @@ counts:
google_compute_route: 2
google_project: 1
google_project_iam_member: 1
google_project_service: 1
google_project_service_identity: 1
google_project_service: 2
google_project_service_identity: 2
google_service_networking_connection: 1
google_sql_database_instance: 1
modules: 3
resources: 11
resources: 14
outputs: {}

View File

@@ -0,0 +1,29 @@
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
values:
module.addresses.google_compute_forwarding_rule.psc_consumer["cloudsql-one"]:
load_balancing_scheme: ''
name: cloudsql-one
project: project-id
recreate_closed_psc: true
region: europe-west8
subnetwork: subnet_self_link
module.addresses.google_compute_address.psc["cloudsql-one"]:
address: 10.0.16.32
address_type: INTERNAL
description: Terraform managed.
name: cloudsql-one
project: project-id
subnetwork: subnet_self_link