# `GcpCompute.Config`
[🔗](https://github.com/nyo16/gcp_compute/blob/v0.3.0/lib/gcp_compute/config.ex#L1)

Validated configuration — and the client handle you pass to every API call.

A `%Config{}` is cheap, immutable, and stateless: build it once and hand it to
`GcpCompute.Instances`/`GcpCompute.Operations` (or the `GcpCompute` facade).
There is no process to supervise — `Req` manages connection pooling under the
hood.

## Building a config

    # Production: tokens minted by Goth
    {:ok, config} =
      GcpCompute.Config.production(
        project: "my-project",
        zone: "us-central1-a",
        goth: MyApp.Goth
      )

    # From application env
    {:ok, config} = GcpCompute.Config.from_env(:my_app, :gcp_compute)

    # Local / emulator / tests (static token)
    {:ok, config} = GcpCompute.Config.local(project: "my-project")

## Options

* `:project` (`t:String.t/0`) - Required. GCP project id.

* `:zone` (`t:String.t/0`) - Default zone for zonal operations. The default value is `"us-central1-a"`.

* `:token_provider` - A `{module, arg}` implementing `GcpCompute.TokenProvider`. The default value is `{GcpCompute.TokenProvider.Goth, GcpCompute.Goth}`.

* `:base_url` (`t:String.t/0`) - Compute API base URL. Override for the emulator or a proxy.
  Treated as **trusted application config** — exactly like
  `:req_options`. Never derive it from user input, a request
  parameter, or a tenant record: it is the one remaining SSRF vector,
  and every request carries a live bearer token, so a hostile
  `:base_url` (`http://169.254.169.254/...`, an attacker's collector)
  exfiltrates the credential on the first call. A trailing `/` is
  trimmed when the config is built. The default value is `"https://compute.googleapis.com/compute/v1"`.

* `:req_options` (`t:keyword/0`) - Extra options merged into every `Req` request (e.g. `:adapter` for
  tests, `:retry`). Treated as **trusted application config**: the
  request `:method`, `:url`, and `:auth` are always computed by the
  library and layered on top, so `req_options` cannot override the
  target URL or drop the bearer token. It can still set transport and
  connection options (`:connect_options`, including `verify`) — that
  is the app owner's choice, not attacker-controlled input.

  The library also supplies two defaults with `Keyword.put_new`, so
  anything you set here wins:

    * `:receive_timeout` — 30 s (Req's own default is 15 s, shorter
      than a normal Compute round trip). The `operations.wait` long
      poll overrides it per-call with ~150 s.
    * `:decoders` — response JSON is decoded with Elixir's native
      `JSON` on 1.18+, falling back to `Jason` below that. Native
      `JSON` is ~36% faster end-to-end on a large `list_page/2`
      response. Override with
      `decoders: [json: &my_decoder/1]` if you need custom decoding.

  The default value is `[]`.

* `:allow_insecure` (`t:boolean/0`) - Permit a non-`https://` `:base_url`. Off by default: a bearer token
  is never sent in cleartext unless you explicitly opt in. Set `true`
  only for a local emulator/proxy on a trusted network (`local/1` sets
  it for you). Independent of the token provider — even a `Static`
  token stays TLS-only unless this is set.

  When it is set *and* the `:base_url` host is not loopback,
  link-local, RFC1918, or an internal-only name, `new/1` logs a
  `Logger.warning` — that combination ships a live bearer token in
  cleartext across a routable network.

  The default value is `false`.

# `t`

```elixir
@type t() :: %GcpCompute.Config{
  allow_insecure: boolean(),
  base_url: String.t(),
  project: String.t(),
  req_options: keyword(),
  token_provider: {module(), term()},
  zone: String.t()
}
```

# `fetch_token`

```elixir
@spec fetch_token(t()) ::
  {:ok, GcpCompute.TokenProvider.token()} | {:error, term()} | term()
```

Fetch a fresh access token using the configured `GcpCompute.TokenProvider`.

The expected return is `{:ok, GcpCompute.TokenProvider.token()}` or
`{:error, reason}` — but the return type is deliberately **not** narrowed to
that. The provider is caller-supplied code that can violate the behaviour, and
the library's internal request layer normalizes whatever actually comes back
into `%GcpCompute.Error{reason: :invalid_token_provider_return}`. Narrowing
this spec would make Dialyzer prove that normalization unreachable and delete
the only thing standing between a third-party provider and a `CaseClauseError`
that `inspect/1`s the token into a crash report.

# `from_env`

```elixir
@spec from_env(atom(), atom()) :: {:ok, t()} | {:error, GcpCompute.Error.t()}
```

Load options from application env and build a config.

    config :my_app, :gcp_compute,
      project: "my-project",
      zone: "europe-west4-a",
      token_provider: {GcpCompute.TokenProvider.Goth, MyApp.Goth}

    {:ok, config} = GcpCompute.Config.from_env(:my_app, :gcp_compute)

# `local`

```elixir
@spec local(keyword()) :: {:ok, t()} | {:error, GcpCompute.Error.t()}
```

Build a config for local development, the emulator, or tests.

> #### Defaults `allow_insecure: true` {: .warning}
>
> `local/1` opts you out of the TLS requirement so an `http://` emulator URL
> works out of the box. **Never combine it with a production token.** With
> `allow_insecure: true`, `local(project: "p", token: real_token, base_url:
> "http://collector.example.com/v1")` sends that live bearer token in
> cleartext to whatever host you named. `new/1` logs a `Logger.warning` when
> the host is not loopback/link-local/RFC1918/internal, but the request is
> still made — pass `allow_insecure: false` to force TLS even locally.

Defaults to a `Static` token provider so you never touch Goth. Pass `:token`
to set the static token (default `"local-token"`), and `:base_url` to point at
an emulator.

    {:ok, config} = GcpCompute.Config.local(project: "p", base_url: "http://localhost:8080/compute/v1")

# `local!`

```elixir
@spec local!(keyword()) :: t()
```

Same as `local/1` but raises on invalid options.

# `new`

```elixir
@spec new(keyword()) :: {:ok, t()} | {:error, GcpCompute.Error.t()}
```

Build and validate a config from a keyword list.

Returns `{:ok, config}` or `{:error, %GcpCompute.Error{reason: :invalid_config}}`
whose `:message` carries the precise validation failure.

# `new!`

```elixir
@spec new!(keyword()) :: t()
```

Same as `new/1` but raises `ArgumentError` on invalid options.

# `production`

```elixir
@spec production(keyword()) :: {:ok, t()} | {:error, GcpCompute.Error.t()}
```

Build a production config that mints tokens via Goth.

Pass `:goth` (the registered Goth name) instead of `:token_provider`.

    {:ok, config} = GcpCompute.Config.production(project: "p", goth: MyApp.Goth)

Uses a Goth token provider with `allow_insecure` defaulting to `false`, so
`new/1`'s https rule applies: the `:base_url` must be `https://` and a bearer
token can't be sent in cleartext.

---

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