diff --git a/environments/stage/services/oai/main.tf b/environments/stage/services/oai/main.tf new file mode 100644 index 000000000..0d4b7db2a --- /dev/null +++ b/environments/stage/services/oai/main.tf @@ -0,0 +1,23 @@ +module "oai_ecs_service" { + source = "../../../../modules/services/oai" + + app_name = "oai" + env = "stage" + vpc_id = var.vpc_id + desired_container_count = 1 + security_group_id = var.security_group_id + subnet_datacite-private_id = var.subnet_datacite-private + subnet_datacite-alt = var.subnet_datacite-alt + task_cpu = 512 + task_memory = 1024 + namespace_id = var.namespace_id + dns_record_name = "oai.stage.datacite.org" + lb_priority = 61 + container_version = "latest" + api_url = var.api_url + api_password = var.api_password + base_url = var.base_url + public_key = var.public_key + sentry_dsn = var.sentry_dsn + log_level = "info" +} diff --git a/environments/stage/services/oai/provider.tf b/environments/stage/services/oai/provider.tf new file mode 100644 index 000000000..cba161be5 --- /dev/null +++ b/environments/stage/services/oai/provider.tf @@ -0,0 +1,5 @@ +provider "aws" { + access_key = "${var.access_key}" + secret_key = "${var.secret_key}" + region = "${var.region}" +} diff --git a/environments/stage/services/oai/terraform.tf b/environments/stage/services/oai/terraform.tf new file mode 100644 index 000000000..0b1be728e --- /dev/null +++ b/environments/stage/services/oai/terraform.tf @@ -0,0 +1,8 @@ +terraform { + cloud { + organization = "datacite-ng" + workspaces { + tags = ["stage", "app:oai"] + } + } +} \ No newline at end of file diff --git a/environments/stage/services/oai/variables.tf b/environments/stage/services/oai/variables.tf new file mode 100644 index 000000000..2662e37e2 --- /dev/null +++ b/environments/stage/services/oai/variables.tf @@ -0,0 +1,46 @@ +variable "viringo_tags" { + type = "map" +} + +variable "vpc_id" { + type = "string" +} + +variable "namespace_id" { + type = "string" +} + +variable "security_group_id" { + type = "string" +} + +variable "subnet_datacite-private_id" { + type = "string" +} + +variable "subnet_datacite-alt_id" { + type = "string" +} + + +# App specific variables + +variable "base_url" { + default = "https://oai.stage.datacite.org/oai" +} + +variable "api_url" { + default = "http://client-api.stage.local" +} + +variable "api_password" { + type = "string" +} + +variable "sentry_dsn" { + type = "string" +} + +variable "public_key" { + type = "string" +} diff --git a/modules/service/main.tf b/modules/service/main.tf new file mode 100644 index 000000000..74e6ed30d --- /dev/null +++ b/modules/service/main.tf @@ -0,0 +1,145 @@ +data "aws_ecs_cluster" "ecs_cluster" { + cluster_name = var.env +} + +data "aws_iam_role" "ecs_task_execution_role" { + name = "ecsTaskExecutionRole" +} + +data "aws_security_group" "datacite-private" { + id = var.security_group_id +} + +data "aws_subnet" "datacite-private" { + id = var.subnet_datacite-private_id +} + +data "aws_subnet" "datacite-alt" { + id = var.subnet_datacite-alt_id +} + +data "aws_lb" "lb" { + name = var.lb_name +} + +data "aws_lb_listener" "lb_listener" { + load_balancer_arn = data.aws_lb.lb.arn + port = 443 +} + +data "aws_route53_zone" "production" { + name = "datacite.org" +} + +data "aws_route53_zone" "internal" { + name = "datacite.org" + private_zone = true +} + +resource "aws_ecs_service" "ecs-service" { + name = "${var.app_name}-${var.env}" + launch_type = var.launch_type + cluster = aws_ecs_cluster.ecs_cluster.id + task_definition = aws_ecs_task_definition.ecs_task_definition.arn + desired_count = var.desired_container_count + + depends_on = [ + data.aws_lb_listener.lb_listener, + ] + + network_configuration { + security_groups = [data.aws_security_group.datacite-private.id] + subnets = [ + data.aws_subnet.datacite-private.id, + data.aws_subnet.datacite-alt.id, + ] + } + + load_balancer { + target_group_arn = aws_lb_target_group.lb_target_group.id + container_name = var.app_name + container_port = var.container_port + } + + service_registries { + registry_arn = aws_service_discovery_service.service-discovery.arn + } + +} + +resource "aws_cloudwatch_log_group" "log_group" { + name = "${var.app_name}-${var.env}" +} + +resource "aws_ecs_task_definition" "ecs_task_definition" { + family = var.app_name + execution_role_arn = data.aws_iam_role.ecs_task_execution_role.arn + network_mode = var.network_mode + requires_compatibilities = var.requires_compatibilities + cpu = var.task_cpu + memory = var.task_memory + + container_definitions = var.container_definition_json +} + +resource "aws_service_discovery_service" "service_discovery" { + name = "${var.app_name}.${var.env}" + + health_check_custom_config { + failure_threshold = 3 + } + + dns_config { + namespace_id = var.namespace_id + + dns_records { + ttl = 300 + type = "A" + } + } +} + +resource "aws_lb_target_group" "lb_target_group" { + name = "${var.app_name}-${var.env}" + port = 80 + protocol = "HTTP" + vpc_id = var.vpc_id + target_type = "ip" + slow_start = 240 + + health_check { + path = var.health_check_path + } +} + + +resource "aws_lb_listener_rule" "primary_listener_rule" { + listener_arn = aws_lb_listener.lb_listener.arn + priority = var.lb_priority + + action { + type = "forward" + target_group_arn = aws_lb_target_group.lb_target_group.id + } + + condition { + field = "host-header" + values = [aws_route53_record.primary_record.name] + } +} + +resource "aws_route53_record" "primary_record" { + zone_id = data.aws_route53_zone.production.zone_id + name = var.dns_record_name + type = "CNAME" + ttl = var.ttl + records = [data.aws_lb.default.dns_name] +} + +resource "aws_route53_record" "split_record" { + zone_id = data.aws_route53_zone.internal.zone_id + name = var.dns_record_name + type = "CNAME" + ttl = var.ttl + records = [data.aws_lb.default.dns_name] +} diff --git a/modules/service/outputs.tf b/modules/service/outputs.tf new file mode 100644 index 000000000..31497fce8 --- /dev/null +++ b/modules/service/outputs.tf @@ -0,0 +1,15 @@ +output "ecs_service_name" { + value = aws_ecs_service.ecs-service.name +} + +output "aws_lb_listener_arn" { + value = aws_lb_listener.lb_listener.arn +} + +output "aws_lb_target_group_arn" { + value = aws_lb_target_group.lb_target_group.arn +} + +output "log_group_name" { + value = aws_cloudwatch_log_group.log_group.name +} \ No newline at end of file diff --git a/modules/service/variables.tf b/modules/service/variables.tf new file mode 100644 index 000000000..6c4d7679a --- /dev/null +++ b/modules/service/variables.tf @@ -0,0 +1,94 @@ +variable "app_name" { + type = "string" + description = "Name of the application e.g. client-api" +} + +variable "env" { + type = "string" + description = "Environment prod|stage|test" +} + +variable "vpc_id" {} + +variable "desired_container_count" { + type = "string" +} + +variable "lb_name" { + type = "string" + default = "lb-${var.env}" +} + +variable "security_group_id" { + type = "string" +} + +variable "subnet_datacite-private" { + type = "string" +} + +variable "subnet_datacite-alt" { + type = "string" +} + +variable "container_port" { + type = "number" + default = 80 +} + +variable "launch_type" { + type = "string" + default = "FARGATE" +} + +variable "requires_compatibilities" { + type = "list" + default = ["FARGATE"] +} + +variable "network_mode" { + type = string + description = "The network mode to use for the task. This is required to be `awsvpc` for `FARGATE` `launch_type` or `null` for `EC2` `launch_type`" + default = "awsvpc" +} + +variable "task_cpu" { + type = number + description = "The number of CPU units used by the task. If using `FARGATE` launch type `task_cpu` must match [supported memory values](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#task_size)" + default = 256 +} + +variable "task_memory" { + type = number + description = "The amount of memory (in MiB) used by the task. If using Fargate launch type `task_memory` must match [supported cpu value](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#task_size)" + default = 512 +} + +variable "container_definition_json" { + type = string + description = "A string containing a JSON-encoded array of container definitions" +} + +variable "health_check_path" { + type = string + description = "The path to the health check endpoint" + default = "/heartbeat" +} + +variable "namespace_id" { + type = "string" +} + +variable "lb_priority" { + type = "number" + default = 1 +} + +variable "dns_record_name" { + type = "string" + description = "Fully qualified domain name for the record i.e. api.datacite.org" +} + +variable "ttl" { + default = "300" +} diff --git a/modules/services/oai/main.tf b/modules/services/oai/main.tf new file mode 100644 index 000000000..42f32cb2a --- /dev/null +++ b/modules/services/oai/main.tf @@ -0,0 +1,34 @@ + +module "oai_ecs_service" { + source = "../../service" + + app_name = "oai" + env = var.env + vpc_id = var.vpc_id + desired_container_count = var.desired_container_count + security_group_id = var.security_group_id + subnet_datacite-private_id = var.subnet_datacite-private + subnet_datacite-alt = var.subnet_datacite-alt + task_cpu = var.task_cpu + task_memory = var.task_memory + container_definition_json = data.template_file.oai_task.rendered + namespace_id = var.namespace_id + dns_record_name = var.dns_record_name + lb_priority = var.lb_priority +} + +data "template_file" "oai_task" { + template = "${file("oai.json")}" + + vars { + app_name = "oai" + container_version = var.container_version + log_group = oai_ecs_service.log_group_name + api_url = var.api_url + api_password = var.api_password + base_url = var.base_url + public_key = var.public_key + sentry_dsn = var.sentry_dsn + log_level = var.log_level + } +} diff --git a/modules/services/oai/oai.json b/modules/services/oai/oai.json new file mode 100644 index 000000000..f7710f2c6 --- /dev/null +++ b/modules/services/oai/oai.json @@ -0,0 +1,48 @@ +[ + { + "name": "${app_name}", + "image": "datacite/viringo:${container_version}", + "networkMode": "awsvpc", + "essential": true, + "portMappings": [ + { + "containerPort": 80, + "hostPort": 80 + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "${log_group}", + "awslogs-region": "eu-west-1", + "awslogs-stream-prefix": "ecs" + } + }, + "environment": [ + { + "name": "PUBLIC_KEY", + "value": "${public_key}" + }, + { + "name": "DATACITE_API_URL", + "value": "${api_url}" + }, + { + "name": "DATACITE_API_ADMIN_PASSWORD", + "value": "${api_password}" + }, + { + "name": "LOG_LEVEL", + "value": "${log_level}" + }, + { + "name": "OAIPMH_BASE_URL", + "value": "${base_url}" + }, + { + "name": "SENTRY_DSN", + "value": "${sentry_dsn}" + } + ] + } +] \ No newline at end of file diff --git a/modules/services/oai/outputs.tf b/modules/services/oai/outputs.tf new file mode 100644 index 000000000..e69de29bb diff --git a/modules/services/oai/variables.tf b/modules/services/oai/variables.tf new file mode 100644 index 000000000..6633935fd --- /dev/null +++ b/modules/services/oai/variables.tf @@ -0,0 +1,75 @@ + +variable "env" { + type = "string" + description = "Environment prod|stage|test" +} + +variable "container_version" { + type = "string" + description = "Version of the container to deploy" + default = "latest" +} + +variable "vpc_id" {} + +variable "desired_container_count" { + type = "string" +} + +variable "security_group_id" { + type = "string" +} + +variable "subnet_datacite-private" { + type = "string" +} + +variable "subnet_datacite-alt" { + type = "string" +} + +variable "task_cpu" { + type = number + description = "The number of CPU units used by the task. If using `FARGATE` launch type `task_cpu` must match [supported memory values](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#task_size)" + default = 256 +} + +variable "task_memory" { + type = number + description = "The amount of memory (in MiB) used by the task. If using Fargate launch type `task_memory` must match [supported cpu value](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#task_size)" + default = 512 +} + +variable "lb_priority" { + type = "number" + default = 1 +} + +variable "dns_record_name" { + type = "string" + description = "Fully qualified domain name for the record i.e. api.datacite.org" +} + +variable "namespace_id" { + type = "string" +} + +variable "base_url" { + default = "https://oai.stage.datacite.org/oai" +} + +variable "api_url" { + default = "http://client-api.stage.local" +} + +variable "api_password" { + type = "string" +} + +variable "sentry_dsn" { + type = "string" +} + +variable "public_key" { + type = "string" +}