From a6e58d754818697b478cc639c627199e7eb50c50 Mon Sep 17 00:00:00 2001 From: Russell Day Date: Thu, 25 Apr 2024 22:01:13 +0100 Subject: [PATCH] Deploy terraform from GitHub (#8) Automate deployment of terraform and apps --- .github/workflows/DeployBlazorClient.yml | 1 - .github/workflows/DeployBranchPush.yml | 16 +++ .github/workflows/DeployEverything.yml | 124 ++++++++++++++++++ .github/workflows/DeployServerWebAPI.yml | 1 - .../wwwroot/appsettings.Production.json | 2 +- .../wwwroot/appsettings.Test.json | 4 - .../wwwroot/appsettings.Test2.json | 4 + .../wwwroot/appsettings.test.json | 4 + .../Migrations/2024_SeedData.sql | 2 +- README.md | 9 +- terraform/api_server.tf | 8 +- terraform/blazor_client.tf | 20 ++- terraform/database.tf | 20 ++- terraform/keyvault.tf | 53 -------- terraform/main.tf | 64 +++++++-- terraform/readme.md | 2 +- terraform/terraform.tf | 13 +- terraform/variables.tf | 24 ++-- tfvars/{prod.tfvars => Production.tfvars} | 0 tfvars/{dev.tfvars => Test.tfvars} | 2 +- tfvars/{test.tfvars => Test2.tfvars} | 4 +- 21 files changed, 268 insertions(+), 109 deletions(-) create mode 100644 .github/workflows/DeployBranchPush.yml create mode 100644 .github/workflows/DeployEverything.yml delete mode 100644 PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Test.json create mode 100644 PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Test2.json create mode 100644 PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.test.json delete mode 100644 terraform/keyvault.tf rename tfvars/{prod.tfvars => Production.tfvars} (100%) rename tfvars/{dev.tfvars => Test.tfvars} (91%) rename tfvars/{test.tfvars => Test2.tfvars} (73%) diff --git a/.github/workflows/DeployBlazorClient.yml b/.github/workflows/DeployBlazorClient.yml index eeb0b43..daf5b41 100644 --- a/.github/workflows/DeployBlazorClient.yml +++ b/.github/workflows/DeployBlazorClient.yml @@ -2,7 +2,6 @@ name: Build and deploy Pocket DDD Blazor Client on: workflow_dispatch: - push: jobs: build_and_deploy_job: diff --git a/.github/workflows/DeployBranchPush.yml b/.github/workflows/DeployBranchPush.yml new file mode 100644 index 0000000..0148f7b --- /dev/null +++ b/.github/workflows/DeployBranchPush.yml @@ -0,0 +1,16 @@ +name: Deploy Branch Push +on: + push: + branches-ignore: + - 'main' + + +jobs: + deploy_to_test: + uses: ./.github/workflows/DeployEverything.yml + with: + env: "Test2" + secrets: + AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} + AZURE_STATIC_WEB_APPS_API_TOKEN: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} + TERRAFORM_STATE_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} diff --git a/.github/workflows/DeployEverything.yml b/.github/workflows/DeployEverything.yml new file mode 100644 index 0000000..c54a705 --- /dev/null +++ b/.github/workflows/DeployEverything.yml @@ -0,0 +1,124 @@ +name: Deploy Everything +on: + workflow_call: + inputs: + env: + required: true + default: "Test" + type: string + secrets: + AZURE_CREDENTIALS: + required: true + AZURE_STATIC_WEB_APPS_API_TOKEN: + required: true + TERRAFORM_STATE_ACCESS_KEY: + required: true + +env: + AZURE_WEBAPP_PACKAGE_PATH: PocketDDD.Server.WebAPI/publish + CONFIGURATION: Release + DOTNET_CORE_VERSION: 8.0.x + WORKING_DIRECTORY: PocketDDD.Server/PocketDDD.Server.WebAPI + +jobs: + deploy_terraform: + runs-on: ubuntu-latest + name: Deploy terraform + environment: ${{ inputs.env }} + defaults: + run: + working-directory: ./terraform + + steps: + - uses: actions/checkout@v4 + - name: Log in with Azure + uses: azure/login@v1 + with: + creds: '${{ secrets.AZURE_CREDENTIALS }}' + - name: Setup terraform + uses: hashicorp/setup-terraform@v3 + - run: | + terraform init -backend-config="key=${{ inputs.env }}.terraform.tfstate" + + terraform apply -auto-approve --var-file ../tfvars/${{ inputs.env }}.tfvars + env: + ARM_ACCESS_KEY: ${{ secrets.TERRAFORM_STATE_ACCESS_KEY }} + + build_api_server: + runs-on: ubuntu-latest + name: Build API Server + steps: + - uses: actions/checkout@v4 + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_CORE_VERSION }} + - name: Restore + run: dotnet restore "${{ env.WORKING_DIRECTORY }}" + - name: Build + run: dotnet build "${{ env.WORKING_DIRECTORY }}" --configuration ${{ env.CONFIGURATION }} --no-restore + - name: Test + run: dotnet test "${{ env.WORKING_DIRECTORY }}" --no-build + - name: Publish + run: dotnet publish "${{ env.WORKING_DIRECTORY }}" --configuration ${{ env.CONFIGURATION }} --no-build --output "${{ env.AZURE_WEBAPP_PACKAGE_PATH }}" + - name: Publish Artifacts + uses: actions/upload-artifact@v4 + with: + name: webapp + path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} + + deploy_api_server: + name: Deploy API Server + runs-on: ubuntu-latest + environment: ${{ inputs.env }} + needs: [deploy_terraform, build_api_server] + steps: + - name: Log in with Azure + uses: azure/login@v1 + with: + creds: '${{ secrets.AZURE_CREDENTIALS }}' + - name: Download artifact from build job + uses: actions/download-artifact@v4 + with: + name: webapp + path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} + - name: Deploy to Azure WebApp + uses: azure/webapps-deploy@v2 + with: + app-name: pocketddd-${{ inputs.env }}-api-server-web-app + package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} + + build_and_deploy_blazor_client: + runs-on: ubuntu-latest + environment: ${{ inputs.env }} + name: Build and Deploy Blazor Client + needs: deploy_terraform + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: Log in with Azure + uses: azure/login@v1 + with: + creds: '${{ secrets.AZURE_CREDENTIALS }}' + + - run: | + cp PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.${{ inputs.env }}.json PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Production.json + + - run: | + apiToken=$(az staticwebapp secrets list --name pocketddd-${{ inputs.env }}-blazorclient --query "properties.apiKey" -o tsv) + echo "WEB_APP_API_TOKEN=$apiToken" >> "$GITHUB_ENV" + + - name: Build And Deploy + id: builddeploy + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ env.WEB_APP_API_TOKEN }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### + # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig + app_location: "/PocketDDD.BlazorClient/PocketDDD.BlazorClient" # App source code path + api_location: "" # Api source code path - optional + output_location: "wwwroot" # Built app content directory - optional + ###### End of Repository/Build Configurations ###### diff --git a/.github/workflows/DeployServerWebAPI.yml b/.github/workflows/DeployServerWebAPI.yml index a599f50..3a3f4a6 100644 --- a/.github/workflows/DeployServerWebAPI.yml +++ b/.github/workflows/DeployServerWebAPI.yml @@ -1,7 +1,6 @@ name: Build and deploy Pocket DDD Server on: workflow_dispatch: - push: env: AZURE_WEBAPP_PACKAGE_PATH: PocketDDD.Server.WebAPI/publish diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Production.json b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Production.json index 5fdc5db..a9c1e55 100644 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Production.json +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Production.json @@ -1,4 +1,4 @@ { - "apiUrl": "https://dddsw2023pocketdddserverwebapi.azurewebsites.net/api/", + "apiUrl": "https://pocketddd-production-api-server-web-app.azurewebsites.net/api/", "fakeBackend": false } \ No newline at end of file diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Test.json b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Test.json deleted file mode 100644 index b71cadf..0000000 --- a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Test.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "apiUrl": "https://pocketddd-dev-api-server.azurewebsites.net/api/", - "fakeBackend": false -} \ No newline at end of file diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Test2.json b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Test2.json new file mode 100644 index 0000000..6e7781d --- /dev/null +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.Test2.json @@ -0,0 +1,4 @@ +{ + "apiUrl": "https://pocketddd-test2-api-server-web-app.azurewebsites.net/api/", + "fakeBackend": false +} \ No newline at end of file diff --git a/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.test.json b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.test.json new file mode 100644 index 0000000..f327669 --- /dev/null +++ b/PocketDDD.BlazorClient/PocketDDD.BlazorClient/wwwroot/appsettings.test.json @@ -0,0 +1,4 @@ +{ + "apiUrl": "https://pocketddd-test-api-server-web-app.azurewebsites.net/api/", + "fakeBackend": false +} \ No newline at end of file diff --git a/PocketDDD.Server/PocketDDD.Server.DB/Migrations/2024_SeedData.sql b/PocketDDD.Server/PocketDDD.Server.DB/Migrations/2024_SeedData.sql index c7b9a49..37e6c8b 100644 --- a/PocketDDD.Server/PocketDDD.Server.DB/Migrations/2024_SeedData.sql +++ b/PocketDDD.Server/PocketDDD.Server.DB/Migrations/2024_SeedData.sql @@ -8,7 +8,7 @@ delete EventDetail GO -DBCC CHECKIDENT ('[EventDetail]', RESEED, 0); +DBCC CHECKIDENT ('[EventDetail]', RESEED, 1); DBCC CHECKIDENT ('[Tracks]', RESEED, 0); DBCC CHECKIDENT ('[TimeSlots]', RESEED, 0); DBCC CHECKIDENT ('[Sessions]', RESEED, 0); diff --git a/README.md b/README.md index 19296f1..38f2267 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Authorization: Ensure the Azure, GitHub, and terraform CLIs are installed ``` brew install azure-cli -bre install gh +brew install gh brew install terraform ``` @@ -24,11 +24,16 @@ az login gh auth login ``` +Retrieve the access key for the terraform state storage account +``` +export ARM_ACCESS_KEY=$(az storage account keys list -g pocketddd-terraform-state -n pocketdddterraformstate --query [0].value -o tsv) +``` + From the `terraform` directory run init, plan, then apply if happy with the changes. ``` cd ./terraform -terraform init +terraform init -backend-config="dev.terraform.tfstate" terraform plan -var-file ../tfvars/dev.tfvars terraform apply -var-file ../tfvars/dev.tfvars ``` \ No newline at end of file diff --git a/terraform/api_server.tf b/terraform/api_server.tf index 70dafb5..adc587a 100644 --- a/terraform/api_server.tf +++ b/terraform/api_server.tf @@ -8,7 +8,7 @@ resource "azurerm_service_plan" "api_server_service_plan" { resource "azurerm_linux_web_app" "api_server_web_app" { - name = "${local.resource_prefix}-api-server" + name = "${local.resource_prefix}-api-server-web-app" resource_group_name = azurerm_resource_group.rg.name location = azurerm_service_plan.api_server_service_plan.location service_plan_id = azurerm_service_plan.api_server_service_plan.id @@ -30,3 +30,9 @@ resource "azurerm_linux_web_app" "api_server_web_app" { "AdminKey" = random_password.admin_api_key.result } } + +resource "azurerm_key_vault_secret" "api_admin_key" { + name = "${local.resource_prefix}-admin-api-key" + value = random_password.admin_api_key.result + key_vault_id = azurerm_key_vault.key_vault.id +} diff --git a/terraform/blazor_client.tf b/terraform/blazor_client.tf index fe5cf9e..905ff05 100644 --- a/terraform/blazor_client.tf +++ b/terraform/blazor_client.tf @@ -5,11 +5,19 @@ resource "azurerm_static_web_app" "blazor-client" { sku_tier = var.client_sku_tier sku_size = var.client_sku_size + + app_settings = { + "ASPNETCORE_ENVIRONMENT": "${ var.env }" + "DOTNET_ENVIRONMENT": "${ var.env }" + "apiUrl": "https://pocketddd-${ var.env }-api-server-web-app.azurewebsites.net/api/" + "fakeBackend": "false" + } + + preview_environments_enabled = false } -resource "github_actions_environment_secret" "test_secret" { - repository = data.github_repository.repo.name - environment = github_repository_environment.repo_environment.environment - secret_name = "AZURE_STATIC_WEB_APPS_API_TOKEN" - plaintext_value = azurerm_static_web_app.blazor-client.api_key -} \ No newline at end of file +resource "azurerm_key_vault_secret" "blazor_client_deployment_token" { + name = "${local.resource_prefix}-blazor-client-deployment-token" + value = azurerm_static_web_app.blazor-client.api_key + key_vault_id = azurerm_key_vault.key_vault.id +} diff --git a/terraform/database.tf b/terraform/database.tf index d376f01..5484dba 100644 --- a/terraform/database.tf +++ b/terraform/database.tf @@ -12,10 +12,10 @@ resource "azurerm_mssql_server" "sqlserver" { } resource "azurerm_mssql_database" "sqldb" { - name = "${local.resource_prefix}-sqldatabase" - server_id = azurerm_mssql_server.sqlserver.id - sku_name = var.sql_db_sku - max_size_gb = var.sql_max_storage + name = "${local.resource_prefix}-sqldatabase" + server_id = azurerm_mssql_server.sqlserver.id + sku_name = var.sql_db_sku + max_size_gb = var.sql_max_storage storage_account_type = "Local" tags = { @@ -34,3 +34,15 @@ resource "azurerm_mssql_firewall_rule" "firewall_rule" { start_ip_address = "0.0.0.0" end_ip_address = "0.0.0.0" } + +resource "azurerm_key_vault_secret" "sqldb_connectionstring" { + name = "${local.resource_prefix}-db-connection-string" + value = local.db_connection_string + key_vault_id = azurerm_key_vault.key_vault.id +} + +resource "azurerm_key_vault_secret" "sqldb_admin_password" { + name = "${local.resource_prefix}-db-admin-password" + value = random_password.admin_password.result + key_vault_id = azurerm_key_vault.key_vault.id +} diff --git a/terraform/keyvault.tf b/terraform/keyvault.tf deleted file mode 100644 index 63d3522..0000000 --- a/terraform/keyvault.tf +++ /dev/null @@ -1,53 +0,0 @@ -data "azurerm_client_config" "current" {} - -resource "azurerm_key_vault" "key_vault" { - name = "${local.resource_prefix}-keyvault" - location = azurerm_resource_group.rg.location - resource_group_name = azurerm_resource_group.rg.name - enabled_for_disk_encryption = true - tenant_id = data.azurerm_client_config.current.tenant_id - soft_delete_retention_days = 7 - purge_protection_enabled = false - - sku_name = "standard" - - access_policy { - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = data.azurerm_client_config.current.object_id - - key_permissions = [ - "Get", - ] - - secret_permissions = [ - "Get", - "Set", - "List", - "Delete", - "Purge", - "Recover" - ] - - storage_permissions = [ - "Get", - ] - } -} - -resource "azurerm_key_vault_secret" "sqldb_connectionstring" { - name = "${local.resource_prefix}-db-connection-string" - value = local.db_connection_string - key_vault_id = azurerm_key_vault.key_vault.id -} - -resource "azurerm_key_vault_secret" "sqldb_admin_password" { - name = "${local.resource_prefix}-db-admin-password" - value = random_password.admin_password.result - key_vault_id = azurerm_key_vault.key_vault.id -} - -resource "azurerm_key_vault_secret" "api_admin_key" { - name = "${local.resource_prefix}-admin-api-key" - value = random_password.admin_api_key.result - key_vault_id = azurerm_key_vault.key_vault.id -} diff --git a/terraform/main.tf b/terraform/main.tf index 3887522..3ddb7f7 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -1,16 +1,18 @@ +data "azurerm_client_config" "current" {} + resource "azurerm_resource_group" "rg" { name = "${local.resource_prefix}-rg" location = "UK South" } resource "random_string" "admin_login" { - length = 16 - special = true - override_special = "/@£$" + length = 20 + special = false } resource "random_password" "admin_password" { - length = 25 + length = 30 + special = false } resource "random_password" "admin_api_key" { @@ -18,14 +20,54 @@ resource "random_password" "admin_api_key" { } locals { - db_connection_string = "Server=tcp:${local.sql_server_name}.database.windows.net,1433;Initial Catalog=pocketddd-dev-sqldatabase;Persist Security Info=False;User ID=${random_string.admin_login.result};Password=${random_password.admin_password.result};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" + db_connection_string = "Server=tcp:${local.sql_server_name}.database.windows.net,1433;Initial Catalog=pocketddd-${var.env}-sqldatabase;Persist Security Info=False;User ID=${random_string.admin_login.result};Password=${random_password.admin_password.result};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" } -data "github_repository" "repo" { - name = "PocketDDD" -} +resource "azurerm_key_vault" "key_vault" { + name = "${local.resource_prefix}-kv" + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + enabled_for_disk_encryption = true + tenant_id = data.azurerm_client_config.current.tenant_id + soft_delete_retention_days = 7 + purge_protection_enabled = false + + sku_name = "standard" + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Get", + ] + + secret_permissions = [ + "Get", + "Set", + "List", + "Delete", + "Purge", + "Recover" + ] + + storage_permissions = [ + "Get", + ] + } + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = "4a9cec89-cee2-44fb-978f-6ded96b60d31" + + key_permissions = [ ] + + secret_permissions = [ + "Get", + "List", + "Purge", + ] -resource "github_repository_environment" "repo_environment" { - repository = "PocketDDD" - environment = var.env + storage_permissions = [ ] + } } diff --git a/terraform/readme.md b/terraform/readme.md index 38a002f..62f4c12 100644 --- a/terraform/readme.md +++ b/terraform/readme.md @@ -1,4 +1,4 @@ -Command to create a new deployment service principal +Command to create a new deployment service principal (requires the User Access Administrator role in an Azure subscription): ``` az ad sp create-for-rbac -n DevDeployment --role Contributor --scopes /subscriptions//resourceGroups/ --sdk-auth ``` diff --git a/terraform/terraform.tf b/terraform/terraform.tf index 5027348..a631d7b 100644 --- a/terraform/terraform.tf +++ b/terraform/terraform.tf @@ -1,13 +1,14 @@ terraform { + backend "azurerm" { + resource_group_name = "pocketddd-terraform-state" + storage_account_name = "pocketdddterraformstate" + container_name = "tfstate" + } required_providers { azurerm = { source = "hashicorp/azurerm" version = "3.100.0" } - github = { - source = "integrations/github" - version = "6.2.1" - } random = { source = "hashicorp/random" version = "3.6.1" @@ -21,7 +22,3 @@ provider "azurerm" { } } - -provider "github" { - owner = "dddsw" -} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf index b3dbd44..a730615 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -5,37 +5,37 @@ variable "env" { } variable "sql_db_sku" { - default = "S0" + default = "S0" nullable = false - type = string + type = string } variable "sql_max_storage" { - default = "2" + default = "2" nullable = false - type = string + type = string } variable "api_app_service_sku" { - default = "B1" + default = "B1" nullable = false - type = string + type = string } variable "api_always_on" { - default = true + default = true nullable = false - type = bool + type = bool } variable "client_sku_tier" { - default = "" + default = "" nullable = false - type = string + type = string } variable "client_sku_size" { - default = "" + default = "" nullable = false - type = string + type = string } diff --git a/tfvars/prod.tfvars b/tfvars/Production.tfvars similarity index 100% rename from tfvars/prod.tfvars rename to tfvars/Production.tfvars diff --git a/tfvars/dev.tfvars b/tfvars/Test.tfvars similarity index 91% rename from tfvars/dev.tfvars rename to tfvars/Test.tfvars index 52b2019..484b4d8 100644 --- a/tfvars/dev.tfvars +++ b/tfvars/Test.tfvars @@ -1,4 +1,4 @@ -env = "dev" +env = "test" sql_db_sku = "Basic" sql_max_storage = "2" api_app_service_sku = "F1" diff --git a/tfvars/test.tfvars b/tfvars/Test2.tfvars similarity index 73% rename from tfvars/test.tfvars rename to tfvars/Test2.tfvars index 52b2019..34a80f3 100644 --- a/tfvars/test.tfvars +++ b/tfvars/Test2.tfvars @@ -1,7 +1,7 @@ -env = "dev" +env = "test2" sql_db_sku = "Basic" sql_max_storage = "2" -api_app_service_sku = "F1" +api_app_service_sku = "B1" api_always_on = false client_sku_tier = "Free" client_sku_size = "Free" \ No newline at end of file