ecs_deploy_cli 0.4.0 → 0.5.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 +4 -4
- data/lib/ecs_deploy_cli/runners/base.rb +10 -0
- data/lib/ecs_deploy_cli/runners/setup.rb +40 -6
- data/lib/ecs_deploy_cli/runners/update_crons.rb +16 -7
- data/lib/ecs_deploy_cli/version.rb +1 -1
- data/spec/ecs_deploy_cli/runner_spec.rb +100 -27
- metadata +26 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0be6eaeb2fd7c5c088b90f3b37983fa859d281620bbcc8c887159a22e8aaa1b8
|
4
|
+
data.tar.gz: 9b59b8669defa19eec6af2946c44b2ff0dff4c2a5b01497685923e09b5779f24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 103a4642983b04c15ed353be27367a317f7de14593676080ee493328756d17549a8717c647cabcef7739ce6000fc09649000f5ce20c7c6e6a0c1cc8857546c0c
|
7
|
+
data.tar.gz: 133202293bc169a56890081c13a8cf866ce77ac3c2c2a284a21b68f4010a00bd478d3eb9b636182ebc490bf210c4cf680dbe83205207d8847fd594b427af1733
|
@@ -97,6 +97,16 @@ module EcsDeployCli
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
+
def iam_client
|
101
|
+
@iam_client ||= begin
|
102
|
+
require 'aws-sdk-iam'
|
103
|
+
Aws::IAM::Client.new(
|
104
|
+
profile: ENV.fetch('AWS_PROFILE', 'default'),
|
105
|
+
region: config[:aws_region]
|
106
|
+
)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
100
110
|
def config
|
101
111
|
@parser.config
|
102
112
|
end
|
@@ -3,33 +3,43 @@
|
|
3
3
|
module EcsDeployCli
|
4
4
|
module Runners
|
5
5
|
class Setup < Base
|
6
|
+
REQUIRED_ECS_ROLES = {
|
7
|
+
'ecsInstanceRole' => 'https://docs.aws.amazon.com/batch/latest/userguide/instance_IAM_role.html',
|
8
|
+
'ecsTaskExecutionRole' => 'https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html'
|
9
|
+
}.freeze
|
10
|
+
class SetupError < StandardError; end
|
11
|
+
|
6
12
|
def run!
|
7
13
|
services, resolved_tasks, _, cluster_options = @parser.resolve
|
8
14
|
|
15
|
+
ensure_ecs_roles_exists!
|
16
|
+
|
9
17
|
setup_cluster! cluster_options
|
10
18
|
setup_services! services, resolved_tasks: resolved_tasks
|
19
|
+
rescue SetupError => e
|
20
|
+
EcsDeployCli.logger.info e.message
|
11
21
|
end
|
12
22
|
|
13
23
|
private
|
14
24
|
|
15
25
|
def setup_cluster!(cluster_options)
|
16
|
-
|
17
|
-
if clusters.length == 1
|
26
|
+
if cluster_exists?
|
18
27
|
EcsDeployCli.logger.info 'Cluster already created, skipping.'
|
19
28
|
return
|
20
29
|
end
|
21
30
|
|
22
31
|
EcsDeployCli.logger.info "Creating cluster #{config[:cluster]}..."
|
23
32
|
|
33
|
+
create_keypair_if_required! cluster_options
|
24
34
|
params = create_params(cluster_options)
|
25
35
|
|
26
36
|
ecs_client.create_cluster(
|
27
37
|
cluster_name: config[:cluster]
|
28
38
|
)
|
39
|
+
EcsDeployCli.logger.info 'Cluster created, now running cloudformation...'
|
29
40
|
|
30
41
|
stack_name = "EC2ContainerService-#{config[:cluster]}"
|
31
42
|
|
32
|
-
|
33
43
|
cf_client.create_stack(
|
34
44
|
stack_name: stack_name,
|
35
45
|
template_body: File.read(File.join(__dir__, '..', 'cloudformation', 'default.yml')),
|
@@ -38,12 +48,13 @@ module EcsDeployCli
|
|
38
48
|
)
|
39
49
|
|
40
50
|
cf_client.wait_until(:stack_create_complete, { stack_name: stack_name }, delay: 30, max_attempts: 120)
|
41
|
-
EcsDeployCli.logger.info "Cluster #{config[:cluster]} created!"
|
51
|
+
EcsDeployCli.logger.info "Cluster #{config[:cluster]} created! 🎉"
|
42
52
|
end
|
43
53
|
|
44
54
|
def setup_services!(services, resolved_tasks:)
|
45
55
|
services.each do |service_name, service_definition|
|
46
|
-
|
56
|
+
existing_services = ecs_client.describe_services(cluster: config[:cluster], services: [service_name]).to_h[:services].filter { |s| s[:status] != 'INACTIVE' }
|
57
|
+
if existing_services.any?
|
47
58
|
EcsDeployCli.logger.info "Service #{service_name} already created, skipping."
|
48
59
|
next
|
49
60
|
end
|
@@ -54,7 +65,7 @@ module EcsDeployCli
|
|
54
65
|
|
55
66
|
ecs_client.create_service(
|
56
67
|
cluster: config[:cluster],
|
57
|
-
desired_count: 1,
|
68
|
+
desired_count: 1, # FIXME: this should be a parameter
|
58
69
|
load_balancers: service_definition.as_definition(task_definition)[:load_balancers],
|
59
70
|
service_name: service_name,
|
60
71
|
task_definition: task_name
|
@@ -110,6 +121,29 @@ module EcsDeployCli
|
|
110
121
|
}
|
111
122
|
end
|
112
123
|
|
124
|
+
def cluster_exists?
|
125
|
+
clusters = ecs_client.describe_clusters(clusters: [config[:cluster]]).to_h[:clusters]
|
126
|
+
|
127
|
+
clusters.filter { |c| c[:status] != 'INACTIVE' }.length == 1
|
128
|
+
end
|
129
|
+
|
130
|
+
def ensure_ecs_roles_exists!
|
131
|
+
REQUIRED_ECS_ROLES.each do |role_name, link|
|
132
|
+
iam_client.get_role(role_name: role_name).to_h
|
133
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
134
|
+
raise SetupError, "IAM Role #{role_name} does not exist. Please create it: #{link}."
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def create_keypair_if_required!(cluster_options)
|
139
|
+
ec2_client.describe_key_pairs(key_names: [cluster_options[:keypair_name]]).to_h[:key_pairs]
|
140
|
+
rescue Aws::EC2::Errors::InvalidKeyPairNotFound
|
141
|
+
EcsDeployCli.logger.info "Keypair \"#{cluster_options[:keypair_name]}\" not found, creating it..."
|
142
|
+
key_material = ec2_client.create_key_pair(key_name: cluster_options[:keypair_name]).to_h[:key_material]
|
143
|
+
File.write("#{cluster_options[:keypair_name]}.pem", key_material)
|
144
|
+
EcsDeployCli.logger.info "Created PEM file at #{Dir.pwd}/#{cluster_options[:keypair_name]}.pem"
|
145
|
+
end
|
146
|
+
|
113
147
|
def format_cloudformation_params(params)
|
114
148
|
params.map { |k, v| { parameter_key: k, parameter_value: v.to_s } }
|
115
149
|
end
|
@@ -8,16 +8,13 @@ module EcsDeployCli
|
|
8
8
|
|
9
9
|
crons.each do |cron_name, cron_definition|
|
10
10
|
task_definition = tasks[cron_definition[:task_name]]
|
11
|
-
|
11
|
+
unless task_definition
|
12
|
+
raise "Undefined task #{cron_definition[:task_name].inspect} in (#{tasks.keys.inspect})"
|
13
|
+
end
|
12
14
|
|
13
15
|
updated_task = _update_task(task_definition)
|
14
16
|
|
15
|
-
current_target =
|
16
|
-
{
|
17
|
-
rule: cron_name,
|
18
|
-
limit: 1
|
19
|
-
}
|
20
|
-
).to_h[:targets].first
|
17
|
+
current_target = load_or_init_target(cron_name)
|
21
18
|
|
22
19
|
cwe_client.put_rule(
|
23
20
|
cron_definition[:rule]
|
@@ -36,6 +33,18 @@ module EcsDeployCli
|
|
36
33
|
EcsDeployCli.logger.info "Deployed scheduled task \"#{cron_name}\"!"
|
37
34
|
end
|
38
35
|
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def load_or_init_target(cron_name)
|
40
|
+
cwe_client.list_targets_by_rule({ rule: cron_name, limit: 1 }).to_h[:targets].first
|
41
|
+
rescue Aws::CloudWatchEvents::Errors::ResourceNotFoundException
|
42
|
+
{
|
43
|
+
id: cron_name,
|
44
|
+
arn: "arn:aws:ecs:#{config[:aws_region]}:#{config[:aws_profile_id]}:cluster/#{config[:cluster]}",
|
45
|
+
role_arn: "arn:aws:iam::#{config[:aws_profile_id]}:role/ecsEventsRole"
|
46
|
+
}
|
47
|
+
end
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
@@ -6,11 +6,13 @@ require 'aws-sdk-cloudwatchlogs'
|
|
6
6
|
require 'aws-sdk-ec2'
|
7
7
|
require 'aws-sdk-ssm'
|
8
8
|
require 'aws-sdk-cloudformation'
|
9
|
+
require 'aws-sdk-iam'
|
9
10
|
|
10
11
|
describe EcsDeployCli::Runner do
|
11
12
|
context 'defines task data' do
|
12
13
|
let(:parser) { EcsDeployCli::DSL::Parser.load('spec/support/ECSFile') }
|
13
14
|
subject { described_class.new(parser) }
|
15
|
+
let(:mock_iam_client) { Aws::IAM::Client.new(stub_responses: true) }
|
14
16
|
let(:mock_cf_client) { Aws::CloudFormation::Client.new(stub_responses: true) }
|
15
17
|
let(:mock_ssm_client) { Aws::SSM::Client.new(stub_responses: true) }
|
16
18
|
let(:mock_ecs_client) { Aws::ECS::Client.new(stub_responses: true) }
|
@@ -90,24 +92,77 @@ describe EcsDeployCli::Runner do
|
|
90
92
|
ENV['AWS_REGION'] = nil
|
91
93
|
end
|
92
94
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
95
|
+
context '#setup!' do
|
96
|
+
before do
|
97
|
+
mock_ssm_client.stub_responses(
|
98
|
+
:get_parameter, {
|
99
|
+
parameter: {
|
100
|
+
name: '/aws/service/ecs/optimized-ami/amazon-linux-2/recommended',
|
101
|
+
type: 'String',
|
102
|
+
value: '{"schema_version":1,"image_name":"amzn2-ami-ecs-hvm-2.0.20210331-x86_64-ebs","image_id":"ami-03bbf53329af34379","os":"Amazon Linux 2","ecs_runtime_version":"Docker version 19.03.13-ce","ecs_agent_version":"1.51.0"}'
|
103
|
+
}
|
104
|
+
}
|
105
|
+
)
|
106
|
+
end
|
101
107
|
|
102
|
-
|
103
|
-
|
108
|
+
it 'setups the cluster correctly' do
|
109
|
+
expect(mock_ec2_client).to receive(:describe_key_pairs).and_return(key_pairs: [{ key_id: 'some' }])
|
104
110
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
111
|
+
expect(mock_iam_client).to receive(:get_role).at_least(:once).and_return({ role: { arn: 'some' } })
|
112
|
+
expect(mock_cf_client).to receive(:wait_until)
|
113
|
+
expect(mock_ecs_client).to receive(:create_service)
|
114
|
+
|
115
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ec2_client).at_least(:once).and_return(mock_ec2_client)
|
116
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:iam_client).at_least(:once).and_return(mock_iam_client)
|
117
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cwl_client).at_least(:once).and_return(mock_cwl_client)
|
118
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
|
119
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ssm_client).at_least(:once).and_return(mock_ssm_client)
|
120
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cf_client).at_least(:once).and_return(mock_cf_client)
|
121
|
+
|
122
|
+
subject.setup!
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'fails if the IAM role is not setup' do
|
126
|
+
expect(EcsDeployCli.logger).to receive(:info).at_least(:once) do |message|
|
127
|
+
puts message
|
128
|
+
end
|
129
|
+
|
130
|
+
expect(mock_iam_client).to receive(:get_role).at_least(:once) do
|
131
|
+
raise Aws::IAM::Errors::NoSuchEntity.new(nil, 'some')
|
132
|
+
end
|
133
|
+
|
134
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:iam_client).at_least(:once).and_return(mock_iam_client)
|
135
|
+
|
136
|
+
expect { subject.setup! }.to output(/IAM Role ecsInstanceRole does not exist./).to_stdout
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'fails if the cluster is already there' do
|
140
|
+
expect(mock_ecs_client).to receive(:describe_clusters).and_return(clusters: [{}])
|
141
|
+
|
142
|
+
expect(EcsDeployCli.logger).to receive(:info).at_least(:once) do |message|
|
143
|
+
puts message
|
144
|
+
end
|
145
|
+
|
146
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cwl_client).at_least(:once).and_return(mock_cwl_client)
|
147
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:iam_client).at_least(:once).and_return(mock_iam_client)
|
148
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
|
149
|
+
|
150
|
+
expect { subject.setup! }.to output(/Cluster already created, skipping./).to_stdout
|
151
|
+
end
|
109
152
|
|
110
|
-
|
153
|
+
it 'creates the keypair if not there' do
|
154
|
+
expect(mock_ec2_client).to receive(:describe_key_pairs) do
|
155
|
+
raise Aws::EC2::Errors::InvalidKeyPairNotFound.new(nil, 'some')
|
156
|
+
end
|
157
|
+
|
158
|
+
expect(mock_ec2_client).to receive(:create_key_pair) { raise 'created keypair' }
|
159
|
+
|
160
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ec2_client).at_least(:once).and_return(mock_ec2_client)
|
161
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:iam_client).at_least(:once).and_return(mock_iam_client)
|
162
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
|
163
|
+
|
164
|
+
expect { subject.setup! }.to raise_error('created keypair')
|
165
|
+
end
|
111
166
|
end
|
112
167
|
|
113
168
|
context '#ssh' do
|
@@ -119,9 +174,11 @@ describe EcsDeployCli::Runner do
|
|
119
174
|
expect(mock_ec2_client).to receive(:describe_instances)
|
120
175
|
.with(instance_ids: ['i-123123'])
|
121
176
|
.and_return(
|
122
|
-
double(
|
123
|
-
|
124
|
-
|
177
|
+
double(
|
178
|
+
reservations: [
|
179
|
+
double(instances: [double(public_dns_name: 'test.com')])
|
180
|
+
]
|
181
|
+
)
|
125
182
|
)
|
126
183
|
|
127
184
|
expect(Process).to receive(:fork) do |&block|
|
@@ -156,8 +213,8 @@ describe EcsDeployCli::Runner do
|
|
156
213
|
.with(instance_ids: ['i-321321'])
|
157
214
|
.and_return(
|
158
215
|
double(reservations: [
|
159
|
-
|
160
|
-
|
216
|
+
double(instances: [double(public_dns_name: 'test.com')])
|
217
|
+
])
|
161
218
|
)
|
162
219
|
|
163
220
|
expect(Process).to receive(:fork) do |&block|
|
@@ -195,16 +252,32 @@ describe EcsDeployCli::Runner do
|
|
195
252
|
subject.run_task!('yourproject-cron', launch_type: 'FARGATE', security_groups: [], subnets: [])
|
196
253
|
end
|
197
254
|
|
198
|
-
|
199
|
-
|
255
|
+
context '#update_crons!' do
|
256
|
+
it 'creates missing crons' do
|
257
|
+
mock_ecs_client.stub_responses(:register_task_definition, { task_definition: { family: 'some', revision: 1, task_definition_arn: 'arn:task:eu-central-1:xxxx' } })
|
200
258
|
|
201
|
-
|
259
|
+
expect(mock_cwe_client).to receive(:list_targets_by_rule) do
|
260
|
+
raise Aws::CloudWatchEvents::Errors::ResourceNotFoundException.new(nil, 'some')
|
261
|
+
end
|
202
262
|
|
203
|
-
|
204
|
-
|
205
|
-
|
263
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cwl_client).at_least(:once).and_return(mock_cwl_client)
|
264
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
|
265
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cwe_client).at_least(:once).and_return(mock_cwe_client)
|
266
|
+
|
267
|
+
subject.update_crons!
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'updates existing crons' do
|
271
|
+
mock_ecs_client.stub_responses(:register_task_definition, { task_definition: { family: 'some', revision: 1, task_definition_arn: 'arn:task:eu-central-1:xxxx' } })
|
206
272
|
|
207
|
-
|
273
|
+
mock_cwe_client.stub_responses(:list_targets_by_rule, { targets: [{ id: '123', arn: 'arn:123' }] })
|
274
|
+
|
275
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cwl_client).at_least(:once).and_return(mock_cwl_client)
|
276
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:ecs_client).at_least(:once).and_return(mock_ecs_client)
|
277
|
+
expect_any_instance_of(EcsDeployCli::Runners::Base).to receive(:cwe_client).at_least(:once).and_return(mock_cwe_client)
|
278
|
+
|
279
|
+
subject.update_crons!
|
280
|
+
end
|
208
281
|
end
|
209
282
|
|
210
283
|
it '#update_services!' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ecs_deploy_cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mònade
|
@@ -115,6 +115,20 @@ dependencies:
|
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '1'
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: aws-sdk-iam
|
120
|
+
requirement: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1'
|
125
|
+
type: :runtime
|
126
|
+
prerelease: false
|
127
|
+
version_requirements: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1'
|
118
132
|
- !ruby/object:Gem::Dependency
|
119
133
|
name: colorize
|
120
134
|
requirement: !ruby/object:Gem::Requirement
|
@@ -251,22 +265,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
265
|
- !ruby/object:Gem::Version
|
252
266
|
version: '0'
|
253
267
|
requirements: []
|
254
|
-
rubygems_version: 3.2.
|
268
|
+
rubygems_version: 3.2.7
|
255
269
|
signing_key:
|
256
270
|
specification_version: 4
|
257
271
|
summary: A command line interface to make ECS deployments easier
|
258
272
|
test_files:
|
259
|
-
- spec/ecs_deploy_cli/cli_spec.rb
|
260
|
-
- spec/ecs_deploy_cli/dsl/cluster_spec.rb
|
261
|
-
- spec/ecs_deploy_cli/dsl/container_spec.rb
|
262
|
-
- spec/ecs_deploy_cli/dsl/cron_spec.rb
|
263
|
-
- spec/ecs_deploy_cli/dsl/parser_spec.rb
|
264
|
-
- spec/ecs_deploy_cli/dsl/service_spec.rb
|
265
|
-
- spec/ecs_deploy_cli/dsl/task_spec.rb
|
266
|
-
- spec/ecs_deploy_cli/runner_spec.rb
|
267
|
-
- spec/ecs_deploy_cli/runners/base_spec.rb
|
268
273
|
- spec/spec_helper.rb
|
269
274
|
- spec/support/ECSFile
|
270
275
|
- spec/support/ECSFile.minimal
|
271
|
-
- spec/support/env_file.ext.yml
|
272
276
|
- spec/support/env_file.yml
|
277
|
+
- spec/support/env_file.ext.yml
|
278
|
+
- spec/ecs_deploy_cli/runner_spec.rb
|
279
|
+
- spec/ecs_deploy_cli/cli_spec.rb
|
280
|
+
- spec/ecs_deploy_cli/runners/base_spec.rb
|
281
|
+
- spec/ecs_deploy_cli/dsl/task_spec.rb
|
282
|
+
- spec/ecs_deploy_cli/dsl/parser_spec.rb
|
283
|
+
- spec/ecs_deploy_cli/dsl/service_spec.rb
|
284
|
+
- spec/ecs_deploy_cli/dsl/cron_spec.rb
|
285
|
+
- spec/ecs_deploy_cli/dsl/cluster_spec.rb
|
286
|
+
- spec/ecs_deploy_cli/dsl/container_spec.rb
|