GcpCompute

Hex.pmDocs

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:

License

MIT.