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 ¶
- Constants
- Variables
- func As[T Task](e ExportedTask) (T, bool)
- func CheckMinimumVersions(ctx context.Context, typeKey string, task Task) error
- func ContextWithRunner(ctx context.Context, fn ExecRunner) context.Context
- func ContextWithTarget(ctx context.Context, target Target) context.Context
- func ContextWithTaskLocks(ctx context.Context) context.Context
- func ContextWithVersionGate(ctx context.Context) context.Context
- func ContextWithoutObserved(ctx context.Context) context.Context
- func IdentityAddress(typeKey string, t Task) string
- func LockTask(ctx context.Context, t Task) (context.Context, func(), error)
- func ParseFieldDefault(fieldType string, itemType string, raw string) (any, error)
- func ParseIdentityAddress(address string) (string, map[string]string, error)
- func TaskDeprecation(t Task) string
- func TaskImperative(t Task) bool
- func TaskSensitiveValues(t Task) []string
- func TaskSynopsis(t Task) string
- func TaskTypeOf(t Task) string
- func TaskTypes() []string
- func TaskValues(t Task) map[string]any
- func ValidateSSHOptions(opts []string) error
- type AclAppTask
- type AclServiceTask
- type AppCloneTask
- type AppJsonPropertyTask
- type AppLockTask
- type AppTask
- type AppsPropertyTask
- type BuilderDockerfilePropertyTask
- type BuilderHerokuishPropertyTask
- type BuilderLambdaPropertyTask
- type BuilderNixpacksPropertyTask
- type BuilderPackPropertyTask
- type BuilderPropertyTask
- type BuilderRailpackPropertyTask
- type BuildpacksPropertyTask
- type BuildpacksTask
- type BuildsPropertyTask
- type CaddyPropertyTask
- type CertsTask
- type ChecksPropertyTask
- type ChecksToggleTask
- type ConfigTask
- type Constraint
- type ConstraintKind
- type ConstraintOption
- type CronPropertyTask
- type Diagnostic
- type DiagnosticSeverity
- type DockerOptionsTask
- type DomainsTask
- type DomainsToggleTask
- type DynamicPropertySchema
- type ExampleRequirementSchema
- type ExampleSchema
- type ExecCommandInput
- type ExecCommandResponse
- type ExecError
- type ExecRunner
- type ExportOptions
- type ExportReport
- type ExportResult
- type ExportStatus
- type ExportSupport
- type ExportedPlay
- type ExportedTask
- type FieldNoteSchema
- type FieldSchema
- type GitAuthTask
- type GitFromArchiveTask
- type GitFromImageTask
- type GitPropertyTask
- type GitSyncTask
- type HaproxyPropertyTask
- type HttpAuthAllowedIpTask
- type HttpAuthDomainTask
- type HttpAuthTask
- type HttpAuthUser
- type HttpAuthUserTask
- type IdentityCollectionSchema
- type IdentitySchema
- type ItemIdentity
- type ItemSchema
- type LetsencryptPropertyTask
- type LetsencryptTask
- type LifecycleSchema
- type LogsPropertyTask
- type MaintenanceCustomPageTask
- type MaintenanceTask
- type Masker
- type NetworkPropertyTask
- type NetworkTask
- type NginxPropertyTask
- type NoteKind
- type NoteSchema
- type ObjectEffects
- type ObjectKind
- type ObjectMissingError
- type ObjectRestart
- type ObjectSchema
- type OpenrestyPropertyTask
- type PlanResult
- type PlanStatus
- type PlanWarning
- type PluginRequirement
- type PluginRequirementSchema
- type PluginTask
- type PortMapping
- type PortsTask
- type ProbeStatus
- type ProbeSupport
- type PropertyEntrySchema
- type PropertySchema
- type ProxyPropertyTask
- type ProxyToggleTask
- type PsPropertyTask
- type PsScaleTask
- type RegistryAuthTask
- type RegistryPropertyTask
- type RejectedPropertySchema
- type ResourceLimitTask
- type ResourceReserveTask
- type ResourceSelector
- type Restart
- type SchedulerDockerLocalPropertyTask
- type SchedulerK3sAnnotationsTask
- type SchedulerK3sAutoscalingAuthTask
- type SchedulerK3sChartTask
- type SchedulerK3sLabelsTask
- type SchedulerK3sNodeSysctlsTask
- type SchedulerK3sProfileTask
- type SchedulerK3sPropertyTask
- type SchedulerPropertyTask
- type ServiceBackupTask
- type ServiceCreateTask
- type ServiceExposeTask
- type ServiceLinkTask
- type ServicePropertyTask
- type Session
- type SessionOption
- type SshKeyTask
- type State
- type StorageEnsureTask
- type StorageEntryTask
- type StorageMount
- type StorageMountTask
- type Target
- type Task
- func DecodeTask(typeKey string, body []byte) (Task, error)
- func Lookup(typeKey string) (Task, bool)
- func NewTask(typeKey string) (Task, error)
- func ReadAddress(ctx context.Context, address string) (Task, bool, error)
- func ReadTask(ctx context.Context, t Task) (Task, bool, error)
- func TaskFromValues(typeKey string, values map[string]any) (Task, error)
- func TaskInState(t Task, state State) (Task, error)
- func TaskRequires(t Task) []Task
- type TaskCatalog
- type TaskObject
- type TaskOutputState
- type TaskSchema
- type TraefikPropertyTask
- type VersionError
- type VersionSupport
- type Violation
Examples ¶
- CatalogFor
- CatalogFor (Requires)
- ContextWithRunner
- ContextWithoutObserved
- Diagnostics
- Diagnostics (FailedCommand)
- ExecutePlan (Observed)
- ExportRecipe
- NewSession
- NewTask
- ReadAddress
- ReadTask
- TaskFromValues
- TaskInState
- TaskRequires
- TaskSensitiveValues
- TaskTypes
- WithControlPersist
- WithUnknown
- WithVersionGate
Constants ¶
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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".
const FormatHCL = tasks.FormatNameHCL
FormatHCL is the canonical name of the HCL codec, one of the recipe formats ExportResult.MarshalRecipe accepts.
const FormatJSON5 = tasks.FormatNameJSON5
FormatJSON5 is the canonical name of the JSON5 codec, one of the recipe formats ExportResult.MarshalRecipe accepts.
const FormatYAML = tasks.FormatYAML
FormatYAML is the canonical name of the YAML codec, one of the recipe formats ExportResult.MarshalRecipe accepts.
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).
const PropertyFieldName = tasks.PropertyFieldName
PropertyFieldName is the recipe key every property table constrains.
Variables ¶
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.
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.
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
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 ¶
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
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
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
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 ¶
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
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
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 ¶
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 ¶
TaskDeprecation returns t's deprecation notice, or "" when t is current.
func TaskImperative ¶ added in v0.10.0
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
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 ¶
TaskSynopsis returns t's description, or "" when it has none.
func TaskTypeOf ¶ added in v0.11.0
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
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
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 AclServiceTask ¶
type AclServiceTask = tasks.AclServiceTask
AclServiceTask is tasks.AclServiceTask.
type AppJsonPropertyTask ¶
type AppJsonPropertyTask = tasks.AppJsonPropertyTask
AppJsonPropertyTask is tasks.AppJsonPropertyTask.
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 ChecksPropertyTask ¶
type ChecksPropertyTask = tasks.ChecksPropertyTask
ChecksPropertyTask is tasks.ChecksPropertyTask.
type ChecksToggleTask ¶
type ChecksToggleTask = tasks.ChecksToggleTask
ChecksToggleTask is tasks.ChecksToggleTask.
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 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 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 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))
}
}
}
Output:
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 FieldNoteSchema ¶ added in v0.11.0
type FieldNoteSchema = tasks.FieldNoteSchema
FieldNoteSchema is tasks.FieldNoteSchema.
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 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 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 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 NetworkPropertyTask ¶
type NetworkPropertyTask = tasks.NetworkPropertyTask
NetworkPropertyTask is tasks.NetworkPropertyTask.
type NginxPropertyTask ¶
type NginxPropertyTask = tasks.NginxPropertyTask
NginxPropertyTask is tasks.NginxPropertyTask.
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 PluginRequirement ¶
type PluginRequirement = tasks.PluginRequirement
PluginRequirement is tasks.PluginRequirement.
type PluginRequirementSchema ¶
type PluginRequirementSchema = tasks.PluginRequirementSchema
PluginRequirementSchema is tasks.PluginRequirementSchema.
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 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 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
}
Output:
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
}
Output:
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())
}
Output:
type StorageEnsureTask ¶
type StorageEnsureTask = tasks.StorageEnsureTask
StorageEnsureTask is tasks.StorageEnsureTask.
type StorageEntryTask ¶
type StorageEntryTask = tasks.StorageEntryTask
StorageEntryTask is tasks.StorageEntryTask.
type StorageMountTask ¶
type StorageMountTask = tasks.StorageMountTask
StorageMountTask is tasks.StorageMountTask.
type Task ¶
Task is tasks.Task.
func DecodeTask ¶
DecodeTask builds a task of the registered type typeKey from a YAML task body, with defaults applied.
func Lookup ¶
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 ¶
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
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
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
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
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
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 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
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. |