ecs_deploy_cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/ecs-deploy +5 -0
- data/lib/ecs-deploy-cli.rb +1 -0
- data/lib/ecs_deploy_cli.rb +32 -0
- data/lib/ecs_deploy_cli/cli.rb +58 -0
- data/lib/ecs_deploy_cli/dsl/auto_options.rb +18 -0
- data/lib/ecs_deploy_cli/dsl/container.rb +71 -0
- data/lib/ecs_deploy_cli/dsl/cron.rb +104 -0
- data/lib/ecs_deploy_cli/dsl/parser.rb +72 -0
- data/lib/ecs_deploy_cli/dsl/service.rb +30 -0
- data/lib/ecs_deploy_cli/dsl/task.rb +51 -0
- data/lib/ecs_deploy_cli/runner.rb +153 -0
- data/lib/ecs_deploy_cli/version.rb +3 -0
- data/spec/ecs_deploy_cli/cli_spec.rb +54 -0
- data/spec/ecs_deploy_cli/dsl/container_spec.rb +106 -0
- data/spec/ecs_deploy_cli/dsl/cron_spec.rb +61 -0
- data/spec/ecs_deploy_cli/dsl/parser_spec.rb +28 -0
- data/spec/ecs_deploy_cli/dsl/task_spec.rb +34 -0
- data/spec/ecs_deploy_cli/runner_spec.rb +75 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/ECSFile +80 -0
- data/spec/support/env_file.ext.yml +2 -0
- data/spec/support/env_file.yml +5 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e5f34210100053703e0137631635b625ea37cc1cb140e4fa432c3375c6cdf87a
|
4
|
+
data.tar.gz: '08d8ead32a4b87cddb19b36539d661fc954e006a8ac3986a8dc60aaac93cd560'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 90cb49d9d8de03d5b82946493d2df2466dec05cd01c190e9673ed6023adf9b5167465032bc47076ac12f7f8bfc451fd78c8568aec3aede6fd82467b7369914ad
|
7
|
+
data.tar.gz: f3dedd74c575d671fc65afb974086d8683c4eb3ee38074aec7d1dacb0b31632dd52813127bb39f1b3df95167353a8871308da8fbd885fd57cbb7f3ef34ca9941
|
data/bin/ecs-deploy
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ecs_deploy_cli'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'logger'
|
3
|
+
require 'thor'
|
4
|
+
require 'aws-sdk-ecs'
|
5
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
6
|
+
|
7
|
+
module EcsDeployCli
|
8
|
+
def self.logger
|
9
|
+
@logger ||= begin
|
10
|
+
logger = Logger.new(STDOUT)
|
11
|
+
logger.formatter = proc { |severity, datetime, progname, msg|
|
12
|
+
"#{msg}\n"
|
13
|
+
}
|
14
|
+
logger.level = Logger::INFO
|
15
|
+
logger
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.logger=(value)
|
20
|
+
@logger = value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'ecs_deploy_cli/version'
|
25
|
+
require 'ecs_deploy_cli/dsl/auto_options'
|
26
|
+
require 'ecs_deploy_cli/dsl/container'
|
27
|
+
require 'ecs_deploy_cli/dsl/task'
|
28
|
+
require 'ecs_deploy_cli/dsl/cron'
|
29
|
+
require 'ecs_deploy_cli/dsl/service'
|
30
|
+
require 'ecs_deploy_cli/dsl/parser'
|
31
|
+
require 'ecs_deploy_cli/runner'
|
32
|
+
require 'ecs_deploy_cli/cli'
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module EcsDeployCli
|
2
|
+
class CLI < Thor
|
3
|
+
desc 'validate', 'Validates your ECSFile'
|
4
|
+
option :file, default: 'ECSFile'
|
5
|
+
def validate
|
6
|
+
@parser = load(options[:file])
|
7
|
+
runner.validate!
|
8
|
+
puts 'Your ECSFile looks fine! 🎉'
|
9
|
+
end
|
10
|
+
|
11
|
+
desc 'version', 'Updates all services defined in your ECSFile'
|
12
|
+
def version
|
13
|
+
puts "ECS Deploy CLI Version #{EcsDeployCli::VERSION}."
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'deploy-scheduled-tasks', 'Updates all scheduled tasks defined in your ECSFile'
|
17
|
+
option :file, default: 'ECSFile'
|
18
|
+
def deploy_scheduled_tasks
|
19
|
+
@parser = load(options[:file])
|
20
|
+
runner.update_crons!
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'deploy-services', 'Updates all services defined in your ECSFile'
|
24
|
+
option :only
|
25
|
+
option :file, default: 'ECSFile'
|
26
|
+
option :timeout, type: :numeric, default: 500
|
27
|
+
def deploy_services
|
28
|
+
@parser = load(options[:file])
|
29
|
+
runner.update_services! timeout: options[:timeout], service: options[:only]
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'deploy', 'Updates a single service defined in your ECSFile'
|
33
|
+
option :file, default: 'ECSFile'
|
34
|
+
option :timeout, type: :numeric, default: 500
|
35
|
+
def deploy
|
36
|
+
@parser = load(options[:file])
|
37
|
+
runner.update_services! timeout: options[:timeout]
|
38
|
+
runner.update_crons!
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'ssh', 'Connects to ECS instance via SSH'
|
42
|
+
option :file, default: 'ECSFile'
|
43
|
+
def ssh
|
44
|
+
@parser = load(options[:file])
|
45
|
+
runner.ssh
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def load(file)
|
51
|
+
EcsDeployCli::DSL::Parser.load(file)
|
52
|
+
end
|
53
|
+
|
54
|
+
def runner
|
55
|
+
@runner ||= EcsDeployCli::Runner.new(@parser)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module EcsDeployCli
|
2
|
+
module DSL
|
3
|
+
module AutoOptions
|
4
|
+
def method_missing(name, *args, &block)
|
5
|
+
if args.count == 1 && !block
|
6
|
+
EcsDeployCli.logger.info("Auto-added option security_group #{name.to_sym} = #{args.first}")
|
7
|
+
_options[name.to_sym] = args.first
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def _options
|
14
|
+
@_options ||= {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EcsDeployCli
|
4
|
+
module DSL
|
5
|
+
class Container
|
6
|
+
include AutoOptions
|
7
|
+
|
8
|
+
def initialize(name, config)
|
9
|
+
@config = config
|
10
|
+
_options[:name] = name.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def image(value)
|
14
|
+
_options[:image] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def command(*command)
|
18
|
+
_options[:command] = command
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_envs(name)
|
22
|
+
_options[:environment] = (_options[:environment] || []) + YAML.safe_load(File.open(name))
|
23
|
+
end
|
24
|
+
|
25
|
+
def env(key:, value:)
|
26
|
+
(_options[:environment] ||= []) << { 'name' => key, 'value' => value }
|
27
|
+
end
|
28
|
+
|
29
|
+
def secret(key:, value:)
|
30
|
+
(_options[:secrets] ||= []) << { name: key, value_from: "arn:aws:ssm:#{@config[:aws_region]}:#{@config[:aws_profile_id]}:parameter/#{value}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
def expose(**options)
|
34
|
+
(_options[:port_mappings] ||= []) << options
|
35
|
+
end
|
36
|
+
|
37
|
+
def cpu(value)
|
38
|
+
_options[:cpu] = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def memory(limit:, reservation:)
|
42
|
+
_options[:memory] = limit
|
43
|
+
_options[:memory_reservation] = reservation
|
44
|
+
end
|
45
|
+
|
46
|
+
def merge(other)
|
47
|
+
other_options = other._options
|
48
|
+
other_options.delete(:name)
|
49
|
+
_options.merge!(other_options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def cloudwatch_logs(value)
|
53
|
+
_options[:log_configuration] = {
|
54
|
+
log_driver: 'awslogs',
|
55
|
+
options: {
|
56
|
+
'awslogs-group' => "/ecs/#{value}",
|
57
|
+
'awslogs-stream-prefix' => 'ecs',
|
58
|
+
'awslogs-region' => @config[:aws_region]
|
59
|
+
}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def as_definition
|
64
|
+
{
|
65
|
+
memory_reservation: nil,
|
66
|
+
essential: true
|
67
|
+
}.merge(_options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EcsDeployCli
|
4
|
+
module DSL
|
5
|
+
class Cron
|
6
|
+
include AutoOptions
|
7
|
+
|
8
|
+
def initialize(name, config)
|
9
|
+
_options[:name] = name
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def task(name, &block)
|
14
|
+
_options[:task] = Task.new(name.to_s, @config)
|
15
|
+
_options[:task].instance_exec(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_at(cron_expression)
|
19
|
+
@cron_expression = "cron(#{cron_expression})"
|
20
|
+
end
|
21
|
+
|
22
|
+
def run_every(interval)
|
23
|
+
@every = "rate(#{interval})"
|
24
|
+
end
|
25
|
+
|
26
|
+
def task_role(role)
|
27
|
+
_options[:task_role] = "arn:aws:iam::#{@config[:aws_profile_id]}:role/#{role}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def subnets(*value)
|
31
|
+
_options[:subnets] = value
|
32
|
+
end
|
33
|
+
|
34
|
+
def security_groups(*value)
|
35
|
+
_options[:security_groups] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
def launch_type(value)
|
39
|
+
_options[:launch_type] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def assign_public_ip(value)
|
43
|
+
_options[:assign_public_ip] = value
|
44
|
+
end
|
45
|
+
|
46
|
+
def as_definition(tasks)
|
47
|
+
raise 'Missing task definition' unless _options[:task]
|
48
|
+
|
49
|
+
input = { 'containerOverrides' => _options[:task].as_definition }
|
50
|
+
input['taskRoleArn'] = _options[:task_role] if _options[:task_role]
|
51
|
+
|
52
|
+
{
|
53
|
+
task_name: _options[:task].name,
|
54
|
+
rule: {
|
55
|
+
name: _options[:name],
|
56
|
+
schedule_expression: @cron_expression || @every || raise("Missing schedule expression.")
|
57
|
+
},
|
58
|
+
input: input,
|
59
|
+
ecs_parameters: {
|
60
|
+
# task_definition_arn: task_definition[:task_definition_arn],
|
61
|
+
task_count: _options[:task_count] || 1,
|
62
|
+
launch_type: _options[:launch_type] || raise('Missing parameter launch_type'),
|
63
|
+
network_configuration: {
|
64
|
+
awsvpc_configuration: {
|
65
|
+
subnets: _options[:subnets] || raise('Missing parameter subnets'),
|
66
|
+
security_groups: _options[:security_groups] || [],
|
67
|
+
assign_public_ip: _options[:assign_public_ip] ? 'ENABLED' : 'DISABLED'
|
68
|
+
}
|
69
|
+
},
|
70
|
+
platform_version: _options[:platform_version] || 'LATEST'
|
71
|
+
}
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
class Task
|
76
|
+
include AutoOptions
|
77
|
+
|
78
|
+
attr_reader :name
|
79
|
+
|
80
|
+
def initialize(name, config)
|
81
|
+
@name = name
|
82
|
+
@config = config
|
83
|
+
end
|
84
|
+
|
85
|
+
def container(name, &block)
|
86
|
+
container = Container.new(name, @config)
|
87
|
+
container.instance_exec(&block)
|
88
|
+
(_options[:containers] ||= []) << container
|
89
|
+
end
|
90
|
+
|
91
|
+
def as_definition
|
92
|
+
# [{"name"=>"cron", "command"=>["rails", "cron:adalytics"]}]
|
93
|
+
(_options[:containers] || []).map(&:as_definition)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Container < EcsDeployCli::DSL::Container
|
98
|
+
def as_definition
|
99
|
+
_options.to_h
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EcsDeployCli
|
4
|
+
module DSL
|
5
|
+
class Parser
|
6
|
+
def aws_profile_id(value)
|
7
|
+
config[:aws_profile_id] = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def aws_region(value)
|
11
|
+
config[:aws_region] = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def stage(stage)
|
15
|
+
config[:stage] = stage
|
16
|
+
end
|
17
|
+
|
18
|
+
def container(container, extends: nil, &block)
|
19
|
+
@containers ||= {}
|
20
|
+
@containers[container] = Container.new(container, config)
|
21
|
+
@containers[container].merge(@containers[extends]) if extends
|
22
|
+
@containers[container].instance_exec(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def task(task, &block)
|
26
|
+
@tasks ||= {}.with_indifferent_access
|
27
|
+
@tasks[task] = Task.new(task, config)
|
28
|
+
@tasks[task].instance_exec(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def service(name, &block)
|
32
|
+
@services ||= {}.with_indifferent_access
|
33
|
+
@services[name.to_s] = Service.new(name, config)
|
34
|
+
@services[name.to_s].instance_exec(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def cron(name, &block)
|
38
|
+
@crons ||= {}.with_indifferent_access
|
39
|
+
@crons[name] = Cron.new(name, config)
|
40
|
+
@crons[name].instance_exec(&block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def cluster(name)
|
44
|
+
config[:cluster] = name
|
45
|
+
end
|
46
|
+
|
47
|
+
def config
|
48
|
+
@config ||= {}
|
49
|
+
end
|
50
|
+
|
51
|
+
def ensure_required_params!
|
52
|
+
[
|
53
|
+
:aws_profile_id, :aws_region, :cluster
|
54
|
+
].each { |key| raise "Missing required parameter #{key}" unless config[key] }
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve
|
58
|
+
resolved_containers = @containers.transform_values(&:as_definition)
|
59
|
+
resolved_tasks = @tasks.transform_values { |t| t.as_definition(resolved_containers) }
|
60
|
+
resolved_crons = @crons.transform_values { |t| t.as_definition(resolved_tasks) }
|
61
|
+
[@services, resolved_tasks, resolved_crons]
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.load(file)
|
65
|
+
result = new
|
66
|
+
result.instance_eval(File.read(file))
|
67
|
+
result.ensure_required_params!
|
68
|
+
result
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EcsDeployCli
|
4
|
+
module DSL
|
5
|
+
class Service
|
6
|
+
include AutoOptions
|
7
|
+
|
8
|
+
def initialize(name, config)
|
9
|
+
_options[:service] = name
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def task(name)
|
14
|
+
_options[:task] = name
|
15
|
+
end
|
16
|
+
|
17
|
+
def options
|
18
|
+
_options
|
19
|
+
end
|
20
|
+
|
21
|
+
def as_definition(task)
|
22
|
+
{
|
23
|
+
cluster: @config[:cluster],
|
24
|
+
service: service_name,
|
25
|
+
task_definition: task
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EcsDeployCli
|
4
|
+
module DSL
|
5
|
+
class Task
|
6
|
+
include AutoOptions
|
7
|
+
|
8
|
+
def initialize(name, config)
|
9
|
+
@config = config
|
10
|
+
_options[:family] = name.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def containers(*containers)
|
14
|
+
@containers = containers
|
15
|
+
end
|
16
|
+
|
17
|
+
def cpu(value)
|
18
|
+
@cpu = value.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def memory(value)
|
22
|
+
@memory = value.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def tag(key, value)
|
26
|
+
(_options[:tags] ||= []) << { key: key, value: value }
|
27
|
+
end
|
28
|
+
|
29
|
+
def volume(value)
|
30
|
+
(_options[:volumes] ||= []) << value
|
31
|
+
end
|
32
|
+
|
33
|
+
def execution_role(name)
|
34
|
+
_options[:execution_role_arn] = "arn:aws:iam::#{@config[:aws_profile_id]}:role/#{name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def as_definition(containers)
|
38
|
+
{
|
39
|
+
container_definitions: containers.values_at(*@containers),
|
40
|
+
execution_role_arn: "arn:aws:iam::#{@config[:aws_profile_id]}:role/ecsTaskExecutionRole",
|
41
|
+
requires_compatibilities: ['EC2'],
|
42
|
+
placement_constraints: [],
|
43
|
+
cpu: @cpu,
|
44
|
+
memory: @memory,
|
45
|
+
volumes: [],
|
46
|
+
network_mode: nil
|
47
|
+
}.merge(_options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
module EcsDeployCli
|
2
|
+
class Runner
|
3
|
+
def initialize(parser)
|
4
|
+
@parser = parser
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate!
|
8
|
+
@parser.resolve
|
9
|
+
end
|
10
|
+
|
11
|
+
def update_crons!
|
12
|
+
_, tasks, crons = @parser.resolve
|
13
|
+
|
14
|
+
crons.each do |cron_name, cron_definition|
|
15
|
+
task_definition = tasks[cron_definition[:task_name]]
|
16
|
+
raise "Undefined task #{cron_definition[:task_name].inspect} in (#{tasks.keys.inspect})" unless task_definition
|
17
|
+
|
18
|
+
updated_task = _update_task(task_definition)
|
19
|
+
|
20
|
+
current_target = cwe_client.list_targets_by_rule(
|
21
|
+
{
|
22
|
+
rule: cron_name,
|
23
|
+
limit: 1
|
24
|
+
}
|
25
|
+
).to_h[:targets].first
|
26
|
+
|
27
|
+
cwe_client.put_rule(
|
28
|
+
cron_definition[:rule]
|
29
|
+
)
|
30
|
+
|
31
|
+
cwe_client.put_targets(
|
32
|
+
rule: cron_name,
|
33
|
+
targets: [
|
34
|
+
id: current_target[:id],
|
35
|
+
arn: current_target[:arn],
|
36
|
+
role_arn: current_target[:role_arn],
|
37
|
+
input: cron_definition[:input].to_json,
|
38
|
+
ecs_parameters: cron_definition[:ecs_parameters].merge(task_definition_arn: updated_task[:task_definition_arn])
|
39
|
+
]
|
40
|
+
)
|
41
|
+
EcsDeployCli.logger.info "Deployed scheduled task \"#{cron_name}\"!"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def ssh
|
46
|
+
instances = ecs_client.list_container_instances(
|
47
|
+
cluster: config[:cluster]
|
48
|
+
).to_h[:container_instance_arns]
|
49
|
+
|
50
|
+
response = ecs_client.describe_container_instances(
|
51
|
+
cluster: config[:cluster],
|
52
|
+
container_instances: instances
|
53
|
+
)
|
54
|
+
|
55
|
+
EcsDeployCli.logger.info "Found instances: #{response.container_instances.map(&:ec2_instance_id).join(', ')}"
|
56
|
+
|
57
|
+
response = ec2_client.describe_instances(
|
58
|
+
instance_ids: response.container_instances.map(&:ec2_instance_id)
|
59
|
+
)
|
60
|
+
|
61
|
+
dns_name = response.reservations[0].instances[0].public_dns_name
|
62
|
+
EcsDeployCli.logger.info "Connecting to ec2-user@#{dns_name}..."
|
63
|
+
|
64
|
+
Process.fork { exec("ssh ec2-user@#{dns_name}") }
|
65
|
+
Process.wait
|
66
|
+
end
|
67
|
+
|
68
|
+
def update_services!(service: nil, timeout: 500)
|
69
|
+
services, resolved_tasks = @parser.resolve
|
70
|
+
|
71
|
+
services.each do |service_name, service_definition|
|
72
|
+
next if !service.nil? && service != service_name
|
73
|
+
|
74
|
+
task_definition = _update_task resolved_tasks[service_definition.options[:task]]
|
75
|
+
task_name = "#{task_definition[:family]}:#{task_definition[:revision]}"
|
76
|
+
|
77
|
+
ecs_client.update_service(
|
78
|
+
cluster: config[:cluster],
|
79
|
+
service: service_name,
|
80
|
+
task_definition: "#{task_definition[:family]}:#{task_name}"
|
81
|
+
)
|
82
|
+
wait_for_deploy(service_name, task_name, timeout: timeout)
|
83
|
+
EcsDeployCli.logger.info "Deployed service \"#{service_name}\"!"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def wait_for_deploy(service_name, task_name, timeout:)
|
90
|
+
wait_data = { cluster: config[:cluster], services: [service_name] }
|
91
|
+
|
92
|
+
started_at = Time.now
|
93
|
+
ecs_client.wait_until(
|
94
|
+
:services_stable, wait_data,
|
95
|
+
max_attempts: nil,
|
96
|
+
before_wait: lambda { |_, response|
|
97
|
+
deployments = response.services.first.deployments
|
98
|
+
log_deployments task_name, deployments
|
99
|
+
|
100
|
+
throw :success if deployments.count == 1 && deployments[0].task_definition.end_with?(task_name)
|
101
|
+
throw :failure if Time.now - started_at > timeout
|
102
|
+
}
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
def _update_task(definition)
|
107
|
+
ecs_client.register_task_definition(
|
108
|
+
definition
|
109
|
+
).to_h[:task_definition]
|
110
|
+
end
|
111
|
+
|
112
|
+
def log_deployments(task_name, deployments)
|
113
|
+
EcsDeployCli.logger.info "Waiting for task: #{task_name} to become ok."
|
114
|
+
EcsDeployCli.logger.info 'Deployment status:'
|
115
|
+
deployments.each do |deploy|
|
116
|
+
EcsDeployCli.logger.info "[#{deploy.status}] task=#{deploy.task_definition.split('/').last}, "\
|
117
|
+
"desired_count=#{deploy.desired_count}, pending_count=#{deploy.pending_count}, running_count=#{deploy.running_count}, failed_tasks=#{deploy.failed_tasks}"
|
118
|
+
end
|
119
|
+
EcsDeployCli.logger.info ''
|
120
|
+
end
|
121
|
+
|
122
|
+
def ec2_client
|
123
|
+
@ec2_client ||= begin
|
124
|
+
require 'aws-sdk-ec2'
|
125
|
+
Aws::EC2::Client.new(
|
126
|
+
profile: ENV.fetch('AWS_PROFILE', 'default'),
|
127
|
+
region: config[:aws_region]
|
128
|
+
)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def ecs_client
|
133
|
+
@ecs_client ||= Aws::ECS::Client.new(
|
134
|
+
profile: ENV.fetch('AWS_PROFILE', 'default'),
|
135
|
+
region: config[:aws_region]
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def cwe_client
|
140
|
+
@cwe_client ||= begin
|
141
|
+
require 'aws-sdk-cloudwatchevents'
|
142
|
+
Aws::CloudWatchEvents::Client.new(
|
143
|
+
profile: ENV.fetch('AWS_PROFILE', 'default'),
|
144
|
+
region: config[:aws_region]
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def config
|
150
|
+
@parser.config
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EcsDeployCli::CLI do
|
4
|
+
context 'defines task data' do
|
5
|
+
let(:runner) { double }
|
6
|
+
|
7
|
+
around(:each) do |example|
|
8
|
+
ENV['AWS_PROFILE_ID'] = '123123123'
|
9
|
+
ENV['AWS_REGION'] = 'us-east-1'
|
10
|
+
example.run
|
11
|
+
ENV['AWS_PROFILE_ID'] = nil
|
12
|
+
ENV['AWS_REGION'] = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'runs help' do
|
16
|
+
expect { described_class.start(['help']) }.to output(/rspec deploy-scheduled-tasks/).to_stdout
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'runs version' do
|
20
|
+
expect { described_class.start(['version']) }.to output(/Version #{EcsDeployCli::VERSION}/).to_stdout
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'runs validate' do
|
24
|
+
expect { described_class.start(['validate', '--file', 'spec/support/ECSFile']) }.to output(/Your ECSFile looks fine! 🎉/).to_stdout
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'runs deploy' do
|
28
|
+
expect(runner).to receive(:update_crons!)
|
29
|
+
expect(runner).to receive(:update_services!).with(timeout: 500)
|
30
|
+
expect_any_instance_of(described_class).to receive(:runner).at_least(:once).and_return(runner)
|
31
|
+
expect { described_class.start(['deploy', '--file', 'spec/support/ECSFile']) }.to output(/[WARNING]/).to_stdout
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'runs deploy-services' do
|
35
|
+
expect(runner).to receive(:update_services!)
|
36
|
+
expect_any_instance_of(described_class).to receive(:runner).and_return(runner)
|
37
|
+
expect { described_class.start(['deploy-services', '--file', 'spec/support/ECSFile']) }.to output(/[WARNING]/).to_stdout
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'runs ssh' do
|
41
|
+
expect(runner).to receive(:ssh)
|
42
|
+
expect_any_instance_of(described_class).to receive(:runner).and_return(runner)
|
43
|
+
|
44
|
+
described_class.start(['ssh', '--file', 'spec/support/ECSFile'])
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'runs deploy-scheduled-tasks' do
|
48
|
+
expect(runner).to receive(:update_crons!)
|
49
|
+
expect_any_instance_of(described_class).to receive(:runner).and_return(runner)
|
50
|
+
|
51
|
+
described_class.start(['deploy-scheduled-tasks', '--file', 'spec/support/ECSFile'])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EcsDeployCli::DSL::Container do
|
4
|
+
context 'defines container data' do
|
5
|
+
subject { described_class.new('test', { aws_profile_id: '123123', aws_region: 'eu-central-1' }) }
|
6
|
+
|
7
|
+
it 'has the correct name' do
|
8
|
+
expect(subject.as_definition[:name]).to eq('test')
|
9
|
+
end
|
10
|
+
|
11
|
+
it '#memory configures memory' do
|
12
|
+
subject.memory limit: 1024, reservation: 900
|
13
|
+
expect(subject.as_definition[:memory]).to eq(1024)
|
14
|
+
expect(subject.as_definition[:memory_reservation]).to eq(900)
|
15
|
+
end
|
16
|
+
|
17
|
+
it '#env configures a single env' do
|
18
|
+
subject.env key: 'SOME', value: 'env'
|
19
|
+
subject.env key: 'SOME2', value: 'env2'
|
20
|
+
expect(subject.as_definition[:environment]).to eq(
|
21
|
+
[
|
22
|
+
{
|
23
|
+
'name' => 'SOME', 'value' => 'env'
|
24
|
+
},
|
25
|
+
{
|
26
|
+
'name' => 'SOME2', 'value' => 'env2'
|
27
|
+
}
|
28
|
+
]
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
it '#cloudwatch_logs configures cloudwatch logs' do
|
33
|
+
subject.cloudwatch_logs 'yourproject'
|
34
|
+
expect(subject.as_definition[:log_configuration]).to eq(
|
35
|
+
{
|
36
|
+
log_driver: 'awslogs',
|
37
|
+
options: { 'awslogs-group' => '/ecs/yourproject', 'awslogs-region' => 'eu-central-1', 'awslogs-stream-prefix' => 'ecs' }
|
38
|
+
}
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
it '#secret configures secrets' do
|
43
|
+
subject.secret key: 'RAILS_MASTER_KEY', value: 'railsMasterKey'
|
44
|
+
|
45
|
+
expect(subject.as_definition[:secrets]).to eq(
|
46
|
+
[
|
47
|
+
{
|
48
|
+
name: 'RAILS_MASTER_KEY',
|
49
|
+
value_from: 'arn:aws:ssm:eu-central-1:123123:parameter/railsMasterKey'
|
50
|
+
}
|
51
|
+
]
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
it '#merge: merges two containers' do
|
56
|
+
other = described_class.new('base', { aws_profile_id: '123123', aws_region: 'eu-central-1' })
|
57
|
+
other.expose host_port: 0, protocol: 'tcp', container_port: 3000
|
58
|
+
|
59
|
+
subject.secret key: 'RAILS_MASTER_KEY', value: 'railsMasterKey'
|
60
|
+
subject.merge(other)
|
61
|
+
|
62
|
+
expect(subject.as_definition[:secrets]).to eq(
|
63
|
+
[
|
64
|
+
{
|
65
|
+
name: 'RAILS_MASTER_KEY',
|
66
|
+
value_from: 'arn:aws:ssm:eu-central-1:123123:parameter/railsMasterKey'
|
67
|
+
}
|
68
|
+
]
|
69
|
+
)
|
70
|
+
|
71
|
+
expect(subject.as_definition[:port_mappings]).to eq(
|
72
|
+
[
|
73
|
+
{ host_port: 0, protocol: 'tcp', container_port: 3000 }
|
74
|
+
]
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
it '#expose: configures port mapping' do
|
79
|
+
subject.expose host_port: 0, protocol: 'tcp', container_port: 3000
|
80
|
+
expect(subject.as_definition[:port_mappings]).to eq(
|
81
|
+
[
|
82
|
+
{ host_port: 0, protocol: 'tcp', container_port: 3000 }
|
83
|
+
]
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
it '#load_envs loads env files' do
|
88
|
+
subject.load_envs 'spec/support/env_file.yml'
|
89
|
+
expect(subject.as_definition[:environment]).to eq(
|
90
|
+
[
|
91
|
+
{
|
92
|
+
'name' => 'RAILS_ENV', 'value' => 'production'
|
93
|
+
},
|
94
|
+
{
|
95
|
+
'name' => 'API_KEY', 'value' => '123123123'
|
96
|
+
}
|
97
|
+
]
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'fallbacks not handled methods to an option in the container definition' do
|
102
|
+
subject.image 'some_image:version'
|
103
|
+
expect(subject.as_definition[:image]).to eq('some_image:version')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EcsDeployCli::DSL::Cron do
|
4
|
+
context 'defines cron data' do
|
5
|
+
subject { described_class.new('test', { aws_profile_id: '123123', aws_region: 'eu-central-1' }) }
|
6
|
+
|
7
|
+
let(:container) do
|
8
|
+
EcsDeployCli::DSL::Container.new('web', { aws_profile_id: '123123', aws_region: 'eu-central-1' }).as_definition
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:task) do
|
12
|
+
task = EcsDeployCli::DSL::Task.new('some', { aws_profile_id: '123123', aws_region: 'eu-central-1' })
|
13
|
+
task.containers :web
|
14
|
+
|
15
|
+
task.as_definition({ web: container })
|
16
|
+
end
|
17
|
+
|
18
|
+
it '#task' do
|
19
|
+
subject.task :some do
|
20
|
+
container :web do
|
21
|
+
command 'rails', 'run:task'
|
22
|
+
memory limit: 2048, reservation: 1024
|
23
|
+
end
|
24
|
+
end
|
25
|
+
subject.subnets 'subnet-1298ca5f'
|
26
|
+
subject.security_groups 'sg-1298ca5f'
|
27
|
+
subject.launch_type 'FARGATE'
|
28
|
+
subject.task_role 'ecsEventsRole'
|
29
|
+
subject.run_every '2 hours'
|
30
|
+
subject.assign_public_ip true
|
31
|
+
|
32
|
+
expect(subject.as_definition({ 'some' => task })).to eq(
|
33
|
+
{
|
34
|
+
task_name: 'some',
|
35
|
+
input: {
|
36
|
+
'containerOverrides' => [
|
37
|
+
{ command: ['rails', 'run:task'], memory: 2048, memory_reservation: 1024, name: 'web' }
|
38
|
+
],
|
39
|
+
'taskRoleArn' => 'arn:aws:iam::123123:role/ecsEventsRole'
|
40
|
+
},
|
41
|
+
rule: {
|
42
|
+
name: 'test',
|
43
|
+
schedule_expression: 'rate(2 hours)'
|
44
|
+
},
|
45
|
+
ecs_parameters: {
|
46
|
+
task_count: 1,
|
47
|
+
launch_type: 'FARGATE',
|
48
|
+
network_configuration: {
|
49
|
+
awsvpc_configuration: {
|
50
|
+
subnets: ['subnet-1298ca5f'],
|
51
|
+
assign_public_ip: 'ENABLED',
|
52
|
+
security_groups: ['sg-1298ca5f']
|
53
|
+
}
|
54
|
+
},
|
55
|
+
platform_version: 'LATEST'
|
56
|
+
}
|
57
|
+
}
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EcsDeployCli::DSL::Parser do
|
4
|
+
context 'defines task data' do
|
5
|
+
subject { described_class.load('spec/support/ECSFile') }
|
6
|
+
|
7
|
+
# TODO: More tests
|
8
|
+
it 'validates required data in a ECSFile' do
|
9
|
+
expect { subject.resolve }.to raise_error('Missing required parameter aws_profile_id')
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'with all required data available' do
|
13
|
+
around(:each) do |example|
|
14
|
+
ENV['AWS_PROFILE_ID'] = '123123123'
|
15
|
+
ENV['AWS_REGION'] = 'us-east-1'
|
16
|
+
example.run
|
17
|
+
ENV['AWS_PROFILE_ID'] = nil
|
18
|
+
ENV['AWS_REGION'] = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'imports the ECSFile' do
|
22
|
+
services, tasks = subject.resolve
|
23
|
+
expect(services).to include('yourproject-service')
|
24
|
+
expect(tasks).to include(:yourproject)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EcsDeployCli::DSL::Task do
|
4
|
+
context 'defines task data' do
|
5
|
+
subject { described_class.new('test', { aws_profile_id: '123123', aws_region: 'eu-central-1' }) }
|
6
|
+
|
7
|
+
it 'has family name equals test' do
|
8
|
+
expect(subject.as_definition({})[:family]).to eq('test')
|
9
|
+
end
|
10
|
+
|
11
|
+
it '#tag adds a tag' do
|
12
|
+
subject.tag 'product', 'yourproject'
|
13
|
+
subject.tag 'product2', 'yourproject2'
|
14
|
+
expect(subject.as_definition({})[:tags]).to eq(
|
15
|
+
[
|
16
|
+
{ key: 'product', value: 'yourproject' },
|
17
|
+
{ key: 'product2', value: 'yourproject2' }
|
18
|
+
]
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
it '#execution_role' do
|
23
|
+
subject.execution_role 'someRole'
|
24
|
+
expect(subject.as_definition({})[:execution_role_arn]).to eq(
|
25
|
+
'arn:aws:iam::123123:role/someRole'
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'fallbacks not handled methods to an option in the container definition' do
|
30
|
+
subject.cpu 256
|
31
|
+
expect(subject.as_definition({})[:cpu]).to eq('256')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'aws-sdk-cloudwatchevents'
|
3
|
+
require 'aws-sdk-ec2'
|
4
|
+
|
5
|
+
describe EcsDeployCli::Runner do
|
6
|
+
context 'defines task data' do
|
7
|
+
let(:parser) { EcsDeployCli::DSL::Parser.load('spec/support/ECSFile') }
|
8
|
+
subject { described_class.new(parser) }
|
9
|
+
let(:mock_ecs_client) { Aws::ECS::Client.new(stub_responses: true) }
|
10
|
+
let(:mock_ec2_client) { Aws::EC2::Client.new(stub_responses: true) }
|
11
|
+
let(:mock_cwe_client) do
|
12
|
+
Aws::CloudWatchEvents::Client.new(stub_responses: true)
|
13
|
+
end
|
14
|
+
|
15
|
+
it '#validate!' do
|
16
|
+
expect { subject.validate! }.to raise_error('Missing required parameter aws_profile_id')
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with envs set' do
|
20
|
+
around(:each) do |example|
|
21
|
+
ENV['AWS_PROFILE_ID'] = '123123123'
|
22
|
+
ENV['AWS_REGION'] = 'us-east-1'
|
23
|
+
example.run
|
24
|
+
ENV['AWS_PROFILE_ID'] = nil
|
25
|
+
ENV['AWS_REGION'] = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it '#ssh' do
|
29
|
+
expect(mock_ecs_client).to receive(:list_container_instances).and_return({ container_instance_arns: ['arn:123123'] })
|
30
|
+
expect(mock_ecs_client).to receive(:describe_container_instances).and_return(double(container_instances: [double(ec2_instance_id: 'i-123123')]))
|
31
|
+
|
32
|
+
expect(mock_ec2_client).to receive(:describe_instances)
|
33
|
+
.with(instance_ids: ['i-123123'])
|
34
|
+
.and_return(
|
35
|
+
double(reservations: [
|
36
|
+
double(instances: [double(public_dns_name: 'test.com')])
|
37
|
+
]
|
38
|
+
)
|
39
|
+
)
|
40
|
+
|
41
|
+
expect(Process).to receive(:fork) do |&block|
|
42
|
+
block.call
|
43
|
+
end
|
44
|
+
expect(Process).to receive(:wait)
|
45
|
+
|
46
|
+
expect(subject).to receive(:exec).with('ssh ec2-user@test.com')
|
47
|
+
expect(subject).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
|
48
|
+
expect(subject).to receive(:ec2_client).at_least(:once).and_return(mock_ec2_client)
|
49
|
+
|
50
|
+
subject.ssh
|
51
|
+
end
|
52
|
+
|
53
|
+
it '#update_crons!' do
|
54
|
+
mock_ecs_client.stub_responses(:register_task_definition, { task_definition: { family: 'some', revision: 1, task_definition_arn: 'arn:task:eu-central-1:xxxx' } })
|
55
|
+
|
56
|
+
mock_cwe_client.stub_responses(:list_targets_by_rule, { targets: [{ id: '123', arn: 'arn:123' }] })
|
57
|
+
|
58
|
+
expect(subject).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
|
59
|
+
expect(subject).to receive(:cwe_client).at_least(:once).and_return(mock_cwe_client)
|
60
|
+
|
61
|
+
subject.update_crons!
|
62
|
+
end
|
63
|
+
|
64
|
+
it '#update_services!' do
|
65
|
+
expect(mock_ecs_client).to receive(:register_task_definition).at_least(:once).and_return({ task_definition: { family: 'some', revision: '1' } })
|
66
|
+
expect(mock_ecs_client).to receive(:update_service)
|
67
|
+
expect(mock_ecs_client).to receive(:wait_until)
|
68
|
+
|
69
|
+
expect(subject).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
|
70
|
+
|
71
|
+
subject.update_services!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'rspec'
|
3
|
+
require 'ecs_deploy_cli'
|
4
|
+
# require 'kaminari-activerecord'
|
5
|
+
|
6
|
+
I18n.enforce_available_locales = false
|
7
|
+
RSpec::Expectations.configuration.warn_about_potential_false_positives = false
|
8
|
+
|
9
|
+
Dir[File.expand_path('../support/*.rb', __FILE__)].each { |f| require f }
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
aws_region ENV.fetch('AWS_REGION', 'eu-central-1')
|
2
|
+
|
3
|
+
# Used to create ARNs
|
4
|
+
aws_profile_id ENV['AWS_PROFILE_ID']
|
5
|
+
|
6
|
+
# Defining the cluster name
|
7
|
+
cluster 'yourproject-cluster'
|
8
|
+
|
9
|
+
# This is used as a template for the next two containers, it will not be used inside a task
|
10
|
+
container :base_container do
|
11
|
+
image "#{ENV['REPO_URL']}:#{ENV['CURRENT_VERSION']}"
|
12
|
+
load_envs 'spec/support/env_file.yml'
|
13
|
+
load_envs 'spec/support/env_file.ext.yml'
|
14
|
+
secret key: 'RAILS_MASTER_KEY', value: 'railsMasterKey' # Taking the secret from AWS System Manager with name "arn:aws:ssm:__AWS_REGION__:__AWS_PROFILE_ID__:parameter/railsMasterKey"
|
15
|
+
working_directory '/app'
|
16
|
+
cloudwatch_logs 'yourproject' # Configuring cloudwatch logs
|
17
|
+
end
|
18
|
+
|
19
|
+
# The rails web application
|
20
|
+
container :web, extends: :base_container do
|
21
|
+
cpu 512
|
22
|
+
memory limit: 3584, reservation: 3584
|
23
|
+
command 'bundle', 'exec', 'puma', '-C', 'config/puma.rb'
|
24
|
+
|
25
|
+
expose host_port: 0, protocol: 'tcp', container_port: 3000
|
26
|
+
end
|
27
|
+
|
28
|
+
# The rails job worker
|
29
|
+
container :worker, extends: :base_container do
|
30
|
+
cpu 1536
|
31
|
+
memory limit: 3584, reservation: 3584
|
32
|
+
command 'bundle', 'exec', 'shoryuken', '-C', 'config/shoryuken.yml', '-R'
|
33
|
+
end
|
34
|
+
|
35
|
+
# A container to exec cron jobs
|
36
|
+
container :cron, extends: :base_container do
|
37
|
+
command 'rails', 'runner'
|
38
|
+
end
|
39
|
+
|
40
|
+
# The main task, having two containers
|
41
|
+
task :yourproject do
|
42
|
+
containers :web, :worker
|
43
|
+
cpu 2048
|
44
|
+
memory 3584
|
45
|
+
|
46
|
+
tag 'product', 'yourproject'
|
47
|
+
end
|
48
|
+
|
49
|
+
# The main service
|
50
|
+
service :'yourproject-service' do
|
51
|
+
task :yourproject
|
52
|
+
end
|
53
|
+
|
54
|
+
# A task for cron jobs
|
55
|
+
task :'yourproject-cron' do
|
56
|
+
containers :cron
|
57
|
+
cpu 256
|
58
|
+
memory 1024
|
59
|
+
execution_role 'ecsTaskExecutionRole'
|
60
|
+
network_mode 'awsvpc'
|
61
|
+
requires_compatibilities ['FARGATE']
|
62
|
+
|
63
|
+
tag 'product', 'yourproject'
|
64
|
+
end
|
65
|
+
|
66
|
+
# Scheduled tasks using Cloudwatch Events / Eventbridge
|
67
|
+
cron :scheduled_emails do
|
68
|
+
task :'yourproject-cron' do
|
69
|
+
# Overrides
|
70
|
+
container :cron do
|
71
|
+
command 'rails', 'cron:exec'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
subnets 'subnet-123123'
|
75
|
+
launch_type 'FARGATE'
|
76
|
+
task_role 'ecsEventsRole'
|
77
|
+
# Examples:
|
78
|
+
# run_every '2 hours'
|
79
|
+
run_at '0 * * * ? *'
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ecs_deploy_cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mònade
|
8
|
+
- ProGM
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-03-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '5'
|
21
|
+
- - "<"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '7'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '5'
|
31
|
+
- - "<"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '7'
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: aws-sdk-cloudwatchevents
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
type: :runtime
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: aws-sdk-ec2
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: aws-sdk-ecs
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1'
|
69
|
+
type: :runtime
|
70
|
+
prerelease: false
|
71
|
+
version_requirements: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1'
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: thor
|
78
|
+
requirement: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.1'
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.1'
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: rspec
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3'
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3'
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: rubocop
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.93'
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.93'
|
118
|
+
description: Declare your cluster structure in a ECSFile and use the CLI to run deploys
|
119
|
+
and monitor its status.
|
120
|
+
email: team@monade.io
|
121
|
+
executables:
|
122
|
+
- ecs-deploy
|
123
|
+
extensions: []
|
124
|
+
extra_rdoc_files: []
|
125
|
+
files:
|
126
|
+
- bin/ecs-deploy
|
127
|
+
- lib/ecs-deploy-cli.rb
|
128
|
+
- lib/ecs_deploy_cli.rb
|
129
|
+
- lib/ecs_deploy_cli/cli.rb
|
130
|
+
- lib/ecs_deploy_cli/dsl/auto_options.rb
|
131
|
+
- lib/ecs_deploy_cli/dsl/container.rb
|
132
|
+
- lib/ecs_deploy_cli/dsl/cron.rb
|
133
|
+
- lib/ecs_deploy_cli/dsl/parser.rb
|
134
|
+
- lib/ecs_deploy_cli/dsl/service.rb
|
135
|
+
- lib/ecs_deploy_cli/dsl/task.rb
|
136
|
+
- lib/ecs_deploy_cli/runner.rb
|
137
|
+
- lib/ecs_deploy_cli/version.rb
|
138
|
+
- spec/ecs_deploy_cli/cli_spec.rb
|
139
|
+
- spec/ecs_deploy_cli/dsl/container_spec.rb
|
140
|
+
- spec/ecs_deploy_cli/dsl/cron_spec.rb
|
141
|
+
- spec/ecs_deploy_cli/dsl/parser_spec.rb
|
142
|
+
- spec/ecs_deploy_cli/dsl/task_spec.rb
|
143
|
+
- spec/ecs_deploy_cli/runner_spec.rb
|
144
|
+
- spec/spec_helper.rb
|
145
|
+
- spec/support/ECSFile
|
146
|
+
- spec/support/env_file.ext.yml
|
147
|
+
- spec/support/env_file.yml
|
148
|
+
homepage: https://rubygems.org/gems/ecs_deploy_cli
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: 2.5.0
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubygems_version: 3.2.7
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: A command line interface to make ECS deployments easier
|
171
|
+
test_files:
|
172
|
+
- spec/spec_helper.rb
|
173
|
+
- spec/support/ECSFile
|
174
|
+
- spec/support/env_file.yml
|
175
|
+
- spec/support/env_file.ext.yml
|
176
|
+
- spec/ecs_deploy_cli/runner_spec.rb
|
177
|
+
- spec/ecs_deploy_cli/cli_spec.rb
|
178
|
+
- spec/ecs_deploy_cli/dsl/task_spec.rb
|
179
|
+
- spec/ecs_deploy_cli/dsl/parser_spec.rb
|
180
|
+
- spec/ecs_deploy_cli/dsl/cron_spec.rb
|
181
|
+
- spec/ecs_deploy_cli/dsl/container_spec.rb
|