Reputation: 150
We create ECS services in Terraform by defining a template_file which populates a task definition JSON template with all needed variables. Then a aws_ecs_task_definition
is created with the rendered template_file. With this task definition the aws_ecs_service
is created:
data "template_file" "web" {
template = "${file("${path.module}/tasks/web.json")}"
vars {
...
}
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web.rendered}"
requires_compatibilities = ["FARGATE"]
...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
}
resource "aws_ecs_service" "web" {
name = "web"
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
...
}
There are additional services with task definitions nearly identical to the first one, only having small differences like another command (for example for starting sidekiq instead of the web app).
Is there any other way of doing this other than duplicating everything (the JSON template, template_file
with all defined variables, aws_ecs_task_definition
and aws_ecs_service
)?
Upvotes: 3
Views: 2025
Reputation: 150
Expanding on the accepted answer to show how to also remove the duplication caused by the defined variables in the template_file
vars block (which do not change and thus would have to be duplicated between invocations of the module). It is also not a solution to just inline these variables or use defaults because they still will change between projects, just not within services of the same project. We can use local variables for setting the defaults and overriding defaults with the merge function:
locals {
task_variables = {
image = "..."
# lots of other variables
command = "[\"nginx\", \"-g\", \"daemon off; error_log /dev/stdout info;\"]"
}
}
# first invocation of the module, overriding the command
module "sidekiq" {
source = "ecs_service"
...
task_variables = "${merge(
local.task_variables,
map(
"command", "[\"bash\", \"-c\", \"exec bundle exec sidekiq\"]",
)
)}"
}
# second invocation of the module, no overrides
module "web" {
source = "ecs_service"
task_variables = "${local.task_variables}"
}
variable "task_variables" {
type = "map"
}
data "template_file" "web_task" {
template = "${file("${path.module}/tasks/task_definition.json")}"
vars = "${var.task_variables}"
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web_task.rendered}"
...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
...
}
resource "aws_ecs_service" "web" {
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
...
}
Upvotes: 0
Reputation: 56877
Modules are the main way to solve this in Terraform.
If you move your existing code into a single folder you can then define variables that allow you to customise that module such as the command to be passed to your ECS service.
So in your case you might have something like this:
data "template_file" "web" {
template = "${file("${path.module}/tasks/web.json")}"
vars {
# ...
command = "${var.command}"
}
}
resource "aws_ecs_task_definition" "web" {
container_definitions = "${data.template_file.web.rendered}"
requires_compatibilities = ["FARGATE"]
# ...
}
data "aws_ecs_task_definition" "web" {
task_definition = "${aws_ecs_task_definition.web.family}"
}
resource "aws_ecs_service" "web" {
name = "web"
task_definition = "${aws_ecs_task_definition.web.family}:${max("${aws_ecs_task_definition.web.revision}", "${data.aws_ecs_task_definition.web.revision}")}"
# ...
}
variable "command" {}
module "foo_service_web" {
source = "../modules/foo-service"
command = "bundle exec server"
}
module "foo_service_sidekiq" {
source = "../modules/foo-service"
command = "bundle exec sidekiq"
}
Upvotes: 2