inspec 4.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +63 -0
  3. data/inspec.gemspec +36 -0
  4. data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/Gemfile +11 -0
  5. data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/inspec-plugin-template.gemspec +43 -0
  6. data/lib/plugins/inspec-init/templates/profiles/aws/README.md +192 -0
  7. data/lib/plugins/inspec-init/templates/profiles/aws/attributes.yml +2 -0
  8. data/lib/plugins/inspec-init/templates/profiles/aws/controls/example.rb +39 -0
  9. data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +22 -0
  10. data/lib/plugins/inspec-init/templates/profiles/azure/README.md +56 -0
  11. data/lib/plugins/inspec-init/templates/profiles/azure/controls/example.rb +14 -0
  12. data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +14 -0
  13. data/lib/plugins/inspec-init/templates/profiles/gcp/README.md +66 -0
  14. data/lib/plugins/inspec-init/templates/profiles/gcp/attributes.yml +2 -0
  15. data/lib/plugins/inspec-init/templates/profiles/gcp/controls/example.rb +27 -0
  16. data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +19 -0
  17. data/lib/resource_support/aws.rb +76 -0
  18. data/lib/resource_support/aws/aws_backend_base.rb +12 -0
  19. data/lib/resource_support/aws/aws_backend_factory_mixin.rb +12 -0
  20. data/lib/resource_support/aws/aws_plural_resource_mixin.rb +24 -0
  21. data/lib/resource_support/aws/aws_resource_mixin.rb +69 -0
  22. data/lib/resource_support/aws/aws_singular_resource_mixin.rb +27 -0
  23. data/lib/resources/aws/aws_billing_report.rb +107 -0
  24. data/lib/resources/aws/aws_billing_reports.rb +74 -0
  25. data/lib/resources/aws/aws_cloudtrail_trail.rb +97 -0
  26. data/lib/resources/aws/aws_cloudtrail_trails.rb +51 -0
  27. data/lib/resources/aws/aws_cloudwatch_alarm.rb +67 -0
  28. data/lib/resources/aws/aws_cloudwatch_log_metric_filter.rb +105 -0
  29. data/lib/resources/aws/aws_config_delivery_channel.rb +74 -0
  30. data/lib/resources/aws/aws_config_recorder.rb +99 -0
  31. data/lib/resources/aws/aws_ebs_volume.rb +127 -0
  32. data/lib/resources/aws/aws_ebs_volumes.rb +69 -0
  33. data/lib/resources/aws/aws_ec2_instance.rb +162 -0
  34. data/lib/resources/aws/aws_ec2_instances.rb +69 -0
  35. data/lib/resources/aws/aws_ecs_cluster.rb +88 -0
  36. data/lib/resources/aws/aws_eks_cluster.rb +105 -0
  37. data/lib/resources/aws/aws_elb.rb +85 -0
  38. data/lib/resources/aws/aws_elbs.rb +84 -0
  39. data/lib/resources/aws/aws_flow_log.rb +106 -0
  40. data/lib/resources/aws/aws_iam_access_key.rb +112 -0
  41. data/lib/resources/aws/aws_iam_access_keys.rb +153 -0
  42. data/lib/resources/aws/aws_iam_group.rb +62 -0
  43. data/lib/resources/aws/aws_iam_groups.rb +56 -0
  44. data/lib/resources/aws/aws_iam_password_policy.rb +121 -0
  45. data/lib/resources/aws/aws_iam_policies.rb +57 -0
  46. data/lib/resources/aws/aws_iam_policy.rb +311 -0
  47. data/lib/resources/aws/aws_iam_role.rb +60 -0
  48. data/lib/resources/aws/aws_iam_root_user.rb +82 -0
  49. data/lib/resources/aws/aws_iam_user.rb +145 -0
  50. data/lib/resources/aws/aws_iam_users.rb +160 -0
  51. data/lib/resources/aws/aws_kms_key.rb +100 -0
  52. data/lib/resources/aws/aws_kms_keys.rb +58 -0
  53. data/lib/resources/aws/aws_rds_instance.rb +74 -0
  54. data/lib/resources/aws/aws_route_table.rb +67 -0
  55. data/lib/resources/aws/aws_route_tables.rb +64 -0
  56. data/lib/resources/aws/aws_s3_bucket.rb +142 -0
  57. data/lib/resources/aws/aws_s3_bucket_object.rb +87 -0
  58. data/lib/resources/aws/aws_s3_buckets.rb +52 -0
  59. data/lib/resources/aws/aws_security_group.rb +314 -0
  60. data/lib/resources/aws/aws_security_groups.rb +71 -0
  61. data/lib/resources/aws/aws_sns_subscription.rb +82 -0
  62. data/lib/resources/aws/aws_sns_topic.rb +57 -0
  63. data/lib/resources/aws/aws_sns_topics.rb +60 -0
  64. data/lib/resources/aws/aws_sqs_queue.rb +66 -0
  65. data/lib/resources/aws/aws_subnet.rb +92 -0
  66. data/lib/resources/aws/aws_subnets.rb +56 -0
  67. data/lib/resources/aws/aws_vpc.rb +77 -0
  68. data/lib/resources/aws/aws_vpcs.rb +55 -0
  69. data/lib/resources/azure/azure_backend.rb +379 -0
  70. data/lib/resources/azure/azure_generic_resource.rb +55 -0
  71. data/lib/resources/azure/azure_resource_group.rb +151 -0
  72. data/lib/resources/azure/azure_virtual_machine.rb +262 -0
  73. data/lib/resources/azure/azure_virtual_machine_data_disk.rb +131 -0
  74. metadata +202 -0
@@ -0,0 +1,379 @@
1
+ # Base class for Azure Resources. This allows the generic class to work
2
+ # as well as the specific target resources for Azure Resources
3
+ #
4
+ # @author Russell Seymour
5
+ module Inspec::Resources
6
+ class AzureResourceBase < Inspec.resource(1)
7
+ attr_reader :opts, :client, :azure
8
+
9
+ # Constructor that retreives the specified resource
10
+ #
11
+ # The opts hash should contain the following
12
+ # :group_name - name of the resource group in which to look for items
13
+ # :type - the type of Azure resource to look for
14
+ # :apiversion - API version to use when looking for a specific resource
15
+ # :name - name of the resource to find
16
+ #
17
+ # @author Russell Seymour
18
+ #
19
+ # @param [Hash] opts Hashtable of options as highlighted above
20
+ # rubocop:disable Metrics/AbcSize
21
+ def initialize(opts)
22
+ # declare the hashtable of counts
23
+ @counts = {}
24
+ @total = 0
25
+ @opts = opts
26
+
27
+ # Determine if the environment variables for the options have been set
28
+ option_var_names = {
29
+ group_name: "AZURE_RESOURCE_GROUP_NAME",
30
+ name: "AZURE_RESOURCE_NAME",
31
+ type: "AZURE_RESOURCE_TYPE",
32
+ apiversion: "AZURE_RESOURCE_API_VERSION",
33
+ }
34
+ option_var_names.each do |option_name, env_var_name|
35
+ opts[option_name] = ENV[env_var_name] unless ENV[env_var_name].nil?
36
+ end
37
+
38
+ @azure = inspec.backend
39
+ @client = azure.azure_client
40
+ @failed_resource = false
41
+ end
42
+
43
+ def failed_resource?
44
+ @failed_resource
45
+ end
46
+
47
+ def catch_azure_errors
48
+ yield
49
+ rescue MsRestAzure::AzureOperationError => e
50
+ # e.message is actually a massive stringified JSON, which might be useful in the future.
51
+ # You want error_message here.
52
+ fail_resource e.error_message
53
+ @failed_resource = true
54
+ nil
55
+ end
56
+
57
+ # Return information about the resource group
58
+ def resource_group
59
+ catch_azure_errors do
60
+ resource_group = client.resource_groups.get(opts[:group_name])
61
+
62
+ # create the methods for the resource group object
63
+ dm = AzureResourceDynamicMethods.new
64
+ dm.create_methods(self, resource_group)
65
+ end
66
+ end
67
+
68
+ def resources
69
+ resources = nil
70
+ catch_azure_errors do
71
+ resources = client.resources.list_by_resource_group(opts[:group_name])
72
+ end
73
+ return if failed_resource?
74
+
75
+ # filter the resources based on the type, and the name if they been specified
76
+ resources = filter_resources(resources, opts)
77
+
78
+ # if there is one resource then define methods on this class
79
+ if resources.count == 1
80
+ @total = 1
81
+
82
+ resource = nil
83
+ catch_azure_errors do
84
+ # get the apiversion for the resource, if one has not been specified
85
+ apiversion = azure.get_api_version(resources[0].type, opts)
86
+
87
+ # get the resource by id so it can be interrogated
88
+ resource = client.resources.get_by_id(resources[0].id, apiversion)
89
+ end
90
+ return if failed_resource?
91
+
92
+ dm = AzureResourceDynamicMethods.new
93
+
94
+ dm.create_methods(self, resource)
95
+ else
96
+
97
+ # As there are many resources, parse each one so that it can be
98
+ # interrogated by the FilterTable
99
+ # @probes = parse_resources(resources, azure)
100
+ @probes = resources.each.map do |item|
101
+ # update the total
102
+ @total += 1
103
+
104
+ # determine the counts for each type
105
+ namespace, type_name = item.type.split(/\./)
106
+ counts.key?(namespace) ? false : counts[namespace] = {}
107
+ counts[namespace].key?(type_name) ? counts[namespace][type_name] += 1 : counts[namespace][type_name] = 1
108
+
109
+ # get the detail about the resource
110
+ apiversion = azure.get_api_version(item.type, opts)
111
+ resource = client.resources.get_by_id(item.id, apiversion)
112
+
113
+ # parse the resource
114
+ parse_resource(resource)
115
+ end.compact
116
+
117
+ # Iterate around the counts and create the necessary classes
118
+ counts.each do |namespace, ns_counts|
119
+ define_singleton_method namespace do
120
+ AzureResourceTypeCounts.new(ns_counts)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ # Does the resource have any tags?
127
+ #
128
+ # If it is a Hashtable then it does not, because there was nothing to parse so there is not
129
+ # a nested object to work with
130
+ #
131
+ # @author Russell Seymour
132
+ def has_tags?
133
+ tags.is_a?(Hash) ? false : true
134
+ end
135
+
136
+ # Returns how many tags have been set on the resource
137
+ #
138
+ # @author Russell Seymour
139
+ def tag_count
140
+ tags.count
141
+ end
142
+
143
+ # It is necessary to be able to test the tags of a resource. It is possible to say of the
144
+ # resource has tags or not, and it is possible to check that the tags include a specific tag
145
+ # However the value is not accessible, this function creates methods for all the tags that
146
+ # are available.
147
+ #
148
+ # The format of the method name is '<TAG_NAME>_tag' and will return the value of that tag
149
+ #
150
+ # Disabling rubopcop check. If this is set as a normal if..then..end statement there is a
151
+ # violation stating it should use a guard. When using a guard it throws this error
152
+ #
153
+ # @author Russell Seymour
154
+ def create_tag_methods
155
+ # Iterate around the items of the tags and create the necessary access methods
156
+ if defined?(tags.item)
157
+ tags.item.each do |name, value|
158
+ method_name = format("%s_tag", name)
159
+ define_singleton_method method_name do
160
+ value
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ # Filter the resources that are returned by the options that have been specified
169
+ #
170
+ def filter_resources(resources, opts)
171
+ if opts[:type] && opts[:name]
172
+ resources.select { |r| r.type == opts[:type] && r.name == opts[:name] }
173
+ elsif opts[:type]
174
+ resources.select { |r| r.type == opts[:type] }
175
+ elsif opts[:name]
176
+ resources.select { |r| r.name == opts[:name] }
177
+ else
178
+ resources
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ # Class to create methods on the calling object at run time.
185
+ # Each of the Azure Resources have different attributes and properties, and they all need
186
+ # to be testable. To do this no methods are hardcoded, each on is craeted based on the
187
+ # information returned from Azure.
188
+ #
189
+ # The class is a helper class essentially as it creates the methods on the calling class
190
+ # rather than itself. This means that there is less duplication of code and it can be
191
+ # reused easily.
192
+ #
193
+ # @author Russell Seymour
194
+ # @since 0.2.0
195
+ class AzureResourceDynamicMethods
196
+ # Given the calling object and its data, create the methods on the object according
197
+ # to the data that has been retrieved. Various types of data can be returned so the method
198
+ # checks the type to ensure that the necessary methods are configured correctly
199
+ #
200
+ # @param AzureResourceProbe|AzureResource object The object on which the methods should be craeted
201
+ # @param variant data The data from which the methods should be created
202
+ def create_methods(object, data)
203
+ # Check the type of data as this affects the setup of the methods
204
+ # If it is an Azure Generic Resource then setup methods for each of
205
+ # the instance variables
206
+ case data.class.to_s
207
+ when /^Azure::Resources::Mgmt::.*::Models::GenericResource$/,
208
+ /^Azure::Resources::Mgmt::.*::Models::ResourceGroup$/
209
+ # iterate around the instance variables
210
+ data.instance_variables.each do |var|
211
+ create_method(object, var.to_s.delete("@"), data.instance_variable_get(var))
212
+ end
213
+ # When the data is a Hash object iterate around each of the key value pairs and
214
+ # craete a method for each one.
215
+ when "Hash"
216
+ data.each do |key, value|
217
+ create_method(object, key, value)
218
+ end
219
+ end
220
+ end
221
+
222
+ private
223
+
224
+ # Method that is responsible for creating the method on the calling object. This is
225
+ # because some nesting maybe required. For example of the value is a Hash then it will
226
+ # need to have an AzureResourceProbe create for each key, whereas if it is a simple
227
+ # string then the value just needs to be returned
228
+ #
229
+ # @private
230
+ #
231
+ # @param AzureResourceProbe|AzureResource object Object on which the methods need to be created
232
+ # @param string name The name of the method
233
+ # @param variant value The value that needs to be returned by the method
234
+ def create_method(object, name, value)
235
+ # Create the necessary method based on the var that has been passed
236
+ # Test the value for its type so that the method can be setup correctly
237
+ case value.class.to_s
238
+ when "String", "Integer", "TrueClass", "FalseClass", "Fixnum"
239
+ object.define_singleton_method name do
240
+ value
241
+ end
242
+ when "Hash"
243
+ value.count == 0 ? return_value = value : return_value = AzureResourceProbe.new(value)
244
+ object.define_singleton_method name do
245
+ return_value
246
+ end
247
+ when /^Azure::Resources::Mgmt::.*::Models::ResourceGroupProperties$/
248
+ # This is a special case where the properties of the resource group is not a simple JSON model
249
+ # This is because the plugin is using the Azure SDK to get this information so it is an SDK object
250
+ # that has to be interrogated in a different way. This is the only object type that behaves like this
251
+ value.instance_variables.each do |var|
252
+ create_method(object, var.to_s.delete("@"), value.instance_variable_get(var))
253
+ end
254
+ when "Array"
255
+ # Some things are just string or integer arrays
256
+ # Check this by seeing if the first element is a string / integer / boolean or
257
+ # a hashtable
258
+ # This may not be the best methid, but short of testing all elements in the array, this is
259
+ # the quickest test
260
+ case value[0].class.to_s
261
+ when "String", "Integer", "TrueClass", "FalseClass", "Fixnum"
262
+ probes = value
263
+ else
264
+ probes = []
265
+ value.each do |value_item|
266
+ probes << AzureResourceProbe.new(value_item)
267
+ end
268
+ end
269
+ object.define_singleton_method name do
270
+ probes
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ # Class object to maintain a count of the Azure Resource types that are found
277
+ # when a less specific test is carried out. For example if all the resoures of a resource
278
+ # group are called for, there will be variaous types and number of those types.
279
+ #
280
+ # Each type is namespaced, so for example a virtual machine has the type 'Microsoft.Compute/virtualMachines'
281
+ # This is broken down into the 'Microsoft' class with the type 'Compute/virtualMachines'
282
+ # This has been done for two reasons:
283
+ # 1. Enable the dotted notation to work in the test
284
+ # 2. Allow third party resource types ot be catered for if they are ever enabled by Microsoft
285
+ #
286
+ # @author Russell Seymour
287
+ # @since 0.2.0
288
+ class AzureResourceTypeCounts
289
+ # Constructor to setup a new class for a specific Azure Resource type.
290
+ # It should be passed a hashtable with information such as:
291
+ # {
292
+ # "Compute/virtualMachines" => 2,
293
+ # "Network/networkInterfaces" => 3
294
+ # }
295
+ # This will result in two methods being created on the class:
296
+ # - Compute/virtualNetworks
297
+ # - Network/networkInterfaces
298
+ # Each of which will return the corresponding count value
299
+ #
300
+ # @param Hash counts Hash table of types and the count of each one
301
+ #
302
+ # @return AzureResourceTypeCounts
303
+ def initialize(counts)
304
+ counts.each do |type, count|
305
+ define_singleton_method type do
306
+ count
307
+ end
308
+ end
309
+ end
310
+ end
311
+
312
+ # Class object that is created for each element that is returned by Azure.
313
+ # This is what is interogated by Inspec. If they are nested hashes, then this results
314
+ # in nested AzureResourceProbe objects.
315
+ #
316
+ # For example, if the following was seen in an Azure Resource
317
+ # properties -> storageProfile -> imageReference
318
+ # Would result in the following nestec classes
319
+ # AzureResource -> AzureResourceProbe -> AzureResourceProbe
320
+ #
321
+ # The methods for each of the classes are dynamically defined at run time and will
322
+ # match the items that are retrieved from Azure. See the 'test/integration/verify/controls' for
323
+ # examples
324
+ #
325
+ # This class will not be called externally
326
+ #
327
+ # @author Russell Seymour
328
+ # @since 0.2.0
329
+ # @attr_reader string name Name of the Azure resource
330
+ # @attr_reader string type Type of the Azure Resource
331
+ # @attr_reader string location Location in Azure of the resource
332
+ class AzureResourceProbe
333
+ attr_reader :name, :type, :location, :item, :count
334
+
335
+ # Initialize method for the class. Accepts an item, be it a scalar value, hash or Azure object
336
+ # It will then create the necessary dynamic methods so that they can be called in the tests
337
+ # This is accomplished by call the AzureResourceDynamicMethods
338
+ #
339
+ # @param varaint The item from which the class will be initialized
340
+ #
341
+ # @return AzureResourceProbe
342
+ def initialize(item)
343
+ dm = AzureResourceDynamicMethods.new
344
+ dm.create_methods(self, item)
345
+
346
+ # Set the item as a property on the class
347
+ # This is so that it is possible to interrogate what has been added to the class and isolate them from
348
+ # the standard methods that a Ruby class has.
349
+ # This used for checking Tags on a resource for example
350
+ # It also allows direct access if so required
351
+ @item = item
352
+
353
+ # Set how many items have been set
354
+ @count = item.length
355
+ end
356
+
357
+ # Allows resources to respond to the include test
358
+ # This means that things like tags can be checked for and then their value tested
359
+ #
360
+ # @author Russell Seymour
361
+ #
362
+ # @param [String] key Name of the item to look for in the @item property
363
+ def include?(key)
364
+ @item.key?(key)
365
+ end
366
+
367
+ # Give a sting like `computer_name` return the camelCase version, e.g.
368
+ # computerName
369
+ #
370
+ # @param string data Data that needs to be converted from snake_case to camelCase
371
+ #
372
+ # @return string
373
+ def camel_case(data)
374
+ camel_case_data = data.split("_").inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
375
+
376
+ # Ensure that gb (as in gigabytes) is uppercased
377
+ camel_case_data.gsub(/[gb]/, &:upcase)
378
+ end
379
+ end
@@ -0,0 +1,55 @@
1
+ require "resources/azure/azure_backend"
2
+ require "inspec/utils/filter"
3
+
4
+ module Inspec::Resources
5
+ class AzureGenericResource < AzureResourceBase
6
+ name "azure_generic_resource"
7
+
8
+ desc '
9
+ InSpec Resource to interrogate any Resource type in Azure
10
+ '
11
+
12
+ supports platform: "azure"
13
+
14
+ attr_accessor :filter, :total, :counts, :name, :type, :location, :probes
15
+
16
+ def initialize(opts = {})
17
+ Inspec.deprecate(:resource_azure_generic_resource)
18
+
19
+ # Call the parent class constructor
20
+ super(opts)
21
+
22
+ # Get the resource group
23
+ resource_group
24
+
25
+ # Get the resources
26
+ resources
27
+
28
+ # Create the tag methods
29
+ create_tag_methods
30
+ end
31
+
32
+ # Define the filter table so that it can be interrogated
33
+ @filter = FilterTable.create
34
+ @filter.register_filter_method(:contains)
35
+ .register_column(:type, field: "type")
36
+ .register_column(:name, field: "name")
37
+ .register_column(:location, field: "location")
38
+ .register_column(:properties, field: "properties")
39
+
40
+ @filter.install_filter_methods_on_resource(self, :probes)
41
+
42
+ def parse_resource(resource)
43
+ # return a hash of information
44
+ parsed = {
45
+ "location" => resource.location,
46
+ "name" => resource.name,
47
+ "type" => resource.type,
48
+ "exist?" => true,
49
+ "properties" => AzureResourceProbe.new(resource.properties),
50
+ }
51
+
52
+ parsed
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,151 @@
1
+ require "resources/azure/azure_backend"
2
+
3
+ module Inspec::Resources
4
+ class AzureResourceGroup < AzureResourceBase
5
+ name "azure_resource_group"
6
+
7
+ desc '
8
+ InSpec Resource to get metadata about a specific Resource Group
9
+ '
10
+
11
+ supports platform: "azure"
12
+
13
+ attr_reader :name, :location, :id, :total, :counts, :mapping
14
+
15
+ # Constructor to get the resource group itself and perform some analysis on the
16
+ # resources that in the resource group.
17
+ #
18
+ # This analysis is defined by the the mapping hashtable which is used to define
19
+ # the 'has_xxx?' methods (see AzureResourceGroup#create_has_methods) and return
20
+ # the counts for each type
21
+ #
22
+ # @author Russell Seymour
23
+ def initialize(opts)
24
+ opts.key?(:name) ? opts[:group_name] = opts[:name] : false
25
+ # Ensure that the opts only have the name of the resource group set
26
+ opts.select! { |k, _v| k == :group_name }
27
+ super(opts)
28
+
29
+ # set the mapping for the Azure Resources
30
+ @mapping = {
31
+ nic: "Microsoft.Network/networkInterfaces",
32
+ vm: "Microsoft.Compute/virtualMachines",
33
+ extension: "Microsoft.Compute/virtualMachines/extensions",
34
+ nsg: "Microsoft.Network/networkSecurityGroups",
35
+ vnet: "Microsoft.Network/virtualNetworks",
36
+ managed_disk: "Microsoft.Compute/disks",
37
+ managed_disk_image: "Microsoft.Compute/images",
38
+ sa: "Microsoft.Storage/storageAccounts",
39
+ public_ip: "Microsoft.Network/publicIPAddresses",
40
+ }
41
+
42
+ # Get information about the resource group itself
43
+ resource_group
44
+
45
+ # Get information about the resources in the resource group
46
+ resources
47
+
48
+ # Call method to create the has_xxxx? methods
49
+ create_has_methods
50
+
51
+ # Call method to allow access to the tag values
52
+ create_tag_methods
53
+ end
54
+
55
+ # Return the provisioning state of the resource group
56
+ #
57
+ # @author Russell Seymour
58
+ def provisioning_state
59
+ properties.provisioningState
60
+ end
61
+
62
+ # Analyze the fully qualified id of the resource group to return the subscription id
63
+ # that this resource group is part of
64
+ #
65
+ # The format of the id is
66
+ # /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>
67
+ #
68
+ # @author Russell Seymour
69
+ def subscription_id
70
+ id.split(%r{\/}).reject(&:empty?)[1]
71
+ end
72
+
73
+ # Method to parse the resources that have been returned
74
+ # This allows the calculations of the amount of resources to be determined
75
+ #
76
+ # @author Russell Seymour
77
+ #
78
+ # @param [Hash] resource A hashtable representing the resource group
79
+ def parse_resource(resource)
80
+ # return a hash of information
81
+ parsed = {
82
+ "name" => resource.name,
83
+ "type" => resource.type,
84
+ }
85
+
86
+ parsed
87
+ end
88
+
89
+ # This method catches the xxx_count calls that are made on the resource.
90
+ #
91
+ # The method that is called is stripped of '_count' and then compared with the
92
+ # mappings table. If that type exists then the number of those items is returned.
93
+ # However if that type is not in the Resource Group then the method will return
94
+ # a NoMethodError exception
95
+ #
96
+ # @author Russell Seymour
97
+ #
98
+ # @param [Symbol] method_id The name of the method that was called
99
+ def method_missing(method_id)
100
+ # Determine the mapping_key based on the method_id
101
+ mapping_key = method_id.to_s.chomp("_count").to_sym
102
+
103
+ if mapping.key?(mapping_key)
104
+ # based on the method id get the
105
+ namespace, type_name = mapping[mapping_key].split(/\./)
106
+
107
+ # check that the type_name is defined, if not return 0
108
+ if send(namespace).methods.include?(type_name.to_sym)
109
+ # return the count for the method id
110
+ send(namespace).send(type_name)
111
+ else
112
+ 0
113
+ end
114
+ else
115
+ msg = format("undefined method `%s` for %s", method_id, self.class)
116
+ raise NoMethodError, msg
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ # For each of the mappings this method creates the has_xxx? method. This allows the use
123
+ # of the following type of test
124
+ #
125
+ # it { should have_nics }
126
+ #
127
+ # For example, it will create a has_nics? method that returns a boolean to state of the
128
+ # resource group has any nics at all.
129
+ #
130
+ # @author Russell Seymour
131
+ # @private
132
+ def create_has_methods
133
+ return if failed_resource?
134
+
135
+ # Create the has methods for each of the mappings
136
+ # This is a quick test to show that the resource group has at least one of these things
137
+ mapping.each do |name, type|
138
+ # Determine the name of the method name
139
+ method_name = format("has_%ss?", name)
140
+ namespace, type_name = type.split(/\./)
141
+
142
+ # use the namespace and the type_name to determine if the resource group has this type or not
143
+ result = send(namespace).methods.include?(type_name.to_sym) ? true : false
144
+
145
+ define_singleton_method method_name do
146
+ result
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end