ecs_deploy_cli 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|