kitchen-vcenter 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3d3bbb3c6bad4d1c38ef1fc4049a0363083e1bd7
4
+ data.tar.gz: e6e3e44a085fdb28159d4562130b73a860948e42
5
+ SHA512:
6
+ metadata.gz: 8b96a21adbe30109d9991564f81eded1b0c647366df9631145c678aec30c73d5dd64c45428b4d715845c77ea07184f3b76d83c8cf3185812eb8a0b0e7b116830
7
+ data.tar.gz: 705e798243b0763f288a7d6938a5b3e1ca9461500c97ccc21c836d48f4ea99d30fe4d3b9fb21eb9aacf02abf699b0e29c3b58995e7c5dd94dd689ede96976b1d
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Change Log
2
+
3
+ ## [1.0.0](https://github.com/chef/kitchen-vcenter/tree/1.0.0) (2017-08-10)
4
+ **Merged pull requests:**
5
+
6
+ - 1.0.0 release [\#4](https://github.com/chef/kitchen-vcenter/pull/4) ([jjasghar](https://github.com/jjasghar))
7
+ - Second walk through [\#3](https://github.com/chef/kitchen-vcenter/pull/3) ([jjasghar](https://github.com/jjasghar))
8
+ - Updated so that the vm\_name is set according to suite and platform. [\#2](https://github.com/chef/kitchen-vcenter/pull/2) ([russellseymour](https://github.com/russellseymour))
9
+ - Prep-for-public release [\#1](https://github.com/chef/kitchen-vcenter/pull/1) ([jjasghar](https://github.com/jjasghar))
10
+
11
+
12
+
13
+ \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
data/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # kitchen-vcenter
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/kitchen-vcenter.svg)](https://rubygems.org/gems/kitchen-vcenter)
4
+ [![Build Status](https://travis-ci.org/chef/kitchen-vcenter.svg?branch=master)](https://travis-ci.org/chef/kitchen-vcenter)
5
+ [![Dependency Status](https://gemnasium.com/chef/kitchen-vcenter.svg)](https://gemnasium.com/chef/kitchen-vcenter)
6
+ [![Inline docs](http://inch-ci.org/github/chef/kitchen-vcenter.svg?branch=master)](http://inch-ci.org/github/chef/kitchen-vcenter)
7
+
8
+ This is the official Chef test-kitchen plugin for VMware REST API. This plugin gives kitchen the ability to create, bootstrap, and test VMware vms.
9
+ - Documentation: [https://github.com/chef/kitchen-vcenter/blob/master/README.md](https://github.com/chef/kitchen-vcenter/blob/master/README.md)
10
+ - Source: [https://github.com/chef/kitchen-vcenter/tree/master](https://github.com/chef/kitchen-vcenter/tree/master)
11
+ - Issues: [https://github.com/chef/kitchen-vcenter/issues](https://github.com/chef/knife-vcenter/issues)
12
+ - Slack: sign up: https://code.vmware.com/slack/ slack channel: #chef
13
+ - Mailing list: [https://discourse.chef.io/](https://discourse.chef.io/)
14
+
15
+ This is a `test-kitchen` plugin that allows interaction with vSphere using the vSphere Automation SDK.
16
+
17
+ Please refer to the [CHANGELOG](CHANGELOG.md) for version history and known issues.
18
+
19
+ ## Requirements
20
+
21
+ - Chef 13.0 higher
22
+ - Ruby 2.3.3 or higher
23
+
24
+ ## Installation
25
+
26
+ Using [ChefDK](https://downloads.chef.io/chef-dk/), simply install the Gem:
27
+
28
+ ```bash
29
+ chef gem install kitchen-vcenter
30
+ ```
31
+
32
+ If you're using bundler, simply add Chef and kitchen-vcenter to your Gemfile:
33
+
34
+ ```ruby
35
+ gem 'chef'
36
+ gem 'kitchen-vcenter'
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ A sample `.kitchen.yml` file, details are below.
42
+
43
+ ```yml
44
+ driver:
45
+ name: vcenter
46
+ vcenter_username: <%= ENV['VCENTER_USER'] || "administrator@vsphere.local" %>
47
+ vcenter_password: <%= ENV['VCENTER_PASSWORD'] || "P@ssw0rd!" %>
48
+ vcenter_host: vcenter.chef.io
49
+ vcenter_disable_ssl_verify: true
50
+ driver_config:
51
+ targethost: 172.16.20.41
52
+ datacenter: "Datacenter"
53
+
54
+ platforms:
55
+ - name: ubuntu-1604
56
+ driver_config:
57
+ template: ubuntu16-template
58
+ - name: centos-7
59
+ driver_config:
60
+ template: centos7-template
61
+
62
+ ```
63
+
64
+ ### Required parameters:
65
+
66
+ The following parameters should be set in the main `driver_config` section as they are common to all platforms.
67
+
68
+ - `vcenter_username` - Name to use when connecting to the vSphere environment
69
+ - `vcenter_password` - Password associated with the specified user
70
+ - `vcenter_host` - Host against which logins should be attempted
71
+ - `vcenter_disable_ssl_verify` - Whether or not to disable SSL verification checks. Good when using self signed certificates. Default: false
72
+
73
+ The following parameters should be set in the `driver_config` for the individual platform.
74
+
75
+ - `targethost` - Host on which the new virtual machine should be created
76
+ - `template` - Template or virtual machine to use when cloning the new machine
77
+ - `datacenter` - Name of the datacenter to use to deploy into
78
+
79
+ ### Optional Parameters
80
+
81
+ The following optional parameters should be used in the `driver_config` for the platform.
82
+
83
+ - `folder` - Folder into which the new machine should be stored. If specified the folder _must_ already exist
84
+
85
+ ## Contributing
86
+
87
+ For information on contributing to this project see <https://github.com/chef/chef/blob/master/CONTRIBUTING.md>
88
+
89
+ ## Development
90
+
91
+ * Report issues/questions/feature requests on [GitHub Issues][issues]
92
+
93
+ Pull requests are very welcome! Make sure your patches are well tested.
94
+ Ideally create a topic branch for every separate change you make. For
95
+ example:
96
+
97
+ 1. Fork the repo
98
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
99
+ 3. Run the tests and rubocop, `bundle exec rake spec` and `bundle exec rake rubocop`
100
+ 4. Commit your changes (`git commit -am 'Added some feature'`)
101
+ 5. Push to the branch (`git push origin my-new-feature`)
102
+ 6. Create new Pull Request
103
+
104
+
105
+ ## License
106
+
107
+ Author:: Russell Seymour ([rseymour@chef.io](mailto:rseymour@chef.io))
108
+
109
+ Author:: JJ Asghar ([jj@chef.io](mailto:jj@chef.io))
110
+
111
+ Copyright:: Copyright (c) 2017 Chef Software, Inc.
112
+
113
+ License:: Apache License, Version 2.0
114
+
115
+ ```text
116
+ Licensed under the Apache License, Version 2.0 (the "License");
117
+ you may not use this file except in compliance with the License.
118
+ You may obtain a copy of the License at
119
+
120
+ http://www.apache.org/licenses/LICENSE-2.0
121
+
122
+ Unless required by applicable law or agreed to in writing, software
123
+ distributed under the License is distributed on an "AS IS" BASIS,
124
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
125
+ See the License for the specific language governing permissions and
126
+ limitations under the License.
127
+ ```
data/lib/base.rb ADDED
@@ -0,0 +1,35 @@
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 'logger'
21
+
22
+ module Base
23
+ attr_accessor :log
24
+
25
+ def self.log
26
+ @log ||= init_logger
27
+ end
28
+
29
+ def self.init_logger
30
+ log = Logger.new(STDOUT)
31
+ log.progname = 'Knife VCenter'
32
+ log.level = Logger::INFO
33
+ log
34
+ end
35
+ end
@@ -0,0 +1,22 @@
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
+ module KitchenVcenter
21
+ VERSION = '1.0.0'.freeze
22
+ end
@@ -0,0 +1,171 @@
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 'kitchen'
21
+ require 'rbvmomi'
22
+ require 'sso'
23
+ require 'base'
24
+ require 'lookup_service_helper'
25
+ require 'vapi'
26
+ require 'com/vmware/cis'
27
+ require 'com/vmware/vcenter'
28
+ require 'com/vmware/vcenter/vm'
29
+ require 'support/clone_vm'
30
+ require 'securerandom'
31
+
32
+ module Kitchen
33
+ module Driver
34
+ #
35
+ # Vcenter
36
+ #
37
+ class Vcenter < Kitchen::Driver::Base
38
+ attr_accessor :connection_options, :ipaddress, :vapi_config, :session_svc, :session_id
39
+
40
+ default_config :targethost
41
+ default_config :folder
42
+ default_config :template
43
+ default_config :datacenter
44
+ default_config :vcenter_username
45
+ default_config :vcenter_password
46
+ default_config :vcenter_host
47
+ default_config :vcenter_disable_ssl_verify, false
48
+ default_config :poweron, true
49
+ default_config :vm_name, nil
50
+
51
+ def create(state)
52
+ # If the vm_name has not been set then set it now based on the suite, platform and a random number
53
+ if config[:vm_name].nil?
54
+ config[:vm_name] = format('%s-%s-%s', instance.suite.name, instance.platform.name, SecureRandom.hex(4))
55
+ end
56
+
57
+ connect
58
+
59
+ # Using the clone class, create a machine for TK
60
+ # Find the identifier for the targethost to pass to rbvmomi
61
+ config[:targethost] = get_host(config[:targethost])
62
+
63
+ # Same thing needs to happen with the folder name if it has been set
64
+ config[:folder] = {
65
+ name: config[:folder],
66
+ id: get_folder(config[:folder])
67
+ } unless config[:folder].nil?
68
+
69
+ # Create a hash of options that the clone requires
70
+ options = {
71
+ name: config[:vm_name],
72
+ targethost: config[:targethost],
73
+ poweron: config[:poweron],
74
+ template: config[:template],
75
+ datacenter: config[:datacenter],
76
+ folder: config[:folder]
77
+ }
78
+
79
+ # Create an object from which the clone operation can be called
80
+ clone_obj = Support::CloneVm.new(connection_options, options)
81
+ state[:hostname] = clone_obj.clone()
82
+ state[:vm_name] = config[:vm_name]
83
+ end
84
+
85
+ def destroy(state)
86
+ return if state[:vm_name].nil?
87
+ connect
88
+ vm = get_vm(state[:vm_name])
89
+
90
+ vm_obj = Com::Vmware::Vcenter::VM.new(vapi_config)
91
+
92
+ # shut the machine down if it is running
93
+ if vm.power_state.value == "POWERED_ON"
94
+ power = Com::Vmware::Vcenter::Vm::Power.new(vapi_config)
95
+ power.stop(vm.vm)
96
+ end
97
+
98
+ # delete the vm
99
+ vm_obj.delete(vm.vm)
100
+ end
101
+
102
+ private
103
+
104
+ def validate_state(state = {})
105
+
106
+ end
107
+
108
+ def existing_state_value?(state, property)
109
+ state.key?(property) && !state[property].nil?
110
+ end
111
+
112
+ def get_host(name)
113
+ filter = Com::Vmware::Vcenter::Host::FilterSpec.new({names: Set.new([name])})
114
+ host_obj = Com::Vmware::Vcenter::Host.new(vapi_config)
115
+ host = host_obj.list
116
+ host[0].host
117
+ end
118
+
119
+ def get_folder(name)
120
+ # Create a filter to ensure that only the named folder is returned
121
+ filter = Com::Vmware::Vcenter::Folder::FilterSpec.new({names: Set.new([name])})
122
+ # filter.names = name
123
+ folder_obj = Com::Vmware::Vcenter::Folder.new(vapi_config)
124
+ folder = folder_obj.list(filter)
125
+
126
+ folder[0].folder
127
+ end
128
+
129
+ def get_vm(name)
130
+ filter = Com::Vmware::Vcenter::VM::FilterSpec.new({names: Set.new([name])})
131
+ vm_obj = Com::Vmware::Vcenter::VM.new(vapi_config)
132
+ vm_obj.list(filter)[0]
133
+ end
134
+
135
+ def connect
136
+ # Configure the connection to vCenter
137
+ lookup_service_helper = LookupServiceHelper.new(config[:vcenter_host])
138
+ vapi_urls = lookup_service_helper.find_vapi_urls()
139
+ vapi_url = vapi_urls.values[0]
140
+
141
+ # Create the VAPI config object
142
+ ssl_options = {}
143
+ ssl_options[:verify] = config[:vcenter_disable_ssl_verify] ? :none : :peer
144
+ @vapi_config = VAPI::Bindings::VapiConfig.new(vapi_url, ssl_options)
145
+
146
+ # get the SSO url
147
+ sso_url = lookup_service_helper.find_sso_url()
148
+ sso = SSO::Connection.new(sso_url).login(config[:vcenter_username], config[:vcenter_password])
149
+ token = sso.request_bearer_token()
150
+ vapi_config.set_security_context(
151
+ VAPI::Security.create_saml_bearer_security_context(token.to_s)
152
+ )
153
+
154
+ # Login and get the session information
155
+ @session_svc = Com::Vmware::Cis::Session.new(vapi_config)
156
+ @session_id = session_svc.create()
157
+ vapi_config.set_security_context(
158
+ VAPI::Security.create_session_security_context(session_id)
159
+ )
160
+
161
+ # Configure the hash for use when connecting for cloning a machine
162
+ @connection_options = {
163
+ user: config[:vcenter_username],
164
+ password: config[:vcenter_password],
165
+ insecure: config[:vcenter_disable_ssl_verify] ? true : false,
166
+ host: config[:vcenter_host],
167
+ }
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,463 @@
1
+ # Copyright 2014-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ require 'savon'
5
+ require 'nokogiri'
6
+ require 'base'
7
+ # require 'sample/framework/sample_base'
8
+
9
+ # Utility class that helps use the lookup service.
10
+ class LookupServiceHelper
11
+
12
+ attr_reader :sample, :wsdl_url, :soap_url
13
+ attr_reader :serviceRegistration
14
+
15
+ # Constructs a new instance.
16
+ # @param sample [SampleBase] the associated sample, which provides access
17
+ # to the configuration properties of the sample
18
+ def initialize(host)
19
+
20
+ @soap_url = format("https://%s/lookupservice/sdk", host)
21
+ @wsdl_url = format("https://%s/lookupservice/wsdl/lookup.wsdl", host)
22
+
23
+ end
24
+
25
+ # Connects to the lookup service.
26
+ def connect
27
+ rsc = RetrieveServiceContent.new(client).invoke()
28
+ @serviceRegistration = rsc.get_service_registration()
29
+ Base.log.info "service registration = #{serviceRegistration}"
30
+ end
31
+
32
+ # Finds the SSO service URL.
33
+ # In a MxN setup where there are more than one PSC nodes;
34
+ # This method returns the first SSO service endpoint URL
35
+ # as returned by the lookup service.
36
+ #
37
+ # @return [String] SSO Service endpoint URL.
38
+ def find_sso_url
39
+ result = find_service_url(product='com.vmware.cis',
40
+ service='cs.identity',
41
+ endpoint='com.vmware.cis.cs.identity.sso',
42
+ protocol='wsTrust')
43
+ raise 'SSO URL not found' unless result && result.size > 0
44
+ return result.values[0]
45
+ end
46
+
47
+ # Finds all the vAPI service endpoint URLs.
48
+ # In a MxN setup where there are more than one management node;
49
+ # this method returns more than one URL
50
+ #
51
+ # @return [Hash] vapi service endpoint URLs in a dictionary
52
+ # where the key is the node_id and the value is the service URL.
53
+ def find_vapi_urls
54
+ return find_service_url(product='com.vmware.cis',
55
+ service='cs.vapi',
56
+ endpoint='com.vmware.vapi.endpoint',
57
+ protocol='vapi.json.https.public')
58
+ end
59
+
60
+ # Finds the vapi service endpoint URL of a management node.
61
+ #
62
+ # @param node_id [String] The UUID of the management node.
63
+ # @return [String] vapi service endpoint URL of a management node or
64
+ # nil if no vapi endpoint is found.
65
+ def find_vapi_url(node_id)
66
+ raise 'node_id is required' if node_id.nil?
67
+ result = find_vapi_urls()
68
+ raise 'VAPI URLs not found' unless result && result.size > 0
69
+ return result[node_id]
70
+ end
71
+
72
+ # Finds all the vim service endpoint URLs
73
+ # In a MxN setup where there are more than one management node;
74
+ # this method returns more than one URL
75
+ #
76
+ # @return [Hash] vim service endpoint URLs in a dictionary where
77
+ # the key is the node_id and the value is the service URL.
78
+ def find_vim_urls
79
+ return find_service_url(product='com.vmware.cis',
80
+ service='vcenterserver',
81
+ endpoint='com.vmware.vim',
82
+ protocol='vmomi')
83
+ end
84
+
85
+ # Finds the vim service endpoint URL of a management node
86
+ #
87
+ # @param node_id [String] The UUID of the management node.
88
+ # @return [String] vim service endpoint URL of a management node or
89
+ # nil if no vim endpoint is found.
90
+ def find_vim_url(node_id)
91
+ raise 'node_id is required' if node_id.nil?
92
+ result = find_vim_urls()
93
+ raise 'VIM URLs not found' unless result && result.size > 0
94
+ return result[node_id]
95
+ end
96
+
97
+ # Finds all the spbm service endpoint URLs
98
+ # In a MxN setup where there are more than one management node;
99
+ # this method returns more than one URL
100
+ #
101
+ # @return [Hash] spbm service endpoint URLs in a dictionary where
102
+ # the key is the node_id and the value is the service URL.
103
+ def find_vim_pbm_urls
104
+ return find_service_url(product='com.vmware.vim.sms',
105
+ service='sms',
106
+ endpoint='com.vmware.vim.pbm',
107
+ protocol='https')
108
+ end
109
+
110
+ # Finds the spbm service endpoint URL of a management node
111
+ #
112
+ # @param node_id [String] The UUID of the management node.
113
+ # @return [String] spbm service endpoint URL of a management node or
114
+ # nil if no spbm endpoint is found.
115
+ def find_vim_pbm_url(node_id)
116
+ raise 'node_id is required' if node_id.nil?
117
+ result = find_vim_pbm_urls()
118
+ raise 'PBM URLs not found' unless result && result.size > 0
119
+ return result[node_id]
120
+ end
121
+
122
+ # Get the management node id from the instance name
123
+ #
124
+ # @param instance_name [String] The instance name of the management node
125
+ # @return [String] The UUID of the management node or
126
+ # nil is no management node is found by the given instance name
127
+ def get_mgmt_node_id(instance_name)
128
+ raise 'instance_name is required' if instance_name.nil?
129
+ result = find_mgmt_nodes()
130
+ raise 'Management nodes not found' unless result && result.size > 0
131
+ return result[instance_name]
132
+ end
133
+
134
+ def get_mgmt_node_instance_name(node_id)
135
+ raise 'node_id is required' if node_id.nil?
136
+ result = find_mgmt_nodes()
137
+ raise 'Management nodes not found' unless result && result.size > 0
138
+ result.each { |k, v| return k if v == node_id }
139
+ nil
140
+ end
141
+
142
+ # Finds the instance name and UUID of the management node for M1xN1 or
143
+ # when the PSC and management services all reside on a single node.
144
+ def get_default_mgmt_node
145
+ result = find_mgmt_nodes()
146
+ raise 'Management nodes not found' unless result && result.size > 0
147
+ #WHY: raise MultipleManagementNodeException.new if result.size > 1
148
+ return [result.keys[0], result.values[0]]
149
+ end
150
+
151
+ # Finds all the management nodes
152
+ #
153
+ # @return [Hash] management node instance name and node id (UUID) in a dictionary.
154
+ def find_mgmt_nodes
155
+ #assert self.serviceRegistration is not None
156
+ list = List.new(client, 'com.vmware.cis', 'vcenterserver',
157
+ 'vmomi', 'com.vmware.vim')
158
+
159
+ list.invoke()
160
+ list.get_instance_names()
161
+ end
162
+
163
+ private
164
+
165
+ # Finds a service URL with the given attributes.
166
+ def find_service_url(product, service, endpoint, protocol)
167
+ #assert serviceRegistration is not None
168
+ list = List.new(client, product, service, protocol, endpoint)
169
+
170
+ list.invoke()
171
+ list.get_service_endpoints()
172
+ end
173
+
174
+ # Gets or creates the Savon client instance.
175
+ def client
176
+ @client ||= Savon.client do |globals|
177
+ # see: http://savonrb.com/version2/globals.html
178
+ globals.wsdl wsdl_url
179
+ globals.endpoint soap_url
180
+
181
+ globals.strip_namespaces false
182
+ globals.env_namespace :S
183
+
184
+ # set like this so https connection does not fail
185
+ # TODO: find an acceptable solution for production
186
+ globals.ssl_verify_mode :none
187
+
188
+ # dev/debug settings
189
+ # globals.pretty_print_xml ENV['DEBUG_SOAP']
190
+ # globals.log ENV['DEBUG_SOAP']
191
+ end
192
+ end
193
+ end
194
+
195
+
196
+ # @abstract Base class for invocable service calls.
197
+ class Invocable
198
+
199
+ attr_reader :operation, :client, :response
200
+
201
+ # Constructs a new instance.
202
+ # @param operation [Symbol] the operation name
203
+ # @param client [Savon::Client] the client
204
+ def initialize(operation, client)
205
+ @operation = operation
206
+ @client = client
207
+ end
208
+
209
+ # Invokes the service call represented by this type.
210
+ def invoke
211
+ request = request_xml.to_s
212
+ Base.log.debug(request)
213
+ @response = client.call(operation, xml:request)
214
+ Base.log.debug(response)
215
+ self # for chaining with new
216
+ end
217
+
218
+ # Builds the request XML content.
219
+ def request_xml
220
+ builder = Builder::XmlMarkup.new()
221
+ builder.instruct!(:xml, encoding: "UTF-8")
222
+
223
+ builder.tag!("S:Envelope",
224
+ "xmlns:S" => "http://schemas.xmlsoap.org/soap/envelope/") do |envelope|
225
+ envelope.tag!("S:Body") do |body|
226
+ body_xml(body)
227
+ end
228
+ end
229
+ builder.target!
230
+ end
231
+
232
+ # Builds the body portion of the request XML content.
233
+ # Specific service operations must override this method.
234
+ def body_xml
235
+ raise 'abstract method not implemented!'
236
+ end
237
+
238
+ # Gets the response XML content.
239
+ def response_xml
240
+ raise 'illegal state: response not set yet' if response.nil?
241
+ @response_xml ||= Nokogiri::XML(response.to_xml)
242
+ end
243
+
244
+ def response_hash
245
+ @response_hash ||= response.to_hash
246
+ end
247
+ end
248
+
249
+ # Encapsulates the list operation of the lookup service.
250
+ class List < Invocable
251
+
252
+ # Constructs a new instance.
253
+ def initialize(client, product, service, protocol, endpoint)
254
+ super(:list, client)
255
+
256
+ @product = product
257
+ @service = service
258
+ @protocol = protocol
259
+ @endpoint = endpoint
260
+ end
261
+
262
+ =begin
263
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
264
+ <S:Body>
265
+ <List xmlns="urn:lookup">
266
+ <_this type="LookupServiceRegistration">ServiceRegistration</_this>
267
+ <filterCriteria>
268
+ <serviceType>
269
+ <product>com.vmware.cis</product>
270
+ <type>cs.identity</type>
271
+ </serviceType>
272
+ <endpointType>
273
+ <protocol>wsTrust</protocol>
274
+ <type>com.vmware.cis.cs.identity.sso</type>
275
+ </endpointType>
276
+ </filterCriteria>
277
+ </List>
278
+ </S:Body>
279
+ </S:Envelope>
280
+ =end
281
+ def body_xml(body)
282
+ body.tag!("List", "xmlns" => "urn:lookup") do |list|
283
+ #TODO: use the copy that was retrieved on startup?
284
+ list.tag!("_this",
285
+ "type" => "LookupServiceRegistration") do |this|
286
+ this << "ServiceRegistration"
287
+ end
288
+ list.tag!("filterCriteria") do |criteria|
289
+ criteria.tag!("serviceType") do |stype|
290
+ stype.tag!("product") do |p|
291
+ p << @product
292
+ end
293
+ stype.tag!("type") do |t|
294
+ t << @service
295
+ end
296
+ end
297
+ criteria.tag!("endpointType") do |etype|
298
+ etype.tag!("protocol") do |p|
299
+ p << @protocol
300
+ end
301
+ etype.tag!("type") do |t|
302
+ t << @endpoint
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ # Gets the service endpoint information from the response.
310
+ # Support for MxN.
311
+ # @return [Hash] a hash where the key is NodeId and the Value is a Service URL
312
+ def get_service_endpoints
313
+ result = {}
314
+ =begin
315
+ <ListResponse xmlns="urn:lookup">
316
+ <returnval>
317
+ <serviceVersion>2.0</serviceVersion>
318
+ <vendorNameResourceKey/>
319
+ <vendorNameDefault/>
320
+ <vendorProductInfoResourceKey/>
321
+ <vendorProductInfoDefault/>
322
+ <serviceEndpoints>
323
+ <url>https://pa-rdinfra3-vm7-dhcp5583.eng.vmware.com/sts/STSService/vsphere.local</url>
324
+ <endpointType>
325
+ <protocol>wsTrust</protocol>
326
+ <type>com.vmware.cis.cs.identity.sso</type>
327
+ </endpointType>
328
+ <sslTrust>
329
+ ...
330
+ </sslTrust>
331
+ </serviceEndpoints>
332
+ <serviceNameResourceKey/>
333
+ <serviceNameDefault/>
334
+ <serviceDescriptionResourceKey/>
335
+ <serviceDescriptionDefault/>
336
+ <ownerId>pa-rdinfra3-vm7-dhcp5583.eng.vmware.com@vsphere.local</ownerId>
337
+ <serviceType>
338
+ <product>com.vmware.cis</product>
339
+ <type>cs.identity</type>
340
+ </serviceType>
341
+ <nodeId/>
342
+ <serviceId>6a8a5058-5d3d-4d42-bb5e-383b91c8732e</serviceId>
343
+ <siteId>default-first-site</siteId>
344
+ </returnval>
345
+ </ListResponse>
346
+ =end
347
+ Base.log.debug "List: response_hash = #{response_hash}"
348
+ return_val = response_hash[:list_response][:returnval]
349
+ return_val = [return_val] if return_val.is_a? Hash
350
+ return_val.each { |entry|
351
+ #FYI: the node_id is sometimes null, so use the service_id in this case
352
+ node_id = entry[:node_id] || entry[:service_id]
353
+ result[node_id] = entry[:service_endpoints][:url]
354
+ }
355
+ Base.log.debug "List: result = #{result}"
356
+ return result
357
+ end
358
+
359
+ def get_instance_names
360
+ result = {}
361
+ =begin
362
+ <serviceAttributes>
363
+ <key>com.vmware.cis.cm.GroupInternalId</key>
364
+ <value>com.vmware.vim.vcenter</value>
365
+ </serviceAttributes>
366
+ <serviceAttributes>
367
+ <key>com.vmware.cis.cm.ControlScript</key>
368
+ <value>vmware-vpxd.sh</value>
369
+ </serviceAttributes>
370
+ <serviceAttributes>
371
+ <key>com.vmware.cis.cm.HostId</key>
372
+ <value>906477a1-24c6-4d48-9e99-55ef962878f7</value>
373
+ </serviceAttributes>
374
+ <serviceAttributes>
375
+ <key>com.vmware.vim.vcenter.instanceName</key>
376
+ <value>pa-rdinfra3-vm7-dhcp5583.eng.vmware.com</value>
377
+ </serviceAttributes>
378
+ =end
379
+ Base.log.debug "List: response_hash = #{response_hash}"
380
+ return_val = response_hash[:list_response][:returnval]
381
+ return_val = [return_val] if return_val.is_a? Hash
382
+ return_val.each { |entry|
383
+ node_id = entry[:node_id]
384
+ #TODO: is it possible there be 0 or 1 attrs? if so, deal with it.
385
+ attrs = entry[:service_attributes]
386
+ Base.log.debug "List: attrs=#{attrs}"
387
+ attrs.each { |attr|
388
+ if attr[:key] == 'com.vmware.vim.vcenter.instanceName'
389
+ result[attr[:value]] = node_id
390
+ end
391
+ }
392
+ }
393
+ Base.log.debug "List: result = #{result}"
394
+ return result
395
+ end
396
+ end
397
+
398
+ # Encapsulates the RetrieveServiceContent operation of the lookup service.
399
+ class RetrieveServiceContent < Invocable
400
+
401
+ # Constructs a new instance.
402
+ def initialize(client)
403
+ super(:retrieve_service_content, client)
404
+ end
405
+
406
+ =begin
407
+ <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
408
+ <S:Body>
409
+ <RetrieveServiceContent xmlns="urn:lookup">
410
+ <_this type="LookupServiceInstance">ServiceInstance</_this>
411
+ </RetrieveServiceContent>
412
+ </S:Body>
413
+ </S:Envelope>
414
+ =end
415
+ def body_xml(body)
416
+ body.tag!("RetrieveServiceContent", "xmlns" => "urn:lookup") do |rsc|
417
+ rsc.tag!("_this", "type" => "LookupServiceInstance") do |this|
418
+ this << "ServiceInstance"
419
+ end
420
+ end
421
+ end
422
+
423
+ =begin
424
+ ...
425
+ <RetrieveServiceContentResponse xmlns="urn:lookup">
426
+ <returnval>
427
+ <lookupService type="LookupLookupService">lookupService</lookupService>
428
+ <serviceRegistration type="LookupServiceRegistration">ServiceRegistration</serviceRegistration>
429
+ <deploymentInformationService type="LookupDeploymentInformationService">deploymentInformationService</deploymentInformationService>
430
+ <l10n type="LookupL10n">l10n</l10n>
431
+ </returnval>
432
+ </RetrieveServiceContentResponse>
433
+ ...
434
+ =end
435
+ def get_service_registration
436
+ Base.log.debug "RetrieveServiceContent: response_hash = #{response_hash}"
437
+ return_val = response_hash[:retrieve_service_content_response][:returnval]
438
+ result = return_val[:service_registration]
439
+ Base.log.debug "RetrieveServiceContent: result = #{result}"
440
+ result
441
+ end
442
+ end
443
+
444
+ class MultipleManagementNodeException < Exception
445
+ end
446
+
447
+ # main: quick self tester
448
+ if __FILE__ == $0
449
+ Base.log.level = Logger::DEBUG if ENV['DEBUG']
450
+ sample = SelfTestSample.new
451
+ sample.ls_ip = ARGV[0] || '10.67.245.207'
452
+ #MXN: sample.ls_ip = '10.160.42.83'
453
+ #MXN: sample.ls_ip = '10.160.35.191'
454
+ #MAYBE: sample.main() # for arg parsing
455
+ ls_helper = LookupServiceHelper.new(sample)
456
+ ls_helper.connect()
457
+ puts '***************************************'
458
+ puts "SSO URL: #{ls_helper.find_sso_url()}"
459
+ puts "VAPI URL: #{ls_helper.find_vapi_urls()}"
460
+ puts "VIM URL: #{ls_helper.find_vim_urls()}"
461
+ puts "PBM URL: #{ls_helper.find_vim_pbm_urls()}"
462
+ puts "Mgmt Nodes: #{ls_helper.find_mgmt_nodes()}"
463
+ end
data/lib/sso.rb ADDED
@@ -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,53 @@
1
+ require 'rbvmomi'
2
+
3
+ class Support
4
+ class CloneVm
5
+ attr_reader :vim, :options
6
+
7
+ def initialize(conn_opts, options)
8
+ @options = options
9
+
10
+ # Connect to vSphere
11
+ @vim ||= RbVmomi::VIM.connect conn_opts
12
+ end
13
+
14
+ def clone
15
+
16
+ # set the datacenter name
17
+ dc = vim.serviceInstance.find_datacenter(options[:datacenter])
18
+ src_vm = dc.find_vm(options[:template])
19
+ hosts = dc.hostFolder.children
20
+
21
+ # Specify where the machine is going to be created
22
+ relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec
23
+ relocate_spec.host = options[:targethost]
24
+ relocate_spec.pool = hosts.first.resourcePool
25
+
26
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(location: relocate_spec,
27
+ powerOn: options[:poweron],
28
+ template: false)
29
+
30
+ # Set the folder to use
31
+ dest_folder = options[:folder].nil? ? src_vm.parent : options[:folder][:id]
32
+
33
+ puts "Cloning the template #{options[:template]} to create the VM..."
34
+ task = src_vm.CloneVM_Task(folder: dest_folder, name: options[:name], spec: clone_spec)
35
+ task.wait_for_completion
36
+
37
+ # get the IP address of the machine for bootstrapping
38
+ # machine name is based on the path, e.g. that includes the folder
39
+ name = options[:folder].nil? ? options[:name] : format("%s/%s", options[:folder][:name], options[:name])
40
+ new_vm = dc.find_vm(name)
41
+
42
+ if new_vm.nil?
43
+ puts format("Unable to find machine: %s", name)
44
+ else
45
+ puts 'Waiting for network interfaces to become available...'
46
+ sleep 2 while new_vm.guest.net.empty? || !new_vm.guest.ipAddress
47
+ new_vm.guest.net[0].ipConfig.ipAddress.detect do |addr|
48
+ addr.origin != 'linklayer'
49
+ end.ipAddress
50
+ end
51
+ end
52
+ end
53
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitchen-vcenter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Russell Seymour
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: test-kitchen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: vsphere-automation-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.5'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rbvmomi
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.11'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: savon
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.11'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: github_changelog_generator
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '10.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '10.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.49'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.49'
125
+ description: Test Kitchen driver for VMware vCenter using SDK
126
+ email:
127
+ - russell@chef.io
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - CHANGELOG.md
133
+ - README.md
134
+ - lib/base.rb
135
+ - lib/kitchen-vcenter/version.rb
136
+ - lib/kitchen/driver/vcenter.rb
137
+ - lib/lookup_service_helper.rb
138
+ - lib/sso.rb
139
+ - lib/support/clone_vm.rb
140
+ homepage: https://github.com/chef/kitchen-vcenter
141
+ licenses:
142
+ - Apache-2.0
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.6.11
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Test Kitchen driver for VMare vCenter
164
+ test_files: []