Hex.pm Docs

Spawn and manage Google Compute Engine instances from Elixir, over the Compute REST API. Pluggable auth, telemetry on every call, and an ergonomic builder for the (verbose) instance body — with cheap, self-deleting Spot VMs as the default.

Why REST and not gRPC? The Compute Engine API is REST/JSON only — it has no gRPC endpoint (it's the one notable exception among GCP APIs). So unlike pubsub_grpc, there is no connection pool to run: a request is a handful of calls dominated by the 20–40s the VM takes to boot, where pooling buys nothing. GcpCompute talks to it with Req; a GcpCompute.Config is the only handle you pass around. The right place for gRPC + grpc_connection_pool is an agent running inside the machine you spawn — see Roadmap.

Installation

def deps do
  [
    {:gcp_compute, "~> 0.1.0"},
    # Recommended token provider (optional dependency):
    {:goth, "~> 1.4"}
  ]
end

Quickstart

# 1. Start Goth to mint OAuth tokens (in your application supervision tree)
children = [{Goth, name: MyApp.Goth}]

# 2. Build a config once and reuse it
{:ok, config} =
  GcpCompute.Config.production(
    project: "my-project",
    zone: "us-central1-a",
    goth: MyApp.Goth
  )

# 3. Spawn a cheap, self-deleting Spot VM and wait until it exists
{:ok, instance} =
  GcpCompute.insert_instance_and_wait(config,
    name: "worker-1",
    machine_type: "e2-micro",
    spot: true,
    max_run_duration: 3600,                  # hard server-side TTL (seconds)
    startup_script: "#!/bin/bash\necho ready > /tmp/ready",
    labels: %{"owner" => "platform"}
  )

GcpCompute.Instance.external_ip(instance)    #=> "34.x.x.x"

# 4. Tear it down (and wait for the delete operation)
{:ok, _op} = GcpCompute.delete_instance_and_wait(config, "worker-1")

Prefer to drive the lifecycle yourself? Every mutating call returns an GcpCompute.Operation you can poll:

{:ok, op}   = GcpCompute.insert_instance(config, name: "worker-1", machine_type: "e2-micro")
{:ok, done} = GcpCompute.wait_for_operation(config, op, timeout: :timer.minutes(3))

Examples & guides

Configuration

GcpCompute.Config is validated with NimbleOptions. Three builders cover the common cases:

# Production — tokens minted by Goth
{:ok, config} = GcpCompute.Config.production(project: "p", goth: MyApp.Goth)

# From application env
#   config :my_app, :gcp_compute,
#     project: "p", zone: "europe-west4-a",
#     token_provider: {GcpCompute.TokenProvider.Goth, MyApp.Goth}
{:ok, config} = GcpCompute.Config.from_env(:my_app, :gcp_compute)

# Local / emulator / tests — a static token, no Goth required
{:ok, config} = GcpCompute.Config.local(project: "p", base_url: "http://localhost:8080/compute/v1")
OptionDefaultNotes
:project— (required)GCP project id.
:zone"us-central1-a"Default zone; override per call with zone:.
:token_provider{TokenProvider.Goth, GcpCompute.Goth}{module, arg} implementing GcpCompute.TokenProvider.
:base_url"https://compute.googleapis.com/compute/v1"Point at the emulator or a proxy.
:req_options[]Merged into every Req request (:retry, :adapter, …).

Pluggable auth

Auth is a behaviour, GcpCompute.TokenProvider, so the library never hard-depends on Goth. Built-ins: TokenProvider.Goth (production), TokenProvider.Static (tests/emulator). Bring your own (workload identity, metadata server, Vault) by implementing one callback:

defmodule MyApp.MetadataToken do
  @behaviour GcpCompute.TokenProvider
  @impl true
  def fetch_token(_arg), do: {:ok, %{token: fetch_from_metadata_server()}}
end

Configurable machines

GcpCompute.Instance.spec/1 turns friendly options into the Compute insert body (defaults shown):

OptionDefault
:machine_type"e2-micro"
:source_imagedebian-cloud/.../debian-12
:disk_size_gb10
:spottrue (SPOT, no auto-restart, DELETE on terminate)
:max_run_durationnil (set seconds for a hard server-side TTL)
:external_iptrue
:startup_script, :metadata, :labels, :tags, :network, :subnetwork, :service_account, :scopes

Need a field it doesn't cover? Pass a raw Compute body map to GcpCompute.Instances.insert/3 instead.

Telemetry

Every API call is a :telemetry.span/3:

EventMetadata
[:gcp_compute, :request, :start]method, path, project
[:gcp_compute, :request, :stop]+ result (:ok/:error), http_status
[:gcp_compute, :request, :exception]+ kind, reason, stacktrace
GcpCompute.Telemetry.attach_default_logger()      # dev convenience
# or wire into Telemetry.Metrics / your reporter

Testing without GCP

Tests stub the network via Req's native :adapter option — no Plug, no creds:

config =
  GcpCompute.Config.local!(
    project: "test",
    req_options: [adapter: fn req -> {req, %Req.Response{status: 200, body: %{...}}} end]
  )

See test/support/req_stub.ex for the routing-table helper used by the suite.

Roadmap

This package is the Compute client layer. A higher-level sandbox orchestration layer is planned on top of it:

  • gen_statem per sandbox (:provisioning → :probing → :running → :terminating)
  • DynamicSupervisor + Registry, named profiles, per-user quotas
  • a reaper for TTL sweeps + orphan reconciliation (cloud resources outlive the BEAM)
  • the gRPC hop: an in-VM agent reached via grpc_connection_pool, lifecycle events published through pubsub_grpc

License

MIT.