wrapbox 0.2.0 → 0.3.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/README.md +15 -7
- data/lib/wrapbox/runner/docker.rb +9 -8
- data/lib/wrapbox/runner/ecs.rb +191 -96
- data/lib/wrapbox/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8118b81067d91ea3535d8c3c5ca1ba55c412d345
|
4
|
+
data.tar.gz: 33fb0eb243ff53a6bc6ec7c5147a69ea3bbbc206
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e9a9730bca6b1dddbe9a8c21a2660645dc0d3f6f3c71a74c24e175930a6bd01544e2f2b2efd6d04fc0b14bdbeeded4a4d5ad7abe38e37c4c5abe83593c1a37c
|
7
|
+
data.tar.gz: 74c5c6831768ed37e5107903426131a4158220b808b39fae6d08b74df14dcfcc2f9bd495ec1ce3f6157a66ee5602f08a5161e0c73b6535f1cefdf4ebbf14a388
|
data/README.md
CHANGED
@@ -42,6 +42,17 @@ docker:
|
|
42
42
|
memory: 1024
|
43
43
|
```
|
44
44
|
|
45
|
+
#### run by CLI
|
46
|
+
|
47
|
+
```sh
|
48
|
+
$ wrapbox ecs run_cmd -f config.yml \
|
49
|
+
-e "FOO=bar,HOGE=fuga" \
|
50
|
+
"bundle exec rspec spec/models" \
|
51
|
+
"bundle exec rspec spec/controllers" \
|
52
|
+
```
|
53
|
+
|
54
|
+
#### run by ruby
|
55
|
+
|
45
56
|
```ruby
|
46
57
|
Wrapbox.configure do |c|
|
47
58
|
c.load_yaml(File.expand_path("../config.yml", __FILE__))
|
@@ -53,10 +64,7 @@ Wrapbox.run("TestJob", :perform, ["arg1", ["arg2", "arg3"]], environments: [{nam
|
|
53
64
|
Wrapbox.run("TestJob", :perform, ["arg1", ["arg2", "arg3"]], config_name: :docker, environments: [{name: "RAILS_ENV", value: "development"}]) # use docker config
|
54
65
|
|
55
66
|
# runs ls . command in ECS container
|
56
|
-
Wrapbox.run_cmd("ls
|
57
|
-
|
58
|
-
# runs ls . command in local docker container
|
59
|
-
Wrapbox.run_cmd("ls", ".", config_name: :docker, environments: [{name: "RAILS_ENV", value: "development"}])
|
67
|
+
Wrapbox.run_cmd(["ls ."], environments: [{name: "RAILS_ENV", value: "development"}])
|
60
68
|
```
|
61
69
|
|
62
70
|
## Config
|
@@ -86,9 +94,9 @@ Wrapbox.run_cmd("ls", ".", config_name: :docker, environments: [{name: "RAILS_EN
|
|
86
94
|
|
87
95
|
## API
|
88
96
|
|
89
|
-
#### `run(class_name, method_name, args, container_definition_overrides: {}, environments: [], task_role_arn: nil, cluster: nil, timeout: 3600 * 24, launch_timeout: 60 * 10, launch_retry: 10)`
|
97
|
+
#### `run(class_name, method_name, args, container_definition_overrides: {}, environments: [], task_role_arn: nil, cluster: nil, timeout: 3600 * 24, launch_timeout: 60 * 10, launch_retry: 10, retry_interval: 1, retry_interval_multiplier: 2, max_retry_interval: 120, execution_retry: 0)`
|
90
98
|
|
91
|
-
#### `run_cmd(*cmd, container_definition_overrides: {}, environments: [], task_role_arn: nil, cluster: nil, timeout: 3600 * 24, launch_timeout: 60 * 10, launch_retry: 10)`
|
99
|
+
#### `run_cmd(*cmd, container_definition_overrides: {}, environments: [], task_role_arn: nil, cluster: nil, timeout: 3600 * 24, launch_timeout: 60 * 10, launch_retry: 10, retry_interval: 1, retry_interval_multiplier: 2, max_retry_interval: 120, execution_retry: 0)`
|
92
100
|
|
93
101
|
following options is only for ECS.
|
94
102
|
|
@@ -98,7 +106,7 @@ following options is only for ECS.
|
|
98
106
|
- launch_timeout
|
99
107
|
- launch_retry
|
100
108
|
|
101
|
-
If Wrapbox cannot
|
109
|
+
If Wrapbox cannot create task, it puts custom metric data to CloudWatch.
|
102
110
|
Custom metric data is `wrapbox/WaitingTaskCount` that has `ClusterName` dimension.
|
103
111
|
And, it retry launching until retry count reach `launch_retry`.
|
104
112
|
|
@@ -29,13 +29,18 @@ module Wrapbox
|
|
29
29
|
exec_docker(definition: definition, cmd: ["bundle", "exec", "rake", "wrapbox:run"], environments: envs)
|
30
30
|
end
|
31
31
|
|
32
|
-
def run_cmd(
|
32
|
+
def run_cmd(cmds, container_definition_overrides: {}, environments: [])
|
33
33
|
definition = container_definition
|
34
34
|
.merge(container_definition_overrides)
|
35
35
|
|
36
36
|
environments = extract_environments(environments)
|
37
37
|
|
38
|
-
|
38
|
+
ths = cmds.map do |cmd|
|
39
|
+
Thread.new(cmd) do |c|
|
40
|
+
exec_docker(definition: definition, cmd: c.split(/\s+/), environments: environments)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
ths.each(&:join)
|
39
44
|
end
|
40
45
|
|
41
46
|
private
|
@@ -67,7 +72,7 @@ module Wrapbox
|
|
67
72
|
|
68
73
|
container = ::Docker::Container.create(options)
|
69
74
|
|
70
|
-
container.start
|
75
|
+
container.start
|
71
76
|
output_container_logs(container)
|
72
77
|
resp = container.wait
|
73
78
|
output_container_logs(container)
|
@@ -97,10 +102,6 @@ module Wrapbox
|
|
97
102
|
method_option :config_name, aliases: "-n", required: true, default: "default"
|
98
103
|
method_option :environments, aliases: "-e"
|
99
104
|
def run_cmd(*args)
|
100
|
-
if args.size == 1
|
101
|
-
args = args[0].split(" ")
|
102
|
-
end
|
103
|
-
|
104
105
|
repo = Wrapbox::ConfigRepository.new.tap { |r| r.load_yaml(options[:config]) }
|
105
106
|
config = repo.get(options[:config_name])
|
106
107
|
config.runner = :docker
|
@@ -108,7 +109,7 @@ module Wrapbox
|
|
108
109
|
environments = options[:environments].to_s.split(/,\s*/).map { |kv| kv.split("=") }.map do |k, v|
|
109
110
|
{name: k, value: v}
|
110
111
|
end
|
111
|
-
runner.run_cmd(
|
112
|
+
runner.run_cmd(args, environments: environments)
|
112
113
|
end
|
113
114
|
end
|
114
115
|
end
|
data/lib/wrapbox/runner/ecs.rb
CHANGED
@@ -11,8 +11,15 @@ require "wrapbox/version"
|
|
11
11
|
module Wrapbox
|
12
12
|
module Runner
|
13
13
|
class Ecs
|
14
|
-
class
|
14
|
+
class ExecutionFailure < StandardError; end
|
15
|
+
class ContainerAbnormalEnd < StandardError; end
|
16
|
+
class ExecutionTimeout < StandardError; end
|
15
17
|
class LaunchFailure < StandardError; end
|
18
|
+
class LackResource < StandardError; end
|
19
|
+
|
20
|
+
EXECUTION_RETRY_INTERVAL = 3
|
21
|
+
WAIT_DELAY = 5
|
22
|
+
HOST_TERMINATED_REASON_REGEXP = /Host EC2.*terminated/
|
16
23
|
|
17
24
|
attr_reader \
|
18
25
|
:name,
|
@@ -31,125 +38,206 @@ module Wrapbox
|
|
31
38
|
@container_definition = options[:container_definition]
|
32
39
|
@additional_container_definitions = options[:additional_container_definitions]
|
33
40
|
@task_role_arn = options[:task_role_arn]
|
41
|
+
$stdout.sync = true
|
34
42
|
@logger = Logger.new($stdout)
|
35
43
|
end
|
36
44
|
|
37
|
-
|
45
|
+
class Parameter
|
46
|
+
attr_reader \
|
47
|
+
:environments,
|
48
|
+
:task_role_arn,
|
49
|
+
:cluster,
|
50
|
+
:timeout,
|
51
|
+
:launch_timeout,
|
52
|
+
:launch_retry,
|
53
|
+
:retry_interval,
|
54
|
+
:retry_interval_multiplier,
|
55
|
+
:max_retry_interval,
|
56
|
+
:execution_retry
|
57
|
+
|
58
|
+
def initialize(environments: [], task_role_arn: nil, cluster: nil, timeout: 3600 * 24, launch_timeout: 60 * 10, launch_retry: 10, retry_interval: 1, retry_interval_multiplier: 2, max_retry_interval: 120, execution_retry: 0)
|
59
|
+
b = binding
|
60
|
+
method(:initialize).parameters.each do |param|
|
61
|
+
instance_variable_set("@#{param[1]}", b.local_variable_get(param[1]))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def run(class_name, method_name, args, container_definition_overrides: {}, **parameters)
|
38
67
|
task_definition = register_task_definition(container_definition_overrides)
|
68
|
+
parameter = Parameter.new(**parameters)
|
69
|
+
|
39
70
|
run_task(
|
40
71
|
task_definition.task_definition_arn, class_name, method_name, args,
|
41
|
-
|
42
|
-
|
43
|
-
task_role_arn: task_role_arn || @task_role_arn,
|
44
|
-
cluster: cluster,
|
45
|
-
timeout: timeout,
|
46
|
-
launch_timeout: launch_timeout,
|
47
|
-
launch_retry: launch_retry,
|
48
|
-
retry_interval: retry_interval,
|
49
|
-
retry_interval_multiplier: retry_interval_multiplier,
|
50
|
-
max_retry_interval: max_retry_interval,
|
72
|
+
["bundle", "exec", "rake", "wrapbox:run"],
|
73
|
+
parameter
|
51
74
|
)
|
52
75
|
end
|
53
76
|
|
54
|
-
def run_cmd(
|
77
|
+
def run_cmd(cmds, container_definition_overrides: {}, **parameters)
|
55
78
|
task_definition = register_task_definition(container_definition_overrides)
|
79
|
+
parameter = Parameter.new(**parameters)
|
56
80
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
81
|
+
ths = cmds.map do |cmd|
|
82
|
+
Thread.new(cmd) do |c|
|
83
|
+
run_task(
|
84
|
+
task_definition.task_definition_arn, nil, nil, nil,
|
85
|
+
c.split(/\s+/),
|
86
|
+
parameter
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
ths.each(&:join)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def run_task(task_definition_arn, class_name, method_name, args, command, parameter)
|
96
|
+
cl = parameter.cluster || self.cluster
|
97
|
+
execution_try_count = 0
|
98
|
+
|
99
|
+
begin
|
100
|
+
task = create_task(task_definition_arn, class_name, method_name, args, command, parameter)
|
101
|
+
|
102
|
+
@logger.debug("Launch Task: #{task.task_arn}")
|
103
|
+
|
104
|
+
wait_task_stopped(cl, task.task_arn, parameter.timeout)
|
105
|
+
|
106
|
+
@logger.debug("Stop Task: #{task.task_arn}")
|
107
|
+
|
108
|
+
# Avoid container exit code fetch miss
|
109
|
+
sleep WAIT_DELAY
|
110
|
+
|
111
|
+
task_status = fetch_task_status(cl, task.task_arn)
|
112
|
+
|
113
|
+
# If exit_code is nil, Container is force killed or ECS failed to launch Container by Irregular situation
|
114
|
+
error_message = "Container #{task_definition_name} is failed. task=#{task.task_arn}, exit_code=#{task_status[:exit_code]}, reason=#{task_status[:stopped_reason]}"
|
115
|
+
raise ContainerAbnormalEnd, error_message unless task_status[:exit_code]
|
116
|
+
raise ExecutionFailure, error_message unless task_status[:exit_code] == 0
|
117
|
+
|
118
|
+
true
|
119
|
+
rescue ContainerAbnormalEnd
|
120
|
+
retry if task_status[:stopped_reason] =~ HOST_TERMINATED_REASON_REGEXP
|
121
|
+
|
122
|
+
if execution_try_count >= parameter.execution_retry
|
123
|
+
raise
|
124
|
+
else
|
125
|
+
execution_try_count += 1
|
126
|
+
@logger.debug("Retry Execution after #{EXECUTION_RETRY_INTERVAL} sec")
|
127
|
+
sleep EXECUTION_RETRY_INTERVAL
|
128
|
+
retry
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def task_definition_name
|
134
|
+
"wrapbox_#{name}"
|
70
135
|
end
|
71
136
|
|
72
|
-
def
|
73
|
-
cl = cluster || self.cluster
|
137
|
+
def create_task(task_definition_arn, class_name, method_name, args, command, parameter)
|
138
|
+
cl = parameter.cluster || self.cluster
|
74
139
|
args = Array(args)
|
75
140
|
|
76
141
|
launch_try_count = 0
|
77
|
-
current_retry_interval = retry_interval
|
78
|
-
|
79
|
-
exit_code = nil
|
142
|
+
current_retry_interval = parameter.retry_interval
|
143
|
+
|
80
144
|
begin
|
81
145
|
task = client
|
82
|
-
.run_task(build_run_task_options(class_name, method_name, args, command,
|
146
|
+
.run_task(build_run_task_options(task_definition_arn, class_name, method_name, args, command, cl, parameter.environments, parameter.task_role_arn))
|
83
147
|
.tasks[0]
|
84
|
-
|
148
|
+
|
149
|
+
raise LackResource unless task # this case is almost lack of container resource.
|
150
|
+
|
85
151
|
@logger.debug("Create Task: #{task.task_arn}")
|
86
|
-
client.wait_until(:tasks_running, cluster: cl, tasks: [task.task_arn]) do |w|
|
87
|
-
if launch_timeout
|
88
|
-
w.delay = 5
|
89
|
-
w.max_attempts = launch_timeout / w.delay
|
90
|
-
else
|
91
|
-
w.max_attempts = nil
|
92
|
-
end
|
93
|
-
end
|
94
|
-
rescue Aws::Waiters::Errors::WaiterFailed, LaunchFailure
|
95
|
-
exit_code = task && fetch_exit_code(cl, task.task_arn)
|
96
|
-
unless exit_code
|
97
|
-
if launch_try_count >= launch_retry
|
98
|
-
client.stop_task(
|
99
|
-
cluster: cl,
|
100
|
-
task: task.task_arn,
|
101
|
-
reason: "launch timeout"
|
102
|
-
) if task
|
103
|
-
raise
|
104
|
-
else
|
105
|
-
put_waiting_task_count_metric(cl)
|
106
|
-
launch_try_count += 1
|
107
|
-
@logger.debug("Retry Create Task after #{current_retry_interval} sec")
|
108
|
-
sleep current_retry_interval
|
109
|
-
current_retry_interval = [current_retry_interval * retry_interval_multiplier, max_retry_interval].min
|
110
|
-
retry
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
152
|
|
115
|
-
|
153
|
+
# Wait ECS Task Status becomes stable
|
154
|
+
sleep WAIT_DELAY
|
116
155
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
156
|
+
begin
|
157
|
+
wait_task_running(cl, task.task_arn, parameter.launch_timeout)
|
158
|
+
task
|
159
|
+
rescue Aws::Waiters::Errors::TooManyAttemptsError
|
160
|
+
client.stop_task(
|
161
|
+
cluster: cl,
|
162
|
+
task: task.task_arn,
|
163
|
+
reason: "launch timeout"
|
164
|
+
)
|
165
|
+
raise
|
166
|
+
rescue Aws::Waiters::Errors::WaiterFailed
|
167
|
+
task_status = fetch_task_status(cl, task.task_arn)
|
168
|
+
|
169
|
+
case task_status[:last_status]
|
170
|
+
when "RUNNING"
|
171
|
+
return task
|
172
|
+
when "PENDING"
|
173
|
+
retry
|
122
174
|
else
|
123
|
-
|
175
|
+
if task_status[:exit_code]
|
176
|
+
return task
|
177
|
+
else
|
178
|
+
raise LaunchFailure
|
179
|
+
end
|
124
180
|
end
|
125
181
|
end
|
126
|
-
rescue
|
127
|
-
|
128
|
-
|
129
|
-
task: task.task_arn,
|
130
|
-
reason: "process timeout",
|
131
|
-
})
|
132
|
-
raise ExecutionError, "process timeout"
|
133
|
-
end
|
182
|
+
rescue LackResource
|
183
|
+
@logger.debug("Failed to create task, because of lack resource")
|
184
|
+
put_waiting_task_count_metric(cl)
|
134
185
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
186
|
+
if launch_try_count >= parameter.launch_retry
|
187
|
+
raise
|
188
|
+
else
|
189
|
+
launch_try_count += 1
|
190
|
+
@logger.debug("Retry Create Task after #{current_retry_interval} sec")
|
191
|
+
sleep current_retry_interval
|
192
|
+
current_retry_interval = [current_retry_interval * parameter.retry_interval_multiplier, parameter.max_retry_interval].min
|
193
|
+
retry
|
194
|
+
end
|
195
|
+
rescue LaunchFailure
|
196
|
+
if launch_try_count >= parameter.launch_retry
|
197
|
+
raise
|
198
|
+
else
|
199
|
+
launch_try_count += 1
|
200
|
+
@logger.debug("Retry Create Task after #{current_retry_interval} sec")
|
201
|
+
sleep current_retry_interval
|
202
|
+
current_retry_interval = [current_retry_interval * parameter.retry_interval_multiplier, parameter.max_retry_interval].min
|
203
|
+
retry
|
204
|
+
end
|
140
205
|
end
|
141
206
|
end
|
142
207
|
|
143
|
-
|
208
|
+
def wait_task_running(cluster, task_arn, launch_timeout)
|
209
|
+
client.wait_until(:tasks_running, cluster: cluster, tasks: [task_arn]) do |w|
|
210
|
+
if launch_timeout
|
211
|
+
w.delay = WAIT_DELAY
|
212
|
+
w.max_attempts = launch_timeout / w.delay
|
213
|
+
else
|
214
|
+
w.max_attempts = nil
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
144
218
|
|
145
|
-
def
|
146
|
-
|
219
|
+
def wait_task_stopped(cluster, task_arn, timeout)
|
220
|
+
client.wait_until(:tasks_stopped, cluster: cluster, tasks: [task_arn]) do |w|
|
221
|
+
if timeout
|
222
|
+
w.delay = WAIT_DELAY
|
223
|
+
w.max_attempts = timeout / w.delay
|
224
|
+
else
|
225
|
+
w.max_attempts = nil
|
226
|
+
end
|
227
|
+
end
|
228
|
+
rescue Aws::Waiters::Errors::TooManyAttemptsError
|
229
|
+
client.stop_task({
|
230
|
+
cluster: cl,
|
231
|
+
task: task_arn,
|
232
|
+
reason: "process timeout",
|
233
|
+
})
|
234
|
+
raise ExecutionTimeout, "Container #{task_definition_name} is timeout. task=#{task_arn}, timeout=#{timeout}"
|
147
235
|
end
|
148
236
|
|
149
|
-
def
|
237
|
+
def fetch_task_status(cluster, task_arn)
|
150
238
|
task = client.describe_tasks(cluster: cluster, tasks: [task_arn]).tasks[0]
|
151
239
|
container = task.containers.find { |c| c.name = task_definition_name }
|
152
|
-
container.exit_code
|
240
|
+
{last_status: task.last_status, exit_code: container.exit_code, stopped_reason: task.stopped_reason}
|
153
241
|
end
|
154
242
|
|
155
243
|
def register_task_definition(container_definition_overrides)
|
@@ -165,10 +253,18 @@ module Wrapbox
|
|
165
253
|
end
|
166
254
|
end
|
167
255
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
256
|
+
register_retry_count = 0
|
257
|
+
begin
|
258
|
+
client.register_task_definition({
|
259
|
+
family: task_definition_name,
|
260
|
+
container_definitions: container_definitions,
|
261
|
+
}).task_definition
|
262
|
+
rescue Aws::ECS::Errors::ClientException
|
263
|
+
raise if register_retry_count > 2
|
264
|
+
register_retry_count += 1
|
265
|
+
sleep 2
|
266
|
+
retry
|
267
|
+
end
|
172
268
|
end
|
173
269
|
|
174
270
|
def client
|
@@ -198,13 +294,14 @@ module Wrapbox
|
|
198
294
|
value: cluster || self.cluster,
|
199
295
|
},
|
200
296
|
],
|
297
|
+
timestamp: Time.now,
|
201
298
|
value: 1.0,
|
202
299
|
unit: "Count",
|
203
300
|
]
|
204
301
|
)
|
205
302
|
end
|
206
303
|
|
207
|
-
def build_run_task_options(class_name, method_name, args, command,
|
304
|
+
def build_run_task_options(task_definition_arn, class_name, method_name, args, command, cluster, environments, task_role_arn)
|
208
305
|
env = environments
|
209
306
|
env += [
|
210
307
|
{
|
@@ -252,12 +349,9 @@ module Wrapbox
|
|
252
349
|
method_option :timeout, type: :numeric
|
253
350
|
method_option :launch_timeout, type: :numeric
|
254
351
|
method_option :launch_retry, type: :numeric
|
352
|
+
method_option :execution_retry, type: :numeric
|
255
353
|
method_option :max_retry_interval, type: :numeric
|
256
354
|
def run_cmd(*args)
|
257
|
-
if args.size == 1
|
258
|
-
args = args[0].split(" ")
|
259
|
-
end
|
260
|
-
|
261
355
|
repo = Wrapbox::ConfigRepository.new.tap { |r| r.load_yaml(options[:config]) }
|
262
356
|
config = repo.get(options[:config_name])
|
263
357
|
config.runner = :ecs
|
@@ -270,9 +364,10 @@ module Wrapbox
|
|
270
364
|
timeout: options[:timeout],
|
271
365
|
launch_timeout: options[:launch_timeout],
|
272
366
|
launch_retry: options[:launch_retry],
|
367
|
+
execution_retry: options[:execution_retry],
|
273
368
|
max_retry_interval: options[:max_retry_interval]
|
274
369
|
}.reject { |_, v| v.nil? }
|
275
|
-
runner.run_cmd(
|
370
|
+
runner.run_cmd(args, environments: environments, **run_options)
|
276
371
|
end
|
277
372
|
end
|
278
373
|
end
|
data/lib/wrapbox/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wrapbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- joker1007
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|