diff --git a/infra/app/.terraform.lock.hcl b/infra/app/.terraform.lock.hcl index 32d1ef0..c1ad399 100644 --- a/infra/app/.terraform.lock.hcl +++ b/infra/app/.terraform.lock.hcl @@ -1,83 +1,84 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. +provider "registry.terraform.io/cyrilgdn/postgresql" { + version = "1.22.0" + constraints = "1.22.0" + hashes = [ + "h1:zQQKupK4vbHP90ESdqSdVVAXvI+cS2g9lE6DlUJ38+s=", + "zh:038df91e31bda2a9a1f33adc886bc2084793e151130e29909b92f18b91315724", + "zh:093328afc09cf13a5aed64daf9dc32d764e0a2b3c626c532e211fc22c08088f3", + "zh:108ae8321312085037b987bbe0a4a8e8aa34a763b5b73ad6d9a78d605fa8074d", + "zh:2ef4ae45a8df16ad3e3b2c8082a605354c5ca34015b1709123a911a98f93320d", + "zh:35e13819840c74d8c01fabbe124b44718364acea905066c68fd7ee7d3b4ffc09", + "zh:4eee265e184067d7574787cb682d7f5e339271adc91ec0026de1ab9700f24e39", + "zh:5defd2ac1825d25897125b21c929827dcd471cff7ba8e92c6ae006830bc7bb8c", + "zh:8ef0f114516bd83d87667472f90e609b2c033431d5c9d563db5ca2b87bad9726", + "zh:97ee6cf040e34f649ddc0554dca7db132f6bddae5defe133f21dffa01ece33fe", + "zh:982a831ee1346ae7790ddb6efa8f34297964d68f2910a1ce401505ea96c4bffa", + "zh:a8aefc6c22ff2ffff69d269ba180fdf021ba8510fa3a00683f356f3434657a4a", + "zh:b153499f77d00fae59c6f7d301fd6ffc3595143d540ed475ccf2747bcc1920ac", + "zh:b27e28690c8ca74b861f7ffccd98ada374c8a2cb0e200dbc028c679c009b5779", + "zh:d05579c9998a3efa36b3397a840676edfc8328a6075110d7365ac322f5ce268c", + ] +} + provider "registry.terraform.io/hashicorp/google" { - version = "4.67.0" + version = "5.32.0" + constraints = ">= 4.50.0, 5.32.0, < 6.0.0" hashes = [ - "h1:dC7iesMxJx2/jL8UgnlGgUwm+y1z37tcsL1zZAYX5To=", - "zh:0cdc7b724f115e9456e6c403c88ba68c36509bb92a717d20db3099b2e6c29968", - "zh:0feb6f8c5d9c064bf614cbe1bc1cc46aaf992bc1c8a95212a9d1ddad3da03d0a", - "zh:626503789c45350f7caed95fc02e67e505a582539c7fc5a1c6a4f3fb662534fc", - "zh:63b1dbc6c223edbdac45cd35e7222c8475d60359d2c1bf50d496f65bc003e3cc", - "zh:7b0e4386ae0e53e77ffaba18bdc482111a6aacf8b536cdf255e4bdc0056be91b", - "zh:7c48f857071c04efa7afb23bd11752d652ded4cbdf747a5f512dec828fcaba4c", - "zh:86c62e8332739d453502f268c3b8eac27454436ec56af2e95642ca18d9bf3502", - "zh:8748472a4c15dca832757ed5d9b618950bc0f9c36aadc1556fa2b94cb9c77225", - "zh:9c4314e89077e1656a3a54b8ef8648fdb0897b6658570d52dce2a53108829b55", - "zh:d506533c2961ae74e65e7908efc34148097470009851352bb77d240375fa628e", - "zh:e4068daf384b8ce45e79005d5696271c135546a245cf47554370d0b60c9ac93f", + "h1:JNE9VJhM4boCrLM0Jum1Mt/gprP+Bqb6bDpR27N0OFc=", + "zh:0cde8353183f6c700be6c50e5d41e7950aa081a542747cb7415c262c1e2665b6", + "zh:1e1db2d58e412142698d33f87c8a56f3146e028d11f59269b079186202d27052", + "zh:2a4b93110be8a7f25c351ccf186a431b59131d5a3d12159b8e3c9d5c3e3d77f0", + "zh:67f468743028f32cda193898d4d42ddfb7e0c820a4047243d5c6bf17ba3bc394", + "zh:7127b2ea83d034ce7fc1f87f4ba14ed42091a814ed08be0655f06a7a12378e0c", + "zh:9184dc2b092b49dd997c409fa11f307d1d839eb416671f81385527d9e0747adb", + "zh:9b7db4bfeac9131ad2829c0bab66cf54a526ce8e538775f3292d0294ce9a7b79", + "zh:a2a9d9f18ab9dfe1c27f74408a0c426c7c2563eec50c37bc18e14930888eb359", + "zh:ad080f92382ae4aa6ffbc73cbe7045ff741db0e35fdb02a37248d1f776190468", + "zh:d71a9ce6044cabf3cc60fd1c3af4fee2e5e57059658a66bdb6b303853c595241", + "zh:f46314fe3427a386e4a7599be48c5a419960aba1d7681f82d8a26056b9ca468c", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } provider "registry.terraform.io/hashicorp/google-beta" { - version = "5.17.0" + version = "5.32.0" constraints = ">= 4.50.0, < 6.0.0" hashes = [ - "h1:g0D9UoAD+6PnUHWTwzelN46a0Vhfvb1A3nbbIHb+x4k=", - "zh:14f5dc5273c66c5be4e9d8c0c25faa3654f8428a337b7c366222f7e25a023460", - "zh:19280f1d390b87ec08962356c44fbec65866d6c3394d73c5671f83e9cbd7ba5f", - "zh:2c8857afaf87bb34f9696ddb803ecdeb97158533e16164dec7d4fd55f08c222d", - "zh:2f0fcb5ea80191c6bea605b2a4f23a8be1dd52c2fcc0add652ed5d00300ab13b", - "zh:7523d307c93f5f5f51f5d27c4b3ea3a66abc86be43357bbb4c7629e53997662c", - "zh:889d7e1cb3638248898b30c656f512841a422416dc2467357c567b2d05e1fafb", - "zh:908fed36796c2e6f3fd71d6a9d00d97ae2224d4665984f5f2a4fdc158bfbc67d", - "zh:95a8d09c628f857bdfcc4ca2aadb029ebed2757b7c01ba005ccf5118080b1139", - "zh:b776986aceab76147b8be59ac34360a5cfa2014d83263e0b2c225452f11ce7c0", - "zh:f1d10391977e071fdc8027d15929ac70983759270b85a35e5e5ca5f232eac787", + "h1:5WDEY2oyfdKcrTaS6Xc9TV4fJ+n5Hll808doZPKpfSw=", + "zh:06e4a80af726a00eae0a2c12feffcd3808d5ffe84fedb29e87f835e19d07617a", + "zh:331f753851c9b10057db6e0d3d6cfa53a092716279c2797a6db6801b75ceb952", + "zh:416eeb0d2ffcb0fde174dca4c068b60d4d7c7cfedff5086623350eae9f750593", + "zh:4436286cfd9b9275a73178f466dc05559bd679063503ee9167b0556bde5d9b92", + "zh:55d4711ff8ef3cd6cd053b05cdf07e0687e2ec14395010150280b83e82c9c316", + "zh:5db932974a7bbdbef9cb149f036fdcacb89fb43fea1dbcf27d00a24fee8ffcf5", + "zh:78857d8ef733bcdddee9b5bec7484a096db1a5633a67f1f5ee0fa51e40a47434", + "zh:83922c66abe730a29ed483fe6bdce44069f9b0b33202cbddaf03a8e6a6bdff35", + "zh:da068d8a958c0f88192bc488f4143139b5bfae9441095dd84c2beec5478aa2cc", + "zh:da55bdb26436d9d82120c097ed22d8df7ba432735eb32e4ffa320704866d4521", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:fff1ad7a3608f7f7b9ee04eba0c2e1c125013296d016fc23daa081d7584a1f53", + "zh:fa980c70fe4aa908bbee8a1d50637132b935ec283530e433c415c77b6530ed5a", ] } provider "registry.terraform.io/hashicorp/random" { - version = "3.6.0" + version = "3.6.2" constraints = ">= 2.1.0" hashes = [ - "h1:R5Ucn26riKIEijcsiOMBR3uOAjuOMfI1x7XvH4P6B1w=", - "zh:03360ed3ecd31e8c5dac9c95fe0858be50f3e9a0d0c654b5e504109c2159287d", - "zh:1c67ac51254ba2a2bb53a25e8ae7e4d076103483f55f39b426ec55e47d1fe211", - "zh:24a17bba7f6d679538ff51b3a2f378cedadede97af8a1db7dad4fd8d6d50f829", - "zh:30ffb297ffd1633175d6545d37c2217e2cef9545a6e03946e514c59c0859b77d", - "zh:454ce4b3dbc73e6775f2f6605d45cee6e16c3872a2e66a2c97993d6e5cbd7055", + "h1:wmG0QFjQ2OfyPy6BB7mQ57WtoZZGGV07uAPQeDmIrAE=", + "zh:0ef01a4f81147b32c1bea3429974d4d104bbc4be2ba3cfa667031a8183ef88ec", + "zh:1bcd2d8161e89e39886119965ef0f37fcce2da9c1aca34263dd3002ba05fcb53", + "zh:37c75d15e9514556a5f4ed02e1548aaa95c0ecd6ff9af1119ac905144c70c114", + "zh:4210550a767226976bc7e57d988b9ce48f4411fa8a60cd74a6b246baf7589dad", + "zh:562007382520cd4baa7320f35e1370ffe84e46ed4e2071fdc7e4b1a9b1f8ae9b", + "zh:5efb9da90f665e43f22c2e13e0ce48e86cae2d960aaf1abf721b497f32025916", + "zh:6f71257a6b1218d02a573fc9bff0657410404fb2ef23bc66ae8cd968f98d5ff6", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:91df0a9fab329aff2ff4cf26797592eb7a3a90b4a0c04d64ce186654e0cc6e17", - "zh:aa57384b85622a9f7bfb5d4512ca88e61f22a9cea9f30febaa4c98c68ff0dc21", - "zh:c4a3e329ba786ffb6f2b694e1fd41d413a7010f3a53c20b432325a94fa71e839", - "zh:e2699bc9116447f96c53d55f2a00570f982e6f9935038c3810603572693712d0", - "zh:e747c0fd5d7684e5bfad8aa0ca441903f15ae7a98a737ff6aca24ba223207e2c", - "zh:f1ca75f417ce490368f047b63ec09fd003711ae48487fba90b4aba2ccf71920e", - ] -} - -provider "registry.terraform.io/kreuzwerker/docker" { - version = "3.0.2" - constraints = "3.0.2" - hashes = [ - "h1:cT2ccWOtlfKYBUE60/v2/4Q6Stk1KYTNnhxSck+VPlU=", - "zh:15b0a2b2b563d8d40f62f83057d91acb02cd0096f207488d8b4298a59203d64f", - "zh:23d919de139f7cd5ebfd2ff1b94e6d9913f0977fcfc2ca02e1573be53e269f95", - "zh:38081b3fe317c7e9555b2aaad325ad3fa516a886d2dfa8605ae6a809c1072138", - "zh:4a9c5065b178082f79ad8160243369c185214d874ff5048556d48d3edd03c4da", - "zh:5438ef6afe057945f28bce43d76c4401254073de01a774760169ac1058830ac2", - "zh:60b7fadc287166e5c9873dfe53a7976d98244979e0ab66428ea0dea1ebf33e06", - "zh:61c5ec1cb94e4c4a4fb1e4a24576d5f39a955f09afb17dab982de62b70a9bdd1", - "zh:a38fe9016ace5f911ab00c88e64b156ebbbbfb72a51a44da3c13d442cd214710", - "zh:c2c4d2b1fd9ebb291c57f524b3bf9d0994ff3e815c0cd9c9bcb87166dc687005", - "zh:d567bb8ce483ab2cf0602e07eae57027a1a53994aba470fa76095912a505533d", - "zh:e83bf05ab6a19dd8c43547ce9a8a511f8c331a124d11ac64687c764ab9d5a792", - "zh:e90c934b5cd65516fbcc454c89a150bfa726e7cf1fe749790c7480bbeb19d387", - "zh:f05f167d2eaf913045d8e7b88c13757e3cf595dd5cd333057fdafc7c4b7fed62", - "zh:fcc9c1cea5ce85e8bcb593862e699a881bd36dffd29e2e367f82d15368659c3d", + "zh:9647e18f221380a85f2f0ab387c68fdafd58af6193a932417299cdcae4710150", + "zh:bb6297ce412c3c2fa9fec726114e5e0508dd2638cad6a0cb433194930c97a544", + "zh:f83e925ed73ff8a5ef6e3608ad9225baa5376446349572c2449c0c0b3cf184b7", + "zh:fbef0781cb64de76b1df1ca11078aecba7800d82fd4a956302734999cfd9a4af", ] } diff --git a/infra/app/db.tf b/infra/app/db.tf new file mode 100644 index 0000000..de1f230 --- /dev/null +++ b/infra/app/db.tf @@ -0,0 +1,81 @@ +################################################################ +## Database server +################################################################ + +resource "random_password" "artist2d_db" { + length = 16 + special = false +} + +resource "google_sql_database_instance" "artist2d" { + name = "artist2d" + database_version = "POSTGRES_15" + region = var.region + root_password = random_password.artist2d_db.result + + settings { + tier = "db-f1-micro" + } + + deletion_protection = "false" +} + +################################################################ +## Logical Database -- the thing we connect to! +## And some user config +################################################################ + +resource "google_sql_database" "database" { + name = "artist" + instance = google_sql_database_instance.artist2d.name +} + + +resource "random_password" "storageapi_db" { + length = 16 + special = false +} + +resource "google_sql_user" "root" { + name = "postgres" + instance = google_sql_database_instance.artist2d.name + password = random_password.artist2d_db.result +} + +resource "google_sql_user" "storageapi" { + name = "storageapi" + instance = google_sql_database_instance.artist2d.name + password = random_password.storageapi_db.result +} + +resource "google_secret_manager_secret" "storageapi-db-pass" { + secret_id = "storageapi-db-pass" + + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "storageapi-current" { + secret = google_secret_manager_secret.storageapi-db-pass.id + + secret_data = random_password.storageapi_db.result +} + +resource "postgresql_grant" "storageapi" { + database = google_sql_database.database.name + role = google_sql_user.storageapi.name + schema = "public" + object_type = "database" + privileges = ["CONNECT", "CREATE", "TEMPORARY"] +} + +provider "postgresql" { + host = google_sql_database_instance.artist2d.ip_address.0.ip_address + port = 5432 + database = google_sql_database.database.name + username = google_sql_user.root.name + password = random_password.artist2d_db.result + sslmode = "require" + connect_timeout = 15 +} diff --git a/infra/app/main.tf b/infra/app/main.tf index cc95369..df66ab4 100644 --- a/infra/app/main.tf +++ b/infra/app/main.tf @@ -1,8 +1,12 @@ terraform { required_providers { - docker = { - source = "kreuzwerker/docker" - version = "3.0.2" + google = { + source = "hashicorp/google" + version = "5.32.0" + } + postgresql = { + source = "cyrilgdn/postgresql" + version = "1.22.0" } } backend "gcs" { @@ -25,10 +29,116 @@ data "google_client_config" "current" { } locals { - image_base = "${var.region}-docker.pkg.dev/${var.project}/${var.project}/" - client_tag = var.app_versions["client"] - client_image = "${local.image_base}client:${local.client_tag}" - painter_image = "${local.image_base}painterapi:${var.app_versions["painterapi"]}" - voting_tag = var.app_versions["votingapi"] - voting_image = "${local.image_base}votingapi:${local.voting_tag}" + image_base = "${var.region}-docker.pkg.dev/${var.project}/${var.project}/" + client_tag = var.app_versions["client"] + voting_tag = var.app_versions["votingapi"] + storageapi_tag = var.app_versions["storageapi"] +} + +################################################################ +## Storage API +################################################################ + +resource "google_service_account" "storageapi" { + account_id = "cloud-run-service-account" + display_name = "Service account for Cloud Run Storage API" +} + +resource "google_cloud_run_v2_service" "storageapi" { + name = "storageapi" + location = var.region + ingress = "INGRESS_TRAFFIC_INTERNAL_ONLY" + + template { + + volumes { + name = "cloudsql" + cloud_sql_instance { + instances = [google_sql_database_instance.artist2d.connection_name] + } + } + + containers { + image = "${local.image_base}storageapi:${local.storageapi_tag}" + # TODO: gunicorn + command = ["python", "manage.py", "runserver", "0.0.0.0:8000"] + env { + name = "ENV" + value = "prod" + } + env { + name = "GOOGLE_CLOUD_PROJECT" + value = var.project + } + env { + name = "POSTGRES_HOST" + value = "/cloudsql/${var.project}:${var.region}:${google_sql_database_instance.artist2d.name}" + } + env { + name = "SECRET_KEY" + value_source { + secret_key_ref { + secret = "django-secret-key" + version = "latest" + } + } + } + env { + name = "POSTGRES_USER" + value = "storageapi" + } + env { + name = "POSTGRES_PASSWORD" + value_source { + secret_key_ref { + secret = "storageapi-db-pass" + version = "latest" + } + } + } + volume_mounts { + name = "cloudsql" + mount_path = "/cloudsql" + } + } + service_account = google_service_account.storageapi.email + } +} + +resource "google_secret_manager_secret_iam_member" "storage-db-api" { + secret_id = google_secret_manager_secret.storageapi-db-pass.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.storageapi.email}" +} + +resource "google_project_iam_member" "cloudsql" { + project = var.project + role = "roles/cloudsql.client" + member = "serviceAccount:${google_service_account.storageapi.email}" +} + +## Django Secret Key secret. + +resource "random_password" "django-secret-key" { + length = 64 +} + +resource "google_secret_manager_secret" "django-secret-key" { + secret_id = "django-secret-key" + + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "django-secret-current" { + secret = google_secret_manager_secret.django-secret-key.id + + secret_data = random_password.django-secret-key.result +} + +resource "google_secret_manager_secret_iam_member" "django-secret-key" { + secret_id = google_secret_manager_secret.django-secret-key.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.storageapi.email}" } diff --git a/infra/app/networking.tf b/infra/app/networking.tf index 55c6b05..98de61a 100644 --- a/infra/app/networking.tf +++ b/infra/app/networking.tf @@ -69,7 +69,7 @@ resource "google_dns_record_set" "api-a" { rrdatas = [module.lb-http.external_ip] } -data "google_compute_address" "external_ip" { +data "google_compute_global_address" "external_ip" { name = "artist-address" } @@ -80,7 +80,7 @@ module "lb-http" { name = "artist" project = var.project - address = data.google_compute_address.external_ip.address + address = data.google_compute_global_address.external_ip.address ssl = var.ssl managed_ssl_certificate_domains = [var.domain, "${local.api_domain}"] https_redirect = var.ssl diff --git a/infra/app/output.tf b/infra/app/output.tf deleted file mode 100644 index 18c06dd..0000000 --- a/infra/app/output.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "image_base" { - value = local.image_base -} - -output "client_image" { - value = local.client_image -} - -output "painter_api_image" { - value = local.painter_image -} - -output "voting_api_image" { - value = local.voting_image -} diff --git a/infra/app/variables.tf b/infra/app/variables.tf index 4afe7d4..63fd178 100644 --- a/infra/app/variables.tf +++ b/infra/app/variables.tf @@ -4,6 +4,7 @@ variable "app_versions" { client : "0.1.0", votingapi : "0.1.1", painterapi : "0.1.0", + storageapi : "0.0.0kmddbtest", } } diff --git a/storageapi/storage/settings.py b/storageapi/storage/settings.py index 1c3e5c2..0178bb7 100644 --- a/storageapi/storage/settings.py +++ b/storageapi/storage/settings.py @@ -19,6 +19,8 @@ ENV = os.environ["ENV"] IS_PROD = ENV == "prod" +PROJECT = os.environ.get("GOOGLE_CLOUD_PROJECT", None) +REGION = os.environ.get("GOOGLE_CLOUD_REGION", "us-west1") SECRET_KEY = os.environ["SECRET_KEY"] SECRET_KEY_FALLBACKS = [ # Put old secret keys here when rotating. Remove promptly! @@ -33,7 +35,7 @@ SECURE_SSL_REDIRECT = True SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") else: - assert not IS_PROD + #assert not IS_PROD ALLOWED_HOSTS = ["*"] @@ -80,10 +82,6 @@ WSGI_APPLICATION = 'storage.wsgi.application' -# Database -# https://docs.djangoproject.com/en/5.0/ref/settings/#databases - - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', @@ -95,11 +93,6 @@ } } -# If the flag as been set, configure to use proxy -#if os.getenv("USE_CLOUD_SQL_AUTH_PROXY", None): -# DATABASES["default"]["HOST"] = "127.0.0.1" -# DATABASES["default"]["PORT"] = 5432 - # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators