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.
@@ -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"