terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "3.74.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "3.6.0"
    }
  }
}

provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = false
    }
  }
}

locals {
  uid              = random_id.uid.hex
  name             = "${var.name}-${local.uid}"
  init_secret_hash = random_password.init_secret.bcrypt_hash
  tags = {
    constellation-uid = local.uid,
  }
  ports_node_range      = "30000-32767"
  cidr_vpc_subnet_nodes = "10.9.0.0/16"
  ports = flatten([
    { name = "kubernetes", port = "6443", health_check_protocol = "Https", path = "/readyz", priority = 100 },
    { name = "bootstrapper", port = "9000", health_check_protocol = "Tcp", path = null, priority = 101 },
    { name = "verify", port = "30081", health_check_protocol = "Tcp", path = null, priority = 102 },
    { name = "konnectivity", port = "8132", health_check_protocol = "Tcp", path = null, priority = 103 },
    { name = "recovery", port = "9999", health_check_protocol = "Tcp", path = null, priority = 104 },
    { name = "join", port = "30090", health_check_protocol = "Tcp", path = null, priority = 105 },
    var.debug ? [{ name = "debugd", port = "4000", health_check_protocol = "Tcp", path = null, priority = 106 }] : [],
  ])
  // wildcard_lb_dns_name is the DNS name of the load balancer with a wildcard for the name.
  // example: given "name-1234567890.location.cloudapp.azure.com" it will return "*.location.cloudapp.azure.com"
  wildcard_lb_dns_name = var.internal_load_balancer ? "" : replace(data.azurerm_public_ip.loadbalancer_ip[0].fqdn, "/^[^.]*\\./", "*.")
  // deduce from format (subscriptions)/$ID/resourceGroups/$RG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/$NAME"
  // move from the right as to ignore the optional prefixes
  uai_resource_group = element(split("/", var.user_assigned_identity), length(split("/", var.user_assigned_identity)) - 5)
  // deduce as above
  uai_name = element(split("/", var.user_assigned_identity), length(split("/", var.user_assigned_identity)) - 1)

  in_cluster_endpoint     = var.internal_load_balancer ? azurerm_lb.loadbalancer.frontend_ip_configuration[0].private_ip_address : azurerm_public_ip.loadbalancer_ip[0].ip_address
  out_of_cluster_endpoint = var.debug && var.internal_load_balancer ? module.jump_host[0].ip : local.in_cluster_endpoint
}

resource "random_id" "uid" {
  byte_length = 4
}

resource "random_password" "init_secret" {
  length           = 32
  special          = true
  override_special = "_%@"
}

resource "azurerm_attestation_provider" "attestation_provider" {
  count = var.create_maa ? 1 : 0
  # name must be between 3 and 24 characters in length and use numbers and lower-case letters only.
  name                = format("constell%s", local.uid)
  resource_group_name = var.resource_group
  location            = var.location

  lifecycle {
    # Attestation policies will be set automatically upon creation, even if not specified in the resource,
    # while they aren't being incorporated into the Terraform state correctly.
    # To prevent them from being set to null when applying an upgrade, ignore the changes until the issue
    # is resolved by Azure.
    # Related issue: https://github.com/hashicorp/terraform-provider-azurerm/issues/21998
    ignore_changes = [open_enclave_policy_base64, sgx_enclave_policy_base64, tpm_policy_base64, sev_snp_policy_base64]
  }
}

resource "azurerm_application_insights" "insights" {
  name                = local.name
  location            = var.location
  resource_group_name = var.resource_group
  application_type    = "other"
  tags                = local.tags
}

resource "azurerm_public_ip" "loadbalancer_ip" {
  count               = var.internal_load_balancer ? 0 : 1
  name                = "${local.name}-lb"
  domain_name_label   = local.name
  resource_group_name = var.resource_group
  location            = var.location
  allocation_method   = "Static"
  sku                 = "Standard"
  tags                = local.tags

  lifecycle {
    ignore_changes = [name]
  }
}

// Reads data from the resource of the same name.
// Used to wait to the actual resource to become ready, before using data from that resource.
// Property "fqdn" only becomes available on azurerm_public_ip resources once domain_name_label is set.
// Since we are setting domain_name_label starting with 2.10 we need to migrate
// resources for clusters created before 2.9. In those cases we need to wait until loadbalancer_ip has
// been updated before reading from it.
data "azurerm_public_ip" "loadbalancer_ip" {
  count               = var.internal_load_balancer ? 0 : 1
  name                = "${local.name}-lb"
  resource_group_name = var.resource_group
  depends_on          = [azurerm_public_ip.loadbalancer_ip]
}

resource "azurerm_public_ip" "nat_gateway_ip" {
  name                = "${local.name}-nat"
  resource_group_name = var.resource_group
  location            = var.location
  allocation_method   = "Static"
  sku                 = "Standard"
  tags                = local.tags
}

resource "azurerm_nat_gateway" "gateway" {
  name                    = local.name
  location                = var.location
  resource_group_name     = var.resource_group
  sku_name                = "Standard"
  idle_timeout_in_minutes = 10
}

resource "azurerm_subnet_nat_gateway_association" "example" {
  nat_gateway_id = azurerm_nat_gateway.gateway.id
  subnet_id      = azurerm_subnet.node_subnet.id
}

resource "azurerm_nat_gateway_public_ip_association" "example" {
  nat_gateway_id       = azurerm_nat_gateway.gateway.id
  public_ip_address_id = azurerm_public_ip.nat_gateway_ip.id
}

resource "azurerm_lb" "loadbalancer" {
  name                = local.name
  location            = var.location
  resource_group_name = var.resource_group
  sku                 = "Standard"
  tags                = local.tags

  dynamic "frontend_ip_configuration" {
    for_each = var.internal_load_balancer ? [] : [1]
    content {
      name                 = "PublicIPAddress"
      public_ip_address_id = azurerm_public_ip.loadbalancer_ip[0].id
    }
  }

  dynamic "frontend_ip_configuration" {
    for_each = var.internal_load_balancer ? [1] : []
    content {
      name                          = "PrivateIPAddress"
      private_ip_address_allocation = "Dynamic"
      subnet_id                     = azurerm_subnet.loadbalancer_subnet[0].id
    }
  }
}

module "loadbalancer_backend_control_plane" {
  source = "./modules/load_balancer_backend"

  name                           = "${local.name}-control-plane"
  loadbalancer_id                = azurerm_lb.loadbalancer.id
  frontend_ip_configuration_name = azurerm_lb.loadbalancer.frontend_ip_configuration[0].name
  ports                          = local.ports
}

module "loadbalancer_backend_worker" {
  source = "./modules/load_balancer_backend"

  name                           = "${local.name}-worker"
  loadbalancer_id                = azurerm_lb.loadbalancer.id
  frontend_ip_configuration_name = azurerm_lb.loadbalancer.frontend_ip_configuration[0].name
  ports                          = []
}

resource "azurerm_lb_backend_address_pool" "all" {
  loadbalancer_id = azurerm_lb.loadbalancer.id
  name            = "${var.name}-all"
}

resource "azurerm_virtual_network" "network" {
  name                = local.name
  resource_group_name = var.resource_group
  location            = var.location
  address_space       = ["10.0.0.0/8"]
  tags                = local.tags
}

resource "azurerm_subnet" "loadbalancer_subnet" {
  count                = var.internal_load_balancer ? 1 : 0
  name                 = "${local.name}-lb"
  resource_group_name  = var.resource_group
  virtual_network_name = azurerm_virtual_network.network.name
  address_prefixes     = ["10.10.0.0/16"]
}

resource "azurerm_subnet" "node_subnet" {
  name                 = "${local.name}-node"
  resource_group_name  = var.resource_group
  virtual_network_name = azurerm_virtual_network.network.name
  address_prefixes     = [local.cidr_vpc_subnet_nodes]
}

resource "azurerm_network_security_group" "security_group" {
  name                = local.name
  location            = var.location
  resource_group_name = var.resource_group
  tags                = local.tags

  dynamic "security_rule" {
    for_each = concat(
      local.ports,
      [{ name = "nodeports", port = local.ports_node_range, priority = 200 }]
    )
    content {
      name                       = security_rule.value.name
      priority                   = security_rule.value.priority
      direction                  = "Inbound"
      access                     = "Allow"
      protocol                   = "Tcp"
      source_port_range          = "*"
      destination_port_range     = security_rule.value.port
      source_address_prefix      = "*"
      destination_address_prefix = "*"
    }
  }
}

module "scale_set_group" {
  source          = "./modules/scale_set"
  for_each        = var.node_groups
  base_name       = local.name
  node_group_name = each.key
  role            = each.value.role
  zones           = each.value.zones
  tags = merge(
    local.tags,
    { constellation-init-secret-hash = local.init_secret_hash },
    { constellation-maa-url = var.create_maa ? azurerm_attestation_provider.attestation_provider[0].attestation_uri : "" },
  )

  initial_count             = each.value.initial_count
  state_disk_size           = each.value.disk_size
  state_disk_type           = each.value.disk_type
  location                  = var.location
  instance_type             = each.value.instance_type
  confidential_vm           = var.confidential_vm
  secure_boot               = var.secure_boot
  resource_group            = var.resource_group
  user_assigned_identity    = var.user_assigned_identity
  image_id                  = var.image_id
  network_security_group_id = azurerm_network_security_group.security_group.id
  subnet_id                 = azurerm_subnet.node_subnet.id
  backend_address_pool_ids = each.value.role == "control-plane" ? [
    azurerm_lb_backend_address_pool.all.id,
    module.loadbalancer_backend_control_plane.backendpool_id
    ] : [
    azurerm_lb_backend_address_pool.all.id,
    module.loadbalancer_backend_worker.backendpool_id
  ]
  marketplace_image = var.marketplace_image
}

module "jump_host" {
  count          = var.internal_load_balancer && var.debug ? 1 : 0
  source         = "./modules/jump_host"
  base_name      = local.name
  resource_group = var.resource_group
  location       = var.location
  subnet_id      = azurerm_subnet.loadbalancer_subnet[0].id
  ports          = [for port in local.ports : port.port]
  lb_internal_ip = azurerm_lb.loadbalancer.frontend_ip_configuration[0].private_ip_address
}

data "azurerm_subscription" "current" {
}

data "azurerm_user_assigned_identity" "uaid" {
  name                = local.uai_name
  resource_group_name = local.uai_resource_group
}
