terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.58"
    }
  }
}

locals {
  resource_group_name      = var.resource_group_name != "" ? var.resource_group_name : var.cluster_name
  node_resource_group_name = var.node_resource_group_name != "" ? var.node_resource_group_name : "${var.cluster_name}-node-rg"
  # Callers that attach AKS to a separately managed network must pass the
  # corresponding names. Those names are known during planning even when the
  # IDs come from resources created in the same apply.
  use_existing_network                = var.vnet_name != "" || var.subnet_name != ""
  use_existing_api_server_subnet      = var.api_server_subnet_id != ""
  use_module_managed_identity         = var.api_server_vnet_integration_enabled && var.control_plane_identity_id == ""
  use_user_assigned_control_plane     = var.control_plane_identity_id != "" || local.use_module_managed_identity
  use_user_assigned_kubelet           = var.kubelet_identity_id != "" || var.kubelet_identity_client_id != "" || var.kubelet_identity_object_id != ""
  user_assigned_kubelet_inputs_set    = var.kubelet_identity_id != "" && var.kubelet_identity_client_id != "" && var.kubelet_identity_object_id != ""
  user_assigned_kubelet_inputs_unset  = var.kubelet_identity_id == "" && var.kubelet_identity_client_id == "" && var.kubelet_identity_object_id == ""
  vnet_id                             = local.use_existing_network ? var.vnet_id : azurerm_virtual_network.this[0].id
  vnet_name                           = local.use_existing_network ? var.vnet_name : azurerm_virtual_network.this[0].name
  subnet_cidr                         = var.api_server_vnet_integration_enabled && !local.use_existing_network && var.subnet_cidr == "10.0.0.0/16" ? "10.0.0.0/17" : var.subnet_cidr
  subnet_id                           = local.use_existing_network ? var.subnet_id : azurerm_subnet.this[0].id
  subnet_name                         = local.use_existing_network ? var.subnet_name : azurerm_subnet.this[0].name
  api_server_subnet_id                = local.use_existing_api_server_subnet ? var.api_server_subnet_id : try(azurerm_subnet.api_server[0].id, "")
  control_plane_identity_id           = var.control_plane_identity_id != "" ? var.control_plane_identity_id : try(azurerm_user_assigned_identity.control_plane[0].id, "")
  control_plane_identity_principal_id = try(azurerm_user_assigned_identity.control_plane[0].principal_id, "")
  kubelet_identity = (
    local.use_module_managed_identity
    ? {
      id           = azurerm_user_assigned_identity.kubelet[0].id
      client_id    = azurerm_user_assigned_identity.kubelet[0].client_id
      principal_id = azurerm_user_assigned_identity.kubelet[0].principal_id
    }
    : {
      id           = var.kubelet_identity_id
      client_id    = var.kubelet_identity_client_id
      principal_id = var.kubelet_identity_object_id
    }
  )
  base_tags = merge({
    usedby      = "dbaas-control-plane-seed"
    tenant      = "admin"
    project     = "admin"
    component   = "regional-control-plane"
    cluster     = "admin"
    servicetype = "premium"
  }, var.tags)
}

check "existing_network_inputs" {
  assert {
    condition = (
      (!local.use_existing_network && var.vnet_name == "" && var.subnet_name == "") ||
      (local.use_existing_network && var.vnet_id != "" && var.vnet_name != "" && var.subnet_id != "" && var.subnet_name != "")
    )
    error_message = "vnet_id, vnet_name, subnet_id, and subnet_name must either all be empty or all be set together."
  }
}

check "user_assigned_identity_inputs" {
  assert {
    condition     = !local.use_user_assigned_control_plane || local.user_assigned_kubelet_inputs_set || local.use_module_managed_identity
    error_message = "kubelet_identity_id, kubelet_identity_client_id, and kubelet_identity_object_id must all be provided when control_plane_identity_id is set."
  }

  assert {
    condition     = local.user_assigned_kubelet_inputs_set || local.user_assigned_kubelet_inputs_unset
    error_message = "kubelet_identity_id, kubelet_identity_client_id, and kubelet_identity_object_id must be set together."
  }
}

check "api_server_vnet_integration_inputs" {
  assert {
    condition     = !var.api_server_vnet_integration_enabled || local.api_server_subnet_id != ""
    error_message = "api_server_subnet_id or api_server_subnet_cidr must provide a dedicated API server subnet when API Server VNet Integration is enabled."
  }

  assert {
    condition     = var.manage_api_server_subnet_role_assignment || var.control_plane_identity_id != ""
    error_message = "control_plane_identity_id must be provided when API server subnet role assignment is managed outside this module."
  }

  assert {
    condition     = !var.api_server_vnet_integration_enabled || !var.manage_api_server_subnet_role_assignment || var.control_plane_identity_id == ""
    error_message = "manage_api_server_subnet_role_assignment must be false when API Server VNet Integration uses a caller-provided control_plane_identity_id."
  }
}

resource "azurerm_resource_group" "this" {
  count = var.create_resource_group ? 1 : 0

  location = var.cloud_region
  name     = local.resource_group_name
  tags     = local.base_tags
}

resource "azurerm_virtual_network" "this" {
  count = local.use_existing_network ? 0 : 1

  name                = "${var.cluster_name}-vnet"
  location            = var.cloud_region
  resource_group_name = local.resource_group_name
  address_space       = [var.vnet_cidr]
  tags                = local.base_tags

  depends_on = [azurerm_resource_group.this]
}

resource "azurerm_public_ip" "nat" {
  name                = "${var.cluster_name}-nat-ip"
  location            = var.cloud_region
  resource_group_name = local.resource_group_name
  allocation_method   = "Static"
  sku                 = "Standard"
  tags                = local.base_tags

  depends_on = [azurerm_resource_group.this]
}

resource "azurerm_nat_gateway" "this" {
  name                = "${var.cluster_name}-nat-gateway"
  location            = var.cloud_region
  resource_group_name = local.resource_group_name
  sku_name            = "Standard"
  tags                = local.base_tags

  depends_on = [azurerm_resource_group.this]
}

resource "azurerm_nat_gateway_public_ip_association" "this" {
  nat_gateway_id       = azurerm_nat_gateway.this.id
  public_ip_address_id = azurerm_public_ip.nat.id
}

resource "azurerm_subnet" "this" {
  count = local.use_existing_network ? 0 : 1

  name                 = "${var.cluster_name}-subnet"
  resource_group_name  = local.resource_group_name
  virtual_network_name = local.vnet_name
  address_prefixes     = [local.subnet_cidr]
}

resource "azurerm_subnet" "api_server" {
  count = var.api_server_vnet_integration_enabled && !local.use_existing_api_server_subnet ? 1 : 0

  name                 = "${var.cluster_name}-api-server"
  resource_group_name  = local.resource_group_name
  virtual_network_name = local.vnet_name
  address_prefixes     = [var.api_server_subnet_cidr]

  delegation {
    name = "aks-api-server"

    service_delegation {
      name = "Microsoft.ContainerService/managedClusters"
      actions = [
        "Microsoft.Network/virtualNetworks/subnets/join/action",
      ]
    }
  }
}

resource "azurerm_subnet_nat_gateway_association" "this" {
  subnet_id      = local.subnet_id
  nat_gateway_id = azurerm_nat_gateway.this.id
}

resource "azurerm_user_assigned_identity" "control_plane" {
  count = local.use_module_managed_identity ? 1 : 0

  name                = "${var.cluster_name}-control-plane"
  location            = var.cloud_region
  resource_group_name = local.resource_group_name
  tags                = local.base_tags

  depends_on = [azurerm_resource_group.this]
}

resource "azurerm_user_assigned_identity" "kubelet" {
  count = local.use_module_managed_identity ? 1 : 0

  name                = "${var.cluster_name}-kubelet"
  location            = var.cloud_region
  resource_group_name = local.resource_group_name
  tags                = local.base_tags

  depends_on = [azurerm_resource_group.this]
}

resource "azurerm_role_assignment" "kubelet_identity_operator" {
  count = local.use_module_managed_identity ? 1 : 0

  scope                = azurerm_user_assigned_identity.kubelet[0].id
  role_definition_name = "Managed Identity Operator"
  principal_id         = azurerm_user_assigned_identity.control_plane[0].principal_id
}

resource "azurerm_kubernetes_cluster" "this" {
  location                  = var.cloud_region
  name                      = var.cluster_name
  resource_group_name       = local.resource_group_name
  node_resource_group       = local.node_resource_group_name
  dns_prefix                = replace(var.cluster_name, "_", "-")
  kubernetes_version        = var.k8s_version
  oidc_issuer_enabled       = true
  workload_identity_enabled = true
  node_os_upgrade_channel   = "None"
  sku_tier                  = "Standard"
  local_account_disabled    = false

  api_server_access_profile {
    authorized_ip_ranges                = var.authorized_ip_ranges
    virtual_network_integration_enabled = var.api_server_vnet_integration_enabled
    subnet_id                           = var.api_server_vnet_integration_enabled ? local.api_server_subnet_id : null
  }

  default_node_pool {
    name                        = "control"
    temporary_name_for_rotation = "tmpctrl"
    vm_size                     = var.vm_size
    os_sku                      = "AzureLinux"
    auto_scaling_enabled        = var.nodepool_autoscaling_enabled
    node_count                  = var.nodepool_autoscaling_enabled ? null : var.nodepool_size
    min_count                   = var.nodepool_autoscaling_enabled ? var.nodepool_min_size : null
    max_count                   = var.nodepool_autoscaling_enabled ? var.nodepool_max_size : null
    node_labels                 = var.nodepool_labels
    vnet_subnet_id              = local.subnet_id
    zones                       = length(var.availability_zones) > 0 ? var.availability_zones : null

    upgrade_settings {
      max_surge = "10%"
    }

    tags = local.base_tags
  }

  network_profile {
    network_plugin    = "azure"
    network_policy    = "azure"
    dns_service_ip    = var.dns_service_ip
    service_cidr      = var.service_cidr
    load_balancer_sku = "standard"
    outbound_type     = "userAssignedNATGateway"

    nat_gateway_profile {
      idle_timeout_in_minutes = 4
    }
  }

  identity {
    type         = local.use_user_assigned_control_plane ? "UserAssigned" : "SystemAssigned"
    identity_ids = local.use_user_assigned_control_plane ? [local.control_plane_identity_id] : null
  }

  dynamic "kubelet_identity" {
    for_each = local.use_user_assigned_kubelet || local.use_module_managed_identity ? [1] : []

    content {
      client_id                 = local.kubelet_identity.client_id
      object_id                 = local.kubelet_identity.principal_id
      user_assigned_identity_id = local.kubelet_identity.id
    }
  }

  azure_active_directory_role_based_access_control {
    admin_group_object_ids = var.admin_group_object_ids
    azure_rbac_enabled     = var.azure_rbac_enabled
  }

  dynamic "ingress_application_gateway" {
    for_each = var.ingress_application_gateway_id == "" ? [] : [var.ingress_application_gateway_id]

    content {
      gateway_id = ingress_application_gateway.value
    }
  }

  tags = local.base_tags

  lifecycle {
    precondition {
      condition     = var.azure_rbac_enabled || length(var.admin_group_object_ids) > 0
      error_message = "admin_group_object_ids must be provided unless azure_rbac_enabled is true."
    }

    ignore_changes = [
      network_profile[0].nat_gateway_profile,
    ]
  }

  depends_on = [
    azurerm_nat_gateway_public_ip_association.this,
    azurerm_subnet_nat_gateway_association.this,
    azurerm_role_assignment.kubelet_identity_operator,
    azurerm_role_assignment.node_subnet_network_contributor,
    azurerm_role_assignment.api_server_network_contributor,
  ]
}

moved {
  from = azurerm_role_assignment.network_contributor
  to   = azurerm_role_assignment.network_contributor[0]
}

moved {
  from = azurerm_role_assignment.acr_pull
  to   = azurerm_role_assignment.acr_pull[0]
}

resource "azurerm_role_assignment" "network_contributor" {
  count = var.create_role_assignments && !var.api_server_vnet_integration_enabled ? 1 : 0

  scope                = local.subnet_id
  role_definition_name = "Network Contributor"
  principal_id         = azurerm_kubernetes_cluster.this.identity[0].principal_id

  depends_on = [azurerm_kubernetes_cluster.this]
}

resource "azurerm_role_assignment" "node_subnet_network_contributor" {
  count = (
    var.create_role_assignments &&
    var.api_server_vnet_integration_enabled &&
    var.manage_api_server_subnet_role_assignment
  ) ? 1 : 0

  scope                = local.subnet_id
  role_definition_name = "Network Contributor"
  principal_id         = local.control_plane_identity_principal_id
}

resource "azurerm_role_assignment" "api_server_network_contributor" {
  count = (
    var.create_role_assignments &&
    var.api_server_vnet_integration_enabled &&
    var.manage_api_server_subnet_role_assignment
  ) ? 1 : 0

  scope                = local.api_server_subnet_id
  role_definition_name = "Network Contributor"
  principal_id         = local.control_plane_identity_principal_id
}

resource "azurerm_role_assignment" "acr_pull" {
  count = var.create_role_assignments ? 1 : 0

  scope                = var.acr_id_scope
  role_definition_name = "AcrPull"
  principal_id         = azurerm_kubernetes_cluster.this.kubelet_identity[0].object_id
}

output "cluster_id" {
  value = azurerm_kubernetes_cluster.this.id
}

output "cluster_name" {
  value = azurerm_kubernetes_cluster.this.name
}

output "resource_group_name" {
  value = azurerm_kubernetes_cluster.this.resource_group_name
}

output "node_resource_group_name" {
  value = azurerm_kubernetes_cluster.this.node_resource_group
}

output "endpoint" {
  value = azurerm_kubernetes_cluster.this.kube_admin_config[0].host
}

output "kubeconfig" {
  value     = azurerm_kubernetes_cluster.this.kube_admin_config_raw
  sensitive = true
}

output "argocd_config" {
  value = jsonencode({
    tlsClientConfig = {
      insecure = false
      certData = azurerm_kubernetes_cluster.this.kube_admin_config[0].client_certificate
      keyData  = azurerm_kubernetes_cluster.this.kube_admin_config[0].client_key
      caData   = azurerm_kubernetes_cluster.this.kube_admin_config[0].cluster_ca_certificate
    }
  })
  sensitive = true
}

output "oidc_issuer" {
  value = azurerm_kubernetes_cluster.this.oidc_issuer_url
}

output "public_ips" {
  value = [azurerm_public_ip.nat.ip_address]
}

output "vnet_id" {
  value = local.vnet_id
}

output "subnet_id" {
  value = local.subnet_id
}

output "subnet_name" {
  value = local.subnet_name
}

output "vnet_name" {
  value = local.vnet_name
}

output "api_server_vnet_integration_enabled" {
  value = var.api_server_vnet_integration_enabled
}
