data "aws_caller_identity" "current" {}

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

# Get the latest CentOS AMI id
data "aws_ami" "centos" {
  most_recent = true

  owners = [
    "679593333241",
  ]

  filter {
    name = "virtualization-type"

    values = [
      "hvm",
    ]
  }

  filter {
    name = "product-code"

    values = [
      "aw0evgkw8e5c1q413zgy5pjce",
    ]
  }
}

# User data for instances
data "template_file" "instance_user_data" {
  template = file("${path.root}/user_data.tpl")
}

data "aws_iam_policy_document" "assume_instance_role" {
  statement {
    actions = [
      "sts:AssumeRole",
    ]

    principals {
      type = "Service"
      identifiers = [
        "ec2.amazonaws.com",
      ]
    }
  }
}

# Policy for instance role to allow Route53 record creation (Chef)
data "aws_iam_policy_document" "instance_route53_policy" {
  statement {
    effect = "Allow"
    resources = [
      "*",
    ]

    actions = [
      "route53:ListHostedZones"
    ]
  }

  statement {
    effect = "Allow"
    resources = [
      "arn:aws:route53:::hostedzone/${var.route53_zone_id}",
    ]

    actions = [
      "route53:GetHostedZone",
      "route53:ChangeResourceRecordSets",
      "route53:ListResourceRecordSets",
    ]
  }
}

# Policy for instance role to allow tag modifications (Chef)
data "aws_iam_policy_document" "instance_tag_policy" {
  statement {
    effect = "Allow"
    resources = [
      "arn:aws:ec2:${var.aws_region}:${data.aws_caller_identity.current.account_id}:instance/*",
    ]

    actions = [
      "ec2:CreateTags",
      "ec2:DeleteTags",
    ]
  }

  statement {
    effect = "Allow"
    resources = [
      "*"
    ]

    actions = [
      "ec2:Describe*",
    ]
  }
}

# Instance role
resource "aws_iam_role" "instance_iam_role" {
  name               = "${var.environment}-${var.service_name}-asg-role"
  assume_role_policy = data.aws_iam_policy_document.assume_instance_role.json
}

resource "aws_iam_instance_profile" "iam_instance_profile" {
  name = "${var.environment}-${var.service_name}-asg-instance-profile"
  role = aws_iam_role.instance_iam_role.name
}

resource "aws_iam_policy" "instance_route53_policy" {
  name   = "${var.environment}-${var.service_name}-instance-route53-policy"
  policy = data.aws_iam_policy_document.instance_route53_policy.json
}

# Attach Route53 policy to instance role
resource "aws_iam_role_policy_attachment" "instance_route53_policy_attachment" {
  role       = aws_iam_role.instance_iam_role.name
  policy_arn = aws_iam_policy.instance_route53_policy.arn
}

resource "aws_iam_policy" "instance_tag_policy" {
  name   = "${var.environment}-${var.service_name}-instance-tag-policy"
  policy = data.aws_iam_policy_document.instance_tag_policy.json
}

# Attach instance tag policy to instance role
resource "aws_iam_role_policy_attachment" "instance_tag_policy_attachment" {
  role       = aws_iam_role.instance_iam_role.name
  policy_arn = aws_iam_policy.instance_tag_policy.arn
}

# Allow IAM policy from file to configure case-specific access requirements, if enabled.
resource "aws_iam_policy" "iam_instance_policy" {
  count  = var.iam_policy_file_enabled ? 1 : 0
  name   = "${var.environment}-${var.service_name}-instance-policy"
  policy = file("policies/${var.service_name}.json")
}

# Attach additional policy to role, if enabled
resource "aws_iam_role_policy_attachment" "instance_iam_policy" {
  count      = var.iam_policy_file_enabled ? 1 : 0
  role       = aws_iam_role.instance_iam_role.name
  policy_arn = aws_iam_policy.iam_instance_policy[0].arn
}

# Security group for instances
resource "aws_security_group" "instance_security_group" {
  name        = "${var.environment}-${var.service_name}-instance-security-group"
  description = "Instance security group for ${var.environment}-${var.service_name}"
  vpc_id      = var.vpc_id

  tags = {
    environment  = var.environment
    service_name = var.service_name
    terraformed  = true
  }
}

resource "aws_security_group_rule" "allow_ssh" {
  type              = "ingress"
  from_port         = var.ssh_port
  to_port           = var.ssh_port
  protocol          = "TCP"
  cidr_blocks       = var.ssh_ingress_cidr_blocks
  security_group_id = aws_security_group.instance_security_group.id
}

resource "aws_security_group_rule" "allow_backups" {
  count             = var.allow_backups ? 1 : 0
  type              = "ingress"
  from_port         = var.backup_port
  to_port           = var.backup_port
  protocol          = "TCP"
  cidr_blocks       = concat(var.backup_ingress_cidr_blocks, [data.aws_vpc.current_vpc.cidr_block])
  security_group_id = aws_security_group.instance_security_group.id
}

resource "aws_security_group_rule" "allow_http" {
  count             = var.allow_http ? 1 : 0
  type              = "ingress"
  from_port         = var.http_port
  to_port           = var.http_port
  protocol          = "TCP"
  cidr_blocks       = var.http_ingress_cidr_blocks
  security_group_id = aws_security_group.instance_security_group.id
}

resource "aws_security_group_rule" "allow_https" {
  count             = var.allow_https ? 1 : 0
  type              = "ingress"
  from_port         = var.https_port
  to_port           = var.https_port
  protocol          = "TCP"
  cidr_blocks       = var.https_ingress_cidr_blocks
  security_group_id = aws_security_group.instance_security_group.id
}

resource "aws_security_group_rule" "allow_bolt" {
  count             = var.allow_bolt ? 1 : 0
  type              = "ingress"
  from_port         = var.bolt_port
  to_port           = var.bolt_port
  protocol          = "TCP"
  cidr_blocks       = concat(var.bolt_ingress_cidr_blocks, [data.aws_vpc.current_vpc.cidr_block])
  security_group_id = aws_security_group.instance_security_group.id
}

# Only allow intra-group clustering access
resource "aws_security_group_rule" "allow_neo4j_clustering" {
  count             = length(var.neo4j_clustering_ports)
  type              = "ingress"
  from_port         = var.neo4j_clustering_ports[count.index]
  to_port           = var.neo4j_clustering_ports[count.index]
  protocol          = "TCP"
  self              = true
  security_group_id = aws_security_group.instance_security_group.id
}

resource "aws_security_group_rule" "allow_instance_egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.instance_security_group.id
}

# Launch configuration
resource "aws_launch_configuration" "launch_configuration" {
  name_prefix                 = "${var.environment}-${var.service_name}-launch-configuration"
  image_id                    = data.aws_ami.centos.id
  instance_type               = var.aws_instance_type
  associate_public_ip_address = false
  iam_instance_profile        = aws_iam_instance_profile.iam_instance_profile.name
  enable_monitoring           = true
  ebs_optimized               = true

  root_block_device {
    volume_type           = "gp2"
    volume_size           = var.root_volume_size
    delete_on_termination = true
    encrypted             = true
  }

  ebs_block_device {
    device_name           = var.data_volume_device_name
    volume_type           = "io1"
    volume_size           = var.data_volume_size
    iops                  = var.data_volume_iops
    delete_on_termination = var.data_volume_delete_on_termination
    encrypted             = true
  }

  security_groups = [
    aws_security_group.instance_security_group.id,
  ]

  key_name  = var.ssh_keypair_name
  user_data = "${data.template_file.instance_user_data.rendered}\n${var.user_data}"

  lifecycle {
    create_before_destroy = true
  }
}

# Auto scaling group
resource "aws_autoscaling_group" "autoscaling_group" {
  name                      = "${var.environment}-${var.service_name}"
  min_size                  = var.min_size
  max_size                  = var.max_size
  desired_capacity          = var.desired_capacity
  default_cooldown          = var.default_cooldown
  health_check_type         = "EC2"
  health_check_grace_period = var.health_check_grace_period
  wait_for_capacity_timeout = var.wait_for_capacity_timeout
  vpc_zone_identifier       = var.vpc_private_subnet_ids
  launch_configuration      = aws_launch_configuration.launch_configuration.name
  enabled_metrics           = var.enabled_metrics

  tags = [
    {
      key                 = "Name"
      value               = "${var.environment}-${var.service_name}"
      propagate_at_launch = true
    },
    {
      key                 = "environment"
      value               = var.environment
      propagate_at_launch = true
    },
    {
      key                 = "service_name"
      value               = var.service_name
      propagate_at_launch = true
    },
  ]

  lifecycle {
    create_before_destroy = true
  }
}

# Create a record for nonproduction environments (e.g. qa-neo4j-artist). Only create if environment is not "prod"
resource "aws_route53_record" "non_production_route53_record" {
  count   = var.environment != "prod" ? 1 : 0
  name    = "${var.environment}-${var.service_name}"
  zone_id = var.route53_zone_id
  type    = "A"
  ttl     = var.route53_record_ttl
  # Record value itself is a placeholder
  records = ["1.1.1.1"]

  # Allow DNS record updates via lifecycle hooks without Terraform trying to change things.
  lifecycle {
    ignore_changes = [
      records
    ]
  }
}

# Create a record for production environments (e.g. neo4j-artist). Only create if environment is "prod"
resource "aws_route53_record" "prod_route53_record" {
  count   = var.environment == "prod" ? 1 : 0
  name    = var.service_name
  zone_id = var.route53_zone_id
  type    = "A"
  ttl     = var.route53_record_ttl
  # Record value itself is a placeholder
  records = ["1.1.1.1"]

  # Allow DNS record updates via lifecycle hooks without Terraform trying to change things.
  lifecycle {
    ignore_changes = [
      records
    ]
  }
}

# Create a cname record of prod-service_name to service_name. Only create if environment is "prod"
resource "aws_route53_record" "prod_cname_route53_record" {
  count   = var.environment == "prod" ? 1 : 0
  name    = "${var.environment}-${var.service_name}"
  zone_id = var.route53_zone_id
  type    = "CNAME"
  ttl     = var.route53_record_ttl
  records = [aws_route53_record.prod_route53_record.0.fqdn]

}


# Backup instance role
resource "aws_iam_role" "backup_iam_role" {
  name               = "${var.environment}-${var.service_name}-backup-role"
  assume_role_policy = data.aws_iam_policy_document.assume_instance_role.json
}

resource "aws_iam_instance_profile" "backup_iam_instance_profile" {
  name = "${var.environment}-${var.service_name}-backup-instance-profile"
  role = aws_iam_role.backup_iam_role.name
}

# Policies for instance role to allow pulling initial backups from S3 (Chef)
# and backup user to write backups to S3
# All backups should be in S3 in the following path format:
# s3://bucket_name/backup/neo4j/${var.environment}-${var.service_name}.domain.com/objects
data "aws_iam_policy_document" "s3_backup_read_only_policy" {
  statement {
    actions = [
      "s3:GetBucketLocation",
      "s3:GetObject",
      "s3:ListBucket",
    ]

    resources = [
      "arn:aws:s3:::${var.dbms_backup_s3_bucket}/backup/neo4j/${var.environment}-${var.service_name}.*"
    ]
  }

  statement {
    actions = [
      "s3:GetBucketLocation",
      "s3:ListAllMyBuckets"
    ]

    resources = [
      "*"
    ]
  }
}

data "aws_iam_policy_document" "s3_backup_read_write_policy" {
  statement {
    actions = [
      "s3:GetBucketLocation",
      "s3:GetObject",
      "s3:GetObject*",
      "s3:ListBucket",
      "s3:PutObject",
      "s3:PutObject*",
    ]

    resources = [
      "arn:aws:s3:::${var.dbms_backup_s3_bucket}/backup/neo4j/${var.environment}-${var.service_name}.*"
    ]
  }

  statement {
    actions = [
      "s3:GetBucketLocation",
      "s3:ListAllMyBuckets"
    ]

    resources = [
      "*"
    ]
  }
}

data "aws_iam_policy_document" "secrets_manager_policy" {
  statement {
    actions = [
      "secretsmanager:GetResourcePolicy",
      "secretsmanager:GetSecretValue",
      "secretsmanager:DescribeSecret",
      "secretsmanager:ListSecretVersionIds",
    ]

    resources = [
      "arn:aws:secretsmanager:*:*:secret:${var.environment}/${var.service_name}-backup/",
      "arn:aws:secretsmanager:*:*:secret:${var.environment}/${var.service_name}-backup/*",
    ]
  }

  statement {
    actions = [
      "secretsmanager:GetRandomPassword",
    ]

    resources = [
      "*",
    ]
  }
}

# Allow the cluster role and backup user to use this key
data "aws_iam_policy_document" "backup_kms_key_policy_document" {
  statement {
    effect = "Allow"
    actions = [
      "kms:Encrypt",
      "kms:Decrypt",
      "kms:ReEncrypt*",
      "kms:GenerateDataKey*",
      "kms:DescribeKey"
    ]
    resources = [
      aws_kms_key.kms_key.arn
    ]
  }
}

# KMS key for backups
resource "aws_kms_key" "kms_key" {
  description             = "${var.environment}-${var.service_name}-backup"
  deletion_window_in_days = 14

  tags = {
    environment  = var.environment
    service_name = var.service_name
    terraformed  = "true"
  }
}

resource "aws_kms_alias" "kms_alias" {
  name          = "alias/${var.environment}-neo4j-orchard-backup"
  target_key_id = aws_kms_key.kms_key.key_id
}

resource "aws_iam_policy" "neo4j_kms_backup_policy" {
  name   = "${var.environment}-${var.service_name}-backup-policy"
  policy = data.aws_iam_policy_document.backup_kms_key_policy_document.json
}

# Attach KMS policy to instance role
resource "aws_iam_role_policy_attachment" "neo4j_kms_backup_policy_attachment" {
  role       = aws_iam_role.instance_iam_role.name
  policy_arn = aws_iam_policy.neo4j_kms_backup_policy.arn
}

# Attach KMS policy to backup role
resource "aws_iam_role_policy_attachment" "neo4j_kms_backup_role_policy_attachment" {
  role       = aws_iam_role.backup_iam_role.name
  policy_arn = aws_iam_policy.neo4j_kms_backup_policy.arn
}

# S3 backup read-only policy
resource "aws_iam_policy" "neo4j_s3_readonly_backup_policy" {
  name   = "${var.environment}-${var.service_name}-s3-readonly-backup-policy"
  policy = data.aws_iam_policy_document.s3_backup_read_only_policy.json
}

# S3 backup read-write policy
resource "aws_iam_policy" "neo4j_s3_readwrite_backup_policy" {
  name   = "${var.environment}-${var.service_name}-s3-readwrite-backup-policy"
  policy = data.aws_iam_policy_document.s3_backup_read_write_policy.json
}

# Attach S3 backup read-only policy to instance role
resource "aws_iam_role_policy_attachment" "neo4j_s3_backup_role_policy_attachment" {
  role       = aws_iam_role.instance_iam_role.name
  policy_arn = aws_iam_policy.neo4j_s3_readonly_backup_policy.arn
}

# Attach S3 backup read-write policy to backup role
resource "aws_iam_role_policy_attachment" "neo4j_s3_backup_readwrite_role_policy_attachment" {
  role       = aws_iam_role.backup_iam_role.name
  policy_arn = aws_iam_policy.neo4j_s3_readwrite_backup_policy.arn
}

# Secrets manager backup policy
resource "aws_iam_policy" "backup_secrets_manager_policy" {
  name   = "SecretsManager-${var.environment}-${var.service_name}-backup-policy"
  policy = data.aws_iam_policy_document.secrets_manager_policy.json
}

# Attach secrets manager policy to backup role
resource "aws_iam_role_policy_attachment" "backup_secrets_manager_policy_attachement" {
  role       = aws_iam_role.backup_iam_role.id
  policy_arn = aws_iam_policy.backup_secrets_manager_policy.arn
}

# Attach ECR policy to backup role
resource "aws_iam_role_policy_attachment" "backup_ecr_policy_attachment" {
  role       = aws_iam_role.backup_iam_role.id
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}

# Attach Datadog policy to backup role
resource "aws_iam_role_policy_attachment" "backup_datadog_policy_attachment" {
  role       = aws_iam_role.backup_iam_role.id
  policy_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/SecretsManager-${var.environment}-datadog-policy"
}

# Backup launch configuration
resource "aws_launch_configuration" "backup_launch_configuration" {
  name_prefix                 = "${var.environment}-${var.service_name}-backup-launch-configuration"
  image_id                    = data.aws_ami.centos.id
  instance_type               = var.backup_aws_instance_type
  associate_public_ip_address = false
  iam_instance_profile        = aws_iam_instance_profile.backup_iam_instance_profile.name
  enable_monitoring           = true
  ebs_optimized               = true

  root_block_device {
    volume_type           = "gp2"
    volume_size           = var.backup_root_volume_size
    delete_on_termination = true
    encrypted             = true
  }

  security_groups = [
    aws_security_group.instance_security_group.id,
  ]

  key_name  = var.ssh_keypair_name
  user_data = "${data.template_file.instance_user_data.rendered}\n${var.backup_user_data}"

  lifecycle {
    create_before_destroy = true
  }
}

# Backup auto scaling group
resource "aws_autoscaling_group" "backup_autoscaling_group" {
  name                      = "${var.environment}-${var.service_name}-backup"
  min_size                  = var.backup_min_size
  max_size                  = var.backup_max_size
  desired_capacity          = var.backup_desired_capacity
  default_cooldown          = var.backup_default_cooldown
  health_check_type         = "EC2"
  health_check_grace_period = var.backup_health_check_grace_period
  wait_for_capacity_timeout = var.backup_wait_for_capacity_timeout
  vpc_zone_identifier       = var.vpc_private_subnet_ids
  launch_configuration      = aws_launch_configuration.backup_launch_configuration.name
  enabled_metrics           = var.enabled_metrics

  tags = [
    {
      key                 = "Name"
      value               = "${var.environment}-${var.service_name}-backup"
      propagate_at_launch = true
    },
    {
      key                 = "environment"
      value               = var.environment
      propagate_at_launch = true
    },
    {
      key                 = "service_name"
      value               = "${var.service_name}-backup"
      propagate_at_launch = true
    },
  ]

  lifecycle {
    create_before_destroy = true
  }
}
