diff --git a/libraries/aws_backend.rb b/libraries/aws_backend.rb index b8aa00cc9..910d89ab2 100644 --- a/libraries/aws_backend.rb +++ b/libraries/aws_backend.rb @@ -81,6 +81,11 @@ def initialize(params) 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 + + # @client_args[:stub_data][:client] ||= klass if @client_args[:stub_data].present? + if @client_args && @client_args[:resource_data] && !@client_args[:resource_data].empty? + return AwsMockResource.new(@client_args[:resource_data]) + end return @cache[klass.to_s.to_sym] ||= klass.new(@client_args) if @client_args @cache[klass.to_s.to_sym] ||= klass.new end @@ -331,6 +336,20 @@ def waf_client end end +class AwsMockResource + def initialize(opts) + @opts = opts + end + + private + + attr_reader :opts + + def method_missing(_symbol, *_args) + [opts[:resource_data]] + end +end + # Base class for AWS resources # class AwsResourceBase < Inspec.resource(1) @@ -354,6 +373,13 @@ def initialize(opts) 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 + if @opts.key?(:resource_data) + @opts[:resource_data] = @opts[:resource_data].to_h + end + if @opts[:resource_data] && !@opts[:resource_data].empty? + client_args[:client_args][:stub_responses] = true + client_args[:client_args][:resource_data] = @opts[:resource_data] + end client_args[:client_args].update(opts[:client_args]) if opts[:client_args] @resource_data = opts[:resource_data].presence&.to_h diff --git a/libraries/aws_ec2_instance.rb b/libraries/aws_ec2_instance.rb index 596b8a276..06d7af894 100644 --- a/libraries/aws_ec2_instance.rb +++ b/libraries/aws_ec2_instance.rb @@ -23,21 +23,23 @@ def initialize(opts = {}) super(opts) validate_parameters(require_any_of: %i(instance_id name)) - if opts[:instance_id] && !opts[:instance_id].empty? # Use instance_id, if provided - if !opts[:instance_id].is_a?(String) || opts[:instance_id] !~ /(^i-[0-9a-f]{8})|(^i-[0-9a-f]{17})$/ - raise ArgumentError, "#{@__resource_name__}: `instance_id` must be a string in the format of 'i-' followed by 8 or 17 hexadecimal characters." + if !opts[:resource_data] + if opts[:instance_id] && !opts[:instance_id].empty? # Use instance_id, if provided + if !opts[:instance_id].is_a?(String) || opts[:instance_id] !~ /(^i-[0-9a-f]{8})|(^i-[0-9a-f]{17})$/ + raise ArgumentError, "#{@__resource_name__}: `instance_id` must be a string in the format of 'i-' followed by 8 or 17 hexadecimal characters." + end + @display_name = opts[:instance_id] + instance_arguments = { instance_ids: [opts[:instance_id]] } + elsif opts[:name] && !opts[:name].empty? # Otherwise use name, if provided + @display_name = opts[:name] + instance_arguments = { filters: [{ name: 'tag:Name', values: [opts[:name]] }] } + else + raise ArgumentError, "#{@__resource_name__}: either instance_id or name must be provided." end - @display_name = opts[:instance_id] - instance_arguments = { instance_ids: [opts[:instance_id]] } - elsif opts[:name] && !opts[:name].empty? # Otherwise use name, if provided - @display_name = opts[:name] - instance_arguments = { filters: [{ name: 'tag:Name', values: [opts[:name]] }] } - else - raise ArgumentError, "#{@__resource_name__}: either instance_id or name must be provided." end catch_aws_errors do - resp = @aws.compute_client.describe_instances(instance_arguments) + resp = opts[:resource_data] || @aws.compute_client.describe_instances(instance_arguments) if resp.reservations.first.nil? || resp.reservations.first.instances.first.nil? empty_response_warn return