data "aws_caller_identity" "current" {
}

# Find the most recent generic single-container docker environment
data "aws_elastic_beanstalk_solution_stack" "single_container_docker" {
  most_recent = true
  name_regex  = "^64bit Amazon Linux (.*) running Docker (.*)$"
}

data "aws_iam_policy_document" "assume_role_policy" {
  statement {
    sid = ""

    actions = [
      "sts:AssumeRole",
    ]

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

    effect = "Allow"
  }
}

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}/",
      "arn:aws:secretsmanager:*:*:secret:${var.environment}/${var.service_name}/*",
    ]
  }

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

    resources = [
      "*",
    ]
  }
}

resource "aws_iam_role" "elasticbeanstalk_instance_profile_role" {
  name               = "${var.environment}-${var.service_name}-service-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json
}

resource "aws_iam_policy" "elasticbeanstalk_secrets_manager_policy" {
  name   = "SecretsManager-${var.environment}-${var.service_name}-policy"
  policy = data.aws_iam_policy_document.secrets_manager_policy.json
}

resource "aws_iam_role_policy_attachment" "elasticbeanstalk_secrets_manager_policy_attachement" {
  role       = aws_iam_role.elasticbeanstalk_instance_profile_role.id
  policy_arn = aws_iam_policy.elasticbeanstalk_secrets_manager_policy.arn
}

resource "aws_iam_role_policy_attachment" "web_tier" {
  role       = aws_iam_role.elasticbeanstalk_instance_profile_role.name
  policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier"
}

resource "aws_iam_role_policy_attachment" "worker_tier" {
  role       = aws_iam_role.elasticbeanstalk_instance_profile_role.name
  policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier"
}

resource "aws_iam_role_policy_attachment" "ecr_readonly" {
  role       = aws_iam_role.elasticbeanstalk_instance_profile_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}

resource "aws_iam_instance_profile" "instance_profile" {
  name = "${var.environment}-${var.service_name}-instance-profile"
  role = aws_iam_role.elasticbeanstalk_instance_profile_role.name
}

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

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

# Attach additional existing managed IAM policies, if enabled
resource "aws_iam_role_policy_attachment" "service_existing_iam_policy_attachment" {
  count      = var.iam_managed_policy_attachments[0] != "" ? length(var.iam_managed_policy_attachments) : 0
  role       = aws_iam_role.elasticbeanstalk_instance_profile_role.name
  policy_arn = var.iam_managed_policy_attachments[count.index]
}

# Attach datadog secrets manager policy to fetch API keys
resource "aws_iam_role_policy_attachment" "datadog_secrets_manager_iam_policy_attachment" {
  count      = var.datadog_enabled ? 1 : 0
  role       = aws_iam_role.elasticbeanstalk_instance_profile_role.name
  policy_arn = local.datadog_secrets_manager_policy_arn
}

# Security group for auto-scaling group instances
resource "aws_security_group" "asg_security_group" {
  name        = "${var.environment}-${var.service_name}-asg"
  description = "Auto-scaling security group for ${var.environment}-${var.service_name}"
  vpc_id      = var.vpc_id

  tags = {
    Name        = "${var.environment}-${var.service_name}-asg"
    environment = var.environment
    terraformed = true
  }
}

resource "aws_security_group_rule" "allow_asg_inbound_from_load_balancer" {
  type                     = "ingress"
  from_port                = var.process_port
  to_port                  = var.process_port
  protocol                 = "TCP"
  source_security_group_id = aws_security_group.load_balancer_security_group.id
  security_group_id        = aws_security_group.asg_security_group.id
}

resource "aws_security_group_rule" "allow_asg_inbound_from_additional_locations" {
  count     = length(var.asg_additional_inbound_rules)
  type      = "ingress"
  from_port = var.asg_additional_inbound_rules[count.index]["port"]
  to_port   = var.asg_additional_inbound_rules[count.index]["port"]
  protocol  = var.asg_additional_inbound_rules[count.index]["protocol"]
  # TF-UPGRADE-TODO: In Terraform v0.10 and earlier, it was sometimes necessary to
  # force an interpolation expression to be interpreted as a list by wrapping it
  # in an extra set of list brackets. That form was supported for compatibility in
  # v0.11, but is no longer supported in Terraform v0.12.
  #
  # If the expression in the following list itself returns a list, remove the
  # brackets to avoid interpretation as a list of lists. If the expression
  # returns a single list item then leave it as-is and remove this TODO comment.
  cidr_blocks       = [var.asg_additional_inbound_rules[count.index]["cidr_block"]]
  security_group_id = aws_security_group.asg_security_group.id
}

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

# Security group for load balancer
resource "aws_security_group" "load_balancer_security_group" {
  name        = "${var.environment}-${var.service_name}-lb"
  description = "Load balancer security group for ${var.environment}-${var.service_name}"
  vpc_id      = var.vpc_id

  tags = {
    Name        = "${var.environment}-${var.service_name}-lb"
    environment = var.environment
    terraformed = true
  }
}

resource "aws_security_group_rule" "allow_lb_inbound" {
  type              = "ingress"
  from_port         = var.load_balancer_listener_port
  to_port           = var.load_balancer_listener_port
  protocol          = "TCP"
  cidr_blocks       = var.load_balancer_inbound_allowed_cidr_blocks
  security_group_id = aws_security_group.load_balancer_security_group.id
}

resource "aws_security_group_rule" "allow_lb_egress" {
  type                     = "egress"
  from_port                = var.process_port
  to_port                  = var.process_port
  protocol                 = var.load_balancer_egress_protocol
  security_group_id        = aws_security_group.load_balancer_security_group.id
  source_security_group_id = aws_security_group.asg_security_group.id
}

# Create the application
resource "aws_elastic_beanstalk_application" "elasticbeanstalk_application" {
  name        = var.use_existing_application ? var.existing_application_name : var.service_name
  description = var.use_existing_application ? var.existing_application_name : var.service_name
}

# Create the environment
resource "aws_elastic_beanstalk_environment" "elasticbeanstalk_environment" {
  name                   = "${var.environment}-${var.service_name}"
  application            = aws_elastic_beanstalk_application.elasticbeanstalk_application.name
  cname_prefix           = var.environment_tier == "WebServer" ? "${var.environment}-${var.service_name}" : ""
  solution_stack_name    = data.aws_elastic_beanstalk_solution_stack.single_container_docker.name
  tier                   = var.environment_tier
  wait_for_ready_timeout = var.environment_wait_for_ready_timeout

  dynamic "setting" {
    for_each = var.environment_variables
    content {
      namespace = setting.value.namespace
      name      = setting.value.name
      value     = setting.value.value
    }
  }

  dynamic "setting" {
    for_each = local.load_balancer_listener_settings[var.environment_tier]
    content {
      namespace = setting.value.namespace
      name      = setting.value.name
      value     = setting.value.value
    }
  }

  dynamic "setting" {
    for_each = local.load_balancer_v2_listener_settings[var.environment_tier]
    content {
      namespace = setting.value.namespace
      name      = setting.value.name
      value     = setting.value.value
    }
  }
  # ASG settings
  setting {
    namespace = "aws:autoscaling:asg"
    name      = "Availability Zones"
    value     = var.asg_availability_zones
  }

  setting {
    namespace = "aws:autoscaling:asg"
    name      = "Cooldown"
    value     = var.asg_cooldown
  }

  setting {
    namespace = "aws:autoscaling:asg"
    name      = "MinSize"
    value     = var.asg_min_size
  }

  setting {
    namespace = "aws:autoscaling:asg"
    name      = "MaxSize"
    value     = var.asg_max_size
  }

  # Launch configuration settings
  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "BlockDeviceMappings"
    value     = var.launchconfiguration_block_device_mappings
  }

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "EC2KeyName"
    value     = var.launchconfiguration_keypair
  }

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "IamInstanceProfile"
    value     = aws_iam_instance_profile.instance_profile.name
  }

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "InstanceType"
    value     = var.launchconfiguration_instance_type
  }

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "MonitoringInterval"
    value     = var.launchconfiguration_monitoring_interval
  }

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "RootVolumeSize"
    value     = var.launchconfiguration_root_volume_size
  }

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "RootVolumeType"
    value     = var.launchconfiguration_root_volume_type
  }

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "SecurityGroups"
    value     = aws_security_group.asg_security_group.id
  }

  setting {
    namespace = "aws:autoscaling:launchconfiguration"
    name      = "SSHSourceRestriction"
    value     = "tcp, 22, 22, ${var.launchconfiguration_ssh_source_restriction}"
  }

  # Trigger settings
  setting {
    namespace = "aws:autoscaling:trigger"
    name      = "BreachDuration"
    value     = var.trigger_breach_duration
  }

  setting {
    namespace = "aws:autoscaling:trigger"
    name      = "LowerThreshold"
    value     = var.trigger_lower_threshold
  }

  setting {
    namespace = "aws:autoscaling:trigger"
    name      = "MeasureName"
    value     = var.trigger_measure_name
  }

  setting {
    namespace = "aws:autoscaling:trigger"
    name      = "Period"
    value     = var.trigger_period
  }

  setting {
    namespace = "aws:autoscaling:trigger"
    name      = "Statistic"
    value     = var.trigger_statistic
  }

  setting {
    namespace = "aws:autoscaling:trigger"
    name      = "Unit"
    value     = var.trigger_unit
  }

  setting {
    namespace = "aws:autoscaling:trigger"
    name      = "UpperThreshold"
    value     = var.trigger_upper_threshold
  }

  # Rolling update settings
  setting {
    namespace = "aws:autoscaling:updatepolicy:rollingupdate"
    name      = "MaxBatchSize"
    value     = var.update_max_batch
  }

  setting {
    namespace = "aws:autoscaling:updatepolicy:rollingupdate"
    name      = "MinInstancesInService"
    value     = var.update_min_in_service
  }

  setting {
    namespace = "aws:autoscaling:updatepolicy:rollingupdate"
    name      = "RollingUpdateEnabled"
    value     = "true"
  }

  setting {
    namespace = "aws:autoscaling:updatepolicy:rollingupdate"
    name      = "RollingUpdateType"
    value     = var.update_type
  }

  # VPC settings
  setting {
    namespace = "aws:ec2:vpc"
    name      = "AssociatePublicIpAddress"
    value     = var.vpc_associate_public_ip_address
  }

  setting {
    namespace = "aws:ec2:vpc"
    name      = "ELBScheme"
    value     = var.vpc_elb_scheme
  }

  setting {
    namespace = "aws:ec2:vpc"
    name      = "ELBSubnets"
    value     = var.vpc_elb_subnets
  }

  setting {
    namespace = "aws:ec2:vpc"
    name      = "Subnets"
    value     = var.vpc_subnets
  }

  setting {
    namespace = "aws:ec2:vpc"
    name      = "VPCId"
    value     = var.vpc_id
  }

  # Health check settings
  setting {
    namespace = "aws:elasticbeanstalk:application"
    name      = "Application Healthcheck URL"
    value     = local.healthcheck_url
  }

  # Cloudwatch log settings
  setting {
    namespace = "aws:elasticbeanstalk:cloudwatch:logs"
    name      = "StreamLogs"
    value     = var.cloudwatch_stream_logs
  }

  setting {
    namespace = "aws:elasticbeanstalk:cloudwatch:logs"
    name      = "DeleteOnTerminate"
    value     = var.cloudwatch_delete_on_termination
  }

  setting {
    namespace = "aws:elasticbeanstalk:cloudwatch:logs"
    name      = "RetentionInDays"
    value     = var.cloudwatch_retention_in_days
  }

  setting {
    namespace = "aws:elasticbeanstalk:cloudwatch:logs:health"
    name      = "HealthStreamingEnabled"
    value     = var.cloudwatch_stream_logs
  }

  setting {
    namespace = "aws:elasticbeanstalk:cloudwatch:logs:health"
    name      = "DeleteOnTerminate"
    value     = var.cloudwatch_delete_on_termination
  }

  setting {
    namespace = "aws:elasticbeanstalk:cloudwatch:logs:health"
    name      = "RetentionInDays"
    value     = var.cloudwatch_retention_in_days
  }

  # Deployment settings
  setting {
    namespace = "aws:elasticbeanstalk:command"
    name      = "BatchSizeType"
    value     = var.deployment_batch_size_type
  }

  setting {
    namespace = "aws:elasticbeanstalk:command"
    name      = "BatchSize"
    value     = var.deployment_batch_size
  }

  setting {
    namespace = "aws:elasticbeanstalk:command"
    name      = "DeploymentPolicy"
    value     = var.deployment_update_type
  }

  setting {
    namespace = "aws:elasticbeanstalk:command"
    name      = "Timeout"
    value     = var.deployment_timeout
  }

  # Environment settings
  setting {
    namespace = "aws:elasticbeanstalk:environment"
    name      = "EnvironmentType"
    value     = var.environment_type
  }

  setting {
    namespace = "aws:elasticbeanstalk:environment"
    name      = "LoadBalancerType"
    value     = var.environment_loadbalancer_type
  }

  setting {
    namespace = "aws:elasticbeanstalk:environment"
    name      = "ServiceRole"
    value     = var.environment_service_role
  }

  # Default process settings
  setting {
    namespace = "aws:elasticbeanstalk:environment:process:default"
    name      = "HealthCheckInterval"
    value     = var.process_instance_health_check_interval
  }

  setting {
    namespace = "aws:elasticbeanstalk:environment:process:default"
    name      = "HealthCheckPath"
    value     = var.process_instance_health_check_path
  }

  setting {
    namespace = "aws:elasticbeanstalk:environment:process:default"
    name      = "MatcherHTTPCode"
    value     = var.process_instance_health_check_matcher_code
  }

  setting {
    namespace = "aws:elasticbeanstalk:environment:process:default"
    name      = "Port"
    value     = var.process_port
  }

  setting {
    namespace = "aws:elasticbeanstalk:environment:process:default"
    name      = "Protocol"
    value     = var.process_protocol
  }

  setting {
    namespace = "aws:elasticbeanstalk:environment:process:default"
    name      = "StickinessEnabled"
    value     = var.process_stickiness_enabled
  }

  # Health reporting settings
  setting {
    namespace = "aws:elasticbeanstalk:healthreporting:system"
    name      = "SystemType"
    value     = "enhanced"
  }

  # Instance log settings
  setting {
    namespace = "aws:elasticbeanstalk:hostmanager"
    name      = "LogPublicationControl"
    value     = "true"
  }

  # Managed update settings
  setting {
    namespace = "aws:elasticbeanstalk:managedactions"
    name      = "ManagedActionsEnabled"
    value     = "true"
  }

  setting {
    namespace = "aws:elasticbeanstalk:managedactions"
    name      = "PreferredStartTime"
    value     = var.managed_update_preferred_start_time
  }

  setting {
    namespace = "aws:elasticbeanstalk:managedactions:platformupdate"
    name      = "UpdateLevel"
    value     = "minor"
  }

  setting {
    namespace = "aws:elasticbeanstalk:managedactions:platformupdate"
    name      = "InstanceRefreshEnabled"
    value     = var.managed_update_weekly_instance_replacement
  }

  # Notification settings
  setting {
    namespace = "aws:elasticbeanstalk:sns:topics"
    name      = "Notification Endpoint"
    value     = var.notification_endpoints
  }

  # ELB health check settings
  setting {
    namespace = "aws:elb:healthcheck"
    name      = "HealthyThreshold"
    value     = var.elb_health_check_healthy_threshold
  }

  setting {
    namespace = "aws:elb:healthcheck"
    name      = "Interval"
    value     = var.elb_health_check_interval
  }

  setting {
    namespace = "aws:elb:healthcheck"
    name      = "UnhealthyThreshold"
    value     = var.elb_health_check_unhealthy_threshold
  }

  # General load balancer settings
  setting {
    namespace = "aws:elb:loadbalancer"
    name      = "CrossZone"
    value     = "true"
  }

  setting {
    namespace = "aws:elb:loadbalancer"
    name      = "SecurityGroups"
    value     = aws_security_group.load_balancer_security_group.id
  }

  setting {
    namespace = "aws:elb:loadbalancer"
    name      = "ManagedSecurityGroup"
    value     = aws_security_group.load_balancer_security_group.id
  }

  # Load balancer connection settings
  setting {
    namespace = "aws:elb:policies"
    name      = "ConnectionDrainingEnabled"
    value     = var.elb_connection_draining_enabled
  }

  setting {
    namespace = "aws:elb:policies"
    name      = "ConnectionDrainingTimeout"
    value     = var.elb_connection_draining_timeout
  }

  setting {
    namespace = "aws:elb:policies"
    name      = "ConnectionSettingIdleTimeout"
    value     = var.elb_connection_idle_timeout
  }

  setting {
    namespace = "aws:elb:policies"
    name      = "LoadBalancerPorts"
    value     = ":all"
  }

  setting {
    namespace = "aws:elb:policies"
    name      = "Stickiness Policy"
    value     = var.elb_connection_stickiness_policy
  }

  # Application load balancer settings
  setting {
    namespace = "aws:elbv2:loadbalancer"
    name      = "AccessLogsS3Bucket"
    value     = var.elb_v2_access_logs_s3_bucket
  }

  setting {
    namespace = "aws:elbv2:loadbalancer"
    name      = "AccessLogsS3Enabled"
    value     = var.elb_v2_access_logs_enabled
  }

  setting {
    namespace = "aws:elbv2:loadbalancer"
    name      = "AccessLogsS3Prefix"
    value     = local.elb_v2_access_logs_s3_prefix
  }

  setting {
    namespace = "aws:elbv2:loadbalancer"
    name      = "SecurityGroups"
    value     = aws_security_group.load_balancer_security_group.id
  }

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

  # Ignore selected changes
  lifecycle {
    ignore_changes = [cname_prefix]
  }
}

# Create a record for non-production environments (e.g. qa-ows-search). Only create if environment is not "prod"
resource "aws_route53_record" "elastic_beanstalk_environment_route53_record" {
  count   = var.environment_tier == "WebServer" ? var.environment != "prod" ? 1 : 0 : 0
  name    = "${var.environment}-${var.service_name}"
  zone_id = var.aws_route53_zone_id
  type    = "CNAME"
  ttl     = var.aws_route53_record_ttl
  records = [aws_elastic_beanstalk_environment.elasticbeanstalk_environment.cname]
}

# Create a record for production environments (e.g. ows-search). Only create if environment is "prod"
resource "aws_route53_record" "prod_elastic_beanstalk_environment_route53_record" {
  count   = var.environment_tier == "WebServer" ? var.environment == "prod" ? 1 : 0 : 0
  name    = var.service_name
  zone_id = var.aws_route53_zone_id
  type    = "CNAME"
  ttl     = var.aws_route53_record_ttl
  records = [aws_elastic_beanstalk_environment.elasticbeanstalk_environment.cname]
}

# Create a cname record of prod-ows-service to ows-service. Only create if environment is "prod"
resource "aws_route53_record" "prod_cname_elastic_beanstalk_environment_route53_record" {
  count   = var.environment_tier == "WebServer" ? var.environment == "prod" ? 1 : 0 : 0
  name    = "${var.environment}-${var.service_name}"
  zone_id = var.aws_route53_zone_id
  type    = "CNAME"
  ttl     = var.aws_route53_record_ttl
  records = aws_route53_record.prod_elastic_beanstalk_environment_route53_record.*.fqdn
}
