kitchen-dokken 2.11.2 → 2.12.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/lib/kitchen/driver/dokken.rb +131 -119
- data/lib/kitchen/driver/dokken_version.rb +1 -1
- data/lib/kitchen/helpers.rb +48 -45
- data/lib/kitchen/provisioner/dokken.rb +19 -16
- data/lib/kitchen/transport/dokken.rb +33 -32
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e00103fefe2a49fd2630337f2a8d0bb0932484b770704b69c1fb31d9ddd0b42
|
|
4
|
+
data.tar.gz: 0acea78f249845712be9d1f97094f190369a1e15707ac559de27be5fc6c29e31
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d84bd393b040ef4bc771a5d5c4448dff1860a9244d0818341f06c834e8133cbd9a77b0b7a4f697c8873be24b567083556f4feb47a57723782fe6575ad7b2a1a
|
|
7
|
+
data.tar.gz: b628d6bce7009efa94713c5c6032ec81e3258686c84f22ebd430e1710a1817cfaf3cb82420a8109e21f1506ff0e661c08da1e6fb2d5baedd6adbb216a0dedaa8
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
# See the License for the specific language governing permissions and
|
|
16
16
|
# limitations under the License.
|
|
17
17
|
|
|
18
|
-
require
|
|
19
|
-
require
|
|
20
|
-
require
|
|
21
|
-
require
|
|
22
|
-
require
|
|
23
|
-
require_relative
|
|
18
|
+
require "digest" unless defined?(Digest)
|
|
19
|
+
require "kitchen"
|
|
20
|
+
require "tmpdir" unless defined?(Dir.mktmpdir)
|
|
21
|
+
require "docker"
|
|
22
|
+
require "lockfile"
|
|
23
|
+
require_relative "../helpers"
|
|
24
24
|
|
|
25
25
|
include Dokken::Helpers
|
|
26
26
|
|
|
@@ -37,31 +37,32 @@ module Kitchen
|
|
|
37
37
|
default_config :binds, []
|
|
38
38
|
default_config :cap_add, nil
|
|
39
39
|
default_config :cap_drop, nil
|
|
40
|
-
default_config :chef_image,
|
|
41
|
-
default_config :chef_version,
|
|
42
|
-
default_config :data_image,
|
|
40
|
+
default_config :chef_image, "chef/chef"
|
|
41
|
+
default_config :chef_version, "latest"
|
|
42
|
+
default_config :data_image, "dokken/kitchen-cache:latest"
|
|
43
43
|
default_config :dns, nil
|
|
44
44
|
default_config :dns_search, nil
|
|
45
|
+
default_config :docker_host_url, default_docker_host
|
|
45
46
|
default_config :docker_info, docker_info
|
|
47
|
+
default_config :docker_registry, nil
|
|
46
48
|
default_config :entrypoint, nil
|
|
47
49
|
default_config :env, nil
|
|
48
|
-
default_config :
|
|
49
|
-
default_config :docker_host_url, default_docker_host
|
|
50
|
-
default_config :hostname, 'dokken'
|
|
50
|
+
default_config :hostname, "dokken"
|
|
51
51
|
default_config :image_prefix, nil
|
|
52
52
|
default_config :links, nil
|
|
53
|
-
default_config :
|
|
53
|
+
default_config :memory_limit, 0
|
|
54
|
+
default_config :network_mode, "dokken"
|
|
54
55
|
default_config :pid_one_command, 'sh -c "trap exit 0 SIGTERM; while :; do sleep 1; done"'
|
|
56
|
+
default_config :ports, nil
|
|
55
57
|
default_config :privileged, false
|
|
58
|
+
default_config :pull_chef_image, true
|
|
59
|
+
default_config :pull_platform_image, true
|
|
56
60
|
default_config :read_timeout, 3600
|
|
57
61
|
default_config :security_opt, nil
|
|
58
62
|
default_config :tmpfs, {}
|
|
63
|
+
default_config :userns_host, false
|
|
59
64
|
default_config :volumes, nil
|
|
60
65
|
default_config :write_timeout, 3600
|
|
61
|
-
default_config :userns_host, false
|
|
62
|
-
default_config :pull_platform_image, true
|
|
63
|
-
default_config :pull_chef_image, true
|
|
64
|
-
default_config :memory_limit, 0
|
|
65
66
|
|
|
66
67
|
# (see Base#create)
|
|
67
68
|
def create(state)
|
|
@@ -126,37 +127,38 @@ module Kitchen
|
|
|
126
127
|
|
|
127
128
|
def delete_work_image
|
|
128
129
|
return unless ::Docker::Image.exist?(work_image, {}, docker_connection)
|
|
130
|
+
|
|
129
131
|
with_retries { @work_image = ::Docker::Image.get(work_image, {}, docker_connection) }
|
|
130
132
|
|
|
131
133
|
with_retries do
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
|
|
135
|
+
with_retries { @work_image.remove(force: true) }
|
|
136
|
+
rescue ::Docker::Error::ConflictError
|
|
137
|
+
debug "driver - #{work_image} cannot be removed"
|
|
138
|
+
|
|
137
139
|
end
|
|
138
140
|
end
|
|
139
141
|
|
|
140
142
|
def build_work_image(state)
|
|
141
|
-
info(
|
|
143
|
+
info("Building work image..")
|
|
142
144
|
return if ::Docker::Image.exist?(work_image, {}, docker_connection)
|
|
143
145
|
|
|
144
146
|
begin
|
|
145
147
|
@intermediate_image = ::Docker::Image.build(
|
|
146
148
|
work_image_dockerfile,
|
|
147
149
|
{
|
|
148
|
-
|
|
150
|
+
"t" => work_image,
|
|
149
151
|
},
|
|
150
152
|
docker_connection
|
|
151
153
|
)
|
|
152
154
|
# credit to https://github.com/someara/kitchen-dokken/issues/95#issue-224697526
|
|
153
155
|
rescue Docker::Error::UnexpectedResponseError => e
|
|
154
|
-
msg =
|
|
155
|
-
msg += JSON.parse(e.to_s.split("\r\n").last)[
|
|
156
|
-
msg +=
|
|
157
|
-
msg +=
|
|
158
|
-
msg +=
|
|
159
|
-
msg +=
|
|
156
|
+
msg = "work_image build failed: "
|
|
157
|
+
msg += JSON.parse(e.to_s.split("\r\n").last)["error"].to_s
|
|
158
|
+
msg += ". The common scenarios are incorrect intermediate "
|
|
159
|
+
msg += "instructions such as not including `-y` on an `apt-get` "
|
|
160
|
+
msg += "or similar. The other common scenario is a transient "
|
|
161
|
+
msg += "error such as an unresponsive mirror."
|
|
160
162
|
raise msg
|
|
161
163
|
# fallback rescue above should catch most of the errors
|
|
162
164
|
rescue => e
|
|
@@ -219,6 +221,7 @@ module Kitchen
|
|
|
219
221
|
|
|
220
222
|
def work_image
|
|
221
223
|
return "#{image_prefix}/#{instance_name}" unless image_prefix.nil?
|
|
224
|
+
|
|
222
225
|
instance_name
|
|
223
226
|
end
|
|
224
227
|
|
|
@@ -244,7 +247,7 @@ module Kitchen
|
|
|
244
247
|
v
|
|
245
248
|
else
|
|
246
249
|
Array(v).each_with_object({}) do |y, h|
|
|
247
|
-
name, opts = y.split(
|
|
250
|
+
name, opts = y.split(":", 2)
|
|
248
251
|
h[name.to_s] = opts.to_s
|
|
249
252
|
end
|
|
250
253
|
end
|
|
@@ -260,12 +263,13 @@ module Kitchen
|
|
|
260
263
|
b = []
|
|
261
264
|
v = Array(v).to_a # in case v.is_A?(Chef::Node::ImmutableArray)
|
|
262
265
|
v.delete_if do |x|
|
|
263
|
-
parts = x.split(
|
|
266
|
+
parts = x.split(":")
|
|
264
267
|
b << x if parts.length > 1
|
|
265
268
|
end
|
|
266
269
|
b = nil if b.empty?
|
|
267
270
|
config[:binds].push(b) unless config[:binds].include?(b) || b.nil?
|
|
268
271
|
return PartialHash.new if v.empty?
|
|
272
|
+
|
|
269
273
|
v.each_with_object(PartialHash.new) { |volume, h| h[volume] = {} }
|
|
270
274
|
end
|
|
271
275
|
end
|
|
@@ -281,41 +285,41 @@ module Kitchen
|
|
|
281
285
|
debug "driver - starting #{runner_container_name}"
|
|
282
286
|
|
|
283
287
|
config = {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
288
|
+
"name" => runner_container_name,
|
|
289
|
+
"Cmd" => Shellwords.shellwords(self[:pid_one_command]),
|
|
290
|
+
"Image" => "#{repo(work_image)}:#{tag(work_image)}",
|
|
291
|
+
"Hostname" => self[:hostname],
|
|
292
|
+
"Env" => self[:env],
|
|
293
|
+
"ExposedPorts" => exposed_ports,
|
|
294
|
+
"Volumes" => dokken_volumes,
|
|
295
|
+
"HostConfig" => {
|
|
296
|
+
"Privileged" => self[:privileged],
|
|
297
|
+
"VolumesFrom" => dokken_volumes_from,
|
|
298
|
+
"Binds" => dokken_binds,
|
|
299
|
+
"Dns" => self[:dns],
|
|
300
|
+
"DnsSearch" => self[:dns_search],
|
|
301
|
+
"Links" => Array(self[:links]),
|
|
302
|
+
"CapAdd" => Array(self[:cap_add]),
|
|
303
|
+
"CapDrop" => Array(self[:cap_drop]),
|
|
304
|
+
"SecurityOpt" => Array(self[:security_opt]),
|
|
305
|
+
"NetworkMode" => self[:network_mode],
|
|
306
|
+
"PortBindings" => port_bindings,
|
|
307
|
+
"Tmpfs" => dokken_tmpfs,
|
|
308
|
+
"Memory" => self[:memory_limit],
|
|
305
309
|
},
|
|
306
|
-
|
|
307
|
-
|
|
310
|
+
"NetworkingConfig" => {
|
|
311
|
+
"EndpointsConfig" => {
|
|
308
312
|
self[:network_mode] => {
|
|
309
|
-
|
|
313
|
+
"Aliases" => Array(self[:hostname]),
|
|
310
314
|
},
|
|
311
315
|
},
|
|
312
316
|
},
|
|
313
317
|
}
|
|
314
318
|
unless self[:entrypoint].to_s.empty?
|
|
315
|
-
config[
|
|
319
|
+
config["Entrypoint"] = self[:entrypoint]
|
|
316
320
|
end
|
|
317
321
|
if self[:userns_host]
|
|
318
|
-
config[
|
|
322
|
+
config["HostConfig"]["UsernsMode"] = "host"
|
|
319
323
|
end
|
|
320
324
|
runner_container = run_container(config)
|
|
321
325
|
state[:runner_container] = runner_container.json
|
|
@@ -324,17 +328,17 @@ module Kitchen
|
|
|
324
328
|
def start_data_container(state)
|
|
325
329
|
debug "driver - creating #{data_container_name}"
|
|
326
330
|
config = {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
"name" => data_container_name,
|
|
332
|
+
"Image" => "#{repo(data_image)}:#{tag(data_image)}",
|
|
333
|
+
"HostConfig" => {
|
|
334
|
+
"PortBindings" => port_bindings,
|
|
335
|
+
"PublishAllPorts" => true,
|
|
336
|
+
"NetworkMode" => "bridge",
|
|
333
337
|
},
|
|
334
|
-
|
|
335
|
-
|
|
338
|
+
"NetworkingConfig" => {
|
|
339
|
+
"EndpointsConfig" => {
|
|
336
340
|
self[:network_mode] => {
|
|
337
|
-
|
|
341
|
+
"Aliases" => Array(self[:hostname]),
|
|
338
342
|
},
|
|
339
343
|
},
|
|
340
344
|
},
|
|
@@ -347,10 +351,10 @@ module Kitchen
|
|
|
347
351
|
lockfile = Lockfile.new "#{home_dir}/.dokken-network.lock"
|
|
348
352
|
begin
|
|
349
353
|
lockfile.lock
|
|
350
|
-
with_retries { ::Docker::Network.get(
|
|
354
|
+
with_retries { ::Docker::Network.get("dokken", {}, docker_connection) }
|
|
351
355
|
rescue
|
|
352
356
|
begin
|
|
353
|
-
with_retries { ::Docker::Network.create(
|
|
357
|
+
with_retries { ::Docker::Network.create("dokken", {}) }
|
|
354
358
|
rescue ::Docker::Error => e
|
|
355
359
|
debug "driver - error :#{e}:"
|
|
356
360
|
end
|
|
@@ -360,8 +364,8 @@ module Kitchen
|
|
|
360
364
|
end
|
|
361
365
|
|
|
362
366
|
def make_data_image
|
|
363
|
-
debug
|
|
364
|
-
create_data_image
|
|
367
|
+
debug "driver - calling create_data_image"
|
|
368
|
+
create_data_image(config[:docker_registry])
|
|
365
369
|
end
|
|
366
370
|
|
|
367
371
|
def create_chef_container(state)
|
|
@@ -372,28 +376,27 @@ module Kitchen
|
|
|
372
376
|
# TEMPORARY FIX - docker-api 2.0.0 has a buggy Docker::Container.get - use .all instead
|
|
373
377
|
# https://github.com/swipely/docker-api/issues/566
|
|
374
378
|
# ::Docker::Container.get(chef_container_name, {}, docker_connection)
|
|
375
|
-
found = ::Docker::Container.all({all: true}, docker_connection).select { |c| c.info["Names"].include?("/#{chef_container_name}") }
|
|
379
|
+
found = ::Docker::Container.all({ all: true }, docker_connection).select { |c| c.info["Names"].include?("/#{chef_container_name}") }
|
|
376
380
|
raise ::Docker::Error::NotFoundError.new(chef_container_name) if found.empty?
|
|
381
|
+
|
|
377
382
|
debug "Chef container already exists, continuing"
|
|
378
383
|
}
|
|
379
384
|
rescue ::Docker::Error::NotFoundError
|
|
380
385
|
debug "Chef container does not exist, creating a new Chef container"
|
|
381
386
|
with_retries do
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
raise "driver - #{chef_container_name} failed to create #{e}"
|
|
396
|
-
end
|
|
387
|
+
debug "driver - creating volume container #{chef_container_name} from #{chef_image}"
|
|
388
|
+
config = {
|
|
389
|
+
"name" => chef_container_name,
|
|
390
|
+
"Cmd" => "true",
|
|
391
|
+
"Image" => "#{repo(chef_image)}:#{tag(chef_image)}",
|
|
392
|
+
"HostConfig" => {
|
|
393
|
+
"NetworkMode" => self[:network_mode],
|
|
394
|
+
},
|
|
395
|
+
}
|
|
396
|
+
chef_container = create_container(config)
|
|
397
|
+
state[:chef_container] = chef_container.json
|
|
398
|
+
rescue ::Docker::Error => e
|
|
399
|
+
raise "driver - #{chef_container_name} failed to create #{e}"
|
|
397
400
|
end
|
|
398
401
|
ensure
|
|
399
402
|
lockfile.unlock
|
|
@@ -424,13 +427,13 @@ module Kitchen
|
|
|
424
427
|
end
|
|
425
428
|
|
|
426
429
|
def parse_image_name(image)
|
|
427
|
-
parts = image.split(
|
|
430
|
+
parts = image.split(":")
|
|
428
431
|
|
|
429
432
|
if parts.size > 2
|
|
430
433
|
tag = parts.pop
|
|
431
|
-
repo = parts.join(
|
|
434
|
+
repo = parts.join(":")
|
|
432
435
|
else
|
|
433
|
-
tag = parts[1] ||
|
|
436
|
+
tag = parts[1] || "latest"
|
|
434
437
|
repo = parts[0]
|
|
435
438
|
end
|
|
436
439
|
|
|
@@ -442,27 +445,25 @@ module Kitchen
|
|
|
442
445
|
end
|
|
443
446
|
|
|
444
447
|
def create_container(args)
|
|
445
|
-
with_retries { @container = ::Docker::Container.get(args[
|
|
448
|
+
with_retries { @container = ::Docker::Container.get(args["name"], {}, docker_connection) }
|
|
446
449
|
rescue
|
|
447
450
|
with_retries do
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
with_retries { @container = ::Docker::Container.get(args['name'], {}, docker_connection) }
|
|
460
|
-
end
|
|
461
|
-
end
|
|
462
|
-
rescue ::Docker::Error => e
|
|
463
|
-
debug "driver - error :#{e}:"
|
|
464
|
-
raise "driver - failed to create_container #{args['name']}"
|
|
451
|
+
args["Env"] = [] if args["Env"].nil?
|
|
452
|
+
args["Env"] << "TEST_KITCHEN=1"
|
|
453
|
+
args["Env"] << "CI=#{ENV["CI"]}" if ENV.include? "CI"
|
|
454
|
+
info "Creating container #{args["name"]}"
|
|
455
|
+
debug "driver - create_container args #{args}"
|
|
456
|
+
with_retries do
|
|
457
|
+
|
|
458
|
+
@container = ::Docker::Container.create(args.clone, docker_connection)
|
|
459
|
+
rescue ::Docker::Error::ConflictError
|
|
460
|
+
debug "driver - rescue ConflictError: #{args["name"]}"
|
|
461
|
+
with_retries { @container = ::Docker::Container.get(args["name"], {}, docker_connection) }
|
|
465
462
|
end
|
|
463
|
+
rescue ::Docker::Error => e
|
|
464
|
+
debug "driver - error :#{e}:"
|
|
465
|
+
raise "driver - failed to create_container #{args["name"]}"
|
|
466
|
+
|
|
466
467
|
end
|
|
467
468
|
end
|
|
468
469
|
|
|
@@ -470,14 +471,14 @@ module Kitchen
|
|
|
470
471
|
create_container(args)
|
|
471
472
|
with_retries do
|
|
472
473
|
@container.start
|
|
473
|
-
@container = ::Docker::Container.get(args[
|
|
474
|
-
wait_running_state(args[
|
|
474
|
+
@container = ::Docker::Container.get(args["name"], {}, docker_connection)
|
|
475
|
+
wait_running_state(args["name"], true)
|
|
475
476
|
end
|
|
476
477
|
@container
|
|
477
478
|
end
|
|
478
479
|
|
|
479
480
|
def container_state
|
|
480
|
-
@container ? @container.info[
|
|
481
|
+
@container ? @container.info["State"] : {}
|
|
481
482
|
end
|
|
482
483
|
|
|
483
484
|
def stop_container(name)
|
|
@@ -501,9 +502,10 @@ module Kitchen
|
|
|
501
502
|
@container = ::Docker::Container.get(name, {}, docker_connection)
|
|
502
503
|
i = 0
|
|
503
504
|
tries = 20
|
|
504
|
-
until container_state[
|
|
505
|
+
until container_state["Running"] == v || container_state["FinishedAt"] != "0001-01-01T00:00:00Z"
|
|
505
506
|
i += 1
|
|
506
507
|
break if i == tries
|
|
508
|
+
|
|
507
509
|
sleep 0.1
|
|
508
510
|
@container = ::Docker::Container.get(name, {}, docker_connection)
|
|
509
511
|
end
|
|
@@ -522,7 +524,8 @@ module Kitchen
|
|
|
522
524
|
end
|
|
523
525
|
|
|
524
526
|
def chef_version
|
|
525
|
-
return
|
|
527
|
+
return "latest" if config[:chef_version] == "stable"
|
|
528
|
+
|
|
526
529
|
config[:chef_version]
|
|
527
530
|
end
|
|
528
531
|
|
|
@@ -539,27 +542,36 @@ module Kitchen
|
|
|
539
542
|
end
|
|
540
543
|
|
|
541
544
|
def platform_image_from_name
|
|
542
|
-
platform, release = instance.platform.name.split(
|
|
543
|
-
release ? [platform, release].join(
|
|
545
|
+
platform, release = instance.platform.name.split("-")
|
|
546
|
+
release ? [platform, release].join(":") : platform
|
|
544
547
|
end
|
|
545
548
|
|
|
546
549
|
def pull_if_missing(image)
|
|
547
550
|
return if ::Docker::Image.exist?("#{repo(image)}:#{tag(image)}", {}, docker_connection)
|
|
551
|
+
|
|
548
552
|
pull_image image
|
|
549
553
|
end
|
|
550
554
|
|
|
551
555
|
# https://github.com/docker/docker/blob/4fcb9ac40ce33c4d6e08d5669af6be5e076e2574/registry/auth.go#L231
|
|
552
556
|
def parse_registry_host(val)
|
|
553
|
-
val.sub(%r{https?://},
|
|
557
|
+
val.sub(%r{https?://}, "").split("/").first
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def image_path(image)
|
|
561
|
+
fqimage = "#{repo(image)}:#{tag(image)}"
|
|
562
|
+
if config[:docker_registry]
|
|
563
|
+
fqimage = "#{config[:docker_registry]}/#{fqimage}"
|
|
564
|
+
end
|
|
565
|
+
fqimage
|
|
554
566
|
end
|
|
555
567
|
|
|
556
568
|
def pull_image(image)
|
|
557
569
|
with_retries do
|
|
558
|
-
if Docker::Image.exist?(
|
|
559
|
-
original_image = Docker::Image.get(
|
|
570
|
+
if Docker::Image.exist?(image_path(image), {}, docker_connection)
|
|
571
|
+
original_image = Docker::Image.get(image_path(image), {}, docker_connection)
|
|
560
572
|
end
|
|
561
573
|
|
|
562
|
-
new_image = Docker::Image.create({
|
|
574
|
+
new_image = Docker::Image.create({ "fromImage" => "#{repo(image)}:#{tag(image)}" }, docker_connection)
|
|
563
575
|
|
|
564
576
|
!(original_image && original_image.id.start_with?(new_image.id))
|
|
565
577
|
end
|
data/lib/kitchen/helpers.rb
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
module Dokken
|
|
2
2
|
module Helpers
|
|
3
3
|
# https://stackoverflow.com/questions/517219/ruby-see-if-a-port-is-open
|
|
4
|
-
require
|
|
5
|
-
require
|
|
4
|
+
require "socket" unless defined?(Socket)
|
|
5
|
+
require "timeout" unless defined?(Timeout)
|
|
6
6
|
|
|
7
7
|
def port_open?(ip, port)
|
|
8
8
|
begin
|
|
9
9
|
Timeout.timeout(1) do
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return false
|
|
16
|
-
end
|
|
10
|
+
s = TCPSocket.new(ip, port)
|
|
11
|
+
s.close
|
|
12
|
+
return true
|
|
13
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ENETDOWN
|
|
14
|
+
return false
|
|
17
15
|
end
|
|
18
16
|
rescue Timeout::Error
|
|
19
17
|
end
|
|
20
|
-
|
|
21
18
|
false
|
|
22
19
|
end
|
|
23
20
|
|
|
@@ -59,9 +56,13 @@ X8N2N9ZNnORJqK374yGj1jWUU66mQhPvn49QpG8P2HEoh2RQjNvyHA==
|
|
|
59
56
|
EOF
|
|
60
57
|
end
|
|
61
58
|
|
|
62
|
-
def data_dockerfile
|
|
59
|
+
def data_dockerfile(registry)
|
|
60
|
+
from = "centos:7"
|
|
61
|
+
if registry
|
|
62
|
+
from = "#{registry}/#{from}"
|
|
63
|
+
end
|
|
63
64
|
<<-EOF
|
|
64
|
-
FROM
|
|
65
|
+
FROM #{from}
|
|
65
66
|
MAINTAINER Sean OMeara \"sean@sean.io\"
|
|
66
67
|
ENV LANG en_US.UTF-8
|
|
67
68
|
|
|
@@ -86,32 +87,32 @@ VOLUME /opt/verifier
|
|
|
86
87
|
EOF
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
def create_data_image
|
|
90
|
+
def create_data_image(registry)
|
|
90
91
|
return if ::Docker::Image.exist?(data_image)
|
|
91
92
|
|
|
92
93
|
tmpdir = Dir.tmpdir
|
|
93
94
|
FileUtils.mkdir_p "#{tmpdir}/dokken"
|
|
94
|
-
File.write("#{tmpdir}/dokken/Dockerfile", data_dockerfile)
|
|
95
|
+
File.write("#{tmpdir}/dokken/Dockerfile", data_dockerfile(registry))
|
|
95
96
|
File.write("#{tmpdir}/dokken/authorized_keys", insecure_ssh_public_key)
|
|
96
97
|
|
|
97
98
|
i = ::Docker::Image.build_from_dir(
|
|
98
99
|
"#{tmpdir}/dokken",
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
"nocache" => true,
|
|
101
|
+
"rm" => true
|
|
101
102
|
)
|
|
102
|
-
i.tag(
|
|
103
|
+
i.tag("repo" => repo(data_image), "tag" => tag(data_image), "force" => true)
|
|
103
104
|
end
|
|
104
105
|
|
|
105
106
|
def default_docker_host
|
|
106
|
-
if ENV[
|
|
107
|
-
ENV[
|
|
108
|
-
elsif File.exist?(
|
|
109
|
-
|
|
107
|
+
if ENV["DOCKER_HOST"]
|
|
108
|
+
ENV["DOCKER_HOST"]
|
|
109
|
+
elsif File.exist?("/var/run/docker.sock")
|
|
110
|
+
"unix:///var/run/docker.sock"
|
|
110
111
|
# TODO: Docker for Windows also operates over a named pipe at
|
|
111
112
|
# //./pipe/docker_engine that can be used if named pipe support is
|
|
112
113
|
# added to the docker-api gem.
|
|
113
114
|
else
|
|
114
|
-
|
|
115
|
+
"tcp://127.0.0.1:2375"
|
|
115
116
|
end
|
|
116
117
|
end
|
|
117
118
|
|
|
@@ -152,7 +153,8 @@ VOLUME /opt/verifier
|
|
|
152
153
|
# refs:
|
|
153
154
|
# https://github.com/docker/machine/issues/1814
|
|
154
155
|
# https://github.com/docker/toolbox/issues/607
|
|
155
|
-
return Dir.home.sub
|
|
156
|
+
return Dir.home.sub "C:/Users", "/c/Users" if Dir.home =~ /^C:/ && remote_docker_host?
|
|
157
|
+
|
|
156
158
|
Dir.home
|
|
157
159
|
end
|
|
158
160
|
|
|
@@ -185,7 +187,7 @@ VOLUME /opt/verifier
|
|
|
185
187
|
x = Array(v).map { |a| parse_port(a) }
|
|
186
188
|
x.flatten!
|
|
187
189
|
x.each_with_object({}) do |y, h|
|
|
188
|
-
h[y[
|
|
190
|
+
h[y["container_port"]] = {}
|
|
189
191
|
end
|
|
190
192
|
end
|
|
191
193
|
end
|
|
@@ -198,52 +200,53 @@ VOLUME /opt/verifier
|
|
|
198
200
|
x = Array(v).map { |a| parse_port(a) }
|
|
199
201
|
x.flatten!
|
|
200
202
|
x.each_with_object({}) do |y, h|
|
|
201
|
-
h[y[
|
|
202
|
-
h[y[
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
h[y["container_port"]] = [] unless h[y["container_port"]]
|
|
204
|
+
h[y["container_port"]] << {
|
|
205
|
+
"HostIp" => y["host_ip"],
|
|
206
|
+
"HostPort" => y["host_port"],
|
|
205
207
|
}
|
|
206
208
|
end
|
|
207
209
|
end
|
|
208
210
|
end
|
|
209
211
|
|
|
210
212
|
def parse_port(v)
|
|
211
|
-
parts = v.split(
|
|
213
|
+
parts = v.split(":")
|
|
212
214
|
case parts.length
|
|
213
215
|
when 3
|
|
214
216
|
host_ip = parts[0]
|
|
215
217
|
host_port = parts[1]
|
|
216
218
|
container_port = parts[2]
|
|
217
219
|
when 2
|
|
218
|
-
host_ip =
|
|
220
|
+
host_ip = "0.0.0.0"
|
|
219
221
|
host_port = parts[0]
|
|
220
222
|
container_port = parts[1]
|
|
221
223
|
when 1
|
|
222
|
-
host_ip =
|
|
223
|
-
host_port =
|
|
224
|
+
host_ip = ""
|
|
225
|
+
host_port = ""
|
|
224
226
|
container_port = parts[0]
|
|
225
227
|
end
|
|
226
|
-
port_range, protocol = container_port.split(
|
|
227
|
-
if port_range.include?(
|
|
228
|
-
port_range = container_port.split(
|
|
228
|
+
port_range, protocol = container_port.split("/")
|
|
229
|
+
if port_range.include?("-")
|
|
230
|
+
port_range = container_port.split("-")
|
|
229
231
|
port_range.map!(&:to_i)
|
|
230
232
|
Chef::Log.fatal("FATAL: Invalid port range! #{container_port}") if port_range[0] > port_range[1]
|
|
231
233
|
port_range = (port_range[0]..port_range[1]).to_a
|
|
232
234
|
end
|
|
233
235
|
# qualify the port-binding protocol even when it is implicitly tcp #427.
|
|
234
|
-
protocol =
|
|
236
|
+
protocol = "tcp" if protocol.nil?
|
|
235
237
|
Array(port_range).map do |port|
|
|
236
238
|
{
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
"host_ip" => host_ip,
|
|
240
|
+
"host_port" => host_port,
|
|
241
|
+
"container_port" => "#{port}/#{protocol}",
|
|
240
242
|
}
|
|
241
243
|
end
|
|
242
244
|
end
|
|
243
245
|
|
|
244
246
|
def remote_docker_host?
|
|
245
|
-
return false if config[:docker_info][
|
|
247
|
+
return false if config[:docker_info]["OperatingSystem"].include?("Boot2Docker")
|
|
246
248
|
return true if /^tcp:/.match?(config[:docker_host_url])
|
|
249
|
+
|
|
247
250
|
false
|
|
248
251
|
end
|
|
249
252
|
|
|
@@ -252,13 +255,13 @@ VOLUME /opt/verifier
|
|
|
252
255
|
end
|
|
253
256
|
|
|
254
257
|
def sandbox_dirs
|
|
255
|
-
Dir.glob(File.join(sandbox_path,
|
|
258
|
+
Dir.glob(File.join(sandbox_path, "*"))
|
|
256
259
|
end
|
|
257
260
|
|
|
258
261
|
def create_sandbox
|
|
259
262
|
info("Creating kitchen sandbox in #{sandbox_path}")
|
|
260
263
|
unless ::Dir.exist?(sandbox_path)
|
|
261
|
-
FileUtils.mkdir_p(sandbox_path, :
|
|
264
|
+
FileUtils.mkdir_p(sandbox_path, mode: 0o755)
|
|
262
265
|
end
|
|
263
266
|
end
|
|
264
267
|
end
|
|
@@ -269,7 +272,7 @@ module Kitchen
|
|
|
269
272
|
class Base
|
|
270
273
|
def create_sandbox
|
|
271
274
|
info("Creating kitchen sandbox in #{sandbox_path}")
|
|
272
|
-
FileUtils.mkdir_p(sandbox_path, :
|
|
275
|
+
FileUtils.mkdir_p(sandbox_path, mode: 0o755)
|
|
273
276
|
end
|
|
274
277
|
|
|
275
278
|
# this MUST be named 'sandbox_path' because ruby.
|
|
@@ -291,7 +294,7 @@ module Kitchen
|
|
|
291
294
|
def create_sandbox
|
|
292
295
|
info("Creating kitchen sandbox in #{sandbox_path}")
|
|
293
296
|
unless ::Dir.exist?(sandbox_path)
|
|
294
|
-
FileUtils.mkdir_p(sandbox_path, :
|
|
297
|
+
FileUtils.mkdir_p(sandbox_path, mode: 0o755)
|
|
295
298
|
end
|
|
296
299
|
end
|
|
297
300
|
|
|
@@ -313,7 +316,7 @@ module Kitchen
|
|
|
313
316
|
conn.execute(init_command)
|
|
314
317
|
info("Transferring files to #{instance.to_str}")
|
|
315
318
|
conn.upload(sandbox_dirs, config[:root_path])
|
|
316
|
-
debug(
|
|
319
|
+
debug("Transfer complete")
|
|
317
320
|
end
|
|
318
321
|
|
|
319
322
|
conn.execute(prepare_command)
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
# See the License for the specific language governing permissions and
|
|
16
16
|
# limitations under the License.
|
|
17
17
|
|
|
18
|
-
require
|
|
19
|
-
require
|
|
20
|
-
require_relative
|
|
18
|
+
require "kitchen"
|
|
19
|
+
require "kitchen/provisioner/chef_zero"
|
|
20
|
+
require_relative "../helpers"
|
|
21
21
|
|
|
22
22
|
include Dokken::Helpers
|
|
23
23
|
|
|
@@ -29,11 +29,12 @@ module Kitchen
|
|
|
29
29
|
|
|
30
30
|
plugin_version Kitchen::VERSION
|
|
31
31
|
|
|
32
|
-
default_config :root_path,
|
|
33
|
-
default_config :chef_binary,
|
|
34
|
-
default_config :chef_options,
|
|
35
|
-
default_config :chef_log_level,
|
|
36
|
-
default_config :chef_output_format,
|
|
32
|
+
default_config :root_path, "/opt/kitchen"
|
|
33
|
+
default_config :chef_binary, "/opt/chef/bin/chef-client"
|
|
34
|
+
default_config :chef_options, " -z"
|
|
35
|
+
default_config :chef_log_level, "warn"
|
|
36
|
+
default_config :chef_output_format, "doc"
|
|
37
|
+
default_config :profile_ruby, false
|
|
37
38
|
default_config :docker_info, docker_info
|
|
38
39
|
default_config :docker_host_url, default_docker_host
|
|
39
40
|
|
|
@@ -45,7 +46,7 @@ module Kitchen
|
|
|
45
46
|
# driver and set it here. If we remove this, users will set their chef_version
|
|
46
47
|
# to 14 in the driver and still get prompted for license acceptance because
|
|
47
48
|
# the ChefZero provisioner defaults product_version to 'latest'.
|
|
48
|
-
default_config :product_name,
|
|
49
|
+
default_config :product_name, "chef"
|
|
49
50
|
default_config :product_version do |provisioner|
|
|
50
51
|
driver = provisioner.instance.driver
|
|
51
52
|
driver[:chef_version]
|
|
@@ -73,15 +74,16 @@ module Kitchen
|
|
|
73
74
|
rescue Kitchen::Transport::TransportFailed => ex
|
|
74
75
|
raise ActionFailed, ex.message
|
|
75
76
|
ensure
|
|
76
|
-
return unless config[:clean_dokken_sandbox]
|
|
77
|
+
return unless config[:clean_dokken_sandbox] # rubocop: disable Lint/EnsureReturn
|
|
78
|
+
|
|
77
79
|
cleanup_dokken_sandbox
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
def validate_config
|
|
81
83
|
# check if we have an space for the user provided options
|
|
82
84
|
# or add it if not to avoid issues
|
|
83
|
-
unless config[:chef_options].start_with?
|
|
84
|
-
config[:chef_options].prepend(
|
|
85
|
+
unless config[:chef_options].start_with? " "
|
|
86
|
+
config[:chef_options].prepend(" ")
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
# strip spaces from all other options
|
|
@@ -91,8 +93,8 @@ module Kitchen
|
|
|
91
93
|
|
|
92
94
|
# if the user wants to be funny and pass empty strings
|
|
93
95
|
# just use the defaults
|
|
94
|
-
config[:chef_log_level] =
|
|
95
|
-
config[:chef_output_format] =
|
|
96
|
+
config[:chef_log_level] = "warn" if config[:chef_log_level].empty?
|
|
97
|
+
config[:chef_output_format] = "doc" if config[:chef_output_format].empty?
|
|
96
98
|
end
|
|
97
99
|
|
|
98
100
|
private
|
|
@@ -104,8 +106,9 @@ module Kitchen
|
|
|
104
106
|
cmd << config[:chef_options].to_s
|
|
105
107
|
cmd << " -l #{config[:chef_log_level]}"
|
|
106
108
|
cmd << " -F #{config[:chef_output_format]}"
|
|
107
|
-
cmd <<
|
|
108
|
-
cmd <<
|
|
109
|
+
cmd << " -c /opt/kitchen/client.rb"
|
|
110
|
+
cmd << " -j /opt/kitchen/dna.json"
|
|
111
|
+
cmd << "--profile-ruby" if config[:profile_ruby]
|
|
109
112
|
|
|
110
113
|
chef_cmd(cmd)
|
|
111
114
|
end
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
# See the License for the specific language governing permissions and
|
|
16
16
|
# limitations under the License.
|
|
17
17
|
|
|
18
|
-
require
|
|
19
|
-
require
|
|
20
|
-
require
|
|
21
|
-
require
|
|
22
|
-
require_relative
|
|
18
|
+
require "kitchen"
|
|
19
|
+
require "net/scp"
|
|
20
|
+
require "tmpdir" unless defined?(Dir.mktmpdir)
|
|
21
|
+
require "digest/sha1" unless defined?(Digest::SHA1)
|
|
22
|
+
require_relative "../helpers"
|
|
23
23
|
|
|
24
24
|
include Dokken::Helpers
|
|
25
25
|
|
|
@@ -44,7 +44,7 @@ module Kitchen
|
|
|
44
44
|
default_config :read_timeout, 3600
|
|
45
45
|
default_config :write_timeout, 3600
|
|
46
46
|
default_config :host_ip_override do |transport|
|
|
47
|
-
transport.docker_for_mac_or_win? ?
|
|
47
|
+
transport.docker_for_mac_or_win? ? "localhost" : false
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# (see Base#connection)
|
|
@@ -69,7 +69,7 @@ module Kitchen
|
|
|
69
69
|
|
|
70
70
|
with_retries { @runner = ::Docker::Container.get(instance_name, {}, docker_connection) }
|
|
71
71
|
with_retries do
|
|
72
|
-
o = @runner.exec(Shellwords.shellwords(command), wait: options[:timeout],
|
|
72
|
+
o = @runner.exec(Shellwords.shellwords(command), wait: options[:timeout], "e" => { "TERM" => "xterm" }) { |_stream, chunk| print chunk.to_s }
|
|
73
73
|
@exit_code = o[2]
|
|
74
74
|
end
|
|
75
75
|
|
|
@@ -83,9 +83,9 @@ module Kitchen
|
|
|
83
83
|
ssh_port = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostPort]
|
|
84
84
|
|
|
85
85
|
elsif /unix:/.match?(options[:docker_host_url])
|
|
86
|
-
if options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostIp] ==
|
|
86
|
+
if options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostIp] == "0.0.0.0"
|
|
87
87
|
ssh_ip = options[:data_container][:NetworkSettings][:IPAddress]
|
|
88
|
-
ssh_port =
|
|
88
|
+
ssh_port = "22"
|
|
89
89
|
else
|
|
90
90
|
# we should read the proper mapped ip, since this allows us to upload the files
|
|
91
91
|
ssh_ip = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostIp]
|
|
@@ -96,12 +96,12 @@ module Kitchen
|
|
|
96
96
|
name = options[:data_container][:Name]
|
|
97
97
|
|
|
98
98
|
# DOCKER_HOST
|
|
99
|
-
docker_host_url_ip = options[:docker_host_url].split(
|
|
99
|
+
docker_host_url_ip = options[:docker_host_url].split("tcp://")[1].split(":")[0]
|
|
100
100
|
|
|
101
101
|
# mapped IP of data container
|
|
102
102
|
candidate_ip = ::Docker::Container.all.find do |x|
|
|
103
|
-
x.info[
|
|
104
|
-
end.info[
|
|
103
|
+
x.info["Names"][0].eql?(name)
|
|
104
|
+
end.info["NetworkSettings"]["Networks"]["dokken"]["IPAddress"]
|
|
105
105
|
|
|
106
106
|
# mapped port
|
|
107
107
|
candidate_ssh_port = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostPort]
|
|
@@ -114,22 +114,22 @@ module Kitchen
|
|
|
114
114
|
ssh_ip = candidate_ip
|
|
115
115
|
ssh_port = candidate_ssh_port
|
|
116
116
|
|
|
117
|
-
elsif port_open?(candidate_ip,
|
|
117
|
+
elsif port_open?(candidate_ip, "22")
|
|
118
118
|
ssh_ip = candidate_ip
|
|
119
|
-
ssh_port =
|
|
119
|
+
ssh_port = "22"
|
|
120
120
|
debug "candidate_ip - #{candidate_ip}/22 open"
|
|
121
121
|
else
|
|
122
122
|
ssh_ip = docker_host_url_ip
|
|
123
123
|
ssh_port = candidate_ssh_port
|
|
124
124
|
end
|
|
125
125
|
else
|
|
126
|
-
raise Kitchen::UserError,
|
|
126
|
+
raise Kitchen::UserError, "docker_host_url must be tcp:// or unix://"
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
debug "ssh_ip : #{ssh_ip}"
|
|
130
130
|
debug "ssh_port : #{ssh_port}"
|
|
131
131
|
|
|
132
|
-
tmpdir = Dir.tmpdir +
|
|
132
|
+
tmpdir = Dir.tmpdir + "/dokken/"
|
|
133
133
|
FileUtils.mkdir_p tmpdir.to_s, mode: 0o777
|
|
134
134
|
tmpdir += Process.uid.to_s
|
|
135
135
|
FileUtils.mkdir_p tmpdir.to_s
|
|
@@ -137,26 +137,26 @@ module Kitchen
|
|
|
137
137
|
FileUtils.chmod(0o600, "#{tmpdir}/id_rsa")
|
|
138
138
|
|
|
139
139
|
begin
|
|
140
|
-
rsync_cmd =
|
|
141
|
-
rsync_cmd <<
|
|
142
|
-
rsync_cmd <<
|
|
140
|
+
rsync_cmd = "/usr/bin/rsync -a -e"
|
|
141
|
+
rsync_cmd << " '"
|
|
142
|
+
rsync_cmd << "ssh -2"
|
|
143
143
|
rsync_cmd << " -i #{tmpdir}/id_rsa"
|
|
144
|
-
rsync_cmd <<
|
|
145
|
-
rsync_cmd <<
|
|
146
|
-
rsync_cmd <<
|
|
147
|
-
rsync_cmd <<
|
|
148
|
-
rsync_cmd <<
|
|
149
|
-
rsync_cmd <<
|
|
144
|
+
rsync_cmd << " -o CheckHostIP=no"
|
|
145
|
+
rsync_cmd << " -o Compression=no"
|
|
146
|
+
rsync_cmd << " -o PasswordAuthentication=no"
|
|
147
|
+
rsync_cmd << " -o StrictHostKeyChecking=no"
|
|
148
|
+
rsync_cmd << " -o UserKnownHostsFile=/dev/null"
|
|
149
|
+
rsync_cmd << " -o LogLevel=ERROR"
|
|
150
150
|
rsync_cmd << " -p #{ssh_port}"
|
|
151
|
-
rsync_cmd << '
|
|
152
|
-
rsync_cmd << " #{locals.join(
|
|
151
|
+
rsync_cmd << "'"
|
|
152
|
+
rsync_cmd << " #{locals.join(" ")} root@#{ssh_ip}:#{remote}"
|
|
153
153
|
debug "rsync_cmd :#{rsync_cmd}:"
|
|
154
154
|
`#{rsync_cmd}`
|
|
155
155
|
rescue Errno::ENOENT
|
|
156
|
-
debug
|
|
156
|
+
debug "Rsync is not installed. Falling back to SCP."
|
|
157
157
|
locals.each do |local|
|
|
158
158
|
Net::SCP.upload!(ssh_ip,
|
|
159
|
-
|
|
159
|
+
"root",
|
|
160
160
|
local,
|
|
161
161
|
remote,
|
|
162
162
|
recursive: true,
|
|
@@ -169,8 +169,8 @@ module Kitchen
|
|
|
169
169
|
@runner = options[:instance_name].to_s
|
|
170
170
|
cols = `tput cols`
|
|
171
171
|
lines = `tput lines`
|
|
172
|
-
args = [
|
|
173
|
-
LoginCommand.new(
|
|
172
|
+
args = ["exec", "-e", "COLUMNS=#{cols}", "-e", "LINES=#{lines}", "-it", @runner, "/bin/bash", "-login", "-i"]
|
|
173
|
+
LoginCommand.new("docker", args)
|
|
174
174
|
end
|
|
175
175
|
|
|
176
176
|
private
|
|
@@ -181,6 +181,7 @@ module Kitchen
|
|
|
181
181
|
|
|
182
182
|
def work_image
|
|
183
183
|
return "#{image_prefix}/#{instance_name}" unless image_prefix.nil?
|
|
184
|
+
|
|
184
185
|
instance_name
|
|
185
186
|
end
|
|
186
187
|
|
|
@@ -208,7 +209,7 @@ module Kitchen
|
|
|
208
209
|
#
|
|
209
210
|
# @return [TrueClass,FalseClass]
|
|
210
211
|
def docker_for_mac_or_win?
|
|
211
|
-
::Docker.info(::Docker::Connection.new(config[:docker_host_url], {}))[
|
|
212
|
+
::Docker.info(::Docker::Connection.new(config[:docker_host_url], {}))["Name"] == "moby"
|
|
212
213
|
rescue
|
|
213
214
|
false
|
|
214
215
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-dokken
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sean OMeara
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-02-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: docker-api
|
|
@@ -90,7 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
90
90
|
requirements:
|
|
91
91
|
- - ">="
|
|
92
92
|
- !ruby/object:Gem::Version
|
|
93
|
-
version: '
|
|
93
|
+
version: '2.5'
|
|
94
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
95
|
requirements:
|
|
96
96
|
- - ">="
|