Newer
Older
# main.tf configures top-level resources
# This data source pulls the configuration for the default google provider.
data "google_client_config" "current" {}
# A service account which the webapp runs in the context of.
resource "google_service_account" "webapp" {
project = var.project
account_id = coalesce(var.service_account_id, "${var.name}-run")
display_name = coalesce(var.service_account_display_name, "Web application Cloud Run service account")
# Grant the webapp service account the ability to connect to the SQL instance
# via the grant_sql_client_role_to_webapp_sa boolean variable.
resource "google_project_iam_member" "webapp_sql_client" {
count = var.grant_sql_client_role_to_webapp_sa ? 1 : 0
project = local.sql_instance_project
role = "roles/cloudsql.client"
member = "serviceAccount:${google_service_account.webapp.email}"
}
# A Cloud Run service which hosts the webapp
resource "google_cloud_run_service" "webapp" {
name = var.name
location = var.cloud_run_region
project = var.project
metadata {
annotations = merge(
{
"serving.knative.dev/creator" : "placeholder",
"serving.knative.dev/lastModifier" : "placeholder",
# As mentioned at https://www.terraform.io/docs/configuration/resources.html#ignore_changes
# placeholders need to be created as the adding the key to the map is
# considered a change and not ignored by ignore_changes. This needs to
# *always* be present in the config in order for it to appear in
# ignore_changes.
"run.googleapis.com/ingress-status" : "placeholder",
# Add the beta launch stage if required.
var.enable_beta_launch_stage ? {
# Required to be able to set ingress type and secrets volume mounts
"run.googleapis.com/launch-stage" : "BETA",
} : {},
# Specify the allowable ingress types.
{
var.service_annotations,
)
}
annotations = merge(
# Annotations which are always set:
{
# Maximum number of auto-scaled instances. For a container with
# N-workers, maxScale should be less than 1/N of the maximum connection
# count for the Cloud SQL instance.
"autoscaling.knative.dev/maxScale" = var.max_scale
# Minim number of instances.
"autoscaling.knative.dev/minScale" = var.min_scale
# As mentioned at https://www.terraform.io/docs/configuration/resources.html#ignore_changes
# placeholders need to be created as the adding the key to the map is
# considered a change and not ignored by ignore_changes
"client.knative.dev/user-image" = "placeholder"
"run.googleapis.com/client-name" = "placeholder"
"run.googleapis.com/client-version" = "placeholder"
"run.googleapis.com/sandbox" = "gvisor"
},
# Annotations which are only set if there is a Cloud SQL instance:
(var.sql_instance_connection_name != "") ? {
# Cloud SQL instances to auto-magically make appear in the container as
# Unix sockets.
"run.googleapis.com/cloudsql-instances" = var.sql_instance_connection_name
# Annocations which are only set if we are allocating a static egress ip:
var.enable_static_egress_ip ? {
# Assign the vpc connector and indicate that it should be used for all traffic
"run.googleapis.com/vpc-access-egress" = "all"
"run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.static-ip-connector[0].id
} : {},
# Additional template annotations passed as a variable.
var.template_annotations,
}
spec {
# Maximum number of concurrent requests to an instance before it is
# auto-scaled. For webapps which use connection pooling, it should be safe
# to set this number without regard to the connection limit of the Cloud
# SQL instance. This can be no greater than 80.
#
# See https://cloud.google.com/run/docs/about-concurrency.
container_concurrency = var.container_concurrency
service_account_name = google_service_account.webapp.email
containers {
resources {
limits = {
cpu = var.cpu_limit
memory = var.memory_limit
}
}
dynamic "env" {
for_each = var.environment_variables
content {
name = env.key
value = env.value
}
}
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
dynamic "env" {
for_each = var.secrets_envars
content {
name = env.value["name"]
value_from {
secret_key_ref {
name = env.value["id"]
key = coalesce(env.value["version"], "latest")
}
}
}
}
dynamic "volume_mounts" {
for_each = var.secrets_volume
content {
name = volume_mounts.value["name"]
mount_path = volume_mounts.value["path"]
}
}
}
dynamic "volumes" {
for_each = var.secrets_volume
content {
name = volumes.value["name"]
secret {
secret_name = volumes.value["id"]
items {
key = coalesce(volumes.value["version"], "latest")
path = volumes.value["name"]
}
}
}
timeout_seconds = var.timeout_seconds
}
}
traffic {
percent = 100
latest_revision = true
}
lifecycle {
ignore_changes = [
# Some common annotations which we don't care about.
template[0].metadata[0].annotations["client.knative.dev/user-image"],
template[0].metadata[0].annotations["run.googleapis.com/client-name"],
template[0].metadata[0].annotations["run.googleapis.com/client-version"],
template[0].metadata[0].annotations["run.googleapis.com/operation-id"],
template[0].metadata[0].labels["run.googleapis.com/startupProbeType"],
template[0].metadata[0].annotations["run.googleapis.com/sandbox"],
metadata[0].annotations["run.googleapis.com/client-name"],
metadata[0].annotations["run.googleapis.com/client-version"],
metadata[0].annotations["run.googleapis.com/operation-id"],
metadata[0].labels["run.googleapis.com/startupProbeType"],
# These are only changed when "run.googleapis.com/launch-stage" is "BETA".
# It's non-trivial to make ignore_changes dependent on input variables so
# we always ignore these annotations even if, strictly speaking, we only
# need to do so is var.enable_beta_launch_stage is true.
metadata[0].annotations["serving.knative.dev/creator"],
metadata[0].annotations["serving.knative.dev/lastModifier"],
# If the allowed ingress variable is specified, ignore feedback about
# its status. We cannot make the presence of this ignore be dependent on
# "allowed_ingress" since ignore_changes needs to be a static list.
metadata[0].annotations["run.googleapis.com/ingress-status"],
depends_on = [
google_secret_manager_secret_iam_member.secrets_access,
null_resource.pre_deploy_job_trigger
# Google Beta provider is required for mounting secrets AToW
provider = google-beta
}
# Allow unauthenticated invocations for the webapp.
resource "google_cloud_run_service_iam_member" "webapp_all_users_invoker" {
count = var.allow_unauthenticated_invocations ? 1 : 0
location = google_cloud_run_service.webapp.location
project = google_cloud_run_service.webapp.project
service = google_cloud_run_service.webapp.name
role = "roles/run.invoker"
member = "allUsers"
}
# Domain mapping for default web-application. Only present if the domain is
# verified. We use the custom DNS name of the webapp if provided but otherwise
# the webapp is hosted at [SERVICE NAME].[PROJECT DNS ZONE]. We can't create
# the domain mapping if the domain is *not* verified because Google won't let
# us.
resource "google_cloud_run_domain_mapping" "webapp" {
for_each = toset(var.ingress_style == "domain-mapping" ? local.dns_names : [])
metadata {
# For managed Cloud Run, the namespace *must* be the project name.
namespace = var.project
}
spec {
route_name = google_cloud_run_service.webapp.name
}
}
for_each = local.monitor_hosts
source = "git::https://gitlab.developers.cam.ac.uk/uis/devops/infra/terraform/gcp-site-monitoring.git?ref=v3"
host = each.value.host
project = var.project
alert_email_addresses = var.alerting_email_address != "" ? [var.alerting_email_address] : []
alert_notification_channels = var.alert_notification_channels
uptime_check = {
# Accept either e.g. "60s" or 60 for timeout and periods for compatibility
# with previous releases.
timeout = tonumber(trimsuffix(var.alerting_uptime_timeout, "s"))
period = tonumber(trimsuffix(var.alerting_uptime_period, "s"))
path = var.monitoring_path
success_threshold_percent = var.alerting_success_threshold_percent
alert_enabled = var.alerting_enabled
}
tls_check = {
alert_enabled = var.alerting_enabled
}
# If required, configure the monitoring to use an authentication proxy, allowing
# the monitoring checks to invoke the cloud run instance.
authentication_proxy = {
enabled = each.value.enable_auth_proxy
cloud_run_project = google_cloud_run_service.webapp.project
cloud_run_service_name = google_cloud_run_service.webapp.name
cloud_run_region = var.cloud_run_region
egress_connector = each.value.enable_egress_connector ? local.auth_proxy_egress_connector : ""
egress_connector_settings = each.value.enable_egress_connector && local.auth_proxy_egress_connector != "" ? "ALL_TRAFFIC" : null
}

Monty Dawson
committed
providers = {
google = google.stackdriver
}
# This extracts information about any currently running Cloud Run revision before
# starting the plan walk. This is current behaviour, but may change in future see
# https://github.com/hashicorp/terraform/issues/17034.
data "google_cloud_run_service" "webapp" {
name = var.name
location = var.cloud_run_region
}
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# Configure a Cloud Run Job which will be executed before the deployment of the google_cloud_run_service.webapp
# resource. This is primarily useful to run database migrations, however other use cases may exist.
resource "google_cloud_run_v2_job" "pre_deploy" {
count = var.enable_pre_deploy_job ? 1 : 0
name = "${var.name}-pre-deploy"
location = var.cloud_run_region
project = var.project
template {
template {
service_account = google_service_account.webapp.email
dynamic "volumes" {
for_each = var.sql_instance_connection_name != "" ? [1] : []
content {
name = "cloudsql"
cloud_sql_instance {
instances = [var.sql_instance_connection_name]
}
}
}
dynamic "volumes" {
for_each = var.secrets_volume
content {
name = volumes.value["name"]
secret {
secret = volumes.value["id"]
items {
version = coalesce(volumes.value["version"], "latest")
path = volumes.value["name"]
mode = 0
}
}
}
}
containers {
image = local.pre_deploy_job_image_name
command = var.pre_deploy_job_command
args = var.pre_deploy_job_args
dynamic "env" {

Roy Harrington
committed
for_each = local.pre_deploy_job_environment_variables
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
content {
name = env.key
value = env.value
}
}
dynamic "env" {
for_each = var.secrets_envars
content {
name = env.value["name"]
value_source {
secret_key_ref {
secret = env.value["id"]
version = coalesce(env.value["version"], "latest")
}
}
}
}
dynamic "volume_mounts" {
for_each = var.sql_instance_connection_name != "" ? [1] : []
content {
name = "cloudsql"
mount_path = "/cloudsql"
}
}
dynamic "volume_mounts" {
for_each = var.secrets_volume
content {
name = volume_mounts.value["name"]
mount_path = volume_mounts.value["path"]
}
}
}
}
}
}
# Trigger the pre-deploy job using the gcloud CLI whenever the var.image_name value changes.
resource "null_resource" "pre_deploy_job_trigger" {
count = var.enable_pre_deploy_job && var.trigger_pre_deploy_job ? 1 : 0
triggers = merge({
image_name = var.image_name
}, var.force_pre_deploy_job ? {
timestamp = timestamp()
} : {})
provisioner "local-exec" {
command = <<EOI
gcloud --project ${var.project} run jobs execute \
--region ${var.cloud_run_region} --wait ${google_cloud_run_v2_job.pre_deploy[0].name}
EOI
environment = {
# This environment variable tells gcloud CLI to authenticate using an access token. We're using the access token
# configured in the default google provider via the google_client_config data source.
CLOUDSDK_AUTH_ACCESS_TOKEN = data.google_client_config.current.access_token
}
}
depends_on = [
google_cloud_run_v2_job.pre_deploy
]
}