NOTE: The following module is preconfigured to use two regions, Canada Central and East US. It also expects that you have your site’s TLS certificate in an Azure Key Vault.

##############################################
#
#   High Availability App Service
#
###############################################

locals {
  full_service_name = "${var.service-name}${var.environment_name == "prod" ? "" : "-${var.environment_name}"}"
  public_hostname   = "${var.service-name}${var.environment_name == "prod" ? "" : "-${var.environment_name}"}.flyswoop.com"
  tags = {
    environment = "${var.environment_name}"
    owner       = "${var.owner}"
    product     = "${var.service-name}"
    department  = "${var.department}"
  }
}

resource "azurerm_resource_group" "cancen-rg" {
  name     = "${var.service-name}-cancen-rg-${var.environment_name}"
  location = "canadacentral"

  tags = "${local.tags}"
}

resource "azurerm_app_service_plan" "cancen-app-service-plan" {
  name                = "${var.service-name}-cancen-app-service-plan-${var.environment_name}"
  location            = "${azurerm_resource_group.cancen-rg.location}"
  resource_group_name = "${azurerm_resource_group.cancen-rg.name}"
  kind                = "Linux"
  reserved            = true

  sku {
    tier = "${var.app-service-plan-sku-tier}"
    size = "${var.app-service-plan-sku-size}"
  }

  tags = "${local.tags}"
}

resource "azurerm_app_service" "cancen-app-service" {
  name                = "${var.service-name}-cancen-app-service-${var.environment_name}"
  location            = "${azurerm_resource_group.cancen-rg.location}"
  resource_group_name = "${azurerm_resource_group.cancen-rg.name}"
  app_service_plan_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}"
  tags                = "${local.tags}"
  https_only          = true

  site_config {
    always_on         = "true"
    min_tls_version   = "1.2"
    default_documents = []
    linux_fx_version  = "DOCKER|nginx"
    scm_type          = "VSTSRM"
    http2_enabled     = true
  }

  identity {
    type = "SystemAssigned"
  }

  logs {
    http_logs {
      file_system {
        retention_in_days = 30
        retention_in_mb   = 35
      }
    }
  }

  lifecycle {
    ignore_changes = [
      app_settings,
      site_config[0].linux_fx_version
    ]
  }
}

resource "azurerm_app_service_slot" "cancen-app-service-slot-blue" {
  # Only create the slots if we're in prod, since the DEV and QA
  # App Service SKUs don't support slots
  count               = "${var.environment_name == "prod" ? 1 : 0}"
  name                = "blue"
  app_service_name    = "${azurerm_app_service.cancen-app-service.name}"
  location            = "${azurerm_resource_group.cancen-rg.location}"
  resource_group_name = "${azurerm_resource_group.cancen-rg.name}"
  app_service_plan_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}"
  https_only          = true

  site_config {
    always_on         = "true"
    min_tls_version   = "1.2"
    default_documents = []
    linux_fx_version  = "DOCKER|nginx:alpine"
    scm_type          = "VSTSRM"
    http2_enabled     = true
  }

  identity {
    type = "SystemAssigned"
  }

  lifecycle {
    ignore_changes = [
      app_settings,
      site_config[0].linux_fx_version
    ]
  }
}

resource "azurerm_resource_group" "centralus-rg" {
  name     = "${var.service-name}-centralus-rg-${var.environment_name}"
  location = "centralus"

  tags = "${local.tags}"
}

resource "azurerm_app_service_plan" "centralus-app-service-plan" {
  name                = "${var.service-name}-centralus-app-service-plan-${var.environment_name}"
  location            = "${azurerm_resource_group.centralus-rg.location}"
  resource_group_name = "${azurerm_resource_group.centralus-rg.name}"
  kind                = "Linux"
  reserved            = true

  sku {
    tier = "${var.app-service-plan-sku-tier}"
    size = "${var.app-service-plan-sku-size}"
  }

  tags = "${local.tags}"
}

resource "azurerm_app_service" "centralus-app-service" {
  name                = "${var.service-name}-centralus-app-service-${var.environment_name}"
  location            = "${azurerm_resource_group.centralus-rg.location}"
  resource_group_name = "${azurerm_resource_group.centralus-rg.name}"
  app_service_plan_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}"
  tags                = "${local.tags}"
  https_only          = true

  site_config {
    always_on         = "true"
    min_tls_version   = "1.2"
    default_documents = []
    linux_fx_version  = "DOCKER|nginx"
    scm_type          = "VSTSRM"
    http2_enabled     = true
  }

  logs {
    http_logs {
      file_system {
        retention_in_days = 30
        retention_in_mb   = 35
      }
    }
  }

  identity {
    type = "SystemAssigned"
  }

  lifecycle {
    ignore_changes = [
      app_settings,
      site_config[0].linux_fx_version
    ]
  }
}

resource "azurerm_app_service_slot" "centralus-app-service-slot-blue" {
  # Only create the slots if we're in prod, since the DEV and QA
  # App Service SKUs don't support slots
  count               = "${var.environment_name == "prod" ? 1 : 0}"
  name                = "blue"
  app_service_name    = "${azurerm_app_service.centralus-app-service.name}"
  location            = "${azurerm_resource_group.centralus-rg.location}"
  resource_group_name = "${azurerm_resource_group.centralus-rg.name}"
  app_service_plan_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}"
  https_only          = true

  site_config {
    always_on         = "true"
    min_tls_version   = "1.2"
    default_documents = []
    linux_fx_version  = "DOCKER|nginx:alpine"
    scm_type          = "VSTSRM"
    http2_enabled     = true
  }

  identity {
    type = "SystemAssigned"
  }

  lifecycle {
    ignore_changes = [
      app_settings,
      site_config[0].linux_fx_version
    ]
  }
}

resource "azurerm_resource_group" "traffic-manager" {
  name     = "${var.service-name}-traffic-manager-${var.environment_name}"
  location = "canadacentral"
}

resource "azurerm_traffic_manager_profile" "traffic-manager-profile" {
  name                   = "${var.service-name}-traffic-manager-${var.environment_name}"
  resource_group_name    = "${azurerm_resource_group.traffic-manager.name}"
  traffic_routing_method = "Weighted"

  dns_config {
    relative_name = "${var.service-name}-traffic-manager-${var.environment_name}"
    ttl           = 30
  }

  monitor_config {
    protocol = "HTTPS"
    port     = 443
    path     = "/"
  }

  tags = "${local.tags}"
}

resource "azurerm_traffic_manager_endpoint" "traffic-endpoint-cancen" {
  name                = "${var.service-name}-traffic-endpoint-cancen-${var.environment_name}"
  resource_group_name = "${azurerm_resource_group.traffic-manager.name}"
  profile_name        = "${azurerm_traffic_manager_profile.traffic-manager-profile.name}"
  type                = "azureEndpoints"
  target_resource_id  = "${azurerm_app_service.cancen-app-service.id}"
  weight              = 100
}

resource "azurerm_traffic_manager_endpoint" "traffic-endpoint-centralus" {
  name                = "${var.service-name}-traffic-endpoint-centralus-${var.environment_name}"
  resource_group_name = "${azurerm_resource_group.traffic-manager.name}"
  profile_name        = "${azurerm_traffic_manager_profile.traffic-manager-profile.name}"
  type                = "azureEndpoints"
  target_resource_id  = "${azurerm_app_service.centralus-app-service.id}"
  weight              = 100
}

resource "azuread_application" "enterprise-application" {
  name                       = "${var.service-name-proper} (${var.environment_name})"
  homepage                   = "https://${local.public_hostname}"
  available_to_other_tenants = true
  oauth2_allow_implicit_flow = true

  reply_urls = [
    "https://${local.public_hostname}",
    "https://${local.public_hostname}/",
    "http://localhost:4000"
  ]

  lifecycle {
    ignore_changes = [
      "required_resource_access"
    ]
  }
}

resource "azurerm_application_insights" "app-insights" {
  application_type    = "Web"
  location            = "${azurerm_resource_group.cancen-rg.location}"
  name                = "${var.service-name}-app-insights-${var.environment_name}"
  resource_group_name = "${azurerm_resource_group.cancen-rg.name}"
  tags                = "${local.tags}"
}

output "instrumentation_key" {
  value = "${azurerm_application_insights.app-insights.instrumentation_key}"
}

output "app_id" {
  value = "${azurerm_application_insights.app-insights.app_id}"
}

resource "azurerm_monitor_autoscale_setting" "cancen-app-service-autoscale" {
  name                = "${var.service-name}-cancen-app-service-autoscale"
  resource_group_name = "${azurerm_resource_group.cancen-rg.name}"
  location            = "${azurerm_resource_group.cancen-rg.location}"
  target_resource_id  = "${azurerm_app_service_plan.cancen-app-service-plan.id}"

  profile {
    name = "${var.service-name}-cancen-app-service-autoscale-profile"

    capacity {
      default = 4
      minimum = 4
      maximum = 10
    }

    rule {
      metric_trigger {
        metric_name        = "CpuPercentage"
        metric_resource_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}"
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
        operator           = "GreaterThan"
        threshold          = 75
      }

      scale_action {
        direction = "Increase"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT1M"
      }
    }

    rule {
      metric_trigger {
        metric_name        = "CpuPercentage"
        metric_resource_id = "${azurerm_app_service_plan.cancen-app-service-plan.id}"
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
        operator           = "LessThan"
        threshold          = 50
      }

      scale_action {
        direction = "Decrease"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT1M"
      }
    }
  }

  notification {
    email {
      send_to_subscription_administrator    = true
      send_to_subscription_co_administrator = true
    }
  }

  # This needs to be started later because it was reporting as "finished" but not *really* being done
  # so when we'd try to apply the custom hostname, it would fail saying there was already
  # an operation in progress
  depends_on = [azurerm_app_service_custom_hostname_binding.cancen]
}

resource "azurerm_monitor_autoscale_setting" "centralus-app-service-autoscale" {
  name                = "${var.service-name}-centralus-app-service-autoscale"
  resource_group_name = "${azurerm_resource_group.centralus-rg.name}"
  location            = "${azurerm_resource_group.centralus-rg.location}"
  target_resource_id  = "${azurerm_app_service_plan.centralus-app-service-plan.id}"

  profile {
    name = "${var.service-name}-centralus-app-service-autoscale-profile"

    capacity {
      default = 4
      minimum = 4
      maximum = 10
    }

    rule {
      metric_trigger {
        metric_name        = "CpuPercentage"
        metric_resource_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}"
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
        operator           = "GreaterThan"
        threshold          = 75
      }

      scale_action {
        direction = "Increase"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT1M"
      }
    }

    rule {
      metric_trigger {
        metric_name        = "CpuPercentage"
        metric_resource_id = "${azurerm_app_service_plan.centralus-app-service-plan.id}"
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
        operator           = "LessThan"
        threshold          = 50
      }

      scale_action {
        direction = "Decrease"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT1M"
      }
    }
  }

  notification {
    email {
      send_to_subscription_administrator    = true
      send_to_subscription_co_administrator = true
    }
  }

  # This needs to be started later because it was reporting as "finished" but not *really* being done
  # so when we'd try to apply the custom hostname, it would fail saying there was already
  # an operation in progress
  depends_on = [azurerm_app_service_custom_hostname_binding.centralus]
}

resource "cloudflare_record" "cloudflare-cancen-dns-record-awverify" {
  zone_id    = "${var.cloudflare-zone-id}"
  name       = "awverify.${local.public_hostname}"
  value      = "awverify.${azurerm_app_service.cancen-app-service.default_site_hostname}"
  type       = "TXT"
  depends_on = [azurerm_app_service.cancen-app-service]
}

resource "null_resource" "cancen-dns-awverify-pause" {
  provisioner "local-exec" {
    command = "sleep 30"
  }
  triggers = {
    "before" = "${cloudflare_record.cloudflare-cancen-dns-record-awverify.id}"
  }
}

resource "azurerm_app_service_custom_hostname_binding" "cancen" {
  hostname            = "${local.public_hostname}"
  app_service_name    = "${azurerm_app_service.cancen-app-service.name}"
  resource_group_name = "${azurerm_resource_group.cancen-rg.name}"
  ssl_state           = "SniEnabled"
  thumbprint          = "${azurerm_app_service_certificate.cancen.thumbprint}"
  depends_on = [
    null_resource.cancen-dns-awverify-pause,
    azurerm_app_service_certificate.cancen,
    cloudflare_record.cloudflare-cancen-dns-record-awverify
  ]
}

resource "cloudflare_record" "cloudflare-centralus-dns-record-awverify" {
  zone_id = "${var.cloudflare-zone-id}"
  name    = "awverify.${local.public_hostname}"
  value   = "awverify.${azurerm_app_service.centralus-app-service.default_site_hostname}"
  type    = "TXT"
  depends_on = [
    azurerm_app_service.centralus-app-service
  ]
}

resource "null_resource" "centralus-dns-awverify-pause" {
  provisioner "local-exec" {
    command = "sleep 30"
  }
  triggers = {
    "before" = "${cloudflare_record.cloudflare-centralus-dns-record-awverify.id}"
  }
}

resource "azurerm_app_service_custom_hostname_binding" "centralus" {
  hostname            = "${local.public_hostname}"
  app_service_name    = "${azurerm_app_service.centralus-app-service.name}"
  resource_group_name = "${azurerm_resource_group.centralus-rg.name}"
  ssl_state           = "SniEnabled"
  thumbprint          = "${azurerm_app_service_certificate.centralus.thumbprint}"
  depends_on = [
    null_resource.centralus-dns-awverify-pause,
    azurerm_app_service_certificate.centralus,
    cloudflare_record.cloudflare-centralus-dns-record-awverify
  ]
}

resource "cloudflare_record" "cloudflare-dns-record-proxied" {
  zone_id = "${var.cloudflare-zone-id}"
  name    = "${local.full_service_name}"
  value   = "${azurerm_traffic_manager_profile.traffic-manager-profile.fqdn}"
  type    = "CNAME"
  ttl     = 1
  proxied = true
  depends_on = [
    azurerm_traffic_manager_profile.traffic-manager-profile,
    cloudflare_record.cloudflare-cancen-dns-record-awverify,
    azurerm_app_service_custom_hostname_binding.cancen,
    cloudflare_record.cloudflare-centralus-dns-record-awverify,
    azurerm_app_service_custom_hostname_binding.centralus
  ]
}

data "azurerm_key_vault" "cert" {
  name                = "${var.key-vault-name}"
  resource_group_name = "${var.key-vault-rg}"
}

data "azurerm_key_vault_secret" "cert" {
  name         = "${var.key-vault-secret-name}"
  key_vault_id = "${data.azurerm_key_vault.cert.id}"
}

resource "azurerm_key_vault_access_policy" "azure-app-service" {
  key_vault_id = "${data.azurerm_key_vault.cert.id}"
  tenant_id    = "${azurerm_app_service.centralus-app-service.identity.0.tenant_id}"

  # This object is the Microsoft Azure Web App Service magic SP 
  # as per https://azure.github.io/AppService/2016/05/24/Deploying-Azure-Web-App-Certificate-through-Key-Vault.html
  object_id = "abfa0a7c-a6b6-4736-8310-5855508787cd"

  secret_permissions = [
    "get"
  ]

  certificate_permissions = [
    "get"
  ]
}

resource "azurerm_key_vault_access_policy" "centralus" {
  key_vault_id = "${data.azurerm_key_vault.cert.id}"
  tenant_id    = "${azurerm_app_service.centralus-app-service.identity.0.tenant_id}"
  object_id    = "${azurerm_app_service.centralus-app-service.identity.0.principal_id}"

  secret_permissions = [
    "get"
  ]

  certificate_permissions = [
    "get"
  ]
}

resource "azurerm_key_vault_access_policy" "cancen" {
  key_vault_id = "${data.azurerm_key_vault.cert.id}"
  tenant_id    = "${azurerm_app_service.cancen-app-service.identity.0.tenant_id}"
  object_id    = "${azurerm_app_service.cancen-app-service.identity.0.principal_id}"

  secret_permissions = [
    "get"
  ]

  certificate_permissions = [
    "get"
  ]
}

resource "azurerm_app_service_certificate" "centralus" {
  name                = "${local.full_service_name}-cert"
  resource_group_name = "${azurerm_resource_group.centralus-rg.name}"
  location            = "${azurerm_resource_group.centralus-rg.location}"
  key_vault_secret_id = "${data.azurerm_key_vault_secret.cert.id}"
  depends_on = [
    azurerm_key_vault_access_policy.azure-app-service,
    data.azurerm_key_vault_secret.cert
  ]
}

resource "azurerm_app_service_certificate" "cancen" {
  name                = "${local.full_service_name}-cert"
  resource_group_name = "${azurerm_resource_group.cancen-rg.name}"
  location            = "${azurerm_resource_group.cancen-rg.location}"
  key_vault_secret_id = "${data.azurerm_key_vault_secret.cert.id}"
  depends_on = [
    azurerm_key_vault_access_policy.azure-app-service,
    data.azurerm_key_vault_secret.cert
  ]
}