knife-vcenter 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,269 @@
1
+ # Copyright 2014-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ require 'savon'
5
+ require 'nokogiri'
6
+ require 'date'
7
+ require 'securerandom'
8
+
9
+ # A little utility library for VMware SSO.
10
+ # For now, this is not a general purpose library that covers all
11
+ # the interfaces of the SSO service.
12
+ # Specifically, the support is limited to the following:
13
+ # * request bearer token.
14
+ module SSO
15
+
16
+ # The XML date format.
17
+ DATE_FORMAT = "%FT%T.%LZ"
18
+
19
+ # The XML namespaces that are required: SOAP, WSDL, et al.
20
+ NAMESPACES = {
21
+ "xmlns:S" => "http://schemas.xmlsoap.org/soap/envelope/",
22
+ "xmlns:wst" => "http://docs.oasis-open.org/ws-sx/ws-trust/200512",
23
+ "xmlns:u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
24
+ "xmlns:x" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
25
+ }
26
+
27
+ # Provides the connection details for the SSO service.
28
+ class Connection
29
+
30
+ attr_accessor :sso_url, :wsdl_url, :username, :password
31
+
32
+ # Creates a new instance.
33
+ def initialize(sso_url, wsdl_url=nil)
34
+ self.sso_url = sso_url
35
+ self.wsdl_url = wsdl_url || "#{sso_url}?wsdl"
36
+ end
37
+
38
+ # Login with the given credentials.
39
+ # Note: this does not invoke a login action, but rather stores the
40
+ # credentials for use later.
41
+ def login(username, password)
42
+ self.username = username
43
+ self.password = password
44
+ self # enable builder pattern
45
+ end
46
+
47
+ # Gets (or creates) the Savon client instance.
48
+ def client
49
+ # construct and init the client proxy
50
+ @client ||= Savon.client do |globals|
51
+ # see: http://savonrb.com/version2/globals.html
52
+ globals.wsdl wsdl_url
53
+ globals.endpoint sso_url
54
+
55
+ globals.strip_namespaces false
56
+ globals.env_namespace :S
57
+
58
+ # set like this so https connection does not fail
59
+ # TODO: find an acceptable solution for production
60
+ globals.ssl_verify_mode :none
61
+
62
+ # dev/debug settings
63
+ # globals.pretty_print_xml ENV['DEBUG_SOAP']
64
+ # globals.log ENV['DEBUG_SOAP']
65
+ end
66
+ end
67
+
68
+ # Invokes the request bearer token operation.
69
+ # @return [SamlToken]
70
+ def request_bearer_token()
71
+ rst = RequestSecurityToken.new(client, username, password)
72
+ rst.invoke()
73
+ rst.saml_token
74
+ end
75
+ end
76
+
77
+ # @abstract Base class for invocable service calls.
78
+ class SoapInvocable
79
+
80
+ attr_reader :operation, :client, :response
81
+
82
+ # Constructs a new instance.
83
+ # @param operation [Symbol] the SOAP operation name (in Symbol form)
84
+ # @param client [Savon::Client] the client
85
+ def initialize(operation, client)
86
+ @operation = operation
87
+ @client = client
88
+ end
89
+
90
+ # Invokes the service call represented by this type.
91
+ def invoke
92
+ request = request_xml.to_s
93
+ puts "request = #{request}" if ENV['DEBUG']
94
+ @response = client.call(operation, xml:request)
95
+ puts "response = #{response}" if ENV['DEBUG']
96
+ self # for chaining with new
97
+ end
98
+
99
+ # Builds the request XML content.
100
+ def request_xml
101
+ builder = Builder::XmlMarkup.new()
102
+ builder.instruct!(:xml, encoding: "UTF-8")
103
+
104
+ builder.tag!("S:Envelope", NAMESPACES) do |envelope|
105
+ if has_header?
106
+ envelope.tag!("S:Header") do |header|
107
+ header_xml(header)
108
+ end
109
+ end
110
+ envelope.tag!("S:Body") do |body|
111
+ body_xml(body)
112
+ end
113
+ end
114
+ builder.target!
115
+ end
116
+
117
+ def has_header?
118
+ true
119
+ end
120
+
121
+ # Builds the header portion of the SOAP request.
122
+ # Specific service operations must override this method.
123
+ def header_xml(header)
124
+ raise 'abstract method not implemented!'
125
+ end
126
+
127
+ # Builds the body portion of the SOAP request.
128
+ # Specific service operations must override this method.
129
+ def body_xml(body)
130
+ raise 'abstract method not implemented!'
131
+ end
132
+
133
+ # Gets the response XML content.
134
+ def response_xml
135
+ raise 'illegal state: response not set yet' if response.nil?
136
+ @response_xml ||= Nokogiri::XML(response.to_xml)
137
+ end
138
+
139
+ def response_hash
140
+ @response_hash ||= response.to_hash
141
+ end
142
+ end
143
+
144
+ # Encapsulates an issue operation that requests a security token
145
+ # from the SSO service.
146
+ class RequestSecurityToken < SoapInvocable
147
+
148
+ attr_accessor :request_type, :delegatable
149
+
150
+ # Constructs a new instance.
151
+ def initialize(client, username, password, hours=2)
152
+ super(:issue, client)
153
+
154
+ @username = username
155
+ @password = password
156
+ @hours = hours
157
+
158
+ #TODO: these things should be configurable, so we can get
159
+ #non-delegatable tokens, HoK tokens, etc.
160
+ @request_type = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue"
161
+ @delegatable = true
162
+ end
163
+
164
+ def now
165
+ @now ||= Time.now.utc.to_datetime
166
+ end
167
+
168
+ def created
169
+ @created ||= now.strftime(DATE_FORMAT)
170
+ end
171
+
172
+ def future
173
+ @future ||= now + (2/24.0) #days (for DateTime math)
174
+ end
175
+
176
+ def expires
177
+ @expires ||= future.strftime(DATE_FORMAT)
178
+ end
179
+
180
+ # Builds the header XML for the SOAP request.
181
+ def header_xml(header)
182
+ id = "uuid-" + SecureRandom.uuid
183
+
184
+ #header.tag!("x:Security", "x:mustUnderstand" => "1") do |security|
185
+ header.tag!("x:Security") do |security|
186
+ security.tag!("u:Timestamp", "u:Id" => "_0") do |timestamp|
187
+ timestamp.tag!("u:Created") do |element|
188
+ element << created
189
+ end
190
+ timestamp.tag!("u:Expires") do |element|
191
+ element << expires
192
+ end
193
+ end
194
+ security.tag!("x:UsernameToken", "u:Id" => id) do |utoken|
195
+ utoken.tag!("x:Username") do |element|
196
+ element << @username
197
+ end
198
+ utoken.tag!("x:Password") do |element|
199
+ element << @password
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ # Builds the body XML for the SOAP request.
206
+ def body_xml(body)
207
+ body.tag!("wst:RequestSecurityToken") do |rst|
208
+ rst.tag!("wst:RequestType") do |element|
209
+ element << request_type
210
+ end
211
+ rst.tag!("wst:Delegatable") do |element|
212
+ element << delegatable.to_s
213
+ end
214
+ =begin
215
+ #TODO: we don't seem to need this, but I'm leaving this
216
+ #here for now as a reminder.
217
+ rst.tag!("wst:Lifetime") do |lifetime|
218
+ lifetime.tag!("u:Created") do |element|
219
+ element << created
220
+ end
221
+ lifetime.tag!("u:Expires") do |element|
222
+ element << expires
223
+ end
224
+ end
225
+ =end
226
+ end
227
+ end
228
+
229
+ # Gets the saml_token from the SOAP response body.
230
+ # @return [SamlToken] the requested SAML token
231
+ def saml_token
232
+ assertion = response_xml.at_xpath('//saml2:Assertion',
233
+ 'saml2' => 'urn:oasis:names:tc:SAML:2.0:assertion')
234
+ SamlToken.new(assertion)
235
+ end
236
+ end
237
+
238
+ # Holds a SAML token.
239
+ class SamlToken
240
+ attr_reader :xml
241
+
242
+ # Creates a new instance.
243
+ def initialize(xml)
244
+ @xml = xml
245
+ end
246
+
247
+ #TODO: add some getters for interesting content
248
+
249
+ def to_s
250
+ esc_token = xml.to_xml(:indent => 0, :encoding => 'UTF-8')
251
+ esc_token = esc_token.gsub(/\n/, '')
252
+ esc_token
253
+ end
254
+ end
255
+ end
256
+
257
+ # main: quick self tester
258
+ if __FILE__ == $0
259
+ cloudvm_ip = ARGV[0]
260
+ cloudvm_ip ||= "10.20.17.0"
261
+ #cloudvm_ip ||= "10.67.245.207"
262
+ sso_url = "https://#{cloudvm_ip}/sts/STSService/vsphere.local"
263
+ wsdl_url = "#{sso_url}?wsdl"
264
+ sso = SSO::Connection.new(sso_url, wsdl_url)
265
+ #sso.login("administrator@vsphere.local", "Admin!23")
266
+ sso.login("root", "vmware")
267
+ token = sso.request_bearer_token
268
+ puts token.to_s
269
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright (c) 2017 Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'rbvmomi'
21
+
22
+ class Support
23
+ class CloneVm
24
+ attr_reader :vim, :options
25
+
26
+ def initialize(conn_opts, options)
27
+ @options = options
28
+
29
+ # Connect to vSphere
30
+ @vim ||= RbVmomi::VIM.connect conn_opts
31
+ end
32
+
33
+ def clone
34
+
35
+ # set the datacenter name
36
+ dc = vim.serviceInstance.find_datacenter(options[:datacenter])
37
+ src_vm = dc.find_vm(options[:template])
38
+ hosts = dc.hostFolder.children
39
+
40
+ # Specify where the machine is going to be created
41
+ relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec
42
+ relocate_spec.host = options[:targethost]
43
+ relocate_spec.pool = hosts.first.resourcePool
44
+
45
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(location: relocate_spec,
46
+ powerOn: options[:poweron],
47
+ template: false)
48
+
49
+ # Set the folder to use
50
+ dest_folder = options[:folder].nil? ? src_vm.parent : options[:folder][:id]
51
+
52
+ puts "Cloning the template to create the new machine..."
53
+ task = src_vm.CloneVM_Task(folder: dest_folder, name: options[:name], spec: clone_spec)
54
+ # TODO: it would be nice to have dots to tell you it's working here
55
+ task.wait_for_completion
56
+
57
+
58
+ # get the IP address of the machine for bootstrapping
59
+ # machine name is based on the path, e.g. that includes the folder
60
+ name = options[:folder].nil? ? options[:name] : format("%s/%s", options[:folder][:name], options[:name])
61
+ new_vm = dc.find_vm(name)
62
+
63
+ if new_vm.nil?
64
+ puts format("Unable to find machine: %s", name)
65
+ else
66
+ puts 'Waiting for network interfaces to become available...'
67
+ sleep 2 while new_vm.guest.net.empty? || !new_vm.guest.ipAddress
68
+ new_vm.guest.net[0].ipConfig.ipAddress.detect do |addr|
69
+ addr.origin != 'linklayer'
70
+ end.ipAddress
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright (c) 2017 Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright (c) 2017 Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'spec_helper'
21
+ require 'chef/knife/vcenter_vm_list'
22
+ require 'support/shared_examples_for_command'
23
+
24
+ class PowerStatus < BasicObject
25
+ attr_reader :value
26
+ def initialize(state)
27
+ @value = state
28
+ end
29
+ end
30
+
31
+ describe Chef::Knife::Cloud::VcenterVmList do
32
+ it_behaves_like Chef::Knife::Cloud::Command, Chef::Knife::Cloud::VcenterVmList.new
33
+
34
+ subject { described_class.new }
35
+
36
+ describe '#format_power_status' do
37
+ context 'when the power is "POWERED_ON"' do
38
+ it 'displays with green' do
39
+ expect(subject.ui).to receive(:color).with('POWERED_ON', :green)
40
+ subject.format_power_status(PowerStatus.new('POWERED_ON'))
41
+ end
42
+ end
43
+
44
+ context 'when the power is "POWERED_OFF"' do
45
+ it 'displays with red' do
46
+ expect(subject.ui).to receive(:color).with('POWERED_OFF', :red)
47
+ subject.format_power_status(PowerStatus.new('POWERED_OFF'))
48
+ end
49
+ end
50
+
51
+ context 'when the power is "SUSPENDED"' do
52
+ it 'displays with red' do
53
+ expect(subject.ui).to receive(:color).with('SUSPENDED', :yellow)
54
+ subject.format_power_status(PowerStatus.new('SUSPENDED'))
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#format_memory_value' do
60
+ context 'when the memory value is 8192' do
61
+ it 'displays as 8,192' do
62
+ expect(subject.ui).to receive(:text).with('8,192')
63
+ subject.format_memory_value(8192)
64
+ end
65
+ end
66
+ end
67
+ end
metadata ADDED
@@ -0,0 +1,257 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knife-vcenter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chef Partner Engineering
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chef
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '12'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: knife-cloud
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rb-readline
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rbvmomi
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.11'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: savon
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.11'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.11'
83
+ - !ruby/object:Gem::Dependency
84
+ name: vsphere-automation-sdk
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.5'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.7'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.7'
111
+ - !ruby/object:Gem::Dependency
112
+ name: debase
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: github_changelog_generator
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rake
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '10.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '10.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0.35'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0.35'
181
+ - !ruby/object:Gem::Dependency
182
+ name: ruby-debug-ide
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.6.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.6.0
195
+ description: Knife plugin to VMware vCenter.
196
+ email:
197
+ - partnereng@chef.io
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - ".github/ISSUE_TEMPLATE.md"
203
+ - ".github/PULL_REQUEST_TEMPLATE.md"
204
+ - ".gitignore"
205
+ - ".rubocop.yml"
206
+ - ".travis.yml"
207
+ - CHANGELOG.md
208
+ - Gemfile
209
+ - LICENSE.txt
210
+ - README.md
211
+ - Rakefile
212
+ - knife-vcenter.gemspec
213
+ - lib/base.rb
214
+ - lib/chef/knife/cloud/vcenter_service.rb
215
+ - lib/chef/knife/cloud/vcenter_service_helpers.rb
216
+ - lib/chef/knife/cloud/vcenter_service_options.rb
217
+ - lib/chef/knife/vcenter_cluster_list.rb
218
+ - lib/chef/knife/vcenter_datacenter_list.rb
219
+ - lib/chef/knife/vcenter_host_list.rb
220
+ - lib/chef/knife/vcenter_vm_clone.rb
221
+ - lib/chef/knife/vcenter_vm_create.rb
222
+ - lib/chef/knife/vcenter_vm_delete.rb
223
+ - lib/chef/knife/vcenter_vm_list.rb
224
+ - lib/chef/knife/vcenter_vm_show.rb
225
+ - lib/knife-vcenter/version.rb
226
+ - lib/lookup_service_helper.rb
227
+ - lib/sso.rb
228
+ - lib/support/clone_vm.rb
229
+ - spec/spec_helper.rb
230
+ - spec/unit/vcenter_vm_list_spec.rb
231
+ homepage: https://github.com/chef/knife-vcenter
232
+ licenses:
233
+ - Apache 2.0
234
+ metadata: {}
235
+ post_install_message:
236
+ rdoc_options: []
237
+ require_paths:
238
+ - lib
239
+ required_ruby_version: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ required_rubygems_version: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - ">="
247
+ - !ruby/object:Gem::Version
248
+ version: '0'
249
+ requirements: []
250
+ rubyforge_project:
251
+ rubygems_version: 2.6.11
252
+ signing_key:
253
+ specification_version: 4
254
+ summary: Knife plugin to VMware vCenter.
255
+ test_files:
256
+ - spec/spec_helper.rb
257
+ - spec/unit/vcenter_vm_list_spec.rb