class AwsEc2Instance

author: Christoph Hartmann

def catch_aws_errors

Copied from resource_support/aws/aws_resource_mixin.rb
TODO: DRY up, see https://github.com/chef/inspec/issues/2633
def catch_aws_errors
  yield
rescue Aws::Errors::MissingCredentialsError
  # The AWS error here is unhelpful:
  # "unable to sign request without credentials set"
  Inspec::Log.error "It appears that you have not set your AWS credentials.  You may set them using environment variables, or using the 'aws://region/aws_credentials_profile' target.  See https://www.inspec.io/docs/reference/platforms for details."
  fail_resource('No AWS credentials available')
rescue Aws::Errors::ServiceError => e
  fail_resource e.message
end

def exists?

def exists?
  return false if instance.nil?
  instance.exists?
end

def has_roles?

def has_roles?
  catch_aws_errors do
    instance_profile = instance.iam_instance_profile
    if instance_profile
      roles = @iam_resource.instance_profile(
        instance_profile.arn.gsub(%r{^.*\/}, ''),
      ).roles
    else
      roles = nil
    end
    roles && !roles.empty?
  end
end

def id

def id
  return @instance_id if defined?(@instance_id)
  catch_aws_errors do
    if @opts.is_a?(Hash)
      first = @ec2_resource.instances(
        {
          filters: [{
            name: 'tag:Name',
            values: [@opts[:name]],
          }],
        },
      ).first
      # catch case where the instance is not known
      @instance_id = first.id unless first.nil?
    else
      @instance_id = @opts
    end
  end
end

def initialize(opts, conn = nil)

TODO: rewrite to avoid direct injection, match other resources, use AwsSingularResourceMixin
def initialize(opts, conn = nil)
  @opts = opts
  @opts.is_a?(Hash) ? @display_name = @opts[:name] : @display_name = opts
  @ec2_client = conn ? conn.ec2_client : inspec_runner.backend.aws_client(Aws::EC2::Client)
  @ec2_resource = conn ? conn.ec2_resource : inspec_runner.backend.aws_resource(Aws::EC2::Resource, {})
  @iam_resource = conn ? conn.iam_resource : inspec_runner.backend.aws_resource(Aws::IAM::Resource, {})
end

def inspec_runner

Copied from resource_support/aws/aws_singular_resource_mixin.rb
TODO: DRY up, see https://github.com/chef/inspec/issues/2633
def inspec_runner
  # When running under inspec-cli, we have an 'inspec' method that
  # returns the runner. When running under unit tests, we don't
  # have that, but we still have to call this to pass something
  # (nil is OK) to the backend.
  # TODO: remove with https://github.com/chef/inspec-aws/issues/216
  # TODO: remove after rewrite to include AwsSingularResource
  inspec if respond_to?(:inspec)
end

def instance

def instance
  catch_aws_errors { @instance ||= @ec2_resource.instance(id) }
end

def security_group_ids

def security_group_ids
  catch_aws_errors do
    @security_group_ids ||= instance.security_groups.map(&:group_id)
  end
end

def security_groups

is to use dumb things, like arrays of strings - use security_group_ids instead.
Don't document this - it's a bit hard to use. Our current doctrine
def security_groups
  catch_aws_errors do
    @security_groups ||= instance.security_groups.map { |sg|
      { id: sg.group_id, name: sg.group_name }
    }
  end
end

def state

returns the instance state
def state
  catch_aws_errors do
    instance&.state&.name
  end
end

def tags

def tags
  catch_aws_errors do
    @tags ||= instance.tags.map { |tag| { key: tag.key, value: tag.value } }
  end
end

def to_s

def to_s
  "EC2 Instance #{@display_name}"
end