-
Notifications
You must be signed in to change notification settings - Fork 787
Description
Module: 4-projects/modules/single_project
The single_project module uses the local part of an email address for the primary_contact and secondary_contact labels. This is done using element(split("@", var.primary_contact), 0).
labels = {
environment = var.environment
application_name = var.application_name
billing_code = var.billing_code
primary_contact = element(split("@", var.primary_contact), 0)
secondary_contact = element(split("@", var.secondary_contact), 0)
business_code = var.business_code
env_code = local.env_code
vpc = var.vpc
}
The problem is that Google Cloud project labels have strict validation requirements and do not allow most special characters, including periods (.).
Google Cloud Label Requirements:
Labels must be key-value pairs. Keys and values can only contain lowercase letters, numeric characters, underscores, and dashes. All characters must use UTF-8 encoding, and entries can be no longer than 63 characters.
See: Google Cloud Documentation
If a contact email like [email protected] is provided, the module will attempt to create a label primary_contact = "jane.doe", which is invalid and will cause the Terraform apply to fail.
Expected Behavior
The module should sanitize the contact strings to make them valid Google Cloud labels. A common convention is to replace invalid characters like . with an allowed character or string, such as _ (underscore) or [dot].
Actual Behavior
The module passes the email local-part directly as the label value. If the local-part contains a period (a very common occurrence), the terraform apply fails due to an invalid label.
Steps to Reproduce
Use the single_project module from 4-projects.
Pass a variable for primary_contact that contains a period before the @ symbol (e.g., "[email protected]").
Run terraform plan or terraform apply.
The operation will fail validation.
Suggested Fix
The label values should be sanitized. For example, using the replace function:
Recommendation: Replace . with _ (underscore), as dashes are also common in email local-parts but might be interpreted as separators.
labels = {
environment = var.environment
application_name = var.application_name
billing_code = var.billing_code
primary_contact = replace(element(split("@", var.primary_contact), 0), ".", "_")
secondary_contact = replace(element(split("@", var.secondary_contact), 0), ".", "_")
business_code = var.business_code
env_code = local.env_code
vpc = var.vpc
}
Alternatively, to match the user's suggestion:
primary_contact = replace(element(split("@", var.primary_contact), 0), ".", "[dot]")
secondary_contact = replace(element(split("@", var.secondary_contact), 0), ".", "[dot]")
However, the underscore _ is more conventional for label sanitization. A more robust regex replacement for all invalid characters might be even better.
Expected behavior
No response
Observed behavior
No response
Terraform Configuration
/**
* Copyright 2021 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 {
env_code = element(split("", var.environment), 0)
source_repos = setintersection(
toset(keys(var.app_infra_pipeline_service_accounts)),
toset(keys(var.sa_roles))
)
pipeline_roles = var.enable_cloudbuild_deploy ? flatten([
for repo in local.source_repos : [
for role in var.sa_roles[repo] :
{
repo = repo
role = role
sa = var.app_infra_pipeline_service_accounts[repo]
}
]
]) : []
network_user_role = var.enable_cloudbuild_deploy ? flatten([
for repo in local.source_repos : [
for subnet in var.shared_vpc_subnets :
{
repo = repo
subnet = element(split("/", subnet), index(split("/", subnet), "subnetworks", ) + 1, )
region = element(split("/", subnet), index(split("/", subnet), "regions") + 1, )
sa = var.app_infra_pipeline_service_accounts[repo]
}
]
]) : []
}
module "project" {
source = "terraform-google-modules/project-factory/google"
version = "~> 18.0"
random_project_id = true
random_project_id_length = 4
activate_apis = distinct(concat(var.activate_apis, ["billingbudgets.googleapis.com"]))
name = "${var.project_prefix}-${local.env_code}-${var.business_code}-${var.project_suffix}"
org_id = var.org_id
billing_account = var.billing_account
folder_id = var.folder_id
deletion_policy = var.project_deletion_policy
svpc_host_project_id = var.shared_vpc_host_project_id
shared_vpc_subnets = var.shared_vpc_subnets # Optional: To enable subnetting, replace to "module.networking_project.subnetwork_self_link"
vpc_service_control_attach_enabled = var.vpc_service_control_attach_enabled
vpc_service_control_attach_dry_run = var.vpc_service_control_attach_dry_run
vpc_service_control_perimeter_name = var.vpc_service_control_perimeter_name
vpc_service_control_sleep_duration = var.vpc_service_control_sleep_duration
labels = {
environment = var.environment
application_name = var.application_name
billing_code = var.billing_code
primary_contact = element(split("@", var.primary_contact), 0)
secondary_contact = element(split("@", var.secondary_contact), 0)
business_code = var.business_code
env_code = local.env_code
vpc = var.vpc
}
budget_alert_pubsub_topic = var.project_budget.alert_pubsub_topic
budget_alert_spent_percents = var.project_budget.alert_spent_percents
budget_amount = var.project_budget.budget_amount
budget_alert_spend_basis = var.project_budget.alert_spend_basis
}
# Additional roles to the App Infra Pipeline service account
resource "google_project_iam_member" "app_infra_pipeline_sa_roles" {
for_each = { for pr in local.pipeline_roles : "${pr.repo}-${pr.sa}-${pr.role}" => pr }
project = module.project.project_id
role = each.value.role
member = "serviceAccount:${each.value.sa}"
}
resource "google_folder_iam_member" "folder_network_viewer" {
for_each = var.app_infra_pipeline_service_accounts
folder = var.folder_id
role = "roles/compute.networkViewer"
member = "serviceAccount:${each.value}"
}
resource "google_compute_subnetwork_iam_member" "service_account_role_to_vpc_subnets" {
provider = google-beta
for_each = { for nr in local.network_user_role : "${nr.repo}-${nr.subnet}-${nr.sa}" => nr }
subnetwork = each.value.subnet
role = "roles/compute.networkUser"
region = each.value.region
project = var.shared_vpc_host_project_id
member = "serviceAccount:${each.value.sa}"
}Terraform Version
1.13.4Terraform Provider Versions
├── provider[terraform.io/builtin/terraform]
└── module.env
├── provider[registry.terraform.io/hashicorp/random] >= 3.3.0
├── provider[registry.terraform.io/hashicorp/google]
├── provider[terraform.io/builtin/terraform]
└── module.product_pipeline
├── provider[registry.terraform.io/hashicorp/google-beta] >= 3.50.0, != 6.26.0, != 6.27.0, < 7.0.0
├── provider[registry.terraform.io/hashicorp/google] >= 3.50.0, != 6.26.0, != 6.27.0, < 7.0.0
└── module.project
├── provider[registry.terraform.io/hashicorp/google] >= 5.41.0, < 8.0.0
├── provider[registry.terraform.io/hashicorp/google-beta] >= 5.41.0, < 8.0.0
├── module.shared_vpc_access
│ ├── provider[registry.terraform.io/hashicorp/google] >= 3.43.0, < 8.0.0
│ └── provider[registry.terraform.io/hashicorp/google-beta] >= 3.43.0, < 8.0.0
├── module.budget
│ └── provider[registry.terraform.io/hashicorp/google] >= 4.28.0, < 8.0.0
├── module.essential_contacts
│ ├── provider[registry.terraform.io/hashicorp/google] >= 3.43.0, < 8.0.0
│ └── provider[registry.terraform.io/hashicorp/google-beta] >= 3.43.0, < 8.0.0
├── module.gsuite_group
│ └── provider[registry.terraform.io/hashicorp/google] >= 3.43.0, < 8.0.0
├── module.project-factory
│ ├── provider[registry.terraform.io/hashicorp/random] >= 2.2.0
│ ├── provider[registry.terraform.io/hashicorp/time] >= 0.5.0
│ ├── provider[registry.terraform.io/hashicorp/google] >= 5.41.0, < 8.0.0
│ ├── provider[registry.terraform.io/hashicorp/google-beta] >= 5.41.0, < 8.0.0
│ ├── provider[registry.terraform.io/hashicorp/null] >= 2.1.0
│ └── module.project_services
│ ├── provider[registry.terraform.io/hashicorp/google-beta] >= 3.43.0, < 8.0.0
│ └── provider[registry.terraform.io/hashicorp/google] >= 3.43.0, < 8.0.0
└── module.quotas
└── provider[registry.terraform.io/hashicorp/google-beta] >= 4.11.0, < 8.0.0Additional information
No response