From fe26d51b9c6e049ace1cfc72928497a6d7e582be Mon Sep 17 00:00:00 2001 From: felipmiguel Date: Sat, 5 Aug 2023 23:51:29 +0200 Subject: [PATCH 1/6] mysql working. psql pending fix for createing user --- infra/core/main.tf | 18 +++---- infra/core/modules/container-apps/main.tf | 50 ++++++++++++------- infra/core/modules/container-apps/outputs.tf | 12 ++++- .../core/modules/container-apps/variables.tf | 20 ++------ infra/core/modules/mysql/main.tf | 3 ++ infra/core/modules/mysql/outputs.tf | 20 ++++++-- infra/core/modules/postgresql/outputs.tf | 20 ++++++-- infra/core/outputs.tf | 39 +++++++++++++-- infra/create-user-mysql.sh | 20 ++++++++ infra/create-user-pgsql.sh | 23 +++++++++ infra/deploy-all.sh | 26 ++++++++++ src/{api => }/Dockerfile | 0 src/api/Program.cs | 31 ++++++++++-- src/api/appsettings.Development.json | 7 ++- src/api/appsettings.json | 7 ++- 15 files changed, 229 insertions(+), 67 deletions(-) create mode 100755 infra/create-user-mysql.sh create mode 100755 infra/create-user-pgsql.sh create mode 100755 infra/deploy-all.sh rename src/{api => }/Dockerfile (100%) diff --git a/infra/core/main.tf b/infra/core/main.tf index d37a325..d75e5b0 100644 --- a/infra/core/main.tf +++ b/infra/core/main.tf @@ -39,17 +39,13 @@ resource "azurerm_resource_group" "main" { } module "application" { - source = "./modules/container-apps" - resource_group = azurerm_resource_group.main.name - application_name = var.application_name - environment = local.environment - location = var.location - - pgsql_database_url = module.pgsql_database.database_url - pgsql_database_username = module.pgsql_database.database_username - - mysql_database_url = module.mysql_database.database_url - mysql_database_username = module.mysql_database.database_username + source = "./modules/container-apps" + resource_group = azurerm_resource_group.main.name + application_name = var.application_name + environment = local.environment + location = var.location + pgsql_connection_string = module.pgsql_database.database_dotnet_connection_string + mysql_connection_string = module.mysql_database.database_dotnet_connection_string } module "pgsql_database" { diff --git a/infra/core/modules/container-apps/main.tf b/infra/core/modules/container-apps/main.tf index cf03c3b..329f934 100644 --- a/infra/core/modules/container-apps/main.tf +++ b/infra/core/modules/container-apps/main.tf @@ -7,6 +7,12 @@ terraform { } } +locals { + database_login_name = var.application_name + mysql_connection_string = "${var.mysql_connection_string};UserID=${local.database_login_name};" + pgsql_connection_string = "${var.pgsql_connection_string};User Id=${local.database_login_name};" +} + resource "azurecaf_name" "container_registry" { name = var.application_name resource_type = "azurerm_container_registry" @@ -92,19 +98,19 @@ resource "azurerm_container_app" "pgsql_application" { identity { type = "UserAssigned" - identity_ids = [ + identity_ids = [ azurerm_user_assigned_identity.msi_container_app.id - ] + ] } registry { identity = azurerm_user_assigned_identity.msi_container_app.id - server = azurerm_container_registry.container_registry.login_server + server = azurerm_container_registry.container_registry.login_server } ingress { external_enabled = true - target_port = 8080 + target_port = 80 traffic_weight { percentage = 100 latest_revision = true @@ -114,16 +120,20 @@ resource "azurerm_container_app" "pgsql_application" { template { container { name = azurecaf_name.pgsql_application.result - image = "ghcr.io/microsoft/nubesgen/nubesgen-native:main" + image = "${azurerm_container_registry.container_registry.name}.azurecr.io/todoapi:latest" cpu = 0.25 memory = "0.5Gi" env { - name = "DATABASE_URL" - value = var.pgsql_database_url + name = "PgSqlConnection" + value = local.pgsql_connection_string + } + env { + name = "TargetDb" + value = "Postgresql" } env { - name = "DATABASE_USERNAME" - value = var.pgsql_database_username + name = "UserAssignedManagedClientId" + value = azurerm_user_assigned_identity.msi_container_app.client_id } } min_replicas = 1 @@ -150,19 +160,19 @@ resource "azurerm_container_app" "mysql_application" { identity { type = "UserAssigned" - identity_ids = [ + identity_ids = [ azurerm_user_assigned_identity.msi_container_app.id - ] + ] } registry { identity = azurerm_user_assigned_identity.msi_container_app.id - server = azurerm_container_registry.container_registry.login_server + server = azurerm_container_registry.container_registry.login_server } ingress { external_enabled = true - target_port = 8080 + target_port = 80 traffic_weight { percentage = 100 latest_revision = true @@ -172,16 +182,20 @@ resource "azurerm_container_app" "mysql_application" { template { container { name = azurecaf_name.mysql_application.result - image = "ghcr.io/microsoft/nubesgen/nubesgen-native:main" + image = "${azurerm_container_registry.container_registry.name}.azurecr.io/todoapi:latest" cpu = 0.25 memory = "0.5Gi" env { - name = "DATABASE_URL" - value = var.mysql_database_url + name = "MySqlConnection" + value = local.mysql_connection_string + } + env { + name = "TargetDb" + value = "MySql" } env { - name = "DATABASE_USERNAME" - value = var.mysql_database_username + name = "UserAssignedManagedClientId" + value = azurerm_user_assigned_identity.msi_container_app.client_id } } min_replicas = 1 diff --git a/infra/core/modules/container-apps/outputs.tf b/infra/core/modules/container-apps/outputs.tf index 35f00a7..028c7e1 100644 --- a/infra/core/modules/container-apps/outputs.tf +++ b/infra/core/modules/container-apps/outputs.tf @@ -9,6 +9,16 @@ output "application_mysql_name" { } output "container_registry_name" { - value = azurecaf_name.container_registry.result + value = azurerm_container_registry.container_registry.name description = "The Docker Container Registry name generated by the Azure Cloud Adoption Framework." } + +output "container_apps_identity" { + value = azurerm_user_assigned_identity.msi_container_app.id + description = "The Managed Identity assigned to container apps" +} + + +output "database_login_name" { + value = local.database_login_name +} diff --git a/infra/core/modules/container-apps/variables.tf b/infra/core/modules/container-apps/variables.tf index 1424ed7..f1c9b34 100644 --- a/infra/core/modules/container-apps/variables.tf +++ b/infra/core/modules/container-apps/variables.tf @@ -19,22 +19,12 @@ variable "location" { description = "The Azure region where all resources in this example should be created" } -variable "pgsql_database_url" { +variable "pgsql_connection_string" { type = string - description = "The URL to the database" + description = "The connection string to postgresql database" } -variable "pgsql_database_username" { +variable "mysql_connection_string" { type = string - description = "The database username" -} - -variable "mysql_database_url" { - type = string - description = "The URL to the database" -} - -variable "mysql_database_username" { - type = string - description = "The database username" -} + description = "The connection string to mysql database" +} \ No newline at end of file diff --git a/infra/core/modules/mysql/main.tf b/infra/core/modules/mysql/main.tf index 0a5acea..dead0b4 100644 --- a/infra/core/modules/mysql/main.tf +++ b/infra/core/modules/mysql/main.tf @@ -80,6 +80,9 @@ resource "azurerm_mysql_flexible_database" "database" { server_name = azurerm_mysql_flexible_server.database.name charset = "utf8" collation = "utf8_unicode_ci" + lifecycle { + ignore_changes = [charset, collation] + } } resource "azurecaf_name" "mysql_firewall_rule" { diff --git a/infra/core/modules/mysql/outputs.tf b/infra/core/modules/mysql/outputs.tf index c3dd062..2e640ed 100644 --- a/infra/core/modules/mysql/outputs.tf +++ b/infra/core/modules/mysql/outputs.tf @@ -1,9 +1,23 @@ -output "database_url" { - value = "${azurerm_mysql_flexible_server.database.name}.mysql.database.azure.com:3306/${azurerm_mysql_flexible_database.database.name}" - description = "The MySQL server URL." +output "database_fqdn" { + value = azurerm_mysql_flexible_server.database.fqdn + description = "The MySQL server FQDN." +} + +output "database_server_name" { + value = azurerm_mysql_flexible_server.database.name + description = "The MySQL server name." +} + +output "database_name" { + value = azurerm_mysql_flexible_database.database.name + description = "The MySQL database name." } output "database_username" { value = azurerm_mysql_flexible_server_active_directory_administrator.aad_admin.login description = "The MySQL server user name." } + +output "database_dotnet_connection_string" { + value= "Server=${azurerm_mysql_flexible_server.database.fqdn};Database=${azurerm_mysql_flexible_database.database.name};SslMode=Required" +} diff --git a/infra/core/modules/postgresql/outputs.tf b/infra/core/modules/postgresql/outputs.tf index 3dadc4c..1098437 100644 --- a/infra/core/modules/postgresql/outputs.tf +++ b/infra/core/modules/postgresql/outputs.tf @@ -1,9 +1,23 @@ -output "database_url" { - value = "${azurerm_postgresql_flexible_server.database.fqdn}:5432/${azurerm_postgresql_flexible_server_database.database.name}" - description = "The PostgreSQL server URL." +output "database_fqdn" { + value = azurerm_postgresql_flexible_server.database.fqdn + description = "The PostgreSQL server FQDN." +} + +output "database_server_name" { + value = azurerm_postgresql_flexible_server.database.name + description = "The PostgreSQL server name." +} + +output "database_name" { + value = azurerm_postgresql_flexible_server_database.database.name + description = "The PostgreSQL database name." } output "database_username" { value = azurerm_postgresql_flexible_server_active_directory_administrator.aad_admin.principal_name description = "The PostgreSQL server user name." } + +output "database_dotnet_connection_string" { + value= "Server=${azurerm_postgresql_flexible_server.database.fqdn};Database=${azurerm_postgresql_flexible_server_database.database.name};Port=5432;Ssl Mode=Require;Trust Server Certificate=true" +} \ No newline at end of file diff --git a/infra/core/outputs.tf b/infra/core/outputs.tf index 799f893..294092f 100644 --- a/infra/core/outputs.tf +++ b/infra/core/outputs.tf @@ -3,8 +3,13 @@ output "resource_group" { description = "The resource group." } -output "pgsql_server_url" { - value = module.pgsql_database.database_url +output "pgsql_server_fqdn" { + value = module.pgsql_database.database_fqdn + description = "Postgresql Database server url" +} + +output "pgsql_server_name" { + value = module.pgsql_database.database_server_name description = "Postgresql Database server url" } @@ -13,8 +18,13 @@ output "pgsql_user_name" { description = "The PostgreSQL user name" } -output "mysql_server_url" { - value = module.mysql_database.database_url +output "mysql_server_name" { + value = module.mysql_database.database_server_name + description = "MySql Database server url" +} + +output "mysql_server_fqdn" { + value = module.mysql_database.database_fqdn description = "MySql Database server url" } @@ -25,5 +35,24 @@ output "mysql_user_name" { output "container_registry_name" { value = module.application.container_registry_name - description = "The Docker Container Registry name generated by the Azure Cloud Adoption Framework." + description = "The Docker Container Registry name." +} + +output "container_apps_identity" { + value = module.application.container_apps_identity + description = "The Managed Identity identifier assigned to container apps" +} + +output "mysql_database_name" { + value = module.mysql_database.database_name + description = "The MySql database name" +} + +output "pgsql_database_name" { + value = module.pgsql_database.database_name + description = "The PostgreSQL database name" +} + +output "msi_database_login_name" { + value = module.application.database_login_name } diff --git a/infra/create-user-mysql.sh b/infra/create-user-mysql.sh new file mode 100755 index 0000000..02dafa0 --- /dev/null +++ b/infra/create-user-mysql.sh @@ -0,0 +1,20 @@ +MYSQL_SERVER=$1 +DATABASE_NAME=$2 +APPLICATION_LOGIN_NAME=$3 +APPLICATION_IDENTITY_APPID=$4 +ADMIN_USER=$5 + +az extension add --name rdbms-connect --upgrade + +echo 'Getting password for current user' +ADMIN_PASSWORD=$(az account get-access-token --resource-type oss-rdbms -o tsv --query accessToken) + +cat < mysqluser.sql +SET aad_auth_validate_oids_in_tenant = OFF; +DROP USER IF EXISTS '${APPLICATION_LOGIN_NAME}'@'%'; +CREATE AADUSER '${APPLICATION_LOGIN_NAME}' IDENTIFIED BY '${APPLICATION_IDENTITY_APPID}'; +GRANT ALL PRIVILEGES ON ${DATABASE_NAME}.* TO '${APPLICATION_LOGIN_NAME}'@'%'; +FLUSH privileges; +EOF +az mysql flexible-server execute --name ${MYSQL_SERVER} --file-path mysqluser.sql --admin-password "${ADMIN_PASSWORD}" --admin-user "${ADMIN_USER}" --verbose +rm mysqluser.sql diff --git a/infra/create-user-pgsql.sh b/infra/create-user-pgsql.sh new file mode 100755 index 0000000..51d776e --- /dev/null +++ b/infra/create-user-pgsql.sh @@ -0,0 +1,23 @@ +PGSQL_SERVER=$1 +DATABASE_NAME=$2 +APPLICATION_LOGIN_NAME=$3 +APPLICATION_IDENTITY_APPID=$4 +ADMIN_USER=$5 + +az extension add --name rdbms-connect --upgrade + +echo 'Getting password for current user' +ADMIN_PASSWORD=$(az account get-access-token --resource-type oss-rdbms -o tsv --query accessToken) + +cat < pgsqluser.sql +SET aad_validate_oids_in_tenant = off; +REVOKE ALL PRIVILEGES ON DATABASE "${DATABASE_NAME}" FROM "${APPLICATION_LOGIN_NAME}"; +DROP USER IF EXISTS "${APPLICATION_LOGIN_NAME}"; +CREATE ROLE "${APPLICATION_LOGIN_NAME}" WITH LOGIN PASSWORD '${APPLICATION_IDENTITY_APPID}' IN ROLE azure_ad_user; +GRANT ALL PRIVILEGES ON DATABASE "${DATABASE_NAME}" TO "${APPLICATION_LOGIN_NAME}"; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "${APPLICATION_LOGIN_NAME}"; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "${APPLICATION_LOGIN_NAME}"; +EOF + +az postgres flexible-server execute --name ${PGSQL_SERVER} --file-path pgsqluser.sql --admin-password "${ADMIN_PASSWORD}" --admin-user "${ADMIN_USER}" --verbose +rm pgsqluser.sql diff --git a/infra/deploy-all.sh b/infra/deploy-all.sh new file mode 100755 index 0000000..b01a485 --- /dev/null +++ b/infra/deploy-all.sh @@ -0,0 +1,26 @@ +cd core +terraform init +terraform apply -auto-approve + +ACR_NAME=$(terraform output -raw container_registry_name) +MYSQL_SERVER=$(terraform output -raw mysql_server_name) +MYSQL_DATABASE_NAME=$(terraform output -raw mysql_database_name) +MYSQL_ADMIN_USER=$(terraform output -raw mysql_user_name) +PGSQL_SERVER=$(terraform output -raw pgsql_server_name) +PGSQL_DATABASE_NAME=$(terraform output -raw pgsql_database_name) +PGSQL_ADMIN_USER=$(terraform output -raw pgsql_user_name) +MSI_LOGIN_NAME=$(terraform output -raw msi_database_login_name) +MSI_CONTAINER_IDENTITY=$(terraform output -raw container_apps_identity) + + +APPLICATION_IDENTITY_APPID=$(az identity show --id "${MSI_CONTAINER_IDENTITY}" -o tsv --query clientId) +cd .. +# create mysq login for managed identity +./create-user-mysql.sh $MYSQL_SERVER $MYSQL_DATABASE_NAME $MSI_LOGIN_NAME $APPLICATION_IDENTITY_APPID $MYSQL_ADMIN_USER + +# create postgresql login for managed identity +./create-user-pgsql.sh $PGSQL_SERVER $PGSQL_DATABASE_NAME $MSI_LOGIN_NAME $APPLICATION_IDENTITY_APPID $PGSQL_ADMIN_USER + +# create docker image for the app +cd ../src +az acr build -t $ACR_NAME.azurecr.io/todoapi:latest -t $ACR_NAME.azurecr.io/todoapi:1.0.0 -r $ACR_NAME . diff --git a/src/api/Dockerfile b/src/Dockerfile similarity index 100% rename from src/api/Dockerfile rename to src/Dockerfile diff --git a/src/api/Program.cs b/src/api/Program.cs index eb89104..df43a51 100644 --- a/src/api/Program.cs +++ b/src/api/Program.cs @@ -1,3 +1,4 @@ +using Azure.Core; using Azure.Identity; using Microsoft.EntityFrameworkCore; using Pomelo.EntityFrameworkCore.MySql.Infrastructure; @@ -7,19 +8,32 @@ builder.Services.AddScoped(); builder.Services.AddDbContext(options => { + DefaultAzureCredential azureCredential; + if (string.IsNullOrEmpty(builder.Configuration["UserAssignedManagedClientId"])) + { + azureCredential = new DefaultAzureCredential(); + } + else + { + azureCredential = new DefaultAzureCredential(new DefaultAzureCredentialOptions + { + ManagedIdentityClientId = builder.Configuration["UserAssignedManagedClientId"] + }); + } + switch (builder.Configuration["TargetDb"]) { case "MySql": - string mysqlConnString = builder.Configuration.GetConnectionString("MySqlConnection") ?? throw new InvalidOperationException("MySqlConnection must be set in the configuration"); + string mysqlConnString = builder.Configuration["MySqlConnection"] ?? throw new InvalidOperationException("MySqlConnection must be set in the configuration"); var serverVersion = ServerVersion.Parse("5.7", ServerType.MySql); options .UseMySql(mysqlConnString, serverVersion) - .UseAzureADAuthentication(new DefaultAzureCredential()); + .UseAzureADAuthentication(azureCredential); break; case "Postgresql": - string npgConnString = builder.Configuration.GetConnectionString("PgSqlConnection") ?? throw new InvalidOperationException("PgSqlConnection must be set in the configuration"); + string npgConnString = builder.Configuration["PgSqlConnection"] ?? throw new InvalidOperationException("PgSqlConnection must be set in the configuration"); options - .UseNpgsql(npgConnString, options => options.UseAzureADAuthentication(new DefaultAzureCredential())); + .UseNpgsql(npgConnString, options => options.UseAzureADAuthentication(azureCredential)); break; default: throw new InvalidOperationException("TargetDb must be set to either MySql or Postgresql"); @@ -34,7 +48,14 @@ await using (var scope = app.Services.CreateAsyncScope()) { var db = scope.ServiceProvider.GetRequiredService(); - await db.Database.EnsureCreatedAsync(); + try + { + await db.Database.EnsureCreatedAsync(); + } + catch (Exception ex) + { + Console.WriteLine("Error ensuring database created: {0}", ex.Message); + } } app.UseCors(policy => diff --git a/src/api/appsettings.Development.json b/src/api/appsettings.Development.json index 0c208ae..8525ab1 100644 --- a/src/api/appsettings.Development.json +++ b/src/api/appsettings.Development.json @@ -4,5 +4,8 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } -} + }, + "TargetDb": "Postgresql", + "MySqlConnection": "Server=\"mysql-batec-ossrdbms-demo-dev.mysql.database.azure.com\";UserID = \"fmiguel_outlook.com#EXT#@fmigueloutlook.onmicrosoft.com\";Database=\"db\";SslMode=Required;", + "PgSqlConnection": "Server=psqlf-batec-ossrdbms-demo-dev.postgres.database.azure.com;Database=;Port=5432;User Id=fmiguel_outlook.com#EXT#@fmigueloutlook.onmicrosoft.com;Ssl Mode=Require;Trust Server Certificate=true" +} \ No newline at end of file diff --git a/src/api/appsettings.json b/src/api/appsettings.json index 1d43c9b..4c7837c 100644 --- a/src/api/appsettings.json +++ b/src/api/appsettings.json @@ -6,9 +6,8 @@ } }, "AllowedHosts": "*", + "UserAssignedManagedClientId": "", "TargetDb": "Postgresql", - "ConnectionStrings": { - "MySqlConnection": "Server=\"mysql-batec-ossrdbms-demo-dev.mysql.database.azure.com\";UserID = \"fmiguel_outlook.com#EXT#@fmigueloutlook.onmicrosoft.com\";Database=\"db\";SslMode=Required;", - "PgSqlConnection": "Server=psqlf-batec-ossrdbms-demo-dev.postgres.database.azure.com;Database=;Port=5432;User Id=fmiguel_outlook.com#EXT#@fmigueloutlook.onmicrosoft.com;Ssl Mode=Require;Trust Server Certificate=true" - } + "MySqlConnection": "", + "PgSqlConnection": "" } From 3b26fde81aedf625aac2499ae48f01f73aec53b5 Mon Sep 17 00:00:00 2001 From: felipmiguel Date: Sun, 6 Aug 2023 00:10:10 +0200 Subject: [PATCH 2/6] fix pgsql. executing ef update during deployment process --- infra/create-user-pgsql.sh | 5 +---- infra/deploy-all.sh | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/infra/create-user-pgsql.sh b/infra/create-user-pgsql.sh index 51d776e..948ad02 100755 --- a/infra/create-user-pgsql.sh +++ b/infra/create-user-pgsql.sh @@ -10,10 +10,7 @@ echo 'Getting password for current user' ADMIN_PASSWORD=$(az account get-access-token --resource-type oss-rdbms -o tsv --query accessToken) cat < pgsqluser.sql -SET aad_validate_oids_in_tenant = off; -REVOKE ALL PRIVILEGES ON DATABASE "${DATABASE_NAME}" FROM "${APPLICATION_LOGIN_NAME}"; -DROP USER IF EXISTS "${APPLICATION_LOGIN_NAME}"; -CREATE ROLE "${APPLICATION_LOGIN_NAME}" WITH LOGIN PASSWORD '${APPLICATION_IDENTITY_APPID}' IN ROLE azure_ad_user; +select * from pgaadauth_create_principal_with_oid('${APPLICATION_LOGIN_NAME}', '${APPLICATION_IDENTITY_APPID}', 'service', false, false); GRANT ALL PRIVILEGES ON DATABASE "${DATABASE_NAME}" TO "${APPLICATION_LOGIN_NAME}"; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "${APPLICATION_LOGIN_NAME}"; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "${APPLICATION_LOGIN_NAME}"; diff --git a/infra/deploy-all.sh b/infra/deploy-all.sh index b01a485..92d6d75 100755 --- a/infra/deploy-all.sh +++ b/infra/deploy-all.sh @@ -21,6 +21,27 @@ cd .. # create postgresql login for managed identity ./create-user-pgsql.sh $PGSQL_SERVER $PGSQL_DATABASE_NAME $MSI_LOGIN_NAME $APPLICATION_IDENTITY_APPID $PGSQL_ADMIN_USER +# create database schema using ef tools +cd ../src/repo.mysql +cat < appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=${MYSQL_SERVER}.mysql.database.azure.com;UserID=${MYSQL_ADMIN_USER};Database=${MYSQL_DATABASE_NAME};SslMode=Required;" + } +} +EOF +dotnet ef database update + +cd ../repo.pgsql +cat < appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "Server=${PGSQL_SERVER}.postgres.database.azure.com;User Id=${PGSQL_ADMIN_USER};Database=${PGSQL_DATABASE_NAME};Ssl Mode=Require;Port=5432;Trust Server Certificate=true" + } +} +EOF +dotnet ef database update + # create docker image for the app -cd ../src +cd .. az acr build -t $ACR_NAME.azurecr.io/todoapi:latest -t $ACR_NAME.azurecr.io/todoapi:1.0.0 -r $ACR_NAME . From 147aca10873ae9995dfd62beb9cc6b938a802e30 Mon Sep 17 00:00:00 2001 From: felipmiguel Date: Tue, 8 Aug 2023 16:49:01 +0200 Subject: [PATCH 3/6] adding random on name to avoid colisions --- infra/core/modules/container-apps/main.tf | 1 + infra/core/modules/mysql/main.tf | 1 + infra/core/modules/postgresql/main.tf | 1 + 3 files changed, 3 insertions(+) diff --git a/infra/core/modules/container-apps/main.tf b/infra/core/modules/container-apps/main.tf index 329f934..b77c39d 100644 --- a/infra/core/modules/container-apps/main.tf +++ b/infra/core/modules/container-apps/main.tf @@ -16,6 +16,7 @@ locals { resource "azurecaf_name" "container_registry" { name = var.application_name resource_type = "azurerm_container_registry" + random_length = 3 suffixes = [var.environment] } diff --git a/infra/core/modules/mysql/main.tf b/infra/core/modules/mysql/main.tf index dead0b4..f666d32 100644 --- a/infra/core/modules/mysql/main.tf +++ b/infra/core/modules/mysql/main.tf @@ -22,6 +22,7 @@ resource "azurerm_user_assigned_identity" "mysql_umi" { resource "azurecaf_name" "mysql_server" { name = var.application_name resource_type = "azurerm_mysql_server" + random_length = 3 suffixes = [var.environment] } diff --git a/infra/core/modules/postgresql/main.tf b/infra/core/modules/postgresql/main.tf index 81fe13c..2e33f2a 100644 --- a/infra/core/modules/postgresql/main.tf +++ b/infra/core/modules/postgresql/main.tf @@ -19,6 +19,7 @@ data "azurerm_client_config" "current_client" { resource "azurecaf_name" "postgresql_server" { name = var.application_name resource_type = "azurerm_postgresql_flexible_server" + random_length = 3 suffixes = [var.environment] } From dabaa5a21124f3db1605a577c5d110503cc44bd1 Mon Sep 17 00:00:00 2001 From: felipmiguel Date: Tue, 8 Aug 2023 22:54:50 +0200 Subject: [PATCH 4/6] Adding RetryOnFailure --- src/api/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/Program.cs b/src/api/Program.cs index df43a51..aec5144 100644 --- a/src/api/Program.cs +++ b/src/api/Program.cs @@ -27,13 +27,13 @@ string mysqlConnString = builder.Configuration["MySqlConnection"] ?? throw new InvalidOperationException("MySqlConnection must be set in the configuration"); var serverVersion = ServerVersion.Parse("5.7", ServerType.MySql); options - .UseMySql(mysqlConnString, serverVersion) + .UseMySql(mysqlConnString, serverVersion, options => options.EnableRetryOnFailure()) .UseAzureADAuthentication(azureCredential); break; case "Postgresql": string npgConnString = builder.Configuration["PgSqlConnection"] ?? throw new InvalidOperationException("PgSqlConnection must be set in the configuration"); options - .UseNpgsql(npgConnString, options => options.UseAzureADAuthentication(azureCredential)); + .UseNpgsql(npgConnString, options => options.UseAzureADAuthentication(azureCredential).EnableRetryOnFailure()); break; default: throw new InvalidOperationException("TargetDb must be set to either MySql or Postgresql"); From 408a719b462bad522a69a23219ef175dc3a5e464 Mon Sep 17 00:00:00 2001 From: felipmiguel Date: Tue, 8 Aug 2023 22:56:29 +0200 Subject: [PATCH 5/6] Moving container app creation out of Terraform --- infra/core/modules/container-apps/main.tf | 118 ------------------- infra/core/modules/container-apps/outputs.tf | 20 ++-- infra/core/outputs.tf | 4 + infra/deploy-all.sh | 28 ++++- 4 files changed, 41 insertions(+), 129 deletions(-) diff --git a/infra/core/modules/container-apps/main.tf b/infra/core/modules/container-apps/main.tf index b77c39d..825e2bd 100644 --- a/infra/core/modules/container-apps/main.tf +++ b/infra/core/modules/container-apps/main.tf @@ -84,121 +84,3 @@ resource "azurecaf_name" "pgsql_application" { resource_type = "azurerm_container_app" suffixes = [var.environment, "pgsql"] } - -resource "azurerm_container_app" "pgsql_application" { - name = azurecaf_name.pgsql_application.result - container_app_environment_id = azurerm_container_app_environment.application.id - resource_group_name = var.resource_group - revision_mode = "Single" - - lifecycle { - ignore_changes = [ - template.0.container["image"] - ] - } - - identity { - type = "UserAssigned" - identity_ids = [ - azurerm_user_assigned_identity.msi_container_app.id - ] - } - - registry { - identity = azurerm_user_assigned_identity.msi_container_app.id - server = azurerm_container_registry.container_registry.login_server - } - - ingress { - external_enabled = true - target_port = 80 - traffic_weight { - percentage = 100 - latest_revision = true - } - } - - template { - container { - name = azurecaf_name.pgsql_application.result - image = "${azurerm_container_registry.container_registry.name}.azurecr.io/todoapi:latest" - cpu = 0.25 - memory = "0.5Gi" - env { - name = "PgSqlConnection" - value = local.pgsql_connection_string - } - env { - name = "TargetDb" - value = "Postgresql" - } - env { - name = "UserAssignedManagedClientId" - value = azurerm_user_assigned_identity.msi_container_app.client_id - } - } - min_replicas = 1 - } -} - -resource "azurecaf_name" "mysql_application" { - name = var.application_name - resource_type = "azurerm_container_app" - suffixes = [var.environment, "mysql"] -} - -resource "azurerm_container_app" "mysql_application" { - name = azurecaf_name.mysql_application.result - container_app_environment_id = azurerm_container_app_environment.application.id - resource_group_name = var.resource_group - revision_mode = "Single" - - lifecycle { - ignore_changes = [ - template.0.container["image"] - ] - } - - identity { - type = "UserAssigned" - identity_ids = [ - azurerm_user_assigned_identity.msi_container_app.id - ] - } - - registry { - identity = azurerm_user_assigned_identity.msi_container_app.id - server = azurerm_container_registry.container_registry.login_server - } - - ingress { - external_enabled = true - target_port = 80 - traffic_weight { - percentage = 100 - latest_revision = true - } - } - - template { - container { - name = azurecaf_name.mysql_application.result - image = "${azurerm_container_registry.container_registry.name}.azurecr.io/todoapi:latest" - cpu = 0.25 - memory = "0.5Gi" - env { - name = "MySqlConnection" - value = local.mysql_connection_string - } - env { - name = "TargetDb" - value = "MySql" - } - env { - name = "UserAssignedManagedClientId" - value = azurerm_user_assigned_identity.msi_container_app.client_id - } - } - min_replicas = 1 - } -} diff --git a/infra/core/modules/container-apps/outputs.tf b/infra/core/modules/container-apps/outputs.tf index 028c7e1..e519131 100644 --- a/infra/core/modules/container-apps/outputs.tf +++ b/infra/core/modules/container-apps/outputs.tf @@ -1,12 +1,12 @@ -output "application_pgsql_name" { - value = azurecaf_name.pgsql_application.result - description = "The application name generated by the Azure Cloud Adoption Framework." -} +# output "application_pgsql_name" { +# value = azurecaf_name.pgsql_application.result +# description = "The application name generated by the Azure Cloud Adoption Framework." +# } -output "application_mysql_name" { - value = azurecaf_name.mysql_application.result - description = "The application name generated by the Azure Cloud Adoption Framework." -} +# output "application_mysql_name" { +# value = azurecaf_name.mysql_application.result +# description = "The application name generated by the Azure Cloud Adoption Framework." +# } output "container_registry_name" { value = azurerm_container_registry.container_registry.name @@ -22,3 +22,7 @@ output "container_apps_identity" { output "database_login_name" { value = local.database_login_name } + +output "container_environment_name" { + value = azurerm_container_app_environment.application.name +} diff --git a/infra/core/outputs.tf b/infra/core/outputs.tf index 294092f..407b47b 100644 --- a/infra/core/outputs.tf +++ b/infra/core/outputs.tf @@ -56,3 +56,7 @@ output "pgsql_database_name" { output "msi_database_login_name" { value = module.application.database_login_name } + +output "container_environment_name" { + value = module.application.container_environment_name +} diff --git a/infra/deploy-all.sh b/infra/deploy-all.sh index 92d6d75..3958588 100755 --- a/infra/deploy-all.sh +++ b/infra/deploy-all.sh @@ -11,9 +11,14 @@ PGSQL_DATABASE_NAME=$(terraform output -raw pgsql_database_name) PGSQL_ADMIN_USER=$(terraform output -raw pgsql_user_name) MSI_LOGIN_NAME=$(terraform output -raw msi_database_login_name) MSI_CONTAINER_IDENTITY=$(terraform output -raw container_apps_identity) - +CONTAINER_APP_ENVIRONMENT=$(terraform output -raw container_environment_name) +RESOURCE_GROUP=$(terraform output -raw resource_group) APPLICATION_IDENTITY_APPID=$(az identity show --id "${MSI_CONTAINER_IDENTITY}" -o tsv --query clientId) + +MYSQL_CONNECTION_STRING="Server=${MYSQL_SERVER}.mysql.database.azure.com;Database=${MYSQL_DATABASE_NAME};SslMode=Required" +PGSQL_CONNECTION_STRING="Server=${PGSQL_SERVER}.postgres.database.azure.com;Database=${PGSQL_DATABASE_NAME};Ssl Mode=Require;Port=5432;Trust Server Certificate=true" + cd .. # create mysq login for managed identity ./create-user-mysql.sh $MYSQL_SERVER $MYSQL_DATABASE_NAME $MSI_LOGIN_NAME $APPLICATION_IDENTITY_APPID $MYSQL_ADMIN_USER @@ -26,7 +31,7 @@ cd ../src/repo.mysql cat < appsettings.json { "ConnectionStrings": { - "DefaultConnection": "Server=${MYSQL_SERVER}.mysql.database.azure.com;UserID=${MYSQL_ADMIN_USER};Database=${MYSQL_DATABASE_NAME};SslMode=Required;" + "DefaultConnection": "${MYSQL_CONNECTION_STRING};UserID=${MYSQL_ADMIN_USER};" } } EOF @@ -36,7 +41,7 @@ cd ../repo.pgsql cat < appsettings.json { "ConnectionStrings": { - "DefaultConnection": "Server=${PGSQL_SERVER}.postgres.database.azure.com;User Id=${PGSQL_ADMIN_USER};Database=${PGSQL_DATABASE_NAME};Ssl Mode=Require;Port=5432;Trust Server Certificate=true" + "DefaultConnection": "${PGSQL_CONNECTION_STRING};User Id=${PGSQL_ADMIN_USER};" } } EOF @@ -45,3 +50,20 @@ dotnet ef database update # create docker image for the app cd .. az acr build -t $ACR_NAME.azurecr.io/todoapi:latest -t $ACR_NAME.azurecr.io/todoapi:1.0.0 -r $ACR_NAME . + + +az containerapp create -n mysqlapi -g ${RESOURCE_GROUP} \ + --image ${ACR_NAME}.azurecr.io/todoapi:1.0.0 --environment ${CONTAINER_APP_ENVIRONMENT} \ + --ingress external --target-port 80 \ + --registry-server ${ACR_NAME}.azurecr.io --registry-identity "${MSI_CONTAINER_IDENTITY}" \ + --user-assigned ${MSI_CONTAINER_IDENTITY} \ + --cpu 0.25 --memory 0.5Gi \ + --env-vars TargetDb="MySql" MySqlConnection="${MYSQL_CONNECTION_STRING};UserID=${MSI_LOGIN_NAME};" UserAssignedManagedClientId="${APPLICATION_IDENTITY_APPID}" + +az containerapp create -n pgsqlapi -g ${RESOURCE_GROUP} \ + --image ${ACR_NAME}.azurecr.io/todoapi:1.0.0 --environment ${CONTAINER_APP_ENVIRONMENT} \ + --ingress external --target-port 80 \ + --registry-server ${ACR_NAME}.azurecr.io --registry-identity "${MSI_CONTAINER_IDENTITY}" \ + --user-assigned ${MSI_CONTAINER_IDENTITY} \ + --cpu 0.25 --memory 0.5Gi \ + --env-vars TargetDb="Postgresql" PgSqlConnection="${PGSQL_CONNECTION_STRING};User Id=${MSI_LOGIN_NAME};" UserAssignedManagedClientId="${APPLICATION_IDENTITY_APPID}" \ No newline at end of file From 2ab44210bcf69a288ad2b7df1dc44f4e8118e420 Mon Sep 17 00:00:00 2001 From: felipmiguel Date: Wed, 9 Aug 2023 23:53:28 +0200 Subject: [PATCH 6/6] working psql --- infra/core/appsettings.json | 5 ++ infra/core/main.tf | 14 +++++- .../modules/application-insights/README.md | 8 ++++ .../core/modules/application-insights/main.tf | 26 ++++++++++ .../modules/application-insights/outputs.tf | 9 ++++ .../modules/application-insights/variables.tf | 23 +++++++++ infra/core/outputs.tf | 7 ++- infra/create-user-pgsql.sh | 5 ++ infra/deploy-all.sh | 48 +++++++++++++++---- 9 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 infra/core/appsettings.json create mode 100644 infra/core/modules/application-insights/README.md create mode 100644 infra/core/modules/application-insights/main.tf create mode 100644 infra/core/modules/application-insights/outputs.tf create mode 100644 infra/core/modules/application-insights/variables.tf diff --git a/infra/core/appsettings.json b/infra/core/appsettings.json new file mode 100644 index 0000000..8055c0e --- /dev/null +++ b/infra/core/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=psqlf-batec-ossrdbms-demo-uak-dev.postgres.database.azure.com;Database=psqlfdb-batec-ossrdbms-demo-dev;Ssl Mode=Require;Port=5432;Trust Server Certificate=true;User Id=fmiguel_outlook.com#EXT#@fmigueloutlook.onmicrosoft.com;" + } +} diff --git a/infra/core/main.tf b/infra/core/main.tf index d75e5b0..c774b3c 100644 --- a/infra/core/main.tf +++ b/infra/core/main.tf @@ -12,7 +12,11 @@ terraform { } provider "azurerm" { - features {} + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } } locals { @@ -64,3 +68,11 @@ module "mysql_database" { environment = local.environment location = var.location } + +module "application_insights" { + source = "./modules/application-insights" + resource_group = azurerm_resource_group.main.name + application_name = var.application_name + environment = local.environment + location = var.location +} \ No newline at end of file diff --git a/infra/core/modules/application-insights/README.md b/infra/core/modules/application-insights/README.md new file mode 100644 index 0000000..e2f7c84 --- /dev/null +++ b/infra/core/modules/application-insights/README.md @@ -0,0 +1,8 @@ +# Terraform module for Azure Application Insights configuration + +This module configures an Azure Application Insights instance with Terraform. + +## Resources + +[What is Azure Application Insights](https://aka.ms/nubesgen-app-insights) +[Terraform Azure Application Insights reference](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_insights) diff --git a/infra/core/modules/application-insights/main.tf b/infra/core/modules/application-insights/main.tf new file mode 100644 index 0000000..2b2ac26 --- /dev/null +++ b/infra/core/modules/application-insights/main.tf @@ -0,0 +1,26 @@ +terraform { + required_providers { + azurecaf = { + source = "aztfmod/azurecaf" + version = "1.2.24" + } + } +} + +resource "azurecaf_name" "application_insights" { + name = var.application_name + resource_type = "azurerm_application_insights" + suffixes = [var.environment] +} + +resource "azurerm_application_insights" "application_insights" { + name = azurecaf_name.application_insights.result + location = var.location + resource_group_name = var.resource_group + application_type = "other" + + tags = { + "environment" = var.environment + "application-name" = var.application_name + } +} diff --git a/infra/core/modules/application-insights/outputs.tf b/infra/core/modules/application-insights/outputs.tf new file mode 100644 index 0000000..973c188 --- /dev/null +++ b/infra/core/modules/application-insights/outputs.tf @@ -0,0 +1,9 @@ +output "azure_application_insights_instrumentation_key" { + value = azurerm_application_insights.application_insights.instrumentation_key + description = "The Azure Application Insights instrumentation key" +} + +output "azure_application_insights_connection_string" { + value = azurerm_application_insights.application_insights.connection_string + description = "The Azure Application Insights connection string" +} \ No newline at end of file diff --git a/infra/core/modules/application-insights/variables.tf b/infra/core/modules/application-insights/variables.tf new file mode 100644 index 0000000..5484945 --- /dev/null +++ b/infra/core/modules/application-insights/variables.tf @@ -0,0 +1,23 @@ +variable "resource_group" { + type = string + description = "The resource group" + default = "" +} + +variable "application_name" { + type = string + description = "The name of your application" + default = "" +} + +variable "environment" { + type = string + description = "The environment (dev, test, prod...)" + default = "dev" +} + +variable "location" { + type = string + description = "The Azure region where all resources in this example should be created" + default = "" +} diff --git a/infra/core/outputs.tf b/infra/core/outputs.tf index 407b47b..ec3ef27 100644 --- a/infra/core/outputs.tf +++ b/infra/core/outputs.tf @@ -52,7 +52,7 @@ output "pgsql_database_name" { value = module.pgsql_database.database_name description = "The PostgreSQL database name" } - + output "msi_database_login_name" { value = module.application.database_login_name } @@ -60,3 +60,8 @@ output "msi_database_login_name" { output "container_environment_name" { value = module.application.container_environment_name } + +output "application_insights_connection_string" { + value = module.application_insights.azure_application_insights_connection_string + sensitive = true +} diff --git a/infra/create-user-pgsql.sh b/infra/create-user-pgsql.sh index 948ad02..e7b4300 100755 --- a/infra/create-user-pgsql.sh +++ b/infra/create-user-pgsql.sh @@ -11,10 +11,15 @@ ADMIN_PASSWORD=$(az account get-access-token --resource-type oss-rdbms -o tsv -- cat < pgsqluser.sql select * from pgaadauth_create_principal_with_oid('${APPLICATION_LOGIN_NAME}', '${APPLICATION_IDENTITY_APPID}', 'service', false, false); +EOF + +cat < grantuser.sql GRANT ALL PRIVILEGES ON DATABASE "${DATABASE_NAME}" TO "${APPLICATION_LOGIN_NAME}"; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "${APPLICATION_LOGIN_NAME}"; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO "${APPLICATION_LOGIN_NAME}"; EOF az postgres flexible-server execute --name ${PGSQL_SERVER} --file-path pgsqluser.sql --admin-password "${ADMIN_PASSWORD}" --admin-user "${ADMIN_USER}" --verbose +az postgres flexible-server execute --name ${PGSQL_SERVER} --file-path grantuser.sql --database-name ${DATABASE_NAME} --admin-password "${ADMIN_PASSWORD}" --admin-user "${ADMIN_USER}" --verbose rm pgsqluser.sql +rm grantuser.sql diff --git a/infra/deploy-all.sh b/infra/deploy-all.sh index 3958588..5f0bb03 100755 --- a/infra/deploy-all.sh +++ b/infra/deploy-all.sh @@ -13,21 +13,15 @@ MSI_LOGIN_NAME=$(terraform output -raw msi_database_login_name) MSI_CONTAINER_IDENTITY=$(terraform output -raw container_apps_identity) CONTAINER_APP_ENVIRONMENT=$(terraform output -raw container_environment_name) RESOURCE_GROUP=$(terraform output -raw resource_group) +APPINSIGHTS_CONNECTIONSTRING=$(terraform output -raw application_insights_connection_string) APPLICATION_IDENTITY_APPID=$(az identity show --id "${MSI_CONTAINER_IDENTITY}" -o tsv --query clientId) MYSQL_CONNECTION_STRING="Server=${MYSQL_SERVER}.mysql.database.azure.com;Database=${MYSQL_DATABASE_NAME};SslMode=Required" PGSQL_CONNECTION_STRING="Server=${PGSQL_SERVER}.postgres.database.azure.com;Database=${PGSQL_DATABASE_NAME};Ssl Mode=Require;Port=5432;Trust Server Certificate=true" -cd .. -# create mysq login for managed identity -./create-user-mysql.sh $MYSQL_SERVER $MYSQL_DATABASE_NAME $MSI_LOGIN_NAME $APPLICATION_IDENTITY_APPID $MYSQL_ADMIN_USER - -# create postgresql login for managed identity -./create-user-pgsql.sh $PGSQL_SERVER $PGSQL_DATABASE_NAME $MSI_LOGIN_NAME $APPLICATION_IDENTITY_APPID $PGSQL_ADMIN_USER - # create database schema using ef tools -cd ../src/repo.mysql +cd ../../src/repo.mysql cat < appsettings.json { "ConnectionStrings": { @@ -47,10 +41,24 @@ cat < appsettings.json EOF dotnet ef database update + +cd ../../infra +pwd +# create mysq login for managed identity +./create-user-mysql.sh $MYSQL_SERVER $MYSQL_DATABASE_NAME $MSI_LOGIN_NAME $APPLICATION_IDENTITY_APPID $MYSQL_ADMIN_USER + +# create postgresql login for managed identity +./create-user-pgsql.sh $PGSQL_SERVER $PGSQL_DATABASE_NAME $MSI_LOGIN_NAME $APPLICATION_IDENTITY_APPID $PGSQL_ADMIN_USER + + + # create docker image for the app -cd .. +cd ../src az acr build -t $ACR_NAME.azurecr.io/todoapi:latest -t $ACR_NAME.azurecr.io/todoapi:1.0.0 -r $ACR_NAME . +# create docker image for the web +cd web +az acr build -t $ACR_NAME.azurecr.io/todoweb:latest -t $ACR_NAME.azurecr.io/todoweb:1.0.0 -r $ACR_NAME . az containerapp create -n mysqlapi -g ${RESOURCE_GROUP} \ --image ${ACR_NAME}.azurecr.io/todoapi:1.0.0 --environment ${CONTAINER_APP_ENVIRONMENT} \ @@ -60,10 +68,30 @@ az containerapp create -n mysqlapi -g ${RESOURCE_GROUP} \ --cpu 0.25 --memory 0.5Gi \ --env-vars TargetDb="MySql" MySqlConnection="${MYSQL_CONNECTION_STRING};UserID=${MSI_LOGIN_NAME};" UserAssignedManagedClientId="${APPLICATION_IDENTITY_APPID}" +az containerapp ingress cors update -n mysqlapi -g ${RESOURCE_GROUP} --allowed-origins "*" --allowed-methods "*" + az containerapp create -n pgsqlapi -g ${RESOURCE_GROUP} \ --image ${ACR_NAME}.azurecr.io/todoapi:1.0.0 --environment ${CONTAINER_APP_ENVIRONMENT} \ --ingress external --target-port 80 \ --registry-server ${ACR_NAME}.azurecr.io --registry-identity "${MSI_CONTAINER_IDENTITY}" \ --user-assigned ${MSI_CONTAINER_IDENTITY} \ --cpu 0.25 --memory 0.5Gi \ - --env-vars TargetDb="Postgresql" PgSqlConnection="${PGSQL_CONNECTION_STRING};User Id=${MSI_LOGIN_NAME};" UserAssignedManagedClientId="${APPLICATION_IDENTITY_APPID}" \ No newline at end of file + --env-vars TargetDb="Postgresql" PgSqlConnection="${PGSQL_CONNECTION_STRING};User Id=${MSI_LOGIN_NAME};" UserAssignedManagedClientId="${APPLICATION_IDENTITY_APPID}" +az containerapp ingress cors update -n pgsqlapi -g ${RESOURCE_GROUP} --allowed-origins "*" --allowed-methods "*" + +MYSQLAPI_FQDN=$(az containerapp show -n mysqlapi -g ${RESOURCE_GROUP} -o tsv --query "properties.configuration.ingress.fqdn") +PGSQLAPI_FQDN=$(az containerapp show -n pgsqlapi -g ${RESOURCE_GROUP} -o tsv --query "properties.configuration.ingress.fqdn") + +az containerapp create -n mysqlweb -g ${RESOURCE_GROUP} \ + --image ${ACR_NAME}.azurecr.io/todoweb:1.0.0 --environment ${CONTAINER_APP_ENVIRONMENT} \ + --ingress external --target-port 80 \ + --registry-server ${ACR_NAME}.azurecr.io --registry-identity "${MSI_CONTAINER_IDENTITY}" \ + --cpu 0.25 --memory 0.5Gi \ + --env-vars REACT_APP_API_BASE_URL="https://${MYSQLAPI_FQDN}" REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING="${APPINSIGHTS_CONNECTIONSTRING}" + +az containerapp create -n pgsqlweb -g ${RESOURCE_GROUP} \ + --image ${ACR_NAME}.azurecr.io/todoweb:1.0.0 --environment ${CONTAINER_APP_ENVIRONMENT} \ + --ingress external --target-port 80 \ + --registry-server ${ACR_NAME}.azurecr.io --registry-identity "${MSI_CONTAINER_IDENTITY}" \ + --cpu 0.25 --memory 0.5Gi \ + --env-vars REACT_APP_API_BASE_URL="https://${PGSQLAPI_FQDN}" REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING="${APPINSIGHTS_CONNECTIONSTRING}" \ No newline at end of file