terraform {
  required_version = ">= 1.0"

  required_providers {
    enos = {
      source = "app.terraform.io/hashicorp-qti/enos"
    }

    helm = {
      source  = "hashicorp/helm"
      version = "2.6.0"
    }
  }
}

locals {
  helm_chart_settings = {
    "server.ha.enabled"             = "true"
    "server.ha.replicas"            = var.vault_instance_count
    "server.ha.raft.enabled"        = "true"
    "server.affinity"               = ""
    "server.image.repository"       = var.image_repository
    "server.image.tag"              = var.image_tag
    "server.image.pullPolicy"       = "Never" # Forces local image use
    "server.resources.requests.cpu" = "50m"
    "server.limits.memory"          = "200m"
    "server.limits.cpu"             = "200m"
    "server.ha.raft.config"         = file("${abspath(path.module)}/raft-config.hcl")
    "server.dataStorage.size"       = "100m"
    "server.logLevel"               = var.vault_log_level
  }
  all_helm_chart_settings = var.ent_license == null ? local.helm_chart_settings : merge(local.helm_chart_settings, {
    "server.extraEnvironmentVars.VAULT_LICENSE" = var.ent_license
  })

  vault_address = "http://127.0.0.1:8200"

  instance_indexes = [for idx in range(var.vault_instance_count) : tostring(idx)]

  leader_idx    = local.instance_indexes[0]
  followers_idx = toset(slice(local.instance_indexes, 1, var.vault_instance_count))
}

resource "helm_release" "vault" {
  name = "vault"

  repository = "https://helm.releases.hashicorp.com"
  chart      = "vault"

  dynamic "set" {
    for_each = local.all_helm_chart_settings

    content {
      name  = set.key
      value = set.value
    }
  }
}

data "enos_kubernetes_pods" "vault_pods" {
  kubeconfig_base64 = var.kubeconfig_base64
  context_name      = var.context_name
  namespace         = helm_release.vault.namespace
  label_selectors = [
    "app.kubernetes.io/name=vault",
    "component=server"
  ]

  depends_on = [helm_release.vault]
}

resource "enos_vault_init" "leader" {
  bin_path   = "/bin/vault"
  vault_addr = local.vault_address

  key_shares    = 5
  key_threshold = 3

  transport = {
    kubernetes = {
      kubeconfig_base64 = var.kubeconfig_base64
      context_name      = var.context_name
      pod               = data.enos_kubernetes_pods.vault_pods.pods[local.leader_idx].name
      namespace         = data.enos_kubernetes_pods.vault_pods.pods[local.leader_idx].namespace
    }
  }
}

resource "enos_vault_unseal" "leader" {
  bin_path    = "/bin/vault"
  vault_addr  = local.vault_address
  seal_type   = "shamir"
  unseal_keys = enos_vault_init.leader.unseal_keys_b64

  transport = {
    kubernetes = {
      kubeconfig_base64 = var.kubeconfig_base64
      context_name      = var.context_name
      pod               = data.enos_kubernetes_pods.vault_pods.pods[local.leader_idx].name
      namespace         = data.enos_kubernetes_pods.vault_pods.pods[local.leader_idx].namespace
    }
  }

  depends_on = [enos_vault_init.leader]
}

// We need to manually join the followers since the join request must only happen after the leader
// has been initialized. We could use retry join, but in that case we'd need to restart the follower
// pods once the leader is setup. The default helm deployment configuration for an HA cluster as
// documented here: https://learn.hashicorp.com/tutorials/vault/kubernetes-raft-deployment-guide#configure-vault-helm-chart
// uses a liveness probe that automatically restarts nodes that are not healthy. This works well for
// clusters that are configured with auto-unseal as eventually the nodes would join and unseal.
resource "enos_remote_exec" "raft_join" {
  for_each = local.followers_idx

  inline = [
    // asserts that vault is ready
    "for i in 1 2 3 4 5; do vault status > /dev/null 2>&1 && break || sleep 5; done",
    // joins the follower to the leader
    "vault operator raft join http://vault-0.vault-internal:8200"
  ]

  transport = {
    kubernetes = {
      kubeconfig_base64 = var.kubeconfig_base64
      context_name      = var.context_name
      pod               = data.enos_kubernetes_pods.vault_pods.pods[each.key].name
      namespace         = data.enos_kubernetes_pods.vault_pods.pods[each.key].namespace
    }
  }

  depends_on = [enos_vault_unseal.leader]
}


resource "enos_vault_unseal" "followers" {
  for_each = local.followers_idx

  bin_path    = "/bin/vault"
  vault_addr  = local.vault_address
  seal_type   = "shamir"
  unseal_keys = enos_vault_init.leader.unseal_keys_b64

  transport = {
    kubernetes = {
      kubeconfig_base64 = var.kubeconfig_base64
      context_name      = var.context_name
      pod               = data.enos_kubernetes_pods.vault_pods.pods[each.key].name
      namespace         = data.enos_kubernetes_pods.vault_pods.pods[each.key].namespace
    }
  }

  depends_on = [enos_remote_exec.raft_join]
}

output "vault_root_token" {
  value = enos_vault_init.leader.root_token
}

output "vault_pods" {
  value = data.enos_kubernetes_pods.vault_pods.pods
}
