module "default_tags" {
  source             = "git@github.com:theorchard/terraform-default-tags.git//?ref=1.0.0"
  environment        = var.environment
  application_family = var.application_family
  service_name       = var.service_name
}

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = module.default_tags.tags
  }
}

provider "aws" {
  region  = var.aws_region
  alias   = "networking"
  profile = "networking"

  default_tags {
    tags = module.default_tags.tags
  }
}

provider "github" {
  owner = var.github_organization
  app_auth {
    id              = ephemeral.aws_secretsmanager_secret_version.gh_app_id.secret_string
    installation_id = ephemeral.aws_secretsmanager_secret_version.gh_app_installation_id.secret_string
    pem_file        = ephemeral.aws_secretsmanager_secret_version.gh_app_key.secret_string
  }
}

terraform {
  backend "s3" {
    bucket  = "shared-orcd-terraform-state"
    key     = "shared/atlantis/infra/terraform.tfstate"
    region  = "us-east-1"
    encrypt = "true"
  }
}

data "aws_caller_identity" "current" {}

data "aws_vpc" "main" {
  tags = {
    Name = "${var.environment}-terraform-aws-vpc"
  }
}

data "aws_subnets" "atlantis_private" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.main.id]
  }

  tags = {
    Name = "${var.environment}_atlantis_subnet*"
  }
}

data "aws_subnets" "public" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.main.id]
  }

  tags = {
    Name = "${var.environment}_public_subnet*"
  }
}

data "aws_route53_zone" "route53_zone" {
  name         = "${var.environment}.theorchard.io"
  private_zone = false
}

data "aws_acm_certificate" "certificate" {
  domain   = "*.${data.aws_route53_zone.route53_zone.name}"
  statuses = ["ISSUED"]
}

data "aws_s3_bucket" "lb_log_bucket" {
  bucket = "${var.environment}-${var.account_group}-lb-logs"
}

data "aws_s3_bucket" "waf_log_bucket" {
  bucket = "aws-waf-logs-${var.environment}-${var.account_group}"
}

data "github_repository" "repository" {
  full_name = "theorchard/terraform-infra"
}

data "aws_secretsmanager_secret_version" "github_webook_secret" {
  secret_id = module.atlantis_secrets["GH_WEBHOOK_SECRET"].secret_arn
}

ephemeral "aws_secretsmanager_secret_version" "gh_app_id" {
  secret_id = module.atlantis_secrets["GH_APP_ID"].secret_arn
}

ephemeral "aws_secretsmanager_secret_version" "gh_app_installation_id" {
  secret_id = module.atlantis_secrets["GH_APP_INSTALLATION_ID"].secret_arn
}

ephemeral "aws_secretsmanager_secret_version" "gh_app_key" {
  secret_id = module.atlantis_secrets["GH_APP_KEY"].secret_arn
}

data "aws_network_interface" "nlb_interface" {
  for_each = toset(data.aws_subnets.public.ids)

  filter {
    name   = "description"
    values = ["ELB ${aws_lb.atlantis_nlb.arn_suffix}"]
  }

  filter {
    name   = "subnet-id"
    values = [each.value]
  }
}

data "github_ip_ranges" "allowed_incoming_ranges" {}

module "atlantis_secrets" {
  source   = "git@github.com:theorchard/terraform-secrets-manager.git//?ref=1.5.1"
  for_each = toset(var.secrets_manager_secret_names)

  environment        = var.environment
  service_name       = var.service_name
  secret_name        = each.value
  application_family = var.application_family
}

module "fargate_atlantis" {
  source = "git@github.com:theorchard/terraform-fargate.git//?ref=6.4.1"

  providers = {
    aws.dns = aws.networking
  }

  environment                              = var.environment
  service_name                             = var.service_name
  application_family                       = var.application_family
  aws_region                               = var.aws_region
  commit_sha                               = "latest"
  container_port                           = var.container_port
  task_type                                = "worker"
  auth_issuers                             = { "shared" : "dummy" }
  desired_task_count                       = 1
  task_cpu                                 = 2048
  task_memory                              = 6144
  maximum_capacity                         = 1
  minimum_capacity                         = 1
  task_ephemeral_storage_size              = 100
  load_balancer_access_logs_s3_bucket_name = data.aws_s3_bucket.lb_log_bucket.id
  vpc_id                                   = data.aws_vpc.main.id
  fargate_service_subnets                  = data.aws_subnets.atlantis_private.ids
  custom_security_group_ids                = [aws_security_group.atlantis_task_firewall.id]
  additional_lb_target_group_arns          = [aws_lb_target_group.atlantis_alb_target_group.arn]
  health_check_command                     = "curl -f http://localhost:${var.container_port}/ || exit 1"
  ows_machine_to_machine_enabled           = false
  splitio_enabled                          = false
  datadog_enabled                          = true
  defender                                 = { enabled = false }
  custom_waf_arn                           = module.atlantis_waf.waf_blocking_arn_output
  non_ecr_image                            = var.atlantis_image_uri
  iam_managed_policy_attachments = [
    aws_iam_policy.assume_shared_atlantis_cross_account_access.arn,
  ]

  stopped_task_monitoring_enabled = true

  environment_variables = [
    {
      Environment = var.environment
    },
    {
      ATLANTIS_ATLANTIS_URL = "https://${aws_route53_record.atlantis.fqdn}"
    },
    {
      ATLANTIS_REPO_ALLOWLIST = join(",", formatlist("github.com/%s", var.github_repositories))
    },
    {
      ATLANTIS_ALLOW_FORK_PRS = true
    },
    {
      ATLANTIS_WRITE_GIT_CREDS = true
    },
    {
      ATLANTIS_CHECKOUT_STRATEGY = "merge"
    },
    {
      ATLANTIS_DEFAULT_TF_VERSION = "v${var.atlantis_terraform_version}"
    },
    {
      ATLANTIS_DISCARD_APPROVAL_ON_PLAN = true
    },
    {
      ATLANTIS_AUTOPLAN_FILE_LIST = join(",", [
        "accounting/**/*.tf*",
        "accounting/**/*.json*",
        "amp/**/*.tf*",
        "amp/**/*.json*",
        "aoma-core/**/*.tf*",
        "aoma-core/**/*.json*",
        "aoma-delivery/**/*.tf*",
        "aoma-delivery/**/*.json*",
        "aoma-gmp/**/*.tf*",
        "aoma-gmp/**/*.json*",
        "dc/**/*.tf*",
        "dc/**/*.json*",
        "business-intelligence/**/*.tf*",
        "business-intelligence/**/*.json*",
        "business-solutions/qa/**/*.tf*",
        "business-solutions/qa/**/*.json*",
        "business-solutions/prod/**/*.tf*",
        "business-solutions/prod/**/*.json*",
        "dev/**/*.tf*",
        "dev/**/*.json*",
        "disaster-recovery/**/*.tf*",
        "disaster-recovery/**/*.json*",
        "dx/**/*.tf*",
        "dx/**/*.json*",
        "ecommerce/**/*.tf*",
        "ecommerce/**/*.json*",
        "fansifter/**/*.tf*",
        "fansifter/**/*.json*",
        "eom/**/*.tf*",
        "eom/**/*.json*",
        "gdb/**/*.tf*",
        "gdb/**/*.json*",
        "global-ds/**/*.tf*",
        "global-ds/**/*.json*",
        "grps/**/*.tf*",
        "grps/**/*.json*",
        "mc-sec/**/*.tf*",
        "mc-sec/**/*.json*",
        "media-conversion/**/*.tf*",
        "media-conversion/**/*.json*",
        "media-services/**/*.tf*",
        "media-services/**/*.json*",
        "mobile-code-push/**/*.tf*",
        "mobile-code-push/**/*.json*",
        "networking/**/*.tf*",
        "networking/**/*.json*",
        "permissions-platform/**/*.tf*",
        "permissions-platform/**/*.json*",
        "prod/**/*.tf*",
        "prod/**/*.json*",
        "royaltyshare/**/*.tf*",
        "royaltyshare/**/*.json*",
        "qa/**/*.tf*",
        "qa/**/*.json*",
        "shared/**/*.tf*",
        "shared/**/*.json*",
        "sme-global-data-systems/**/*.tf*",
        "sme-global-data-systems/**/*.json*",
        "songwhip/**/*.tf*",
        "songwhip/**/*.json*",
        "supply-chain/**/*.tf*",
        "supply-chain/**/*.json*",
        "supply-chain-max/**/*.tf*",
        "supply-chain-max/**/*.json*",
        "test-automation/**/*.tf*",
        "test-automation/**/*.json*",
        "uat/**/*.json*",
        "uat/**/*.tf*",
        "youtube-audit/qa/**/*.tf*",
        "youtube-audit/qa/**/*.json*",
        "youtube-audit/prod/**/*.tf*",
        "youtube-audit/prod/**/*.json*",
        "orch-switchboard/dev/**/*.tf*",
        "orch-switchboard/dev/**/*.json*",
        "orch-switchboard/prod/**/*.tf*",
        "orch-switchboard/prod/**/*.json*",
      ])
    },
    {
      ATLANTIS_SILENCE_NO_PROJECTS = true
    },
    {
      ATLANTIS_REPO_CONFIG_JSON = jsonencode({
        repos = [{
          id                 = "/.*/"
          apply_requirements = ["approved", "mergeable"]
          },
          {
            id                 = "github.com/theorchard/terraform-infra"
            apply_requirements = ["approved", "mergeable"]
            workflow           = "terraform_infra"
        }],
        workflows = local.workflows
      })
    },
    {
      ATLANTIS_ALLOW_COMMANDS = "version,plan,apply,unlock,approve_policies,import,state"
    },
    {
      TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE = true
    },
    {
      AWS_PROFILES = join(";", [
        for account in local.accounts : "${account.name},${account.role}"
      ])
    },
    {
      # Requires version 0.98+ of the Snowflake provider.
      # Older configs will need to be upgraded or explicitly set authenticator = "jwt" in the provider config
      SNOWFLAKE_AUTHENTICATOR = "SNOWFLAKE_JWT"
    },
    {
      SNOWFLAKE_ORGANIZATION_NAME = "sme"
    },
    {
      ATLANTIS_HIDE_PREV_PLAN_COMMENTS = true # # When using the GitHub App, you need to set --gh-app-slug to enable this feature.
    },
    {
      ATLANTIS_GH_APP_SLUG = "atlantis-terraform-infra"
    },
    {
      # Enables us to set atlantis/apply to the mergeable requirement
      ATLANTIS_GH_ALLOW_MERGEABLE_BYPASS_APPLY = true
    },
    {
      STS_EXTERNAL_ID = var.external_id
    },
    {
      # For configuring the GitHub provider.
      GITHUB_OWNER = "theorchard"
    },
  ]
  secrets = [
    {
      ATLANTIS_GH_WEBHOOK_SECRET = "${var.environment}/${var.service_name}/GH_WEBHOOK_SECRET"
    },
    {
      ATLANTIS_GH_APP_ID = "${var.environment}/${var.service_name}/GH_APP_ID"
    },
    {
      ATLANTIS_GH_APP_INSTALLATION_ID = "${var.environment}/${var.service_name}/GH_APP_INSTALLATION_ID"
    },
    {
      ATLANTIS_GH_APP_KEY = "${var.environment}/${var.service_name}/GH_APP_KEY"
    },
    {
      DD_API_KEY = "${var.environment}/datadog/DD_API_KEY"
    },
    {
      DD_APP_KEY = "${var.environment}/datadog/DD_APP_KEY"
    },
    {
      SENTRY_AUTH_TOKEN = "${var.environment}/${var.service_name}/SENTRY_API_KEY"
    },
    {
      GITHUB_APP_ID = "${var.environment}/${var.service_name}/GH_APP_ID"
    },
    {
      GITHUB_APP_INSTALLATION_ID = "${var.environment}/${var.service_name}/GH_APP_INSTALLATION_ID"
    },
    {
      GITHUB_APP_PEM_FILE = "${var.environment}/${var.service_name}/GH_APP_KEY"
    },
    {
      GOOGLE_CREDENTIALS = "${var.environment}/${var.service_name}/GOOGLE_CREDENTIALS"
    },
    {
      CLOUDFLARE_API_TOKEN = "${var.environment}/${var.service_name}/CLOUDFLARE_API_TOKEN"
    },
    {
      SNOWFLAKE_USER = "${var.environment}/${var.service_name}/SNOWFLAKE_USER"
    },
    {
      SNOWFLAKE_PRIVATE_KEY = "${var.environment}/${var.service_name}/SNOWFLAKE_PRIVATE_KEY"
    },
    {
      SNOWFLAKE_PRIVATE_KEY_PASSPHRASE = "${var.environment}/${var.service_name}/SNOWFLAKE_PRIVATE_KEY_PASSPHRASE"
    },
    {
      FIVETRAN_APIKEY = "${var.environment}/${var.service_name}/FIVETRAN_APIKEY"
    },
    {
      FIVETRAN_APISECRET = "${var.environment}/${var.service_name}/FIVETRAN_APISECRET"
    },
  ]
}

resource "aws_lb" "atlantis_alb" {
  name                       = "${var.environment}-${var.service_name}"
  internal                   = true
  load_balancer_type         = "application"
  security_groups            = [aws_security_group.atlantis_lb_firewall.id]
  subnets                    = data.aws_subnets.atlantis_private.ids
  enable_deletion_protection = false

  access_logs {
    bucket  = data.aws_s3_bucket.lb_log_bucket.id
    prefix  = "${data.aws_caller_identity.current.account_id}/${var.environment}-${var.service_name}"
    enabled = true
  }

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

resource "aws_lb_listener" "atlantis_alb_listener" {
  load_balancer_arn = aws_lb.atlantis_alb.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS-1-2-2017-01"
  certificate_arn   = data.aws_acm_certificate.certificate.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.atlantis_alb_target_group.arn
  }
}

resource "aws_lb_target_group" "atlantis_alb_target_group" {
  name        = "${var.environment}-${var.service_name}-tg"
  port        = var.container_port
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = data.aws_vpc.main.id
}

resource "aws_security_group" "atlantis_lb_firewall" {
  name        = "${var.environment}_${var.service_name}_lb_sg"
  description = "Allow TLS inbound traffic from github"
  vpc_id      = data.aws_vpc.main.id
  ingress {
    description      = "Allow inbound traffic from GitHub"
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = data.github_ip_ranges.allowed_incoming_ranges.hooks_ipv4
    ipv6_cidr_blocks = data.github_ip_ranges.allowed_incoming_ranges.hooks_ipv6
  }

  ingress {
    description = "Allow additional CIDR blocks"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = var.additional_allowed_cidr_blocks
  }

  ingress {
    description = "Allow healthcheck from NLB"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = formatlist("%s/32", [for eni in data.aws_network_interface.nlb_interface : eni.private_ip])
  }

  egress {
    from_port       = var.container_port
    to_port         = var.container_port
    protocol        = "tcp"
    security_groups = [aws_security_group.atlantis_task_firewall.id]
  }

  tags = local.security_group_tags
}

resource "aws_security_group" "atlantis_task_firewall" {
  name        = "${var.environment}_${var.service_name}_task_sg"
  description = "Allow traffic from atlantis LB to fargate task"
  vpc_id      = data.aws_vpc.main.id

  tags = local.security_group_tags
}

resource "aws_security_group_rule" "atlantis_task_allow_inbound_from_lb" {
  type                     = "ingress"
  from_port                = var.container_port
  to_port                  = var.container_port
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.atlantis_lb_firewall.id
  security_group_id        = aws_security_group.atlantis_task_firewall.id
}

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

resource "aws_eip" "nlb_ip" {
  for_each = toset(data.aws_subnets.public.ids)
  domain   = "vpc"

  tags = {
    Name         = "${var.environment}-${var.service_name}-${each.value}-eip"
    environment  = var.environment
    service_name = var.service_name
    terraformed  = true
  }
}

# Provision an NLB in front of the ALB to provide static IP addresses for use in firewall configuration
resource "aws_lb" "atlantis_nlb" {
  name                       = "${var.environment}-${var.service_name}-nlb"
  internal                   = false
  load_balancer_type         = "network"
  enable_deletion_protection = false

  access_logs {
    bucket  = data.aws_s3_bucket.lb_log_bucket.id
    prefix  = "${data.aws_caller_identity.current.account_id}/${var.environment}-${var.service_name}-nlb"
    enabled = true
  }

  dynamic "subnet_mapping" {
    for_each = data.aws_subnets.public.ids
    content {
      subnet_id     = subnet_mapping.value
      allocation_id = aws_eip.nlb_ip[subnet_mapping.value].allocation_id
    }
  }

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

resource "aws_lb_listener" "atlantis_nlb_listener" {
  load_balancer_arn = aws_lb.atlantis_nlb.arn
  port              = "443"
  protocol          = "TCP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.atlantis_nlb_target_group.arn
  }
}

resource "aws_lb_target_group" "atlantis_nlb_target_group" {
  name        = "${var.environment}-${var.service_name}-nlb-tg"
  port        = 443
  protocol    = "TCP"
  target_type = "alb"
  vpc_id      = data.aws_vpc.main.id

  health_check {
    protocol            = "HTTPS"
    interval            = 10
    healthy_threshold   = 2
    unhealthy_threshold = 3
    timeout             = 5
    matcher             = "200"
  }
}

resource "aws_lb_target_group_attachment" "atlantis_nlb_target_group_attachment" {
  depends_on       = [aws_lb_listener.atlantis_alb_listener]
  target_group_arn = aws_lb_target_group.atlantis_nlb_target_group.arn
  target_id        = aws_lb.atlantis_alb.arn
}

resource "aws_route53_record" "atlantis" {
  zone_id = data.aws_route53_zone.route53_zone.zone_id
  name    = "${var.environment}-${var.service_name}"
  type    = "A"

  alias {
    name                   = aws_lb.atlantis_nlb.dns_name
    zone_id                = aws_lb.atlantis_nlb.zone_id
    evaluate_target_health = true
  }
}

module "atlantis_service_dashboard" {
  source                            = "git@github.com:theorchard/terraform-datadog.git//modules/service?ref=6.17.1"
  environment                       = var.environment
  environment_type                  = "fargate"
  service_name                      = var.service_name
  application_family                = var.application_family
  notification_endpoints            = "@slack-terraform"
  escalation_notification_endpoints = "@slack-terraform"
}

module "atlantis_waf" {
  source            = "git@github.com:theorchard/terraform-aws-waf.git//?ref=2.0.2"
  environment       = var.environment
  service_name      = var.service_name
  aws_region        = var.aws_region
  count_waf_enabled = false
  block_waf_enabled = true

  excluded_rules = [
    "SizeRestrictions_BODY",
  ]
}

moved {
  from = aws_wafv2_web_acl_logging_configuration.waf_block_web_acl_logging_configuration
  to   = module.atlantis_waf.aws_wafv2_web_acl_logging_configuration.waf[0]
}

resource "aws_wafv2_web_acl_association" "waf_association" {
  resource_arn = aws_lb.atlantis_alb.arn
  web_acl_arn  = module.atlantis_waf.waf_blocking_arn_output
}

# If the GH_WEBHOOK_SECRET secret has not been updated since creation when this is applied, the value will be "dummy".
# As such, please update the secret value and apply this resource once more, which should update the hook in place.
resource "github_repository_webhook" "atlantis" {
  repository = data.github_repository.repository.name
  active     = true

  configuration {
    url          = "https://${aws_route53_record.atlantis.fqdn}/events"
    content_type = "json"
    insecure_ssl = false
    secret       = data.aws_secretsmanager_secret_version.github_webook_secret.secret_string
  }

  events = [
    "issue_comment",
    "pull_request",
    "pull_request_review",
    "push",
  ]
}
