provider "aws" {
  region = var.region 
}

################################################################################
# create VPC 
################################################################################

resource "random_id" "random" {
  byte_length = 8
}

data "aws_availability_zones" "available" {
}

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "tidb_demo_vpc${random_id.random.hex}"
  cidr = "10.0.0.0/16"

  azs  = data.aws_availability_zones.available.names 
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = true
  enable_dns_hostnames = true

  # The following tags are required for ELB
  private_subnet_tags = {
    "kubernetes.io/cluster/tidb-demo${random_id.random.hex}" = "shared"
    "kubernetes.io/role/internal-elb" = 1 
  }
  public_subnet_tags = {
    "kubernetes.io/cluster/tidb-demo${random_id.random.hex}" = "shared"
    "kubernetes.io/role/elb" = 1
  }
  vpc_tags = {
    "kubernetes.io/cluster/tidb-demo${random_id.random.hex}" = "shared"
  }

  tags = {
    Terraform = "true"
    Environment = "demo"
  }
}

resource "aws_vpc_endpoint" "s3" {
  vpc_id = module.vpc.vpc_id
  service_name = "com.amazonaws.${var.region}.s3"
  route_table_ids = concat(module.vpc.public_route_table_ids, module.vpc.private_route_table_ids)
}


################################################################################
# create EKS 
################################################################################
module "eks" {
  source = "terraform-aws-modules/eks/aws"
  version = "17.18.0"

  cluster_name       = "tidb-demo${random_id.random.hex}" 
  cluster_version    = var.eks_version
  vpc_id             = module.vpc.vpc_id 
  subnets            = module.vpc.private_subnets

  wait_for_cluster_timeout = 60

  tags = {
    app = "tidb"
  }
  
  map_roles = [
    {
      rolearn  = aws_iam_role.jump.arn
      username = "jump"
      groups   = ["system:masters"]
    },
  ]

  node_groups = concat(
    # pd nodes 
    [
        for subnet in module.vpc.private_subnets:
        {
          name = "pd-${subnet}"
          launch_template_id      = aws_launch_template.pd.id
          launch_template_version = aws_launch_template.pd.default_version
          instance_types = [var.pd_instance_type]
          desired_capacity = var.pd_instance_number 
          subnets = [subnet] 
          k8s_labels = {dedicated = "pd"}
          taints = [{
            key = "dedicated"
            value = "pd"
            effect = "NO_SCHEDULE"
          }]
        }
    ],
    # tidb nodes 
    [
        for subnet in module.vpc.private_subnets:
        {
          name = "tidb-${subnet}"
          launch_template_id      = aws_launch_template.tidb.id
          launch_template_version = aws_launch_template.tidb.default_version
          instance_types = [var.tidb_instance_type]
          desired_capacity = var.tidb_instance_number 
          max_capacity = var.tidb_instance_number + var.tidb_instance_number_scaleout
          subnets = [subnet] 
          k8s_labels = {dedicated = "tidb"}
          taints = [{
            key = "dedicated"
            value = "tidb"
            effect = "NO_SCHEDULE"
          }]
        }
    ],
    # tikv nodes 
    [
        for subnet in module.vpc.private_subnets:
        {
          name = "tikv-${subnet}"
          launch_template_id      = aws_launch_template.tikv.id
          launch_template_version = aws_launch_template.tikv.default_version
          instance_types = [var.tikv_instance_type]
          desired_capacity = var.tikv_instance_number 
          max_capacity = var.tikv_instance_number + var.tikv_instance_number_scaleout
          subnets = [subnet] 
          k8s_labels = {dedicated = "tikv"}
          taints = [{
            key = "dedicated"
            value = "tikv"
            effect = "NO_SCHEDULE"
          }]
        }
    ],
    # admin node
    [
        {
          name = "admin"
          instance_types = [var.admin_instance_type]
          disk_size = var.admin_instance_disk_size
          desired_capacity = 1 
          max_capacity = 1 
          subnets = module.vpc.private_subnets
          k8s_labels = {dedicated = "admin"}
        }
    ]
  )
}



# lauch templates
################################################################################
resource "aws_launch_template" "pd" {
  name_prefix            = "tidb-demo-pd"
  update_default_version = true

  block_device_mappings {
    device_name = "/dev/xvda"
    ebs {
        volume_size = var.pd_instance_disk_size
        volume_type = "gp3"
    }
  }
}

resource "aws_launch_template" "tidb" {
  name_prefix            = "tidb-demo-tidb"
  update_default_version = true

  block_device_mappings {
    device_name = "/dev/xvda"
    ebs {
        volume_size = var.tidb_instance_disk_size
        volume_type = "gp3"
    }
  }
}

resource "aws_launch_template" "tikv" {
  name_prefix            = "tidb-demo-tikv"
  update_default_version = true

  block_device_mappings {
    device_name = "/dev/xvda"
    ebs {
        volume_size = var.tikv_instance_disk_size
        iops = var.tikv_ebs_iops
        throughput = var.tikv_ebs_throughput
        volume_type = "gp3"
    }
  }
}

resource "time_sleep" "nlb_creation_wait" {
  # tidb nlbs are created in bootstrap of the jump server
  depends_on = [aws_instance.jump]
  create_duration = "180s"
}

data "aws_lb" "tidb" {
  tags = {
    "kubernetes.io/service-name" = "tidb-cluster/basic-tidb"
  }
  depends_on = [time_sleep.nlb_creation_wait]
}

data "aws_lb" "grafana" {
  tags = {
    "kubernetes.io/service-name" = "tidb-cluster/basic-grafana"
  }
  depends_on = [time_sleep.nlb_creation_wait]
}

data "aws_lb" "dashboard" {
  tags = {
    "kubernetes.io/service-name" = "ingress-nginx/ingress-nginx-controller"
  }
  depends_on = [time_sleep.nlb_creation_wait]
}

# kubernetes
################################################################################
data "aws_eks_cluster" "cluster" {
  name = module.eks.cluster_id
}

data "aws_eks_cluster_auth" "cluster" {
  name = module.eks.cluster_id
}

provider "kubernetes" {
  host                   = data.aws_eks_cluster.cluster.endpoint
  cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
  token                  = data.aws_eks_cluster_auth.cluster.token
}

################################################################################
# jump server
################################################################################
resource "aws_security_group" "jump" {
  name        = "jump"
  description = "Allow ssh access to jump server" 
  vpc_id      = module.vpc.vpc_id 
}

resource "aws_security_group_rule" "ssh" {
  type              = "ingress"
  from_port         = 22 
  to_port           = 22
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"] 
  security_group_id = aws_security_group.jump.id 
}

resource "aws_security_group_rule" "web" {
  type              = "ingress"
  from_port         = 8000 
  to_port           = 8000 
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"] 
  security_group_id = aws_security_group.jump.id 
}

resource "aws_security_group_rule" "jump_out" {
  type              = "egress"
  from_port         = 0 
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"] 
  ipv6_cidr_blocks  = ["::/0"]
  security_group_id = aws_security_group.jump.id 
}

# instance profile to access s3 bucket
resource "aws_iam_instance_profile" "jump" {
  name = "jump${random_id.random.hex}"
  role = aws_iam_role.jump.name
}

resource "aws_iam_role" "jump" {
  name = "jump${random_id.random.hex}"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Sid    = ""
        Principal = {
          Service = "ec2.amazonaws.com"
        }
      },
    ]
  })

  inline_policy {
    name = "my_inline_policy"

    policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Action   = ["s3:*"]
          Effect   = "Allow"
          Resource = [aws_s3_bucket.tidb_demo.arn, "${aws_s3_bucket.tidb_demo.arn}/*"]
        },
      ]
    })
  }
}

resource "aws_instance" "jump" {
  ami = var.ami_id[var.region] 
  instance_type = var.jump_instance_type 
  subnet_id = module.vpc.public_subnets[0]

  root_block_device {
    volume_size = var.jump_instance_disk_size
    volume_type = "gp3"
  }

  vpc_security_group_ids = [aws_security_group.jump.id]
  key_name = aws_key_pair.demo.key_name
  iam_instance_profile = aws_iam_instance_profile.jump.name

  user_data = templatefile("${path.module}/jump_userdata.yaml",{
                bucket_name = aws_s3_bucket.tidb_demo.bucket
                private_key = filebase64(local_file.private_key.filename)
                kube_config = base64encode(module.eks.kubeconfig)
              })

  tags = {
    Name = "jump server"
    # this is a walkaround to allow destroy provisioner know the private key name
    # https://github.com/hashicorp/terraform/issues/23679
    "private_key_name" = local_file.private_key.filename
  }

  # wait until all init commands finishes
  provisioner "remote-exec" {
    inline = [
      "sudo cloud-init status --wait"
    ]
   
    connection {
        type     = "ssh"
        user     = "centos"
        host     = aws_instance.jump.public_ip
        private_key = file(local_file.private_key.filename)
    }
  }

  # delete nlbs provided by kubernetes#
  provisioner "remote-exec" {
    when = destroy
    inline = [". /home/centos/tioperator/files_k8s/uninstall_tidb.sh"]
   
    connection {
        type     = "ssh"
        user     = "centos"
        host     = self.public_ip
        private_key = file(self.tags_all["private_key_name"])
    }
  }

  # jump server needs to wait for all s3 files been uploaded
  depends_on = [
    module.eks,
    module.vpc,
    aws_s3_bucket_object.files,
    aws_vpc_endpoint.s3,
    aws_security_group_rule.ssh, 
    aws_security_group_rule.jump_out 
  ]
}

# key pair
################################################################################
resource "tls_private_key" "demo" {
  algorithm = "RSA"
}

resource "aws_key_pair" "demo" {
  key_name   = "demo${random_id.random.hex}"
  public_key = tls_private_key.demo.public_key_openssh

  lifecycle {
    ignore_changes = [key_name]
  }
}

resource "local_file" "public_key" {
  content  = tls_private_key.demo.public_key_openssh
  filename = "${path.module}/tidb_demo${random_id.random.hex}.pub"
}

resource "local_file" "private_key" {
  content  = tls_private_key.demo.private_key_pem
  filename = "${path.module}/tidb_demo${random_id.random.hex}.pem"
}

resource "null_resource" "chmod" {
  depends_on = [local_file.private_key]

  triggers = {
    key = tls_private_key.demo.private_key_pem
  }

  provisioner "local-exec" {
    command = "chmod 400 ${local_file.private_key.filename}"
  }
}

################################################################################
# s3 bucket and files to store supporting data of tidb cluster creation 
################################################################################

resource "aws_s3_bucket" "tidb_demo" {
  bucket = "tidb-demo${random_id.random.hex}"
  acl    = "private"
}

resource "aws_s3_bucket_object" "files" {
  for_each = fileset("${path.module}/..", "{demos,tioperator}/**/files_k8s/*.{template,sh}")

  key     = each.value 
  bucket  = aws_s3_bucket.tidb_demo.id
  acl     = "private"

  content = templatefile("${path.module}/../${each.value}", {
                s3_bucket = aws_s3_bucket.tidb_demo.id
                region = var.region
            }) 
}



