kitchen-vcenter 1.0.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 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: []