rbvmomi 1.8.5 → 1.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 2971a40037e23e51c5253e553138edc469253ee05747d9761f5c158bd34c5feb
4
- data.tar.gz: 24742f39140e8d8d961c6ed5248b3d86c2658795ddd7819b3ca868970c7a6461
2
+ SHA1:
3
+ metadata.gz: c8e16685061414bc6f641e2a7ba8b1a2c3067ccc
4
+ data.tar.gz: 2d4f2ef6b5bedcc5c929548c8d0d74ac128e2434
5
5
  SHA512:
6
- metadata.gz: c98371ab294a9ccc14ac0b570950eebb0415b821ad85684931ee731837cb7ab176163bc8409f72212cab9577bf0c47846287435fe9db2114bafae1cc91654070
7
- data.tar.gz: d86ae7c8c6cd4ae0cf359c420f8f9cf7df0e069861cccacdd5a18fc3bf3b21bf1ffcd41ca67a5c2ca18f6dc4a2ced6b7cf333e72e704bc43865311222550f3af
6
+ metadata.gz: 91f7933b69a4704a508d95e709cbe49a4ae268127e7596b5b8f47fd97de8d650a88e35ef07320319a3b200fb097a0a9590f610e4daf5efddff0d86496fd70c85
7
+ data.tar.gz: 608f43fe7d372adc23319e2bf7dcf354c341caef54d642243b99430e3543772424cfaed78f93c26880b8d504d6029fdff8b73e68ddc6e821906231542d6ee6c6
@@ -0,0 +1,13 @@
1
+ *.rbc
2
+ *.swp
3
+ *.swo
4
+ .ruby-version
5
+ .ruby-gemset
6
+ Gemfile.lock
7
+ /.bundle/
8
+ /.yardoc/
9
+ /coverage/
10
+ /doc/
11
+ /pkg/
12
+ /vendor/bundle/
13
+ /vmodl/
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.2.5
6
+ - 2.3.1
7
+ - jruby-1.7.25
8
+ - jruby-9.1.2.0
9
+ before_install:
10
+ - gem install bundler
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -1,12 +1,16 @@
1
1
  = RbVmomi
2
2
 
3
+ {<img src="https://travis-ci.org/vmware/rbvmomi.svg?branch=master" alt="travis-ci">}[http://travis-ci.org/vmware/rbvmomi]
4
+
5
+ This is an Open Source community supported project at VMware. It is built and maintained by programmers like you!
6
+
3
7
  == Introduction
4
8
 
5
9
  RbVmomi is a Ruby interface to the vSphere API. Like the Perl and Java SDKs,
6
10
  you can use it to manage ESX and VirtualCenter servers. The current release
7
11
  supports the vSphere 5.0 API. RbVmomi specific documentation is
8
12
  online[http://rdoc.info/github/rlane/rbvmomi/master/frames] and is meant to
9
- be used alongside the official documentation[http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/index.html].
13
+ be used alongside the official documentation[http://pubs.vmware.com/vsphere-60/topic/com.vmware.sdk.doc/GUID-19793BCA-9EAB-42E2-8B9F-F9F2129E7741.html].
10
14
 
11
15
  == Installation
12
16
 
@@ -56,7 +60,7 @@ in the first example uses the SearchIndex for fast lookups.
56
60
 
57
61
  A few important points:
58
62
 
59
- * All class, method, parameter, and property names match the official documentation[http://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/index.html].
63
+ * All class, method, parameter, and property names match the official documentation[http://pubs.vmware.com/vsphere-60/topic/com.vmware.sdk.doc/GUID-19793BCA-9EAB-42E2-8B9F-F9F2129E7741.html].
60
64
  * Properties are exposed as accessor methods.
61
65
  * Data object types can usually be inferred from context, so you may use a hash instead.
62
66
  * Enumeration values are simply strings.
data/Rakefile CHANGED
@@ -1,27 +1,8 @@
1
+ require 'bundler/gem_tasks'
1
2
  require 'rake/testtask'
2
- require 'rdoc/task'
3
3
  require 'yard'
4
4
 
5
- begin
6
- require 'jeweler'
7
- Jeweler::Tasks.new do |gem|
8
- gem.name = "rbvmomi"
9
- gem.summary = "Ruby interface to the VMware vSphere API"
10
- #gem.description = ""
11
- gem.email = "rlane@vmware.com"
12
- gem.homepage = "https://github.com/vmware/rbvmomi"
13
- gem.authors = ["Rich Lane", "Christian Dickmann"]
14
- gem.add_dependency 'nokogiri', '>= 1.4.1'
15
- gem.add_dependency 'builder'
16
- gem.add_dependency 'trollop'
17
- gem.required_ruby_version = '>= 1.8.7'
18
- gem.files.include 'vmodl.db'
19
- gem.files.include '.yardopts'
20
- gem.version = '1.8.4'
21
- end
22
- rescue LoadError
23
- puts "Jeweler not available. Install it with: gem install jeweler"
24
- end
5
+ task(:default => :test)
25
6
 
26
7
  Rake::TestTask.new do |t|
27
8
  t.libs << "test"
@@ -30,17 +11,3 @@ Rake::TestTask.new do |t|
30
11
  end
31
12
 
32
13
  YARD::Rake::YardocTask.new
33
-
34
- begin
35
- require 'rcov/rcovtask'
36
- desc 'Measures test coverage using rcov'
37
- Rcov::RcovTask.new do |rcov|
38
- rcov.pattern = 'test/test_*.rb'
39
- rcov.output_dir = 'coverage'
40
- rcov.verbose = true
41
- rcov.libs << "test"
42
- rcov.rcov_opts << '--exclude "gems/*"'
43
- end
44
- rescue LoadError
45
- puts "Rcov not available. Install it with: gem install rcov"
46
- end
File without changes
@@ -0,0 +1,24 @@
1
+ require 'rubygems/dependency_installer'
2
+
3
+ # SEE: http://en.wikibooks.org/wiki/Ruby_Programming/RubyGems#How_to_install_different_versions_of_gems_depending_on_which_version_of_ruby_the_installee_is_using
4
+
5
+ di = Gem::DependencyInstaller.new
6
+
7
+ begin
8
+ if RUBY_VERSION =~ /^1.8/
9
+ puts "Not installing test-unit. It is included in Ruby #{RUBY_VERSION}"
10
+ else
11
+ di.install 'test-unit', '~> 3.2'
12
+ end
13
+ rescue => e
14
+ warn "#{$0}: #{e}"
15
+
16
+ exit!
17
+ end
18
+
19
+ puts "Writing fake Rakefile"
20
+
21
+ # Write fake Rakefile for rake since Makefile isn't used
22
+ File.open(File.join(File.dirname(__FILE__), 'Rakefile'), 'w') do |f|
23
+ f.write("task :default" + $/)
24
+ end
@@ -158,6 +158,12 @@ class DataObject < ObjectWithProperties
158
158
  q.text ')'
159
159
  end
160
160
 
161
+ def to_json(*args)
162
+ h = self.props
163
+ m = h.merge({ JSON.create_id => self.class.name })
164
+ m.to_json(*args)
165
+ end
166
+
161
167
  init
162
168
  end
163
169
 
@@ -1,4 +1,3 @@
1
-
2
1
  # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
3
2
  require 'rubygems'
4
3
  require 'builder'
@@ -43,15 +42,14 @@ class RbVmomi::TrivialSoap
43
42
  if @opts[:insecure]
44
43
  @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
45
44
  else
46
- @http.cert_store = OpenSSL::X509::Store.new
47
- @http.cert_store.set_default_paths
48
- @http.cert_store.add_file(@opts[:cert]) if @opts[:cert]
49
- @http.key = OpenSSL::PKey::RSA.new(@opts[:key]) if @opts[:key]
45
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
50
46
  end
47
+ @http.cert = OpenSSL::X509::Certificate.new(@opts[:cert]) if @opts[:cert]
48
+ @http.key = OpenSSL::PKey::RSA.new(@opts[:key]) if @opts[:key]
51
49
  end
52
50
  @http.set_debug_output(STDERR) if $DEBUG
53
- @http.read_timeout = 1000000
54
- @http.open_timeout = 60
51
+ @http.read_timeout = @opts[:read_timeout] || 1000000
52
+ @http.open_timeout = @opts[:open_timeout] || 60
55
53
  def @http.on_connect
56
54
  @socket.io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
57
55
  end
@@ -77,11 +77,11 @@ class AdmissionControlledResourceScheduler
77
77
  # Returns the used VM folder. If not set yet, uses the vm_folder_path to
78
78
  # lookup the folder. If it doesn't exist, it is created. Collisions between
79
79
  # multiple clients concurrently creating the same folder are handled.
80
- # @return [VIM::Folder] The VM folder
80
+ # @return [RbVmomi::VIM::Folder] The VM folder
81
81
  def vm_folder
82
82
  retries = 1
83
83
  begin
84
- @vm_folder ||= datacenter.vmFolder.traverse!(@vm_folder_path, VIM::Folder)
84
+ @vm_folder ||= datacenter.vmFolder.traverse!(@vm_folder_path, RbVmomi::VIM::Folder)
85
85
  if !@vm_folder
86
86
  fail "VM folder #{@vm_folder_path} not found"
87
87
  end
@@ -98,10 +98,10 @@ class AdmissionControlledResourceScheduler
98
98
 
99
99
  # Returns the used Datacenter. If not set yet, uses the datacenter_path to
100
100
  # lookup the datacenter.
101
- # @return [VIM::Datacenter] The datacenter
101
+ # @return [RbVmomi::VIM::Datacenter] The datacenter
102
102
  def datacenter
103
103
  if !@datacenter
104
- @datacenter = @root_folder.traverse(@datacenter_path, VIM::Datacenter)
104
+ @datacenter = @root_folder.traverse(@datacenter_path, RbVmomi::VIM::Datacenter)
105
105
  if !@datacenter
106
106
  fail "datacenter #{@datacenter_path} not found"
107
107
  end
@@ -112,11 +112,11 @@ class AdmissionControlledResourceScheduler
112
112
  # Returns the candidate datastores. If not set yet, uses the datastore_paths
113
113
  # to lookup the datastores under the datacenter.
114
114
  # As a side effect, also looks up properties about all the datastores
115
- # @return [Array] List of VIM::Datastore
115
+ # @return [Array] List of RbVmomi::VIM::Datastore
116
116
  def datastores
117
117
  if !@datastores
118
118
  @datastores = @datastore_paths.map do |path|
119
- ds = datacenter.datastoreFolder.traverse(path, VIM::Datastore)
119
+ ds = datacenter.datastoreFolder.traverse(path, RbVmomi::VIM::Datastore)
120
120
  if !ds
121
121
  fail "datastore #{path} not found"
122
122
  end
@@ -131,7 +131,7 @@ class AdmissionControlledResourceScheduler
131
131
 
132
132
  # Returns the candidate computers (aka clusters). If not set yet, uses the
133
133
  # computer_names to look them up.
134
- # @return [Array] List of [VIM::ClusterComputeResource, Hash] tuples, where
134
+ # @return [Array] List of [RbVmomi::VIM::ClusterComputeResource, Hash] tuples, where
135
135
  # the Hash is a list of stats about the computer
136
136
  def computers
137
137
  if !@computers
@@ -145,7 +145,7 @@ class AdmissionControlledResourceScheduler
145
145
 
146
146
  # Returns the candidate pods. If not set, automatically computes the pods
147
147
  # based on the list of computers (aka clusters) and datastores.
148
- # @return [Array] List of pods, where a pod is a list of VIM::ClusterComputeResource
148
+ # @return [Array] List of pods, where a pod is a list of RbVmomi::VIM::ClusterComputeResource
149
149
  def pods
150
150
  if !@pods
151
151
  # A pod is defined as a set of clusters (aka computers) that share the same
@@ -168,22 +168,22 @@ class AdmissionControlledResourceScheduler
168
168
  # @return [Hash] Hash of VMs as keys and their properties as values.
169
169
  def pod_vms pod
170
170
  # This function retrieves all VMs residing inside a pod
171
- filterSpec = VIM.PropertyFilterSpec(
171
+ filterSpec = RbVmomi::VIM.PropertyFilterSpec(
172
172
  objectSet: pod.map do |computer, stats|
173
173
  {
174
174
  obj: computer.resourcePool,
175
175
  selectSet: [
176
- VIM.TraversalSpec(
176
+ RbVmomi::VIM.TraversalSpec(
177
177
  name: 'tsFolder',
178
178
  type: 'ResourcePool',
179
179
  path: 'resourcePool',
180
180
  skip: false,
181
181
  selectSet: [
182
- VIM.SelectionSpec(name: 'tsFolder'),
183
- VIM.SelectionSpec(name: 'tsVM'),
182
+ RbVmomi::VIM.SelectionSpec(name: 'tsFolder'),
183
+ RbVmomi::VIM.SelectionSpec(name: 'tsVM'),
184
184
  ]
185
185
  ),
186
- VIM.TraversalSpec(
186
+ RbVmomi::VIM.TraversalSpec(
187
187
  name: 'tsVM',
188
188
  type: 'ResourcePool',
189
189
  path: 'vm',
@@ -202,11 +202,11 @@ class AdmissionControlledResourceScheduler
202
202
  result = @vim.propertyCollector.RetrieveProperties(specSet: [filterSpec])
203
203
 
204
204
  out = result.map { |x| [x.obj, Hash[x.propSet.map { |y| [y.name, y.val] }]] }
205
- out.select{|obj, props| obj.is_a?(VIM::VirtualMachine)}
205
+ out.select{|obj, props| obj.is_a?(RbVmomi::VIM::VirtualMachine)}
206
206
  end
207
207
 
208
208
  # Returns all candidate datastores for a given pod.
209
- # @return [Array] List of VIM::Datastore
209
+ # @return [Array] List of RbVmomi::VIM::Datastore
210
210
  def pod_datastores pod
211
211
  pod.first.datastore & self.datastores
212
212
  end
@@ -214,7 +214,7 @@ class AdmissionControlledResourceScheduler
214
214
  # Returns the list of pods that pass admission control. If not set yet, performs
215
215
  # admission control to compute the list. If no pods passed the admission
216
216
  # control, an exception is thrown.
217
- # @return [Array] List of pods, where a pod is a list of VIM::ClusterComputeResource
217
+ # @return [Array] List of pods, where a pod is a list of RbVmomi::VIM::ClusterComputeResource
218
218
  def filtered_pods
219
219
  # This function applies admission control and returns those pods that have
220
220
  # passed admission control. An exception is thrown if access was denied to
@@ -300,7 +300,7 @@ class AdmissionControlledResourceScheduler
300
300
  # Returns the computer (aka cluster) to be used for placement. If not set yet,
301
301
  # computs the least loaded cluster (using a metric that combines CPU and Memory
302
302
  # load) that passes admission control.
303
- # @return [VIM::ClusterComputeResource] Chosen computer (aka cluster)
303
+ # @return [RbVmomi::VIM::ClusterComputeResource] Chosen computer (aka cluster)
304
304
  def pick_computer placementhint = nil
305
305
  if !@computer
306
306
  # Out of the pods to which we have been granted access, pick the cluster
@@ -330,7 +330,7 @@ class AdmissionControlledResourceScheduler
330
330
 
331
331
  # Returns the datastore to be used for placement. If not set yet, picks a
332
332
  # datastore without much intelligence, as long as it passes admission control.
333
- # @return [VIM::Datastore] Chosen datastore
333
+ # @return [RbVmomi::VIM::Datastore] Chosen datastore
334
334
  def datastore placementHint = nil
335
335
  if @datastore
336
336
  return @datastore
@@ -395,4 +395,4 @@ class AdmissionControlledResourceScheduler
395
395
  datastore = self.datastore @placement_hint
396
396
  log "Datastore: #{datastore.name}"
397
397
  end
398
- end
398
+ end
@@ -336,7 +336,7 @@ class PerfAggregator
336
336
  vms_props, inventory = all_inventory_flat root_folder, prop_names
337
337
  vms = vms_props.keys
338
338
 
339
- hosts_props = inventory.select{|k, v| k.is_a?(VIM::HostSystem)}
339
+ hosts_props = inventory.select{|k, v| k.is_a?(RbVmomi::VIM::HostSystem)}
340
340
 
341
341
  conn = root_folder._connection
342
342
  sc = conn.serviceContent
@@ -0,0 +1,3 @@
1
+ module RbVmomi
2
+ VERSION = '1.9.0'.freeze
3
+ end
@@ -1,6 +1,13 @@
1
1
  # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
2
  require 'rbvmomi'
3
3
 
4
+ # Win32::SSPI is part of core on Windows
5
+ begin
6
+ require 'win32/sspi'
7
+ rescue LoadError
8
+ end
9
+ WIN32 = (defined? Win32::SSPI)
10
+
4
11
  module RbVmomi
5
12
 
6
13
  # A connection to one vSphere SDK endpoint.
@@ -22,7 +29,7 @@ class VIM < Connection
22
29
  fail unless opts.is_a? Hash
23
30
  fail "host option required" unless opts[:host]
24
31
  opts[:cookie] ||= nil
25
- opts[:user] ||= 'root'
32
+ opts[:user] ||= (WIN32 ? ENV['USERNAME'].dup : 'root')
26
33
  opts[:password] ||= ''
27
34
  opts[:ssl] = true unless opts.member? :ssl or opts[:"no-ssl"]
28
35
  opts[:insecure] ||= false
@@ -30,16 +37,30 @@ class VIM < Connection
30
37
  opts[:path] ||= '/sdk'
31
38
  opts[:ns] ||= 'urn:vim25'
32
39
  rev_given = opts[:rev] != nil
33
- opts[:rev] = '4.0' unless rev_given
40
+ opts[:rev] = '6.0' unless rev_given
34
41
  opts[:debug] = (!ENV['RBVMOMI_DEBUG'].empty? rescue false) unless opts.member? :debug
35
42
 
36
43
  new(opts).tap do |vim|
37
44
  unless opts[:cookie]
38
- vim.serviceContent.sessionManager.Login :userName => opts[:user], :password => opts[:password]
45
+ if WIN32 && opts[:password] == ''
46
+ # Attempt login by SSPI if no password specified on Windows
47
+ negotiation = Win32::SSPI::NegotiateAuth.new opts[:user], ENV['USERDOMAIN'].dup
48
+ begin
49
+ vim.serviceContent.sessionManager.LoginBySSPI :base64Token => negotiation.get_initial_token
50
+ rescue RbVmomi::Fault => fault
51
+ if !fault.fault.is_a?(RbVmomi::VIM::SSPIChallenge)
52
+ raise
53
+ else
54
+ vim.serviceContent.sessionManager.LoginBySSPI :base64Token => negotiation.complete_authentication(fault.base64Token)
55
+ end
56
+ end
57
+ else
58
+ vim.serviceContent.sessionManager.Login :userName => opts[:user], :password => opts[:password]
59
+ end
39
60
  end
40
61
  unless rev_given
41
62
  rev = vim.serviceContent.about.apiVersion
42
- vim.rev = [rev, '5.5'].min
63
+ vim.rev = [rev, '6.0'].min
43
64
  end
44
65
  end
45
66
  end
@@ -13,5 +13,10 @@ class RbVmomi::VIM::Datacenter
13
13
  def find_vm path
14
14
  vmFolder.traverse path, RbVmomi::VIM::VirtualMachine
15
15
  end
16
+
17
+ # Traverse the given inventory +path+ to find a Folder.
18
+ def find_folder path
19
+ vmFolder.traverse path, RbVmomi::VIM::Folder
20
+ end
16
21
  end
17
22
 
@@ -63,6 +63,7 @@ class RbVmomi::VIM::Datastore
63
63
  end
64
64
 
65
65
  def mkuripath path
66
- "/folder/#{URI.escape path}?dcPath=#{URI.escape datacenter.name}&dsName=#{URI.escape name}"
66
+ datacenter_path_str = datacenter.path[1..-1].map{|elem| elem[1]}.join('/')
67
+ "/folder/#{URI.escape path}?dcPath=#{URI.escape datacenter_path_str }&dsName=#{URI.escape name}"
67
68
  end
68
69
  end
@@ -16,6 +16,7 @@ class RbVmomi::VIM::OvfManager
16
16
  # @option opts [String] :diskProvisioning (thin) Disk provisioning mode.
17
17
  # @option opts [Hash] :networkMappings Network mappings.
18
18
  # @option opts [Hash] :propertyMappings Property mappings.
19
+ # @option opts [String] :deploymentOption Deployment option key.
19
20
  def deployOVF opts
20
21
  opts = { :networkMappings => {},
21
22
  :propertyMappings => {},
@@ -29,7 +30,7 @@ class RbVmomi::VIM::OvfManager
29
30
  :hostSystem => opts[:host],
30
31
  :locale => "US",
31
32
  :entityName => opts[:vmName],
32
- :deploymentOption => "",
33
+ :deploymentOption => opts[:deploymentOption] || "",
33
34
  :networkMapping => opts[:networkMappings].map{|from, to| RbVmomi::VIM::OvfNetworkMapping(:name => from, :network => to)},
34
35
  :propertyMapping => opts[:propertyMappings].to_a,
35
36
  :diskProvisioning => opts[:diskProvisioning]
@@ -2,7 +2,7 @@ module RbVmomi
2
2
 
3
3
  class VIM::ReflectManagedMethodExecuter
4
4
  def fetch moid, prop
5
- result = FetchSoap(:moid => moid, :version => 'urn:vim25/5.0', :prop => prop)
5
+ result = FetchSoap(:moid => moid, :version => 'urn:vim25/6.0', :prop => prop)
6
6
  xml = Nokogiri(result.response)
7
7
  _connection.deserializer.deserialize xml.root, nil
8
8
  end
@@ -16,7 +16,7 @@ class VIM::ReflectManagedMethodExecuter
16
16
  soap_arg.val = xml.target!
17
17
  end
18
18
  end
19
- result = ExecuteSoap(:moid => moid, :version => 'urn:vim25/5.0',
19
+ result = ExecuteSoap(:moid => moid, :version => 'urn:vim25/6.0',
20
20
  :method => method, :argument => soap_args)
21
21
  if result
22
22
  _connection.deserializer.deserialize Nokogiri(result.response).root, nil
@@ -0,0 +1,33 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
+
3
+ require 'rbvmomi/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rbvmomi'
7
+ spec.summary = 'Ruby interface to the VMware vSphere API'
8
+ spec.version = RbVmomi::VERSION
9
+
10
+ spec.authors = ['Rich Lane', 'Christian Dickmann']
11
+ spec.email = 'jr@garciaole.com'
12
+ spec.homepage = 'https://github.com/vmware/rbvmomi'
13
+
14
+ spec.bindir = 'exe'
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(/^test\//) }
16
+ spec.executables << 'rbvmomish'
17
+ spec.extensions << 'ext/mkrf_conf.rb'
18
+
19
+ spec.add_runtime_dependency('builder', '~> 3.2')
20
+ spec.add_runtime_dependency('json', '~> 1.8')
21
+ spec.add_runtime_dependency('nokogiri', '~> 1.5.11')
22
+ spec.add_runtime_dependency('trollop', '~> 2.1')
23
+
24
+ spec.add_development_dependency('rake', '~> 10.5')
25
+ spec.add_development_dependency('simplecov', '~> 0.12.0')
26
+ spec.add_development_dependency('yard', '~> 0.9.5')
27
+
28
+ unless RUBY_VERSION =~ /^1.8/
29
+ spec.add_development_dependency('test-unit', '~> 3.2')
30
+ end
31
+
32
+ spec.required_ruby_version = '>= 1.8.7'
33
+ end