chef-metal-fogsphere 0.1.0.alpha.1 → 0.1.0.alpha.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f498e6888cc6338d35fc9e418440d9f06ed49765
4
+ data.tar.gz: 7121c432739e9f8caa9b5a76922f90b0999285a6
5
+ SHA512:
6
+ metadata.gz: e2c422291302a8b16d079714a889c9b446b1b5d474934d02c183fac483aec0e69404ae14d2f6eff7e593b9074285c03304816f7ee14d9941f453baafe40e771d
7
+ data.tar.gz: 5a434cadf0d45da3eea5fcb985323363d87d94bd59abd94cf29766d2689c389817792e4c3c5c65761a1e1aaa729fd997bb60559cfee67c145a2ae7627de4124d
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "chef-metal-fogsphere"
7
+ gem.version = '0.1.0.alpha.2'
8
+ gem.license = 'Apache 2.0'
9
+ gem.authors = ["Matt Wrock"]
10
+ gem.email = ["matt@centurylinkcloud.com"]
11
+ gem.description = "A fork of Chef-Metal-fog for vsphere"
12
+ gem.summary = gem.description
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = []
16
+ gem.require_paths = ["lib"]
17
+
18
+ gem.add_dependency 'rbvmomi', '~> 1.5.1'
19
+ gem.add_dependency 'chef-metal'
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'chef_metal'
2
+
3
+ class Chef
4
+ module DSL
5
+ module Recipe
6
+ def with_vsphere_provisioner(options = {}, &block)
7
+ run_context.chef_metal.with_provisioner(ChefMetalVsphere::VsphereProvisioner.new({ :provider => 'vsphere' }.merge(options)), &block)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,419 @@
1
+ require 'chef_metal/provisioner'
2
+ require 'chef_metal/aws_credentials'
3
+ require 'chef_metal/openstack_credentials'
4
+ require 'chef_metal/machine/windows_machine'
5
+ require 'chef_metal/machine/unix_machine'
6
+ require 'chef_metal/convergence_strategy/install_msi'
7
+ require 'chef_metal/convergence_strategy/install_cached'
8
+ require 'chef_metal/transport/ssh'
9
+ require 'fog'
10
+ require 'fog/core'
11
+ require 'fog/compute'
12
+ require 'fog/aws'
13
+
14
+ module ChefMetalVsphere
15
+ # Provisions machines in vagrant.
16
+ class VsphereProvisioner < ChefMetal::Provisioner
17
+ include Chef::Mixin::ShellOut
18
+
19
+ DEFAULT_OPTIONS = {
20
+ :create_timeout => 600,
21
+ :start_timeout => 600,
22
+ :ssh_timeout => 20
23
+ } unless defined? DEFAULT_OPTIONS
24
+
25
+ def self.inflate(node_json)
26
+ url = node_json['normal']['provisioner_output']['provisioner_url']
27
+ scheme, provider, id = url.split(':', 3)
28
+ VsphereProvisioner.new({ :provider => provider }, id)
29
+ end
30
+
31
+ # Create a new fog provisioner.
32
+ #
33
+ # ## Parameters
34
+ # compute_options - hash of options to be passed to Fog::Compute.new
35
+ # Special options:
36
+ # - :base_bootstrap_options is merged with bootstrap_options in acquire_machine
37
+ # to present the full set of bootstrap options. Write down any bootstrap_options
38
+ # you intend to apply universally here.
39
+ # - :aws_credentials is an AWS CSV file (created with Download Credentials)
40
+ # containing your aws key information. If you do not specify aws_access_key_id
41
+ # and aws_secret_access_key explicitly, the first line from this file
42
+ # will be used. You may pass a Cheffish::AWSCredentials object.
43
+ # - :create_timeout - the time to wait for the instance to boot to ssh (defaults to 600)
44
+ # - :start_timeout - the time to wait for the instance to start (defaults to 600)
45
+ # - :ssh_timeout - the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
46
+ # id - the ID in the provisioner_url (fog:PROVIDER:ID)
47
+ def initialize(compute_options, id=nil)
48
+ @compute_options = compute_options
49
+ @base_bootstrap_options = compute_options.delete(:base_bootstrap_options) || {}
50
+ @base_bootstrap_options_for = {}
51
+ end
52
+
53
+ attr_reader :compute_options
54
+
55
+ def current_base_bootstrap_options
56
+ result = @base_bootstrap_options.dup
57
+ result
58
+ end
59
+
60
+ # Inflate a provisioner from node_json information; we don't want to force the
61
+ # driver to figure out what the provisioner really needs, since it varies
62
+ # from provisioner to provisioner.
63
+ #
64
+ # ## Parameters
65
+ # node_json - node_json to inflate the provisioner for
66
+ #
67
+ # returns a FogProvisioner
68
+ # TODO: def self.inflate(node_json)
69
+ # right now, not implemented, will raise error from base class until overridden
70
+
71
+ # Acquire a machine, generally by provisioning it. Returns a Machine
72
+ # object pointing at the machine, allowing useful actions like setup,
73
+ # converge, execute, file and directory. The Machine object will have a
74
+ # "node_json" property which must be saved to the server (if it is any
75
+ # different from the original node_json object).
76
+ #
77
+ # ## Parameters
78
+ # action_handler - the action_handler object that is calling this method; this
79
+ # is generally a action_handler, but could be anything that can support the
80
+ # ChefMetal::ActionHandler interface (i.e., in the case of the test
81
+ # kitchen metal driver for acquiring and destroying VMs; see the base
82
+ # class for what needs providing).
83
+ # node_json - node_json object (deserialized json) representing this machine. If
84
+ # the node_json has a provisioner_options hash in it, these will be used
85
+ # instead of options provided by the provisioner. TODO compare and
86
+ # fail if different?
87
+ # node_json will have node_json['normal']['provisioner_options'] in it with any options.
88
+ # It is a hash with this format:
89
+ #
90
+ # -- provisioner_url: fog:<relevant_fog_options>
91
+ # -- bootstrap_options: hash of options to pass to compute.servers.create
92
+ # -- is_windows: true if windows. TODO detect this from ami?
93
+ # -- create_timeout - the time to wait for the instance to boot to ssh (defaults to 600)
94
+ # -- start_timeout - the time to wait for the instance to start (defaults to 600)
95
+ # -- ssh_timeout - the time to wait for ssh to be available if the instance is detected as up (defaults to 20)
96
+ #
97
+ # Example bootstrap_options for ec2:
98
+ # 'bootstrap_options' => {
99
+ # 'image_id' =>'ami-311f2b45',
100
+ # 'flavor_id' =>'t1.micro',
101
+ # 'key_name' => 'key-pair-name'
102
+ # }
103
+ #
104
+ # node_json['normal']['provisioner_output'] will be populated with information
105
+ # about the created machine. For vagrant, it is a hash with this
106
+ # format:
107
+ #
108
+ # -- provisioner_url: fog:<relevant_fog_options>
109
+ # -- server_id: the ID of the server so it can be found again
110
+ #
111
+ def acquire_machine(action_handler, node_json)
112
+ # Set up the modified node_json data
113
+ provisioner_output = node_json['normal']['provisioner_output'] || {
114
+ 'provisioner_url' => provisioner_url,
115
+ 'provisioner_version' => '0.0.1'
116
+ }
117
+
118
+ if provisioner_output['provisioner_url'] != provisioner_url
119
+ raise "Switching a machine's provider from #{provisioner_output['provisioner_url']} to #{provisioner_url} for is not currently supported! Use machine :destroy and then re-create the machine on the new provisioner."
120
+ end
121
+
122
+ node_json['normal']['provisioner_output'] = provisioner_output
123
+
124
+ if provisioner_output['server_id']
125
+
126
+ # If the server already exists, make sure it is up
127
+
128
+ # TODO verify that the server info matches the specification (ami, etc.)\
129
+ server = server_for(node_json)
130
+ if !server
131
+ Chef::Log.warn "Machine #{node_json['name']} (#{provisioner_output['server_id']} on #{provisioner_url}) is not associated with the ec2 account. Recreating ..."
132
+ need_to_create = true
133
+ elsif server.respond_to?(:state) && %w(terminated archive).include?(server.state) # Can't come back from that
134
+ Chef::Log.warn "Machine #{node_json['name']} (#{server.id} on #{provisioner_url}) is terminated. Recreating ..."
135
+ need_to_create = true
136
+ else
137
+ need_to_create = false
138
+ if !server.ready?
139
+ action_handler.perform_action "start machine #{node_json['name']} (#{server.id} on #{provisioner_url})" do
140
+ server.start
141
+ end
142
+ action_handler.perform_action "wait for machine #{node_json['name']} (#{server.id} on #{provisioner_url}) to be ready" do
143
+ wait_until_ready(node_json, server, option_for(node_json, :start_timeout))
144
+ end
145
+ else
146
+ wait_until_ready(node_json, server, option_for(node_json, :ssh_timeout))
147
+ end
148
+ end
149
+ else
150
+ need_to_create = true
151
+ end
152
+
153
+ if need_to_create
154
+ # If the server does not exist, create it
155
+ bootstrap_options = bootstrap_options_for(action_handler.new_resource, node_json)
156
+ bootstrap_options = bootstrap_options.merge(:name => node_json['name'])
157
+
158
+ start_time = Time.now
159
+ timeout = option_for(node_json, :create_timeout)
160
+
161
+ description = [ "create machine #{node_json['name']} on #{provisioner_url}" ]
162
+ bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
163
+ server = nil
164
+ action_handler.perform_action description do
165
+ clone_results = compute.vm_clone(convert_to_strings(bootstrap_options))
166
+ server = compute.servers.get(clone_results['new_vm']['id'])
167
+ if(bootstrap_options[:additional_disk_size_gb] && bootstrap_options[:additional_disk_size_gb].is_a?(Integer) && bootstrap_options[:additional_disk_size_gb] > 0)
168
+ volume_options = {
169
+ 'server_id' => server.id,
170
+ 'datastore' => bootstrap_options[:datastore],
171
+ 'size_gb' => bootstrap_options[:additional_disk_size_gb]
172
+ }
173
+ volume = compute.volumes.create(volume_options)
174
+ end
175
+ provisioner_output['server_id'] = server.id
176
+ # Save quickly in case something goes wrong
177
+ save_node(action_handler, node_json, action_handler.new_resource.chef_server)
178
+ end
179
+
180
+ if server
181
+ @@ip_pool_lock = Mutex.new
182
+ # Re-retrieve the server in a more malleable form and wait for it to be ready
183
+ server = compute.servers.get(server.id)
184
+ action_handler.perform_action "machine #{node_json['name']} created as #{server.id} on #{provisioner_url}" do
185
+ end
186
+ # Wait for the machine to come up
187
+ Chef::Log.info "waiting for machine to come up"
188
+ server.wait_for(timeout - (Time.now - start_time)) { tools_state != 'toolsNotRunning' }
189
+
190
+ if has_static_ip(node_json)
191
+ Chef::Log.info "waiting for customizations to complete"
192
+ sleep(30)
193
+ Chef::Log.info "rebooting..."
194
+ server.reboot
195
+ end
196
+
197
+ # wait for ssh to start listening
198
+ transport = nil
199
+ _self = self
200
+ action_handler.perform_action "wait for machine #{node_json['name']} to boot" do
201
+ server.wait_for(timeout - (Time.now - start_time)) do
202
+ if tools_state != 'toolsNotRunning'
203
+ transport ||= _self.transport_for(server, node_json)
204
+ begin
205
+ transport.execute('pwd')
206
+ Chef::Log.info "ssh succeeded"
207
+ true
208
+ rescue ChefMetal::Transport::SSH::InitialConnectTimeout, Errno::ECONNREFUSED, Net::SSH::Disconnect, Errno::EHOSTUNREACH => err
209
+ Chef::Log.info "ssh failed1: #{err}"
210
+ false
211
+ rescue Exception => e
212
+ Chef::Log.info "ssh failed2: #{e}"
213
+ true
214
+ end
215
+ else
216
+ false
217
+ end
218
+ end
219
+ end
220
+
221
+ # If there is some other error, we just wait patiently for SSH
222
+ Chef::Log.info "entering next block"
223
+ begin
224
+ server.wait_for(option_for(node_json, :ssh_timeout)) { transport.available? }
225
+ rescue Fog::Errors::TimeoutError
226
+ # Sometimes (on EC2) the machine comes up but gets stuck or has
227
+ # some other problem. If this is the case, we restart the server
228
+ # to unstick it. Reboot covers a multitude of sins.
229
+ Chef::Log.warn "Machine #{node_json['name']} (#{server.id} on #{provisioner_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
230
+ action_handler.perform_action "reboot machine #{node_json['name']} to try to unstick it" do
231
+ server.reboot
232
+ end
233
+ Chef::Log.info "rebooted"
234
+ action_handler.perform_action "wait for machine #{node_json['name']} to be ready after reboot" do
235
+ wait_until_ready(node_json, server, option_for(node_json, :start_timeout))
236
+ end
237
+ Chef::Log.info "reported ready"
238
+ end
239
+ end
240
+ end
241
+
242
+ # Create machine object for callers to use
243
+ machine_for(node_json, server)
244
+ end
245
+
246
+ def has_static_ip(node_json)
247
+ bootstrap_options = node_json['normal']['provisioner_options']['bootstrap_options']
248
+ if bootstrap_options.has_key?('customization_spec')
249
+ bootstrap_options = bootstrap_options['customization_spec']
250
+ if bootstrap_options.has_key?('ipsettings')
251
+ bootstrap_options = bootstrap_options['ipsettings']
252
+ if bootstrap_options.has_key?('ip')
253
+ return true
254
+ end
255
+ end
256
+ end
257
+ false
258
+ end
259
+
260
+ def convert_to_strings(objay)
261
+ if objay.kind_of?(Array)
262
+ objay.map { |v| convert_to_strings(v) }
263
+ elsif objay.kind_of?(Hash)
264
+ Hash[objay.map { |(k, v)| [k.to_s, convert_to_strings(v)] }]
265
+ else
266
+ objay
267
+ end
268
+ end
269
+
270
+ # Connect to machine without acquiring it
271
+ def connect_to_machine(node_json)
272
+ machine_for(node_json)
273
+ end
274
+
275
+ def delete_machine(action_handler, node_json)
276
+ if node_json['normal']['provisioner_output'] && node_json['normal']['provisioner_output']['server_id']
277
+ server = compute.servers.get(node_json['normal']['provisioner_output']['server_id'])
278
+ if server
279
+ action_handler.perform_action "destroy machine #{node_json['name']} (#{node_json['normal']['provisioner_output']['server_id']} at #{provisioner_url})" do
280
+ server.destroy
281
+ end
282
+ end
283
+ convergence_strategy_for(node_json).cleanup_convergence(action_handler, node_json)
284
+ end
285
+ end
286
+
287
+ def stop_machine(action_handler, node_json)
288
+ # If the machine doesn't exist, we silently do nothing
289
+ if node_json['normal']['provisioner_output'] && node_json['normal']['provisioner_output']['server_id']
290
+ server = compute.servers.get(node_json['normal']['provisioner_output']['server_id'])
291
+ action_handler.perform_action "stop machine #{node_json['name']} (#{server.id} at #{provisioner_url})" do
292
+ server.stop
293
+ end
294
+ end
295
+ end
296
+
297
+ def resource_created(machine)
298
+ @base_bootstrap_options_for[machine] = current_base_bootstrap_options
299
+ end
300
+
301
+ def compute
302
+ @compute ||= Fog::Compute.new(compute_options)
303
+ end
304
+
305
+ def provisioner_url
306
+ provider_identifier = compute_options[:vsphere_server]
307
+ "fog:#{compute_options[:provider]}:#{provider_identifier}"
308
+ end
309
+
310
+ # Not meant to be part of public interface
311
+ def transport_for(server, node_json = nil)
312
+ # TODO winrm
313
+ create_ssh_transport(server, node_json)
314
+ end
315
+
316
+ protected
317
+
318
+ def option_for(node_json, key)
319
+ if node_json['normal']['provisioner_options'] && node_json['normal']['provisioner_options'][key.to_s]
320
+ node_json['normal']['provisioner_options'][key.to_s]
321
+ elsif compute_options[key]
322
+ compute_options[key]
323
+ else
324
+ DEFAULT_OPTIONS[key]
325
+ end
326
+ end
327
+
328
+ def symbolize_keys(options)
329
+ options.inject({}) { |result,(key,value)| result[key.to_sym] = value; result }
330
+ end
331
+
332
+ def server_for(node_json)
333
+ if node_json['normal']['provisioner_output'] && node_json['normal']['provisioner_output']['server_id']
334
+ compute.servers.get(node_json['normal']['provisioner_output']['server_id'])
335
+ else
336
+ nil
337
+ end
338
+ end
339
+
340
+ def bootstrap_options_for(machine, node_json)
341
+ provisioner_options = node_json['normal']['provisioner_options'] || {}
342
+ bootstrap_options = @base_bootstrap_options_for[machine] || current_base_bootstrap_options
343
+ bootstrap_options = bootstrap_options.merge(symbolize_keys(provisioner_options['bootstrap_options'] || {}))
344
+ require 'socket'
345
+ require 'etc'
346
+ tags = {
347
+ 'Name' => node_json['name'],
348
+ 'BootstrapChefServer' => machine.chef_server[:chef_server_url],
349
+ 'BootstrapHost' => Socket.gethostname,
350
+ 'BootstrapUser' => Etc.getlogin,
351
+ 'Bootstrapnode_jsonName' => node_json['name']
352
+ }
353
+ if machine.chef_server[:options] && machine.chef_server[:options][:data_store]
354
+ tags['ChefLocalRepository'] = machine.chef_server[:options][:data_store].chef_fs.fs_description
355
+ end
356
+ # User-defined tags override the ones we set
357
+ tags.merge!(bootstrap_options[:tags]) if bootstrap_options[:tags]
358
+ bootstrap_options.merge!({ :tags => tags })
359
+
360
+ bootstrap_options
361
+ end
362
+
363
+ def machine_for(node_json, server = nil)
364
+ server ||= server_for(node_json)
365
+ if !server
366
+ raise "Server for node_json #{node_json['name']} has not been created!"
367
+ end
368
+
369
+ if node_json['normal']['provisioner_options'] && node_json['normal']['provisioner_options']['is_windows']
370
+ ChefMetal::Machine::WindowsMachine.new(node_json, transport_for(server, node_json), convergence_strategy_for(node_json))
371
+ else
372
+ ChefMetal::Machine::UnixMachine.new(node_json, transport_for(server, node_json), convergence_strategy_for(node_json))
373
+ end
374
+ end
375
+
376
+ def convergence_strategy_for(node_json)
377
+ if node_json['normal']['provisioner_options'] && node_json['normal']['provisioner_options']['is_windows']
378
+ @windows_convergence_strategy ||= begin
379
+ options = {}
380
+ provisioner_options = node_json['normal']['provisioner_options'] || {}
381
+ options[:chef_client_timeout] = provisioner_options['chef_client_timeout'] if provisioner_options.has_key?('chef_client_timeout')
382
+ ChefMetal::ConvergenceStrategy::InstallMsi.new(options)
383
+ end
384
+ else
385
+ @unix_convergence_strategy ||= begin
386
+ options = {}
387
+ provisioner_options = node_json['normal']['provisioner_options'] || {}
388
+ options[:chef_client_timeout] = provisioner_options['chef_client_timeout'] if provisioner_options.has_key?('chef_client_timeout')
389
+ ChefMetal::ConvergenceStrategy::InstallCached.new(options)
390
+ end
391
+ end
392
+ end
393
+
394
+ def create_ssh_transport(server, node_json)
395
+ username = compute_options[:ssh_username] || 'root'
396
+
397
+ bootstrap_options = node_json['normal']['provisioner_options']['bootstrap_options']
398
+ remote_host = has_static_ip(node_json) ? bootstrap_options['customization_spec']['ipsettings']['ip'] : server.ipaddress
399
+
400
+ ChefMetal::Transport::SSH.new(remote_host, username, {:password => compute_options[:ssh_password], :paranoid => false}, {})
401
+ end
402
+
403
+ def wait_until_ready(node_json, server, timeout)
404
+ transport = nil
405
+ _self = self
406
+ server.wait_for(timeout) do
407
+ if transport
408
+ transport.available?
409
+ elsif tools_state != 'toolsNotRunning'
410
+ # Don't create the transport until the machine is ready (we won't have the host till then)
411
+ transport = _self.transport_for(server, node_json)
412
+ transport.available?
413
+ else
414
+ false
415
+ end
416
+ end
417
+ end
418
+ end
419
+ end
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-metal-fogsphere
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha.1
5
- prerelease: 6
4
+ version: 0.1.0.alpha.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Matt Wrock
@@ -14,33 +13,29 @@ dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rbvmomi
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
19
  version: 1.5.1
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
26
  version: 1.5.1
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: chef-metal
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - ">="
36
32
  - !ruby/object:Gem::Version
37
33
  version: '0'
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - ">="
44
39
  - !ruby/object:Gem::Version
45
40
  version: '0'
46
41
  description: A fork of Chef-Metal-fog for vsphere
@@ -49,31 +44,36 @@ email:
49
44
  executables: []
50
45
  extensions: []
51
46
  extra_rdoc_files: []
52
- files: []
47
+ files:
48
+ - ".gitignore"
49
+ - LICENSE
50
+ - chef-metal-fogsphere-0.1.0.alpha.1.zip
51
+ - chef-metal-fogsphere.gemspec
52
+ - lib/chef_metal_clc.rb
53
+ - lib/clc_provisioner.rb
53
54
  homepage:
54
55
  licenses:
55
56
  - Apache 2.0
57
+ metadata: {}
56
58
  post_install_message:
57
59
  rdoc_options: []
58
60
  require_paths:
59
61
  - lib
60
62
  required_ruby_version: !ruby/object:Gem::Requirement
61
- none: false
62
63
  requirements:
63
- - - ! '>='
64
+ - - ">="
64
65
  - !ruby/object:Gem::Version
65
66
  version: '0'
66
67
  required_rubygems_version: !ruby/object:Gem::Requirement
67
- none: false
68
68
  requirements:
69
- - - ! '>'
69
+ - - ">"
70
70
  - !ruby/object:Gem::Version
71
71
  version: 1.3.1
72
72
  requirements: []
73
73
  rubyforge_project:
74
- rubygems_version: 1.8.28
74
+ rubygems_version: 2.2.1
75
75
  signing_key:
76
- specification_version: 3
76
+ specification_version: 4
77
77
  summary: A fork of Chef-Metal-fog for vsphere
78
78
  test_files: []
79
79
  has_rdoc: