GcpCompute.Config (GcpCompute v0.3.0)

Copy Markdown View Source

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 (String.t/0) - Required. GCP project id.

  • :zone (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 (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 (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 (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.

Summary

Functions

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

Load options from application env and build a config.

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

Same as local/1 but raises on invalid options.

Build and validate a config from a keyword list.

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

Build a production config that mints tokens via Goth.

Types

t()

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

Functions

fetch_token(config)

@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/1s the token into a crash report.

from_env(app, key)

@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(opts)

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

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

Defaults allow_insecure: true

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!(opts)

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

Same as local/1 but raises on invalid options.

new(opts)

@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!(opts)

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

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

production(opts)

@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.