kitchen-vro 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8490c96d7b62dc70c78d8ec753584f9f15b851d4
4
+ data.tar.gz: 1586d1bc1fd23498c259a11384d2fcdc246f9f00
5
+ SHA512:
6
+ metadata.gz: 26f14f71b98940f198a7059e124c37b5127991dd6c40585e97e9a0863274b46c243c1baabf3f560f2a9330c487dcd1f6e586845d4152b124d6bac8b7cb8bdc6d
7
+ data.tar.gz: 585ff5759186e6f087d91b934ae334835cfe384241252265aa254ab0b8cf49efd82cfd384ec4a82a40bdcdcee5c2506856b86ff9724b27afcdb1b9116eedefaf
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,14 @@
1
+ Metrics/AbcSize:
2
+ Max: 50
3
+ Metrics/ClassLength:
4
+ Max: 125
5
+ Metrics/LineLength:
6
+ Max: 130
7
+ Metrics/MethodLength:
8
+ Max: 25
9
+ Style/Documentation:
10
+ Enabled: false
11
+ Style/SignalException:
12
+ Enabled: false
13
+ Style/SpaceInsideBrackets:
14
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kitchen-vro.gemspec
4
+ gemspec
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,140 @@
1
+ # kitchen-vro
2
+
3
+ A driver to allow Test Kitchen to consume VMware resources provisioned by
4
+ way of vRealize Orchestrator workflows.
5
+
6
+ vRO allows you to create any workflow you can imagine, but our plugin needs
7
+ to make some assumptions about the workflows you design in order to properly
8
+ provision and destroy resources. Therefore, please pay special attention to
9
+ the **Workflow Design** section below and ensure your workflows adhere to
10
+ these design requirements.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'kitchen-vro'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install kitchen-vro
27
+
28
+ Or even better, install it via ChefDK:
29
+
30
+ $ chef gem install kitchen-vro
31
+
32
+ ## Usage
33
+
34
+ After installing the gem as described above, edit your .kitchen.yml file to set the driver to 'vro' and supply your login credentials:
35
+
36
+ ```yaml
37
+ driver:
38
+ name: vro
39
+ vro_username: user@domain.com
40
+ vro_password: MyS33kretPassword
41
+ vro_base_url: https://vra.corp.local:8281
42
+ ```
43
+
44
+ Additionally, the following parameters are required, either globally or per-platform:
45
+
46
+ * **create_workflow_name**: The name of the vRO workflow to execute to create a server.
47
+ * **destroy_workflow_name**: The name of the vRO workflow to execute to destroy a server.
48
+
49
+ There are a number of optional parameters you can configure as well:
50
+
51
+ * **create_workflow_id**: If your create workflow name is not unique within vRO, you can use
52
+ this parameter to specify the workflow unique ID.
53
+ * **destroy_workflow_id**: If your destroy workflow name is not unique within vRO, you can use
54
+ this parameter to specify the workflow unique ID.
55
+ * **create_workflow_parameters**: A hash of key-value pairs of parameters to pass to your
56
+ create workflow.
57
+ * **destroy_workflow_parameters**: A hash of key-value pairs of parameters to pass to your
58
+ destroy workflow.
59
+ * **request_timeout**: Number of seconds to wait for a vRO workflow to execute. Default: 300
60
+ * **vro_disable_ssl_verify**: Disable SSL validation. Default: false
61
+
62
+ An example `.kitchen.yml` that uses a combination of global and per-platform
63
+ settings might look like this:
64
+
65
+ ```yaml
66
+ driver:
67
+ name: vro
68
+ vro_username: user@domain.com
69
+ vro_password: MyS33kretPassword
70
+ vro_base_url: https://vra.corp.local:8281
71
+ create_workflow_name: Create TK Server
72
+ destroy_workflow_name: Destroy TK Server
73
+
74
+ platforms:
75
+ - name: centos
76
+ driver:
77
+ create_workflow_parameters:
78
+ os_name: centos
79
+ os_version: 6.7
80
+ - name: windows
81
+ driver:
82
+ create_workflow_parameters:
83
+ os_name: windows
84
+ os_version: server2012
85
+ cpus: 4
86
+ memory: 4096
87
+ ```
88
+
89
+ ## Workflow Design
90
+
91
+ There are no limits as to what you can do with your vRO workflows! However,
92
+ they must meet the following requirements.
93
+
94
+ ### Create Workflow
95
+
96
+ * Must contain an output parameter called `ip_address` that Test Kitchen can
97
+ connect to in order to bootstrap and test your node.
98
+ * Must contain an output parameter called `server_id` that is a unique ID of
99
+ the server created. Test Kitchen will provide this value to the Destroy
100
+ Workflow in order to request the destruction of the server once testing is
101
+ complete.
102
+ * Must end the workflow with a raised exception if the creation did not
103
+ succeed. The workflow status must not be 'completed.'
104
+
105
+ ### Destroy Workflow
106
+
107
+ * Must contain an input parameter called `server_id` that Test Kitchen will
108
+ populate with the unique ID returned from the Create Workflow output.
109
+ * Must end the workflow with a raised exception if the creation did not
110
+ succeed. The workflow status must not be 'completed.'
111
+
112
+ ## License and Authors
113
+
114
+ Author:: Chef Partner Engineering (<partnereng@chef.io>)
115
+
116
+ Copyright:: Copyright (c) 2015 Chef Software, Inc.
117
+
118
+ License:: Apache License, Version 2.0
119
+
120
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
121
+ this file except in compliance with the License. You may obtain a copy of the License at
122
+
123
+ ```
124
+ http://www.apache.org/licenses/LICENSE-2.0
125
+ ```
126
+
127
+ Unless required by applicable law or agreed to in writing, software distributed under the
128
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
129
+ either express or implied. See the License for the specific language governing permissions
130
+ and limitations under the License.
131
+
132
+ ## Contributing
133
+
134
+ We'd love to hear from you if this doesn't perform in the manner you expect. Please log a GitHub issue, or even better, submit a Pull Request with a fix!
135
+
136
+ 1. Fork it ( https://github.com/chef-partners/kitchen-vro/fork )
137
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
138
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
139
+ 4. Push to the branch (`git push origin my-new-feature`)
140
+ 5. Create a new Pull Request
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kitchen/driver/vro_version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'kitchen-vro'
8
+ spec.version = Kitchen::Driver::VRO_VERSION
9
+ spec.authors = ['Chef Partner Engineering']
10
+ spec.email = ['partnereng@chef.io']
11
+ spec.summary = 'A Test Kitchen driver for VMware vRealize Orchestrator (vRO)'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/chef-partners/kitchen-vro'
14
+ spec.license = 'Apache 2.0'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = []
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'test-kitchen', '~> 1.4', '>= 1.4.1'
22
+ spec.add_dependency 'vcoworkflows', '~> 0.2'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.7'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec', '~> 3.2'
27
+ spec.add_development_dependency 'webmock', '~> 1.21'
28
+ end
@@ -0,0 +1,181 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'kitchen'
20
+ require 'vcoworkflows'
21
+ require_relative 'vro_version'
22
+
23
+ module Kitchen
24
+ module Driver
25
+ class Vro < Kitchen::Driver::Base
26
+ attr_accessor :workflow_name, :workflow_id
27
+
28
+ kitchen_driver_api_version 2
29
+ plugin_version Kitchen::Driver::VRO_VERSION
30
+
31
+ required_config :vro_username
32
+ required_config :vro_password
33
+ required_config :vro_base_url
34
+ required_config :create_workflow_name
35
+ required_config :destroy_workflow_name
36
+
37
+ default_config :vro_disable_ssl_verify, false
38
+ default_config :create_workflow_id, nil
39
+ default_config :destroy_workflow_id, nil
40
+ default_config :create_workflow_parameters, {}
41
+ default_config :destroy_workflow_parameters, {}
42
+ default_config :request_timeout, 300
43
+
44
+ def name
45
+ 'vRO'
46
+ end
47
+
48
+ def create(state)
49
+ return unless state[:server_id].nil?
50
+
51
+ info('Executing the create-server workflow...')
52
+ execute_create_workflow(state)
53
+
54
+ info("Server #{state[:hostname]} (#{state[:server_id]}) created. Waiting for it to be ready...")
55
+ wait_for_server(state)
56
+ info("Server #{state[:hostname]} (#{state[:server_id]}) ready.")
57
+ end
58
+
59
+ def destroy(state)
60
+ return if state[:server_id].nil?
61
+
62
+ info("Executing the destroy-server workflow for #{state[:hostname]} (#{state[:server_id]})...")
63
+ execute_destroy_workflow(state)
64
+ info("Server #{state[:hostname]} (#{state[:server_id]}) destroyed.")
65
+ end
66
+
67
+ def vro_config
68
+ @vro_config ||= VcoWorkflows::Config.new(
69
+ url: config[:vro_base_url],
70
+ username: config[:vro_username],
71
+ password: config[:vro_password],
72
+ verify_ssl: verify_ssl?
73
+ )
74
+ end
75
+
76
+ def vro_client
77
+ @vro_client ||= VcoWorkflows::Workflow.new(
78
+ workflow_name,
79
+ id: workflow_id,
80
+ config: vro_config
81
+ )
82
+ end
83
+
84
+ def verify_ssl?
85
+ !config[:vro_disable_ssl_verify]
86
+ end
87
+
88
+ def set_workflow_vars(name, id)
89
+ @vro_client = nil
90
+ @workflow_name = name
91
+ @workflow_id = id
92
+ end
93
+
94
+ def execute_create_workflow(state)
95
+ set_workflow_vars(config[:create_workflow_name], config[:create_workflow_id])
96
+ set_workflow_parameters(config[:create_workflow_parameters])
97
+ execute_workflow
98
+ wait_for_workflow
99
+
100
+ raise 'The workflow did not complete successfully. Check the vRO UI for more info.' unless workflow_successful?
101
+
102
+ validate_create_output_parameters!
103
+
104
+ state[:server_id] = output_parameter_value('server_id')
105
+ state[:hostname] = output_parameter_value('ip_address')
106
+ end
107
+
108
+ def execute_destroy_workflow(state)
109
+ set_workflow_vars(config[:destroy_workflow_name], config[:destroy_workflow_id])
110
+ set_workflow_parameters(config[:destroy_workflow_parameters])
111
+ vro_client.parameter('server_id', state[:server_id])
112
+ execute_workflow
113
+ wait_for_workflow
114
+
115
+ raise 'The workflow did not complete successfully. Check the vRO UI for more info.' unless workflow_successful?
116
+ end
117
+
118
+ def execute_workflow
119
+ vro_client.execute
120
+ rescue RestClient::BadRequest => e
121
+ error("The workflow execution request failed: #{e.response}")
122
+ raise
123
+ rescue => e
124
+ error("The workflow execution request failed: #{e.message}")
125
+ raise
126
+ end
127
+
128
+ def wait_for_workflow
129
+ wait_time = config[:request_timeout]
130
+ Timeout.timeout(wait_time) do
131
+ loop do
132
+ token = vro_client.token
133
+ break unless token.alive?
134
+
135
+ sleep 2
136
+ end
137
+ end
138
+ rescue Timeout::Error
139
+ raise Timeout::Error, "Workflow did not complete in #{wait_time} seconds. Please check the vRO UI for more information."
140
+ end
141
+
142
+ def wait_for_server(state)
143
+ instance.transport.connection(state).wait_until_ready
144
+ rescue
145
+ error("Server #{state[:hostname]} (#{state[:server_id]}) not reachable. Destroying server...")
146
+ destroy(state)
147
+ raise
148
+ end
149
+
150
+ def set_workflow_parameters(data) # rubocop:disable Style/AccessorMethodName
151
+ data.each do |key, value|
152
+ vro_client.parameter(key.to_s, value)
153
+ end
154
+ end
155
+
156
+ def output_parameters
157
+ @output_parameters ||= vro_client.token.output_parameters
158
+ end
159
+
160
+ def output_parameter_value(key)
161
+ output_parameters[key].value.to_s
162
+ end
163
+
164
+ def output_parameter_empty?(key)
165
+ output_parameter_value(key).nil? || output_parameter_value(key).empty?
166
+ end
167
+
168
+ def validate_create_output_parameters!
169
+ raise 'The workflow output did not contain a server_id and ip_address parameter.' unless
170
+ output_parameters.key?('server_id') && output_parameters.key?('ip_address')
171
+
172
+ raise 'The server_id parameter was empty.' if output_parameter_empty?('server_id')
173
+ raise 'The ip_address parameter was empty.' if output_parameter_empty?('ip_address')
174
+ end
175
+
176
+ def workflow_successful?
177
+ vro_client.token.state == 'completed'
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,23 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Kitchen
20
+ module Driver
21
+ VRO_VERSION = '1.0.0'
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ #
2
+ # Author:: Chef Partner Engineering (<partnereng@chef.io>)
3
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
@@ -0,0 +1,462 @@
1
+ # Encoding: UTF-8
2
+ #
3
+ # Authors:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright (c) 2015 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
+ require 'spec_helper'
20
+ require 'kitchen/driver/vro'
21
+ require 'kitchen/provisioner/dummy'
22
+ require 'kitchen/transport/dummy'
23
+ require 'kitchen/verifier/dummy'
24
+
25
+ shared_examples 'output parameters that are missing a required key' do
26
+ it 'raises an exception' do
27
+ allow(driver).to receive(:output_parameters).and_return(output_parameters)
28
+ expect { driver.validate_create_output_parameters! }.to raise_error(
29
+ RuntimeError,
30
+ 'The workflow output did not contain a server_id and ip_address parameter.'
31
+ )
32
+ end
33
+ end
34
+
35
+ describe Kitchen::Driver::Vro do
36
+ let(:logged_output) { StringIO.new }
37
+ let(:logger) { Logger.new(logged_output) }
38
+ let(:platform) { Kitchen::Platform.new(name: 'fake_platform') }
39
+ let(:transport) { Kitchen::Transport::Dummy.new }
40
+ let(:driver) { Kitchen::Driver::Vro.new(config) }
41
+
42
+ let(:config) do
43
+ {
44
+ vro_username: 'myuser',
45
+ vro_password: 'mypassword',
46
+ vro_base_url: 'https://vra.corp.local:8281',
47
+ create_workflow_name: 'Create Workflow',
48
+ create_workflow_id: 'workflow-1',
49
+ destroy_workflow_name: 'Destroy Workflow',
50
+ destroy_workflow_id: 'workflow-2'
51
+ }
52
+ end
53
+
54
+ let(:instance) do
55
+ instance_double(Kitchen::Instance,
56
+ logger: logger,
57
+ transport: transport,
58
+ platform: platform,
59
+ to_str: 'instance_str'
60
+ )
61
+ end
62
+
63
+ before do
64
+ allow(driver).to receive(:instance).and_return(instance)
65
+ end
66
+
67
+ describe '#create' do
68
+ context 'when a server already exists' do
69
+ let(:state) { { server_id: 'server-12345' } }
70
+
71
+ it 'does not create the server' do
72
+ expect(driver).not_to receive(:execute_create_workflow)
73
+
74
+ driver.create(state)
75
+ end
76
+ end
77
+
78
+ let(:state) { {} }
79
+
80
+ it 'calls the expected methods' do
81
+ expect(driver).to receive(:execute_create_workflow).with(state)
82
+ expect(driver).to receive(:wait_for_server).with(state)
83
+
84
+ driver.create(state)
85
+ end
86
+ end
87
+
88
+ describe '#destroy' do
89
+ context 'when a server does not exist' do
90
+ let(:state) { {} }
91
+
92
+ it 'does not destroy the server' do
93
+ expect(driver).not_to receive(:execute_destroy_workflow)
94
+
95
+ driver.destroy(state)
96
+ end
97
+ end
98
+
99
+ let(:state) { { server_id: 'server-12345' } }
100
+
101
+ it 'calls the expected methods' do
102
+ expect(driver).to receive(:execute_destroy_workflow).with(state)
103
+ driver.destroy(state)
104
+ end
105
+ end
106
+
107
+ describe '#vro_config' do
108
+ it 'creates a VcoWorkflows::Config object' do
109
+ expect(VcoWorkflows::Config).to receive(:new).with(url: 'https://vra.corp.local:8281',
110
+ username: 'myuser',
111
+ password: 'mypassword',
112
+ verify_ssl: true)
113
+ driver.vro_config
114
+ end
115
+ end
116
+
117
+ describe '#vro_client' do
118
+ let(:vro_config) { double('vro_config') }
119
+ it 'creates a VcoWorkflows::Workflow object' do
120
+ allow(driver).to receive(:workflow_name).and_return('workflow name')
121
+ allow(driver).to receive(:workflow_id).and_return('workflow-12345')
122
+ allow(driver).to receive(:vro_config).and_return(vro_config)
123
+
124
+ expect(VcoWorkflows::Workflow).to receive(:new).with('workflow name',
125
+ id: 'workflow-12345',
126
+ config: vro_config)
127
+
128
+ driver.vro_client
129
+ end
130
+ end
131
+
132
+ describe '#verify_ssl?' do
133
+ context 'when vro_disable_ssl_verify is true' do
134
+ before do
135
+ config[:vro_disable_ssl_verify] = true
136
+ end
137
+
138
+ it 'returns false' do
139
+ expect(driver.verify_ssl?).to eq(false)
140
+ end
141
+ end
142
+
143
+ context 'when vro_disable_ssl_verify is false' do
144
+ before do
145
+ config[:vro_disable_ssl_verify] = true
146
+ end
147
+
148
+ it 'returns true' do
149
+ expect(driver.verify_ssl?).to eq(false)
150
+ end
151
+ end
152
+ end
153
+
154
+ describe '#execute_create_workflow' do
155
+ let(:state) { {} }
156
+ let(:server_id) { double('server_id', value: 'server-12345') }
157
+ let(:ip_address) { double('ip_address', value: '1.2.3.4') }
158
+ let(:output_parameters) do
159
+ {
160
+ 'server_id' => server_id,
161
+ 'ip_address' => ip_address
162
+ }
163
+ end
164
+
165
+ before do
166
+ allow(driver).to receive(:set_workflow_vars)
167
+ allow(driver).to receive(:set_workflow_parameters)
168
+ allow(driver).to receive(:execute_workflow)
169
+ allow(driver).to receive(:wait_for_workflow)
170
+ allow(driver).to receive(:workflow_successful?).and_return(true)
171
+ allow(driver).to receive(:validate_create_output_parameters!)
172
+ allow(driver).to receive(:output_parameters).and_return(output_parameters)
173
+ end
174
+
175
+ it 'calls the expected methods' do
176
+ expect(driver).to receive(:set_workflow_vars).with('Create Workflow', 'workflow-1')
177
+ expect(driver).to receive(:set_workflow_parameters).with({})
178
+ expect(driver).to receive(:execute_workflow)
179
+ expect(driver).to receive(:wait_for_workflow)
180
+ expect(driver).to receive(:workflow_successful?)
181
+
182
+ driver.execute_create_workflow(state)
183
+ end
184
+
185
+ it 'raises an error if the workflow did not complete successfully' do
186
+ allow(driver).to receive(:workflow_successful?).and_return(false)
187
+ expect { driver.execute_create_workflow(state) }.to raise_error(RuntimeError)
188
+ end
189
+
190
+ it 'sets the state hash with the proper info' do
191
+ driver.execute_create_workflow(state)
192
+
193
+ expect(state[:server_id]).to eq('server-12345')
194
+ expect(state[:hostname]).to eq('1.2.3.4')
195
+ end
196
+ end
197
+
198
+ describe '#execute_destroy_workflow' do
199
+ let(:state) { { server_id: 'server-12345' } }
200
+ let(:vro_client) { double('vro_client') }
201
+
202
+ before do
203
+ allow(driver).to receive(:vro_client).and_return(vro_client)
204
+ allow(vro_client).to receive(:parameter)
205
+ allow(driver).to receive(:set_workflow_vars)
206
+ allow(driver).to receive(:set_workflow_parameters)
207
+ allow(driver).to receive(:execute_workflow)
208
+ allow(driver).to receive(:wait_for_workflow)
209
+ allow(driver).to receive(:workflow_successful?).and_return(true)
210
+ end
211
+
212
+ it 'calls the expected methods' do
213
+ expect(driver).to receive(:set_workflow_vars).with('Destroy Workflow', 'workflow-2')
214
+ expect(driver).to receive(:set_workflow_parameters).with({})
215
+ expect(vro_client).to receive(:parameter).with('server_id', 'server-12345')
216
+ expect(driver).to receive(:execute_workflow)
217
+ expect(driver).to receive(:wait_for_workflow)
218
+ expect(driver).to receive(:workflow_successful?)
219
+
220
+ driver.execute_destroy_workflow(state)
221
+ end
222
+
223
+ it 'raises an error if the workflow did not complete successfully' do
224
+ allow(driver).to receive(:workflow_successful?).and_return(false)
225
+ expect { driver.execute_destroy_workflow(state) }.to raise_error(RuntimeError)
226
+ end
227
+ end
228
+
229
+ describe '#execute_workflow' do
230
+ let(:vro_client) { double('vro_client') }
231
+ before do
232
+ allow(driver).to receive(:vro_client).and_return(vro_client)
233
+ end
234
+
235
+ it 'executes the workflow' do
236
+ expect(vro_client).to receive(:execute)
237
+ driver.execute_workflow
238
+ end
239
+
240
+ context 'when execute fails with a RestClient::BadRequest' do
241
+ it 'prints an error with the HTTP response' do
242
+ HTTPResponse = Struct.new(:code, :to_s)
243
+ response = HTTPResponse.new(400, 'an HTTP error occurred')
244
+ exception = RestClient::BadRequest.new
245
+ exception.response = response
246
+ allow(vro_client).to receive(:execute).and_raise(exception)
247
+ expect(driver).to receive(:error).with('The workflow execution request failed: an HTTP error occurred')
248
+ expect { driver.execute_workflow }.to raise_error(RestClient::BadRequest)
249
+ end
250
+ end
251
+
252
+ context 'when execute fails with any other exception' do
253
+ it 'prints an error with the exception message' do
254
+ allow(vro_client).to receive(:execute).and_raise(RuntimeError, 'a non-HTTP error occurred')
255
+ expect(driver).to receive(:error).with('The workflow execution request failed: a non-HTTP error occurred')
256
+ expect { driver.execute_workflow }.to raise_error(RuntimeError)
257
+ end
258
+ end
259
+ end
260
+
261
+ describe '#wait_for_workflow' do
262
+ let(:vro_client) { double('vro_client') }
263
+ let(:token) { double('token') }
264
+
265
+ before do
266
+ allow(driver).to receive(:vro_client).and_return(vro_client)
267
+ allow(vro_client).to receive(:token).and_return(token)
268
+
269
+ # don't actually sleep
270
+ allow(driver).to receive(:sleep)
271
+ end
272
+
273
+ context 'when the requests completes normally, 3 loops' do
274
+ it 'only fetches the token 3 times' do
275
+ allow(token).to receive(:alive?).exactly(3).times.and_return(true, true, false)
276
+ expect(vro_client).to receive(:token).exactly(3).times
277
+
278
+ driver.wait_for_workflow
279
+ end
280
+ end
281
+
282
+ context 'when the request is completed on the first loop' do
283
+ it 'only refreshes the request 1 time' do
284
+ expect(token).to receive(:alive?).once.and_return(false)
285
+ driver.wait_for_workflow
286
+ end
287
+ end
288
+
289
+ context 'when the timeout is exceeded' do
290
+ it 'raises a Timeout exception' do
291
+ allow(Timeout).to receive(:timeout).and_raise(Timeout::Error)
292
+ expect { driver.wait_for_workflow }.to raise_error(
293
+ Timeout::Error, 'Workflow did not complete in 300 seconds. ' \
294
+ 'Please check the vRO UI for more information.')
295
+ end
296
+ end
297
+
298
+ context 'when a non-timeout exception is raised' do
299
+ it 'raises the original exception' do
300
+ allow(vro_client).to receive(:token).and_raise(RuntimeError, 'an error occurred')
301
+ expect { driver.wait_for_workflow }.to raise_error(RuntimeError, 'an error occurred')
302
+ end
303
+ end
304
+ end
305
+
306
+ describe '#wait_for_server' do
307
+ let(:connection) { instance.transport.connection(state) }
308
+ let(:state) { { hostname: 'host1', server_id: 'server-12345' } }
309
+
310
+ before do
311
+ allow(transport).to receive(:connection).and_return(connection)
312
+ end
313
+
314
+ it 'calls wait_until_ready on the transport connection' do
315
+ expect(connection).to receive(:wait_until_ready)
316
+ driver.wait_for_server(state)
317
+ end
318
+
319
+ it 'destroys the server if the server failed to become ready' do
320
+ allow(connection).to receive(:wait_until_ready).and_raise(RuntimeError)
321
+ expect(driver).to receive(:destroy).with(state)
322
+ expect { driver.wait_for_server(state) }.to raise_error(RuntimeError)
323
+ end
324
+ end
325
+
326
+ describe '#set_workflow_parameters' do
327
+ let(:vro_client) { double('vro_client') }
328
+ let(:params) { { key1: 'value1', key2: 'value2' } }
329
+
330
+ it 'sets parameters on the client' do
331
+ allow(driver).to receive(:vro_client).and_return(vro_client)
332
+ expect(vro_client).to receive(:parameter).with('key1', 'value1')
333
+ expect(vro_client).to receive(:parameter).with('key2', 'value2')
334
+
335
+ driver.set_workflow_parameters(params)
336
+ end
337
+ end
338
+
339
+ describe '#output_parameters' do
340
+ let(:vro_client) { double('vro_client') }
341
+ let(:token) { double('token') }
342
+ let(:output) { double('output_parameters') }
343
+
344
+ it 'returns the output parameters from the workflow token' do
345
+ allow(driver).to receive(:vro_client).and_return(vro_client)
346
+ allow(vro_client).to receive(:token).and_return(token)
347
+ expect(token).to receive(:output_parameters).and_return(output)
348
+ expect(driver.output_parameters).to eq(output)
349
+ end
350
+ end
351
+
352
+ describe '#output_parameter_value' do
353
+ let(:test_key) { double('test_key', value: 'test_value') }
354
+ let(:output_parameters) { { 'test_key' => test_key } }
355
+ it 'returns the correct value' do
356
+ allow(driver).to receive(:output_parameters).and_return(output_parameters)
357
+ expect(driver.output_parameter_value('test_key')).to eq('test_value')
358
+ end
359
+ end
360
+
361
+ describe '#output_parameter_empty?' do
362
+ context 'when the value is not nil or empty' do
363
+ it 'returns false' do
364
+ allow(driver).to receive(:output_parameter_value).with('test_key').and_return('test_value')
365
+ expect(driver.output_parameter_empty?('test_key')).to eq(false)
366
+ end
367
+ end
368
+
369
+ context 'when the value is nil' do
370
+ it 'returns true' do
371
+ allow(driver).to receive(:output_parameter_value).with('test_key').and_return(nil)
372
+ expect(driver.output_parameter_empty?('test_key')).to eq(true)
373
+ end
374
+ end
375
+
376
+ context 'when the value is empty' do
377
+ it 'returns true' do
378
+ allow(driver).to receive(:output_parameter_value).with('test_key').and_return('')
379
+ expect(driver.output_parameter_empty?('test_key')).to eq(true)
380
+ end
381
+ end
382
+ end
383
+
384
+ describe '#validate_create_output_parameters!' do
385
+ let(:server_id) { double('server_id', value: 'server-12345') }
386
+ let(:ip_address) { double('ip_address', value: '1.2.3.4') }
387
+
388
+ context 'when the output parameters do not include server_id and ip_address' do
389
+ let(:output_parameters) { {} }
390
+ it_behaves_like 'output parameters that are missing a required key'
391
+ end
392
+
393
+ context 'when the output parameters do not include server_id' do
394
+ let(:output_parameters) { { 'ip_address' => ip_address } }
395
+ it_behaves_like 'output parameters that are missing a required key'
396
+ end
397
+
398
+ context 'when the output parameters do not include ip_address' do
399
+ let(:output_parameters) { { 'server_id' => server_id } }
400
+ it_behaves_like 'output parameters that are missing a required key'
401
+ end
402
+
403
+ context 'when server_id is empty' do
404
+ let(:server_id) { double('server_id', value: '') }
405
+ let(:output_parameters) do
406
+ {
407
+ 'server_id' => server_id,
408
+ 'ip_address' => ip_address
409
+ }
410
+ end
411
+
412
+ it 'raises an exception' do
413
+ allow(driver).to receive(:output_parameters).and_return(output_parameters)
414
+ expect { driver.validate_create_output_parameters! }.to raise_error(
415
+ RuntimeError,
416
+ 'The server_id parameter was empty.'
417
+ )
418
+ end
419
+ end
420
+
421
+ context 'when ip_address is empty' do
422
+ let(:ip_address) { double('ip_address', value: '') }
423
+ let(:output_parameters) do
424
+ {
425
+ 'server_id' => server_id,
426
+ 'ip_address' => ip_address
427
+ }
428
+ end
429
+
430
+ it 'raises an exception' do
431
+ allow(driver).to receive(:output_parameters).and_return(output_parameters)
432
+ expect { driver.validate_create_output_parameters! }.to raise_error(
433
+ RuntimeError,
434
+ 'The ip_address parameter was empty.'
435
+ )
436
+ end
437
+ end
438
+ end
439
+
440
+ describe '#workflow_successful?' do
441
+ let(:vro_client) { double('vro_client') }
442
+ let(:token) { double('token') }
443
+ before do
444
+ allow(driver).to receive(:vro_client).and_return(vro_client)
445
+ allow(vro_client).to receive(:token).and_return(token)
446
+ end
447
+
448
+ context 'when the state is completed' do
449
+ it 'returns true' do
450
+ allow(token).to receive(:state).and_return('completed')
451
+ expect(driver.workflow_successful?).to eq(true)
452
+ end
453
+ end
454
+
455
+ context 'when the state is failed' do
456
+ it 'returns true' do
457
+ allow(token).to receive(:state).and_return('failed')
458
+ expect(driver.workflow_successful?).to eq(false)
459
+ end
460
+ end
461
+ end
462
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitchen-vro
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: 2015-08-25 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.4'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.4.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.4'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.4.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: vcoworkflows
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.2'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.2'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.7'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.7'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '10.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '10.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.2'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.2'
89
+ - !ruby/object:Gem::Dependency
90
+ name: webmock
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1.21'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.21'
103
+ description: A Test Kitchen driver for VMware vRealize Orchestrator (vRO)
104
+ email:
105
+ - partnereng@chef.io
106
+ executables: []
107
+ extensions: []
108
+ extra_rdoc_files: []
109
+ files:
110
+ - ".gitignore"
111
+ - ".rspec"
112
+ - ".rubocop.yml"
113
+ - Gemfile
114
+ - LICENSE.txt
115
+ - README.md
116
+ - Rakefile
117
+ - kitchen-vro.gemspec
118
+ - lib/kitchen/driver/vro.rb
119
+ - lib/kitchen/driver/vro_version.rb
120
+ - spec/spec_helper.rb
121
+ - spec/vro_spec.rb
122
+ homepage: https://github.com/chef-partners/kitchen-vro
123
+ licenses:
124
+ - Apache 2.0
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.4.4
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: A Test Kitchen driver for VMware vRealize Orchestrator (vRO)
146
+ test_files:
147
+ - spec/spec_helper.rb
148
+ - spec/vro_spec.rb
149
+ has_rdoc: