From 43eb846c726073d454f8c0e71a767e771c58e7d1 Mon Sep 17 00:00:00 2001 From: Jonny Rylands Date: Mon, 15 Jul 2024 15:43:34 +0100 Subject: [PATCH] Azure SQL Workspace Service (#3970) * Azure SQL Workspace Service - New Azure SQL workspace service, based on existing MySQL workspace - TRE Core changes: - Add Azure SQL privatelink DNS zone to core terraform - Base workspace changes: - Add Azure SQL privatelink DNS zone virtual network link to base workspace - Documentation * Update CHANGELOG.md * Fix lint issues * Add #tflint-ignore directive until a new release is created on microsoft/terraform-azurerm-environment-configuration * Pin 0.5.0 version to https://github.com/microsoft/terraform-azurerm-environment-configuration.git * Update CHANGELOG.md * Update terraform versions --- .github/workflows/deploy_tre_reusable.yml | 4 + CHANGELOG.md | 1 + core/terraform/dns_zones_non_core.tf | 11 ++ core/terraform/locals.tf | 1 + core/terraform/main.tf | 2 +- core/version.txt | 2 +- .../workspace-services/azuresql.md | 54 ++++++++ e2e_tests/resources/strings.py | 1 + e2e_tests/test_workspace_services.py | 1 + mkdocs.yml | 1 + .../workspace_services/azuresql/.dockerignore | 7 + .../workspace_services/azuresql/.env.sample | 5 + .../workspace_services/azuresql/.gitignore | 1 + .../azuresql/Dockerfile.tmpl | 15 +++ .../azuresql/parameters.json | 68 ++++++++++ .../workspace_services/azuresql/porter.yaml | 121 ++++++++++++++++++ .../azuresql/template_schema.json | 39 ++++++ .../azuresql/terraform/.terraform.lock.hcl | 42 ++++++ .../azuresql/terraform/azuresql.tf | 65 ++++++++++ .../azuresql/terraform/locals.tf | 35 +++++ .../azuresql/terraform/main.tf | 63 +++++++++ .../azuresql/terraform/outputs.tf | 3 + .../azuresql/terraform/variables.tf | 32 +++++ templates/workspaces/base/porter.yaml | 2 +- .../workspaces/base/terraform/network/data.tf | 5 + .../base/terraform/network/network.tf | 2 +- .../base/terraform/network/zone_links.tf | 10 ++ 27 files changed, 589 insertions(+), 4 deletions(-) create mode 100644 docs/tre-templates/workspace-services/azuresql.md create mode 100644 templates/workspace_services/azuresql/.dockerignore create mode 100644 templates/workspace_services/azuresql/.env.sample create mode 100644 templates/workspace_services/azuresql/.gitignore create mode 100644 templates/workspace_services/azuresql/Dockerfile.tmpl create mode 100644 templates/workspace_services/azuresql/parameters.json create mode 100644 templates/workspace_services/azuresql/porter.yaml create mode 100644 templates/workspace_services/azuresql/template_schema.json create mode 100644 templates/workspace_services/azuresql/terraform/.terraform.lock.hcl create mode 100644 templates/workspace_services/azuresql/terraform/azuresql.tf create mode 100644 templates/workspace_services/azuresql/terraform/locals.tf create mode 100644 templates/workspace_services/azuresql/terraform/main.tf create mode 100644 templates/workspace_services/azuresql/terraform/outputs.tf create mode 100644 templates/workspace_services/azuresql/terraform/variables.tf diff --git a/.github/workflows/deploy_tre_reusable.yml b/.github/workflows/deploy_tre_reusable.yml index 4b43636ab6..8c022360da 100644 --- a/.github/workflows/deploy_tre_reusable.yml +++ b/.github/workflows/deploy_tre_reusable.yml @@ -399,6 +399,8 @@ jobs: BUNDLE_DIR: "./templates/workspace_services/databricks"} - {BUNDLE_TYPE: "workspace_service", BUNDLE_DIR: "./templates/workspace_services/ohdsi"} + - {BUNDLE_TYPE: "workspace_service", + BUNDLE_DIR: "./templates/workspace_services/azuresql"} - {BUNDLE_TYPE: "user_resource", BUNDLE_DIR: "./templates/workspace_services/guacamole/user_resources/guacamole-azure-windowsvm"} - {BUNDLE_TYPE: "user_resource", @@ -558,6 +560,8 @@ jobs: BUNDLE_DIR: "./templates/workspace_services/databricks"} - {BUNDLE_TYPE: "workspace_service", BUNDLE_DIR: "./templates/workspace_services/ohdsi"} + - {BUNDLE_TYPE: "workspace_service", + BUNDLE_DIR: "./templates/workspace_services/azuresql"} environment: ${{ inputs.environmentName }} steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index a860ebe024..904fda7598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ **BREAKING CHANGES & MIGRATIONS**: FEATURES: +* Azure SQL Workspace Service ([#3969](https://github.com/microsoft/AzureTRE/issues/3969)) ENHANCEMENTS: * Add Case Study Docs ([#1366](https://github.com/microsoft/AzureTRE/issues/1366)) diff --git a/core/terraform/dns_zones_non_core.tf b/core/terraform/dns_zones_non_core.tf index 882767a1ae..d2b044b349 100644 --- a/core/terraform/dns_zones_non_core.tf +++ b/core/terraform/dns_zones_non_core.tf @@ -21,6 +21,17 @@ resource "azurerm_private_dns_zone_virtual_network_link" "mysql" { lifecycle { ignore_changes = [tags] } } +# since shared services are in the core network, their dns link could exist once and must be defined here. +resource "azurerm_private_dns_zone_virtual_network_link" "azuresql" { + resource_group_name = azurerm_resource_group.core.name + virtual_network_id = module.network.core_vnet_id + private_dns_zone_name = azurerm_private_dns_zone.non_core["privatelink.database.windows.net"].name + name = azurerm_private_dns_zone.non_core["privatelink.database.windows.net"].name + registration_enabled = false + tags = local.tre_core_tags + lifecycle { ignore_changes = [tags] } +} + # Once the deployment of the app gateway is complete, we can proceed to include the required DNS zone for Nexus, which is dependent on the FQDN of the app gateway. resource "azurerm_private_dns_zone" "nexus" { name = "nexus-${module.appgateway.app_gateway_fqdn}" diff --git a/core/terraform/locals.tf b/core/terraform/locals.tf index 97608ad1e1..76882f8a51 100644 --- a/core/terraform/locals.tf +++ b/core/terraform/locals.tf @@ -32,6 +32,7 @@ locals { "privatelink.notebooks.azure.net", "privatelink.postgres.database.azure.com", "privatelink.mysql.database.azure.com", + "privatelink.database.windows.net", "privatelink.azuredatabricks.net" ]) diff --git a/core/terraform/main.tf b/core/terraform/main.tf index 3a102b292f..4e8fa6932f 100644 --- a/core/terraform/main.tf +++ b/core/terraform/main.tf @@ -179,6 +179,6 @@ module "resource_processor_vmss_porter" { } module "terraform_azurerm_environment_configuration" { - source = "git::https://github.com/microsoft/terraform-azurerm-environment-configuration.git?ref=0.2.0" + source = "git::https://github.com/microsoft/terraform-azurerm-environment-configuration.git?ref=0.5.0" arm_environment = var.arm_environment } diff --git a/core/version.txt b/core/version.txt index 17c1a6260b..aa87fd3bff 100644 --- a/core/version.txt +++ b/core/version.txt @@ -1 +1 @@ -__version__ = "0.10.2" +__version__ = "0.10.3" \ No newline at end of file diff --git a/docs/tre-templates/workspace-services/azuresql.md b/docs/tre-templates/workspace-services/azuresql.md new file mode 100644 index 0000000000..d815bf2fe6 --- /dev/null +++ b/docs/tre-templates/workspace-services/azuresql.md @@ -0,0 +1,54 @@ +# Azure SQL Workspace Service + +See: [Azure SQL Database](https://learn.microsoft.com/en-us/azure/azure-sql/database) + +## Prerequisites + +- The base workspace deployed, or a workspace derived from the base workspace + +- The Azure SQL workspace service container image published to your TRE: + + `make workspace_service_bundle BUNDLE=azuresql` + +- Guacamole, with a VM containing SQL Server Management Studio or Azure Data Studio in order to connect - the Azure Data Science VM template contains both of these + + +## Authentication + +- Server name: Shown on the details page of the service in the Azure TRE portal under **Azure SQL FQDN** +- Authentication method: **SQL Server Authentication** +- Administrator credentials: + - Username: **azuresqladmin** + - Password: *(available in the workspace keyvault)* + +## Supported SKUs + +The following Azure SQL SKUs have been added to the template: + +| Service Tier | Level | DTUs | +|--------------|-------|----------| +| Standard | S1 | 20 DTUs | +| Standard | S2 | 50 DTUs | +| Standard | S3 | 100 DTUs | +| Standard | S4 | 200 DTUs | +| Standard | S6 | 400 DTUs | + +For costs please [Azure SQL Database pricing](https://azure.microsoft.com/en-us/pricing/details/azure-sql-database/single/) and select **DTU** as the purchase model. + +### Adding new SKUs + +To add new SKU options within the template, please determine the SKU names using: + +```bash +az sql db list-editions --location --output table +``` + +Then add the SKUs in the following places: + +1. In the `templates/workspace_services/azuresql/template_schema.yaml` file under `properties.sql_sku.enum`. +2. In the `templates/workspace_services/azuresql/terraform/locals.tf` file under `azuresql_sku`. +3. Above in this document. + +Once added, increment the version number in the `templates/workspace_services/azuresql/porter.yaml` file, and republish the template with the following command: + + `make workspace_service_bundle BUNDLE=azuresql` diff --git a/e2e_tests/resources/strings.py b/e2e_tests/resources/strings.py index ce06b588d5..2821407a68 100644 --- a/e2e_tests/resources/strings.py +++ b/e2e_tests/resources/strings.py @@ -19,6 +19,7 @@ MLFLOW_SERVICE = "tre-service-mlflow" MYSQL_SERVICE = "tre-workspace-service-mysql" HEALTH_SERVICE = "tre-workspace-service-health" +AZURESQL_SERVICE = "tre-workspace-service-azuresql" FIREWALL_SHARED_SERVICE = "tre-shared-service-firewall" GITEA_SHARED_SERVICE = "tre-shared-service-gitea" diff --git a/e2e_tests/test_workspace_services.py b/e2e_tests/test_workspace_services.py index 31ac5a0b14..c6d8f1429b 100644 --- a/e2e_tests/test_workspace_services.py +++ b/e2e_tests/test_workspace_services.py @@ -13,6 +13,7 @@ strings.MLFLOW_SERVICE, strings.MYSQL_SERVICE, strings.HEALTH_SERVICE, + strings.AZURESQL_SERVICE ] diff --git a/mkdocs.yml b/mkdocs.yml index dbbdc922cf..886992b882 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -99,6 +99,7 @@ nav: - Azure Databricks: tre-templates/workspace-services/databricks.md - OHDSI: tre-templates/workspace-services/ohdsi.md - MySQL: tre-templates/workspace-services/mysql.md + - Azure SQL: tre-templates/workspace-services/azuresql.md - Shared Services: - Gitea (Source Mirror): tre-templates/shared-services/gitea.md - Nexus (Package Mirror): tre-templates/shared-services/nexus.md diff --git a/templates/workspace_services/azuresql/.dockerignore b/templates/workspace_services/azuresql/.dockerignore new file mode 100644 index 0000000000..01f9314130 --- /dev/null +++ b/templates/workspace_services/azuresql/.dockerignore @@ -0,0 +1,7 @@ +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Put files here that you don't want copied into your bundle's invocation image +.gitignore +Dockerfile.tmpl + +# Local .terraform directories +**/.terraform/* diff --git a/templates/workspace_services/azuresql/.env.sample b/templates/workspace_services/azuresql/.env.sample new file mode 100644 index 0000000000..f46a9df64d --- /dev/null +++ b/templates/workspace_services/azuresql/.env.sample @@ -0,0 +1,5 @@ +ID="__CHANGE_ME__" +WORKSPACE_ID="__CHANGE_ME__" +SQL_SKU="__CHANGE_ME__" +STORAGE_GB="__CHANGE_ME__" +DB_NAME="__CHANGE_ME__" diff --git a/templates/workspace_services/azuresql/.gitignore b/templates/workspace_services/azuresql/.gitignore new file mode 100644 index 0000000000..e08a3e22b9 --- /dev/null +++ b/templates/workspace_services/azuresql/.gitignore @@ -0,0 +1 @@ +.cnab/ diff --git a/templates/workspace_services/azuresql/Dockerfile.tmpl b/templates/workspace_services/azuresql/Dockerfile.tmpl new file mode 100644 index 0000000000..c584174140 --- /dev/null +++ b/templates/workspace_services/azuresql/Dockerfile.tmpl @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile-upstream:1.4.0 +FROM --platform=linux/amd64 debian:bullseye-slim + +# PORTER_INIT + +RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache + +# Git is required for terraform_azurerm_environment_configuration +RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \ + apt-get update && apt-get install -y git --no-install-recommends + +# PORTER_MIXINS + +# Use the BUNDLE_DIR build argument to copy files into the bundle +COPY --link . ${BUNDLE_DIR}/ diff --git a/templates/workspace_services/azuresql/parameters.json b/templates/workspace_services/azuresql/parameters.json new file mode 100644 index 0000000000..d43e5fa6b5 --- /dev/null +++ b/templates/workspace_services/azuresql/parameters.json @@ -0,0 +1,68 @@ +{ + "schemaType": "ParameterSet", + "schemaVersion": "1.0.1", + "namespace": "", + "name": "tre-workspace-service-azuresql", + "parameters": [ + { + "name": "tre_id", + "source": { + "env": "TRE_ID" + } + }, + { + "name": "id", + "source": { + "env": "ID" + } + }, + { + "name": "tfstate_container_name", + "source": { + "env": "TERRAFORM_STATE_CONTAINER_NAME" + } + }, + { + "name": "tfstate_resource_group_name", + "source": { + "env": "MGMT_RESOURCE_GROUP_NAME" + } + }, + { + "name": "tfstate_storage_account_name", + "source": { + "env": "MGMT_STORAGE_ACCOUNT_NAME" + } + }, + { + "name": "sql_sku", + "source": { + "env": "SQL_SKU" + } + }, + { + "name": "storage_gb", + "source": { + "env": "STORAGE_GB" + } + }, + { + "name": "db_name", + "source": { + "env": "DB_NAME" + } + }, + { + "name": "workspace_id", + "source": { + "env": "WORKSPACE_ID" + } + }, + { + "name": "arm_environment", + "source": { + "env": "ARM_ENVIRONMENT" + } + } + ] +} diff --git a/templates/workspace_services/azuresql/porter.yaml b/templates/workspace_services/azuresql/porter.yaml new file mode 100644 index 0000000000..1316577ac5 --- /dev/null +++ b/templates/workspace_services/azuresql/porter.yaml @@ -0,0 +1,121 @@ +--- +schemaVersion: 1.0.0 +name: tre-workspace-service-azuresql +version: 1.0.9 +description: "An Azure SQL workspace service" +registry: azuretre +dockerfile: Dockerfile.tmpl + +credentials: + - name: azure_tenant_id + env: ARM_TENANT_ID + - name: azure_subscription_id + env: ARM_SUBSCRIPTION_ID + - name: azure_client_id + env: ARM_CLIENT_ID + - name: azure_client_secret + env: ARM_CLIENT_SECRET +parameters: + - name: workspace_id + type: string + - name: tre_id + type: string + + # the following are added automatically by the resource processor + - name: id + type: string + description: "Resource ID" + env: id + - name: tfstate_resource_group_name + type: string + description: "Resource group containing the Terraform state storage account" + - name: tfstate_storage_account_name + type: string + description: "The name of the Terraform state storage account" + - name: tfstate_container_name + env: tfstate_container_name + type: string + default: "tfstate" + description: "The name of the Terraform state storage container" + - name: arm_use_msi + env: ARM_USE_MSI + type: boolean + default: false + - name: arm_environment + env: ARM_ENVIRONMENT + type: string + default: "public" + - name: sql_sku + type: string + default: "S2 | 50 DTUs" + - name: storage_gb + type: integer + default: 5 + - name: db_name + type: string + default: tredb + +mixins: + - exec + - terraform: + clientVersion: 1.9.2 + +outputs: + - name: azuresql_fqdn + type: string + applyTo: + - install + - upgrade + +install: + - terraform: + description: "Deploy Azure SQL workspace service" + vars: + workspace_id: ${ bundle.parameters.workspace_id } + tre_id: ${ bundle.parameters.tre_id } + tre_resource_id: ${ bundle.parameters.id } + sql_sku: ${ bundle.parameters.sql_sku } + storage_gb: ${ bundle.parameters.storage_gb } + db_name: ${ bundle.parameters.db_name } + arm_environment: ${ bundle.parameters.arm_environment } + backendConfig: + resource_group_name: ${ bundle.parameters.tfstate_resource_group_name } + storage_account_name: ${ bundle.parameters.tfstate_storage_account_name } + container_name: ${ bundle.parameters.tfstate_container_name } + key: tre-workspace-service-azuresql-${ bundle.parameters.id } + outputs: + - name: azuresql_fqdn +upgrade: + - terraform: + description: "Upgrade Azure SQL workspace service" + vars: + workspace_id: ${ bundle.parameters.workspace_id } + tre_id: ${ bundle.parameters.tre_id } + tre_resource_id: ${ bundle.parameters.id } + sql_sku: ${ bundle.parameters.sql_sku } + storage_gb: ${ bundle.parameters.storage_gb } + db_name: ${ bundle.parameters.db_name } + arm_environment: ${ bundle.parameters.arm_environment } + backendConfig: + resource_group_name: ${ bundle.parameters.tfstate_resource_group_name } + storage_account_name: ${ bundle.parameters.tfstate_storage_account_name } + container_name: ${ bundle.parameters.tfstate_container_name } + key: tre-workspace-service-azuresql-${ bundle.parameters.id } + outputs: + - name: azuresql_fqdn +uninstall: + - terraform: + description: "Tear down Azure SQL workspace service" + vars: + workspace_id: ${ bundle.parameters.workspace_id } + tre_id: ${ bundle.parameters.tre_id } + tre_resource_id: ${ bundle.parameters.id } + sql_sku: ${ bundle.parameters.sql_sku } + storage_gb: ${ bundle.parameters.storage_gb } + db_name: ${ bundle.parameters.db_name } + arm_environment: ${ bundle.parameters.arm_environment } + backendConfig: + resource_group_name: ${ bundle.parameters.tfstate_resource_group_name } + storage_account_name: ${ bundle.parameters.tfstate_storage_account_name } + container_name: ${ bundle.parameters.tfstate_container_name } + key: tre-workspace-service-azuresql-${ bundle.parameters.id } diff --git a/templates/workspace_services/azuresql/template_schema.json b/templates/workspace_services/azuresql/template_schema.json new file mode 100644 index 0000000000..2e80406307 --- /dev/null +++ b/templates/workspace_services/azuresql/template_schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://github.com/microsoft/AzureTRE/templates/workspace_services/azuresql/template_schema.json", + "type": "object", + "title": "Azure SQL", + "description": "Provides Azure SQL within the workspace", + "required": [], + "properties": { + "sql_sku": { + "$id": "#/properties/sql_sku", + "type": "string", + "title": "Azure SQL SKU", + "description": "Azure SQL SKU", + "updateable": true, + "enum": [ + "S1 | 20 DTUs", + "S2 | 50 DTUs", + "S3 | 100 DTUs", + "S4 | 200 DTUs", + "S6 | 400 DTUs" + ], + "default": "S2 | 50 DTUs" + }, + "storage_gb": { + "$id": "#/properties/storage_gb", + "type": "number", + "title": "Max storage allowed for a database (GB)", + "description": "Max storage allowed for a database (GB)", + "default": 5 + }, + "db_name": { + "$id": "#/properties/db_name", + "type": "string", + "title": "Database name", + "description": "Database name", + "default": "tredb" + } + } +} diff --git a/templates/workspace_services/azuresql/terraform/.terraform.lock.hcl b/templates/workspace_services/azuresql/terraform/.terraform.lock.hcl new file mode 100644 index 0000000000..fae00c026f --- /dev/null +++ b/templates/workspace_services/azuresql/terraform/.terraform.lock.hcl @@ -0,0 +1,42 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.111.0" + constraints = "3.111.0" + hashes = [ + "h1:vgrdy5JWGAK5N44/V75etoHIAMvXKNlMrIHTaWApehA=", + "zh:0db8afb9278993df7e74796bdd125153b07a7045e5ca1756783a8b8cfec564f4", + "zh:22c424fcfda13dc720caa289248c1b71b2ad20e329fd4a52cc6be7e45f795a4a", + "zh:471a2c1d7353bc21ef28963f006d2cf5276e7885b423fc0b73f2d8ce6cde72dd", + "zh:68bf81cb353c755d48792e881b6405919daa041e35de1d510209237d90d6c21f", + "zh:841d8664955bbc77f12095c9b1a4b3923362564a790fd945337759e9bc95d07e", + "zh:86e92f959056c573bf4b2be1d6cfa838dab06d3e5a944f371a1131e4c6477d88", + "zh:95a096ced57616659687970b5d618c2ce3cd54fa0311b7a7569435cacf39f26f", + "zh:c5656a11253ffdaee973e7292dd3c10a1db81f1fc9ee2d3041ae1182f7d25379", + "zh:cd6a1049de69280f339d6f83f30a9006bbe003a840a39eb7b5900990c5aadbb0", + "zh:e7b3d96f0c9ea47261dbd015f1f64fdb43c8ccb196afda862c0865e30d88245c", + "zh:f1ec7da6ab5526845274bff77e023b9faec71c2cf38bd18587274932b2aa2e89", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.2" + constraints = "3.6.2" + hashes = [ + "h1:wmG0QFjQ2OfyPy6BB7mQ57WtoZZGGV07uAPQeDmIrAE=", + "zh:0ef01a4f81147b32c1bea3429974d4d104bbc4be2ba3cfa667031a8183ef88ec", + "zh:1bcd2d8161e89e39886119965ef0f37fcce2da9c1aca34263dd3002ba05fcb53", + "zh:37c75d15e9514556a5f4ed02e1548aaa95c0ecd6ff9af1119ac905144c70c114", + "zh:4210550a767226976bc7e57d988b9ce48f4411fa8a60cd74a6b246baf7589dad", + "zh:562007382520cd4baa7320f35e1370ffe84e46ed4e2071fdc7e4b1a9b1f8ae9b", + "zh:5efb9da90f665e43f22c2e13e0ce48e86cae2d960aaf1abf721b497f32025916", + "zh:6f71257a6b1218d02a573fc9bff0657410404fb2ef23bc66ae8cd968f98d5ff6", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:9647e18f221380a85f2f0ab387c68fdafd58af6193a932417299cdcae4710150", + "zh:bb6297ce412c3c2fa9fec726114e5e0508dd2638cad6a0cb433194930c97a544", + "zh:f83e925ed73ff8a5ef6e3608ad9225baa5376446349572c2449c0c0b3cf184b7", + "zh:fbef0781cb64de76b1df1ca11078aecba7800d82fd4a956302734999cfd9a4af", + ] +} diff --git a/templates/workspace_services/azuresql/terraform/azuresql.tf b/templates/workspace_services/azuresql/terraform/azuresql.tf new file mode 100644 index 0000000000..0e435cb567 --- /dev/null +++ b/templates/workspace_services/azuresql/terraform/azuresql.tf @@ -0,0 +1,65 @@ +resource "random_password" "password" { + length = 20 + min_upper = 2 + min_lower = 2 + min_numeric = 2 + min_special = 2 +} + +resource "azurerm_mssql_server" "azuresql" { + name = local.azuresql_server_name + resource_group_name = data.azurerm_resource_group.ws.name + location = data.azurerm_resource_group.ws.location + version = "12.0" + administrator_login = local.azuresql_administrator_login + administrator_login_password = random_password.password.result + minimum_tls_version = "1.2" + public_network_access_enabled = false + outbound_network_restriction_enabled = true + tags = local.workspace_service_tags + + lifecycle { ignore_changes = [tags] } +} + +resource "azurerm_mssql_database" "azuresqldatabase" { + name = var.db_name + server_id = azurerm_mssql_server.azuresql.id + collation = local.azuresql_collation + license_type = "LicenseIncluded" + max_size_gb = var.storage_gb + sku_name = local.azuresql_sku[var.sql_sku].value + tags = local.workspace_service_tags + + lifecycle { ignore_changes = [tags] } +} + +resource "azurerm_private_endpoint" "azuresql_private_endpoint" { + name = local.azuresql_private_endpoint_name + location = data.azurerm_resource_group.ws.location + resource_group_name = data.azurerm_resource_group.ws.name + subnet_id = data.azurerm_subnet.services.id + tags = local.workspace_service_tags + + private_service_connection { + private_connection_resource_id = azurerm_mssql_server.azuresql.id + name = local.azuresql_private_service_connection_name + subresource_names = ["sqlServer"] + is_manual_connection = false + } + + private_dns_zone_group { + name = module.terraform_azurerm_environment_configuration.private_links["privatelink.database.windows.net"] + private_dns_zone_ids = [data.azurerm_private_dns_zone.azuresql.id] + } + + lifecycle { ignore_changes = [tags] } +} + +resource "azurerm_key_vault_secret" "db_password" { + name = local.azuresql_password_keyvault_secret_name + value = random_password.password.result + key_vault_id = data.azurerm_key_vault.ws.id + tags = local.workspace_service_tags + + lifecycle { ignore_changes = [tags] } +} diff --git a/templates/workspace_services/azuresql/terraform/locals.tf b/templates/workspace_services/azuresql/terraform/locals.tf new file mode 100644 index 0000000000..4c3750fcf5 --- /dev/null +++ b/templates/workspace_services/azuresql/terraform/locals.tf @@ -0,0 +1,35 @@ +locals { + + core_resource_group_name = "rg-${var.tre_id}" + + workspace_short_id = substr(var.workspace_id, -4, -1) + workspace_resource_name_suffix = "${var.tre_id}-ws-${local.workspace_short_id}" + workspace_resource_group_name = "rg-${local.workspace_resource_name_suffix}" + workspace_vnet_name = "vnet-${local.workspace_resource_name_suffix}" + workspace_keyvault_name = lower("kv-${substr(local.workspace_resource_name_suffix, -20, -1)}") + + service_short_id = substr(var.tre_resource_id, -4, -1) + service_resource_name_suffix = "${local.workspace_resource_name_suffix}-svc-${local.service_short_id}" + + azuresql_server_name = "azsql-${local.service_resource_name_suffix}" + azuresql_administrator_login = "azuresqladmin" + azuresql_collation = "SQL_Latin1_General_CP1_CI_AS" + + azuresql_sku = { + "S1 | 20 DTUs" = { value = "S1" }, + "S2 | 50 DTUs" = { value = "S2" }, + "S3 | 100 DTUs" = { value = "S3" }, + "S4 | 200 DTUs" = { value = "S4" }, + "S6 | 400 DTUs" = { value = "S6" }, + } + + azuresql_private_endpoint_name = "pe-${azurerm_mssql_server.azuresql.name}" + azuresql_private_service_connection_name = "psc-${azurerm_mssql_server.azuresql.name}" + azuresql_password_keyvault_secret_name = "${azurerm_mssql_server.azuresql.name}-administrator-password" + + workspace_service_tags = { + tre_id = var.tre_id + tre_workspace_id = var.workspace_id + tre_workspace_service_id = var.tre_resource_id + } +} diff --git a/templates/workspace_services/azuresql/terraform/main.tf b/templates/workspace_services/azuresql/terraform/main.tf new file mode 100644 index 0000000000..6ea4d2d51a --- /dev/null +++ b/templates/workspace_services/azuresql/terraform/main.tf @@ -0,0 +1,63 @@ +# Azure Provider source and version being used +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "3.111.0" + } + random = { + source = "hashicorp/random" + version = "3.6.2" + } + } + + backend "azurerm" {} +} + +provider "azurerm" { + features { + key_vault { + # Don't purge on destroy (this would fail due to purge protection being enabled on keyvault) + purge_soft_delete_on_destroy = false + purge_soft_deleted_secrets_on_destroy = false + purge_soft_deleted_certificates_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + # When recreating an environment, recover any previously soft deleted secrets - set to true by default + recover_soft_deleted_key_vaults = true + recover_soft_deleted_secrets = true + recover_soft_deleted_certificates = true + recover_soft_deleted_keys = true + } + } +} + +module "terraform_azurerm_environment_configuration" { + source = "git::https://github.com/microsoft/terraform-azurerm-environment-configuration.git?ref=0.5.0" + arm_environment = var.arm_environment +} + + +data "azurerm_resource_group" "ws" { + name = local.workspace_resource_group_name +} + +data "azurerm_virtual_network" "ws" { + name = local.workspace_vnet_name + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "azurerm_key_vault" "ws" { + name = local.workspace_keyvault_name + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "azurerm_subnet" "services" { + name = "ServicesSubnet" + virtual_network_name = data.azurerm_virtual_network.ws.name + resource_group_name = data.azurerm_resource_group.ws.name +} + +data "azurerm_private_dns_zone" "azuresql" { + name = module.terraform_azurerm_environment_configuration.private_links["privatelink.database.windows.net"] + resource_group_name = local.core_resource_group_name +} diff --git a/templates/workspace_services/azuresql/terraform/outputs.tf b/templates/workspace_services/azuresql/terraform/outputs.tf new file mode 100644 index 0000000000..1746deca7f --- /dev/null +++ b/templates/workspace_services/azuresql/terraform/outputs.tf @@ -0,0 +1,3 @@ +output "azuresql_fqdn" { + value = azurerm_mssql_server.azuresql.fully_qualified_domain_name +} diff --git a/templates/workspace_services/azuresql/terraform/variables.tf b/templates/workspace_services/azuresql/terraform/variables.tf new file mode 100644 index 0000000000..0a386dba5f --- /dev/null +++ b/templates/workspace_services/azuresql/terraform/variables.tf @@ -0,0 +1,32 @@ +variable "workspace_id" { + type = string +} + +variable "tre_id" { + type = string +} + +variable "tre_resource_id" { + type = string +} + +variable "sql_sku" { + type = string +} + +variable "db_name" { + type = string +} + +variable "storage_gb" { + type = number + + validation { + condition = var.storage_gb > 1 && var.storage_gb < 1024 + error_message = "The storage value is out of range." + } +} + +variable "arm_environment" { + type = string +} diff --git a/templates/workspaces/base/porter.yaml b/templates/workspaces/base/porter.yaml index f7a1802477..81297e717d 100644 --- a/templates/workspaces/base/porter.yaml +++ b/templates/workspaces/base/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-workspace-base -version: 1.5.3 +version: 1.5.4 description: "A base Azure TRE workspace" dockerfile: Dockerfile.tmpl registry: azuretre diff --git a/templates/workspaces/base/terraform/network/data.tf b/templates/workspaces/base/terraform/network/data.tf index b54dea230e..48dee597b7 100644 --- a/templates/workspaces/base/terraform/network/data.tf +++ b/templates/workspaces/base/terraform/network/data.tf @@ -93,6 +93,11 @@ data "azurerm_private_dns_zone" "postgres" { resource_group_name = local.core_resource_group_name } +data "azurerm_private_dns_zone" "azuresql" { + name = module.terraform_azurerm_environment_configuration.private_links["privatelink.database.windows.net"] + resource_group_name = local.core_resource_group_name +} + data "azurerm_public_ip" "app_gateway_ip" { name = "pip-agw-${var.tre_id}" resource_group_name = local.core_resource_group_name diff --git a/templates/workspaces/base/terraform/network/network.tf b/templates/workspaces/base/terraform/network/network.tf index bbae1ec1ab..c50ebff6f2 100644 --- a/templates/workspaces/base/terraform/network/network.tf +++ b/templates/workspaces/base/terraform/network/network.tf @@ -105,6 +105,6 @@ resource "azurerm_subnet_route_table_association" "rt_webapps_subnet_association } module "terraform_azurerm_environment_configuration" { - source = "git::https://github.com/microsoft/terraform-azurerm-environment-configuration.git?ref=0.2.0" + source = "git::https://github.com/microsoft/terraform-azurerm-environment-configuration.git?ref=0.5.0" arm_environment = var.arm_environment } diff --git a/templates/workspaces/base/terraform/network/zone_links.tf b/templates/workspaces/base/terraform/network/zone_links.tf index 8dc927c54b..e5a0f60781 100644 --- a/templates/workspaces/base/terraform/network/zone_links.tf +++ b/templates/workspaces/base/terraform/network/zone_links.tf @@ -129,6 +129,16 @@ resource "azurerm_private_dns_zone_virtual_network_link" "postgreslink" { lifecycle { ignore_changes = [tags] } } +resource "azurerm_private_dns_zone_virtual_network_link" "azuresqllink" { + name = "azuresqllink-${local.workspace_resource_name_suffix}" + resource_group_name = local.core_resource_group_name + private_dns_zone_name = data.azurerm_private_dns_zone.azuresql.name + virtual_network_id = azurerm_virtual_network.ws.id + tags = var.tre_workspace_tags + + lifecycle { ignore_changes = [tags] } +} + resource "azurerm_private_dns_zone_virtual_network_link" "nexuslink" { name = "nexuslink-${local.workspace_resource_name_suffix}" resource_group_name = local.core_resource_group_name