wrapbox 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.dockerignore +0 -1
- data/.github/workflows/main.yml +30 -0
- data/README.md +94 -30
- data/lib/wrapbox/config_repository.rb +6 -1
- data/lib/wrapbox/configuration.rb +23 -6
- data/lib/wrapbox/log_fetcher/awslogs.rb +25 -13
- data/lib/wrapbox/runner/docker.rb +16 -2
- data/lib/wrapbox/runner/ecs/instance_manager.rb +92 -0
- data/lib/wrapbox/runner/ecs/task_waiter.rb +120 -0
- data/lib/wrapbox/runner/ecs.rb +174 -109
- data/lib/wrapbox/version.rb +1 -1
- data/lib/wrapbox.rb +11 -6
- data/wrapbox.gemspec +5 -3
- metadata +45 -16
- data/.travis.yml +0 -5
data/lib/wrapbox/runner/ecs.rb
CHANGED
@@ -4,12 +4,15 @@ require "multi_json"
|
|
4
4
|
require "thor"
|
5
5
|
require "yaml"
|
6
6
|
require "active_support/core_ext/hash"
|
7
|
-
require "logger"
|
8
7
|
require "pp"
|
9
8
|
require "shellwords"
|
9
|
+
require "thwait"
|
10
10
|
|
11
|
+
require "wrapbox"
|
11
12
|
require "wrapbox/config_repository"
|
12
13
|
require "wrapbox/log_fetcher"
|
14
|
+
require "wrapbox/runner/ecs/instance_manager"
|
15
|
+
require "wrapbox/runner/ecs/task_waiter"
|
13
16
|
require "wrapbox/version"
|
14
17
|
|
15
18
|
module Wrapbox
|
@@ -29,13 +32,11 @@ module Wrapbox
|
|
29
32
|
attr_reader \
|
30
33
|
:name,
|
31
34
|
:revision,
|
32
|
-
:cluster,
|
33
35
|
:region,
|
34
36
|
:container_definitions,
|
35
37
|
:volumes,
|
36
38
|
:placement_constraints,
|
37
39
|
:placement_strategy,
|
38
|
-
:launch_type,
|
39
40
|
:requires_compatibilities,
|
40
41
|
:task_definition_name,
|
41
42
|
:main_container_name,
|
@@ -43,8 +44,21 @@ module Wrapbox
|
|
43
44
|
:network_configuration,
|
44
45
|
:cpu,
|
45
46
|
:memory,
|
46
|
-
:
|
47
|
-
:
|
47
|
+
:enable_ecs_managed_tags,
|
48
|
+
:tags,
|
49
|
+
:propagate_tags,
|
50
|
+
:enable_execute_command
|
51
|
+
|
52
|
+
def self.split_overridable_options_and_parameters(options)
|
53
|
+
opts = options.dup
|
54
|
+
overridable_options = {}
|
55
|
+
%i[cluster launch_type task_role_arn execution_role_arn tags propagate_tags].each do |key|
|
56
|
+
value = opts.delete(key)
|
57
|
+
overridable_options[key] = value if value
|
58
|
+
end
|
59
|
+
|
60
|
+
[overridable_options, opts]
|
61
|
+
end
|
48
62
|
|
49
63
|
def initialize(options)
|
50
64
|
@name = options[:name]
|
@@ -53,14 +67,23 @@ module Wrapbox
|
|
53
67
|
@cluster = options[:cluster]
|
54
68
|
@region = options[:region]
|
55
69
|
@volumes = options[:volumes]
|
56
|
-
@placement_constraints = options[:placement_constraints]
|
70
|
+
@placement_constraints = options[:placement_constraints] || []
|
57
71
|
@placement_strategy = options[:placement_strategy]
|
72
|
+
@capacity_provider_strategy = options[:capacity_provider_strategy] || []
|
58
73
|
@launch_type = options[:launch_type]
|
59
74
|
@requires_compatibilities = options[:requires_compatibilities]
|
60
75
|
@network_mode = options[:network_mode]
|
61
76
|
@network_configuration = options[:network_configuration]
|
62
77
|
@cpu = options[:cpu]
|
63
78
|
@memory = options[:memory]
|
79
|
+
@enable_ecs_managed_tags = options[:enable_ecs_managed_tags]
|
80
|
+
@tags = options[:tags]
|
81
|
+
@propagate_tags = options[:propagate_tags]
|
82
|
+
@enable_execute_command = options[:enable_execute_command]
|
83
|
+
if options[:launch_instances]
|
84
|
+
@instance_manager = Wrapbox::Runner::Ecs::InstanceManager.new(@cluster, @region, **options[:launch_instances])
|
85
|
+
end
|
86
|
+
@task_waiter = Wrapbox::Runner::Ecs::TaskWaiter.new(cluster: @cluster, region: @region, delay: WAIT_DELAY)
|
64
87
|
|
65
88
|
@container_definitions = options[:container_definition] ? [options[:container_definition]] : options[:container_definitions] || []
|
66
89
|
@container_definitions.concat(options[:additional_container_definitions]) if options[:additional_container_definitions] # deprecated
|
@@ -96,18 +119,14 @@ module Wrapbox
|
|
96
119
|
@logger = Wrapbox.logger
|
97
120
|
if options[:log_fetcher]
|
98
121
|
type = options[:log_fetcher][:type]
|
99
|
-
@log_fetcher = LogFetcher.new(type, options[:log_fetcher])
|
122
|
+
@log_fetcher = LogFetcher.new(type, **options[:log_fetcher])
|
100
123
|
end
|
101
124
|
end
|
102
125
|
|
103
126
|
class Parameter
|
104
127
|
attr_reader \
|
105
128
|
:environments,
|
106
|
-
:task_role_arn,
|
107
|
-
:execution_role_arn,
|
108
|
-
:cluster,
|
109
129
|
:timeout,
|
110
|
-
:launch_type,
|
111
130
|
:launch_timeout,
|
112
131
|
:launch_retry,
|
113
132
|
:retry_interval,
|
@@ -115,7 +134,7 @@ module Wrapbox
|
|
115
134
|
:max_retry_interval,
|
116
135
|
:execution_retry
|
117
136
|
|
118
|
-
def initialize(environments: [],
|
137
|
+
def initialize(environments: [], timeout: 3600 * 24, launch_timeout: 60 * 10, launch_retry: 10, retry_interval: 1, retry_interval_multiplier: 2, max_retry_interval: 120, execution_retry: 0)
|
119
138
|
b = binding
|
120
139
|
method(:initialize).parameters.each do |param|
|
121
140
|
instance_variable_set("@#{param[1]}", b.local_variable_get(param[1]))
|
@@ -127,30 +146,52 @@ module Wrapbox
|
|
127
146
|
task_definition = prepare_task_definition(container_definition_overrides)
|
128
147
|
parameter = Parameter.new(**parameters)
|
129
148
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
149
|
+
envs = parameters[:environments] || []
|
150
|
+
envs += [
|
151
|
+
{
|
152
|
+
name: CLASS_NAME_ENV,
|
153
|
+
value: class_name.to_s,
|
154
|
+
},
|
155
|
+
{
|
156
|
+
name: METHOD_NAME_ENV,
|
157
|
+
value: method_name.to_s,
|
158
|
+
},
|
159
|
+
{
|
160
|
+
name: METHOD_ARGS_ENV,
|
161
|
+
value: MultiJson.dump(args),
|
162
|
+
},
|
163
|
+
]
|
164
|
+
|
165
|
+
if @instance_manager
|
166
|
+
Thread.new { @instance_manager.start_preparing_instances(1) }
|
167
|
+
end
|
168
|
+
|
169
|
+
run_task(task_definition.task_definition_arn, ["bundle", "exec", "rake", "wrapbox:run"], envs, parameter)
|
170
|
+
ensure
|
171
|
+
@instance_manager&.terminate_all_instances
|
135
172
|
end
|
136
173
|
|
137
174
|
def run_cmd(cmds, container_definition_overrides: {}, ignore_signal: false, **parameters)
|
138
175
|
ths = []
|
139
176
|
|
140
177
|
task_definition = prepare_task_definition(container_definition_overrides)
|
178
|
+
parameter = Parameter.new(**parameters)
|
141
179
|
|
142
180
|
cmds << nil if cmds.empty?
|
181
|
+
|
182
|
+
if @instance_manager
|
183
|
+
Thread.new { @instance_manager.start_preparing_instances(cmds.size) }
|
184
|
+
end
|
185
|
+
|
143
186
|
cmds.each_with_index do |cmd, idx|
|
144
187
|
ths << Thread.new(cmd, idx) do |c, i|
|
145
188
|
Thread.current[:cmd_index] = i
|
146
189
|
envs = (parameters[:environments] || []) + [{name: "WRAPBOX_CMD_INDEX", value: i.to_s}]
|
147
|
-
run_task(
|
148
|
-
task_definition.task_definition_arn, nil, nil, nil,
|
149
|
-
c&.shellsplit,
|
150
|
-
Parameter.new(**parameters.merge(environments: envs))
|
151
|
-
)
|
190
|
+
run_task(task_definition.task_definition_arn, c&.shellsplit, envs, parameter)
|
152
191
|
end
|
153
192
|
end
|
193
|
+
ThreadsWait.all_waits(ths)
|
194
|
+
# Raise an error if some threads have an error
|
154
195
|
ths.each(&:join)
|
155
196
|
|
156
197
|
true
|
@@ -171,6 +212,8 @@ module Wrapbox
|
|
171
212
|
end
|
172
213
|
end
|
173
214
|
nil
|
215
|
+
ensure
|
216
|
+
@instance_manager&.terminate_all_instances
|
174
217
|
end
|
175
218
|
|
176
219
|
private
|
@@ -179,24 +222,24 @@ module Wrapbox
|
|
179
222
|
!!@task_definition_info
|
180
223
|
end
|
181
224
|
|
182
|
-
def run_task(task_definition_arn,
|
183
|
-
cl = parameter.cluster || self.cluster
|
225
|
+
def run_task(task_definition_arn, command, environments, parameter)
|
184
226
|
execution_try_count = 0
|
185
227
|
|
228
|
+
ec2_instance_id = @instance_manager&.pop_ec2_instance_id
|
186
229
|
begin
|
187
|
-
task = create_task(task_definition_arn,
|
230
|
+
task = create_task(task_definition_arn, command, environments, parameter, ec2_instance_id)
|
188
231
|
return unless task # only Task creation aborted by SignalException
|
189
232
|
|
190
|
-
@logger.
|
233
|
+
@logger.info("#{log_prefix}Launch Task: #{task.task_arn}")
|
191
234
|
|
192
|
-
wait_task_stopped(
|
235
|
+
wait_task_stopped(task.task_arn, parameter.timeout)
|
193
236
|
|
194
|
-
@logger.
|
237
|
+
@logger.info("#{log_prefix}Stop Task: #{task.task_arn}")
|
195
238
|
|
196
239
|
# Avoid container exit code fetch miss
|
197
240
|
sleep WAIT_DELAY
|
198
241
|
|
199
|
-
task_status = fetch_task_status(
|
242
|
+
task_status = fetch_task_status(task.task_arn)
|
200
243
|
|
201
244
|
# If exit_code is nil, Container is force killed or ECS failed to launch Container by Irregular situation
|
202
245
|
error_message = build_error_message(task_definition_name, task.task_arn, task_status)
|
@@ -211,18 +254,18 @@ module Wrapbox
|
|
211
254
|
raise
|
212
255
|
else
|
213
256
|
execution_try_count += 1
|
214
|
-
@logger.warn("Retry Execution after #{EXECUTION_RETRY_INTERVAL} sec (#{execution_try_count}/#{parameter.execution_retry})")
|
257
|
+
@logger.warn("#{log_prefix}Retry Execution after #{EXECUTION_RETRY_INTERVAL} sec (#{execution_try_count}/#{parameter.execution_retry})")
|
215
258
|
sleep EXECUTION_RETRY_INTERVAL
|
216
259
|
retry
|
217
260
|
end
|
218
261
|
rescue SignalException
|
219
262
|
client.stop_task(
|
220
|
-
cluster:
|
263
|
+
cluster: @cluster,
|
221
264
|
task: task.task_arn,
|
222
265
|
reason: "signal interrupted"
|
223
266
|
)
|
224
|
-
wait_task_stopped(
|
225
|
-
@logger.debug("Stop Task: #{task.task_arn}")
|
267
|
+
wait_task_stopped(task.task_arn, TERM_TIMEOUT)
|
268
|
+
@logger.debug("#{log_prefix}Stop Task: #{task.task_arn}")
|
226
269
|
ensure
|
227
270
|
if @log_fetcher
|
228
271
|
begin
|
@@ -231,29 +274,43 @@ module Wrapbox
|
|
231
274
|
@logger.warn(e)
|
232
275
|
end
|
233
276
|
end
|
277
|
+
@instance_manager.terminate_instance(ec2_instance_id) if ec2_instance_id
|
234
278
|
end
|
235
279
|
end
|
236
280
|
|
237
|
-
def create_task(task_definition_arn,
|
238
|
-
cl = parameter.cluster || self.cluster
|
239
|
-
launch_type = parameter.launch_type || self.launch_type
|
281
|
+
def create_task(task_definition_arn, command, environments, parameter, ec2_instance_id)
|
240
282
|
args = Array(args)
|
241
283
|
|
242
284
|
launch_try_count = 0
|
243
285
|
current_retry_interval = parameter.retry_interval
|
244
286
|
|
245
287
|
begin
|
246
|
-
run_task_options = build_run_task_options(task_definition_arn,
|
247
|
-
@logger.debug("Task Options: #{run_task_options}")
|
248
|
-
|
288
|
+
run_task_options = build_run_task_options(task_definition_arn, command, environments, ec2_instance_id)
|
289
|
+
@logger.debug("#{log_prefix}Task Options: #{run_task_options}")
|
290
|
+
|
291
|
+
begin
|
292
|
+
resp = client.run_task(run_task_options)
|
293
|
+
rescue Aws::ECS::Errors::ThrottlingException
|
294
|
+
@logger.warn("#{log_prefix}Failure: Rate exceeded.")
|
295
|
+
raise LaunchFailure
|
296
|
+
rescue Aws::ECS::Errors::InvalidParameterException => e
|
297
|
+
# ec2:DescribeSecurityGroups is called in ecs:RunTask when awsvpc mode is used,
|
298
|
+
# and some errors like "Request limit exceeded", "InternalError" etc. caused by
|
299
|
+
# ec2:DescribeSecurityGroups are retriable.
|
300
|
+
# cf. https://github.com/reproio/wrapbox/issues/32, https://github.com/reproio/wrapbox/issues/43
|
301
|
+
raise if !e.message.include?("Request limit exceeded") && !e.message.include?("InternalError")
|
302
|
+
|
303
|
+
@logger.warn("#{log_prefix}Failure: #{e.message}")
|
304
|
+
raise LaunchFailure
|
305
|
+
end
|
249
306
|
task = resp.tasks[0]
|
250
307
|
|
251
308
|
resp.failures.each do |failure|
|
252
|
-
@logger.warn("Failure: Arn=#{failure.arn}, Reason=#{failure.reason}")
|
309
|
+
@logger.warn("#{log_prefix}Failure: Arn=#{failure.arn}, Reason=#{failure.reason}")
|
253
310
|
end
|
254
311
|
raise LackResource unless task # this case is almost lack of container resource.
|
255
312
|
|
256
|
-
@logger.debug("Create Task: #{task.task_arn}")
|
313
|
+
@logger.debug("#{log_prefix}Create Task: #{task.task_arn}")
|
257
314
|
|
258
315
|
@log_fetcher.run(task: task) if @log_fetcher
|
259
316
|
|
@@ -261,17 +318,17 @@ module Wrapbox
|
|
261
318
|
sleep WAIT_DELAY
|
262
319
|
|
263
320
|
begin
|
264
|
-
wait_task_running(
|
321
|
+
wait_task_running(task.task_arn, parameter.launch_timeout)
|
265
322
|
task
|
266
|
-
rescue
|
323
|
+
rescue Wrapbox::Runner::Ecs::TaskWaiter::WaitTimeout
|
267
324
|
client.stop_task(
|
268
|
-
cluster:
|
325
|
+
cluster: @cluster,
|
269
326
|
task: task.task_arn,
|
270
327
|
reason: "launch timeout"
|
271
328
|
)
|
272
329
|
raise
|
273
|
-
rescue
|
274
|
-
task_status = fetch_task_status(
|
330
|
+
rescue Wrapbox::Runner::Ecs::TaskWaiter::WaitFailure
|
331
|
+
task_status = fetch_task_status(task.task_arn)
|
275
332
|
|
276
333
|
case task_status[:last_status]
|
277
334
|
when "RUNNING"
|
@@ -287,71 +344,62 @@ module Wrapbox
|
|
287
344
|
end
|
288
345
|
end
|
289
346
|
rescue LackResource
|
290
|
-
@logger.warn("Failed to create task, because of lack resource")
|
291
|
-
put_waiting_task_count_metric
|
347
|
+
@logger.warn("#{log_prefix}Failed to create task, because of lack resource")
|
348
|
+
put_waiting_task_count_metric
|
292
349
|
|
293
350
|
if launch_try_count >= parameter.launch_retry
|
294
351
|
raise
|
295
352
|
else
|
296
353
|
launch_try_count += 1
|
297
|
-
|
298
|
-
|
354
|
+
retry_interval = current_retry_interval/2 + rand(current_retry_interval/2)
|
355
|
+
@logger.warn("#{log_prefix}Retry Create Task after #{retry_interval} sec (#{launch_try_count}/#{parameter.launch_retry})")
|
356
|
+
sleep retry_interval
|
299
357
|
current_retry_interval = [current_retry_interval * parameter.retry_interval_multiplier, parameter.max_retry_interval].min
|
300
358
|
retry
|
301
359
|
end
|
302
360
|
rescue LaunchFailure
|
303
361
|
if launch_try_count >= parameter.launch_retry
|
304
|
-
task_status = fetch_task_status(
|
362
|
+
task_status = fetch_task_status(task.task_arn)
|
305
363
|
raise LaunchFailure, build_error_message(task_definition_name, task.task_arn, task_status)
|
306
364
|
else
|
307
365
|
launch_try_count += 1
|
308
|
-
|
309
|
-
|
366
|
+
retry_interval = current_retry_interval/2 + rand(current_retry_interval/2)
|
367
|
+
@logger.warn("#{log_prefix}Retry Create Task after #{retry_interval} sec (#{launch_try_count}/#{parameter.launch_retry})")
|
368
|
+
sleep retry_interval
|
310
369
|
current_retry_interval = [current_retry_interval * parameter.retry_interval_multiplier, parameter.max_retry_interval].min
|
311
370
|
retry
|
312
371
|
end
|
313
372
|
rescue SignalException
|
314
373
|
if task
|
315
374
|
client.stop_task(
|
316
|
-
cluster:
|
375
|
+
cluster: @cluster,
|
317
376
|
task: task.task_arn,
|
318
377
|
reason: "signal interrupted"
|
319
378
|
)
|
320
|
-
wait_task_stopped(
|
321
|
-
@logger.debug("Stop Task: #{task.task_arn}")
|
379
|
+
wait_task_stopped(task.task_arn, TERM_TIMEOUT)
|
380
|
+
@logger.debug("#{log_prefix}Stop Task: #{task.task_arn}")
|
322
381
|
nil
|
323
382
|
end
|
324
383
|
end
|
325
384
|
end
|
326
385
|
|
327
|
-
def
|
328
|
-
|
329
|
-
if timeout
|
330
|
-
w.delay = WAIT_DELAY
|
331
|
-
w.max_attempts = timeout / w.delay
|
332
|
-
else
|
333
|
-
w.max_attempts = nil
|
334
|
-
end
|
335
|
-
end
|
386
|
+
def wait_task_running(task_arn, launch_timeout)
|
387
|
+
@task_waiter.wait_task_running(task_arn, timeout: launch_timeout)
|
336
388
|
end
|
337
389
|
|
338
|
-
def
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
def wait_task_stopped(cluster, task_arn, execution_timeout)
|
343
|
-
wait_until_with_timeout(cluster, task_arn, execution_timeout, :tasks_stopped)
|
344
|
-
rescue Aws::Waiters::Errors::TooManyAttemptsError
|
390
|
+
def wait_task_stopped(task_arn, execution_timeout)
|
391
|
+
@task_waiter.wait_task_stopped(task_arn, timeout: execution_timeout)
|
392
|
+
rescue Wrapbox::Runner::Ecs::TaskWaiter::WaitTimeout
|
345
393
|
client.stop_task({
|
346
|
-
cluster: cluster,
|
394
|
+
cluster: @cluster,
|
347
395
|
task: task_arn,
|
348
396
|
reason: "process timeout",
|
349
397
|
})
|
350
398
|
raise ExecutionTimeout, "Task #{task_definition_name} is timeout. task=#{task_arn}, timeout=#{execution_timeout}"
|
351
399
|
end
|
352
400
|
|
353
|
-
def fetch_task_status(
|
354
|
-
task = client.describe_tasks(cluster: cluster, tasks: [task_arn]).tasks[0]
|
401
|
+
def fetch_task_status(task_arn)
|
402
|
+
task = client.describe_tasks(cluster: @cluster, tasks: [task_arn]).tasks[0]
|
355
403
|
container = task.containers.find { |c| c.name == main_container_name }
|
356
404
|
{
|
357
405
|
last_status: task.last_status,
|
@@ -384,7 +432,7 @@ module Wrapbox
|
|
384
432
|
end
|
385
433
|
end
|
386
434
|
|
387
|
-
@logger.debug("Container Definitions: #{overrided_container_definitions}")
|
435
|
+
@logger.debug("#{log_prefix}Container Definitions: #{overrided_container_definitions}")
|
388
436
|
register_retry_count = 0
|
389
437
|
begin
|
390
438
|
client.register_task_definition({
|
@@ -395,10 +443,11 @@ module Wrapbox
|
|
395
443
|
container_definitions: overrided_container_definitions,
|
396
444
|
volumes: volumes,
|
397
445
|
requires_compatibilities: requires_compatibilities,
|
398
|
-
task_role_arn: task_role_arn,
|
399
|
-
execution_role_arn: execution_role_arn
|
446
|
+
task_role_arn: @task_role_arn,
|
447
|
+
execution_role_arn: @execution_role_arn,
|
448
|
+
tags: tags,
|
400
449
|
}).task_definition
|
401
|
-
rescue Aws::ECS::Errors::ClientException
|
450
|
+
rescue Aws::ECS::Errors::ClientException, Aws::ECS::Errors::ThrottlingException
|
402
451
|
raise if register_retry_count > 2
|
403
452
|
register_retry_count += 1
|
404
453
|
sleep 2
|
@@ -406,6 +455,14 @@ module Wrapbox
|
|
406
455
|
end
|
407
456
|
end
|
408
457
|
|
458
|
+
def cmd_index
|
459
|
+
Thread.current[:cmd_index]
|
460
|
+
end
|
461
|
+
|
462
|
+
def log_prefix
|
463
|
+
cmd_index ? "##{cmd_index} " : ""
|
464
|
+
end
|
465
|
+
|
409
466
|
def client
|
410
467
|
return @client if @client
|
411
468
|
|
@@ -422,7 +479,7 @@ module Wrapbox
|
|
422
479
|
@cloud_watch_client = Aws::CloudWatch::Client.new(options)
|
423
480
|
end
|
424
481
|
|
425
|
-
def put_waiting_task_count_metric
|
482
|
+
def put_waiting_task_count_metric
|
426
483
|
cloud_watch_client.put_metric_data(
|
427
484
|
namespace: "wrapbox",
|
428
485
|
metric_data: [
|
@@ -430,7 +487,7 @@ module Wrapbox
|
|
430
487
|
dimensions: [
|
431
488
|
{
|
432
489
|
name: "ClusterName",
|
433
|
-
value: cluster
|
490
|
+
value: @cluster,
|
434
491
|
},
|
435
492
|
],
|
436
493
|
timestamp: Time.now,
|
@@ -440,54 +497,53 @@ module Wrapbox
|
|
440
497
|
)
|
441
498
|
end
|
442
499
|
|
443
|
-
def build_run_task_options(task_definition_arn,
|
444
|
-
env = environments
|
445
|
-
env += [
|
446
|
-
{
|
447
|
-
name: CLASS_NAME_ENV,
|
448
|
-
value: class_name.to_s,
|
449
|
-
},
|
450
|
-
{
|
451
|
-
name: METHOD_NAME_ENV,
|
452
|
-
value: method_name.to_s,
|
453
|
-
},
|
454
|
-
{
|
455
|
-
name: METHOD_ARGS_ENV,
|
456
|
-
value: MultiJson.dump(args),
|
457
|
-
},
|
458
|
-
] if class_name && method_name && args
|
500
|
+
def build_run_task_options(task_definition_arn, command, environments, ec2_instance_id)
|
459
501
|
overrides = {
|
460
502
|
container_overrides: [
|
461
503
|
{
|
462
504
|
name: main_container_name,
|
463
|
-
environment:
|
505
|
+
environment: environments,
|
464
506
|
}.tap { |o| o[:command] = command if command },
|
465
507
|
*container_definitions.drop(1).map do |c|
|
466
508
|
{
|
467
509
|
name: c[:name],
|
468
|
-
environment:
|
510
|
+
environment: environments,
|
469
511
|
}
|
470
512
|
end
|
471
513
|
],
|
472
514
|
}
|
473
|
-
|
474
|
-
overrides[:task_role_arn] = role_arn if role_arn
|
515
|
+
overrides[:task_role_arn] = @task_role_arn if @task_role_arn
|
475
516
|
|
476
|
-
|
477
|
-
|
517
|
+
additional_placement_constraints = []
|
518
|
+
if ec2_instance_id
|
519
|
+
additional_placement_constraints << { type: "memberOf", expression: "ec2InstanceId == #{ec2_instance_id}" }
|
520
|
+
end
|
521
|
+
options = {
|
522
|
+
cluster: @cluster,
|
523
|
+
enable_execute_command: enable_execute_command,
|
478
524
|
task_definition: task_definition_arn,
|
479
525
|
overrides: overrides,
|
480
526
|
placement_strategy: placement_strategy,
|
481
|
-
placement_constraints: placement_constraints,
|
482
|
-
launch_type: launch_type,
|
527
|
+
placement_constraints: placement_constraints + additional_placement_constraints,
|
483
528
|
network_configuration: network_configuration,
|
484
529
|
started_by: "wrapbox-#{Wrapbox::VERSION}",
|
530
|
+
enable_ecs_managed_tags: enable_ecs_managed_tags,
|
531
|
+
propagate_tags: propagate_tags,
|
485
532
|
}
|
533
|
+
if @capacity_provider_strategy.empty?
|
534
|
+
options[:launch_type] = @launch_type if @launch_type
|
535
|
+
else
|
536
|
+
if @launch_type
|
537
|
+
@logger.warn("#{log_prefix}Ignore --launch_type and launch_type in the configuration file when specified capacity_provider_strategy in the configuration file")
|
538
|
+
end
|
539
|
+
options[:capacity_provider_strategy] = @capacity_provider_strategy
|
540
|
+
end
|
541
|
+
options
|
486
542
|
end
|
487
543
|
|
488
544
|
def build_error_message(task_definition_name, task_arn, task_status)
|
489
545
|
error_message = "Task #{task_definition_name} is failed. task=#{task_arn}, "
|
490
|
-
error_message << "cmd_index=#{
|
546
|
+
error_message << "cmd_index=#{cmd_index}, " if cmd_index
|
491
547
|
error_message << "exit_code=#{task_status[:exit_code]}, task_stopped_reason=#{task_status[:stopped_reason]}, container_stopped_reason=#{task_status[:container_stopped_reason]}"
|
492
548
|
error_message
|
493
549
|
end
|
@@ -501,15 +557,18 @@ module Wrapbox
|
|
501
557
|
method_option :cluster, aliases: "-c"
|
502
558
|
method_option :cpu, type: :numeric
|
503
559
|
method_option :memory, type: :numeric
|
560
|
+
method_option :working_directory, aliases: "-w", type: :string
|
504
561
|
method_option :environments, aliases: "-e"
|
505
562
|
method_option :task_role_arn
|
506
563
|
method_option :timeout, type: :numeric
|
507
|
-
method_option :launch_type, type: :string,
|
564
|
+
method_option :launch_type, type: :string, enum: ["EC2", "FARGATE"]
|
508
565
|
method_option :launch_timeout, type: :numeric
|
509
566
|
method_option :launch_retry, type: :numeric
|
510
567
|
method_option :execution_retry, type: :numeric
|
511
568
|
method_option :max_retry_interval, type: :numeric
|
512
569
|
method_option :ignore_signal, type: :boolean, default: false, desc: "Even if receive a signal (like TERM, INT, QUIT), ECS Tasks continue running"
|
570
|
+
method_option :tags, type: :string, aliases: "-t", repeatable: true
|
571
|
+
method_option :propagate_tags, type: :string, enum: ["TASK_DEFINITION", "SERVICE"]
|
513
572
|
method_option :verbose, aliases: "-v", type: :boolean, default: false, desc: "Verbose mode"
|
514
573
|
def run_cmd(*args)
|
515
574
|
Wrapbox.logger.level = :debug if options[:verbose]
|
@@ -518,6 +577,10 @@ module Wrapbox
|
|
518
577
|
environments = options[:environments].to_s.split(/,\s*/).map { |kv| kv.split("=") }.map do |k, v|
|
519
578
|
{name: k, value: v}
|
520
579
|
end
|
580
|
+
tags = options.fetch(:tags, []).map do |kv|
|
581
|
+
k, v = kv.split("=", 2)
|
582
|
+
{key: k, value: v}
|
583
|
+
end.presence
|
521
584
|
run_options = {
|
522
585
|
cluster: options[:cluster],
|
523
586
|
task_role_arn: options[:task_role_arn],
|
@@ -528,9 +591,11 @@ module Wrapbox
|
|
528
591
|
execution_retry: options[:execution_retry],
|
529
592
|
max_retry_interval: options[:max_retry_interval],
|
530
593
|
ignore_signal: options[:ignore_signal],
|
594
|
+
tags: tags,
|
595
|
+
propagate_tags: options[:propagate_tags],
|
531
596
|
}.reject { |_, v| v.nil? }
|
532
|
-
if options[:cpu] || options[:memory]
|
533
|
-
container_definition_overrides = {cpu: options[:cpu], memory: options[:memory]}.reject { |_, v| v.nil? }
|
597
|
+
if options[:cpu] || options[:memory] || options[:working_directory]
|
598
|
+
container_definition_overrides = {cpu: options[:cpu], memory: options[:memory], working_directory: options[:working_directory]}.reject { |_, v| v.nil? }
|
534
599
|
else
|
535
600
|
container_definition_overrides = {}
|
536
601
|
end
|
data/lib/wrapbox/version.rb
CHANGED
data/lib/wrapbox.rb
CHANGED
@@ -20,14 +20,19 @@ module Wrapbox
|
|
20
20
|
yield configs
|
21
21
|
end
|
22
22
|
|
23
|
-
def run(*args,
|
24
|
-
|
25
|
-
config.run(*args, **options)
|
23
|
+
def run(*args, config_name: nil, **options)
|
24
|
+
get_config(config_name).run(*args, **options)
|
26
25
|
end
|
27
26
|
|
28
|
-
def run_cmd(*args,
|
29
|
-
|
30
|
-
|
27
|
+
def run_cmd(*args, config_name: nil, **options)
|
28
|
+
get_config(config_name).run_cmd(*args, **options)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def get_config(config_name)
|
34
|
+
@configs.get(config_name) or
|
35
|
+
raise RuntimeError, %Q{The configuration "#{config_name}" is not registered}
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
data/wrapbox.gemspec
CHANGED
@@ -20,15 +20,17 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
+
spec.add_runtime_dependency "aws-sdk-ec2", "~> 1"
|
23
24
|
spec.add_runtime_dependency "aws-sdk-ecs", "~> 1"
|
24
25
|
spec.add_runtime_dependency "aws-sdk-cloudwatch", "~> 1"
|
25
26
|
spec.add_runtime_dependency "activesupport", ">= 4"
|
26
27
|
spec.add_runtime_dependency "docker-api"
|
27
28
|
spec.add_runtime_dependency "multi_json"
|
28
|
-
spec.add_runtime_dependency "thor"
|
29
|
+
spec.add_runtime_dependency "thor", ">= 1"
|
30
|
+
spec.add_runtime_dependency "thwait"
|
29
31
|
|
30
|
-
spec.add_development_dependency "bundler"
|
31
|
-
spec.add_development_dependency "rake"
|
32
|
+
spec.add_development_dependency "bundler"
|
33
|
+
spec.add_development_dependency "rake"
|
32
34
|
spec.add_development_dependency "rspec", "~> 3.0"
|
33
35
|
spec.add_development_dependency "webmock"
|
34
36
|
spec.add_development_dependency "tapp"
|