Help us shape the future of Go! Take the 2026 Go Developer Survey

sdk

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package sdk is the supported way to drive docket's engine from Go: build and run tasks, read a server back as a recipe or one resource at a time, and describe the registered task types.

Everything else in the module lives under internal/ and cannot be imported from outside it. The recipe loader, the codecs, template rendering and the CLI are docket's own business; this package is the part meant to be built on.

Stability

The exported identifiers in this package are stable from the release they first ship in. While docket is at 0.x, a change that breaks a caller only ships in a release that bumps the minor version, and the release notes call it out. From 1.0 on, only in a major version.

The one exception is the fields of the task types (AppTask, ConfigTask and so on). They mirror the recipe format, and change when it does: a field added, renamed or removed in a recipe is added, renamed or removed here in the same release.

Types are aliases of the engine's own, so a value built here is the value the engine runs. Every type their exported fields and methods take or return is named here too.

Runs

Every call takes a context.Context, and the context carries what a run needs: the server to send dokku commands to and the values to mask. Build it from a Session, which also owns the SSH connections made under it:

session := sdk.NewSession(sdk.NewMasker("s3cr3t"))
defer func() { _ = session.Close() }()
ctx := session.Context(context.Background(), sdk.Target{Host: "dokku@dokku.example.com"})

The zero Target runs dokku locally. Pass WithVersionGate to NewSession to refuse tasks the server is too old for, with the server's versions read once per session however many contexts it hands out.

Tasks executed at once under one session take turns on the same app or datastore service, so concurrent callers do not collide on dokku's deploy lock. Hold the same locks across a separate Plan and ExecutePlan with LockTask.

Testing

A test can drive tasks without a server by installing a fake runner with ContextWithRunner. The fake answers every command a call makes, and since it travels on the context, tests with different fakes can run in parallel. Production code must not use it.

Package dokkutest is a stateful fake built on it: it holds apps, services and their settings, answers the commands the tasks send, applies their changes, and records every call, so a test can assert on the state a task leaves rather than on the commands it ran.

Index

Examples

Constants

View Source
const (
	// ConstraintExactlyOneOf requires exactly one of Fields to be set.
	ConstraintExactlyOneOf = tasks.ConstraintExactlyOneOf

	// ConstraintAtLeastOneOf requires one or more of Fields to be set.
	ConstraintAtLeastOneOf = tasks.ConstraintAtLeastOneOf

	// ConstraintAllOrNoneOf requires Fields to be set together or not at
	// all.
	ConstraintAllOrNoneOf = tasks.ConstraintAllOrNoneOf

	// ConstraintConflictsWith refuses any of Fields while Field is set.
	ConstraintConflictsWith = tasks.ConstraintConflictsWith

	// ConstraintRequiredWith requires every one of Fields while Field is set.
	ConstraintRequiredWith = tasks.ConstraintRequiredWith
)

Kinds of Constraint.

View Source
const (
	// DiagnosticError marks a diagnostic for an error: the task could not be
	// planned, applied or read.
	DiagnosticError = tasks.DiagnosticError

	// DiagnosticWarning marks a diagnostic for a PlanWarning: the task ran,
	// and something about it needs a look.
	DiagnosticWarning = tasks.DiagnosticWarning
)

Diagnostic severities, as Diagnostic.Severity.

View Source
const (
	// DiagnosticReasonVersion marks an error for a *VersionError: the server,
	// or a plugin the task needs, is older than the task's floor, or its
	// version could not be read or parsed.
	DiagnosticReasonVersion = tasks.DiagnosticReasonVersion

	// DiagnosticReasonObjectMissing marks an error for an
	// *ObjectMissingError: the app or service the task depends on does not
	// exist.
	DiagnosticReasonObjectMissing = tasks.DiagnosticReasonObjectMissing

	// DiagnosticReasonStateUnreadable marks an error wrapping
	// ErrStateUnreadable: the task's current state cannot be read.
	DiagnosticReasonStateUnreadable = tasks.DiagnosticReasonStateUnreadable

	// DiagnosticReasonSSH marks an error for a failure of the ssh transport
	// itself - connect, auth, host key - rather than of the command it ran.
	DiagnosticReasonSSH = tasks.DiagnosticReasonSSH

	// DiagnosticReasonCommandFailed marks an error for a command that ran and
	// exited non-zero.
	DiagnosticReasonCommandFailed = tasks.DiagnosticReasonCommandFailed

	// DiagnosticReasonCommandNotRun marks an error for a command that could
	// not be run to completion: it could not start, was cancelled or was
	// killed.
	DiagnosticReasonCommandNotRun = tasks.DiagnosticReasonCommandNotRun

	// DiagnosticReasonError marks any other error.
	DiagnosticReasonError = tasks.DiagnosticReasonError
)

Diagnostic reasons for an error, as Diagnostic.Reason. A warning's Reason is its PlanWarning.Reason, one of the WarnReason* constants.

View Source
const (
	// ExportSupported means the task's full state can be read back from the
	// server and reconstructed faithfully.
	ExportSupported = tasks.ExportSupported

	// ExportPartial means the task can be exported but with caveats, for
	// example a secret value that is written to the companion vars-file, or a
	// field that cannot be read back and becomes a required input.
	ExportPartial = tasks.ExportPartial

	// ExportUnsupported means the task cannot be reconstructed from live
	// state: write-only secrets that never read back, imperative operations
	// that are not state, or resources whose export is not implemented yet.
	ExportUnsupported = tasks.ExportUnsupported
)

Export support levels, as ExportSupport.Status.

View Source
const (
	// IdentityRoleKey marks a field that decides which resource the task
	// manages. Declaration order is key order.
	IdentityRoleKey = tasks.IdentityRoleKey

	// IdentityRoleCollection marks a collection the task manages wholesale.
	IdentityRoleCollection = tasks.IdentityRoleCollection
)

Identity roles a field can carry, from its `identity:` struct tag.

View Source
const (
	// ItemIdentityValue means the element value is the item's identity, as in
	// a `[]string` of domain names.
	ItemIdentityValue = tasks.ItemIdentityValue

	// ItemIdentityMapKey means the map key is the item's identity, as in a
	// `map[string]string` of config pairs.
	ItemIdentityMapKey = tasks.ItemIdentityMapKey

	// ItemIdentityFields means the element is a struct whose own
	// `identity:"key"` fields identify the item, as in PortMapping.
	ItemIdentityFields = tasks.ItemIdentityFields
)

How a collection's items are told apart.

View Source
const (
	// NoteDeprecation is the Deprecated admonition.
	NoteDeprecation = tasks.NoteDeprecation

	// NoteImperative says the task runs an operation and has nothing to undo.
	NoteImperative = tasks.NoteImperative

	// NoteRequirements lists the Dokku floor and the plugins the task needs.
	NoteRequirements = tasks.NoteRequirements

	// NoteRunnerRequirements lists the files docket reads on the machine it
	// runs on.
	NoteRunnerRequirements = tasks.NoteRunnerRequirements

	// NoteExport says whether `docket export` can reconstruct the task.
	NoteExport = tasks.NoteExport

	// NoteProbe says how much of its own state the task can read back.
	NoteProbe = tasks.NoteProbe

	// NoteLifecycle names the states that apply and tear down the task.
	NoteLifecycle = tasks.NoteLifecycle

	// NoteIdentity names the fields that select the resource.
	NoteIdentity = tasks.NoteIdentity

	// NoteObjects names the app and service the task changes.
	NoteObjects = tasks.NoteObjects

	// NoteConstraints lists the rules over several fields.
	NoteConstraints = tasks.NoteConstraints

	// NoteProperties is the table of names a property task accepts.
	NoteProperties = tasks.NoteProperties
)

Kinds of task note, in the order a reference page prints them.

View Source
const (
	// ObjectApp is a dokku app, identified by its name.
	ObjectApp = tasks.ObjectApp

	// ObjectService is a datastore service, identified by its datastore type
	// and its name.
	ObjectService = tasks.ObjectService
)

Object kinds, as TaskObject.Kind.

View Source
const (
	// PlanStatusOK indicates the task is in sync; no change would be made.
	PlanStatusOK = tasks.PlanStatusOK

	// PlanStatusModify indicates the task would modify existing state.
	PlanStatusModify = tasks.PlanStatusModify

	// PlanStatusCreate indicates the task would create new state.
	PlanStatusCreate = tasks.PlanStatusCreate

	// PlanStatusDestroy indicates the task would remove existing state.
	PlanStatusDestroy = tasks.PlanStatusDestroy

	// PlanStatusError indicates the read-state probe itself failed.
	PlanStatusError = tasks.PlanStatusError
)

Plan statuses, as PlanResult.Status.

View Source
const (
	// ProbeSupported means Plan() reads the resource it manages, so a server
	// that already matches the recipe plans as in sync and a second apply is
	// a no-op.
	ProbeSupported = tasks.ProbeSupported

	// ProbePartial means Plan() reads some of the resource but not all of it:
	// a write-only field with no read command, or an idempotency key that is
	// coarser than the task's inputs. The task converges for the dimensions it
	// probes and plans as drift forever for the rest.
	ProbePartial = tasks.ProbePartial

	// ProbeUnsupported means Plan() cannot read the resource at all. The task
	// plans as drift on every run and never converges, so a recipe containing
	// one never settles to `plan --detailed-exitcode` exit 0.
	ProbeUnsupported = tasks.ProbeUnsupported
)

Probe support levels, as ProbeSupport.Status.

View Source
const (
	// PropertyScopeApp means the property has a per-app form, set by naming an
	// app.
	PropertyScopeApp = tasks.PropertyScopeApp

	// PropertyScopeGlobal means the property has a global form, set with
	// `global: true`.
	PropertyScopeGlobal = tasks.PropertyScopeGlobal
)

Scopes a property can be set in.

View Source
const (
	// StatePresent represents the present state
	StatePresent = tasks.StatePresent

	// StateAbsent represents the absent state
	StateAbsent = tasks.StateAbsent

	// StateDeployed represents the deployed state
	StateDeployed = tasks.StateDeployed

	// StateSet represents the set state
	StateSet = tasks.StateSet

	// StateClear represents the clear state
	StateClear = tasks.StateClear
)

Task states.

View Source
const (
	// TypeString covers Go strings and named string types such as State.
	TypeString = tasks.TypeString

	// TypeBool covers bool and *bool.
	TypeBool = tasks.TypeBool

	// TypeInt covers every sized integer kind.
	TypeInt = tasks.TypeInt

	// TypeFloat covers float32 and float64. No task field uses one today.
	TypeFloat = tasks.TypeFloat

	// TypeList is a slice or array field.
	TypeList = tasks.TypeList

	// TypeDict is a map field.
	TypeDict = tasks.TypeDict

	// TypeObject is a struct element inside a list, such as a PortMapping. It
	// never appears as a field's own type - a task has no bare struct field.
	TypeObject = tasks.TypeObject

	// TypeAny is an element with no declared type, as in map[string]any.
	TypeAny = tasks.TypeAny

	// TypeUnknown is the fallback for a Go kind none of the above covers.
	// TestCatalogFieldTypesAreKnown fails on it, so adding an exotic field type
	// to a task is a build failure rather than a leaked Go type name in the
	// published JSON.
	TypeUnknown = tasks.TypeUnknown
)

Field and item type names. These are the doc-facing type vocabulary, shared with the Parameters table the generator renders, and they are what the published JSON Schema enumerates.

View Source
const (
	// WarnReasonUnknownProperty marks a probe warning raised when a property's
	// key is absent from the plugin's JSON report (a stale key map or a dokku
	// version that does not emit it).
	WarnReasonUnknownProperty = tasks.WarnReasonUnknownProperty

	// WarnReasonProbeRejected marks a probe warning raised when an older plugin
	// rejects `:report --format json` outright.
	WarnReasonProbeRejected = tasks.WarnReasonProbeRejected

	// WarnReasonProbeIndeterminate marks a probe warning raised when a probe
	// ran, was understood, and still could not answer: the server holds the
	// state but will not reveal enough of it to compare. dokku's
	// registry:auth-status reports exactly that when a docker credential
	// helper or an identity token holds the secret, or when the config it
	// would read does not parse. The task drifts and applies rather than
	// assuming a match, so the warning is what tells the operator why a run
	// against that server never settles.
	WarnReasonProbeIndeterminate = tasks.WarnReasonProbeIndeterminate

	// WarnReasonServiceImageDrift marks a warning raised when a datastore
	// service is running an image other than the one the recipe pins, and the
	// task is leaving it alone. Unlike the three above it is not a probe failure:
	// the state was read successfully and the task is declining to reconcile
	// it, because dokku's only remedy recreates the container. It also covers
	// the case where the running image could not be read at all, so a recipe
	// that asked for drift to be caught is never silently told everything is
	// fine.
	WarnReasonServiceImageDrift = tasks.WarnReasonServiceImageDrift

	// WarnReasonServiceOptionDrift marks a warning raised when a datastore
	// service holds a create-time option - config_options, custom_env,
	// memory, shm_size or a network - other than the one the recipe declares,
	// and the task is leaving it alone. Like WarnReasonServiceImageDrift it is
	// not a probe failure: the value was read, and reconciling it means a
	// rebuild the recipe did not ask for. It is a separate reason because the
	// remedy is: an option is set in place and picked up by a stop and a
	// start, while an image moves only with `<service>:upgrade`.
	WarnReasonServiceOptionDrift = tasks.WarnReasonServiceOptionDrift
)

PlanWarning reasons, as PlanWarning.Reason.

View Source
const CatalogVersion = tasks.CatalogVersion

CatalogVersion is the TaskCatalog.Version this release emits, the `version` field of every catalog. It changes only when the catalog's shape does, so a consumer can branch on it: adding a key does not bump it.

One change was made within version 1 against that rule: a field's `default` was a string whatever its type, and is now a typed value matching the field's type (#645), so a bool field's default reads false, not "false".

View Source
const FormatHCL = tasks.FormatNameHCL

FormatHCL is the canonical name of the HCL codec, one of the recipe formats ExportResult.MarshalRecipe accepts.

View Source
const FormatJSON5 = tasks.FormatNameJSON5

FormatJSON5 is the canonical name of the JSON5 codec, one of the recipe formats ExportResult.MarshalRecipe accepts.

View Source
const FormatYAML = tasks.FormatYAML

FormatYAML is the canonical name of the YAML codec, one of the recipe formats ExportResult.MarshalRecipe accepts.

View Source
const MaskPlaceholder = subprocess.MaskPlaceholder

MaskPlaceholder is what a Masker replaces a sensitive value with. Its length is fixed at three asterisks so masked output reveals nothing about the original value (no length, prefix, or suffix).

View Source
const PropertyFieldName = tasks.PropertyFieldName

PropertyFieldName is the recipe key every property table constrains.

Variables

View Source
var ErrObjectMissing = tasks.ErrObjectMissing

ErrObjectMissing is matched by the error ReadTask and ReadAddress return when the app or datastore service a task depends on does not exist on the server. errors.As against *ObjectMissingError names the missing object by its kind, recipe keys and values.

View Source
var ErrSessionClosed = subprocess.ErrSessionClosed

ErrSessionClosed is the error a call made under a closed session fails with, wrapped in the error the call returns. Test for it with errors.Is.

View Source
var ErrStateUnreadable = tasks.ErrStateUnreadable

ErrStateUnreadable is wrapped by the error ReadTask returns when a task's probe cannot read its current state, such as dokku_storage_ensure, or when the probe failed in a way the plan treats as drift.

Functions

func As

func As[T Task](e ExportedTask) (T, bool)

As returns an exported task's body as T, the value type registered under e.Type (ConfigTask, not *ConfigTask), so a caller reads fields without hand-writing an assertion on Body:

if cfg, ok := As[ConfigTask](task); ok { ... }

It reports false for any other type, including a pointer type: bodies are never pointers.

func CheckMinimumVersions added in v0.9.1

func CheckMinimumVersions(ctx context.Context, typeKey string, task Task) error

CheckMinimumVersions returns a *VersionError when the server ctx targets, or an installed plugin task needs, is older than the floor task declares, or when a version does not parse. A task's Plan and Execute already refuse through it when ctx carries a gate; call it to check a task before planning it. It returns nil when ctx carries no gate, when task declares no floor, and when a plugin task needs is not installed, which the task's own probe reports and which a recipe can install earlier in the same run. typeKey names the task in the error, such as "dokku_app".

func ContextWithRunner added in v0.9.1

func ContextWithRunner(ctx context.Context, fn ExecRunner) context.Context

ContextWithRunner returns a copy of ctx whose commands go to fn instead of to a real process or server. The runner travels on the context rather than in a package variable, so two tests can install different fakes at the same time and both can call t.Parallel().

Production code must not use it. The seam exists so a test can drive a task without a server; a caller that wants to change where commands actually run sets a Target.

fn receives every command a call makes, not only dokku's, with its arguments as they would have run: unmasked, and without the sudo or SSH wrapping a Target adds. A Target on ctx has no effect while fn is installed.

A command that ran and exited non-zero is a response carrying that ExitCode. A probe reads it as its answer whether fn returns a nil error alongside it or, as the real runner does, an *ExecError with Ran set. An apply fails only on an error, so return the *ExecError to fail one. Any other error is a command that could not run, which a probe reports as an error rather than as absence.

The response's Command is what an apply records in TaskOutputState.Commands, so set it when a test reads that.

Example

Planning and applying a task against a fake dokku in a test, with no server.

package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/dokku/docket/sdk"
)

func main() {
	var calls []string
	ctx := sdk.ContextWithRunner(context.Background(), func(_ context.Context, in sdk.ExecCommandInput) (sdk.ExecCommandResponse, error) {
		calls = append(calls, strings.Join(in.Args, " "))
		if len(in.Args) > 1 && in.Args[1] == "apps:exists" {
			// The app is absent: the command ran and exited non-zero.
			return sdk.ExecCommandResponse{ExitCode: 1}, nil
		}
		return sdk.ExecCommandResponse{}, nil
	})

	task, _ := sdk.NewTask("dokku_app")
	app := task.(*sdk.AppTask)
	app.App = "api"

	plan := app.Plan(ctx)
	fmt.Println(plan.Reason, plan.Commands)

	state := sdk.ExecutePlan(ctx, plan)
	fmt.Println(state.Changed, calls)
}
Output:
app missing [dokku --quiet apps:create api]
true [--quiet apps:exists api --quiet apps:create api]

func ContextWithTarget

func ContextWithTarget(ctx context.Context, target Target) context.Context

ContextWithTarget returns a copy of ctx that sends dokku commands to target. A context derived from a session context still belongs to that session.

func ContextWithTaskLocks added in v0.10.0

func ContextWithTaskLocks(ctx context.Context) context.Context

ContextWithTaskLocks returns a copy of ctx on which Execute serializes the tasks that change the same app or datastore service, on the same server. A context that already carries a lock table is returned as-is, so installing it twice does not split it. Every context a NewSession session hands out already carries one, so this is only for a context built without a session. Without a table, Execute and LockTask lock nothing.

func ContextWithVersionGate added in v0.9.1

func ContextWithVersionGate(ctx context.Context) context.Context

ContextWithVersionGate returns a copy of ctx on which CheckMinimumVersions compares a task's floor with the server, so plan, apply, read and export refuse a task whose declared floor is newer than the server. The versions it reads are cached per target for as long as the context lives, so install it once per run. A context that already carries a gate is returned as-is. A caller that gets a new context for every call it serves gives the gate to its session with WithVersionGate instead.

func ContextWithoutObserved added in v0.11.0

func ContextWithoutObserved(ctx context.Context) context.Context

ContextWithoutObserved returns a copy of ctx on which ExecutePlan, and so Execute, leaves TaskOutputState.Observed and Exists unset and never re-probes the server for them. `docket apply` runs its tasks under it: it reports and registers what a task did, not what the server now holds, so the extra read would be wasted. An embedder that never reads Observed can do the same.

Example

Skipping the observed state when nothing reads it, so an apply never probes the server a second time.

package main

import (
	"context"
	"fmt"

	"github.com/dokku/docket/sdk"
	"github.com/dokku/docket/sdk/dokkutest"
)

func main() {
	server := dokkutest.New(nil).SeedDatastore("postgres", "postgres:17.2")
	ctx := server.Context(context.Background())

	task := sdk.ServiceCreateTask{Service: "postgres", Name: "db", State: sdk.StatePresent}
	state := task.Execute(sdk.ContextWithoutObserved(ctx))
	fmt.Println(state.Changed, state.Observed == nil, server.Lines())
}
Output:
true true [--quiet postgres:exists db --quiet postgres:create db]

func IdentityAddress

func IdentityAddress(typeKey string, t Task) string

IdentityAddress renders the resource t addresses, such as `dokku_config[app=api]`. Keys appear in declaration order; keys holding their zero value are omitted, so a task with no key value at all renders as the bare type key. It is the form ParseIdentityAddress and ParseResourceSelectors read back.

The result is a parseable surface, not just a label: it is the auto-generated task name in the `--json` event stream, the argument `--start-at-task` accepts, and the argument `export --resource` accepts. Values are therefore never truncated - a lossy address could not be resolved back.

func LockTask added in v0.10.0

func LockTask(ctx context.Context, t Task) (context.Context, func(), error)

LockTask takes the locks t needs: one for each app or datastore service it changes, on the server ctx targets, waiting while another task holds them. It returns a context recording them, so an Execute under it does not wait on the locks its caller already holds, and a function that releases them, safe to call more than once.

Execute takes the same locks itself; Plan, ReadTask and ExecutePlan take none. LockTask is for a caller that plans and applies separately: hold the locks across a Plan and its ExecutePlan, so no other task on the same object runs in between.

A context with no lock table returns ctx and locks nothing. A caller whose ctx ends while it waits gets an error wrapping ctx's and holds nothing. Locks are always taken app first, then service, so a nested call needing a lock ordered before one already held - executing a dokku_service_link while holding only its service - returns an error rather than risk a deadlock: lock the task that changes both first.

func ParseFieldDefault added in v0.11.0

func ParseFieldDefault(fieldType string, itemType string, raw string) (any, error)

ParseFieldDefault turns a field's raw `default:` tag text into the typed value the catalog publishes as FieldSchema.Default: a bool, an int64, a float64, a string, or a []any of those, matching fieldType. fieldType and itemType are the catalog's type names - a FieldSchema's Type and its Item.Type - so a caller holding either a task struct or a published catalog can call it. itemType is read only when fieldType is TypeList. An empty raw tag means the field has no default, and returns nil.

It follows go-defaults' parsing rules, which are what the loader applies through defaults.SetDefaults: strconv for scalars, the string as-is, and a list spelled `[a,b]` and split on commas with no trimming. Where go-defaults would silently store a zero value - an unparsable bool or number, a list tag without brackets, a default on a dict or on a list of objects, which it never applies - this returns an error instead, so a typo in a tag cannot publish a default the task does not have.

A pointer field is described by the type it points to, so a *bool's tag parses as a bool. go-defaults leaves a pointer nil, so on a pointer field the value is what the task coerces nil to, not what the loader writes into the struct.

func ParseIdentityAddress

func ParseIdentityAddress(address string) (string, map[string]string, error)

ParseIdentityAddress parses `dokku_config[app=api]` into its type key and key map. A bare type key parses to an empty map, which `export --resource` reads as "every resource of this type".

The parse is strict: it rejects an auto-generated task name carrying a collision ordinal (`dokku_config[app=api] #2`), because that names a task, not a resource, and two tasks can address one resource.

func TaskDeprecation

func TaskDeprecation(t Task) string

TaskDeprecation returns t's deprecation notice, or "" when t is current.

func TaskImperative added in v0.10.0

func TaskImperative(t Task) bool

TaskImperative reports whether t runs an operation rather than declaring a resource, so it has nothing to undo.

func TaskSensitiveValues added in v0.9.1

func TaskSensitiveValues(t Task) []string

TaskSensitiveValues returns the values that must be masked in anything rendered for t: its `sensitive:"true"` fields, including those of nested items, the value of a property marked sensitive, and whatever t's own SensitiveValues method returns. Empty values are dropped and duplicates removed; the order is unspecified. It returns nil for a nil task.

A task's Plan, Execute, ReadTask, ReadAddress and ExecutePlan already add these values to the masker on their context before they run anything, and Plan adds the secrets it reads from the server too, so a caller need not register them. Call it to mask output you render yourself before any of those has run, such as a task printed before it is planned. A context with no masker has nothing registered with it.

Example

Masking output rendered for a task before it is planned. Plan, Execute, ReadTask and ExecutePlan register these values with the context's masker themselves.

package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	task, err := sdk.DecodeTask("dokku_http_auth_user", []byte(`
app: api
users:
  - username: admin
    password: s3cr3t
`))
	if err != nil {
		panic(err)
	}

	masker := sdk.NewMasker(sdk.TaskSensitiveValues(task)...)
	fmt.Println(masker.String("http-auth:add-user api admin s3cr3t"))
}
Output:
http-auth:add-user api admin ***

func TaskSynopsis

func TaskSynopsis(t Task) string

TaskSynopsis returns t's description, or "" when it has none.

func TaskTypeOf added in v0.11.0

func TaskTypeOf(t Task) string

TaskTypeOf returns the task type t is registered under, such as dokku_app for an AppTask or an *AppTask, or "" for a type docket does not register. It names the tasks TaskRequires returns, which carry no type of their own.

func TaskTypes

func TaskTypes() []string

TaskTypes returns every registered task type, sorted. The result is a fresh slice, so a caller may sort, filter or truncate it without disturbing the registry.

Example
package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	for _, typeKey := range sdk.TaskTypes() {
		task, _ := sdk.Lookup(typeKey)
		if typeKey == "dokku_app" {
			fmt.Println(typeKey, "-", sdk.TaskSynopsis(task))
		}
	}
}
Output:
dokku_app - Creates or destroys an app

func TaskValues added in v0.11.0

func TaskValues(t Task) map[string]any

TaskValues returns every field of t keyed by recipe key, typed per the catalog's FieldSchema: a string, a bool, an int64, a float64, a map[string]any for a dict or an object, and a []any for a list. An any item comes back with the same types, integers as int64. t may be the registered pointer or the value type, as Observed is.

Every field is present, including one left at its zero value or default, so a caller sees the same keys for every task of a type and applies no defaults itself. An empty list is []any{} and an empty dict map[string]any{}, never nil. A pointer field with no value reads as the default its catalog entry documents, which is what the task coerces nil to, so an unset `restart` on dokku_config reads true; a pointer field with no default would read nil, and no task declares one today. Feeding the map back to TaskFromValues sets the pointer to that default, which the task treats the same as nil.

Values are raw: mask sensitive ones before showing them. A nil t returns nil.

func ValidateSSHOptions added in v0.9.1

func ValidateSSHOptions(opts []string) error

ValidateSSHOptions reports whether opts can be a Target's SSHOptions, the `-o` values passed to ssh, without contacting anything: each must be non-empty, free of control characters, and must not set an option docket manages itself.

Types

type AclAppTask

type AclAppTask = tasks.AclAppTask

AclAppTask is tasks.AclAppTask.

type AclServiceTask

type AclServiceTask = tasks.AclServiceTask

AclServiceTask is tasks.AclServiceTask.

type AppCloneTask

type AppCloneTask = tasks.AppCloneTask

AppCloneTask is tasks.AppCloneTask.

type AppJsonPropertyTask

type AppJsonPropertyTask = tasks.AppJsonPropertyTask

AppJsonPropertyTask is tasks.AppJsonPropertyTask.

type AppLockTask

type AppLockTask = tasks.AppLockTask

AppLockTask is tasks.AppLockTask.

type AppTask

type AppTask = tasks.AppTask

AppTask is tasks.AppTask.

type AppsPropertyTask

type AppsPropertyTask = tasks.AppsPropertyTask

AppsPropertyTask is tasks.AppsPropertyTask.

type BuilderDockerfilePropertyTask

type BuilderDockerfilePropertyTask = tasks.BuilderDockerfilePropertyTask

BuilderDockerfilePropertyTask is tasks.BuilderDockerfilePropertyTask.

type BuilderHerokuishPropertyTask

type BuilderHerokuishPropertyTask = tasks.BuilderHerokuishPropertyTask

BuilderHerokuishPropertyTask is tasks.BuilderHerokuishPropertyTask.

type BuilderLambdaPropertyTask

type BuilderLambdaPropertyTask = tasks.BuilderLambdaPropertyTask

BuilderLambdaPropertyTask is tasks.BuilderLambdaPropertyTask.

type BuilderNixpacksPropertyTask

type BuilderNixpacksPropertyTask = tasks.BuilderNixpacksPropertyTask

BuilderNixpacksPropertyTask is tasks.BuilderNixpacksPropertyTask.

type BuilderPackPropertyTask

type BuilderPackPropertyTask = tasks.BuilderPackPropertyTask

BuilderPackPropertyTask is tasks.BuilderPackPropertyTask.

type BuilderPropertyTask

type BuilderPropertyTask = tasks.BuilderPropertyTask

BuilderPropertyTask is tasks.BuilderPropertyTask.

type BuilderRailpackPropertyTask

type BuilderRailpackPropertyTask = tasks.BuilderRailpackPropertyTask

BuilderRailpackPropertyTask is tasks.BuilderRailpackPropertyTask.

type BuildpacksPropertyTask

type BuildpacksPropertyTask = tasks.BuildpacksPropertyTask

BuildpacksPropertyTask is tasks.BuildpacksPropertyTask.

type BuildpacksTask

type BuildpacksTask = tasks.BuildpacksTask

BuildpacksTask is tasks.BuildpacksTask.

type BuildsPropertyTask

type BuildsPropertyTask = tasks.BuildsPropertyTask

BuildsPropertyTask is tasks.BuildsPropertyTask.

type CaddyPropertyTask

type CaddyPropertyTask = tasks.CaddyPropertyTask

CaddyPropertyTask is tasks.CaddyPropertyTask.

type CertsTask

type CertsTask = tasks.CertsTask

CertsTask is tasks.CertsTask.

type ChecksPropertyTask

type ChecksPropertyTask = tasks.ChecksPropertyTask

ChecksPropertyTask is tasks.ChecksPropertyTask.

type ChecksToggleTask

type ChecksToggleTask = tasks.ChecksToggleTask

ChecksToggleTask is tasks.ChecksToggleTask.

type ConfigTask

type ConfigTask = tasks.ConfigTask

ConfigTask is tasks.ConfigTask.

type Constraint added in v0.10.0

type Constraint = tasks.Constraint

Constraint is tasks.Constraint.

func TaskConstraints added in v0.10.0

func TaskConstraints(v any) []Constraint

TaskConstraints returns the rules over several of v's fields, the same ones TaskSchema.Constraints publishes. v is a task, an element struct, or a pointer to either. Nil when it declares none.

type ConstraintKind added in v0.10.0

type ConstraintKind = tasks.ConstraintKind

ConstraintKind is tasks.ConstraintKind.

type ConstraintOption added in v0.11.0

type ConstraintOption = tasks.ConstraintOption

ConstraintOption is tasks.ConstraintOption.

func WithUnknown added in v0.11.0

func WithUnknown(paths ...string) ConstraintOption

WithUnknown is a ConstraintViolations option naming the recipe key paths whose values are not known yet, in the form Violation.Field uses: `app`, `users`, `users[1]` or `users[1].hash`.

An unknown field counts as set for a rule that needs it present: its required_when, a list item's required keys, an at_least_one_of or all_or_none_of it is in, and the fields a required_with requires. A rule whose outcome turns on its value is skipped rather than reported: its forbidden_when, an exactly_one_of with at most one known field set, which it might complete or break, a conflicts_with it is in, and a required_with it is the Field of. A rule the known fields decide on their own is reported as without the option: two set fields in an exactly_one_of or a conflicts_with, or a set and an unset field in an all_or_none_of, whatever the unknown ones hold.

An unknown `state` leaves every rule scoped to a state undecided, so they are all skipped: each field's required_when and forbidden_when and every constraint that lists States, the task's own and its list items'. Rules that apply in every state are still checked. An unknown `property` skips the check that a property task's property is not one another task manages.

An unknown structured list (`users`) is unknown as a field, and none of its items are checked, since neither how many there are nor what they hold is known; an unknown item (`users[1]`) is skipped the same way while the other items are checked. The same holds for a list tagged unique: an unknown list is not checked for repeats, and an unknown member (`phases[1]`) is not compared with the others, while two known members that repeat each other are still reported. A path naming no field of the task is ignored.

Example

A task checked before every value is known: the second user's hash comes from elsewhere, so it counts as set and only the first user's conflict, which its known fields decide, is reported.

package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	task, err := sdk.NewTask("dokku_http_auth_user")
	if err != nil {
		panic(err)
	}
	users := task.(*sdk.HttpAuthUserTask)
	users.Users = []sdk.HttpAuthUser{
		{Username: "a", Password: "p", Hash: "h"},
		{Username: "b"},
	}

	for _, v := range sdk.ConstraintViolations(users, sdk.WithUnknown("app", "users[1].hash")) {
		fmt.Printf("%s: %s\n", v.Field, v.Message)
	}
}
Output:
users[0].password: 'users[0].password' and 'users[0].hash' are mutually exclusive

type CronPropertyTask

type CronPropertyTask = tasks.CronPropertyTask

CronPropertyTask is tasks.CronPropertyTask.

type Diagnostic added in v0.11.0

type Diagnostic = tasks.Diagnostic

Diagnostic is tasks.Diagnostic.

func Diagnostics added in v0.11.0

func Diagnostics(ctx context.Context, result any) []Diagnostic

Diagnostics returns the errors and warnings in result, masked with the masker on ctx, so a caller can show them without masking or classifying each one itself. result is a PlanResult, a TaskOutputState (or a pointer to either), or an error, such as the one ReadTask or ReadAddress returns.

The error, when there is one, comes first, then one warning per entry in Warnings, in order, each keeping its Reason. A result with neither, a nil error and a nil pointer return nil. Any other type returns a single error diagnostic naming it.

An error is classified by what it wraps, first match wins: a failure of the ssh transport, a *VersionError, an *ObjectMissingError, ErrStateUnreadable, a command that failed (an *ExecError, or a result carrying a non-zero ExitCode), and anything else.

Example

Showing why a read failed: the error becomes a short summary to show and a detail carrying the rest, both masked with the session's masker.

package main

import (
	"context"
	"fmt"

	"github.com/dokku/docket/sdk"
	"github.com/dokku/docket/sdk/dokkutest"
)

func main() {
	session := sdk.NewSession(sdk.NewMasker())
	defer func() { _ = session.Close() }()
	// The server holds no app web.
	ctx := dokkutest.New(nil).Context(session.Context(context.Background(), sdk.Target{}))

	task := sdk.ConfigTask{App: "web", Config: map[string]string{"LOG_LEVEL": "info"}, State: sdk.StatePresent}
	_, _, err := sdk.ReadTask(ctx, task)
	for _, d := range sdk.Diagnostics(ctx, err) {
		fmt.Println(d.Severity, d.Reason, d.Summary)
	}
}
Output:
error object_missing app web does not exist
Example (FailedCommand)

A failed apply's command output is in the detail, with every secret the task declares masked.

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/dokku/docket/sdk"
	"github.com/dokku/docket/sdk/dokkutest"
)

func main() {
	session := sdk.NewSession(sdk.NewMasker())
	defer func() { _ = session.Close() }()
	server := dokkutest.New(nil).SeedApp("web")
	server.HandlePrefix("--quiet config:set", func(context.Context, sdk.ExecCommandInput) (sdk.ExecCommandResponse, error) {
		res := sdk.ExecCommandResponse{Stdout: "-----> Setting config vars", Stderr: "refusing to set TOKEN=hunter2", ExitCode: 1}
		return res, &sdk.ExecError{Response: res, Err: errors.New(res.Stderr), Ran: true}
	})
	ctx := server.Context(session.Context(context.Background(), sdk.Target{}))

	task := sdk.ConfigTask{App: "web", Config: map[string]string{"TOKEN": "hunter2"}, State: sdk.StatePresent}
	state := sdk.ExecutePlan(ctx, task.Plan(ctx))
	for _, d := range sdk.Diagnostics(ctx, state) {
		fmt.Println(d.Summary)
		fmt.Println(d.Detail)
	}
}
Output:
the command failed with exit code 1
refusing to set TOKEN=***

stdout:
-----> Setting config vars

type DiagnosticSeverity added in v0.11.0

type DiagnosticSeverity = tasks.DiagnosticSeverity

DiagnosticSeverity is tasks.DiagnosticSeverity.

type DockerOptionsTask

type DockerOptionsTask = tasks.DockerOptionsTask

DockerOptionsTask is tasks.DockerOptionsTask.

type DomainsTask

type DomainsTask = tasks.DomainsTask

DomainsTask is tasks.DomainsTask.

type DomainsToggleTask

type DomainsToggleTask = tasks.DomainsToggleTask

DomainsToggleTask is tasks.DomainsToggleTask.

type DynamicPropertySchema

type DynamicPropertySchema = tasks.DynamicPropertySchema

DynamicPropertySchema is tasks.DynamicPropertySchema.

type ExampleRequirementSchema added in v0.11.0

type ExampleRequirementSchema = tasks.ExampleRequirementSchema

ExampleRequirementSchema is tasks.ExampleRequirementSchema.

type ExampleSchema

type ExampleSchema = tasks.ExampleSchema

ExampleSchema is tasks.ExampleSchema.

type ExecCommandInput added in v0.9.1

type ExecCommandInput = subprocess.ExecCommandInput

ExecCommandInput is subprocess.ExecCommandInput.

type ExecCommandResponse added in v0.9.1

type ExecCommandResponse = subprocess.ExecCommandResponse

ExecCommandResponse is subprocess.ExecCommandResponse.

type ExecError added in v0.9.1

type ExecError = subprocess.ExecError

ExecError is subprocess.ExecError.

type ExecRunner added in v0.9.1

type ExecRunner = subprocess.ExecRunner

ExecRunner is subprocess.ExecRunner.

type ExportOptions

type ExportOptions = tasks.ExportOptions

ExportOptions is tasks.ExportOptions.

type ExportReport

type ExportReport = tasks.ExportReport

ExportReport is tasks.ExportReport.

type ExportResult

type ExportResult = tasks.ExportResult

ExportResult is tasks.ExportResult.

func ExportRecipe

func ExportRecipe(ctx context.Context, opts ExportOptions) (*ExportResult, error)

ExportRecipe reads the server ctx targets and returns the recipe that describes it. An export that finds nothing is not an error: check Report.MissingApps and Report.MissingResources.

An exporter whose task declares a floor newer than the server is skipped with a warning and listed in Report.VersionErrors. Set ExportOptions.IgnoreVersion to read it anyway.

Example

Reading a server back and picking out one task type.

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/dokku/docket/sdk"
)

func main() {
	session := sdk.NewSession(nil)
	defer func() { _ = session.Close() }()
	ctx := session.Context(context.Background(), sdk.Target{Host: "dokku@dokku.example.com"})

	res, err := sdk.ExportRecipe(ctx, sdk.ExportOptions{Inline: true})
	if err != nil {
		panic(err)
	}

	// Register what the export read before printing anything it produced.
	masker := sdk.NewMasker(res.SensitiveValues()...)
	for _, w := range res.Report.Warnings {
		fmt.Fprintln(os.Stderr, masker.String(w))
	}

	for _, play := range res.Plays() {
		for _, task := range play.Tasks {
			cfg, ok := sdk.As[sdk.ConfigTask](task)
			if !ok {
				continue
			}
			fmt.Println(play.Name, cfg.App, len(cfg.Config))
		}
	}
}

type ExportStatus

type ExportStatus = tasks.ExportStatus

ExportStatus is tasks.ExportStatus.

type ExportSupport

type ExportSupport = tasks.ExportSupport

ExportSupport is tasks.ExportSupport.

func TaskExportSupport

func TaskExportSupport(t Task) (ExportSupport, bool)

TaskExportSupport reports whether ExportRecipe can reconstruct t from a server. The bool is false when t declares nothing.

type ExportedPlay

type ExportedPlay = tasks.ExportedPlay

ExportedPlay is tasks.ExportedPlay.

type ExportedTask

type ExportedTask = tasks.ExportedTask

ExportedTask is tasks.ExportedTask.

type FieldNoteSchema added in v0.11.0

type FieldNoteSchema = tasks.FieldNoteSchema

FieldNoteSchema is tasks.FieldNoteSchema.

type FieldSchema

type FieldSchema = tasks.FieldSchema

FieldSchema is tasks.FieldSchema.

type GitAuthTask

type GitAuthTask = tasks.GitAuthTask

GitAuthTask is tasks.GitAuthTask.

type GitFromArchiveTask

type GitFromArchiveTask = tasks.GitFromArchiveTask

GitFromArchiveTask is tasks.GitFromArchiveTask.

type GitFromImageTask

type GitFromImageTask = tasks.GitFromImageTask

GitFromImageTask is tasks.GitFromImageTask.

type GitPropertyTask

type GitPropertyTask = tasks.GitPropertyTask

GitPropertyTask is tasks.GitPropertyTask.

type GitSyncTask

type GitSyncTask = tasks.GitSyncTask

GitSyncTask is tasks.GitSyncTask.

type HaproxyPropertyTask

type HaproxyPropertyTask = tasks.HaproxyPropertyTask

HaproxyPropertyTask is tasks.HaproxyPropertyTask.

type HttpAuthAllowedIpTask

type HttpAuthAllowedIpTask = tasks.HttpAuthAllowedIpTask

HttpAuthAllowedIpTask is tasks.HttpAuthAllowedIpTask.

type HttpAuthDomainTask

type HttpAuthDomainTask = tasks.HttpAuthDomainTask

HttpAuthDomainTask is tasks.HttpAuthDomainTask.

type HttpAuthTask

type HttpAuthTask = tasks.HttpAuthTask

HttpAuthTask is tasks.HttpAuthTask.

type HttpAuthUser

type HttpAuthUser = tasks.HttpAuthUser

HttpAuthUser is tasks.HttpAuthUser.

type HttpAuthUserTask

type HttpAuthUserTask = tasks.HttpAuthUserTask

HttpAuthUserTask is tasks.HttpAuthUserTask.

type IdentityCollectionSchema

type IdentityCollectionSchema = tasks.IdentityCollectionSchema

IdentityCollectionSchema is tasks.IdentityCollectionSchema.

type IdentitySchema

type IdentitySchema = tasks.IdentitySchema

IdentitySchema is tasks.IdentitySchema.

type ItemIdentity

type ItemIdentity = tasks.ItemIdentity

ItemIdentity is tasks.ItemIdentity.

type ItemSchema

type ItemSchema = tasks.ItemSchema

ItemSchema is tasks.ItemSchema.

type LetsencryptPropertyTask

type LetsencryptPropertyTask = tasks.LetsencryptPropertyTask

LetsencryptPropertyTask is tasks.LetsencryptPropertyTask.

type LetsencryptTask

type LetsencryptTask = tasks.LetsencryptTask

LetsencryptTask is tasks.LetsencryptTask.

type LifecycleSchema added in v0.11.0

type LifecycleSchema = tasks.LifecycleSchema

LifecycleSchema is tasks.LifecycleSchema.

func TaskLifecycle added in v0.11.0

func TaskLifecycle(t Task) LifecycleSchema

TaskLifecycle returns the state that applies t's resource and the state that tears it down, the same pair the catalog publishes as `lifecycle`. Apply is `set` when t offers it and the `state` field's default otherwise; Teardown is `clear` when offered, else `absent`, and empty when t is imperative. A task the rule does not fit declares its own pair instead: dokku_storage_ensure offers `absent` but refuses it, so it has no Teardown.

type LogsPropertyTask

type LogsPropertyTask = tasks.LogsPropertyTask

LogsPropertyTask is tasks.LogsPropertyTask.

type MaintenanceCustomPageTask

type MaintenanceCustomPageTask = tasks.MaintenanceCustomPageTask

MaintenanceCustomPageTask is tasks.MaintenanceCustomPageTask.

type MaintenanceTask

type MaintenanceTask = tasks.MaintenanceTask

MaintenanceTask is tasks.MaintenanceTask.

type Masker

type Masker = subprocess.Masker

Masker is subprocess.Masker.

func NewMasker

func NewMasker(values ...string) *Masker

NewMasker returns a masker for values. A Masker built with no values is still usable; more can be added.

type NetworkPropertyTask

type NetworkPropertyTask = tasks.NetworkPropertyTask

NetworkPropertyTask is tasks.NetworkPropertyTask.

type NetworkTask

type NetworkTask = tasks.NetworkTask

NetworkTask is tasks.NetworkTask.

type NginxPropertyTask

type NginxPropertyTask = tasks.NginxPropertyTask

NginxPropertyTask is tasks.NginxPropertyTask.

type NoteKind added in v0.11.0

type NoteKind = tasks.NoteKind

NoteKind is tasks.NoteKind.

type NoteSchema added in v0.11.0

type NoteSchema = tasks.NoteSchema

NoteSchema is tasks.NoteSchema.

type ObjectEffects added in v0.10.0

type ObjectEffects = tasks.ObjectEffects

ObjectEffects is tasks.ObjectEffects.

type ObjectKind added in v0.10.0

type ObjectKind = tasks.ObjectKind

ObjectKind is tasks.ObjectKind.

type ObjectMissingError added in v0.10.1

type ObjectMissingError = tasks.ObjectMissingError

ObjectMissingError is tasks.ObjectMissingError.

type ObjectRestart added in v0.10.0

type ObjectRestart = tasks.ObjectRestart

ObjectRestart is tasks.ObjectRestart.

type ObjectSchema added in v0.10.0

type ObjectSchema = tasks.ObjectSchema

ObjectSchema is tasks.ObjectSchema.

type OpenrestyPropertyTask

type OpenrestyPropertyTask = tasks.OpenrestyPropertyTask

OpenrestyPropertyTask is tasks.OpenrestyPropertyTask.

type PlanResult

type PlanResult = tasks.PlanResult

PlanResult is tasks.PlanResult.

type PlanStatus

type PlanStatus = tasks.PlanStatus

PlanStatus is tasks.PlanStatus.

type PlanWarning

type PlanWarning = tasks.PlanWarning

PlanWarning is tasks.PlanWarning.

type PluginRequirement

type PluginRequirement = tasks.PluginRequirement

PluginRequirement is tasks.PluginRequirement.

type PluginRequirementSchema

type PluginRequirementSchema = tasks.PluginRequirementSchema

PluginRequirementSchema is tasks.PluginRequirementSchema.

type PluginTask

type PluginTask = tasks.PluginTask

PluginTask is tasks.PluginTask.

type PortMapping

type PortMapping = tasks.PortMapping

PortMapping is tasks.PortMapping.

type PortsTask

type PortsTask = tasks.PortsTask

PortsTask is tasks.PortsTask.

type ProbeStatus

type ProbeStatus = tasks.ProbeStatus

ProbeStatus is tasks.ProbeStatus.

type ProbeSupport

type ProbeSupport = tasks.ProbeSupport

ProbeSupport is tasks.ProbeSupport.

func TaskProbeSupport

func TaskProbeSupport(t Task) (ProbeSupport, bool)

TaskProbeSupport reports how much of its own state t's Plan can read back. The bool is false when t declares nothing.

type PropertyEntrySchema

type PropertyEntrySchema = tasks.PropertyEntrySchema

PropertyEntrySchema is tasks.PropertyEntrySchema.

type PropertySchema

type PropertySchema = tasks.PropertySchema

PropertySchema is tasks.PropertySchema.

type ProxyPropertyTask

type ProxyPropertyTask = tasks.ProxyPropertyTask

ProxyPropertyTask is tasks.ProxyPropertyTask.

type ProxyToggleTask

type ProxyToggleTask = tasks.ProxyToggleTask

ProxyToggleTask is tasks.ProxyToggleTask.

type PsPropertyTask

type PsPropertyTask = tasks.PsPropertyTask

PsPropertyTask is tasks.PsPropertyTask.

type PsScaleTask

type PsScaleTask = tasks.PsScaleTask

PsScaleTask is tasks.PsScaleTask.

type RegistryAuthTask

type RegistryAuthTask = tasks.RegistryAuthTask

RegistryAuthTask is tasks.RegistryAuthTask.

type RegistryPropertyTask

type RegistryPropertyTask = tasks.RegistryPropertyTask

RegistryPropertyTask is tasks.RegistryPropertyTask.

type RejectedPropertySchema

type RejectedPropertySchema = tasks.RejectedPropertySchema

RejectedPropertySchema is tasks.RejectedPropertySchema.

type ResourceLimitTask

type ResourceLimitTask = tasks.ResourceLimitTask

ResourceLimitTask is tasks.ResourceLimitTask.

type ResourceReserveTask

type ResourceReserveTask = tasks.ResourceReserveTask

ResourceReserveTask is tasks.ResourceReserveTask.

type ResourceSelector

type ResourceSelector = tasks.ResourceSelector

ResourceSelector is tasks.ResourceSelector.

func ParseResourceSelectors

func ParseResourceSelectors(addresses []string) ([]ResourceSelector, error)

ParseResourceSelectors parses addresses for ExportOptions.Resources, the values of `export --resource`. Each must name a registered task type that ExportRecipe can reach, and only keys that task declares.

Validation happens here, before the export contacts the server, so a typo in an address fails immediately rather than after an SSH round trip.

type Restart added in v0.10.0

type Restart = tasks.Restart

Restart is tasks.Restart.

type SchedulerDockerLocalPropertyTask

type SchedulerDockerLocalPropertyTask = tasks.SchedulerDockerLocalPropertyTask

SchedulerDockerLocalPropertyTask is tasks.SchedulerDockerLocalPropertyTask.

type SchedulerK3sAnnotationsTask

type SchedulerK3sAnnotationsTask = tasks.SchedulerK3sAnnotationsTask

SchedulerK3sAnnotationsTask is tasks.SchedulerK3sAnnotationsTask.

type SchedulerK3sAutoscalingAuthTask

type SchedulerK3sAutoscalingAuthTask = tasks.SchedulerK3sAutoscalingAuthTask

SchedulerK3sAutoscalingAuthTask is tasks.SchedulerK3sAutoscalingAuthTask.

type SchedulerK3sChartTask

type SchedulerK3sChartTask = tasks.SchedulerK3sChartTask

SchedulerK3sChartTask is tasks.SchedulerK3sChartTask.

type SchedulerK3sLabelsTask

type SchedulerK3sLabelsTask = tasks.SchedulerK3sLabelsTask

SchedulerK3sLabelsTask is tasks.SchedulerK3sLabelsTask.

type SchedulerK3sNodeSysctlsTask

type SchedulerK3sNodeSysctlsTask = tasks.SchedulerK3sNodeSysctlsTask

SchedulerK3sNodeSysctlsTask is tasks.SchedulerK3sNodeSysctlsTask.

type SchedulerK3sProfileTask

type SchedulerK3sProfileTask = tasks.SchedulerK3sProfileTask

SchedulerK3sProfileTask is tasks.SchedulerK3sProfileTask.

type SchedulerK3sPropertyTask

type SchedulerK3sPropertyTask = tasks.SchedulerK3sPropertyTask

SchedulerK3sPropertyTask is tasks.SchedulerK3sPropertyTask.

type SchedulerPropertyTask

type SchedulerPropertyTask = tasks.SchedulerPropertyTask

SchedulerPropertyTask is tasks.SchedulerPropertyTask.

type ServiceBackupTask

type ServiceBackupTask = tasks.ServiceBackupTask

ServiceBackupTask is tasks.ServiceBackupTask.

type ServiceCreateTask

type ServiceCreateTask = tasks.ServiceCreateTask

ServiceCreateTask is tasks.ServiceCreateTask.

type ServiceExposeTask

type ServiceExposeTask = tasks.ServiceExposeTask

ServiceExposeTask is tasks.ServiceExposeTask.

type ServiceLinkTask

type ServiceLinkTask = tasks.ServiceLinkTask

ServiceLinkTask is tasks.ServiceLinkTask.

type ServicePropertyTask

type ServicePropertyTask = tasks.ServicePropertyTask

ServicePropertyTask is tasks.ServicePropertyTask.

type Session

type Session = subprocess.Session

Session is subprocess.Session.

func NewSession

func NewSession(masker *Masker, opts ...SessionOption) *Session

NewSession returns a session whose contexts carry masker and whatever opts, such as the version gate, install. A nil masker leaves any masker already on the parent context in place.

Every context the session hands out also carries one lock table, so Execute calls made under the session - from any goroutine - take turns on the same app or datastore service rather than colliding on dokku's deploy lock. See LockTask.

Example

A session installs the masker and target a run needs, and closes the SSH connections made under it.

package main

import (
	"context"

	"github.com/dokku/docket/sdk"
)

func main() {
	session := sdk.NewSession(sdk.NewMasker("s3cr3t"))
	defer func() { _ = session.Close() }()

	ctx := session.Context(context.Background(), sdk.Target{
		Host: "deploy@dokku.example.com",
		Sudo: true,
	})
	_ = ctx
}

type SessionOption added in v0.10.0

type SessionOption = subprocess.SessionOption

SessionOption is subprocess.SessionOption.

func WithControlPersist added in v0.11.0

func WithControlPersist(d time.Duration) SessionOption

WithControlPersist sets how long each SSH connection the session opens stays open after its last command returns, in place of the default 60 seconds. It is rounded up to whole seconds, which is what ssh's ControlPersist counts.

Zero, or a negative d, closes a connection as soon as no command under the session is using it: the call that finishes the last command closes the connection before it returns. That suits an embedder with nowhere to call Close, such as a process that is killed rather than shut down, at the cost of a new connection for each command that runs after the previous one finished. Commands that overlap still share one.

Example

A process that may be killed without a chance to call Close, such as a Terraform provider, can have its session close each SSH connection as soon as no command is using it, so none lingers after the last command.

package main

import (
	"context"

	"github.com/dokku/docket/sdk"
)

func main() {
	session := sdk.NewSession(nil, sdk.WithControlPersist(0))

	ctx := session.Context(context.Background(), sdk.Target{Host: "deploy@dokku.example.com"})
	_ = ctx
}

func WithVersionGate added in v0.10.0

func WithVersionGate() SessionOption

WithVersionGate is a NewSession option that installs the version gate on every context the session hands out. They share one cache per target for as long as the session lives, so `dokku version` and `plugin:list` are read once per server per session rather than once per call, while each context keeps the cancellation of the parent it was built from. Only a successful read is cached; a failed one is retried by the next call. Each session gets its own cache, and the session's gate replaces one already on the parent context.

Example

A session given the version gate refuses tasks the server is too old for, and reads the server's versions once however many calls it serves, each with a context of its own.

package main

import (
	"context"

	"github.com/dokku/docket/sdk"
)

func main() {
	session := sdk.NewSession(nil, sdk.WithVersionGate())
	defer func() { _ = session.Close() }()

	serve := func(reqCtx context.Context) {
		ctx := session.Context(reqCtx, sdk.Target{Host: "deploy@dokku.example.com"})
		_ = ctx
	}
	serve(context.Background())
	serve(context.Background())
}

type SshKeyTask

type SshKeyTask = tasks.SshKeyTask

SshKeyTask is tasks.SshKeyTask.

type State

type State = tasks.State

State is tasks.State.

type StorageEnsureTask

type StorageEnsureTask = tasks.StorageEnsureTask

StorageEnsureTask is tasks.StorageEnsureTask.

type StorageEntryTask

type StorageEntryTask = tasks.StorageEntryTask

StorageEntryTask is tasks.StorageEntryTask.

type StorageMount

type StorageMount = tasks.StorageMount

StorageMount is tasks.StorageMount.

type StorageMountTask

type StorageMountTask = tasks.StorageMountTask

StorageMountTask is tasks.StorageMountTask.

type Target

type Target = subprocess.Target

Target is subprocess.Target.

type Task

type Task = tasks.Task

Task is tasks.Task.

func DecodeTask

func DecodeTask(typeKey string, body []byte) (Task, error)

DecodeTask builds a task of the registered type typeKey from a YAML task body, with defaults applied.

func Lookup

func Lookup(typeKey string) (Task, bool)

Lookup reports whether typeKey is a registered task type and, when it is, returns a zero-value instance of it for reading the task's metadata - its synopsis, identity keys, export support and so on.

Every call allocates, so a caller may do anything with the result without affecting the registry or any other caller. The instance has no `default:` tags applied; use NewTask for a task to set fields on and run.

func NewTask

func NewTask(typeKey string) (Task, error)

NewTask returns a new task of the registered type typeKey, such as "dokku_app", with its `default:` values applied. Use it rather than a struct literal, which leaves State as "" instead of the default the field documents.

The task is a pointer to the registered type, so fields are set through a type assertion:

task, err := sdk.NewTask("dokku_app")
app := task.(*sdk.AppTask)
app.App = "api"
Example
package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	task, err := sdk.NewTask("dokku_app")
	if err != nil {
		panic(err)
	}
	app := task.(*sdk.AppTask)
	app.App = "api"

	fmt.Println(sdk.IdentityAddress("dokku_app", app), app.State)
}
Output:
dokku_app[app=api] present

func ReadAddress added in v0.10.0

func ReadAddress(ctx context.Context, address string) (Task, bool, error)

ReadAddress returns the resource an identity address names, such as `dokku_buildpacks[app=api]`, as it exists on the server, and whether it exists, from the address alone; nothing is applied. It reads every member the resource has, the way a task in state set or clear would own them: a dokku_config leaves out the keys docket keeps (linked-service DSNs, NO_VHOST, the git rev env var), and a dokku_ps_scale leaves out process types scaled to zero. A whole collection read that way always exists: one with no members comes back in state clear with exists true, since a missing app or service is reported as an error instead. The returned task and errors are ReadTask's: the value type registered for the task, the *VersionError when ctx carries a gate the server is behind, an *ObjectMissingError when the app or service the resource depends on is missing, and an error wrapping ErrStateUnreadable for a task whose probe cannot read its state.

The address must name one resource. A bare type, a key the task does not declare, or a missing required key is an error, raised before the server is contacted. IdentityAddress renders the address of a task you hold.

Example

Reading a resource known only by its identity address, such as one being imported, reads every member it has.

package main

import (
	"context"
	"fmt"

	"github.com/dokku/docket/sdk"
	"github.com/dokku/docket/sdk/dokkutest"
)

func main() {
	server := dokkutest.New(nil).SeedBuildpacks("api", "https://github.com/heroku/heroku-buildpack-nodejs.git")
	ctx := sdk.ContextWithVersionGate(server.Context(context.Background()))

	current, exists, err := sdk.ReadAddress(ctx, "dokku_buildpacks[app=api]")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(exists, current.(sdk.BuildpacksTask).Buildpacks)
}
Output:
true [https://github.com/heroku/heroku-buildpack-nodejs.git]

func ReadTask added in v0.9.2

func ReadTask(ctx context.Context, t Task) (Task, bool, error)

ReadTask returns t as it exists on the server, and whether the resource exists, from a single Plan; nothing is applied. The returned task is the value type registered for t (AppTask, not *AppTask), carrying the identity keys from t and every field the server reports. Fields whose catalog entry is WriteOnly or ApplyOnly are left empty or at their default.

t must be a valid task: its State picks which members of a collection are read, so a dokku_config in state present reads the keys it names, and one in state set reads every key it would own. A property task known only by its identity keys is read with State absent, which needs no Value.

The State also picks what exists means. A collection task in set or clear owns the whole collection, which exists whenever the read succeeds, with or without members. In present or absent it owns only the members it names, and exists reports whether any of them is there.

When ctx carries a version gate and the server is older than t's floor, the error is the *VersionError, never a resource reported as absent. When the read does not find the resource, or finds a whole collection with no members, and the app or service t depends on is missing, the error is an *ObjectMissingError naming it, which matches ErrObjectMissing; a task that creates its app or service, such as dokku_app, reports itself absent instead. A task whose probe cannot read its state returns an error wrapping ErrStateUnreadable.

Example

Reading one resource's current state through its task, without exporting the server.

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/dokku/docket/sdk"
	"github.com/dokku/docket/sdk/dokkutest"
)

func main() {
	server := dokkutest.New(nil).SeedConfig("api", map[string]string{"LOG_LEVEL": "debug", "OTHER": "x"})
	ctx := sdk.ContextWithVersionGate(server.Context(context.Background()))

	task, _ := sdk.NewTask("dokku_config")
	cfg := task.(*sdk.ConfigTask)
	cfg.App = "api"
	cfg.Config = map[string]string{"LOG_LEVEL": "info"}

	current, exists, err := sdk.ReadTask(ctx, cfg)
	var verr *sdk.VersionError
	if errors.As(err, &verr) {
		fmt.Println("server too old for", verr.TaskType)
		return
	}
	if err != nil {
		fmt.Println(err)
		return
	}
	observed := current.(sdk.ConfigTask)
	fmt.Println(exists, observed.Config)
}
Output:
true map[LOG_LEVEL:debug]

func TaskFromValues added in v0.11.0

func TaskFromValues(typeKey string, values map[string]any) (Task, error)

TaskFromValues builds a task of the registered type typeKey from values keyed by recipe key, with defaults applied, the way DecodeTask builds one from a YAML body. Like DecodeTask it checks shape and type only; the task's own rules - required fields, choices, constraints - are for ConstraintViolations and the task's Validate method.

Each value must match its field's catalog type, and is accepted where the conversion is lossless:

  • string: any Go string, including a named string type such as State. A json.Number is a number, not a string, and is refused.
  • bool: a bool.
  • int: any Go integer type, a float32 or float64 holding a whole number, or a json.Number holding an integer, within the field's range.
  • float: any Go integer or float type, or a json.Number.
  • list: any slice or array, []any or typed such as []string, whose elements each match the item type.
  • dict and object: any map with string keys, map[string]any or typed such as map[string]string; a dict's values each match the item type, and an object's keys are its element's recipe keys.
  • any: nil, a bool, a string, a number, or a list or string-keyed map of those, stored with integers as int64 and floats as float64.

A nil value leaves the field unset, so it takes its default; a nil list element or dict value is refused unless the item type is any. An unknown key or a value of the wrong type is an error naming the recipe key path, such as `users[1].hash` or `scale["web"]`, and the first one in sorted key order is reported.

Example

A task built from values a caller holds, such as a decoded JSON body, and read back with every field present: the ints come back as int64 and the unset state at its default.

package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	task, err := sdk.TaskFromValues("dokku_ps_scale", map[string]any{
		"app":   "api",
		"scale": map[string]any{"web": 2, "worker": float64(1)},
	})
	if err != nil {
		panic(err)
	}
	values := sdk.TaskValues(task)
	fmt.Printf("%v %T %v %v\n", values["state"], values["scale"].(map[string]any)["web"], values["scale"], values["skip_deploy"])

	_, err = sdk.TaskFromValues("dokku_ps_scale", map[string]any{"scale": map[string]any{"web": "two"}})
	fmt.Println(err)
}
Output:
present int64 map[web:2 worker:1] false
dokku_ps_scale: scale["web"]: expected int, got string

func TaskInState added in v0.11.0

func TaskInState(t Task, state State) (Task, error)

TaskInState returns a copy of t moved into state: its `state` key set to state, and every field whose `forbidden_when` names state reset to its default, or to its zero value when it has none. Element fields of a list of structs are reset the same way. t itself is left unchanged, and the copy shares no list or map with it.

It is how a consumer tears down a resource it applied: the task it applied carries values the teardown state refuses, such as dokku_ps_scale's `scale` under `clear` or a property task's `value` under `absent`, and moving it with TaskInState(t, TaskLifecycle(t).Teardown) drops them. A field the state requires is not invented, so moving a task into a state it lacks the input for still fails its validation.

The error is a state t does not offer.

Example

ExampleTaskInState tears down a process formation: dokku_ps_scale applies with set and tears down with clear, which refuses scale, so the teardown copy leaves it out.

package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	task, err := sdk.NewTask("dokku_ps_scale")
	if err != nil {
		panic(err)
	}
	applied := task.(*sdk.PsScaleTask)
	applied.App = "api"
	applied.Scale = map[string]int{"web": 2}

	lifecycle := sdk.TaskLifecycle(applied)
	applied.State = lifecycle.Apply

	moved, err := sdk.TaskInState(applied, lifecycle.Teardown)
	if err != nil {
		panic(err)
	}
	destroy := moved.(*sdk.PsScaleTask)
	fmt.Println(lifecycle.Apply, lifecycle.Teardown)
	fmt.Println(destroy.State, len(destroy.Scale), len(applied.Scale))
	fmt.Println(len(sdk.ConstraintViolations(destroy)))
}
Output:
set clear
clear 0 1
0

func TaskRequires added in v0.11.0

func TaskRequires(t Task) []Task

TaskRequires returns the tasks t needs applied before it, in the order to apply them, the same derivation the catalog publishes as each example's `requires`: a dokku_plugin installing each non-core plugin t's plugin requirements name, then the task creating each object t names but does not create itself - dokku_app for an app, dokku_service_create for a service - each preceded by the tasks it needs in turn. A task needing nothing returns nil.

The list covers what docket can derive from the task's declarations alone. An example that needs more, such as a deployed app or a storage entry it names, declares it, and the catalog publishes both.

Example

ExampleTaskRequires lists what a service link needs applied before it: the datastore plugin its service type names, then the app and the service it links, each created by its own task.

package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	task, err := sdk.NewTask("dokku_service_link")
	if err != nil {
		panic(err)
	}
	link := task.(*sdk.ServiceLinkTask)
	link.App = "api"
	link.Service = "redis"
	link.Name = "cache"

	for _, required := range sdk.TaskRequires(link) {
		fmt.Println(sdk.IdentityAddress(sdk.TaskTypeOf(required), required))
	}
}
Output:
dokku_plugin[name=redis]
dokku_app[app=api]
dokku_service_create[service=redis,name=cache]

type TaskCatalog

type TaskCatalog = tasks.TaskCatalog

TaskCatalog is tasks.TaskCatalog.

func Catalog

func Catalog() (TaskCatalog, error)

Catalog describes every registered task type, sorted by type key. It is what `docket schema` emits.

func CatalogFor

func CatalogFor(typeKeys []string) (TaskCatalog, error)

CatalogFor describes only the given task types, sorted by type key. A nil or empty selection means every registered task, so the whole catalog and a narrowed one are the same code path rather than two that can drift.

Sorting is by type key whatever order the keys arrived in, which is what lets a narrowed catalog be diffed against a full one and makes two runs of the same selection byte-identical. Duplicates collapse: the document is a set keyed by type, so naming a type twice is not an error.

An unregistered type is an error rather than a silently smaller document - a caller asking for a type that does not exist wants to know. Keys are checked in the order given, so the message names the first one the caller wrote, and all of them are checked before any schema is built.

Example
package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	catalog, err := sdk.CatalogFor([]string{"dokku_app"})
	if err != nil {
		panic(err)
	}
	for _, schema := range catalog.Tasks {
		fmt.Println(schema.Type, "-", schema.Synopsis)
	}
}
Output:
dokku_app - Creates or destroys an app
Example (Requires)

ExampleCatalogFor_requires reads the tasks an example needs applied first from the catalog, the same list the reference page prints with it.

package main

import (
	"fmt"

	"github.com/dokku/docket/sdk"
)

func main() {
	catalog, err := sdk.CatalogFor([]string{"dokku_app_clone"})
	if err != nil {
		panic(err)
	}
	example := catalog.Tasks[0].Examples[0]
	fmt.Println(example.Name)
	for _, required := range example.Requires {
		fmt.Print(required.YAML)
	}
}
Output:
Clone an app
dokku_app:
    app: node-js-app
dokku_git_from_image:
    app: node-js-app
    image: dokku/smoke-test-app:dockerfile

type TaskObject added in v0.10.0

type TaskObject = tasks.TaskObject

TaskObject is tasks.TaskObject.

func TaskObjects added in v0.10.0

func TaskObjects(t Task) []TaskObject

TaskObjects returns the app and the datastore service t changes, app first: the recipe keys naming each and the values t holds for them, whether t creates it, and when t restarts or redeploys it. The catalog publishes the same data as TaskSchema.Objects. Nil when t changes neither.

type TaskOutputState

type TaskOutputState = tasks.TaskOutputState

TaskOutputState is tasks.TaskOutputState.

func ExecutePlan

func ExecutePlan(ctx context.Context, p PlanResult) TaskOutputState

ExecutePlan applies p, the result of a task's Plan. A plan that is already in sync applies nothing. It first adds the planned task's sensitive values to the masker on ctx, even when p was planned under another context. It takes no lock; see LockTask.

The returned state's Observed and Exists are the task as the server holds it once the plan is carried out, by the rules PlanResult.Observed follows for a plan in sync: every field the planned task declares as declared, every field it leaves unset as the server reports it, and write_only and apply_only fields left unread. A plan in sync carries its own Observed and Exists. After a successful apply they are built from the plan's read when the planned task declares every field a read fills, so the server is not probed again; otherwise the planned task is planned once more after the apply, since the apply may have moved a field the task leaves to the server, such as the image a dokku_service_create without one is created on. Observed is nil when the plan errored, when the apply failed, when the plan read nothing (a task whose probe cannot read its state, such as dokku_storage_ensure), when that second plan fails, for an imperative task, which runs an operation rather than declaring a resource to observe, and on a context from ContextWithoutObserved.

Example (Observed)

Storing a resource's state after creating it, without reading it back: the returned state carries the task as the server now holds it, including the image dokku picked for a service the task does not pin.

package main

import (
	"context"
	"fmt"

	"github.com/dokku/docket/sdk"
	"github.com/dokku/docket/sdk/dokkutest"
)

func main() {
	ctx := dokkutest.New(nil).SeedDatastore("postgres", "postgres:17.2").Context(context.Background())

	task := sdk.ServiceCreateTask{Service: "postgres", Name: "db", State: sdk.StatePresent}
	state := sdk.ExecutePlan(ctx, task.Plan(ctx))
	if state.Error != nil {
		fmt.Println(state.Error)
		return
	}
	observed := state.Observed.(sdk.ServiceCreateTask)
	fmt.Println(state.Changed, state.Exists, observed.Image, observed.ImageVersion)
}
Output:
true true postgres 17.2

type TaskSchema

type TaskSchema = tasks.TaskSchema

TaskSchema is tasks.TaskSchema.

type TraefikPropertyTask

type TraefikPropertyTask = tasks.TraefikPropertyTask

TraefikPropertyTask is tasks.TraefikPropertyTask.

type VersionError added in v0.9.1

type VersionError = tasks.VersionError

VersionError is tasks.VersionError.

type VersionSupport

type VersionSupport = tasks.VersionSupport

VersionSupport is tasks.VersionSupport.

func TaskMinimumVersions added in v0.9.1

func TaskMinimumVersions(t Task) (VersionSupport, bool)

TaskMinimumVersions reports the oldest Dokku, and the plugins, t declares it runs on. The bool is false when t declares nothing.

type Violation added in v0.10.0

type Violation = tasks.Violation

Violation is tasks.Violation.

func ConstraintViolations added in v0.10.0

func ConstraintViolations(task any, opts ...ConstraintOption) []Violation

ConstraintViolations returns every declared field constraint task breaks - the required_when, forbidden_when and unique of each field in declaration order, the task's Constraints, then those of each element of its structured lists in turn - or nil when it satisfies them all. A task's Validate and Plan reject the first of these; this reports them all at once, as `docket validate` does.

A property task naming a property another task manages is the one exception: that is reported alone, because the user wrote a name this task will never manage, and naming the task that does is more use than telling them the app field is missing too.

Pass WithUnknown to check a task before every value is known, such as a Terraform plan whose attributes come from another resource's output: a rule is then reported only when the known fields decide it, and one that turns on an unknown value is skipped. Without options every field holds its final value.

Directories

Path Synopsis
Package dokkutest is a stateful fake dokku server for tests that drive docket's tasks through the sdk package without a real server.
Package dokkutest is a stateful fake dokku server for tests that drive docket's tasks through the sdk package without a real server.

Jump to

Keyboard shortcuts

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