class Aws::EC2Metadata

A client that can query version 2 of the EC2 Instance Metadata

Constants

METADATA_TOKEN_PATH

Path for PUT request for token @api private

Public Class Methods

new(options = {}) click to toggle source

Creates a client that can query version 2 of the EC2 Instance Metadata

service (IMDS).

@note Customers using containers may need to increase their hop limit

to access IMDSv2.

@see docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html#instance-metadata-transition-to-version-2

@param [Hash] options @option options [Integer] :token_ttl (21600) The session token's TTL,

defaulting to 6 hours.

@option options [Integer] :retries (3) The number of retries for failed

requests.

@option options [String] :endpoint ('169.254.169.254') The IMDS

endpoint. This option has precedence over the :endpoint_mode.

@option options [String] :endpoint_mode ('IPv4') The endpoint mode for

the instance metadata service. This is either 'IPv4'
('http://169.254.169.254') or 'IPv6' ('http://[fd00:ec2::254]').

@option options [Integer] :port (80) The IMDS endpoint port. @option options [Integer] :http_open_timeout (1) The number of seconds to

wait for the connection to open.

@option options [Integer] :http_read_timeout (1) The number of seconds for

one chunk of data to be read.

@option options [IO] :http_debug_output An output stream for debugging. Do

not use this in production.

@option options [Integer,Proc] :backoff A backoff used for retryable

requests. When given an Integer, it sleeps that amount. When given a
Proc, it is called with the current number of failed retries.
# File lib/aws-sdk-core/ec2_metadata.rb, line 57
def initialize(options = {})
  @token_ttl = options[:token_ttl] || 21_600
  @retries = options[:retries] || 3
  @backoff = backoff(options[:backoff])

  endpoint_mode = options[:endpoint_mode] || 'IPv4'
  @endpoint = resolve_endpoint(options[:endpoint], endpoint_mode)
  @port = options[:port] || 80

  @http_open_timeout = options[:http_open_timeout] || 1
  @http_read_timeout = options[:http_read_timeout] || 1
  @http_debug_output = options[:http_debug_output]

  @token = nil
  @mutex = Mutex.new
end

Public Instance Methods

get(path) click to toggle source

Fetches a given metadata category using a String path, and returns the

result as a String. A path starts with the API version (usually
"/latest/"). See the instance data categories for possible paths.

@example Fetching the instance ID

ec2_metadata = Aws::EC2Metadata.new
ec2_metadata.get('/latest/meta-data/instance-id')
=> "i-023a25f10a73a0f79"

@note This implementation always returns a String and will not parse any

responses. Parsable responses may include JSON objects or directory
listings, which are strings separated by line feeds (ASCII 10).

@example Fetching and parsing JSON meta-data

require 'json'
data = ec2_metadata.get('/latest/dynamic/instance-identity/document')
JSON.parse(data)
=> {"accountId"=>"012345678912", ... }

@example Fetching and parsing directory listings

listing = ec2_metadata.get('/latest/meta-data')
listing.split(10.chr)
=> ["ami-id", "ami-launch-index", ...]

@note Unlike other services, IMDS does not have a service API model. This

means that we cannot confidently generate code with methods and
response structures. This implementation ensures that new IMDS features
are always supported by being deployed to the instance and does not
require code changes.

@see docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html @see docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html @param [String] path The full path to the metadata.

# File lib/aws-sdk-core/ec2_metadata.rb, line 110
def get(path)
  retry_errors(max_retries: @retries) do
    @mutex.synchronize do
      fetch_token unless @token && !@token.expired?
    end

    open_connection do |conn|
      http_get(conn, path, @token.value)
    end
  end
end

Private Instance Methods

backoff(backoff) click to toggle source
# File lib/aws-sdk-core/ec2_metadata.rb, line 213
def backoff(backoff)
  case backoff
  when Proc then backoff
  when Numeric then ->(_) { Kernel.sleep(backoff) }
  else ->(num_failures) { Kernel.sleep(1.2**num_failures) }
  end
end
fetch_token() click to toggle source
# File lib/aws-sdk-core/ec2_metadata.rb, line 137
def fetch_token
  open_connection do |conn|
    created_time = Time.now
    token_value, token_ttl = http_put(conn, @token_ttl)
    @token = Token.new(value: token_value, ttl: token_ttl, created_time: created_time)
  end
end
http_get(connection, path, token) click to toggle source
# File lib/aws-sdk-core/ec2_metadata.rb, line 145
def http_get(connection, path, token)
  headers = {
    'User-Agent' => "aws-sdk-ruby3/#{CORE_GEM_VERSION}",
    'x-aws-ec2-metadata-token' => token
  }
  request = Net::HTTP::Get.new(path, headers)
  response = connection.request(request)

  case response.code.to_i
  when 200
    response.body
  when 401
    raise TokenExpiredError
  when 404
    raise MetadataNotFoundError
  end
end
http_put(connection, ttl) click to toggle source
# File lib/aws-sdk-core/ec2_metadata.rb, line 163
def http_put(connection, ttl)
  headers = {
    'User-Agent' => "aws-sdk-ruby3/#{CORE_GEM_VERSION}",
    'x-aws-ec2-metadata-token-ttl-seconds' => ttl.to_s
  }
  request = Net::HTTP::Put.new(METADATA_TOKEN_PATH, headers)
  response = connection.request(request)

  case response.code.to_i
  when 200
    [
      response.body,
      response.header['x-aws-ec2-metadata-token-ttl-seconds'].to_i
    ]
  when 400
    raise TokenRetrievalError
  when 403
    raise RequestForbiddenError
  end
end
open_connection() { |http| ... } click to toggle source
# File lib/aws-sdk-core/ec2_metadata.rb, line 184
def open_connection
  uri = URI.parse(@endpoint)
  http = Net::HTTP.new(uri.hostname || @endpoint, @port || uri.port)
  http.open_timeout = @http_open_timeout
  http.read_timeout = @http_read_timeout
  http.set_debug_output(@http_debug_output) if @http_debug_output
  http.start
  yield(http).tap { http.finish }
end
resolve_endpoint(endpoint, endpoint_mode) click to toggle source
# File lib/aws-sdk-core/ec2_metadata.rb, line 124
def resolve_endpoint(endpoint, endpoint_mode)
  return endpoint if endpoint

  case endpoint_mode.downcase
  when 'ipv4' then 'http://169.254.169.254'
  when 'ipv6' then 'http://[fd00:ec2::254]'
  else
    raise ArgumentError,
          ':endpoint_mode is not valid, expected IPv4 or IPv6, '\
          "got: #{endpoint_mode}"
  end
end
retry_errors(options = {}) { || ... } click to toggle source
# File lib/aws-sdk-core/ec2_metadata.rb, line 194
def retry_errors(options = {}, &_block)
  max_retries = options[:max_retries]
  retries = 0
  begin
    yield
  # These errors should not be retried.
  rescue TokenRetrievalError, MetadataNotFoundError, RequestForbiddenError
    raise
  # StandardError is not ideal but it covers Net::HTTP errors.
  # https://gist.github.com/tenderlove/245188
  rescue StandardError, TokenExpiredError
    raise unless retries < max_retries

    @backoff.call(retries)
    retries += 1
    retry
  end
end