# Specify the provider and access details
provider "aws" {
  region = var.aws_region
}

data "aws_caller_identity" "current" {}

# Get the latest PDE Debian 12 AMI id by default, or look up custom AMI
data "aws_ami" "ami" {
  most_recent = var.custom_ami_id == "" ? true : null
  owners      = var.custom_ami_id == "" ? ["086679231553"] : null

  dynamic "filter" {
    for_each = var.custom_ami_id == "" ? [" "] : []
    content {
      name   = "name"
      values = ["debian-12-amd64-*"]
    }
  }

  dynamic "filter" {
    for_each = var.custom_ami_id == "" ? [" "] : []
    content {
      name   = "architecture"
      values = ["x86_64"]
    }
  }

  dynamic "filter" {
    for_each = var.custom_ami_id != "" ? [" "] : []
    content {
      name   = "image-id"
      values = [var.custom_ami_id]
    }
  }
}

data "aws_vpc" "vpc" {
  id = var.vpc_id
}

# Get private subnets for default allow rules
data "aws_subnets" "private_subnet_ids" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.vpc.id]
  }

  filter {
    name   = "tag:tier"
    values = ["private"]
  }
}

data "aws_subnet" "private_subnets" {
  for_each = toset(data.aws_subnets.private_subnet_ids.ids)
  id       = each.value
}

# IAM policy document for ec2 instance's role within ASG
data "aws_iam_policy_document" "instance-assume-role-policy" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
  }
  statement {
    sid     = "AllowRoleToAssumeItself"
    actions = ["sts:AssumeRole"]

    principals {
      type = "AWS"
      identifiers = [
        "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root",
      ]
    }

    condition {
      test = "ArnEquals"
      values = [
        "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.aws_stack_name}",
      ]
      variable = "aws:PrincipalArn"
    }
  }
}

# Policy document for IAM policy
data "aws_iam_policy_document" "iam_policy_document_ecr" {
  statement {
    actions = [
      "ecr:GetAuthorizationToken",
    ]
    resources = [
      "*",
    ]
  }
}

data "aws_iam_policy_document" "ecr_policy_document" {
  statement {
    sid = var.aws_stack_name
    actions = [
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchGetImage",
      "ecr:BatchCheckLayerAvailability",
    ]
    principals {
      identifiers = [
        aws_iam_role.iam-role.arn,
      ]
      type = "AWS"
    }
  }
}

# Security group for agents.
resource "aws_security_group" "security_group" {
  name   = var.aws_stack_name
  vpc_id = data.aws_vpc.vpc.id

  tags = {
    Name        = var.aws_stack_name
    terraformed = true
  }
}

resource "aws_security_group_rule" "security_group_ingress_rule_ssh" {
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
  security_group_id = aws_security_group.security_group.id
  type              = "ingress"
  cidr_blocks = concat(
    var.additional_ssh_cidr_blocks,
    [for s in data.aws_subnet.private_subnets : s.cidr_block],
  )
}

resource "aws_security_group_rule" "security_group_egress_rule" {
  from_port         = 0
  to_port           = 65535
  protocol          = "tcp"
  security_group_id = aws_security_group.security_group.id
  type              = "egress"
  cidr_blocks = [
    "0.0.0.0/0",
  ]
}

# IAM role
resource "aws_iam_role" "iam-role" {
  name               = var.aws_stack_name
  assume_role_policy = data.aws_iam_policy_document.instance-assume-role-policy.json
}

# IAM instance profile
resource "aws_iam_instance_profile" "iam-instance-profile" {
  name = var.aws_stack_name
  role = coalesce(var.existing_iam_role_name, aws_iam_role.iam-role.name)
}

resource "aws_iam_policy" "iam_policy_ecr" {
  name   = "ECR-${var.aws_stack_name}-getAuthToken"
  policy = data.aws_iam_policy_document.iam_policy_document_ecr.json
}

resource "aws_iam_policy_attachment" "iam_policy_ecr_attachment" {
  name = "${var.aws_stack_name}-ecr-get-auth-token"
  roles = [
    aws_iam_role.iam-role.id,
  ]
  policy_arn = aws_iam_policy.iam_policy_ecr.arn
}

resource "aws_ecr_repository_policy" "ecr_policy" {
  count      = length(var.ecr_ro_repositories)
  repository = element(var.ecr_ro_repositories, count.index)

  policy = data.aws_iam_policy_document.ecr_policy_document.json
}

resource "aws_launch_template" "launch_template" {
  # checkov:skip=CKV_AWS_79:terraform-chef-bootstrap needs IMDSv1
  description             = var.aws_stack_name
  name_prefix             = "${var.aws_stack_name}-"
  update_default_version  = true
  disable_api_stop        = false
  disable_api_termination = true
  ebs_optimized           = true
  image_id                = data.aws_ami.ami.id
  instance_type           = var.aws_instance_type
  key_name                = var.aws_key_name
  user_data               = base64encode(var.user_data)
  vpc_security_group_ids = concat(
    [aws_security_group.security_group.id],
    var.additional_security_group_ids
  )

  block_device_mappings {
    device_name = data.aws_ami.ami.root_device_name
    ebs {
      volume_size           = var.aws_volume_size
      volume_type           = var.root_volume_type
      throughput            = var.root_volume_throughput
      iops                  = var.root_volume_iops
      delete_on_termination = true
      encrypted             = true
    }
  }

  # Once terraform-chef-bootstrap supports IMDSv2, we can restrict this
  # metadata_options {
  #   http_endpoint = "enabled"
  #   http_tokens   = "required"
  #

  iam_instance_profile {
    name = aws_iam_instance_profile.iam-instance-profile.name
  }

  monitoring {
    enabled = true
  }

  lifecycle {
    create_before_destroy = true
  }
}

# Auto scaling group
resource "aws_autoscaling_group" "asg" {
  name                      = var.aws_stack_name
  min_size                  = var.aws_asg_min_size
  max_size                  = var.aws_asg_max_size
  desired_capacity          = var.aws_asg_desired_capacity
  health_check_type         = "EC2"
  health_check_grace_period = 900
  vpc_zone_identifier       = var.vpc_private_subnet_ids
  suspended_processes       = var.asg_suspended_processes

  launch_template {
    id      = aws_launch_template.launch_template.id
    version = "$Latest"
  }

  tag {
    key                 = "Name"
    value               = var.aws_stack_name
    propagate_at_launch = true
  }

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_autoscaling_policy" "asg_policy_out" {
  name                   = "${var.aws_stack_name}-out"
  scaling_adjustment     = 1
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 300
  autoscaling_group_name = aws_autoscaling_group.asg.name
}

resource "aws_cloudwatch_metric_alarm" "scale_out" {
  alarm_name          = "${var.aws_stack_name}-scale-out"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "4"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "60"
  statistic           = "Average"
  threshold           = "80"

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.asg.name
  }

  alarm_description = "This metric monitors ec2 cpu utilization"
  alarm_actions     = [aws_autoscaling_policy.asg_policy_out.arn]
}

resource "aws_autoscaling_policy" "asg_policy_in" {
  name                   = "${var.aws_stack_name}-in"
  scaling_adjustment     = -1
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 300
  autoscaling_group_name = aws_autoscaling_group.asg.name
}

resource "aws_cloudwatch_metric_alarm" "scale_in" {
  alarm_name          = "${var.aws_stack_name}-scale-in"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "50"

  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.asg.name
  }

  alarm_description = "This metric monitors ec2 cpu utilization"
  ok_actions        = [aws_autoscaling_policy.asg_policy_in.arn]
}
