Repository: google/exposure-notifications-server
Pull request #488 (2020-05-29 18:36:31)

Allow per-service location customization
Builds on top of the previous PR with a few docs edits and updates.

Fixes GH-434

google-prow-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/488#" title="Author self-approved">sethvargo</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=google%2Fexposure-notifications-server).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/google/exposure-notifications-server/blob/master/OWNERS)~~ [sethvargo]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
sethvargo: /assign @mikehelmick 
googlebot: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogle%2Fexposure-notifications-server%2Fpull%2F488) for more info**.

<!-- need_author_consent -->
googlebot: A Googler has manually verified that the CLAs look good.

(Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.)

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogle%2Fexposure-notifications-server%2Fpull%2F488) for more info**.

<!-- cla_yes -->
sethvargo: https://github.com/google/exposure-notifications-server/pull/434 notes the author signed the CLA

Patch:
diff --git a/.gitignore b/.gitignore
index a52567a..df5cb8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,7 +14,7 @@
 # Output of terraform
 *.tfstate*
 *.terraform/
-terraform.tfvars
+*.tfvars
 terraform/state.tf
 
 /local/
diff --git a/pkg/api/v1alpha1/verification_types.go b/pkg/api/v1alpha1/verification_types.go
new file mode 100644
index 0000000..55a01f2
--- /dev/null
+++ b/pkg/api/v1alpha1/verification_types.go
@@ -0,0 +1,55 @@
+// Copyright 2020 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.
+
+// Package v1alpha1 contains API definitions that can be used outside of this
+// codebase in their alpha form.
+// These APIs are not considered to be stable at HEAD.
+package v1alpha1
+
+import "sort"
+
+const (
+	exposureKeyHMACClaim          = "tekmac"
+	healthAuthorityDataClaim      = "phadata"
+	transmissionRiskOverrideClaim = "tris"
+	keyVersionClaim               = "keyVersion"
+)
+
+// TransmissionRiskVector is an additional set of claims that can be
+// included in the verification certificatin for a diagnosis as recieved
+// from a trusted public health authority.
+type TransmissionRiskVector []TransmissionRiskOverride
+
+// Compile time check that TranismissionRiskVector implements the sort interface.
+var _ sort.Interface = TransmissionRiskVector{}
+
+// TransmissionRiskOverride is an indvidual transmission risk override.
+type TransmissionRiskOverride struct {
+	TranismissionRisk  int `json:"tr"`
+	SinceRollingPeriod int `json:"sinceRollingPeriod"`
+}
+
+func (a TransmissionRiskVector) Len() int {
+	return len(a)
+}
+
+// This sorts the TransmissionRiskVector vector with the largest SinceRollingPeriod
+// value first. Descending sort.
+func (a TransmissionRiskVector) Less(i, j int) bool {
+	return a[i].SinceRollingPeriod > a[j].SinceRollingPeriod
+}
+
+func (a TransmissionRiskVector) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
diff --git a/terraform/README.md b/terraform/README.md
index 30372e3..4bae42f 100644
--- a/terraform/README.md
+++ b/terraform/README.md
@@ -69,7 +69,7 @@ For full instructions on deploying, view the [deployment docs](../docs/deploying
     $ gsutil mb -p ${PROJECT_ID} gs://${PROJECT_ID}-tf-state
     ```
 
-    Configurre Terraform to store state in the bucket:
+    Configure Terraform to store state in the bucket:
 
     ```text
     $ cat <<EOF > ./terraform/state.tf
@@ -111,3 +111,16 @@ project               = "..."
 cloudsql_tier         = "db-custom-1-3840"
 cloudsql_disk_size_gb = "16"
 ```
+
+### Changing Regions
+
+The target cloud region for each resource types are exposed as Terraform
+variables in `vars.tf`. Each region or location variable may be changed,
+however, they are not necessarily independent. The comments for each variable
+make a note of required dependencies and also link to the associated docs page
+listing the valid values.
+
+Note that not all resources used by this project are currently available in all
+regions, but bringing up infrastructure in different regions needs careful
+consideration as geographic location of resources does impact service
+performance.
diff --git a/terraform/database.tf b/terraform/database.tf
index e00d450..1404067 100644
--- a/terraform/database.tf
+++ b/terraform/database.tf
@@ -21,7 +21,7 @@ resource "random_string" "db-name" {
 
 resource "google_sql_database_instance" "db-inst" {
   project          = data.google_project.project.project_id
-  region           = var.region
+  region           = var.db_location
   database_version = "POSTGRES_11"
   name             = "en-${random_string.db-name.result}"
 
@@ -166,7 +166,7 @@ resource "null_resource" "migrate" {
       DB_USER        = google_sql_user.user.name
       COMMAND        = "up"
 
-      REGION   = var.region
+      REGION   = var.db_location
       SERVICES = "all"
       TAG      = "initial"
     }
diff --git a/terraform/key_management.tf b/terraform/key_management.tf
index 9ca4696..0932760 100644
--- a/terraform/key_management.tf
+++ b/terraform/key_management.tf
@@ -15,7 +15,7 @@
 resource "google_kms_key_ring" "export-signing" {
   project  = data.google_project.project.project_id
   name     = "export-signing"
-  location = var.region
+  location = var.kms_location
 
   depends_on = [
     google_project_service.services["cloudkms.googleapis.com"],
diff --git a/terraform/main.tf b/terraform/main.tf
index 5d3ac36..298452d 100644
--- a/terraform/main.tf
+++ b/terraform/main.tf
@@ -76,7 +76,7 @@ resource "google_service_networking_connection" "private_vpc_connection" {
 resource "google_vpc_access_connector" "connector" {
   project       = data.google_project.project.project_id
   name          = "serverless-vpc-connector"
-  region        = var.region
+  region        = var.network_location
   network       = "default"
   ip_cidr_range = "10.8.0.0/28"
 
@@ -91,7 +91,7 @@ resource "null_resource" "build" {
   provisioner "local-exec" {
     environment = {
       PROJECT_ID = data.google_project.project.project_id
-      REGION     = var.region
+      REGION     = var.cloudrun_location
       SERVICES   = "all"
       TAG        = "initial"
     }
@@ -172,6 +172,34 @@ output "region" {
   value = var.region
 }
 
+output "db_location" {
+  value = var.db_location
+}
+
+output "network_location" {
+  value = var.network_location
+}
+
+output "kms_location" {
+  value = var.kms_location
+}
+
+output "appengine_location" {
+  value = var.appengine_location
+}
+
+output "cloudscheduler_location" {
+  value = var.cloudscheduler_location
+}
+
+output "cloudrun_location" {
+  value = var.cloudrun_location
+}
+
+output "storage_location" {
+  value = var.storage_location
+}
+
 output "project" {
   value = data.google_project.project.project_id
 }
diff --git a/terraform/service_cleanup_export.tf b/terraform/service_cleanup_export.tf
index 9027cdf..f1c8000 100644
--- a/terraform/service_cleanup_export.tf
+++ b/terraform/service_cleanup_export.tf
@@ -55,7 +55,7 @@ resource "google_storage_bucket_iam_member" "cleanup-export-objectadmin" {
 
 resource "google_cloud_run_service" "cleanup-export" {
   name     = "cleanup-export"
-  location = var.region
+  location = var.cloudrun_location
 
   template {
     spec {
@@ -131,6 +131,7 @@ resource "google_cloud_run_service_iam_member" "cleanup-export-invoker" {
 
 resource "google_cloud_scheduler_job" "cleanup-export-worker" {
   name             = "cleanup-export-worker"
+  region           = var.cloudscheduler_location
   schedule         = "0 */6 * * *"
   time_zone        = "Etc/UTC"
   attempt_deadline = "600s"
diff --git a/terraform/service_cleanup_exposure.tf b/terraform/service_cleanup_exposure.tf
index f99bc10..bdf5a78 100644
--- a/terraform/service_cleanup_exposure.tf
+++ b/terraform/service_cleanup_exposure.tf
@@ -49,7 +49,7 @@ resource "google_secret_manager_secret_iam_member" "cleanup-exposure-db" {
 
 resource "google_cloud_run_service" "cleanup-exposure" {
   name     = "cleanup-exposure"
-  location = var.region
+  location = var.cloudrun_location
 
   template {
     spec {
@@ -125,6 +125,7 @@ resource "google_cloud_run_service_iam_member" "cleanup-exposure-invoker" {
 
 resource "google_cloud_scheduler_job" "cleanup-exposure-worker" {
   name             = "cleanup-exposure-worker"
+  region           = var.cloudscheduler_location
   schedule         = "0 */4 * * *"
   time_zone        = "Etc/UTC"
   attempt_deadline = "600s"
diff --git a/terraform/service_export.tf b/terraform/service_export.tf
index a1a7107..3b62ab8 100644
--- a/terraform/service_export.tf
+++ b/terraform/service_export.tf
@@ -61,7 +61,7 @@ resource "google_kms_key_ring_iam_member" "export-signerverifier" {
 
 resource "google_cloud_run_service" "export" {
   name     = "export"
-  location = var.region
+  location = var.cloudrun_location
 
   template {
     spec {
@@ -139,6 +139,7 @@ resource "google_cloud_run_service_iam_member" "export-invoker" {
 
 resource "google_cloud_scheduler_job" "export-worker" {
   name             = "export-worker"
+  region           = var.cloudscheduler_location
   schedule         = "* * * * *"
   time_zone        = "Etc/UTC"
   attempt_deadline = "600s"
@@ -165,6 +166,7 @@ resource "google_cloud_scheduler_job" "export-worker" {
 
 resource "google_cloud_scheduler_job" "export-create-batches" {
   name             = "export-create-batches"
+  region           = var.cloudscheduler_location
   schedule         = "*/5 * * * *"
   time_zone        = "Etc/UTC"
   attempt_deadline = "600s"
diff --git a/terraform/service_exposure.tf b/terraform/service_exposure.tf
index 7c28722..0ade75e 100644
--- a/terraform/service_exposure.tf
+++ b/terraform/service_exposure.tf
@@ -49,7 +49,7 @@ resource "google_secret_manager_secret_iam_member" "exposure-db" {
 
 resource "google_cloud_run_service" "exposure" {
   name     = "exposure"
-  location = var.region
+  location = var.cloudrun_location
 
   template {
     spec {
diff --git a/terraform/service_federationin.tf b/terraform/service_federationin.tf
index 7bda2b6..dbbb201 100644
--- a/terraform/service_federationin.tf
+++ b/terraform/service_federationin.tf
@@ -49,7 +49,7 @@ resource "google_secret_manager_secret_iam_member" "federationin" {
 
 resource "google_cloud_run_service" "federationin" {
   name     = "federationin"
-  location = var.region
+  location = var.cloudrun_location
 
   template {
     spec {
diff --git a/terraform/service_federationout.tf b/terraform/service_federationout.tf
index 5fcd400..088e5ce 100644
--- a/terraform/service_federationout.tf
+++ b/terraform/service_federationout.tf
@@ -49,7 +49,7 @@ resource "google_secret_manager_secret_iam_member" "federationout-db" {
 
 resource "google_cloud_run_service" "federationout" {
   name     = "federationout"
-  location = var.region
+  location = var.cloudrun_location
 
   template {
     spec {
diff --git a/terraform/service_gcr_cleaner.tf b/terraform/service_gcr_cleaner.tf
index 4a53f3c..3cd05bf 100644
--- a/terraform/service_gcr_cleaner.tf
+++ b/terraform/service_gcr_cleaner.tf
@@ -44,7 +44,7 @@ resource "google_storage_bucket_iam_member" "gcr-cleaner-objectadmin" {
 
 resource "google_cloud_run_service" "gcr-cleaner" {
   name     = "gcr-cleaner"
-  location = var.region
+  location = var.cloudrun_location
 
   template {
     spec {
diff --git a/terraform/service_generate.tf b/terraform/service_generate.tf
index a8d7a40..41dcb8f 100644
--- a/terraform/service_generate.tf
+++ b/terraform/service_generate.tf
@@ -49,7 +49,7 @@ resource "google_secret_manager_secret_iam_member" "generate-db" {
 
 resource "google_cloud_run_service" "generate" {
   name     = "generate"
-  location = var.region
+  location = var.cloudrun_location
 
   template {
     spec {
diff --git a/terraform/storage.tf b/terraform/storage.tf
index acc7372..78df25f 100644
--- a/terraform/storage.tf
+++ b/terraform/storage.tf
@@ -21,6 +21,7 @@ resource "random_string" "bucket-name" {
 
 resource "google_storage_bucket" "export" {
   project            = data.google_project.project.project_id
+  location           = var.storage_location
   name               = "exposure-notification-export-${random_string.bucket-name.result}"
   bucket_policy_only = true
 }
diff --git a/terraform/vars.tf b/terraform/vars.tf
index 8f9d1fe..38ae6fd 100644
--- a/terraform/vars.tf
+++ b/terraform/vars.tf
@@ -12,28 +12,71 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# The default region for resources in the project, individual resources should
+# have more specific variables defined to specify their region/location which
+# increases the flexibility of deployments
 variable "region" {
   type    = string
   default = "us-central1"
 }
 
+# The region in which to put the SQL DB: it is currently configured to use
+# PostgreSQL.
+# https://cloud.google.com/sql/docs/postgres/locations
+variable "db_location" {
+  type    = string
+  default = "us-central1"
+}
+
+# The region for the networking components.
+# https://cloud.google.com/compute/docs/regions-zones
+variable "network_location" {
+  type    = string
+  default = "us-central1"
+}
+
+# The region for the key management service.
+# https://cloud.google.com/kms/docs/locations
+variable "kms_location" {
+  type    = string
+  default = "us-central1"
+}
+
+# The location for the app engine; this implicitly defines the region for
+# scheduler jobs as specified by the cloudscheduler_location variable but the
+# values are sometimes different (as in the default values) so they are kept as
+# separate variables.
+# https://cloud.google.com/appengine/docs/locations
 variable "appengine_location" {
   type    = string
   default = "us-central"
 }
 
-variable "project" {
-  type = string
+# The cloudscheduler_location MUST use the same region as appengine_location but
+# it must include the region number even if this is omitted from the
+# appengine_location (as in the default values).
+variable "cloudscheduler_location" {
+  type    = string
+  default = "us-central1"
 }
 
-variable "repo_owner" {
+
+# The region in which cloudrun jobs are executed.
+# https://cloud.google.com/run/docs/locations
+variable "cloudrun_location" {
   type    = string
-  default = "google"
+  default = "us-central1"
 }
 
-variable "repo_name" {
+# The location holding the storage bucket for exported files.
+# https://cloud.google.com/storage/docs/locations
+variable "storage_location" {
   type    = string
-  default = "exposure-notifications-server"
+  default = "US"
+}
+
+variable "project" {
+  type = string
 }
 
 variable "cloudsql_tier" {


Test patch:
diff --git a/pkg/api/v1alpha1/verification_types_test.go b/pkg/api/v1alpha1/verification_types_test.go
new file mode 100644
index 0000000..97caf0e
--- /dev/null
+++ b/pkg/api/v1alpha1/verification_types_test.go
@@ -0,0 +1,38 @@
+// Copyright 2020 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.
+
+package v1alpha1
+
+import (
+	"sort"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestTransmissionRiskVectorSort(t *testing.T) {
+
+	got := TransmissionRiskVector{
+		{0, 0},
+		{3, 100},
+		{5, 200},
+	}
+	sort.Sort(got)
+
+	want := TransmissionRiskVector{{5, 200}, {3, 100}, {0, 0}}
+
+	if diff := cmp.Diff(want, got); diff != "" {
+		t.Errorf("sort(TransmissionRiskVector) mismatch (-want +got):\n%v", diff)
+	}
+}