inspec 4.22.1

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.
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