wrapbox 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- :task_role_arn,
47
- :execution_role_arn
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: [], task_role_arn: nil, cluster: nil, timeout: 3600 * 24, launch_type: "EC2", launch_timeout: 60 * 10, launch_retry: 10, retry_interval: 1, retry_interval_multiplier: 2, max_retry_interval: 120, execution_retry: 0)
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
- run_task(
131
- task_definition.task_definition_arn, class_name, method_name, args,
132
- ["bundle", "exec", "rake", "wrapbox:run"],
133
- parameter
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, class_name, method_name, args, command, parameter)
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, class_name, method_name, args, command, parameter)
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.debug("Launch Task: #{task.task_arn}")
233
+ @logger.info("#{log_prefix}Launch Task: #{task.task_arn}")
191
234
 
192
- wait_task_stopped(cl, task.task_arn, parameter.timeout)
235
+ wait_task_stopped(task.task_arn, parameter.timeout)
193
236
 
194
- @logger.debug("Stop Task: #{task.task_arn}")
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(cl, task.task_arn)
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: cl,
263
+ cluster: @cluster,
221
264
  task: task.task_arn,
222
265
  reason: "signal interrupted"
223
266
  )
224
- wait_task_stopped(cl, task.task_arn, TERM_TIMEOUT)
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, class_name, method_name, args, command, parameter)
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, class_name, method_name, args, command, cl, launch_type, parameter.environments, parameter.task_role_arn)
247
- @logger.debug("Task Options: #{run_task_options}")
248
- resp = client.run_task(run_task_options)
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(cl, task.task_arn, parameter.launch_timeout)
321
+ wait_task_running(task.task_arn, parameter.launch_timeout)
265
322
  task
266
- rescue Aws::Waiters::Errors::TooManyAttemptsError
323
+ rescue Wrapbox::Runner::Ecs::TaskWaiter::WaitTimeout
267
324
  client.stop_task(
268
- cluster: cl,
325
+ cluster: @cluster,
269
326
  task: task.task_arn,
270
327
  reason: "launch timeout"
271
328
  )
272
329
  raise
273
- rescue Aws::Waiters::Errors::WaiterFailed
274
- task_status = fetch_task_status(cl, task.task_arn)
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(cl)
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
- @logger.warn("Retry Create Task after #{current_retry_interval} sec (#{launch_try_count}/#{parameter.launch_retry})")
298
- sleep current_retry_interval
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(cl, task.task_arn)
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
- @logger.warn("Retry Create Task after #{current_retry_interval} sec (#{launch_try_count}/#{parameter.launch_retry})")
309
- sleep current_retry_interval
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: cl,
375
+ cluster: @cluster,
317
376
  task: task.task_arn,
318
377
  reason: "signal interrupted"
319
378
  )
320
- wait_task_stopped(cl, task.task_arn, TERM_TIMEOUT)
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 wait_until_with_timeout(cluster, task_arn, timeout, waiter_name)
328
- client.wait_until(waiter_name, cluster: cluster, tasks: [task_arn]) do |w|
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 wait_task_running(cluster, task_arn, launch_timeout)
339
- wait_until_with_timeout(cluster, task_arn, launch_timeout, :tasks_running)
340
- end
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(cluster, task_arn)
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(cluster)
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 || self.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, class_name, method_name, args, command, cluster, launch_type, environments, task_role_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: env,
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: env,
510
+ environment: environments,
469
511
  }
470
512
  end
471
513
  ],
472
514
  }
473
- role_arn = task_role_arn || self.task_role_arn
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
- cluster: cluster || self.cluster,
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=#{Thread.current[:cmd_index]}, " if Thread.current[: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, default: "EC2", enum: ["EC2", "FARGATE"]
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
@@ -1,3 +1,3 @@
1
1
  module Wrapbox
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/wrapbox.rb CHANGED
@@ -20,14 +20,19 @@ module Wrapbox
20
20
  yield configs
21
21
  end
22
22
 
23
- def run(*args, runner: nil, config_name: nil, **options)
24
- config = @configs.get(config_name)
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, runner: nil, config_name: nil, **options)
29
- config = @configs.get(config_name)
30
- config.run_cmd(*args, **options)
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", "~> 1.13"
31
- spec.add_development_dependency "rake", "~> 10.0"
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"