# main.tf configures top-level resources # 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") } # The webapp service account has the ability to connect to the SQL instance. # (Only if sql_instance_connection_name is non-empty.) resource "google_project_iam_member" "webapp_sql_client" { count = (var.sql_instance_connection_name != "") ? 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 # Google Beta provider is required for mounting secrets AToW provider = google-beta autogenerate_revision_name = true 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. local.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. { "run.googleapis.com/ingress" : var.allowed_ingress, }, var.service_annotations, ) } template { metadata { 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 { image = var.image_name resources { limits = { cpu = var.cpu_limit memory = var.memory_limit } } dynamic "env" { for_each = var.environment_variables content { name = env.key value = env.value } } 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/sandbox"], # 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 local.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, ] } # 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" { count = local.domain_mapping_present ? 1 : 0 location = var.cloud_run_region name = var.dns_name metadata { # For managed Cloud Run, the namespace *must* be the project name. namespace = var.project } spec { route_name = google_cloud_run_service.webapp.name } } module "uptime_monitoring" { for_each = toset(local.monitor_hosts) source = "git::https://gitlab.developers.cam.ac.uk/uis/devops/infra/terraform/gcp-site-monitoring.git?ref=v1" host = each.value project = var.project alert_email_addresses = var.alerting_email_address != "" ? [var.alerting_email_address] : [] 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 unathenticated access is not allowed, configure the monitoring to use # an authentication proxy, allowing the monitoring checks to invoke the cloud # run instance. authentication_proxy = !var.allow_unauthenticated_invocations ? { enabled = true 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 } : {} 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 = "webapp" location = var.cloud_run_region }