wrapbox 0.9.0 → 0.10.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/.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"
|