Skip to content

Commit

Permalink
Merge pull request #275 from trussworks/adh-enable-execute-command
Browse files Browse the repository at this point in the history
Add support for enable_execute_command
  • Loading branch information
Michael Kania authored Sep 1, 2021
2 parents 86c3c04 + efa5c22 commit 1b3762f
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 8 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ module "app_ecs_service" {
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 3.34 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.0 |
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 3.34 |

## Modules

Expand All @@ -127,10 +127,12 @@ No modules.
| [aws_cloudwatch_metric_alarm.alarm_mem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource |
| [aws_ecs_service.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource |
| [aws_ecs_task_definition.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource |
| [aws_iam_policy.task_role_ecs_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.task_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role.task_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy.instance_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy.task_execution_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource |
| [aws_iam_role_policy_attachment.task_role_ecs_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_security_group.ecs_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws_security_group_rule.app_ecs_allow_health_check_from_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
| [aws_security_group_rule.app_ecs_allow_health_check_from_nlb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource |
Expand All @@ -141,6 +143,7 @@ No modules.
| [aws_iam_policy_document.ecs_assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.instance_role_policy_doc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.task_execution_role_policy_doc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.task_role_ecs_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |

## Inputs
Expand All @@ -163,6 +166,7 @@ No modules.
| <a name="input_ec2_create_task_execution_role"></a> [ec2\_create\_task\_execution\_role](#input\_ec2\_create\_task\_execution\_role) | Set to true to create ecs task execution role to ECS EC2 Tasks. | `bool` | `false` | no |
| <a name="input_ecr_repo_arns"></a> [ecr\_repo\_arns](#input\_ecr\_repo\_arns) | The ARNs of the ECR repos. By default, allows all repositories. | `list(string)` | <pre>[<br> "*"<br>]</pre> | no |
| <a name="input_ecs_cluster"></a> [ecs\_cluster](#input\_ecs\_cluster) | ECS cluster object for this task. | <pre>object({<br> arn = string<br> name = string<br> })</pre> | n/a | yes |
| <a name="input_ecs_exec_enable"></a> [ecs\_exec\_enable](#input\_ecs\_exec\_enable) | Enable the ability to execute commands on the containers via Amazon ECS Exec | `bool` | `false` | no |
| <a name="input_ecs_instance_role"></a> [ecs\_instance\_role](#input\_ecs\_instance\_role) | The name of the ECS instance role. | `string` | `""` | no |
| <a name="input_ecs_subnet_ids"></a> [ecs\_subnet\_ids](#input\_ecs\_subnet\_ids) | Subnet IDs for the ECS tasks. | `list(string)` | n/a | yes |
| <a name="input_ecs_use_fargate"></a> [ecs\_use\_fargate](#input\_ecs\_use\_fargate) | Whether to use Fargate for the task definition. | `bool` | `false` | no |
Expand Down
1 change: 1 addition & 0 deletions examples/no-load-balancer/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ module "ecs-service" {
ecs_subnet_ids = module.vpc.public_subnets
ecs_vpc_id = module.vpc.vpc_id
ecs_use_fargate = true
ecs_exec_enable = var.ecs_exec_enable
assign_public_ip = true
additional_security_group_ids = [
aws_security_group.ecs_allow_http.id
Expand Down
5 changes: 5 additions & 0 deletions examples/no-load-balancer/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ variable "test_name" {
variable "vpc_azs" {
type = list(string)
}

variable "ecs_exec_enable" {
type = bool
}

58 changes: 56 additions & 2 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,59 @@ resource "aws_iam_role_policy" "task_execution_role_policy" {
policy = data.aws_iam_policy_document.task_execution_role_policy_doc.json
}

#
# ECS Exec
#

data "aws_iam_policy_document" "task_role_ecs_exec" {
count = var.ecs_exec_enable ? 1 : 0
statement {
sid = "AllowECSExec"
effect = "Allow"

actions = [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel"
]

resources = ["*"]
}

statement {
sid = "AllowDescribeLogGroups"
actions = [
"logs:DescribeLogGroups",
]

resources = ["*"]
}

statement {
sid = "AllowECSExecLogging"
actions = [
"logs:CreateLogStream",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
]
resources = ["${aws_cloudwatch_log_group.main.arn}:*"]
}
}

resource "aws_iam_policy" "task_role_ecs_exec" {
count = var.ecs_exec_enable ? 1 : 0
name = "${aws_iam_role.task_role.name}-ecs-exec"
description = "Allow ECS Exec with Cloudwatch logging when attached to an ECS task role"
policy = join("", data.aws_iam_policy_document.task_role_ecs_exec.*.json)
}

resource "aws_iam_role_policy_attachment" "task_role_ecs_exec" {
count = var.ecs_exec_enable ? 1 : 0
role = join("", aws_iam_role.task_role.*.name)
policy_arn = join("", aws_iam_policy.task_role_ecs_exec.*.arn)
}

#
# ECS
#
Expand Down Expand Up @@ -447,8 +500,9 @@ resource "aws_ecs_service" "main" {
name = var.name
cluster = var.ecs_cluster.arn

launch_type = local.ecs_service_launch_type
platform_version = local.fargate_platform_version
launch_type = local.ecs_service_launch_type
platform_version = local.fargate_platform_version
enable_execute_command = var.ecs_exec_enable

# Use latest active revision
task_definition = "${aws_ecs_task_definition.main.family}:${max(
Expand Down
41 changes: 38 additions & 3 deletions test/terraform_aws_ecs_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/terraform"
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
"github.com/stretchr/testify/require"
)

func TestTerraformAwsEcsServiceNoLoadBalancer(t *testing.T) {
Expand All @@ -29,9 +30,10 @@ func TestTerraformAwsEcsServiceNoLoadBalancer(t *testing.T) {

// Variables to pass to our Terraform code using -var options
Vars: map[string]interface{}{
"test_name": ecsServiceName,
"vpc_azs": vpcAzs,
"region": awsRegion,
"test_name": ecsServiceName,
"vpc_azs": vpcAzs,
"region": awsRegion,
"ecs_exec_enable": false,
},
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": awsRegion,
Expand Down Expand Up @@ -192,3 +194,36 @@ func TestTerraformAwsEcsServiceNlb(t *testing.T) {
timeBetweenRetries,
)
}

func TestTerraformAwsEcsServiceEcsExec(t *testing.T) {
t.Parallel()

tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, "../", "examples/no-load-balancer")

ecsServiceName := fmt.Sprintf("terratest-simple-%s", strings.ToLower(random.UniqueId()))
awsRegion := "us-west-2"
vpcAzs := aws.GetAvailabilityZones(t, awsRegion)[:3]

terraformOptions := &terraform.Options{
// The path to where our Terraform code is located
TerraformDir: tempTestFolder,

// Variables to pass to our Terraform code using -var options
Vars: map[string]interface{}{
"test_name": ecsServiceName,
"vpc_azs": vpcAzs,
"region": awsRegion,
"ecs_exec_enable": true,
},
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": awsRegion,
},
}

defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)

// Test by execing uname on the running container
err := EcsExecCommand(t, awsRegion, ecsServiceName, "uname")
require.Nil(t, err, err)
}
32 changes: 32 additions & 0 deletions test/test_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package test

import (
"fmt"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -90,3 +91,34 @@ func GetPublicIP(t *testing.T, region string, enis []string) *string {
publicIP := eniDetail.NetworkInterfaces[0].Association.PublicIp
return publicIP
}

func EcsExecCommand(t *testing.T, region string, cluster string, command string) error {
ecsClient, err := aws.NewEcsClientE(t, region)
if err != nil {
return err
}

tasksOutput := GetTasks(t, region, cluster)
taskSplit := strings.Split(*tasksOutput.TaskArns[0], "/")
task := taskSplit[len(taskSplit)-1]

params := &ecs.ExecuteCommandInput{
Cluster: awssdk.String(cluster),
Command: awssdk.String(command),
Task: awssdk.String(task),
Interactive: awssdk.Bool(true),
}

maxRetries := 3
retryDuration, _ := time.ParseDuration("30s")
_, err = retry.DoWithRetryE(t, fmt.Sprintf("Execute ECS command with params %v", params), maxRetries, retryDuration, func() (string, error) {
req, _ := ecsClient.ExecuteCommandRequest(params)
err = req.Send()
if err != nil {
return "failed to execute command", err
}
return fmt.Sprintf("Executed command %s", command), nil
},
)
return err
}
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,9 @@ variable "health_check_grace_period_seconds" {
default = null
type = number
}

variable "ecs_exec_enable" {
description = "Enable the ability to execute commands on the containers via Amazon ECS Exec"
default = false
type = bool
}
2 changes: 1 addition & 1 deletion versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 3.0"
version = ">= 3.34"
}
}
}

0 comments on commit 1b3762f

Please sign in to comment.