# `IMDS`

Read instance metadata from a cloud provider through one interface.

Supported providers:

  * `:aws` - `IMDS.Providers.AWS`, the AWS EC2 Instance Metadata Service,
    version 2 (IMDSv2)
  * `:gce` - `IMDS.Providers.GCE`, the Google Compute Engine VM metadata
    server
  * `:azure` - `IMDS.Providers.Azure`, the Azure Instance Metadata Service
    (IMDS)
  * `:oracle` - `IMDS.Providers.Oracle`, the Oracle Cloud Infrastructure
    Instance Metadata Service (IMDSv2)
  * `:hetzner` - `IMDS.Providers.Hetzner`, the Hetzner Cloud metadata
    service
  * `:scaleway` - `IMDS.Providers.Scaleway`, the Scaleway instance metadata
    service
  * `:openstack` - `IMDS.Providers.OpenStack`, the OpenStack Compute (nova)
    metadata service
  * `:digital_ocean` - `IMDS.Providers.DigitalOcean`, the DigitalOcean
    Droplet metadata service
  * `:akamai` - `IMDS.Providers.Akamai`, the Akamai Cloud Computing
    (Linode) metadata service
  * `:vultr` - `IMDS.Providers.Vultr`, the Vultr instance metadata service

`detect/1` probes the providers in the order of `providers/0` and answers
with the first one that passes a strict check. No other provider passes the
check of a given provider, so the shared link-local address `169.254.169.254`
is safe to probe. The module keeps no state between calls: every call probes
again.

## Options

All functions accept these options:

  * `:provider` - skip detection and use this provider; the value is a
    provider name or a provider module
  * `:base_url` - replace the base URL of the provider, used in tests
  * `:timeout` - timeout of a single request in milliseconds, default
    `5000`; the probe requests of provider detection default to `1000`
  * `:req_options` - options added to the `Req` request, for example
    `[plug: {Req.Test, IMDS}]` in a test
  * `:aws_imds_v1_fallback` - permit IMDSv1 when the token request fails,
    default `true`. Detection requires IMDSv2: on an instance with only
    IMDSv1, pass `provider: :aws`
  * `:api_version` - the Azure IMDS API version, default `2021-02-01`

The environment variable `INSTANCE_METADATA_PROVIDER` sets the provider when
the `:provider` option is absent. The variable holds the name of a provider,
for example `gce`. A value that names no provider is an error.

# `identity`

```elixir
@type identity() :: IMDS.Provider.identity()
```

Normalized identity of the instance.

# `option`

```elixir
@type option() ::
  {:provider, provider() | module() | binary()}
  | {:base_url, binary()}
  | {:timeout, timeout()}
  | {:req_options, keyword()}
  | {:aws_imds_v1_fallback, boolean()}
  | {:api_version, binary()}
```

# `provider`

```elixir
@type provider() :: IMDS.Provider.provider()
```

Identifier of a provider.

# `reason`

```elixir
@type reason() :: IMDS.Provider.reason()
```

Reason of a failed operation.

# `detect`

```elixir
@spec detect([option()]) :: {:ok, provider()} | {:error, reason()}
```

Detect the provider of the current instance.

Probes the providers in the order of `providers/0` and returns the first
provider that passes its check. When the `:provider` option or the
`INSTANCE_METADATA_PROVIDER` variable is set, returns that provider without
a request.

## Examples

    iex> IMDS.detect(provider: :aws)
    {:ok, :aws}

# `detect!`

```elixir
@spec detect!([option()]) :: provider()
```

Same as `detect/1`, but raises `IMDS.Error` on failure.

# `get`

```elixir
@spec get(binary(), [option()]) :: {:ok, binary()} | {:error, reason()}
```

Return the body of one metadata path as a binary.

The path is relative to the base URL of the provider and MUST start with
`/`. Pass the provider name or a provider module.

## Examples

    IMDS.get("/computeMetadata/v1/instance/id", provider: :gce)

# `get_json`

```elixir
@spec get_json(binary(), [option()]) :: {:ok, term()} | {:error, reason()}
```

Return the body of one metadata path, decoded as JSON.

# `identity`

```elixir
@spec identity([option()]) :: {:ok, identity()} | {:error, reason()}
```

Return the normalized identity of the instance.

The identity map holds the provider name, the instance identifier, the
instance type, the region, the zone or availability domain, the owner scope,
and the raw document of the provider. A field that the provider does not
report is `nil`.

The `instance_type` field holds the machine size descriptor. The `scope_id`
field holds the owner scope of the instance:

  * `:aws` - the account ID
  * `:gce` - the project ID
  * `:azure` - the subscription ID
  * `:oracle` - the compartment OCID
  * `:scaleway` - the organization identifier
  * `:openstack` - the project ID
  * `:akamai` - the account EUUID
  * `:hetzner`, `:digital_ocean`, `:vultr` - always `nil`, because the
    service reports no owner scope

A successful result has this shape:

    {:ok, %{
      provider: :aws,
      instance_id: "i-0123456789abcdef0",
      instance_type: "t3.micro",
      region: "eu-central-1",
      zone: "eu-central-1a",
      scope_id: "111122223333",
      raw: %{"accountId" => "111122223333"}
    }}

Every provider module also accepts the `:base_url` and `:req_options`
options, which the test suite uses.

# `identity!`

```elixir
@spec identity!([option()]) :: identity()
```

Same as `identity/1`, but raises `IMDS.Error` on failure.

The `provider` field of the error holds the name of the provider that
failed, or `nil` when no provider was found.

# `providers`

```elixir
@spec providers() :: [provider()]
```

Return the provider identifiers in probe order.

## Examples

    iex> IMDS.providers()
    [
      :aws,
      :gce,
      :azure,
      :oracle,
      :hetzner,
      :scaleway,
      :openstack,
      :digital_ocean,
      :akamai,
      :vultr
    ]

# `set_global_logger_metadata`

```elixir
@spec set_global_logger_metadata([option()]) :: :ok | {:error, term()}
```

Merge instance metadata to global logger metadata.

It will not override already set keys. A field that the provider does not
report is not merged.

`:raw` key is removed to not bloat logger metadata.

Accepts the same options as `identity/1`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
