vcloud-core 0.3.0 → 0.4.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.
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
1
  /Gemfile.lock
2
2
  /coverage/
3
3
  .idea
4
+ spec/integration/vcloud_tools_testing_config.yaml
5
+ .bundle
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.4.0 (2014-05-23)
2
+
3
+ Features:
4
+
5
+ - Add a 'warnings' variable/method to ConfigValidator.
6
+ - Support simple parameter deprecations in ConfigValidator.
7
+ - Log schema warnings encountered in ConfigLoader.
8
+
9
+ API changes:
10
+
11
+ - Breaking changes to the order and name of arguments for VappTemplate#get
12
+ - Remove unused methods Vcloud::Fog::ServiceInterface#get_catalog and
13
+ Vcloud::Fog::ServiceInterface#get_catalog_item, plus associated
14
+ Vcloud::Fog::ContentTypes constants.
15
+ - Restrict variable scope available to preamble ERB templates so that they
16
+ cannot access or modify the Vm object.
17
+
1
18
  ## 0.3.0 (2014-05-13)
2
19
 
3
20
  Features:
data/README.md CHANGED
@@ -153,18 +153,9 @@ Run the integration tests (slower and requires a real environment):
153
153
 
154
154
  bundle exec rake integration
155
155
 
156
- ### setting up and describing your environment for test runs
157
-
158
- You need access to a suitable vCloud Director organization to run the integration tests - it also needs some basic
159
- configuration: an Edge Gateway, and a routed network.
160
- It is not necessarily safe to run them against an existing environment, unless care is taken with the entities being
161
- tested.
162
-
163
- A number of ENV vars specifying items under test in the environment need to be set for the tests to run successfully.
164
-
165
- - `VCLOUD_EDGE_GATEWAY`: _name of edge gateway under test_
166
- - `VCLOUD_NETWORK1_ID`: _Id of network under test_
167
- - `VCLOUD_PROVIDER_NETWORK_ID`: _Id of the uplink network (or external network) of the VCLOUD_EDGE_GATEWAY under test_
156
+ You need access to a suitable vCloud Director organization to run the
157
+ integration tests. See the [integration tests README](/spec/integration/README.md) for
158
+ further details.
168
159
 
169
160
  ## Contributing
170
161
 
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'rspec/core/rake_task'
2
2
  require 'cucumber/rake/task'
3
3
 
4
- task :default => [:spec, :features]
4
+ task :default => [:rubocop, :spec, :features]
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec) do |task|
7
7
  # Set a bogus Fog credential, otherwise it's possible for the unit
data/jenkins.sh CHANGED
@@ -1,11 +1,14 @@
1
1
  #!/bin/bash -x
2
2
  set -e
3
3
 
4
- rm -f Gemfile.lock
5
- git clean -fdx
6
-
4
+ git clean -ffdx
7
5
  bundle install --path "${HOME}/bundles/${JOB_NAME}"
8
6
 
7
+ # Obtain the integration test parameters
8
+ git clone git@github.gds:gds/vcloud-tools-testing-config.git
9
+ mv vcloud-tools-testing-config/vcloud_tools_testing_config.yaml spec/integration/
10
+ rm -rf vcloud-tools-testing-config
11
+
9
12
  bundle exec rake
10
13
  RUBYOPT="-r ./tools/fog_credentials" bundle exec rake integration
11
14
  bundle exec rake publish_gem
@@ -1,4 +1,12 @@
1
1
  #!/bin/bash -x
2
2
  set -e
3
+
4
+ git clean -ffdx
3
5
  bundle install --path "${HOME}/bundles/${JOB_NAME}"
6
+
7
+ # Obtain the integration test parameters
8
+ git clone git@github.gds:gds/vcloud-tools-testing-config.git
9
+ mv vcloud-tools-testing-config/vcloud_tools_testing_config.yaml spec/integration/
10
+ rm -rf vcloud-tools-testing-config
11
+
4
12
  RUBYOPT="-r ./tools/fog_credentials" bundle exec rake integration
@@ -21,6 +21,9 @@ module Vcloud
21
21
 
22
22
  if schema
23
23
  validation = Core::ConfigValidator.validate(:base, config, schema)
24
+ validation.warnings.each do |warning|
25
+ Vcloud::Core.logger.warn(warning)
26
+ end
24
27
  unless validation.valid?
25
28
  validation.errors.each do |error|
26
29
  Vcloud::Core.logger.fatal(error)
@@ -1,18 +1,42 @@
1
1
  require 'ipaddr'
2
2
 
3
+ ##
4
+ # self::validate is entry point; this class method is called to
5
+ # instantiate ConfigValidator. For example:
6
+ #
7
+ # Core::ConfigValidator.validate(key, data, schema)
8
+ #
9
+ # = Recursion in this class
10
+ #
11
+ # Note that this class will recursively call itself in order to validate deep
12
+ # hash and array structures.
13
+ #
14
+ # The +data+ variable is usually either an array or hash and so will pass
15
+ # through the ConfigValidator#validate_array and
16
+ # ConfigValidator#validate_hash methods respectively.
17
+ #
18
+ # These methods then recursively instantiate this class by calling
19
+ # ConfigValidator::validate again (ConfigValidator#validate_hash calls this
20
+ # indirectly via the ConfigValidator#check_hash_parameter method).
21
+
3
22
  module Vcloud
4
23
  module Core
5
24
  class ConfigValidator
6
25
 
7
- attr_reader :key, :data, :schema, :type, :errors
26
+ attr_reader :key, :data, :schema, :type, :errors, :warnings
8
27
 
9
28
  VALID_ALPHABETICAL_VALUES_FOR_IP_RANGE = %w(Any external internal)
10
29
 
30
+ def self.validate(key, data, schema)
31
+ new(key, data, schema)
32
+ end
33
+
11
34
  def initialize(key, data, schema)
12
35
  raise "Nil schema" unless schema
13
36
  raise "Invalid schema" unless schema.key?(:type)
14
37
  @type = schema[:type].to_s.downcase
15
- @errors = []
38
+ @errors = []
39
+ @warnings = []
16
40
  @data = data
17
41
  @schema = schema
18
42
  @key = key
@@ -23,47 +47,147 @@ module Vcloud
23
47
  @errors.empty?
24
48
  end
25
49
 
26
- def self.validate(key, data, schema)
27
- new(key, data, schema)
28
- end
29
-
30
50
  private
31
51
 
52
+ # Call the corresponding function in this class (dependant on schema[:type])
32
53
  def validate
33
54
  self.send("validate_#{type}".to_sym)
34
55
  end
35
56
 
36
- def validate_string
37
- unless @data.is_a? String
38
- errors << "#{key}: #{@data} is not a string"
57
+ def validate_array
58
+ unless data.is_a? Array
59
+ @errors << "#{key} is not an array"
39
60
  return
40
61
  end
41
62
  return unless check_emptyness_ok
42
- return unless check_matcher_matches
63
+ if schema.key?(:each_element_is)
64
+ element_schema = schema[:each_element_is]
65
+ data.each do |element|
66
+ sub_validator = ConfigValidator.validate(key, element, element_schema)
67
+ @warnings = warnings + sub_validator.warnings
68
+ unless sub_validator.valid?
69
+ @errors = errors + sub_validator.errors
70
+ end
71
+ end
72
+ end
43
73
  end
44
74
 
45
- def validate_string_or_number
46
- unless data.is_a?(String) || data.is_a?(Numeric)
47
- @errors << "#{key}: #{@data} is not a string_or_number"
75
+ def validate_hash
76
+ unless data.is_a? Hash
77
+ @errors << "#{key}: is not a hash"
48
78
  return
49
79
  end
80
+ return unless check_emptyness_ok
81
+ check_for_unknown_parameters
82
+
83
+ if schema.key?(:internals)
84
+ check_for_invalid_deprecations
85
+ deprecations_used = get_deprecations_used
86
+ warn_on_deprecations_used(deprecations_used)
87
+
88
+ schema[:internals].each do |param_key,param_schema|
89
+ ignore_required = (
90
+ param_schema[:deprecated_by] ||
91
+ deprecations_used.key?(param_key)
92
+ )
93
+ check_hash_parameter(param_key, param_schema, ignore_required)
94
+ end
95
+ end
50
96
  end
51
97
 
52
- def validate_ip_address
53
- unless data.is_a?(String)
54
- @errors << "#{key}: #{@data} is not a valid ip_address"
55
- return
98
+ # Return a hash of deprecated params referenced in @data. Where the
99
+ # structure is: `{ :deprecator => :deprecatee }`
100
+ def get_deprecations_used
101
+ used = {}
102
+ schema[:internals].each do |param_key,param_schema|
103
+ deprecated_by = param_schema[:deprecated_by]
104
+ if deprecated_by && data[param_key]
105
+ used[deprecated_by.to_sym] = param_key
106
+ end
56
107
  end
57
- @errors << "#{key}: #{@data} is not a valid ip_address" unless valid_ip_address?(data)
108
+
109
+ used
58
110
  end
59
111
 
60
- def validate_ip_address_range
61
- unless data.is_a?(String)
62
- @errors << "#{key}: #{@data} is not a valid IP address range. Valid values can be IP address, CIDR, IP range, 'Any','internal' and 'external'."
63
- return
112
+ # Append warnings for any deprecations used. Takes the output of
113
+ # `#get_deprecations_used`.
114
+ def warn_on_deprecations_used(deprecations_used)
115
+ deprecations_used.each do |deprecator, deprecatee|
116
+ @warnings << "#{deprecatee}: is deprecated by '#{deprecator}'"
117
+ end
118
+ end
119
+
120
+ def check_emptyness_ok
121
+ unless schema.key?(:allowed_empty) && schema[:allowed_empty]
122
+ if data.empty?
123
+ @errors << "#{key}: cannot be empty #{type}"
124
+ return false
125
+ end
126
+ end
127
+ true
128
+ end
129
+
130
+ # Raise an exception if any `deprecated_by` params refer to params
131
+ # that don't exist in the schema.
132
+ def check_for_invalid_deprecations
133
+ schema[:internals].each do |param_key,param_schema|
134
+ deprecated_by = param_schema[:deprecated_by]
135
+ if deprecated_by && !schema[:internals].key?(deprecated_by.to_sym)
136
+ raise "#{param_key}: deprecated_by target '#{deprecated_by}' not found in schema"
137
+ end
138
+ end
139
+ end
140
+
141
+ def check_for_unknown_parameters
142
+ internals = schema[:internals]
143
+ # if there are no parameters specified, then assume all are ok.
144
+ return true unless internals
145
+ return true if schema[:permit_unknown_parameters]
146
+ data.keys.each do |k|
147
+ @errors << "#{key}: parameter '#{k}' is invalid" unless internals[k]
148
+ end
149
+ end
150
+
151
+ def check_hash_parameter(sub_key, sub_schema, ignore_required=false)
152
+ unless data.key?(sub_key)
153
+ if sub_schema[:required] == false || ignore_required
154
+ return true
155
+ end
156
+
157
+ @errors << "#{key}: missing '#{sub_key}' parameter"
158
+ return false
159
+ end
160
+
161
+ sub_validator = ConfigValidator.validate(
162
+ sub_key,
163
+ data[sub_key],
164
+ sub_schema
165
+ )
166
+ @warnings = warnings + sub_validator.warnings
167
+ unless sub_validator.valid?
168
+ @errors = errors + sub_validator.errors
169
+ end
170
+ end
171
+
172
+ def check_matcher_matches
173
+ regex = schema[:matcher]
174
+ return unless regex
175
+ raise "#{key}: #{regex} is not a Regexp" unless regex.is_a? Regexp
176
+ unless data =~ regex
177
+ @errors << "#{key}: #{data} does not match"
178
+ return false
179
+ end
180
+ true
181
+ end
182
+
183
+ def valid_alphabetical_ip_range?
184
+ VALID_ALPHABETICAL_VALUES_FOR_IP_RANGE.include?(data)
185
+ end
186
+
187
+ def validate_boolean
188
+ unless [true, false].include?(data)
189
+ @errors << "#{key}: #{data} is not a valid boolean value."
64
190
  end
65
- valid = valid_cidr_or_ip_address? || valid_alphabetical_ip_range? || valid_ip_range?
66
- @errors << "#{key}: #{@data} is not a valid IP address range. Valid values can be IP address, CIDR, IP range, 'Any','internal' and 'external'." unless valid
67
191
  end
68
192
 
69
193
  def valid_cidr_or_ip_address?
@@ -75,8 +199,21 @@ module Vcloud
75
199
  end
76
200
  end
77
201
 
78
- def valid_alphabetical_ip_range?
79
- VALID_ALPHABETICAL_VALUES_FOR_IP_RANGE.include?(data)
202
+ def validate_enum
203
+ acceptable_values = schema[:acceptable_values]
204
+ raise "Must set :acceptable_values for type 'enum'" unless acceptable_values.is_a?(Array)
205
+ unless acceptable_values.include?(data)
206
+ acceptable_values_string = acceptable_values.collect {|v| "'#{v}'" }.join(', ')
207
+ @errors << "#{key}: #{@data} is not a valid value. Acceptable values are #{acceptable_values_string}."
208
+ end
209
+ end
210
+
211
+ def validate_ip_address
212
+ unless data.is_a?(String)
213
+ @errors << "#{key}: #{@data} is not a valid ip_address"
214
+ return
215
+ end
216
+ @errors << "#{key}: #{@data} is not a valid ip_address" unless valid_ip_address?(data)
80
217
  end
81
218
 
82
219
  def valid_ip_address? ip_address
@@ -91,6 +228,15 @@ module Vcloud
91
228
  end
92
229
  end
93
230
 
231
+ def validate_ip_address_range
232
+ unless data.is_a?(String)
233
+ @errors << "#{key}: #{@data} is not a valid IP address range. Valid values can be IP address, CIDR, IP range, 'Any','internal' and 'external'."
234
+ return
235
+ end
236
+ valid = valid_cidr_or_ip_address? || valid_alphabetical_ip_range? || valid_ip_range?
237
+ @errors << "#{key}: #{@data} is not a valid IP address range. Valid values can be IP address, CIDR, IP range, 'Any','internal' and 'external'." unless valid
238
+ end
239
+
94
240
  def valid_ip_range?
95
241
  range_parts = data.split('-')
96
242
  return false if range_parts.size != 2
@@ -104,101 +250,20 @@ module Vcloud
104
250
  IPAddr.new(start_address) < IPAddr.new(end_address)
105
251
  end
106
252
 
107
- def validate_hash
108
- unless data.is_a? Hash
109
- @errors << "#{key}: is not a hash"
253
+ def validate_string
254
+ unless @data.is_a? String
255
+ errors << "#{key}: #{@data} is not a string"
110
256
  return
111
257
  end
112
258
  return unless check_emptyness_ok
113
- check_for_unknown_parameters
114
- if schema.key?(:internals)
115
- internals = schema[:internals]
116
- internals.each do |param_key,param_schema|
117
- check_hash_parameter(param_key, param_schema)
118
- end
119
- end
259
+ return unless check_matcher_matches
120
260
  end
121
261
 
122
- def validate_array
123
- unless data.is_a? Array
124
- @errors << "#{key} is not an array"
262
+ def validate_string_or_number
263
+ unless data.is_a?(String) || data.is_a?(Numeric)
264
+ @errors << "#{key}: #{@data} is not a string_or_number"
125
265
  return
126
266
  end
127
- return unless check_emptyness_ok
128
- if schema.key?(:each_element_is)
129
- element_schema = schema[:each_element_is]
130
- data.each do |element|
131
- sub_validator = ConfigValidator.validate(key, element, element_schema)
132
- unless sub_validator.valid?
133
- @errors = errors + sub_validator.errors
134
- end
135
- end
136
- end
137
- end
138
-
139
- def validate_enum
140
- acceptable_values = schema[:acceptable_values]
141
- raise "Must set :acceptable_values for type 'enum'" unless acceptable_values.is_a?(Array)
142
- unless acceptable_values.include?(data)
143
- acceptable_values_string = acceptable_values.collect {|v| "'#{v}'" }.join(', ')
144
- @errors << "#{key}: #{@data} is not a valid value. Acceptable values are #{acceptable_values_string}."
145
- end
146
- end
147
-
148
- def validate_boolean
149
- unless [true, false].include?(data)
150
- @errors << "#{key}: #{data} is not a valid boolean value."
151
- end
152
- end
153
-
154
- def check_emptyness_ok
155
- unless schema.key?(:allowed_empty) && schema[:allowed_empty]
156
- if data.empty?
157
- @errors << "#{key}: cannot be empty #{type}"
158
- return false
159
- end
160
- end
161
- true
162
- end
163
-
164
- def check_matcher_matches
165
- regex = schema[:matcher]
166
- return unless regex
167
- raise "#{key}: #{regex} is not a Regexp" unless regex.is_a? Regexp
168
- unless data =~ regex
169
- @errors << "#{key}: #{data} does not match"
170
- return false
171
- end
172
- true
173
- end
174
-
175
- def check_hash_parameter(sub_key, sub_schema)
176
- if sub_schema.key?(:required) && sub_schema[:required] == false
177
- # short circuit out if we do not have the key, but it's not required.
178
- return true unless data.key?(sub_key)
179
- end
180
- unless data.key?(sub_key)
181
- @errors << "#{key}: missing '#{sub_key}' parameter"
182
- return false
183
- end
184
- sub_validator = ConfigValidator.validate(
185
- sub_key,
186
- data[sub_key],
187
- sub_schema
188
- )
189
- unless sub_validator.valid?
190
- @errors = errors + sub_validator.errors
191
- end
192
- end
193
-
194
- def check_for_unknown_parameters
195
- internals = schema[:internals]
196
- # if there are no parameters specified, then assume all are ok.
197
- return true unless internals
198
- return true if schema[:permit_unknown_parameters]
199
- data.keys.each do |k|
200
- @errors << "#{key}: parameter '#{k}' is invalid" unless internals[k]
201
- end
202
267
  end
203
268
  end
204
269
  end
@@ -6,7 +6,7 @@ module Vcloud
6
6
 
7
7
  def initialize(gateway_interface_hash)
8
8
  if gateway_interface_hash.nil?
9
- raise "EdgeGatewayInterface: gateway_interface_hash cannot be nil"
9
+ raise "EdgeGatewayInterface: gateway_interface_hash cannot be nil"
10
10
  end
11
11
  unless gateway_interface_hash[:Name] && gateway_interface_hash[:Network]
12
12
  raise "EdgeGatewayInterface: bad input: #{gateway_interface_hash}"
@@ -33,11 +33,11 @@ module Vcloud
33
33
  end
34
34
  end
35
35
 
36
- def self.get catalog_name, catalog_item_name
37
- ids = self.get_ids_by_name_and_catalog(catalog_item_name, catalog_name)
36
+ def self.get vapp_template_name, catalog_name
37
+ ids = self.get_ids_by_name_and_catalog(vapp_template_name, catalog_name)
38
38
  raise 'Could not find template vApp' if ids.size == 0
39
39
  if ids.size > 1
40
- raise "Template #{catalog_item_name} is not unique in catalog #{catalog_name}"
40
+ raise "Template #{vapp_template_name} is not unique in catalog #{catalog_name}"
41
41
  end
42
42
  return self.new(ids.first)
43
43
  end
@@ -1,5 +1,5 @@
1
1
  module Vcloud
2
2
  module Core
3
- VERSION = '0.3.0'
3
+ VERSION = '0.4.0'
4
4
  end
5
5
  end
@@ -112,15 +112,21 @@ module Vcloud
112
112
  Vcloud::Fog::ServiceInterface.new.put_guest_customization_section(id, name, interpolated_preamble)
113
113
  end
114
114
 
115
- def generate_preamble(script_path, script_post_processor, vars)
116
- script = ERB.new(File.read(File.expand_path(script_path)), nil, '>-').result(binding)
115
+ def generate_preamble(script_path, script_post_processor, preamble_vars)
116
+ erb_vars = OpenStruct.new({
117
+ vapp_name: vapp_name,
118
+ vars: preamble_vars
119
+ })
120
+ erb_vars_binding_object = erb_vars.instance_eval { binding }
121
+ erb_output = interpolate_erb_file(script_path, erb_vars_binding_object)
117
122
  if script_post_processor
118
- script = Open3.capture2(File.expand_path(script_post_processor),
119
- stdin_data: script).first
123
+ post_process_erb_output(erb_output, script_post_processor) if script_post_processor
124
+ else
125
+ erb_output
120
126
  end
121
- script
122
127
  end
123
128
 
129
+
124
130
  def update_storage_profile storage_profile
125
131
  storage_profile_href = get_storage_profile_href_by_name(storage_profile, @vapp.name)
126
132
  Vcloud::Fog::ServiceInterface.new.put_vm(id, name, {
@@ -132,6 +138,18 @@ module Vcloud
132
138
  end
133
139
 
134
140
  private
141
+
142
+ def interpolate_erb_file(erb_file, binding_object)
143
+ ERB.new(File.read(File.expand_path(erb_file)), nil, '>-').result(binding_object)
144
+ end
145
+
146
+ def post_process_erb_output(data_to_process, post_processor_script)
147
+ # Open3.capture2, as we just need to return STDOUT of the post_processor_script
148
+ Open3.capture2(
149
+ File.expand_path(post_processor_script),
150
+ stdin_data: data_to_process).first
151
+ end
152
+
135
153
  def virtual_hardware_section
136
154
  vcloud_attributes[:'ovf:VirtualHardwareSection'][:'ovf:Item']
137
155
  end
@@ -1,10 +1,8 @@
1
1
  module Vcloud
2
2
  module Fog
3
3
  module ContentTypes
4
- CATALOG = 'application/vnd.vmware.vcloud.catalog+xml'
5
4
  ORG = 'application/vnd.vmware.vcloud.org+xml'
6
5
  VDC = 'application/vnd.vmware.vcloud.vdc+xml'
7
- CATALOG_ITEM = 'application/vnd.vmware.vcloud.catalogItem+xml'
8
6
  NETWORK = 'application/vnd.vmware.vcloud.network+xml'
9
7
  METADATA = 'application/vnd.vmware.vcloud.metadata.value+xml'
10
8
  end
@@ -35,10 +35,6 @@ module Vcloud
35
35
  @vcloud.get_vapps_in_lease_from_query(options).body
36
36
  end
37
37
 
38
- def get_catalog_item(id)
39
- @vcloud.get_catalog_item(id).body
40
- end
41
-
42
38
  def post_instantiate_vapp_template(vdc, template, name, params)
43
39
  Vcloud::Core.logger.debug("instantiating #{name} vapp in #{vdc[:name]}")
44
40
  vapp = @vcloud.post_instantiate_vapp_template(extract_id(vdc), template, name, params).body
@@ -151,10 +147,6 @@ module Vcloud
151
147
  @vcloud.process_task(task)
152
148
  end
153
149
 
154
- def get_catalog(id)
155
- @vcloud.get_catalog(id).body
156
- end
157
-
158
150
  def put_vapp_metadata_value(id, k, v)
159
151
  Vcloud::Core.logger.debug("putting metadata pair '#{k}'=>'#{v}' to #{id}")
160
152
  # need to convert key to_s since Fog 0.17 borks on symbol key
@@ -0,0 +1,36 @@
1
+ # Running vCloud Core Integration Tests
2
+
3
+ ## Prerequisites
4
+
5
+ - Access to a suitable vCloud Director organisation.
6
+
7
+ **NB** It is not safe to run them against an environment that is in use
8
+ (e.g. production, preview) as many of the tests clear down all config at
9
+ the beginning and/or end to ensure the environment is as the tests expect.
10
+
11
+ - A config file with the settings configured.
12
+
13
+ There is a [template file](spec/integration/vcloud_tools_testing_config.yaml.template) to
14
+ help with this. Copy the template file to `spec/integration/vcloud_tools_testing_config.yaml`
15
+ and update with parameters suitable for your environment.
16
+
17
+ - You need to include the set-up for your testing environment in your
18
+ [fog file](https://github.com/alphagov/vcloud-core#credentials).
19
+
20
+ - The tests use the [vCloud Tools Tester](http://rubygems.org/gems/vcloud-tools-tester) gem.
21
+ You do not need to install this, `bundler` will do this for you.
22
+
23
+ ## Parameters
24
+
25
+ ````
26
+ default: # This is the fog credential that refers to your testing environment, e.g. `test_credential`
27
+ vdc_1_name: # The name of a VDC
28
+ catalog: # A catalog
29
+ vapp_template: # A vApp Template within that catalog
30
+ network_1: # The name of the primary network
31
+ network_1_ip: # The IP address of the primary network
32
+ ````
33
+
34
+ ## To run the tests
35
+
36
+ `FOG_CREDENTIAL=test_credential bundle exec integration`
@@ -4,28 +4,12 @@ module Vcloud
4
4
  module Core
5
5
  describe QueryRunner do
6
6
 
7
- required_env = {
8
- 'VCLOUD_VDC_NAME' =>
9
- 'to the name of an orgVdc to use to instantiate vApps into',
10
- 'VCLOUD_TEMPLATE_NAME' =>
11
- 'to the name of a vAppTemplate to use create vApps in tests',
12
- 'VCLOUD_CATALOG_NAME' =>
13
- 'to the name of the catalog that VCLOUD_VAPP_TEMPLATE_NAME is stored in',
14
- }
15
-
16
- error = false
17
- required_env.each do |var,message|
18
- unless ENV[var]
19
- puts "Must set #{var} #{message}" unless ENV[var]
20
- error = true
21
- end
22
- end
23
- Kernel.exit(2) if error
24
-
25
7
  before(:all) do
26
- @vapp_template_name = ENV['VCLOUD_TEMPLATE_NAME']
27
- @vapp_template_catalog_name = ENV['VCLOUD_CATALOG_NAME']
28
- @vdc_name = ENV['VCLOUD_VDC_NAME']
8
+ config_file = File.join(File.dirname(__FILE__), "../vcloud_tools_testing_config.yaml")
9
+ test_data = Vcloud::Tools::Tester::TestParameters.new(config_file)
10
+ @vapp_template_name = test_data.vapp_template
11
+ @vapp_template_catalog_name = test_data.catalog
12
+ @vdc_name = test_data.vdc_1_name
29
13
  end
30
14
 
31
15
  context "#available_query_types" do
@@ -92,11 +76,13 @@ module Vcloud
92
76
 
93
77
  before(:all) do
94
78
  @number_of_vapps_to_create = 2
95
- @test_case_vapps = create_test_case_vapps(
79
+ @test_case_vapps = IntegrationHelper.create_test_case_vapps(
96
80
  @number_of_vapps_to_create,
97
81
  @vdc_name,
98
82
  @vapp_template_catalog_name,
99
83
  @vapp_template_name,
84
+ [],
85
+ "vcloud-core-query-tests"
100
86
  )
101
87
  end
102
88
 
@@ -194,27 +180,7 @@ module Vcloud
194
180
  end
195
181
 
196
182
  after(:all) do
197
- fsi = Vcloud::Fog::ServiceInterface.new()
198
- @test_case_vapps.each do |vapp|
199
- fsi.delete_vapp(vapp.id)
200
- end
201
- end
202
-
203
- def create_test_case_vapps(quantity, vdc_name, catalog_name, vapp_template_name)
204
- vapp_template = VappTemplate.get(catalog_name, vapp_template_name)
205
- timestamp_in_s = Time.new.to_i
206
- base_vapp_name = "vcloud-core-query-tests-#{timestamp_in_s}-"
207
- network_names = []
208
- vapp_list = []
209
- quantity.times do |index|
210
- vapp_list << Vapp.instantiate(
211
- base_vapp_name + index.to_s,
212
- network_names,
213
- vapp_template.id,
214
- vdc_name
215
- )
216
- end
217
- vapp_list
183
+ IntegrationHelper.delete_vapps(@test_case_vapps)
218
184
  end
219
185
 
220
186
  end
@@ -0,0 +1,6 @@
1
+ default:
2
+ vdc_1_name:
3
+ catalog:
4
+ vapp_template:
5
+ network_1:
6
+ network_1_ip:
data/spec/spec_helper.rb CHANGED
@@ -14,15 +14,17 @@ end
14
14
 
15
15
  require 'bundler/setup'
16
16
  require 'vcloud/core'
17
+ require 'vcloud/tools/tester'
17
18
  require 'support/stub_fog_interface.rb'
19
+ require 'support/integration_helper'
18
20
 
19
21
  if ENV['COVERAGE']
20
22
  SimpleCov.at_exit do
21
23
  SimpleCov.result.format!
22
24
  # do not change the coverage percentage, instead add more unit tests to fix coverage failures.
23
- if SimpleCov.result.covered_percent < 71
25
+ if SimpleCov.result.covered_percent < 81
24
26
  print "ERROR::BAD_COVERAGE\n"
25
- print "Coverage is less than acceptable limit(71%). Please add more tests to improve the coverage"
27
+ print "Coverage is less than acceptable limit(81%). Please add more tests to improve the coverage"
26
28
  exit(1)
27
29
  end
28
30
  end
@@ -0,0 +1,32 @@
1
+ module IntegrationHelper
2
+
3
+ def self.create_test_case_vapps(number_of_vapps,
4
+ vdc_name,
5
+ catalog_name,
6
+ vapp_template_name,
7
+ network_names = [],
8
+ prefix = "vcloud-core-tests"
9
+ )
10
+ vapp_template = Vcloud::Core::VappTemplate.get(vapp_template_name, catalog_name)
11
+ timestamp_in_s = Time.new.to_i
12
+ base_vapp_name = "#{prefix}-#{timestamp_in_s}-"
13
+ vapp_list = []
14
+ number_of_vapps.times do |index|
15
+ vapp_list << Vcloud::Core::Vapp.instantiate(
16
+ base_vapp_name + index.to_s,
17
+ network_names,
18
+ vapp_template.id,
19
+ vdc_name
20
+ )
21
+ end
22
+ vapp_list
23
+ end
24
+
25
+ def self.delete_vapps(vapp_list)
26
+ fsi = Vcloud::Fog::ServiceInterface.new()
27
+ vapp_list.each do |vapp|
28
+ fsi.delete_vapp(vapp.id)
29
+ end
30
+ end
31
+
32
+ end
@@ -31,7 +31,7 @@ class StubFogInterface
31
31
  end
32
32
 
33
33
  def get_edge_gateway(id)
34
- {
34
+ {
35
35
  :name => 'test-edgegw-1',
36
36
  :href => "/#{id}",
37
37
  }
@@ -57,6 +57,32 @@ module Vcloud
57
57
  expect { loader.load_config(input_file, invalid_schema) }.
58
58
  to raise_error('Supplied configuration does not match supplied schema')
59
59
  end
60
+
61
+ it "should not log warnings if there are none" do
62
+ input_file = "#{@data_dir}/working_with_defaults.yaml"
63
+ loader = ConfigLoader.new
64
+
65
+ Vcloud::Core.logger.should_not_receive(:warn)
66
+ loader.load_config(input_file, vapp_config_schema)
67
+ end
68
+
69
+ it "should log warnings if checked against a deprecated schema" do
70
+ input_file = "#{@data_dir}/working_with_defaults.yaml"
71
+ loader = ConfigLoader.new
72
+
73
+ Vcloud::Core.logger.should_receive(:warn).with("vapps: is deprecated by 'vapps_new'")
74
+ loader.load_config(input_file, deprecated_schema)
75
+ end
76
+
77
+ it "should log warning before raising error against an invalid and deprecated schema" do
78
+ input_file = "#{@data_dir}/working_with_defaults.yaml"
79
+ loader = ConfigLoader.new
80
+
81
+ Vcloud::Core.logger.should_receive(:warn).with("vapps: is deprecated by 'vapps_new'")
82
+ Vcloud::Core.logger.should_receive(:fatal).with("vapps: is not a hash")
83
+ expect { loader.load_config(input_file, invalid_and_deprecated_schema) }.
84
+ to raise_error('Supplied configuration does not match supplied schema')
85
+ end
60
86
  end
61
87
 
62
88
  def vapp_config_schema
@@ -84,13 +110,35 @@ module Vcloud
84
110
  }
85
111
  end
86
112
 
113
+ def invalid_and_deprecated_schema
114
+ {
115
+ type: 'hash',
116
+ permit_unknown_parameters: true,
117
+ internals: {
118
+ vapps: { type: Hash, deprecated_by: 'vapps_new' },
119
+ vapps_new: { type: 'array' },
120
+ }
121
+ }
122
+ end
123
+
124
+ def deprecated_schema
125
+ {
126
+ type: 'hash',
127
+ permit_unknown_parameters: true,
128
+ internals: {
129
+ vapps: { type: 'array', deprecated_by: 'vapps_new' },
130
+ vapps_new: { type: 'array' },
131
+ }
132
+ }
133
+ end
134
+
87
135
  def valid_config
88
136
  {
89
137
  :vapps=>[{
90
138
  :name=>"vapp-vcloud-tools-tests",
91
139
  :vdc_name=>"VDC_NAME",
92
140
  :catalog=>"CATALOG_NAME",
93
- :catalog_item=>"CATALOG_ITEM",
141
+ :vapp_template=>"VAPP_TEMPLATE",
94
142
  :vm=>{
95
143
  :hardware_config=>{:memory=>"4096", :cpu=>"2"},
96
144
  :extra_disks=>[{:size=>"8192"}],
@@ -564,7 +564,125 @@ module Vcloud
564
564
  expect(v.valid?).to be_true
565
565
  end
566
566
  end
567
+ end
568
+
569
+ context "deprecated_by" do
570
+ # For clarification:
571
+ #
572
+ # - deprecatee: is the old param with a `deprecated_by` field.
573
+ # - deprecator: is the new param listed in the `deprecated_by` field.
574
+ #
575
+ context "deprecatee is provided and deprecator is not" do
576
+ let(:data) {{ name: "santa" }}
577
+
578
+ it "should validate and warn when deprecator is required" do
579
+ schema = {
580
+ type: "Hash",
581
+ internals: {
582
+ name: { type: 'string', deprecated_by: 'full_name' },
583
+ full_name: { type: 'string', required: true },
584
+ }
585
+ }
586
+ v = ConfigValidator.validate(:base, data, schema)
587
+ expect(v.valid?).to be_true
588
+ expect(v.warnings).to eq(["name: is deprecated by 'full_name'"])
589
+ end
590
+
591
+ it "should validate and warn when deprecator appears before deprecatee in schema" do
592
+ schema = {
593
+ type: "Hash",
594
+ internals: {
595
+ full_name: { type: 'string', required: true },
596
+ name: { type: 'string', deprecated_by: 'full_name' },
597
+ }
598
+ }
599
+ v = ConfigValidator.validate(:base, data, schema)
600
+ expect(v.valid?).to be_true
601
+ expect(v.warnings).to eq(["name: is deprecated by 'full_name'"])
602
+ end
603
+
604
+ it "should warn about deprecatee even when both are not required" do
605
+ schema = {
606
+ type: "Hash",
607
+ internals: {
608
+ name: { type: 'string', required: false, deprecated_by: 'full_name' },
609
+ full_name: { type: 'string', required: false },
610
+ }
611
+ }
612
+ v = ConfigValidator.validate(:base, data, schema)
613
+ expect(v.valid?).to be_true
614
+ expect(v.warnings).to eq(["name: is deprecated by 'full_name'"])
615
+ end
616
+ end
617
+
618
+ context "neither deprecatee or deprecator are provided" do
619
+ let(:data) {{ bogus: "blah" }}
620
+
621
+ it "should return error for deprecator but not deprecatee if neither are set" do
622
+ schema = {
623
+ type: "Hash",
624
+ internals: {
625
+ name: { type: 'string', deprecated_by: 'full_name' },
626
+ full_name: { type: 'string', required: true },
627
+ bogus: { type: 'string' },
628
+ }
629
+ }
630
+ v = ConfigValidator.validate(:base, data, schema)
631
+ expect(v.valid?).to be_false
632
+ expect(v.errors).to eq(["base: missing 'full_name' parameter"])
633
+ end
634
+
635
+ it "should raise exception if deprecator does not exist in schema" do
636
+ schema = {
637
+ type: "Hash",
638
+ internals: {
639
+ name: { type: 'string', deprecated_by: 'does_not_exist' },
640
+ bogus: { type: 'string' },
641
+ }
642
+ }
643
+ expect {
644
+ ConfigValidator.validate(:base, data, schema)
645
+ }.to raise_error("name: deprecated_by target 'does_not_exist' not found in schema")
646
+ end
647
+ end
648
+
649
+ context "deprecatee and deprecator are provided and of the wrong type" do
650
+ let(:data) {{ name: 123, full_name: 123 }}
651
+
652
+ it "should return an error for each and a warning for deprecatee" do
653
+ schema = {
654
+ type: "Hash",
655
+ internals: {
656
+ name: { type: 'string', deprecated_by: 'full_name' },
657
+ full_name: { type: 'string', required: true },
658
+ }
659
+ }
660
+ v = ConfigValidator.validate(:base, data, schema)
661
+ expect(v.valid?).to be_false
662
+ expect(v.errors).to eq([
663
+ "name: 123 is not a string",
664
+ "full_name: 123 is not a string",
665
+ ])
666
+ expect(v.warnings).to eq(["name: is deprecated by 'full_name'"])
667
+ end
668
+ end
567
669
 
670
+ it "should not honour deprecation across nested structures" do
671
+ data = { bogus: "blah" }
672
+ schema = {
673
+ type: "Hash",
674
+ internals: {
675
+ name: { type: 'string', deprecated_by: 'full_name' },
676
+ nested: { type: 'hash', internals: {
677
+ full_name: { type: 'string', required: true },
678
+ }},
679
+ bogus: { type: 'string' },
680
+ }
681
+ }
682
+ expect {
683
+ ConfigValidator.validate(:base, data, schema)
684
+ }.to raise_error("name: deprecated_by target 'full_name' not found in schema")
685
+ end
568
686
  end
569
687
 
570
688
  end
@@ -4,6 +4,10 @@
4
4
 
5
5
  (
6
6
  echo "in $*"
7
+ echo "env_var: <%= ENV['TEST_INTERPOLATED_ENVVAR'] -%>"
7
8
  echo "vapp_name: <%= vapp_name -%>"
8
9
  echo "message: <%= vars[:message] -%>"
10
+ <% vars[:array_test].each_with_index do |element, index| -%>
11
+ echo "index<%= index -%>: <%= element -%>"
12
+ <% end -%>
9
13
  ) >> /PREAMBLE_OUTPUT
@@ -4,6 +4,9 @@
4
4
 
5
5
  (
6
6
  echo "in $*"
7
+ echo "env_var: test_interpolated_env"
7
8
  echo "vapp_name: test-vapp-1"
8
9
  echo "message: hello world"
10
+ echo "index0: foo"
11
+ echo "index1: bar"
9
12
  ) >> /PREAMBLE_OUTPUT
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ # This is a sample test file that is really just
4
+ # to confirm that when we run this through a
5
+ # postprocessor script, that the output is
6
+ # consistent.
7
+
@@ -3,7 +3,7 @@
3
3
  "name":"vapp-vcloud-tools-tests",
4
4
  "vdc_name":"VDC_NAME",
5
5
  "catalog":"CATALOG_NAME",
6
- "catalog_item":"CATALOG_ITEM",
6
+ "vapp_template":"VAPP_TEMPLATE",
7
7
  "vm":{
8
8
  "hardware_config":{"memory":"4096", "cpu":"2"},
9
9
  "extra_disks":[{"size":"8192"}],
@@ -3,7 +3,7 @@ vapps:
3
3
  - name: vapp-vcloud-tools-tests
4
4
  vdc_name: VDC_NAME
5
5
  catalog: CATALOG_NAME
6
- catalog_item: CATALOG_ITEM
6
+ vapp_template: VAPP_TEMPLATE
7
7
  vm:
8
8
  hardware_config:
9
9
  memory: '4096'
@@ -3,7 +3,7 @@ vapps:
3
3
  - name: vapp-vcloud-tools-tests
4
4
  vdc_name: VDC_NAME
5
5
  catalog: CATALOG_NAME
6
- catalog_item: CATALOG_ITEM
6
+ vapp_template: VAPP_TEMPLATE
7
7
  vm:
8
8
  hardware_config:
9
9
  memory: '4096'
@@ -6,7 +6,7 @@ vapps:
6
6
  - name: vapp-vcloud-tools-tests
7
7
  vdc_name: *VDC_NAME
8
8
  catalog: CATALOG_NAME
9
- catalog_item: CATALOG_ITEM
9
+ vapp_template: VAPP_TEMPLATE
10
10
  vm:
11
11
  hardware_config:
12
12
  memory: '4096'
@@ -8,10 +8,6 @@ module Vcloud
8
8
  @id = 'vappTemplate-12345678-1234-1234-1234-000000234121'
9
9
  @mock_fog_interface = StubFogInterface.new
10
10
  Vcloud::Fog::ServiceInterface.stub(:new).and_return(@mock_fog_interface)
11
- @test_config = {
12
- :catalog => 'test_catalog',
13
- :catalog_item => 'test_template'
14
- }
15
11
  end
16
12
 
17
13
  context "Class public interface" do
@@ -55,7 +51,7 @@ module Vcloud
55
51
  mock_query.should_receive(:run).
56
52
  with('vAppTemplate', :filter => "name==test_template;catalogName==test_catalog").
57
53
  and_return(q_results)
58
- expect { VappTemplate.get('test_catalog', 'test_template') }.
54
+ expect { VappTemplate.get('test_template', 'test_catalog') }.
59
55
  to raise_error('Could not find template vApp')
60
56
  end
61
57
 
@@ -71,7 +67,7 @@ module Vcloud
71
67
  mock_query.should_receive(:run).
72
68
  with('vAppTemplate', :filter => "name==test_template;catalogName==test_catalog").
73
69
  and_return(q_results)
74
- expect { VappTemplate.get('test_catalog', 'test_template') }.
70
+ expect { VappTemplate.get('test_template', 'test_catalog') }.
75
71
  to raise_error('Template test_template is not unique in catalog test_catalog')
76
72
  end
77
73
 
@@ -85,7 +81,7 @@ module Vcloud
85
81
  mock_query.should_receive(:run).
86
82
  with('vAppTemplate', :filter => "name==test_template;catalogName==test_catalog").
87
83
  and_return(q_results)
88
- test_template = VappTemplate.get('test_catalog', 'test_template')
84
+ test_template = VappTemplate.get('test_template', 'test_catalog')
89
85
  test_template.id.should == 'vappTemplate-12345678-90ab-cdef-0123-4567890abcde'
90
86
  end
91
87
 
@@ -181,18 +181,26 @@ module Vcloud
181
181
  end
182
182
 
183
183
  context '#generate_preamble' do
184
- it "should interpolate vars hash and vapp_name into template" do
185
- vars = {:message => 'hello world'}
184
+ it "should interpolate vars hash, ENV, and vapp_name into template" do
185
+ vars = {
186
+ :message => 'hello world',
187
+ :array_test => [ 'foo', 'bar' ],
188
+ }
189
+ stub_const('ENV', {'TEST_INTERPOLATED_ENVVAR' => 'test_interpolated_env'})
186
190
  erbfile = "#{@data_dir}/basic_preamble_test.erb"
187
191
  expected_output = File.read("#{erbfile}.OUT")
188
192
  @vm.generate_preamble(erbfile, nil, vars).should == expected_output
189
193
  end
190
194
 
191
- it "should interpolate vars hash and post-process template" do
192
- vars = {:message => 'hello world'}
193
- erbfile = "#{@data_dir}/basic_preamble_test.erb"
194
- expected_output = File.read("#{erbfile}.OUT")
195
- @vm.generate_preamble(erbfile, '/bin/cat', vars).should == expected_output
195
+ it "passes the output of ERB through an optional post-processor tool" do
196
+ # we use 'wc' as post processor since it will give us the character
197
+ # count in the file, which we can easily match on, and is common
198
+ # across most OSes.
199
+ vars = {}
200
+ erbfile = "#{@data_dir}/preamble_post_processor_test_input.erb"
201
+ characters_in_file = File.read(erbfile).size
202
+ expect(@vm.generate_preamble(erbfile, '/usr/bin/wc', vars)).
203
+ to match(/^\s+\d+\s+\d+\s+#{characters_in_file}\s/)
196
204
  end
197
205
  end
198
206
 
data/vcloud-core.gemspec CHANGED
@@ -32,4 +32,5 @@ Gem::Specification.new do |s|
32
32
  s.add_development_dependency 'rubocop'
33
33
  s.add_development_dependency 'simplecov', '~> 0.8.2'
34
34
  s.add_development_dependency 'gem_publisher', '1.2.0'
35
+ s.add_development_dependency 'vcloud-tools-tester', '0.0.3'
35
36
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vcloud-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-05-14 00:00:00.000000000 Z
12
+ date: 2014-05-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
@@ -171,6 +171,22 @@ dependencies:
171
171
  - - '='
172
172
  - !ruby/object:Gem::Version
173
173
  version: 1.2.0
174
+ - !ruby/object:Gem::Dependency
175
+ name: vcloud-tools-tester
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - '='
180
+ - !ruby/object:Gem::Version
181
+ version: 0.0.3
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - '='
188
+ - !ruby/object:Gem::Version
189
+ version: 0.0.3
174
190
  description: Core tools for interacting with VMware vCloud Director. Includes VCloud
175
191
  Query, a light wrapper round the vCloud Query API.
176
192
  email:
@@ -213,15 +229,19 @@ files:
213
229
  - lib/vcloud/fog/model_interface.rb
214
230
  - lib/vcloud/fog/relation.rb
215
231
  - lib/vcloud/fog/service_interface.rb
232
+ - spec/integration/README.md
233
+ - spec/integration/core/query_runner_spec.rb
216
234
  - spec/integration/edge_gateway/configure_edge_gateway_services_spec.rb
217
235
  - spec/integration/edge_gateway/edge_gateway_spec.rb
218
- - spec/integration/query/query_runner_spec.rb
236
+ - spec/integration/vcloud_tools_testing_config.yaml.template
219
237
  - spec/spec_helper.rb
238
+ - spec/support/integration_helper.rb
220
239
  - spec/support/stub_fog_interface.rb
221
240
  - spec/vcloud/core/config_loader_spec.rb
222
241
  - spec/vcloud/core/config_validator_spec.rb
223
242
  - spec/vcloud/core/data/basic_preamble_test.erb
224
243
  - spec/vcloud/core/data/basic_preamble_test.erb.OUT
244
+ - spec/vcloud/core/data/preamble_post_processor_test_input.erb
225
245
  - spec/vcloud/core/data/working.json
226
246
  - spec/vcloud/core/data/working.yaml
227
247
  - spec/vcloud/core/data/working_template.yaml
@@ -262,7 +282,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
262
282
  version: '0'
263
283
  segments:
264
284
  - 0
265
- hash: 2968796492213479520
285
+ hash: -2177023225627992680
266
286
  requirements: []
267
287
  rubyforge_project:
268
288
  rubygems_version: 1.8.23
@@ -272,15 +292,19 @@ summary: Core tools for interacting with VMware vCloud Director
272
292
  test_files:
273
293
  - features/support/env.rb
274
294
  - features/vcloud-query.feature
295
+ - spec/integration/README.md
296
+ - spec/integration/core/query_runner_spec.rb
275
297
  - spec/integration/edge_gateway/configure_edge_gateway_services_spec.rb
276
298
  - spec/integration/edge_gateway/edge_gateway_spec.rb
277
- - spec/integration/query/query_runner_spec.rb
299
+ - spec/integration/vcloud_tools_testing_config.yaml.template
278
300
  - spec/spec_helper.rb
301
+ - spec/support/integration_helper.rb
279
302
  - spec/support/stub_fog_interface.rb
280
303
  - spec/vcloud/core/config_loader_spec.rb
281
304
  - spec/vcloud/core/config_validator_spec.rb
282
305
  - spec/vcloud/core/data/basic_preamble_test.erb
283
306
  - spec/vcloud/core/data/basic_preamble_test.erb.OUT
307
+ - spec/vcloud/core/data/preamble_post_processor_test_input.erb
284
308
  - spec/vcloud/core/data/working.json
285
309
  - spec/vcloud/core/data/working.yaml
286
310
  - spec/vcloud/core/data/working_template.yaml