Repository: google/exposure-notifications-server
Pull request #1097 (2020-10-17 17:43:44)

Terraform db instance explicitly depends_on db, cert and user
When terraform destroys database instance, it could happen that database instance was deleted ahead of db, cert and user, which were left orphaned and terraform didn't know how to handle them, and this prevents terraform smoke test from succeeding. Example error:

```
Error: Error when reading or editing SQL User "notification" in instance "en-server-kbcnp": googleapi: Error 400: Invalid request: Invalid request since instance is not running., invalid
```

https://prow.k8s.io/view/gcs/oss-prow/logs/ci-en-server-terraform-smoke/1317495728049754112#1:build-log.txt%3A227

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

This pull-request has been approved by: *<a href="https://github.com/google/exposure-notifications-server/pull/1097#" title="Author self-approved">chaodaiG</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:

- ~~[terraform/OWNERS](https://github.com/google/exposure-notifications-server/blob/main/terraform/OWNERS)~~ [chaodaiG]

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":[]} -->
chaodaiG: /cc @sethvargo 
Found the solution from https://github.com/hashicorp/terraform/issues/20196#issuecomment-502648235, and don't have a good way to test this out. Seth please advise with your terraform expertise.

Patch:
diff --git a/internal/efgs/regions.go b/internal/efgs/regions.go
new file mode 100644
index 0000000..fc79589
--- /dev/null
+++ b/internal/efgs/regions.go
@@ -0,0 +1,272 @@
+// 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 efgs contains utilities for interacting eith the EU Federation Gateway.
+package efgs
+
+var (
+	// TravelerRegions represents regions a key has been in, if the traveler flag is true.
+	// Data source: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
+	TravelerRegions = []string{
+		"AD", //	Andorra
+		"AE", //	United Arab Emirates
+		"AF", //	Afghanistan
+		"AG", //	Antigua and Barbuda
+		"AI", //	Anguilla
+		"AL", //	Albania
+		"AM", //	Armenia
+		"AO", //	Angola
+		"AQ", //	Antarctica
+		"AR", //	Argentina
+		"AS", //	American Samoa
+		"AT", //	Austria
+		"AU", //	Australia
+		"AW", //	Aruba
+		"AX", //	Åland Islands
+		"AZ", //	Azerbaijan
+		"BA", //	Bosnia and Herzegovina
+		"BB", //	Barbados
+		"BD", //	Bangladesh
+		"BE", //	Belgium
+		"BF", //	Burkina Faso
+		"BG", //	Bulgaria
+		"BH", //	Bahrain
+		"BI", //	Burundi
+		"BJ", //	Benin
+		"BL", //	Saint Barthélemy
+		"BM", //	Bermuda
+		"BN", //	Brunei Darussalam
+		"BO", //	Bolivia (Plurinational State of)
+		"BQ", //	Bonaire, Sint Eustatius and Saba
+		"BR", //	Brazil
+		"BS", //	Bahamas
+		"BT", //	Bhutan
+		"BV", //	Bouvet Island
+		"BW", //	Botswana
+		"BY", //	Belarus
+		"BZ", //	Belize
+		"CA", //	Canada
+		"CC", //	Cocos (Keeling) Islands
+		"CD", //	Congo, Democratic Republic of the
+		"CF", //	Central African Republic
+		"CG", //	Congo
+		"CH", //	Switzerland
+		"CI", //	Côte d'Ivoire
+		"CK", //	Cook Islands
+		"CL", //	Chile
+		"CM", //	Cameroon
+		"CN", //	China
+		"CO", //	Colombia
+		"CR", //	Costa Rica
+		"CU", //	Cuba
+		"CV", //	Cabo Verde
+		"CW", //	Curaçao
+		"CX", //	Christmas Island
+		"CY", //	Cyprus
+		"CZ", //	Czechia
+		"DE", //	Germany
+		"DJ", //	Djibouti
+		"DK", //	Denmark
+		"DM", //	Dominica
+		"DO", //	Dominican Republic
+		"DZ", //	Algeria
+		"EC", //	Ecuador
+		"EE", //	Estonia
+		"EG", //	Egypt
+		"EH", //	Western Sahara
+		"ER", //	Eritrea
+		"ES", //	Spain
+		"ET", //	Ethiopia
+		"FI", //	Finland
+		"FJ", //	Fiji
+		"FK", //	Falkland Islands (Malvinas)
+		"FM", //	Micronesia (Federated States of)
+		"FO", //	Faroe Islands
+		"FR", //	France
+		"GA", //	Gabon
+		"GB", //	United Kingdom of Great Britain and Northern Ireland
+		"GD", //	Grenada
+		"GE", //	Georgia
+		"GF", //	French Guiana
+		"GG", //	Guernsey
+		"GH", //	Ghana
+		"GI", //	Gibraltar
+		"GL", //	Greenland
+		"GM", //	Gambia
+		"GN", //	Guinea
+		"GP", //	Guadeloupe
+		"GQ", //	Equatorial Guinea
+		"GR", //	Greece
+		"GS", //	South Georgia and the South Sandwich Islands
+		"GT", //	Guatemala
+		"GU", //	Guam
+		"GW", //	Guinea-Bissau
+		"GY", //	Guyana
+		"HK", //	Hong Kong
+		"HM", //	Heard Island and McDonald Islands
+		"HN", //	Honduras
+		"HR", //	Croatia
+		"HT", //	Haiti
+		"HU", //	Hungary
+		"ID", //	Indonesia
+		"IE", //	Ireland
+		"IL", //	Israel
+		"IM", //	Isle of Man
+		"IN", //	India
+		"IO", //	British Indian Ocean Territory
+		"IQ", //	Iraq
+		"IR", //	Iran (Islamic Republic of)
+		"IS", //	Iceland
+		"IT", //	Italy
+		"JE", //	Jersey
+		"JM", //	Jamaica
+		"JO", //	Jordan
+		"JP", //	Japan
+		"KE", //	Kenya
+		"KG", //	Kyrgyzstan
+		"KH", //	Cambodia
+		"KI", //	Kiribati
+		"KM", //	Comoros
+		"KN", //	Saint Kitts and Nevis
+		"KP", //	Korea (Democratic People's Republic of)
+		"KR", //	Korea, Republic of
+		"KW", //	Kuwait
+		"KY", //	Cayman Islands
+		"KZ", //	Kazakhstan
+		"LA", //	Lao People's Democratic Republic
+		"LB", //	Lebanon
+		"LC", //	Saint Lucia
+		"LI", //	Liechtenstein
+		"LK", //	Sri Lanka
+		"LR", //	Liberia
+		"LS", //	Lesotho
+		"LT", //	Lithuania
+		"LU", //	Luxembourg
+		"LV", //	Latvia
+		"LY", //	Libya
+		"MA", //	Morocco
+		"MC", //	Monaco
+		"MD", //	Moldova, Republic of
+		"ME", //	Montenegro
+		"MF", //	Saint Martin (French part)
+		"MG", //	Madagascar
+		"MH", //	Marshall Islands
+		"MK", //	North Macedonia
+		"ML", //	Mali
+		"MM", //	Myanmar
+		"MN", //	Mongolia
+		"MO", //	Macao
+		"MP", //	Northern Mariana Islands
+		"MQ", //	Martinique
+		"MR", //	Mauritania
+		"MS", //	Montserrat
+		"MT", //	Malta
+		"MU", //	Mauritius
+		"MV", //	Maldives
+		"MW", //	Malawi
+		"MX", //	Mexico
+		"MY", //	Malaysia
+		"MZ", //	Mozambique
+		"NA", //	Namibia
+		"NC", //	New Caledonia
+		"NE", //	Niger
+		"NF", //	Norfolk Island
+		"NG", //	Nigeria
+		"NI", //	Nicaragua
+		"NL", //	Netherlands
+		"NO", //	Norway
+		"NP", //	Nepal
+		"NR", //	Nauru
+		"NU", //	Niue
+		"NZ", //	New Zealand
+		"OM", //	Oman
+		"PA", //	Panama
+		"PE", //	Peru
+		"PF", //	French Polynesia
+		"PG", //	Papua New Guinea
+		"PH", //	Philippines
+		"PK", //	Pakistan
+		"PL", //	Poland
+		"PM", //	Saint Pierre and Miquelon
+		"PN", //	Pitcairn
+		"PR", //	Puerto Rico
+		"PS", //	Palestine, State of
+		"PT", //	Portugal
+		"PW", //	Palau
+		"PY", //	Paraguay
+		"QA", //	Qatar
+		"RE", //	Réunion
+		"RO", //	Romania
+		"RS", //	Serbia
+		"RU", //	Russian Federation
+		"RW", //	Rwanda
+		"SA", //	Saudi Arabia
+		"SB", //	Solomon Islands
+		"SC", //	Seychelles
+		"SD", //	Sudan
+		"SE", //	Sweden
+		"SG", //	Singapore
+		"SH", //	Saint Helena, Ascension and Tristan da Cunha
+		"SI", //	Slovenia
+		"SJ", //	Svalbard and Jan Mayen
+		"SK", //	Slovakia
+		"SL", //	Sierra Leone
+		"SM", //	San Marino
+		"SN", //	Senegal
+		"SO", //	Somalia
+		"SR", //	Suriname
+		"SS", //	South Sudan
+		"ST", //	Sao Tome and Principe
+		"SV", //	El Salvador
+		"SX", //	Sint Maarten (Dutch part)
+		"SY", //	Syrian Arab Republic
+		"SZ", //	Eswatini
+		"TC", //	Turks and Caicos Islands
+		"TD", //	Chad
+		"TF", //	French Southern Territories
+		"TG", //	Togo
+		"TH", //	Thailand
+		"TJ", //	Tajikistan
+		"TK", //	Tokelau
+		"TL", //	Timor-Leste
+		"TM", //	Turkmenistan
+		"TN", //	Tunisia
+		"TO", //	Tonga
+		"TR", //	Turkey
+		"TT", //	Trinidad and Tobago
+		"TV", //	Tuvalu
+		"TW", //	Taiwan, Province of China
+		"TZ", //	Tanzania, United Republic of
+		"UA", //	Ukraine
+		"UG", //	Uganda
+		"UM", //	United States Minor Outlying Islands
+		"US", //	United States of America
+		"UY", //	Uruguay
+		"UZ", //	Uzbekistan
+		"VA", //	Holy See
+		"VC", //	Saint Vincent and the Grenadines
+		"VE", //	Venezuela (Bolivarian Republic of)
+		"VG", //	Virgin Islands (British)
+		"VI", //	Virgin Islands (U.S.)
+		"VN", //	Viet Nam
+		"VU", //	Vanuatu
+		"WF", //	Wallis and Futuna
+		"WS", //	Samoa
+		"YE", //	Yemen
+		"YT", //	Mayotte
+		"ZA", //	South Africa
+		"ZM", //	Zambia
+		"ZW", //	Zimbabwe
+	}
+)
diff --git a/internal/export/database/export.go b/internal/export/database/export.go
index 3dc9638..0cab273 100644
--- a/internal/export/database/export.go
+++ b/internal/export/database/export.go
@@ -49,20 +49,27 @@ func (db *ExportDB) AddExportConfig(ctx context.Context, ec *model.ExportConfig)
 
 	thru := db.db.NullableTime(ec.Thru)
 	return db.db.InTx(ctx, pgx.Serializable, func(tx pgx.Tx) error {
+		efgs := &ec.EFGSConfig
 		row := tx.QueryRow(ctx, `
 			INSERT INTO
 				ExportConfig
-				(bucket_name, filename_root, period_seconds, output_region, from_timestamp, thru_timestamp, signature_info_ids, input_regions, include_travelers, exclude_regions, only_non_travelers)
+				(bucket_name, filename_root, period_seconds, output_region, from_timestamp, thru_timestamp,
+				 signature_info_ids, input_regions, include_travelers, exclude_regions, only_non_travelers,
+				  efgs_export, efgs_upload_host, efgs_mtls_cert_secret, efgs_mtls_key_secret,
+				  efgs_signing_cert_secret, efgs_signing_key_secret)
 			VALUES
-				($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
+				($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
 			RETURNING config_id
 		`, ec.BucketName, ec.FilenameRoot, int(ec.Period.Seconds()), ec.OutputRegion,
 			ec.From, thru, ec.SignatureInfoIDs, ec.InputRegions, ec.IncludeTravelers,
-			ec.ExcludeRegions, ec.OnlyNonTravelers)
+			ec.ExcludeRegions, ec.OnlyNonTravelers,
+			efgs.Enabled, efgs.UploadHost, efgs.MTLSCertSecret, efgs.MTLSKeySecret,
+			efgs.SigningCertSecret, efgs.SigningKeySecret)
 
 		if err := row.Scan(&ec.ConfigID); err != nil {
 			return fmt.Errorf("fetching config_id: %w", err)
 		}
+
 		return nil
 	})
 }
@@ -79,11 +86,18 @@ func (db *ExportDB) UpdateExportConfig(ctx context.Context, ec *model.ExportConf
 			UPDATE
 				ExportConfig
 			SET
-				bucket_name = $1, filename_root = $2, period_seconds = $3, output_region = $4, from_timestamp = $5, thru_timestamp = $6, signature_info_ids = $7, input_regions = $8, include_travelers = $9, exclude_regions = $10, only_non_travelers = $11
-			WHERE config_id = $12
+				bucket_name = $1, filename_root = $2, period_seconds = $3, output_region = $4, from_timestamp = $5,
+				thru_timestamp = $6, signature_info_ids = $7, input_regions = $8, include_travelers = $9,
+				exclude_regions = $10, only_non_travelers = $11,
+				efgs_export = $12, efgs_upload_host = $13, efgs_mtls_cert_secret = $14, efgs_mtls_key_secret = $15,
+				efgs_signing_cert_secret = $16, efgs_signing_key_secret = $17
+			WHERE config_id = $18
 		`, ec.BucketName, ec.FilenameRoot, int(ec.Period.Seconds()), ec.OutputRegion,
 			ec.From, thru, ec.SignatureInfoIDs, ec.InputRegions, ec.IncludeTravelers,
-			ec.ExcludeRegions, ec.OnlyNonTravelers, ec.ConfigID)
+			ec.ExcludeRegions, ec.OnlyNonTravelers, ec.EFGSConfig.Enabled, ec.EFGSConfig.UploadHost,
+			ec.EFGSConfig.MTLSCertSecret, ec.EFGSConfig.MTLSKeySecret,
+			ec.EFGSConfig.SigningCertSecret, ec.EFGSConfig.SigningKeySecret,
+			ec.ConfigID)
 		if err != nil {
 			return fmt.Errorf("updating signatureinfo: %w", err)
 		}
@@ -99,7 +113,11 @@ func (db *ExportDB) GetExportConfig(ctx context.Context, id int64) (*model.Expor
 	if err := db.db.InTx(ctx, pgx.ReadCommitted, func(tx pgx.Tx) error {
 		row := tx.QueryRow(ctx, `
 			SELECT
-				config_id, bucket_name, filename_root, period_seconds, output_region, from_timestamp, thru_timestamp, signature_info_ids, input_regions, include_travelers, exclude_regions, only_non_travelers
+				config_id, bucket_name, filename_root, period_seconds, output_region,
+				from_timestamp, thru_timestamp, signature_info_ids, input_regions,
+				include_travelers, exclude_regions, only_non_travelers,
+				efgs_export, efgs_upload_host, efgs_mtls_cert_secret, efgs_mtls_key_secret,
+				efgs_signing_cert_secret, efgs_signing_key_secret
 			FROM
 				ExportConfig
 			WHERE
@@ -125,7 +143,11 @@ func (db *ExportDB) GetAllExportConfigs(ctx context.Context) ([]*model.ExportCon
 	if err := db.db.InTx(ctx, pgx.ReadCommitted, func(tx pgx.Tx) error {
 		rows, err := tx.Query(ctx, `
 			SELECT
-				config_id, bucket_name, filename_root, period_seconds, output_region, from_timestamp, thru_timestamp, signature_info_ids, input_regions, include_travelers, exclude_regions, only_non_travelers
+				config_id, bucket_name, filename_root, period_seconds, output_region,
+				from_timestamp, thru_timestamp, signature_info_ids, input_regions, include_travelers,
+				exclude_regions, only_non_travelers,
+				efgs_export, efgs_upload_host, efgs_mtls_cert_secret, efgs_mtls_key_secret,
+				efgs_signing_cert_secret, efgs_signing_key_secret	
 			FROM
 				ExportConfig
 			ORDER BY config_id
@@ -162,7 +184,11 @@ func (db *ExportDB) IterateExportConfigs(ctx context.Context, t time.Time, f fun
 	if err := db.db.InTx(ctx, pgx.ReadCommitted, func(tx pgx.Tx) error {
 		rows, err := tx.Query(ctx, `
 			SELECT
-				config_id, bucket_name, filename_root, period_seconds, output_region, from_timestamp, thru_timestamp, signature_info_ids, input_regions, include_travelers, exclude_regions, only_non_travelers
+				config_id, bucket_name, filename_root, period_seconds, output_region,
+				from_timestamp, thru_timestamp, signature_info_ids, input_regions, include_travelers,
+				exclude_regions, only_non_travelers,
+				efgs_export, efgs_upload_host, efgs_mtls_cert_secret, efgs_mtls_key_secret,
+				efgs_signing_cert_secret, efgs_signing_key_secret
 			FROM
 				ExportConfig
 			WHERE
@@ -204,7 +230,10 @@ func scanOneExportConfig(row pgx.Row) (*model.ExportConfig, error) {
 		periodSeconds int
 		thru          *time.Time
 	)
-	if err := row.Scan(&m.ConfigID, &m.BucketName, &m.FilenameRoot, &periodSeconds, &outputRegion, &m.From, &thru, &m.SignatureInfoIDs, &m.InputRegions, &m.IncludeTravelers, &m.ExcludeRegions, &m.OnlyNonTravelers); err != nil {
+	if err := row.Scan(&m.ConfigID, &m.BucketName, &m.FilenameRoot, &periodSeconds, &outputRegion, &m.From, &thru,
+		&m.SignatureInfoIDs, &m.InputRegions, &m.IncludeTravelers, &m.ExcludeRegions, &m.OnlyNonTravelers,
+		&m.EFGSConfig.Enabled, &m.EFGSConfig.UploadHost, &m.EFGSConfig.MTLSCertSecret, &m.EFGSConfig.MTLSKeySecret,
+		&m.EFGSConfig.SigningCertSecret, &m.EFGSConfig.SigningKeySecret); err != nil {
 		return nil, err
 	}
 
diff --git a/internal/export/model/export_model.go b/internal/export/model/export_model.go
index ae61db8..89c7e1a 100644
--- a/internal/export/model/export_model.go
+++ b/internal/export/model/export_model.go
@@ -32,6 +32,16 @@ const (
 	oneDay = 24 * time.Hour
 )
 
+// EFGSUploadConfig ties a specific export config to EFGS Upload Config
+type EFGSUploadConfig struct {
+	Enabled           bool
+	UploadHost        string
+	MTLSCertSecret    string
+	MTLSKeySecret     string
+	SigningCertSecret string
+	SigningKeySecret  string
+}
+
 type ExportConfig struct {
 	ConfigID         int64
 	BucketName       string
@@ -45,6 +55,8 @@ type ExportConfig struct {
 	From             time.Time
 	Thru             time.Time
 	SignatureInfoIDs []int64
+	// Data related to EFGS
+	EFGSConfig EFGSUploadConfig
 }
 
 // EffectiveInputRegions either returns `InputRegions` or if that array is
diff --git a/internal/integration/helpers.go b/internal/integration/helpers.go
index b9714e9..a565548 100644
--- a/internal/integration/helpers.go
+++ b/internal/integration/helpers.go
@@ -252,7 +252,7 @@ func Seed(tb testing.TB, ctx context.Context, db *database.DB, exportPeriod time
 	bucketName := testRandomID(tb, 32)
 	filenameRoot := testRandomID(tb, 32)
 
-	if v := os.Getenv("GOOGLE_CLOUD_BUCKET"); v != "" && !testing.Short() {
+	if v := os.Getenv("E2E_GOOGLE_CLOUD_BUCKET"); v != "" && !testing.Short() {
 		bucketName = v
 		tb.Logf("Bucket name is %q", bucketName)
 	}
diff --git a/migrations/000047_AddEFGSUploadConfig.down.sql b/migrations/000047_AddEFGSUploadConfig.down.sql
new file mode 100644
index 0000000..954ed05
--- /dev/null
+++ b/migrations/000047_AddEFGSUploadConfig.down.sql
@@ -0,0 +1,27 @@
+-- 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.
+
+BEGIN;
+
+DROP TABLE EFGSUploadConfig;
+
+ALTER TABLE ExportConfig
+    DROP COLUMN efgs_export,
+    DROP COLUMN efgs_upload_host,
+    DROP COLUMN efgs_mtls_cert_secret,
+    DROP COLUMN efgs_mtls_key_secret,
+    DROP COLUMN efgs_signing_cert_secret,
+    DROP COLUMN efgs_signing_key_secret;
+
+END;
diff --git a/migrations/000047_AddEFGSUploadConfig.up.sql b/migrations/000047_AddEFGSUploadConfig.up.sql
new file mode 100644
index 0000000..76dfc1a
--- /dev/null
+++ b/migrations/000047_AddEFGSUploadConfig.up.sql
@@ -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.
+
+BEGIN;
+
+-- Allow for batches to be federated out. 
+ALTER TABLE ExportBatch
+    ADD COLUMN efgs_status exportbatchstatus DEFAULT 'OPEN';
+
+-- Mark a specific export config as needing to be federated UP to EFGS.
+ALTER TABLE ExportConfig
+    ADD COLUMN efgs_export BOOLEAN DEFAULT false,
+    ADD COLUMN efgs_upload_host VARCHAR(250) DEFAULT '',
+    ADD COLUMN efgs_mtls_cert_secret VARCHAR(250) DEFAULT '',
+    ADD COLUMN efgs_mtls_key_secret VARCHAR(250) DEFAULT '',
+    ADD COLUMN efgs_signing_cert_secret VARCHAR(250) DEFAULT '',
+    ADD COLUMN efgs_signing_key_secret VARCHAR(250) DEFAULT '';
+
+ALTER TABLE ExportConfig
+    ALTER COLUMN efgs_export SET NOT NULL,
+    ALTER COLUMN efgs_upload_host SET NOT NULL,
+    ALTER COLUMN efgs_mtls_cert_secret SET NOT NULL,
+    ALTER COLUMN efgs_mtls_key_secret SET NOT NULL,
+    ALTER COLUMN efgs_signing_cert_secret SET NOT NULL,
+    ALTER COLUMN efgs_signing_key_secret SET NOT NULL;
+
+END;
diff --git a/terraform/database.tf b/terraform/database.tf
index 8c38d36..592a949 100644
--- a/terraform/database.tf
+++ b/terraform/database.tf
@@ -64,6 +64,11 @@ resource "google_sql_database_instance" "db-inst" {
 
   depends_on = [
     google_project_service.services["sql-component.googleapis.com"],
+    # Explicitly label dependencies of db instance, so that destroy won't
+    # delete db instance first, and leave the following resources orphaned.
+    google_sql_database.db,
+    google_sql_ssl_cert.db-cert,
+    google_sql_user.user,
   ]
 }
 


Test patch:
diff --git a/internal/export/database/export_test.go b/internal/export/database/export_test.go
index 5c42bad..5475ce3 100644
--- a/internal/export/database/export_test.go
+++ b/internal/export/database/export_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/google/exposure-notifications-server/internal/export/model"
 	publishdb "github.com/google/exposure-notifications-server/internal/publish/database"
 	publishmodel "github.com/google/exposure-notifications-server/internal/publish/model"
+
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	pgx "github.com/jackc/pgx/v4"
@@ -166,6 +167,12 @@ func TestAddGetUpdateExportConfig(t *testing.T) {
 	want.Thru = time.Time{}
 	want.SignatureInfoIDs = []int64{1, 2, 3, 4, 5}
 	want.InputRegions = []string{"US", "CA"}
+	want.EFGSConfig.Enabled = true
+	want.EFGSConfig.UploadHost = "https://somwehere.else.com/"
+	want.EFGSConfig.MTLSCertSecret = "secret-ref/mtls/cert"
+	want.EFGSConfig.MTLSKeySecret = "secret-ref/mtls/key"
+	want.EFGSConfig.SigningCertSecret = "secret-ref/signing/cert"
+	want.EFGSConfig.SigningKeySecret = "secret-ref/signing/key"
 
 	if err := exportDB.UpdateExportConfig(ctx, want); err != nil {
 		t.Fatal(err)