-
Notifications
You must be signed in to change notification settings - Fork 105
/
Copy pathaws_backend.rb
770 lines (658 loc) · 23.5 KB
/
aws_backend.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
require "active_support"
require "active_support/core_ext/string"
require "aws-sdk-autoscaling"
require "aws-sdk-batch"
require "aws-sdk-cloudformation"
require "aws-sdk-cloudfront"
require "aws-sdk-cloudtrail"
require "aws-sdk-cloudwatch"
require "aws-sdk-cloudwatchlogs"
require "aws-sdk-configservice"
require "aws-sdk-core"
require "aws-sdk-dynamodb"
require "aws-sdk-ec2"
require "aws-sdk-ecr"
require "aws-sdk-ecrpublic"
require "aws-sdk-ecs"
require "aws-sdk-eks"
require "aws-sdk-elasticache"
require "aws-sdk-elasticloadbalancing"
require "aws-sdk-elasticloadbalancingv2"
require "aws-sdk-guardduty"
require "aws-sdk-iam"
require "aws-sdk-kms"
require "aws-sdk-lambda"
require "aws-sdk-organizations"
require "aws-sdk-rds"
require "aws-sdk-route53"
require "aws-sdk-s3"
require "aws-sdk-shield"
require "aws-sdk-sns"
require "aws-sdk-sqs"
require "aws-sdk-efs"
require "aws-sdk-ssm"
require "rspec/expectations"
require "aws-sdk-transfer"
require "aws-sdk-elasticsearchservice"
require "aws-sdk-cognitoidentity"
require "aws-sdk-redshift"
require "aws-sdk-athena"
require "aws-sdk-applicationautoscaling"
require "aws-sdk-cognitoidentityprovider"
require "aws-sdk-apigateway"
require "aws-sdk-databasemigrationservice"
require "aws-sdk-route53resolver"
require "aws-sdk-servicecatalog"
require "aws-sdk-glue"
require "aws-sdk-eventbridge"
require "aws-sdk-states"
require "aws-sdk-ram"
require "aws-sdk-secretsmanager"
require "aws-sdk-networkfirewall"
require "aws-sdk-mq"
require "aws-sdk-networkmanager"
require "aws-sdk-signer"
require "aws-sdk-amplify"
require "aws-sdk-simpledb"
require "aws-sdk-emr"
require "aws-sdk-securityhub"
require "aws-sdk-ses"
require "aws-sdk-waf"
require "aws-sdk-wafv2"
require "aws-sdk-synthetics"
require "aws-sdk-apigatewayv2"
# AWS Inspec Backend Classes
#
# Class to manage the AWS connection, instantiates all required clients for inspec resources
#
class AwsConnection
def initialize(params)
params = {} if params.nil?
# Special case for AWS, let's allow all resources to specify parameters that propagate to the client init
# This can be useful for e.g.
# https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/stubbing.html
# https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html#aws-ruby-sdk-setting-non-standard-endpoint
if params.is_a?(Hash)
@client_args = params.fetch(:client_args, nil)
end
@cache = {}
end
def aws_client(klass)
# TODO: make this a dict with keys of klass.to_s.to_sym such that we can send different args per client in cases such as EC2 instance that use multiple different clients
return @cache[klass.to_s.to_sym] ||= klass.new(@client_args) if @client_args
@cache[klass.to_s.to_sym] ||= klass.new
end
def aws_resource(klass, args)
return klass.new(args, @client_args) if @client_args
klass.new(args)
end
def unique_identifier
# use aws account id
client = aws_client(::Aws::STS::Client)
client.get_caller_identity.account
end
# SDK Client convenience methods
def cloudformation_client
aws_client(Aws::CloudFormation::Client)
end
def cloudfront_client
aws_client(Aws::CloudFront::Client)
end
def cloudtrail_client
aws_client(Aws::CloudTrail::Client)
end
def cloudwatch_client
aws_client(Aws::CloudWatch::Client)
end
def cloudwatchlogs_client
aws_client(Aws::CloudWatchLogs::Client)
end
def compute_client
aws_client(Aws::EC2::Client)
end
def batch_client
aws_client(Aws::Batch::Client)
end
def config_client
aws_client(Aws::ConfigService::Client)
end
def dynamodb_client
aws_client(Aws::DynamoDB::Client)
end
def ecr_client
aws_client(Aws::ECR::Client)
end
def ecrpublic_client
aws_client(Aws::ECRPublic::Client)
end
def ecs_client
aws_client(Aws::ECS::Client)
end
def efs_client
aws_client(Aws::EFS::Client)
end
def eks_client
aws_client(Aws::EKS::Client)
end
def elasti_cache_client
aws_client(Aws::ElastiCache::Client)
end
def elb_client
aws_client(Aws::ElasticLoadBalancing::Client)
end
def elb_client_v2
aws_client(Aws::ElasticLoadBalancingV2::Client)
end
def guardduty_client
aws_client(Aws::GuardDuty::Client)
end
def iam_client
aws_client(Aws::IAM::Client)
end
def kms_client
aws_client(Aws::KMS::Client)
end
def lambda_client
aws_client(Aws::Lambda::Client)
end
def org_client
aws_client(Aws::Organizations::Client)
end
def rds_client
aws_client(Aws::RDS::Client)
end
def route53_client
aws_client(Aws::Route53::Client)
end
def service_client
aws_client(Aws::AutoScaling::Client)
end
def shield_client
aws_client(Aws::Shield::Client)
end
def sqs_client
aws_client(Aws::SQS::Client)
end
def sns_client
aws_client(Aws::SNS::Client)
end
def storage_client
aws_client(Aws::S3::Client)
end
def sts_client
aws_client(Aws::STS::Client)
end
def ssm_client
aws_client(Aws::SSM::Client)
end
def glue_client
aws_client(Aws::Glue::Client)
end
def athena_client
aws_client(Aws::Athena::Client)
end
def redshift_client
aws_client(Aws::Redshift::Client)
end
def autoscaling_client
aws_client(Aws::AutoScaling::Client)
end
def cognitoidentity_client
aws_client(Aws::CognitoIdentity::Client)
end
def cognitoidentityprovider_client
aws_client(Aws::CognitoIdentityProvider::Client)
end
def apigateway_client
aws_client(Aws::APIGateway::Client)
end
def applicationautoscaling_client
aws_client(Aws::ApplicationAutoScaling::Client)
end
def ram_client
aws_client(Aws::RAM::Client)
end
def dmsmigrationservice_client
aws_client(Aws::DatabaseMigrationService::Client)
end
def elasticsearch_service_client
aws_client(Aws::ElasticsearchService::Client)
end
def eventbridge_client
aws_client(Aws::EventBridge::Client)
end
def states_client
aws_client(Aws::States::Client)
end
def transfer_client
aws_client(Aws::Transfer::Client)
end
def servicecatalog_client
aws_client(Aws::ServiceCatalog::Client)
end
def route53resolver_client
aws_client(Aws::Route53Resolver::Client)
end
def secretsmanager_client
aws_client(Aws::SecretsManager::Client)
end
def signer_client
aws_client(Aws::Signer::Client)
end
def network_manager_client
aws_client(Aws::NetworkManager::Client)
end
def mq_client
aws_client(Aws::MQ::Client)
end
def securityhub_client
aws_client(Aws::SecurityHub::Client)
end
def amplify_client
aws_client(Aws::Amplify::Client)
end
def network_firewall_client
aws_client(Aws::NetworkFirewall::Client)
end
def simpledb_client
aws_client(Aws::SimpleDB::Client)
end
def emr_client
aws_client(Aws::EMR::Client)
end
def ses_client
aws_client(Aws::SES::Client)
end
def waf_client
aws_client(Aws::WAF::Client)
end
def waf_client_v2
aws_client(Aws::WAFWAFV2::Client)
end
def synthetics_client
aws_client(Aws::Synthetics::Client)
end
def apigatewayv2_client
aws_client(Aws::ApiGatewayV2::Client)
end
end
# Base class for AWS resources
#
class AwsResourceBase < Inspec.resource(1)
attr_reader :opts, :aws
# disabled to address CI failure
# rubocop:disable Lint/MissingSuper
def initialize(opts)
@opts = opts
# ensure we have a AWS connection, resources can choose which of the clients to instantiate
client_args = { client_args: {} }
if opts.is_a?(Hash)
# below allows each resource to optionally and conveniently set a region
client_args[:client_args][:region] = opts[:aws_region] if opts[:aws_region]
# below allows each resource to optionally and conveniently set an endpoint
client_args[:client_args][:endpoint] = opts[:aws_endpoint] if opts[:aws_endpoint]
# below allows each resource to optionally and conveniently set max_retries and retry_backoff
env_hash = ENV.map { |k, v| [k.downcase, v] }.to_h
opts[:aws_retry_limit]= env_hash["aws_retry_limit"].to_i if !opts[:aws_retry_limit] && env_hash["aws_retry_limit"]
opts[:aws_retry_backoff]= env_hash["aws_retry_backoff"].to_i if !opts[:aws_retry_backoff] && env_hash["aws_retry_backoff"]
client_args[:client_args][:retry_limit] = opts[:aws_retry_limit] if opts[:aws_retry_limit]
client_args[:client_args][:retry_backoff] = "lambda { |c| sleep(#{opts[:aws_retry_backoff]}) }" if opts[:aws_retry_backoff]
# this catches the stub_data true option for unit testing - and others that could be useful for consumers
client_args[:client_args].update(opts[:client_args]) if opts[:client_args]
@resource_data = opts[:resource_data].presence&.to_h
end
@aws = AwsConnection.new(client_args)
# N.B. if/when we migrate AwsConnection to train, can update above and inject args via:
# inspec.backend.aws_client(Aws::EC2::Resource,opts)
# inspec.backend.aws_resource(Aws::EC2::Resource,opts)
# However, for the unit testing case, would potentially have to instantiate the client ourselves...
# here we might want to inject stub data for testing, let's use an option for that
return if !defined?(@opts.keys) || !@opts.include?(:stub_data)
raise ArgumentError, "Expected stub data to be an array" if !opts[:stub_data].is_a?(Array)
opts[:stub_data].each do |stub|
raise ArgumentError, "Expect each stub_data hash to have :client, :method and :data keys" if !stub.keys.all? { |a| %i(method data client).include?(a) }
@aws.aws_client(stub[:client]).stub_responses(stub[:method], stub[:data])
end
end
# rubocop:enable Lint/MissingSuper
# Ensure required parameters have been set to perform backend operations.
# Some resources may require several parameters to be set, in which case use `required`
# Some resources may require at least 1 of n parameters to be set, in which case use `require_any_of`
# If a parameter is entirely optional, use `allow`
def validate_parameters(allow: [], required: nil, require_any_of: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
if required
raise ArgumentError, "Expected required parameters as Array of Symbols, got #{required}" unless required.is_a?(Array) && required.all? { |r| r.is_a?(Symbol) }
raise ArgumentError, "#{@__resource_name__}: `#{required}` must be provided" unless @opts.is_a?(Hash) && required.all? { |req| @opts.key?(req) && !@opts[req].nil? && @opts[req] != "" }
allow += required
end
if require_any_of
raise ArgumentError, "Expected required parameters as Array of Symbols, got #{require_any_of}" unless require_any_of.is_a?(Array) && require_any_of.all? { |r| r.is_a?(Symbol) }
raise ArgumentError, "#{@__resource_name__}: One of `#{require_any_of}` must be provided." unless @opts.is_a?(Hash) && require_any_of.any? { |req| @opts.key?(req) && !@opts[req].nil? && @opts[req] != "" }
allow += require_any_of
end
allow += %i(client_args stub_data aws_region aws_endpoint aws_retry_limit aws_retry_backoff resource_data)
raise ArgumentError, "Scalar arguments not supported" unless defined?(@opts.keys)
raise ArgumentError, "Unexpected arguments found" unless @opts.keys.all? { |a| allow.include?(a) }
raise ArgumentError, "Provided parameter should not be empty" unless @opts.values.all? do |a|
return true if a.instance_of?(Integer)
return true if [TrueClass, FalseClass].include?(a.class)
!a.empty?
end
true
end
def failed_resource?
@failed_resource ||= false
end
def tags
return if failed_resource?
# Prevent `NoMethod` error if AWS API does not return `tags` property when it is not applied to the resource.
# E.g., subnets.
{}
end
def name
return unless tags
return tags["Name"] if tags.is_a?(Hash)
# tags might be in the original format: [{:key=>"Name", :value=>"aws-linux-ubuntu-vm"}], e.g in EC2
tags.select { |tag| tag[:key] == "Name" }.first&.dig(:value)
end
# Intercept AWS exceptions
def catch_aws_errors
yield # Catch and create custom messages as needed
rescue Aws::Errors::MissingCredentialsError
Inspec::Log.error "It appears that you have not set your AWS credentials. See https://www.inspec.io/docs/reference/platforms for details."
fail_resource("No AWS credentials available")
nil
rescue Aws::Errors::NoSuchEndpointError
Inspec::Log.error "The endpoint that is trying to be accessed does not exist."
fail_resource("Invalid Endpoint error")
nil
rescue Aws::Errors::ServiceError => e
if is_permissions_error(e)
advice = ""
error_type = e.class.to_s.split("::").last
case error_type
when "InvalidAccessKeyId"
advice = "Please ensure your AWS Access Key ID is set correctly."
when "InvalidClientTokenId"
advice = "Please ensure that the aws access key, aws secret access key, and the aws session token are correct."
when "AccessDenied"
advice = "Please check the IAM permissions required for this Resource in the documentation, " \
"and ensure your Service Principal has these permissions set."
end
error_message = "#{e.message}: #{advice}"
raise Inspec::Exceptions::ResourceFailed, error_message
else
Inspec::Log.warn "AWS Service Error encountered running a control with Resource #{@__resource_name__}. " \
"Error message: #{e.message}. You should address this error to ensure your controls are " \
"behaving as expected."
@failed_resource = true
end
nil
end
def create_resource_methods(object)
dm = AwsResourceDynamicMethods.new
dm.create_methods(self, object)
end
# Each client has its own variation of Aws::*::Errors::AccessDenied, making the checking cumbersome and flaky.
# Checking the status code is more reliable.
def is_permissions_error(error)
true if error.context.http_response.status_code == 403
end
def map_tags(tag_list)
return {} if tag_list.nil? || tag_list.empty?
tags = {}
tag_list.each do |tag|
tags[tag[:key]] = tag[:value]
end
tags
end
# Prevent undefined method error by returning nil.
# This will prevent breaking a test when queried a non-existing method.
# @return [NilClass]
# @see https://github.com/inspec/inspec-azure/blob/master/libraries/support/azure/response.rb
def method_missing(method_name, *args, &block)
unless respond_to?(method_name)
NullResponse.new
else
super
end
end
# This is to make RuboCop happy.
# Disabling Useless method definition detection as there is an issue with rubocop
# rubocop:disable Lint/UselessMethodDefinition
def respond_to_missing?(*several_variants)
super
end
# rubocop:enable Lint/UselessMethodDefinition
private
# This method should be used when AWS API returns multiple resources for the provided criteria.
def resource_fail(message = nil)
message ||= "#{@__resource_name__}: #{@display_name}. Multiple AWS resources were returned for the provided criteria. "\
"If you wish to test multiple entities, please use the plural resource. "\
"Otherwise, please provide more specific criteria to lookup the resource."
# Fail resource in resource pack. `exists?` method will return `false`.
@failed_resource = true
# Fail resource in InSpec core. Tests in InSpec profile will return the message.
fail_resource(message)
end
# This method should be used when AWS API returns an empty response, e.g. `[]`.
def empty_response_warn(message = nil)
message ||= "#{@__resource_name__}: #{@display_name} not found."
# Fail resource in resource pack. `exists?` method will return `false`.
@failed_resource = true
# Do not fail in InSpec core. The test `it { should_not exist }` will pass.
Inspec::Log.warn message
end
end
class AwsCollectionResourceBase < AwsResourceBase
attr_reader :table
# Populate the FilterTable.
# FilterTable is a class bound object so is this method.
# @param raw_data [Symbol] Method name of the table with raw data.
# @param table_scheme [Array] [{column: :blahs, field: :blah}, {..}]
def self.populate_filter_table(raw_data, table_scheme)
filter_table = FilterTable.create
table_scheme.each do |col_field|
opts = { field: col_field[:field] }
opts[:style] = col_field[:style] if col_field[:style]
filter_table.register_column(col_field[:column], opts)
end
filter_table.install_filter_methods_on_resource(self, raw_data)
end
def fetch(client:, operation:, kwargs: {})
raise ArgumentError, "Valid Client not found!" unless @aws.respond_to?(client)
client_obj = @aws.send(client)
raise ArgumentError, "#{client} does not support #{operation}" unless client_obj.respond_to?(operation)
catch_aws_errors do
client_obj.send(operation, **kwargs)
end
end
private
def populate_filter_table_from_response
return unless @table.present?
table_schema = @table.first.keys.map { |key| { column: key.to_s.pluralize.to_sym, field: key, style: :simple } }
AwsCollectionResourceBase.populate_filter_table(:table, table_schema)
end
end
# Class to create methods on the calling object at run time. Heavily based on the Azure Inspec resources.
#
class AwsResourceDynamicMethods
# Given the calling object and its data, create the methods on the object according
# to the data that has been retrieved. Various types of data can be returned so the method
# checks the type to ensure that the necessary methods are configured correctly
#
# @param object The object on which the methods should be created
# @param data The data from which the methods should be created
def create_methods(object, data)
# Check the type of data as this affects the setup of the methods
# If it is an Aws Generic Resource then setup methods for each of
# the instance variables
case data.class.to_s
when /Aws::.*/
# iterate around the instance variables
data.instance_variables.each do |var|
create_method(object, var.to_s.delete("@"), data.instance_variable_get(var))
end
# When the data is a Hash object iterate around each of the key value pairs and
# create a method for each one.
when "Hash"
data.each do |key, value|
create_method(object, key, value)
end
end
end
private
# Method that is responsible for creating the method on the calling object. This is
# because some nesting maybe required. For example of the value is a Hash then it will
# need to have an AwsResourceProbe create for each key, whereas if it is a simple
# string then the value just needs to be returned
#
def create_method(object, name, value)
# Create the necessary method based on the var that has been passed
# Test the value for its type so that the method can be setup correctly
case value.class.to_s
when "String", "Integer", "TrueClass", "FalseClass", "Fixnum", "Time"
object.define_singleton_method name do
value
end
when "Hash"
value.count == 0 ? return_value = value : return_value = AwsResourceProbe.new(value)
object.define_singleton_method name do
return_value
end
# there are nested AWS API classes throughout
when /Aws::.*/
object.define_singleton_method name do
value = value.to_h if value.respond_to? :to_h
AwsResourceProbe.new(value)
end
when "Array"
# Some things are just string or integer arrays
# Check this by seeing if the first element is a string / integer / boolean or
# a hashtable
# This may not be the best method, but short of testing all elements in the array, this is
# the quickest test
# p value[0].class.to_s
case value[0].class.to_s
when "String", "Integer", "TrueClass", "FalseClass", "Fixnum", "Time"
probes = value
else
if name.eql?(:tags)
probes = {}
value.each do |tag|
probes[tag[:key]] = tag[:value]
end
else
probes = []
value.each do |value_item|
value_item = value_item.to_h if value_item.respond_to? :to_h
probes << AwsResourceProbe.new(value_item)
end
end
end
object.define_singleton_method name do
probes
end
end
end
end
# Class object that is created for each element that is returned by AWS.
# This is what is interrogated by InSpec. If they are nested hashes, then this results
# in nested AwsResourceProbe objects.
#
# The methods for each of the classes are dynamically defined at run time and will
# match the items that are retrieved from AWS. See the 'test/integration/verify/controls' for
# examples
#
class AwsResourceProbe
attr_reader :name, :type, :location, :item, :count
# Initialize method for the class. Accepts an item, be it a scalar value, hash or AWS object
# It will then create the necessary dynamic methods so that they can be called in the tests
# This is accomplished by call the AwsResourceDynamicMethods
#
# @param item The item from which the class will be initialized
#
# @return AwsResourceProbe
def initialize(item)
dm = AwsResourceDynamicMethods.new
dm.create_methods(self, item)
# Set the item as a property on the class
# This is so that it is possible to interrogate what has been added to the class and isolate them from
# the standard methods that a Ruby class has.
# This used for checking Tags on a resource for example
# It also allows direct access if so required
@item = item
# Set how many items have been set
@count = item.length if item.respond_to? :length
end
# Allows resources to respond to the `include` test
#
# @param [String, Hash, Symbol] opt => the item to look for in the @item property
# String, Symbol: Key name
# Hash: Key=>Value pair to look for in the @item property
def include?(opt)
unless opt.is_a?(Symbol) || opt.is_a?(Hash) || opt.is_a?(String)
raise ArgumentError, "Key or Key:Value pair should be provided."
end
if opt.is_a?(Hash)
raise ArgumentError, "Only one item can be provided" if opt.keys.size > 1
return @item[opt.keys.first] == opt.values.first
end
@item.key?(opt.to_sym)
end
# Prevent undefined method error by returning nil.
# This will prevent breaking a test when queried a non-existing method.
# @return [NilClass]
# @see https://github.com/inspec/inspec-azure/blob/master/libraries/support/azure/response.rb
def method_missing(method_name, *args, &block)
unless respond_to?(method_name)
NullResponse.new
else
super
end
end
# This is to make RuboCop happy.
# Disabling Useless method definition detection as there is an issue with rubocop
# rubocop:disable Lint/UselessMethodDefinition
def respond_to_missing?(*several_variants)
super
end
# rubocop:enable Lint/UselessMethodDefinition
def to_s
"Property is missing! The following are available: #{item.keys.map(&:to_s)}"
end
end
# Ensure to return nil recursively.
# @see https://github.com/inspec/inspec-azure/blob/master/libraries/support/azure/response.rb
#
class NullResponse
def nil?
true
end
alias empty? nil?
def ==(other)
other.nil?
end
alias === ==
alias <=> ==
def key?(_key)
false
end
def method_missing(method_name, *args, &block)
unless respond_to?(method_name)
self
else
super
end
end
# This is to make RuboCop happy.
# Disabling Useless method definition detection as there is an issue with rubocop
# rubocop:disable Lint/UselessMethodDefinition
def respond_to_missing?(*several_variants)
super
end
# rubocop:enable Lint/UselessMethodDefinition
def to_s
nil
end
end