Skip to content

Commit

Permalink
Merge pull request #8 from theonestack/feature/ecs-service-tasks
Browse files Browse the repository at this point in the history
brings task def features inline with the ecs-services component
  • Loading branch information
aaronwalker authored Apr 1, 2021
2 parents c8390db + cfe9ae1 commit 46bc147
Show file tree
Hide file tree
Showing 13 changed files with 642 additions and 72 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/rspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: cftest

on: [push, pull_request]

jobs:
test:
name: test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: set up ruby 2.7
uses: actions/setup-ruby@v1
with:
ruby-version: 2.7.x
- name: install gems
run: gem install cfhighlander rspec
- name: set cfndsl spec
run: cfndsl -u
- name: cftest
run: rspec
env:
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ap-southeast-2
10 changes: 0 additions & 10 deletions .travis.yml

This file was deleted.

26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# hl-component-ecs-task
![cftest](https://github.com/theonestack/hl-component-ecs-task/actions/workflows/rspec.yaml/badge.svg)

### Cfhighlander ECS Task component

```bash

# install highlander gem
$ gem install cfhighlander

# build and validate standalone component
$ cfhighlander cfcompile --validate ecs-task

```

### Parameters

TBD

### Configuration options

TBD

### Outputs

TBD
2 changes: 2 additions & 0 deletions ecs-task.cfhighlander.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Description "ecs-task - #{component_name} - #{component_version}"

DependsOn 'lib-iam'

Parameters do
ComponentParam 'EnvironmentName', 'dev', isGlobal: true
ComponentParam 'EnvironmentType', 'development', allowedValues: ['development','production'], isGlobal: true
Expand Down
196 changes: 137 additions & 59 deletions ecs-task.cfndsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
RetentionInDays "#{log_retention}"
}

definitions, task_volumes, secrets, secrets_policy = Array.new(4){[]}
definitions, task_volumes, secrets = Array.new(4){[]}
secrets_policy = {}

task_definition = external_parameters.fetch(:task_definition, {})
task_definition.each do |task_name, task|
Expand Down Expand Up @@ -46,6 +47,10 @@
}
}

if task.has_key?('log_pattern')
task_def[:LogConfiguration][:Options]["awslogs-multiline-pattern"] = task['log_pattern']
end

task_def.merge!({ MemoryReservation: task['memory'] }) if task.has_key?('memory')
task_def.merge!({ Cpu: task['cpu'] }) if task.has_key?('cpu')

Expand Down Expand Up @@ -99,10 +104,13 @@
task_def.merge!({MountPoints: mount_points })
end

# volumes from
# add volumes from
if task.key?('volumes_from')
task['volumes_from'].each do |source_container|
task_def.merge!({ VolumesFrom: [ SourceContainer: source_container ] })
if task['volumes_from'].kind_of?(Array)
task['volumes_from'].each do |source_container|
volumes_from << { SourceContainer: source_container }
end
task_def.merge!({ VolumesFrom: volumes_from })
end
end

Expand All @@ -119,37 +127,91 @@
task_def.merge!({PortMappings: port_mapppings})
end

# add DependsOn
# The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed.
# For tasks using the EC2 launch type, the container instances require at least version 1.3.0 of the container agent to enable container dependencies
depends_on = []
if !(task['depends_on'].nil?)
task['depends_on'].each do |name,value|
depends_on << { ContainerName: name, Condition: value}
end
end

linux_parameters = {}

if task.key?('cap_add')
linux_parameters[:Capabilities] = {Add: task['cap_add']}
end

if task.key?('cap_drop')
if linux_parameters.key?(:Capabilities)
linux_parameters[:Capabilities][:Drop] = task['cap_drop']
else
linux_parameters[:Capabilities] = {Drop: task['cap_drop']}
end
end

if task.key?('init')
linux_parameters[:InitProcessEnabled] = task['init']
end

if task.key?('memory_swap')
linux_parameters[:MaxSwap] = task['memory_swap'].to_i
end

if task.key?('shm_size')
linux_parameters[:SharedMemorySize] = task['shm_size'].to_i
end

if task.key?('memory_swappiness')
linux_parameters[:Swappiness] = task['memory_swappiness'].to_i
end

task_def.merge!({LinuxParameters: linux_parameters}) if linux_parameters.any?
task_def.merge!({EntryPoint: task['entrypoint'] }) if task.key?('entrypoint')
task_def.merge!({Command: task['command'] }) if task.key?('command')
task_def.merge!({HealthCheck: task['healthcheck'] }) if task.key?('healthcheck')
task_def.merge!({WorkingDirectory: task['working_dir'] }) if task.key?('working_dir')
task_def.merge!({Privileged: task['privileged'] }) if task.key?('privileged')
task_def.merge!({User: task['user'] }) if task.key?('user')
task_def.merge!({DependsOn: depends_on }) if depends_on.length > 0
task_def.merge!({ ExtraHosts: task['extra_hosts'] }) if task.has_key?('extra_hosts')

if task.key?('secrets')

if task['secrets'].key?('ssm')
secrets.push *task['secrets']['ssm'].map {|k,v| { Name: k, ValueFrom: v.is_a?(String) && v.start_with?('/') ? FnSub("arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter#{v}") : v }}
resources = task['secrets']['ssm'].map {|k,v| v.is_a?(String) && v.start_with?('/') ? FnSub("arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter#{v}") : v }
secrets_policy.push iam_policy_allow('ssm-secrets','ssm:GetParameters', resources)
secrets_policy['ssm-secrets'] = {
'action' => 'ssm:GetParameters',
'resource' => resources
}
task['secrets'].reject! { |k| k == 'ssm' }
end

if task['secrets'].key?('secretsmanager')
secrets.push *task['secrets']['secretsmanager'].map {|k,v| { Name: k, ValueFrom: v.is_a?(String) && v.start_with?('/') ? FnSub("arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:#{v}") : v }}
resources = task['secrets']['secretsmanager'].map {|k,v| v.is_a?(String) && v.start_with?('/') ? FnSub("arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:#{v}-*") : v }
secrets_policy.push iam_policy_allow('secretsmanager','secretsmanager:GetSecretValue', resources)
secrets_policy['secretsmanager'] = {
'action' => 'secretsmanager:GetSecretValue',
'resource' => resources
}
task['secrets'].reject! { |k| k == 'secretsmanager' }
end

unless task['secrets'].empty?
secrets.push *task['secrets'].map {|k,v| { Name: k, ValueFrom: v.is_a?(String) && v.start_with?('/') ? FnSub("arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter#{v}") : v }}
resources = task['secrets'].map {|k,v| v.is_a?(String) && v.start_with?('/') ? FnSub("arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter#{v}") : v }
secrets_policy.push iam_policy_allow('ssm-secrets','ssm:GetParameters', resources)
secrets_policy['ssm-secrets-inline'] = {
'action' => 'ssm:GetParameters',
'resource' => resources
}
end

if secrets.any?
task_def.merge!({Secrets: secrets})
end

end

definitions << task_def
Expand All @@ -169,77 +231,93 @@
task_volumes << object
end

# add task placement constraints
task_constraints =[];
task_placement_constraints = external_parameters.fetch(:task_placement_constraints, [])
task_placement_constraints.each do |cntr|
object = {Type: "memberOf"}
object.merge!({ Expression: FnSub(cntr)})
task_constraints << object
end

iam_policies = external_parameters.fetch(:iam_policies, {})
service_discovery = external_parameters.fetch(:service_discovery, {})
unless iam_policies.empty?

policies = []
iam_policies.each do |name,policy|
policies << iam_policy_allow(name,policy['action'],policy['resource'] || '*')

unless service_discovery.empty?
iam_policies['ecs-service-discovery'] = {
'action' => %w(
servicediscovery:RegisterInstance
servicediscovery:DeregisterInstance
servicediscovery:DiscoverInstances
servicediscovery:Get*
servicediscovery:List*
route53:GetHostedZone
route53:ListHostedZonesByName
route53:ChangeResourceRecordSets
route53:CreateHealthCheck
route53:GetHealthCheck
route53:DeleteHealthCheck
route53:UpdateHealthCheck
)
}
end

IAM_Role('TaskRole') do
AssumeRolePolicyDocument ({
Statement: [
{
Effect: 'Allow',
Principal: { Service: [ 'ecs-tasks.amazonaws.com' ] },
Action: [ 'sts:AssumeRole' ]
},
{
Effect: 'Allow',
Principal: { Service: [ 'ssm.amazonaws.com' ] },
Action: [ 'sts:AssumeRole' ]
}
]
})
AssumeRolePolicyDocument service_assume_role_policy(['ecs-tasks','ssm'])
Path '/'
Policies(policies)
Policies(iam_role_policies(iam_policies))
end

IAM_Role('ExecutionRole') do
AssumeRolePolicyDocument service_role_assume_policy(['ecs-tasks', 'ssm'])
AssumeRolePolicyDocument service_assume_role_policy(['ecs-tasks','ssm'])
Path '/'
ManagedPolicyArns ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]

if secrets_policy.any?
Policies secrets_policy
Policies iam_role_policies(secrets_policy)
end

end
end

task_type = external_parameters.fetch(:task_type, 'EC2')
ECS_TaskDefinition('Task') do
ContainerDefinitions definitions
RequiresCompatibilities [task_type]
unless task_definition.empty?

if external_parameters[:cpu]
Cpu external_parameters[:cpu]
end
ECS_TaskDefinition('Task') do
ContainerDefinitions definitions
RequiresCompatibilities [task_type]

if external_parameters[:memory]
Memory external_parameters[:memory]
end
if external_parameters[:cpu]
Cpu external_parameters[:cpu]
end

if external_parameters[:network_mode]
NetworkMode external_parameters[:network_mode]
end
if external_parameters[:memory]
Memory external_parameters[:memory]
end

if task_volumes.any?
Volumes task_volumes
end
if external_parameters[:network_mode]
NetworkMode external_parameters[:network_mode]
end

if task_volumes.any?
Volumes task_volumes
end

unless iam_policies.empty?
TaskRoleArn Ref('TaskRole')
ExecutionRoleArn Ref('ExecutionRole')
end

Tags task_tags

unless iam_policies.empty?
TaskRoleArn Ref('TaskRole')
ExecutionRoleArn Ref('ExecutionRole')
end

Tags task_tags

Output("EcsTaskArn") {
Value(Ref('Task'))
Export FnSub("${EnvironmentName}-#{export}-EcsTaskArn")
}
end

Output("EcsTaskArn") {
Value(Ref('Task'))
Export FnSub("${EnvironmentName}-#{export}-EcsTaskArn")
}

end
26 changes: 26 additions & 0 deletions spec/default_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'yaml'

describe 'compiled component' do

context 'cftest' do
it 'compiles test' do
expect(system("cfhighlander cftest #{@validate} --tests tests/default.test.yaml")).to be_truthy
end
end

let(:template) { YAML.load_file("#{File.dirname(__FILE__)}/../out/tests/default/ecs-task.compiled.yaml") }

context 'Resources' do
it 'has No Task ' do
expect(template["Resources"]['Task']).to eq(nil)
end

it 'has a Log Group' do
expect(template["Resources"]['LogGroup']).to eq({
"Type"=>"AWS::Logs::LogGroup",
"Properties"=>{"LogGroupName"=>{"Ref"=>"AWS::StackName"}, "RetentionInDays"=>"7"}
})
end
end

end
Loading

0 comments on commit 46bc147

Please sign in to comment.