Go SDK for the Massdriver platform.
Beta — breaking changes possible. The shape of this SDK tracks the Massdriver platform API, which is still evolving. Pin to a specific commit or tagged release until the API stabilizes; expect occasional incompatible changes between minor versions.
go get github.com/massdriver-cloud/massdriver-sdk-go/massdriver@latestRequires Go 1.24 or later.
package main
import (
"context"
"fmt"
"github.com/massdriver-cloud/massdriver-sdk-go/massdriver"
)
func main() {
c, err := massdriver.NewClient()
if err != nil {
panic(err)
}
proj, err := c.Projects.Get(context.Background(), "ecommerce")
if err != nil {
panic(err)
}
fmt.Printf("%s — %d environments\n", proj.Name, len(proj.Environments))
}massdriver.NewClient() resolves configuration from (in order of precedence):
| Source | Notes |
|---|---|
Functional options: WithAPIKey, WithOrganizationID, WithBaseURL, WithProfile |
Highest precedence; useful for explicit credentials in CI or tests. |
MASSDRIVER_API_KEY, MASSDRIVER_ORGANIZATION_ID, MASSDRIVER_URL, MASSDRIVER_PROFILE |
Environment variables override the config file. |
~/.config/massdriver/config.yaml, profile selected by MASSDRIVER_PROFILE (default default) |
Created and managed by the Massdriver CLI. |
// Explicit credentials (e.g. CI):
c, _ := massdriver.NewClient(
massdriver.WithAPIKey(os.Getenv("DEPLOY_KEY")),
massdriver.WithOrganizationID("ecommerce"),
)
// Self-hosted instance:
c, _ := massdriver.NewClient(
massdriver.WithBaseURL("https://massdriver.internal.acme.com"),
)The top-level *massdriver.Client exposes every domain service as a
field. Common entry points:
| Field | Purpose |
|---|---|
c.Projects |
Top-level project blueprints. |
c.Environments |
Deployment contexts within a project. |
c.Components |
Components and links inside a project blueprint. |
c.Instances |
Deployed bundle instances, their alarms, secrets, and produced resources. |
c.Deployments |
Trigger and inspect provisioning runs (incl. live log streaming). |
c.Resources |
Provisioned and imported resources, exports, grants. |
c.OciRepos |
OCI repositories (CRUD + oras.Target for direct artifact access). |
c.Bundles |
Read the published bundle catalog. |
c.Groups, c.Policies |
ABAC groups, members, and policies. |
c.AccessTokens, c.ServiceAccounts |
Programmatic identities. |
c.AuditLogs |
The organization's audit trail (use Iter for large queries). |
c.Organizations |
Organization metadata + custom-attribute schema. |
c.Server, c.Viewer, c.URLs |
Server metadata, current identity, deep links. |
This repo ships two separate SDK surfaces. Most users only need the first.
massdriver/platform— the GraphQL platform API, accessed throughmassdriver.NewClient(). Authenticated with personal access tokens or service-account keys. This is the public SDK for any tooling that talks to Massdriver from outside a deployment (CLIs, dashboards, automation, custom integrations).massdriver/provisioning— REST endpoints accessed throughprovisioning.NewClient(). Intended only for code running inside a provisioner during a deployment run, where the platform has injectedMASSDRIVER_DEPLOYMENT_ID+MASSDRIVER_TOKEN. Don't import this from general-purpose tooling — useplatformfor that.
Every wrapper returns errors that callers can classify with errors.Is
against sentinels in package gql:
proj, err := c.Projects.Get(ctx, id)
switch {
case errors.Is(err, gql.ErrNotFound): // record doesn't exist
case errors.Is(err, gql.ErrUnauthenticated): // bad/missing credentials
case errors.Is(err, gql.ErrForbidden): // policy denies the action
case err != nil: // anything else
return err
}For mutations that fail server-side validation, unwrap to
*gql.MutationFailedError for per-field messages:
if mf, ok := gql.AsMutationFailedError(err); ok {
for _, m := range mf.Messages {
log.Printf("%s: %s (%s)", m.Field, m.Message, m.Code)
}
}List methods auto-follow cursors and return a slice. For unbounded
queries (e.g. organization-wide audit logs), use Iter — a lazy Go 1.23
range-over-func iterator that fetches pages on demand:
for ev, err := range c.AuditLogs.Iter(ctx, auditlogs.ListInput{TimeRangeStart: yesterday}) {
if err != nil { return err }
process(ev)
}The SDK exposes two flavors of live data over Absinthe WebSocket
subscriptions. Every Stream* method requires a personal-access-token
credential and returns streaming.ErrRequiresPAT otherwise.
if err := c.Deployments.TailLogs(ctx, "deploy-abc123", os.Stdout); err != nil {
log.Fatal(err)
}c.Deployments.StreamLogsyields oneLogBatchper provisioner flush.c.Deployments.TailLogsis the high-level form — backfill, live tailing, and terminal-state detection in one call.
Each Service exposes StreamEvents, returning a typed <-chan types.Event.
Type-assert each frame to read the affected resource:
events, err := c.Instances.StreamEvents(ctx, "ecomm-prod-database")
if err != nil { log.Fatal(err) }
for ev := range events {
switch e := ev.(type) {
case *types.InstanceEvent: fmt.Println(e.Action, e.Instance.Name)
case *types.AlarmEvent: fmt.Println(e.Action, e.Alarm.DisplayName)
case *types.DeploymentEvent: fmt.Println(e.Action, e.Deployment.Status)
}
}| Method | Variants on the channel |
|---|---|
c.Organizations.StreamEvents |
ProjectEvent, OciRepoEvent, BundleEvent |
c.Projects.StreamEvents |
ProjectEvent, EnvironmentEvent, ComponentEvent, LinkEvent |
c.Environments.StreamEvents |
EnvironmentEvent, EnvironmentDefaultEvent, InstanceEvent, ConnectionEvent, AlarmEvent, DeploymentEvent |
c.Instances.StreamEvents |
InstanceEvent, ConnectionEvent, AlarmEvent, DeploymentEvent |
c.Deployments.StreamEvents |
DeploymentEvent (lifecycle transitions only — no log content) |
The Service owns the socket lifetime; cancel ctx to stop.
Most code that uses the SDK should mock at its own boundary — define a
small interface for the methods your code calls and inject a fake. The
SDK's domain types (projects.Project, deployments.Deployment, etc.)
are public and constructable.
For tests that need to exercise SDK methods end-to-end, combine
WithGQLClient with the gql/gqltest mock
GraphQL client:
mock := gqltest.NewClient(
gqltest.RespondWithData(map[string]any{
"project": map[string]any{"id": "x", "name": "X"},
}),
)
c, _ := massdriver.NewClient(
massdriver.WithGQLClient(mock),
massdriver.WithOrganizationID("test-org"),
)
proj, _ := c.Projects.Get(ctx, "x")For HTTP-level integration tests, point the SDK at an httptest
server with WithBaseURL.
The SDK ships a build-tag-gated integration suite that exercises each operation against a real Massdriver server. These are intended for on-demand validation before tagging a release — not for CI on every commit.
export MASSDRIVER_API_KEY=mds_...
export MASSDRIVER_ORGANIZATION_ID=<sandbox-org>
# Optional: point at staging
# export MASSDRIVER_URL=https://api.staging.massdriver.cloud
# Run all integration tests:
go test -tags=integration ./massdriver/...
# Run one package's:
go test -tags=integration -v ./massdriver/platform/projects/Conventions:
- Tests live in
*_integration_test.gowith//go:build integration. They are invisible to a normalgo test ./...run. - All fixtures are named with the prefix
inttest-so orphans from a crashed run can be swept manually. - Tests use
t.Cleanupfor inline teardown — fixtures are removed even on assertion failure (but not on panic). - Running with
-tags=integrationrequires credentials. Without them the suite fails fast — there is no meaningful integration test we can run without a live API.
If you need to clean up orphaned fixtures from a previous crash, list
projects (or other domain entities) in the sandbox and delete those
whose names start with inttest-.
Per-package godoc is the primary reference — every service, type, and
input field has an inline doc comment, with runnable Example snippets
for the common shapes.
See LICENSE.