firstboot

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

Firstboot Go SDK

The Go client for the Firstboot API.

go get github.com/firstboot-io/go-sdk
c, err := firstboot.New()          // reads FIRSTBOOT_API_URL and FIRSTBOOT_TOKEN
if err != nil {
        return err
}

resp, err := c.API.ServerCreateWithResponse(ctx, &fbapi.ServerCreateParams{}, fbapi.CreateInputBody{
        Name:  "web-1",
        Plan:  "s1",
        Image: ptr("ubuntu-24-04"),
})
if err != nil {
        return err
}

srv, err := c.WaitForServer(ctx, resp.JSON200.Server.Id)   // until it is running

What this adds over the generated client

fbapi is generated from the platform's openapi.json and covers every customer endpoint. This package is the layer a generator cannot write, and it exists because the hard part of talking to this API is not sending a request.

A retry that cannot buy a second server. Every create accepts an Idempotency-Key. This client sets one automatically and, crucially, reuses it across its own retries: the key is minted once before the first attempt, so a request whose response was lost is answered with the resource the first attempt created rather than making another. Without that, retrying a create is how you end up paying for two machines and managing one.

If your key needs to mean something across process restarts (a Terraform resource address, a job id), set it yourself and the client leaves it alone:

c.API.ServerCreateWithResponse(ctx, params, body,
        firstboot.WithIdempotencyKey("tf:"+resourceAddress))

Waiting that knows what finished means. A create answers 202 and converges in the background. Which state values are terminal differs per resource and cannot be derived from the schema, so state.go carries the table and state_test.go holds it against the generated enums. A value this client does not recognise counts as still working, never as done: an old client waiting a little too long is a delay, an old client that guessed "ready" hands you a server that is not.

There is a waiter per thing worth waiting for, and their default budgets differ because the work does:

Waiter Default timeout What it is waiting for
WaitForServer 15 min provisioning; settles on running
WaitForVolume 15 min available, or attached when created onto a server
WaitForLoadBalancer 15 min the data plane; settles on active
WaitForDatabase 15 min the appliance; settles on active
WaitForServerAction 5 min one power action's own succeeded/failed
WaitForBuild 30 min an image build; canceled returns without an error
WaitForISO 60 min a download from a URL nobody here controls
WaitForDomain 20 min a REGISTRY's answer to a registration

WaitForDomain's default is nowhere near enough for a transfer, which is measured in days rather than minutes. Say so with WithTimeout rather than trusting it.

A rate limit read rather than guessed at. A refused create carries Retry-After, measured by the platform from the moment a slot actually frees. The client honours it up to RetryPolicy.MaxRetryAfter and falls back to jittered exponential backoff otherwise.

Refusals as typed errors. The API's machine-readable codes become Go sentinels, so the difference that matters is a comparison rather than a string match:

if errors.Is(err, firstboot.ErrNoCapacity) { /* waiting can help */ }
if errors.Is(err, firstboot.ErrPlanNotOffered) { /* waiting can never help */ }

Paged lists as iterators. Servers, Volumes, Networks, Databases, LoadBalancers and Domains, each fetching a page when it runs out.

for srv, err := range c.Servers(ctx, firstboot.SearchServers("web")) {
        if err != nil {
                return err
        }
        fmt.Println(srv.Name, srv.State)
}

They stop on the page that did not fill rather than on offset >= total, because total is computed per request: a list that grows while being walked would otherwise loop past the end. They make no attempt at a consistent snapshot -- nothing in this API offers a cursor -- so a resource created mid-walk may be missed and one deleted mid-walk may appear twice.

There is deliberately no Isos iterator: GET /v1/isos takes no limit and offset and answers with the whole list, so a paged walk over it would be an invention rather than a convenience.

Selecting a set, not a page. Every groupable kind has a walk that takes the two grouping filters, applied by the API before paging so a filtered walk narrows the whole account:

var backends []string
for srv, err := range c.Servers(ctx, firstboot.ServersWithTags("role:web")) {
        if err != nil {
                return err
        }
        backends = append(backends, srv.Id)
}

Repeating a tag NARROWS: the filter is a containment test, so two tags mean both. …InProject("none") asks the different question "in no project at all", which a UUID cannot spell.

Scope

Only the customer surface. The staff endpoints under /admin/v1/ authenticate with a session cookie that an API token can never hold, so generating them would produce methods whose only possible answer is 401; they are excluded at generation time rather than filtered later.

Regenerating

fbapi/ is generated and never edited by hand.

go generate ./...

The directive lives in generate.go. It reads ../platform/api/openapi/openapi.json, so it needs the platform repository checked out as a sibling. The spec is deliberately not vendored here: a second copy is a second answer to "what does the API look like", and the first time they disagree nobody knows which is wrong.

The generator is a tool dependency in go.mod, so the version that produced the checked-in client is recorded rather than remembered. That version matters: the spec is OpenAPI 3.1 with 289 nullable unions, and oapi-codegen v2.8.0 was measured against it and handles them.

After regenerating, go test ./... is what tells you whether the API added a state this client does not classify.

Requirements

Go 1.25 or newer (the list iterators use iter.Seq2).

License

Apache License 2.0. See LICENSE.

Documentation

Overview

Package firstboot is the Go client for the Firstboot API.

It is two layers. `fbapi` is generated from the platform's own `openapi.json` and is never edited by hand: every endpoint, every request and response type, every state enum. This package is the layer the generator cannot write, and it exists because the hard part of talking to this API is not sending a request:

  • Knowing when a create is FINISHED. A create answers 202 and the resource converges in the background, so the client has to poll until the state is one that has settled. Which values those are differs per resource, and a value the client does not recognise must count as still working.
  • Retrying WITHOUT creating a second server. Every create accepts an `Idempotency-Key`; this client sets one automatically and reuses it across its own retries, which is the whole reason a retry here is safe.
  • Reading a rate limit's answer instead of guessing at it. A refused create carries `Retry-After`, measured from the moment a slot actually frees.

The three consumers this was built for (a Terraform provider, a CLI and an MCP server) all need those three things and would otherwise each write their own, differently.

Index

Constants

View Source
const (
	EnvBaseURL = "FIRSTBOOT_API_URL"
	EnvToken   = "FIRSTBOOT_TOKEN"
)

Environment variables the client reads when an option is not given. They are the names the customer documentation already uses, so a reader who followed the API guide has them exported already.

View Source
const (
	// DefaultISOWaitTimeout covers a multi-gigabyte download from a URL the
	// customer chose, over a link nobody here controls.
	DefaultISOWaitTimeout = 60 * time.Minute
	// DefaultBuildWaitTimeout covers an image build: dependency install,
	// compile, push. A cold cache on a first deploy is the slow case.
	DefaultBuildWaitTimeout = 30 * time.Minute
	// DefaultDomainWaitTimeout covers a REGISTRY's answer. Long enough for the
	// slow TLDs and nowhere near long enough for a transfer, which takes days --
	// a caller waiting on one has to say so with WithTimeout.
	DefaultDomainWaitTimeout = 20 * time.Minute
)

The three waits whose work is not this platform's, and whose budgets therefore have nothing to do with how fast a server boots.

View Source
const DefaultUserAgent = "firstboot-go"

DefaultUserAgent identifies this client in the platform's access log. Worth setting: "which client is hammering this endpoint" is otherwise answerable only by IP, and a CI runner's IP says nothing.

View Source
const DefaultWaitTimeout = 15 * time.Minute

DefaultWaitTimeout is generous on purpose. A create is measured against "SSH open in 77 s" on real hardware, but a cold template on a busy host is slower and a caller that gave up at two minutes would report a failure the platform did not have.

View Source
const IdempotencyHeader = "Idempotency-Key"

IdempotencyHeader is the header name, exported because a caller managing its own keys should not have to spell it.

View Source
const PageSize = 200

PageSize is the per-request page. 200 is the API's ceiling (a larger limit is REFUSED rather than clamped, so asking for more is an error, not a hint), and asking for the maximum is right here: an iterator's caller has already said it wants everything.

Variables

View Source
var (
	// ErrNoCapacity: the region has no host with room. Retrying later can work;
	// retrying immediately cannot.
	ErrNoCapacity = &APIError{Code: "NO_CAPACITY_IN_REGION"}
	// ErrPlanNotOffered: no host in that region sells the plan. Waiting does
	// not help -- this is a catalog fact, not a capacity one, and the two used
	// to be one 503 that told customers to wait for room that would not have
	// helped.
	ErrPlanNotOffered = &APIError{Code: "PLAN_NOT_OFFERED_IN_REGION"}
	// ErrInsufficientBalance: the wallet cannot cover the first month.
	ErrInsufficientBalance = &APIError{Code: "INSUFFICIENT_BALANCE"}
	// ErrCreateCooldown: too many resources created in the rolling window. The
	// response carries Retry-After and this client already waited on it, so
	// seeing this error means the wait exceeded the retry policy's budget.
	ErrCreateCooldown = &APIError{Code: "CREATE_COOLDOWN"}
	// ErrIdempotencyKeyReused: the same key was sent with a different body.
	// Never retryable as sent -- the caller is generating one key for two
	// requests, which is almost always a key built outside its loop.
	ErrIdempotencyKeyReused = &APIError{Code: "IDEMPOTENCY_KEY_REUSED"}
	// ErrIdempotencyConflict: another request holding the same key committed
	// between the lookup and the insert. Retryable, and this client retries it.
	ErrIdempotencyConflict = &APIError{Code: "IDEMPOTENCY_CONFLICT"}
	// ErrQuotaExceeded covers both levels; the API distinguishes them and a
	// caller almost never can act on the difference.
	ErrQuotaExceeded = &APIError{Code: "QUOTA_EXCEEDED"}
	// ErrOrganizationSuspended: nothing new is provisioned for this account.
	ErrOrganizationSuspended = &APIError{Code: "ORGANIZATION_SUSPENDED"}
)

Sentinels for the refusals a caller can actually do something about. This is deliberately not every code the API can return: a list that tried to be exhaustive would go stale silently, while a caller comparing `apiErr.Code == "SOMETHING_NEW"` always works.

Functions

func CodeFromDetail

func CodeFromDetail(detail string) string

CodeFromDetail pulls the machine-readable code off a problem document's `detail`. Exported for the same reason as ErrorFrom: a caller holding a detail string from somewhere else should not re-derive the rule.

func Done

func Done(kind, state string) bool

Done reports whether polling can stop: anything that is not Working.

func ErrorFrom

func ErrorFrom(status int, model *fbapi.ErrorModel, header http.Header) error

ErrorFrom builds an APIError from a generated response's parsed problem document. Exported because every consumer of this library needs it: the generated methods hand back a typed response rather than an error, so turning a non-2xx into something errors.Is can match is the caller's job, and three callers doing it three ways is three subtly different error surfaces.

if resp.JSON200 == nil {
        return firstboot.ErrorFrom(resp.StatusCode(),
                resp.ApplicationproblemJSONDefault, resp.HTTPResponse.Header)
}

func WithIdempotencyKey

func WithIdempotencyKey(key string) fbapi.RequestEditorFn

WithIdempotencyKey returns a request editor that pins one key to one call. Use it when the key has to mean something outside this process:

resp, err := c.API.ServerCreateWithResponse(ctx, params, body,
        firstboot.WithIdempotencyKey("tf:"+resourceAddress))

A key must identify the REQUEST, not the caller: the same key with a different body is refused with IDEMPOTENCY_KEY_REUSED, which is the API telling you the key was built outside the loop that varies.

Types

type APIError

type APIError struct {
	// Status is the HTTP status. Kept because the code alone does not say
	// whether waiting could help.
	Status int
	// Code is the machine-readable half: the leading token of `detail`, which
	// this API writes as SCREAMING_SNAKE. Empty when the response carried none,
	// which is the shape of a 500 -- those deliberately drop their detail.
	Code string
	// Detail is the whole `detail` field, code included. Some codes carry a
	// human sentence after them (USER_DATA_NOT_SUPPORTED does); it is here
	// rather than parsed off, because parsing it would invent a second contract.
	Detail string
	// Title is the problem document's title, e.g. "Unprocessable Entity".
	Title string
	// RequestID is the platform's own request id when the response carried one.
	// It is the join key between what a caller saw and what the operator's log
	// says, and quoting it in a support ticket is the difference between a
	// diagnosis and a guess.
	RequestID string
}

APIError is any refusal the API expressed as a problem document.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) Is

func (e *APIError) Is(target error) bool

Is lets callers compare against the sentinels below without unwrapping.

type AppListOption

type AppListOption func(*fbapi.AppsListParams)

AppListOption narrows an app walk.

func AppsInProject

func AppsInProject(id string) AppListOption

AppsInProject narrows to one project, or to "none".

func AppsWithTags

func AppsWithTags(tags ...string) AppListOption

AppsWithTags narrows to apps carrying EVERY tag given.

type Client

type Client struct {
	// API is the generated client. Exported on purpose: this package wraps the
	// calls that need wrapping and gets out of the way for the ~190 that do
	// not. A consumer reaching straight for c.API is using this library
	// correctly, not working around it.
	API *fbapi.ClientWithResponses
	// contains filtered or unexported fields
}

Client talks to one Firstboot account.

An API token is pinned to one organization for the life of the token, so a Client IS an organization: there is deliberately no per-call organization parameter and no switcher. Two organizations means two Clients.

func New

func New(opts ...Option) (*Client, error)

New builds a Client. It fails rather than defaulting when the base URL or the token is missing: a client pointed at nothing produces a connection error pages later, and a client with no token produces 401s that read like a permissions problem.

func (*Client) Apps

func (c *Client) Apps(ctx context.Context, opts ...AppListOption) iter.Seq2[fbapi.AppBody, error]

Apps walks every app in the organization.

func (*Client) Databases

func (c *Client) Databases(ctx context.Context, opts ...DatabaseListOption) iter.Seq2[fbapi.DatabaseBody, error]

Databases walks every managed database instance in the organization.

func (*Client) Domains

func (c *Client) Domains(ctx context.Context, opts ...DomainListOption) iter.Seq2[fbapi.DomainBody, error]

Domains walks every domain registration in the organization.

func (*Client) LoadBalancers

LoadBalancers walks every load balancer in the organization.

func (*Client) Networks

func (c *Client) Networks(ctx context.Context, opts ...NetworkListOption) iter.Seq2[fbapi.NetworkBody, error]

Networks walks every private network in the organization.

func (*Client) Servers

func (c *Client) Servers(ctx context.Context, opts ...ServerListOption) iter.Seq2[fbapi.ServerBody, error]

Servers walks every server in the organization.

for srv, err := range c.Servers(ctx) {
        if err != nil { return err }
        fmt.Println(srv.Name, srv.State)
}

The filters `serversList` accepts (search, state, project) are applied BEFORE paging by the API, so a filtered walk scans the whole account rather than one page. Pass them through opts.

func (*Client) Volumes

func (c *Client) Volumes(ctx context.Context, opts ...VolumeListOption) iter.Seq2[fbapi.VolumeBody, error]

Volumes walks every volume in the organization.

Note what this costs: a volume is billed at its provisioned size from creation to deletion whether or not it is attached, so a walk that finds detached ones is finding money.

func (*Client) WaitForBuild

func (c *Client) WaitForBuild(ctx context.Context, appCode, buildID string, opts ...WaitOption) (*fbapi.BuildBody, error)

WaitForBuild polls one app build to its own terminal state.

Separate from the app for the same reason a power action is separate from its server: an app that is already running keeps running while a new version builds, so watching the APP shows nothing happening and cannot tell a finished build from one that never started. The build is the thing with an answer.

A cancelled build returns WITHOUT an error. It is terminal and it is the customer's own doing.

func (*Client) WaitForDatabase

func (c *Client) WaitForDatabase(ctx context.Context, id string, opts ...WaitOption) (*fbapi.DatabaseBody, error)

WaitForDatabase polls until the database instance has settled.

It does NOT wait for `pending_apply` to clear. That flag tracks an edit reaching the appliance, which is a second thing to wait for and a different one: a resize leaves the state `active` throughout and only the flag moves. Waiting on the state here keeps this waiter answering one question.

func (*Client) WaitForDomain

func (c *Client) WaitForDomain(ctx context.Context, id string, opts ...WaitOption) (*fbapi.DomainBody, error)

WaitForDomain polls a registration until the registry has answered.

The budget is its own and it is long, because the thing being waited on is not this platform: a registry answers a register in seconds for most TLDs and in minutes for some, and a transfer is measured in days rather than minutes. A caller waiting on a transfer should say so with WithTimeout rather than trust any default.

A domain that settles into `expired` or `redemption` is returned WITHOUT an error: the name was really registered and really lapsed later, which is not a failure of the call that bought it.

func (*Client) WaitForISO

func (c *Client) WaitForISO(ctx context.Context, id string, opts ...WaitOption) (*fbapi.IsoBody, error)

WaitForISO polls until a custom ISO has downloaded.

The budget is its own: an ISO is a multi-gigabyte fetch from a URL the customer chose, over a link this platform does not control, so the default that suits a server create is far too short for it.

func (*Client) WaitForLoadBalancer

func (c *Client) WaitForLoadBalancer(ctx context.Context, id string, opts ...WaitOption) (*fbapi.LoadBalancerBody, error)

WaitForLoadBalancer polls until the load balancer has settled.

A load balancer answers 202 and its data plane is configured afterwards, so the address in the create's response answers nothing until this returns.

func (*Client) WaitForServer

func (c *Client) WaitForServer(ctx context.Context, id string, opts ...WaitOption) (*fbapi.ServerBody, error)

WaitForServer polls until the server has settled, and returns it.

A server that settles into `stopped` is returned WITHOUT an error: it is a real, finished, billable machine, and a library that called that a failure would be making a product judgement it has no standing to make. Only an `error_*` state produces a *StateError.

func (*Client) WaitForServerAction

func (c *Client) WaitForServerAction(ctx context.Context, serverID string, actionID uuid.UUID, opts ...WaitOption) (*fbapi.ActionBody, error)

WaitForServerAction polls one power action to its own terminal state.

Separate from WaitForServer because the SERVER's state is not the answer for every action: a reboot leaves it `running` throughout, so a caller watching the server sees nothing happen and cannot tell success from a request that was never applied.

func (*Client) WaitForVolume

func (c *Client) WaitForVolume(ctx context.Context, id uuid.UUID, opts ...WaitOption) (*fbapi.VolumeBody, error)

WaitForVolume polls until the volume has settled.

func (*Client) Zones

func (c *Client) Zones(ctx context.Context, opts ...ZoneListOption) iter.Seq2[fbapi.DnsZoneBody, error]

Zones walks every DNS zone in the organization.

type DatabaseListOption

type DatabaseListOption func(*fbapi.DatabasesListParams)

DatabaseListOption narrows a managed-database walk.

func DatabasesInProject

func DatabasesInProject(id string) DatabaseListOption

DatabasesInProject narrows to one project, or to "none".

func DatabasesWithTags

func DatabasesWithTags(tags ...string) DatabaseListOption

DatabasesWithTags narrows to instances carrying EVERY tag given.

type DomainListOption

type DomainListOption func(*fbapi.DomainsListParams)

DomainListOption narrows a domain walk.

func DomainsInProject

func DomainsInProject(id string) DomainListOption

DomainsInProject narrows to one project, or to "none".

func DomainsInState

func DomainsInState(state string) DomainListOption

DomainsInState narrows to one lifecycle state, e.g. `active` or `expired`.

func DomainsWithTags

func DomainsWithTags(tags ...string) DomainListOption

DomainsWithTags narrows to domains carrying EVERY tag given.

func SearchDomains

func SearchDomains(q string) DomainListOption

SearchDomains matches a substring of the name.

type LoadBalancerListOption

type LoadBalancerListOption func(*fbapi.LoadBalancersListParams)

LoadBalancerListOption narrows a load-balancer walk.

func LoadBalancersInProject

func LoadBalancersInProject(id string) LoadBalancerListOption

LoadBalancersInProject narrows to one project, or to "none".

func LoadBalancersWithTags

func LoadBalancersWithTags(tags ...string) LoadBalancerListOption

LoadBalancersWithTags narrows to load balancers carrying EVERY tag given.

type NetworkListOption

type NetworkListOption func(*fbapi.NetworksListParams)

NetworkListOption narrows a private-network walk.

func NetworksInProject

func NetworksInProject(id string) NetworkListOption

NetworksInProject narrows to one project, or to "none".

func NetworksWithTags

func NetworksWithTags(tags ...string) NetworkListOption

NetworksWithTags narrows to networks carrying EVERY tag given.

type Option

type Option func(*Client)

Option configures a Client. Options are applied in order, so a later one wins, which is what lets a caller override an environment default.

func WithBaseURL

func WithBaseURL(u string) Option

WithBaseURL sets the API origin, e.g. https://api.example.com. Overrides FIRSTBOOT_API_URL.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient replaces the underlying client. The retry and idempotency transports are layered ON TOP of whatever Transport it carries, so a caller supplying an instrumented client keeps their instrumentation.

func WithRetry

func WithRetry(p RetryPolicy) Option

WithRetry replaces the retry policy. A zero RetryPolicy disables retrying.

func WithToken

func WithToken(t string) Option

WithToken sets the API token (the `pat_` credential minted in the panel). Overrides FIRSTBOOT_TOKEN.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent appends a product token to the User-Agent, e.g. "terraform-provider-firstboot/0.1.0". The library's own token stays.

func WithoutAutoIdempotency

func WithoutAutoIdempotency() Option

WithoutAutoIdempotency stops the client generating an `Idempotency-Key` for creates that do not carry one.

There is one honest reason to use it and it is not performance: a caller that manages keys itself across process restarts, where a key generated per process is the wrong scope. Turning it off to "keep requests simple" removes the protection that makes this client's retries safe.

type Outcome

type Outcome int

Outcome classifies one state value.

const (
	// Working: the value will change on its own. Keep polling.
	Working Outcome = iota
	// Ready: the resource reached the state a successful create aims at.
	Ready
	// Settled: terminal, but not what a create was waiting for -- a server that
	// is `stopped`, a database that is `suspended`. Waiting longer is pointless;
	// whether it is a problem is the caller's judgement, not this library's.
	Settled
	// Failed: terminal and a failure. The waiters turn this into a *StateError.
	Failed
)

func Classify

func Classify(kind, state string) Outcome

Classify answers what one state value means for one kind of resource.

type RetryPolicy

type RetryPolicy struct {
	// MaxAttempts includes the first one, so 1 means "no retry" and 0 means the
	// same thing said by accident.
	MaxAttempts int
	// Base is the first backoff; each attempt doubles it up to Max.
	Base time.Duration
	Max  time.Duration
	// MaxRetryAfter caps how long the client will honour a server-sent
	// Retry-After. The platform's create cooldown can answer with most of an
	// hour, and a library that sleeps for an hour inside one call has stopped
	// being a library. Past this the error is returned and the caller decides.
	MaxRetryAfter time.Duration
}

RetryPolicy bounds how hard the client tries. The zero value retries nothing, which is what WithRetry(RetryPolicy{}) means.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy is the backoff the customer documentation already prescribes for polling (1s to 15s), applied to transport failures.

type ServerListOption

type ServerListOption func(*fbapi.ServersListParams)

ServerListOption narrows a server walk.

func SearchServers

func SearchServers(q string) ServerListOption

SearchServers matches a name, IP address or image name, partially.

func ServersInProject

func ServersInProject(id string) ServerListOption

ServersInProject narrows to one project, or to the servers in none of them with the literal "none".

func ServersInState

func ServersInState(bucket fbapi.ServersListParamsState) ServerListOption

ServersInState narrows to `running`, `stopped` or `other`. It is a BUCKET, not a state value: `other` is everything that is neither of the first two, which is why it cannot take an `error_provisioning`.

func ServersWithTags

func ServersWithTags(tags ...string) ServerListOption

ServersWithTags narrows to servers carrying EVERY tag given.

type StateError

type StateError struct {
	Kind  string // "server", "volume", ...
	ID    string
	State string
	// Code is the resource's own error_code when it carries one. A server has
	// it; not every resource does.
	Code string
}

StateError is returned by the waiters when a resource settles into a failure rather than into success. It is NOT an APIError: every request involved succeeded, and the thing that failed is the work.

func (*StateError) Error

func (e *StateError) Error() string

type TimeoutError

type TimeoutError struct {
	Kind      string
	ID        string
	LastState string
	Waited    string
}

TimeoutError is returned when a waiter's budget ran out. It reports the LAST state seen rather than only "timed out", because those are two different conversations: a server still in `provisioning` after ten minutes is a platform question, one that reached `stopped` is the caller's own.

func (*TimeoutError) Error

func (e *TimeoutError) Error() string

type VolumeListOption

type VolumeListOption func(*fbapi.VolumeListParams)

VolumeListOption narrows a volume walk.

func VolumesInProject

func VolumesInProject(id string) VolumeListOption

VolumesInProject narrows to one project, or to the volumes in none of them with the literal "none".

func VolumesOnServer

func VolumesOnServer(id string) VolumeListOption

VolumesOnServer narrows to the volumes attached to one server.

func VolumesWithTags

func VolumesWithTags(tags ...string) VolumeListOption

VolumesWithTags narrows to volumes carrying EVERY tag given.

type WaitOption

type WaitOption func(*WaitOptions)

WaitOption is the functional form, for callers who want one knob.

func WithProgress

func WithProgress(f func(state string)) WaitOption

WithProgress reports every observed state.

func WithTimeout

func WithTimeout(d time.Duration) WaitOption

WithTimeout bounds the total wait.

type WaitOptions

type WaitOptions struct {
	// Timeout is the total budget. Zero means DefaultWaitTimeout.
	Timeout time.Duration
	// Interval is the first poll delay; it doubles up to MaxInterval. Zero
	// means the schedule the customer documentation prescribes (1s to 15s).
	Interval    time.Duration
	MaxInterval time.Duration
	// OnState, when set, is called with every state observed, including
	// repeats. It exists so a CLI can render progress and a Terraform provider
	// can log one line per transition without either of them re-polling.
	OnState func(state string)
}

WaitOptions bounds a wait.

type ZoneListOption

type ZoneListOption func(*fbapi.DnsZonesListParams)

ZoneListOption narrows a DNS-zone walk.

func ZonesInProject

func ZonesInProject(id string) ZoneListOption

ZonesInProject narrows to one project, or to "none".

func ZonesWithTags

func ZonesWithTags(tags ...string) ZoneListOption

ZonesWithTags narrows to zones carrying EVERY tag given.

Directories

Path Synopsis
Package fbapi provides primitives to interact with the openapi HTTP API.
Package fbapi provides primitives to interact with the openapi HTTP API.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL