kitchen-kerberos 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/.ruby-version +1 -0
- data/Gemfile +1 -0
- data/README.md +7 -1
- data/lib/kitchen/kerberos/version.rb +1 -1
- data/lib/kitchen/verifier/inspec_kerberos.rb +271 -0
- data/lib/train/transports/kerberos.rb +1 -1
- metadata +16 -14
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46d7b5e77112c6a28aa1b106204a372ec6e1278f
|
|
4
|
+
data.tar.gz: 071bf8bde44d418f3c09b43104d69b7d49edd18d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b006dc6a0f793f1f66dfce6ce99f56d712d3fff0e3b86c4a8722a61e2a1deb2cdc19609ce84f88ef9aabd7b3615d900a0fbec34f2b2849a6ac64f05bc356fa8
|
|
7
|
+
data.tar.gz: a40f669447d8334255e0c05a4b863e30582aa16abc554eb20eece878bae6d277be52ab0c205d853a52624eaf7b393e24af167c083191075ef1b36ab899ce3e8f
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.3.3
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
If you want test kitchen to login via kerberos tickets, this is what you need.
|
|
3
3
|
|
|
4
4
|
This is a subclass of the ssh kitchen transport to allow for gssapi-with-mic login for NET::SSH
|
|
5
|
+
|
|
6
|
+
Additionally, if you use kitchen-inspec this provides a kerberos transport for train and a new kitchen verifier for inspec_kerberos.
|
|
7
|
+
|
|
8
|
+
If you only use inspec you can utilize the train transport as well without the use of kitchen.
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
Add this line to your application's Gemfile:
|
|
@@ -24,6 +28,9 @@ Or install it yourself as:
|
|
|
24
28
|
transport:
|
|
25
29
|
name: kerberos
|
|
26
30
|
user: <%= ENV['USER'] %> # if you don't want the root user
|
|
31
|
+
|
|
32
|
+
verifier:
|
|
33
|
+
name: inspec_kerberos
|
|
27
34
|
```
|
|
28
35
|
|
|
29
36
|
## Development
|
|
@@ -40,4 +47,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/logicm
|
|
|
40
47
|
## License
|
|
41
48
|
|
|
42
49
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
43
|
-
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# Author:: Fletcher Nichol (<fnichol@chef.io>)
|
|
4
|
+
# Author:: Christoph Hartmann (<chartmann@chef.io>)
|
|
5
|
+
#
|
|
6
|
+
# Copyright (C) 2015, Chef Software Inc.
|
|
7
|
+
#
|
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
# you may not use this file except in compliance with the License.
|
|
10
|
+
# You may obtain a copy of the License at
|
|
11
|
+
#
|
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
#
|
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
# See the License for the specific language governing permissions and
|
|
18
|
+
# limitations under the License.
|
|
19
|
+
require "kitchen/transport/ssh"
|
|
20
|
+
require "kitchen/transport/winrm"
|
|
21
|
+
require "kitchen/verifier/inspec_version"
|
|
22
|
+
require "kitchen/verifier/base"
|
|
23
|
+
require 'kitchen/transport/kerberos'
|
|
24
|
+
require "uri"
|
|
25
|
+
require "pathname"
|
|
26
|
+
|
|
27
|
+
module Kitchen
|
|
28
|
+
module Verifier
|
|
29
|
+
# InSpec verifier for Kitchen.
|
|
30
|
+
#
|
|
31
|
+
# @author Fletcher Nichol <fnichol@chef.io>
|
|
32
|
+
class InspecKerberos < Kitchen::Verifier::Base # rubocop:disable Metrics/ClassLength
|
|
33
|
+
kitchen_verifier_api_version 1
|
|
34
|
+
plugin_version Kitchen::Verifier::INSPEC_VERSION
|
|
35
|
+
|
|
36
|
+
default_config :inspec_tests, []
|
|
37
|
+
|
|
38
|
+
# A lifecycle method that should be invoked when the object is about
|
|
39
|
+
# ready to be used. A reference to an Instance is required as
|
|
40
|
+
# configuration dependant data may be access through an Instance. This
|
|
41
|
+
# also acts as a hook point where the object may wish to perform other
|
|
42
|
+
# last minute checks, validations, or configuration expansions.
|
|
43
|
+
#
|
|
44
|
+
# @param instance [Instance] an associated instance
|
|
45
|
+
# @return [self] itself, for use in chaining
|
|
46
|
+
# @raise [ClientError] if instance parameter is nil
|
|
47
|
+
def finalize_config!(instance)
|
|
48
|
+
super
|
|
49
|
+
|
|
50
|
+
# We want to switch kitchen-inspec to look for its tests in
|
|
51
|
+
# `cookbook_dir/test/recipes` instead of `cookbook_dir/test/integration`
|
|
52
|
+
# Unfortunately there is no way to read `test_base_path` from the
|
|
53
|
+
# .kitchen.yml, it can only be provided on the CLI.
|
|
54
|
+
# See https://github.com/test-kitchen/test-kitchen/issues/1077
|
|
55
|
+
inspec_test_dir = File.join(config[:kitchen_root], "test", "recipes")
|
|
56
|
+
if File.directory?(inspec_test_dir)
|
|
57
|
+
config[:test_base_path] = inspec_test_dir
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# (see Base#call)
|
|
64
|
+
def call(state)
|
|
65
|
+
logger.debug("Initialize InSpec")
|
|
66
|
+
|
|
67
|
+
# gather connection options
|
|
68
|
+
opts = runner_options(instance.transport, state, instance.platform.name, instance.suite.name)
|
|
69
|
+
|
|
70
|
+
# add attributes
|
|
71
|
+
opts[:attrs] = config[:attrs]
|
|
72
|
+
opts[:attributes] = Hashie.stringify_keys config[:attributes] unless config[:attributes].nil?
|
|
73
|
+
|
|
74
|
+
# setup logger
|
|
75
|
+
::Inspec::Log.init(STDERR)
|
|
76
|
+
::Inspec::Log.level = Kitchen::Util.from_logger_level(logger.level)
|
|
77
|
+
|
|
78
|
+
# initialize runner
|
|
79
|
+
runner = ::Inspec::Runner.new(opts)
|
|
80
|
+
|
|
81
|
+
# add each profile to runner
|
|
82
|
+
tests = collect_tests
|
|
83
|
+
profile_ctx = nil
|
|
84
|
+
tests.each do |target|
|
|
85
|
+
profile_ctx = runner.add_target(target, opts)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
profile_ctx ||= []
|
|
89
|
+
profile_ctx.each do |profile|
|
|
90
|
+
logger.info("Loaded #{profile.name} ")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
exit_code = runner.run
|
|
94
|
+
return if exit_code == 0
|
|
95
|
+
raise ActionFailed, "Inspec Runner returns #{exit_code}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# (see Base#load_needed_dependencies!)
|
|
103
|
+
def load_needed_dependencies!
|
|
104
|
+
require "inspec"
|
|
105
|
+
# TODO: this should be easier. I would expect to load a single class here
|
|
106
|
+
# load supermarket plugin, this is part of the inspec gem
|
|
107
|
+
require "bundles/inspec-supermarket/api"
|
|
108
|
+
require "bundles/inspec-supermarket/target"
|
|
109
|
+
|
|
110
|
+
# load the compliance plugin
|
|
111
|
+
require "bundles/inspec-compliance/configuration"
|
|
112
|
+
require "bundles/inspec-compliance/support"
|
|
113
|
+
require "bundles/inspec-compliance/http"
|
|
114
|
+
require "bundles/inspec-compliance/api"
|
|
115
|
+
require "bundles/inspec-compliance/target"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Returns an Array of test suite filenames for the related suite currently
|
|
119
|
+
# residing on the local workstation. Any special provisioner-specific
|
|
120
|
+
# directories (such as a Chef roles/ directory) are excluded.
|
|
121
|
+
#
|
|
122
|
+
# we support the base directories
|
|
123
|
+
# - test/integration
|
|
124
|
+
# - test/integration/inspec (prefered if used with other test environments)
|
|
125
|
+
#
|
|
126
|
+
# we do not filter for specific directories, this is core of inspec
|
|
127
|
+
#
|
|
128
|
+
# @return [Array<String>] array of suite directories
|
|
129
|
+
# @api private
|
|
130
|
+
def local_suite_files
|
|
131
|
+
base = File.join(config[:test_base_path], config[:suite_name])
|
|
132
|
+
legacy_mode = false
|
|
133
|
+
# check for testing frameworks, we may need to add more
|
|
134
|
+
%w{inspec serverspec bats pester rspec cucumber minitest bash}.each do |fw|
|
|
135
|
+
if Pathname.new(File.join(base, fw)).exist?
|
|
136
|
+
logger.info("Detected alternative framework tests for `#{fw}`")
|
|
137
|
+
legacy_mode = true
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
base = File.join(base, "inspec") if legacy_mode
|
|
142
|
+
|
|
143
|
+
# only return the directory if it exists
|
|
144
|
+
Pathname.new(base).exist? ? [{ :path => base }] : []
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Takes config[:inspec_tests] and modifies any value with a key of :path by adding the full path
|
|
148
|
+
# @return [Array] array of modified hashes
|
|
149
|
+
# @api private
|
|
150
|
+
def resolve_config_inspec_tests
|
|
151
|
+
config[:inspec_tests].map do |test_hash|
|
|
152
|
+
if test_hash.is_a? Hash
|
|
153
|
+
test_hash = { :path => config[:kitchen_root] + "/" + test_hash[:path] } if test_hash.has_key?(:path)
|
|
154
|
+
test_hash
|
|
155
|
+
else
|
|
156
|
+
test_hash # if it's not a hash, just return it as is
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returns an array of test profiles
|
|
162
|
+
# @return [Array<String>] array of suite directories or remote urls
|
|
163
|
+
# @api private
|
|
164
|
+
def collect_tests
|
|
165
|
+
# get local tests and get run list of profiles
|
|
166
|
+
(local_suite_files + resolve_config_inspec_tests).compact.uniq
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Returns a configuration Hash that can be passed to a `Inspec::Runner`.
|
|
170
|
+
#
|
|
171
|
+
# @return [Hash] a configuration hash of string-based keys
|
|
172
|
+
# @api private
|
|
173
|
+
def runner_options(transport, state = {}, platform = nil, suite = nil) # rubocop:disable Metrics/AbcSize
|
|
174
|
+
transport_data = transport.diagnose.merge(state)
|
|
175
|
+
if defined?(Kitchen::Transport::Kerberos) && transport.is_a?(Kitchen::Transport::Kerberos)
|
|
176
|
+
runner_options_for_ssh(transport_data, 'kerberos')
|
|
177
|
+
elsif transport.is_a?(Kitchen::Transport::Ssh)
|
|
178
|
+
runner_options_for_ssh(transport_data, 'ssh')
|
|
179
|
+
elsif transport.is_a?(Kitchen::Transport::Winrm)
|
|
180
|
+
runner_options_for_winrm(transport_data)
|
|
181
|
+
# optional transport which is not in core test-kitchen
|
|
182
|
+
elsif defined?(Kitchen::Transport::Dokken) && transport.is_a?(Kitchen::Transport::Dokken)
|
|
183
|
+
runner_options_for_docker(transport_data)
|
|
184
|
+
else
|
|
185
|
+
raise Kitchen::UserError, "Verifier #{name} does not support the #{transport.name} Transport"
|
|
186
|
+
end.tap do |runner_options|
|
|
187
|
+
# default color to true to match InSpec behavior
|
|
188
|
+
runner_options["color"] = (config[:color].nil? ? true : config[:color])
|
|
189
|
+
runner_options["format"] = config[:format] unless config[:format].nil?
|
|
190
|
+
runner_options["output"] = config[:output] % { platform: platform, suite: suite } unless config[:output].nil?
|
|
191
|
+
runner_options["profiles_path"] = config[:profiles_path] unless config[:profiles_path].nil?
|
|
192
|
+
runner_options[:controls] = config[:controls]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Returns a configuration Hash that can be passed to a `Inspec::Runner`.
|
|
197
|
+
#
|
|
198
|
+
# @return [Hash] a configuration hash of string-based keys
|
|
199
|
+
# @api private
|
|
200
|
+
def runner_options_for_ssh(config_data, backend = 'ssh')
|
|
201
|
+
kitchen = instance.transport.send(:connection_options, config_data).dup
|
|
202
|
+
opts = {
|
|
203
|
+
"backend" => backend,
|
|
204
|
+
"logger" => logger,
|
|
205
|
+
# pass-in sudo config from kitchen verifier
|
|
206
|
+
"sudo" => config[:sudo],
|
|
207
|
+
"sudo_command" => config[:sudo_command],
|
|
208
|
+
"sudo_options" => config[:sudo_options],
|
|
209
|
+
"host" => config[:host] || kitchen[:hostname],
|
|
210
|
+
"port" => config[:port] || kitchen[:port],
|
|
211
|
+
"user" => kitchen[:username],
|
|
212
|
+
"keepalive" => kitchen[:keepalive],
|
|
213
|
+
"keepalive_interval" => kitchen[:keepalive_interval],
|
|
214
|
+
"connection_timeout" => kitchen[:timeout],
|
|
215
|
+
"connection_retries" => kitchen[:connection_retries],
|
|
216
|
+
"connection_retry_sleep" => kitchen[:connection_retry_sleep],
|
|
217
|
+
"max_wait_until_ready" => kitchen[:max_wait_until_ready],
|
|
218
|
+
"compression" => kitchen[:compression],
|
|
219
|
+
"compression_level" => kitchen[:compression_level],
|
|
220
|
+
"keys_only" => true,
|
|
221
|
+
}
|
|
222
|
+
opts["key_files"] = kitchen[:keys] unless kitchen[:keys].nil?
|
|
223
|
+
opts["password"] = kitchen[:password] unless kitchen[:password].nil?
|
|
224
|
+
opts
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Returns a configuration Hash that can be passed to a `Inspec::Runner`.
|
|
228
|
+
#
|
|
229
|
+
# @return [Hash] a configuration hash of string-based keys
|
|
230
|
+
# @api private
|
|
231
|
+
def runner_options_for_winrm(config_data)
|
|
232
|
+
kitchen = instance.transport.send(:connection_options, config_data).dup
|
|
233
|
+
opts = {
|
|
234
|
+
"backend" => "winrm",
|
|
235
|
+
"logger" => logger,
|
|
236
|
+
"host" => config[:host] || URI(kitchen[:endpoint]).hostname,
|
|
237
|
+
"port" => config[:port] || URI(kitchen[:endpoint]).port,
|
|
238
|
+
"user" => kitchen[:user],
|
|
239
|
+
"password" => kitchen[:password] || kitchen[:pass],
|
|
240
|
+
"connection_retries" => kitchen[:connection_retries],
|
|
241
|
+
"connection_retry_sleep" => kitchen[:connection_retry_sleep],
|
|
242
|
+
"max_wait_until_ready" => kitchen[:max_wait_until_ready],
|
|
243
|
+
}
|
|
244
|
+
opts
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Returns a configuration Hash that can be passed to a `Inspec::Runner`.
|
|
248
|
+
#
|
|
249
|
+
# @return [Hash] a configuration hash of string-based keys
|
|
250
|
+
# @api private
|
|
251
|
+
def runner_options_for_docker(config_data)
|
|
252
|
+
kitchen = instance.transport.send(:connection_options, config_data).dup
|
|
253
|
+
#
|
|
254
|
+
# Note: kitchen-dokken uses two containers the
|
|
255
|
+
# - config_data[:data_container][:Id] : (hosts chef-client)
|
|
256
|
+
# - config_data[:runner_container][:Id] : (the kitchen-container)
|
|
257
|
+
opts = {
|
|
258
|
+
"backend" => "docker",
|
|
259
|
+
"logger" => logger,
|
|
260
|
+
"host" => config_data[:runner_container][:Id],
|
|
261
|
+
"connection_timeout" => kitchen[:timeout],
|
|
262
|
+
"connection_retries" => kitchen[:connection_retries],
|
|
263
|
+
"connection_retry_sleep" => kitchen[:connection_retry_sleep],
|
|
264
|
+
"max_wait_until_ready" => kitchen[:max_wait_until_ready],
|
|
265
|
+
}
|
|
266
|
+
logger.debug "Connect to Container: #{opts['host']}"
|
|
267
|
+
opts
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
metadata
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-kerberos
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Corey Osman
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-06-
|
|
11
|
+
date: 2017-06-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: net-ssh-krb
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - ~>
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: 0.4.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - ~>
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: 0.4.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: bundler
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - ~>
|
|
31
|
+
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '1.14'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - ~>
|
|
38
|
+
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '1.14'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rake
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - ~>
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
47
|
version: '10.0'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - ~>
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '10.0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: minitest
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - ~>
|
|
59
|
+
- - "~>"
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
61
|
version: '5.0'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
|
-
- - ~>
|
|
66
|
+
- - "~>"
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '5.0'
|
|
69
69
|
description: Adds a kerberos ticket authentication to test-kitchen transport
|
|
@@ -73,8 +73,9 @@ executables: []
|
|
|
73
73
|
extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
|
75
75
|
files:
|
|
76
|
-
- .gitignore
|
|
77
|
-
- .
|
|
76
|
+
- ".gitignore"
|
|
77
|
+
- ".ruby-version"
|
|
78
|
+
- ".travis.yml"
|
|
78
79
|
- CODE_OF_CONDUCT.md
|
|
79
80
|
- Gemfile
|
|
80
81
|
- LICENSE.txt
|
|
@@ -83,6 +84,7 @@ files:
|
|
|
83
84
|
- kitchen-kerberos.gemspec
|
|
84
85
|
- lib/kitchen/kerberos/version.rb
|
|
85
86
|
- lib/kitchen/transport/kerberos.rb
|
|
87
|
+
- lib/kitchen/verifier/inspec_kerberos.rb
|
|
86
88
|
- lib/train/transports/kerberos.rb
|
|
87
89
|
homepage: https://github.com/nwops/kitchen-kerberos
|
|
88
90
|
licenses:
|
|
@@ -94,12 +96,12 @@ require_paths:
|
|
|
94
96
|
- lib
|
|
95
97
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
98
|
requirements:
|
|
97
|
-
- -
|
|
99
|
+
- - ">="
|
|
98
100
|
- !ruby/object:Gem::Version
|
|
99
101
|
version: '0'
|
|
100
102
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
103
|
requirements:
|
|
102
|
-
- -
|
|
104
|
+
- - ">="
|
|
103
105
|
- !ruby/object:Gem::Version
|
|
104
106
|
version: '0'
|
|
105
107
|
requirements: []
|