IMDS

imds reads instance metadata from multiple cloud providers through one Elixir API using the provider's instance metadata service.

Use

{:ok, identity} = IMDS.identity()

IMDS.identity/1 returns:

%{
  provider:
    :aws
    | :gce
    | :azure
    | :oracle
    | :hetzner
    | :scaleway
    | :openstack
    | :digital_ocean
    | :akamai
    | :vultr,
  instance_id: binary() | nil,
  instance_type: binary() | nil,
  region: binary() | nil,
  zone: binary() | nil,
  scope_id: binary() | nil,
  raw: map()
}

The raw field holds the provider identity document when available.

AWS reads the instance type and the account ID from the instance identity Detection requires AWS IMDSv2. On an instance that permits only IMDSv1, pass provider: :aws or set INSTANCE_METADATA_PROVIDER=aws.

Use a provider name to skip discovery:

{:ok, identity} = IMDS.identity(provider: :gce)

Use IMDS.detect/1 to find the provider without reading its identity:

{:ok, :azure} = IMDS.detect()

Use IMDS.get/2 and IMDS.get_json/2 to read a provider path. For Azure, IMDS.get/2 adds the api-version query parameter when the path does not hold one.

{:ok, id} = IMDS.get("/computeMetadata/v1/instance/id")
{:ok, attributes} =
  IMDS.get_json("/computeMetadata/v1/instance/attributes/?recursive=true")

Logger metadata

IMDS.set_global_logger_metadata/1 copies the identity to the primary logger metadata, so every log line holds the provider and the instance:

:ok = IMDS.set_global_logger_metadata()

The function sets the keys :provider, :instance_id, :instance_type, :region, :zone, and :scope_id. It does not set :raw. It does not set a field that the provider does not report, so the metadata holds no nil value. It does not replace a key that the logger metadata already holds.

It accepts the same options as identity/1. It returns {:error, reason} and changes nothing when detection or the identity read fails.

Call it one time at application start. Without :provider or INSTANCE_METADATA_PROVIDER, each call probes all providers.

Providers

Provider Metadata service Request requirement
AWS EC2 IMDSv2 Token from PUT /latest/api/token; token header on metadata GETs
GCE VM metadata server Metadata-Flavor: Google header
Azure Instance Metadata Service (IMDS) Metadata: true header and API version 2021-02-01
Oracle Cloud Infrastructure IMDSv2 Authorization: Bearer Oracle header
Hetzner Cloud Metadata service No header; plain text values below /hetzner/v1/metadata
Scaleway Instance metadata service No header; JSON document at /conf?format=json
OpenStack (nova) Metadata service No header; JSON document at /openstack/latest/meta_data.json
DigitalOcean Droplet metadata service No header; JSON document at /metadata/v1.json
Akamai Cloud Computing Metadata service Token from PUT /v1/token; Metadata-Token header and Accept: application/json on /v1/instance
Vultr Instance metadata service No header; JSON document at /v1.json

The module keeps no state between calls. Every call of IMDS.detect/1 probes the providers again. The environment variable INSTANCE_METADATA_PROVIDER can select provider and omit probing. The :provider option takes precedence over the environment variable.

Options

Option Default Purpose
:provider detection Select one provider and skip detection
:base_url provider address Replace the provider base URL, for example in a local test
:timeout 5,000 ms Timeout for one request; discovery uses 1,000 ms unless set
:req_options [] Options added to the Req request, for example a test plug
:aws_imds_v1_fallback true Permit AWS IMDSv1 after token requests answer 403, 404 or 405
:api_version 2021-02-01 Azure IMDS API version

License

Apache 2.0