diff --git a/.github/workflows/test.yaml b/.github/workflows/test-deploy.yaml similarity index 82% rename from .github/workflows/test.yaml rename to .github/workflows/test-deploy.yaml index 025b8b80..ee52818a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test-deploy.yaml @@ -1,4 +1,4 @@ -name: test +name: Test-deploy on: workflow_dispatch: @@ -10,6 +10,9 @@ on: options: - poc +env: + TARGET_ENV: ${{ inputs.target_env }} + jobs: deploy: name: Deploy @@ -36,7 +39,7 @@ jobs: terraform_version: 1.11.4 - name: Terraform init - env: - TARGET_ENV: ${{ inputs.target_env }} - run: | - make ${TARGET_ENV} terraform-init + run: make ${TARGET_ENV} terraform-init + + - name: Terraform plan + run: make ${TARGET_ENV} terraform-plan diff --git a/infrastructure/environments/poc/variables.sh b/infrastructure/environments/poc/variables.sh index 79118a62..83cecc05 100644 --- a/infrastructure/environments/poc/variables.sh +++ b/infrastructure/environments/poc/variables.sh @@ -5,3 +5,5 @@ HUB_SUBSCRIPTION="Lung Cancer Screening - Dev" STORAGE_ACCOUNT_RG=rg-tfstate-poc-uks TERRAFORM_MODULES_REF=main ENABLE_SOFT_DELETE=false +DOCKER_IMAGE=docker.io/nginxdemos/hello +DOCKER_IMAGE_TAG=latest diff --git a/infrastructure/modules/infra/main.tf b/infrastructure/modules/infra/main.tf new file mode 100644 index 00000000..433e21d2 --- /dev/null +++ b/infrastructure/modules/infra/main.tf @@ -0,0 +1,54 @@ +resource "azurerm_resource_group" "main" { + name = var.resource_group_name + location = var.region +} + +module "app-key-vault" { + source = "../dtos-devops-templates/infrastructure/modules/key-vault" + + name = "kv-${var.app_short_name}-${var.environment}-app" + resource_group_name = azurerm_resource_group.main.name + enable_rbac_authorization = true + location = var.region + log_analytics_workspace_id = module.log_analytics_workspace_audit.id + monitor_diagnostic_setting_keyvault_enabled_logs = ["AuditEvent", "AzurePolicyEvaluationDetails"] + monitor_diagnostic_setting_keyvault_metrics = ["AllMetrics"] + private_endpoint_properties = var.features.private_networking ? { + private_dns_zone_ids_keyvault = [data.azurerm_private_dns_zone.key-vault[0].id] + private_endpoint_enabled = true + private_endpoint_subnet_id = module.main_subnet.id + private_endpoint_resource_group_name = azurerm_resource_group.main.name + private_service_connection_is_manual = false + } : null + purge_protection_enabled = var.protect_keyvault +} + +module "log_analytics_workspace_audit" { + source = "../dtos-devops-templates/infrastructure/modules/log-analytics-workspace" + + name = "law-${var.environment}-uks-${var.app_short_name}" + location = var.region + + law_sku = "PerGB2018" + retention_days = 30 + + monitor_diagnostic_setting_log_analytics_workspace_enabled_logs = ["SummaryLogs", "Audit"] + monitor_diagnostic_setting_log_analytics_workspace_metrics = ["AllMetrics"] + + resource_group_name = azurerm_resource_group.main.name +} + +module "container-app-environment" { + source = "../dtos-devops-templates/infrastructure/modules/container-app-environment" + + providers = { + azurerm = azurerm + azurerm.dns = azurerm.hub + } + + name = "cae-${var.environment}-uks-${var.app_short_name}" + resource_group_name = azurerm_resource_group.main.name + log_analytics_workspace_id = module.log_analytics_workspace_audit.id + vnet_integration_subnet_id = module.container_app_subnet.id + private_dns_zone_rg_name = var.features.private_networking ? "rg-hub-${var.hub}-uks-private-dns-zones" : null +} diff --git a/infrastructure/modules/infra/networking.tf b/infrastructure/modules/infra/networking.tf new file mode 100644 index 00000000..d9919d5c --- /dev/null +++ b/infrastructure/modules/infra/networking.tf @@ -0,0 +1,134 @@ +module "main_vnet" { + source = "../dtos-devops-templates/infrastructure/modules/vnet" + + name = "vnet-${var.environment}-uks-${var.app_short_name}" + resource_group_name = azurerm_resource_group.main.name + location = var.region + dns_servers = var.features.private_networking ? [data.azurerm_private_dns_resolver_inbound_endpoint.this[0].ip_configurations[0].private_ip_address] : [] + log_analytics_workspace_id = module.log_analytics_workspace_audit.id + monitor_diagnostic_setting_vnet_enabled_logs = ["VMProtectionAlerts"] + monitor_diagnostic_setting_vnet_metrics = ["AllMetrics"] + vnet_address_space = var.vnet_address_space +} + +module "postgres_subnet" { + source = "../dtos-devops-templates/infrastructure/modules/subnet" + + name = "snet-postgres" + resource_group_name = azurerm_resource_group.main.name + vnet_name = module.main_vnet.name + address_prefixes = [cidrsubnet(var.vnet_address_space, 7, 1)] + create_nsg = false + location = var.region + monitor_diagnostic_setting_network_security_group_enabled_logs = [] + log_analytics_workspace_id = module.log_analytics_workspace_audit.id + network_security_group_name = "nsg-postgres" +} + + +data "azurerm_private_dns_resolver" "this" { + count = var.features.private_networking ? 1 : 0 + + provider = azurerm.hub + + name = "${var.hub}-uks-hub-private-dns-zone-resolver" + resource_group_name = "rg-hub-${var.hub}-uks-private-dns-zones" +} + +data "azurerm_private_dns_resolver_inbound_endpoint" "this" { + count = var.features.private_networking ? 1 : 0 + + provider = azurerm.hub + + name = "private-dns-resolver-inbound-endpoint" + private_dns_resolver_id = data.azurerm_private_dns_resolver.this[0].id +} + +data "azurerm_virtual_network" "hub" { + count = var.features.hub_and_spoke ? 1 : 0 + + provider = azurerm.hub + + name = module.hub_config.names.virtual-network + resource_group_name = local.hub_vnet_rg_name +} + +module "peering_spoke_hub" { + count = var.features.hub_and_spoke ? 1 : 0 + + source = "../dtos-devops-templates/infrastructure/modules/vnet-peering" + + name = "${module.main_vnet.name}-to-hub-peering" + resource_group_name = azurerm_resource_group.main.name + vnet_name = module.main_vnet.name + remote_vnet_id = data.azurerm_virtual_network.hub[0].id + + allow_virtual_network_access = true + allow_forwarded_traffic = true + allow_gateway_transit = false + + use_remote_gateways = false +} + +module "peering_hub_spoke" { + count = var.features.hub_and_spoke ? 1 : 0 + + providers = { + azurerm = azurerm.hub + } + + source = "../dtos-devops-templates/infrastructure/modules/vnet-peering" + + name = "hub-to-${module.main_vnet.name}-peering" + resource_group_name = local.hub_vnet_rg_name + vnet_name = data.azurerm_virtual_network.hub[0].name + remote_vnet_id = module.main_vnet.vnet.id + + allow_virtual_network_access = true + allow_forwarded_traffic = true + allow_gateway_transit = false + + use_remote_gateways = false +} + + +module "container_app_subnet" { + source = "../dtos-devops-templates/infrastructure/modules/subnet" + + name = "snet-container-apps" + resource_group_name = azurerm_resource_group.main.name + vnet_name = module.main_vnet.name + address_prefixes = [cidrsubnet(var.vnet_address_space, 7, 0)] + create_nsg = false + location = "UK South" + monitor_diagnostic_setting_network_security_group_enabled_logs = [] + log_analytics_workspace_id = module.log_analytics_workspace_audit.id + network_security_group_name = "nsg-container-apps" + delegation_name = "delegation" + service_delegation_name = "Microsoft.App/environments" + service_delegation_actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"] +} + +module "main_subnet" { + source = "../dtos-devops-templates/infrastructure/modules/subnet" + + name = "snet-main" + resource_group_name = azurerm_resource_group.main.name + vnet_name = module.main_vnet.name + address_prefixes = [cidrsubnet(var.vnet_address_space, 7, 2)] + create_nsg = false + location = "UK South" + monitor_diagnostic_setting_network_security_group_enabled_logs = [] + log_analytics_workspace_id = module.log_analytics_workspace_audit.id + network_security_group_name = "nsg-container-apps" +} + +data "azurerm_private_dns_zone" "key-vault" { + count = var.features.private_networking ? 1 : 0 + + provider = azurerm.hub + + name = "privatelink.vaultcore.azure.net" + resource_group_name = "rg-hub-${var.hub}-uks-private-dns-zones" +} + diff --git a/infrastructure/modules/infra/output.tf b/infrastructure/modules/infra/output.tf new file mode 100644 index 00000000..10baa0b4 --- /dev/null +++ b/infrastructure/modules/infra/output.tf @@ -0,0 +1,27 @@ +output "app_key_vault_id" { + value = module.app-key-vault.key_vault_id +} + +output "container_app_environment_id" { + value = module.container-app-environment.id +} + +output "vnet_name" { + value = module.main_vnet.name +} + +output "log_analytics_workspace_audit_id" { + value = module.log_analytics_workspace_audit.id +} + +output "default_domain" { + value = module.container-app-environment.default_domain +} + +output "postgres_subnet_id" { + value = module.postgres_subnet.id +} + +output "main_subnet_id" { + value = module.main_subnet.id +} diff --git a/infrastructure/modules/infra/providers.tf b/infrastructure/modules/infra/providers.tf new file mode 100644 index 00000000..63db07d5 --- /dev/null +++ b/infrastructure/modules/infra/providers.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + configuration_aliases = [azurerm.hub] + } + } +} diff --git a/infrastructure/modules/infra/variables.tf b/infrastructure/modules/infra/variables.tf new file mode 100644 index 00000000..3c97be46 --- /dev/null +++ b/infrastructure/modules/infra/variables.tf @@ -0,0 +1,48 @@ +variable "app_short_name" { + description = "Application short name (6 characters)" + type = string +} + +variable "environment" { + description = "Application environment name" + type = string +} + +variable "features" { + description = "Feature flags for the deployment" + type = object({ + front_door = optional(bool, true) + hub_and_spoke = optional(bool, true) + private_networking = optional(bool, true) + }) +} + +variable "resource_group_name" { + description = "Infra resource group name" + type = string +} + +variable "hub" { + description = "Hub name (dev or prod)" + type = string +} + +variable "region" { + description = "The region to deploy in" + type = string +} + +variable "vnet_address_space" { + description = "VNET address space. Must be unique across the hub." + type = string +} + +variable "protect_keyvault" { + description = "Ability to recover the key vault or its secrets after deletion" + type = bool + default = true +} + +locals { + hub_vnet_rg_name = "rg-hub-${var.hub}-uks-hub-networking" +} diff --git a/infrastructure/terraform/main.tf b/infrastructure/terraform/main.tf new file mode 100644 index 00000000..3c9d75d6 --- /dev/null +++ b/infrastructure/terraform/main.tf @@ -0,0 +1,57 @@ +module "infra" { + count = var.deploy_infra ? 1 : 0 + + source = "../modules/infra" + + providers = { + azurerm = azurerm + azurerm.hub = azurerm.hub + } + + region = local.region + resource_group_name = local.resource_group_name + app_short_name = var.app_short_name + environment = var.env_config + features = var.features + hub = var.hub + private_networking = var.private_networking + protect_keyvault = var.protect_keyvault + vnet_address_space = var.vnet_address_space +} + +# module "container-apps" { +# count = var.deploy_container_apps ? 1 : 0 + +# source = "../modules/container-apps" + +# providers = { +# azurerm = azurerm +# azurerm.hub = azurerm.hub +# } + +# region = local.region +# app_key_vault_id = var.deploy_infra ? module.infra[0].app_key_vault_id : data.azurerm_key_vault.app_key_vault[0].id +# app_short_name = var.app_short_name +# container_app_environment_id = var.deploy_infra ? module.infra[0].container_app_environment_id : data.azurerm_container_app_environment.this[0].id +# default_domain = var.deploy_infra ? module.infra[0].default_domain : data.azurerm_container_app_environment.this[0].default_domain +# dns_zone_name = var.dns_zone_name +# docker_image = var.docker_image +# deploy_database_as_container = var.deploy_database_as_container +# enable_auth = var.enable_auth +# environment = var.environment +# env_config = var.env_config +# fetch_secrets_from_app_key_vault = var.fetch_secrets_from_app_key_vault +# front_door_profile = var.front_door_profile +# hub = var.hub +# log_analytics_workspace_audit_id = var.deploy_infra ? module.infra[0].log_analytics_workspace_audit_id : data.azurerm_log_analytics_workspace.audit[0].id +# postgres_backup_retention_days = var.postgres_backup_retention_days +# postgres_geo_redundant_backup_enabled = var.postgres_geo_redundant_backup_enabled +# postgres_sku_name = var.postgres_sku_name +# postgres_sql_admin_group = "postgres_${var.app_short_name}_${var.env_config}_uks_admin" +# postgres_storage_mb = var.postgres_storage_mb +# postgres_storage_tier = var.postgres_storage_tier +# postgres_subnet_id = var.deploy_infra ? module.infra[0].postgres_subnet_id : data.azurerm_subnet.postgres[0].id +# main_subnet_id = var.deploy_infra ? module.infra[0].main_subnet_id : data.azurerm_subnet.main[0].id +# seed_demo_data = var.seed_demo_data +# use_apex_domain = var.use_apex_domain +# } diff --git a/infrastructure/terraform/variables.tf b/infrastructure/terraform/variables.tf new file mode 100644 index 00000000..9eed6e28 --- /dev/null +++ b/infrastructure/terraform/variables.tf @@ -0,0 +1,148 @@ +variable "deploy_infra" { + description = "The foundational layer of infrastructure for the application to run on" + type = bool + default = true +} + +variable "deploy_container_apps" { + description = "Deploy the container app to the foundational infra" + type = bool + default = true +} + +variable "app_short_name" { + description = "Application short name (6 characters)" + type = string +} + +variable "environment" { + description = "Application environment name" + type = string +} + +variable "env_config" { + description = "Environment configuration. Different environments may share the same environment config and the same infrastructure" + type = string +} + +variable "hub" { + description = "Hub name (dev or prod)" + type = string +} + +variable "docker_image" { + description = "Docker image full path including registry, repository and tag" + type = string +} + +variable "hub_subscription_id" { + description = "ID of the hub Azure subscription" + type = string +} + +variable "vnet_address_space" { + description = "VNET address space. Must be unique across the hub." + type = string +} + +variable "fetch_secrets_from_app_key_vault" { + description = < terraform-plan DOCKER_IMAGE_TAG=abcd123 terraform -chdir=infrastructure/terraform plan -var-file ../environments/${ENV_CONFIG}/variables.tfvars