Skip to content

Commit 79d152e

Browse files
feat: Added support for persisting Atlantis state using EFS (#247)
Co-authored-by: Anton Babenko <[email protected]>
1 parent 1293fb5 commit 79d152e

File tree

4 files changed

+95
-10
lines changed

4 files changed

+95
-10
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ allow_github_webhooks = true
248248
| <a name="module_container_definition_bitbucket"></a> [container\_definition\_bitbucket](#module\_container\_definition\_bitbucket) | cloudposse/ecs-container-definition/aws | v0.58.1 |
249249
| <a name="module_container_definition_github_gitlab"></a> [container\_definition\_github\_gitlab](#module\_container\_definition\_github\_gitlab) | cloudposse/ecs-container-definition/aws | v0.58.1 |
250250
| <a name="module_ecs"></a> [ecs](#module\_ecs) | terraform-aws-modules/ecs/aws | v3.3.0 |
251+
| <a name="module_efs_sg"></a> [efs\_sg](#module\_efs\_sg) | terraform-aws-modules/security-group/aws//modules/nfs | v4.8.0 |
251252
| <a name="module_vpc"></a> [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | v3.6.0 |
252253

253254
## Resources
@@ -257,6 +258,9 @@ allow_github_webhooks = true
257258
| [aws_cloudwatch_log_group.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
258259
| [aws_ecs_service.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource |
259260
| [aws_ecs_task_definition.atlantis](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource |
261+
| [aws_efs_access_point.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_access_point) | resource |
262+
| [aws_efs_file_system.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_file_system) | resource |
263+
| [aws_efs_mount_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/efs_mount_target) | resource |
260264
| [aws_iam_role.ecs_task_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
261265
| [aws_iam_role_policy.ecs_task_access_secrets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
262266
| [aws_iam_role_policy_attachment.ecs_task_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
@@ -337,8 +341,8 @@ allow_github_webhooks = true
337341
| <a name="input_ecs_container_insights"></a> [ecs\_container\_insights](#input\_ecs\_container\_insights) | Controls if ECS Cluster has container insights enabled | `bool` | `false` | no |
338342
| <a name="input_ecs_fargate_spot"></a> [ecs\_fargate\_spot](#input\_ecs\_fargate\_spot) | Whether to run ECS Fargate Spot or not | `bool` | `false` | no |
339343
| <a name="input_ecs_service_assign_public_ip"></a> [ecs\_service\_assign\_public\_ip](#input\_ecs\_service\_assign\_public\_ip) | Should be true, if ECS service is using public subnets (more info: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_cannot_pull_image.html) | `bool` | `false` | no |
340-
| <a name="input_ecs_service_deployment_maximum_percent"></a> [ecs\_service\_deployment\_maximum\_percent](#input\_ecs\_service\_deployment\_maximum\_percent) | The upper limit (as a percentage of the service's desiredCount) of the number of running tasks that can be running in a service during a deployment | `number` | `200` | no |
341-
| <a name="input_ecs_service_deployment_minimum_healthy_percent"></a> [ecs\_service\_deployment\_minimum\_healthy\_percent](#input\_ecs\_service\_deployment\_minimum\_healthy\_percent) | The lower limit (as a percentage of the service's desiredCount) of the number of running tasks that must remain running and healthy in a service during a deployment | `number` | `50` | no |
344+
| <a name="input_ecs_service_deployment_maximum_percent"></a> [ecs\_service\_deployment\_maximum\_percent](#input\_ecs\_service\_deployment\_maximum\_percent) | The upper limit (as a percentage of the service's desiredCount) of the number of running tasks that can be running in a service during a deployment | `number` | `100` | no |
345+
| <a name="input_ecs_service_deployment_minimum_healthy_percent"></a> [ecs\_service\_deployment\_minimum\_healthy\_percent](#input\_ecs\_service\_deployment\_minimum\_healthy\_percent) | The lower limit (as a percentage of the service's desiredCount) of the number of running tasks that must remain running and healthy in a service during a deployment | `number` | `0` | no |
342346
| <a name="input_ecs_service_desired_count"></a> [ecs\_service\_desired\_count](#input\_ecs\_service\_desired\_count) | The number of instances of the task definition to place and keep running | `number` | `1` | no |
343347
| <a name="input_ecs_service_enable_execute_command"></a> [ecs\_service\_enable\_execute\_command](#input\_ecs\_service\_enable\_execute\_command) | Enable ECS exec for the service. This can be used to allow interactive sessions and commands to be executed in the container | `bool` | `true` | no |
344348
| <a name="input_ecs_service_force_new_deployment"></a> [ecs\_service\_force\_new\_deployment](#input\_ecs\_service\_force\_new\_deployment) | Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination (e.g. myimage:latest) | `bool` | `false` | no |
@@ -379,7 +383,7 @@ allow_github_webhooks = true
379383
| <a name="input_trusted_principals"></a> [trusted\_principals](#input\_trusted\_principals) | A list of principals, in addition to ecs-tasks.amazonaws.com, that can assume the task role | `list(string)` | `[]` | no |
380384
| <a name="input_ulimits"></a> [ulimits](#input\_ulimits) | Container ulimit settings. This is a list of maps, where each map should contain "name", "hardLimit" and "softLimit" | <pre>list(object({<br> name = string<br> hardLimit = number<br> softLimit = number<br> }))</pre> | `null` | no |
381385
| <a name="input_use_ecs_old_arn_format"></a> [use\_ecs\_old\_arn\_format](#input\_use\_ecs\_old\_arn\_format) | A flag to enable/disable tagging the ecs resources that require the new longer arn format | `bool` | `false` | no |
382-
| <a name="input_user"></a> [user](#input\_user) | The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set. | `string` | `null` | no |
386+
| <a name="input_user"></a> [user](#input\_user) | The user to run as inside the container. Must be in the uid:gid or the default (null) will use the container's configured `USER` directive or root if not set. | `string` | `null` | no |
383387
| <a name="input_volumes_from"></a> [volumes\_from](#input\_volumes\_from) | A list of VolumesFrom maps which contain "sourceContainer" (name of the container that has the volumes to mount) and "readOnly" (whether the container can write to the volume) | <pre>list(object({<br> sourceContainer = string<br> readOnly = bool<br> }))</pre> | `[]` | no |
384388
| <a name="input_vpc_id"></a> [vpc\_id](#input\_vpc\_id) | ID of an existing VPC where resources will be created | `string` | `""` | no |
385389
| <a name="input_webhook_ssm_parameter_name"></a> [webhook\_ssm\_parameter\_name](#input\_webhook\_ssm\_parameter\_name) | Name of SSM parameter to keep webhook secret | `string` | `"/atlantis/webhook/secret"` | no |

examples/github-complete/main.tf

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ module "atlantis" {
5757
start_timeout = 30
5858
stop_timeout = 30
5959

60-
user = "atlantis"
6160
readonly_root_filesystem = false # atlantis currently mutable access to root filesystem
6261
ulimits = [{
6362
name = "nofile"

main.tf

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ locals {
110110
sort(compact(concat(var.allow_github_webhooks ? var.github_webhooks_cidr_blocks : [], var.whitelist_unauthenticated_cidr_blocks))),
111111
5
112112
)
113+
114+
# break up user to uid and gid -- set both to 0 if null
115+
uid = var.user == null ? 0 : split(":", var.user)[0]
116+
gid = var.user == null ? 0 : split(":", var.user)[1]
117+
118+
# default mount points for efs if ephemeral storage is not enabled and mount points aren't specified
119+
mount_points = var.enable_ephemeral_storage || length(var.mount_points) > 0 ? var.mount_points : [{
120+
containerPath = "/home/atlantis"
121+
sourceVolume = "efs-storage"
122+
readOnly = "false"
123+
}]
113124
}
114125

115126
data "aws_partition" "current" {}
@@ -189,8 +200,9 @@ module "vpc" {
189200
private_subnets = var.private_subnets
190201
public_subnets = var.public_subnets
191202

192-
enable_nat_gateway = true
193-
single_nat_gateway = true
203+
enable_nat_gateway = true
204+
single_nat_gateway = true
205+
enable_dns_hostnames = !var.enable_ephemeral_storage
194206

195207
manage_default_security_group = var.manage_default_security_group
196208
default_security_group_ingress = var.default_security_group_ingress
@@ -355,6 +367,24 @@ module "atlantis_sg" {
355367
tags = merge(local.tags, var.atlantis_security_group_tags)
356368
}
357369

370+
module "efs_sg" {
371+
source = "terraform-aws-modules/security-group/aws//modules/nfs"
372+
version = "v4.8.0"
373+
count = var.enable_ephemeral_storage ? 0 : 1
374+
375+
name = "${var.name}-efs"
376+
vpc_id = local.vpc_id
377+
description = "Security group allowing access to the EFS storage"
378+
379+
ingress_cidr_blocks = [var.cidr]
380+
ingress_with_source_security_group_id = [{
381+
rule = "nfs-tcp",
382+
source_security_group_id = module.atlantis_sg.security_group_id
383+
}]
384+
385+
tags = local.tags
386+
}
387+
358388
################################################################################
359389
# ACM (SSL certificate)
360390
################################################################################
@@ -388,6 +418,35 @@ resource "aws_route53_record" "atlantis" {
388418
}
389419
}
390420

421+
################################################################################
422+
# EFS
423+
################################################################################
424+
425+
resource "aws_efs_file_system" "this" {
426+
count = var.enable_ephemeral_storage ? 0 : 1
427+
428+
creation_token = var.name
429+
}
430+
431+
resource "aws_efs_mount_target" "this" {
432+
# we coalescelist in order to specify the resource keys when we create the subnets using the VPC or they're specified for us. This works around the for_each value depends on attributes which can't be determined until apply error
433+
for_each = zipmap(coalescelist(var.private_subnets, var.private_subnet_ids), local.private_subnet_ids)
434+
435+
file_system_id = aws_efs_file_system.this[0].id
436+
subnet_id = each.value
437+
security_groups = [module.efs_sg[0].security_group_id, module.atlantis_sg.security_group_id]
438+
}
439+
440+
resource "aws_efs_access_point" "this" {
441+
count = var.enable_ephemeral_storage ? 0 : 1
442+
443+
file_system_id = aws_efs_file_system.this[0].id
444+
posix_user {
445+
gid = local.gid
446+
uid = local.uid
447+
}
448+
}
449+
391450
################################################################################
392451
# ECS
393452
################################################################################
@@ -521,7 +580,7 @@ module "container_definition_github_gitlab" {
521580
container_depends_on = var.container_depends_on
522581
essential = var.essential
523582
readonly_root_filesystem = var.readonly_root_filesystem
524-
mount_points = var.mount_points
583+
mount_points = local.mount_points
525584
volumes_from = var.volumes_from
526585

527586
port_mappings = [
@@ -624,11 +683,29 @@ resource "aws_ecs_task_definition" "atlantis" {
624683

625684
dynamic "ephemeral_storage" {
626685
for_each = var.enable_ephemeral_storage ? [1] : []
686+
627687
content {
628688
size_in_gib = var.ephemeral_storage_size
629689
}
630690
}
631691

692+
dynamic "volume" {
693+
for_each = var.enable_ephemeral_storage ? [] : [1]
694+
695+
content {
696+
name = "efs-storage"
697+
efs_volume_configuration {
698+
file_system_id = aws_efs_file_system.this[0].id
699+
transit_encryption = "ENABLED"
700+
transit_encryption_port = 2999
701+
authorization_config {
702+
access_point_id = aws_efs_access_point.this[0].id
703+
iam = "ENABLED"
704+
}
705+
}
706+
}
707+
}
708+
632709
tags = local.tags
633710
}
634711

@@ -676,6 +753,7 @@ resource "aws_ecs_service" "atlantis" {
676753

677754
dynamic "capacity_provider_strategy" {
678755
for_each = var.ecs_fargate_spot ? [true] : []
756+
679757
content {
680758
capacity_provider = "FARGATE_SPOT"
681759
weight = 100

variables.tf

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,13 @@ variable "ecs_service_platform_version" {
332332
variable "ecs_service_deployment_maximum_percent" {
333333
description = "The upper limit (as a percentage of the service's desiredCount) of the number of running tasks that can be running in a service during a deployment"
334334
type = number
335-
default = 200
335+
default = 100
336336
}
337337

338338
variable "ecs_service_deployment_minimum_healthy_percent" {
339339
description = "The lower limit (as a percentage of the service's desiredCount) of the number of running tasks that must remain running and healthy in a service during a deployment"
340340
type = number
341-
default = 50
341+
default = 0
342342
}
343343

344344
variable "ecs_task_cpu" {
@@ -462,9 +462,13 @@ variable "volumes_from" {
462462
}
463463

464464
variable "user" {
465-
description = "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set."
465+
description = "The user to run as inside the container. Must be in the uid:gid or the default (null) will use the container's configured `USER` directive or root if not set."
466466
type = string
467467
default = null
468+
validation {
469+
condition = can(regex("[0-9]+:[0-9]+", var.user)) || var.user == null
470+
error_message = "User variable must be in the uid:gid format or null."
471+
}
468472
}
469473

470474
variable "ulimits" {

0 commit comments

Comments
 (0)