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

taskflow

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 15 Imported by: 0

README

taskflow

Go Reference

Optional DAG runner for containerkit. It orders the same *kit.Container, kit.Build, and *kit.Compose values you already have, plus ordinary Go functions.

pkg/kit and pkg/client do not import this package. A program that never imports taskflow never downloads go-taskflow.

go get github.com/aptd3v/containerkit
import "github.com/aptd3v/containerkit/pkg/taskflow"

Contents

When to use it

Use taskflow when main would otherwise be a sequence of Build / Up / ContainerCreate / Down calls and you want:

  • independent steps to run in parallel
  • a failed step to skip its dependents without aborting siblings
  • teardown that still runs after a failure (Remove, or Always)
  • a branch (If) or a retry cycle (Condition)
  • a DOT dump, a flamegraph, or a Chrome trace of the graph

Stay in kit.NewCompose / client when the work is one stack or one container. Taskflow does not replace Compose DependsOn; it orders your calls.

Quick start

flow := taskflow.New("ci", cli)
build := flow.Build("build",
	kit.NewBuild().Dockerfile(df),
	&client.ImageBuild{Tags: []string{"app:ci"}},
)
up := flow.Compose("up-db", db, &kit.Up{Detach: true, Wait: true})
test := flow.Container("test", kit.NewContainer().Image("app:ci"), nil)

test.DependsOn(up.DependsOn(build))

defer flow.Remove(ctx)
err := flow.Run(ctx, &taskflow.Run{Writer: os.Stdout})

cli may be nil when every step is Task or Compose. Container and Build need a client.

Runnable demo: examples/taskflow.

Mental model

flowchart LR
  build --> up-db --> tests --> release --> tag

A Flow is a named graph. You add steps, then wire edges, then call Run once. Independent steps with all predecessors done run concurrently.

Piece Role
Flow The graph. One Run per value.
Task One node. Every constructor returns *Task.
Subflow Nested graph with the same constructors as Flow.
Precede / DependsOn Edge. a.Precede(b) is b.DependsOn(a): A then B.
Run Options for Flow.Run. Nil means defaults.

Steps

Flow and Subflow share these constructors.

Constructor Runs
Task(name, fn) fn(ctx) error
Build(name, kit.Build, *client.ImageBuild) client.ImageBuild
Container(name, *kit.Container, *ContainerRun) remove by name → create → start → wait-for-exit
Compose(name, *kit.Compose, cmd) *kit.Up / *kit.Logs / … or fn(ctx, app)
Subflow(name, instantiate) nested graph; instantiate runs once when the subflow starts
If(name, pred) boolean branch (Then / Else)
Condition(name, pred) indexed branch (Precede order)
Container

One-shot. A non-zero exit fails the step. RestartAlways / UnlessStopped / OnFailure and AutoRemove are rejected: this step records the first exit, then copies logs. Long-running services belong in Compose with *kit.Up; flaky jobs use Retry.

The step writes to the *kit.Container you pass: it sets Name when empty and adds the containerkit.* labels. Do not reuse that value as if it were untouched.

unit := flow.Container("unit",
	kit.NewContainer().Image("app:ci").Command("go", "test", "./..."),
	nil,
)

ContainerRun.Logs is a per-step override. Nil means Run.Writer. io.Discard silences one step.

Build
flow.Build("build", kit.NewBuild().Dockerfile(df), &client.ImageBuild{
	Tags: []string{"app:ci"},
})

Output goes to Run.Writer, with a tee into Run.Log when set.

Compose

cmd is a *kit option. The struct is copied so the caller's value is not mutated. A nil Writer on the option means Run.Writer.

up := flow.Compose("up-db", db, &kit.Up{Detach: true, Wait: true})
logs := flow.Compose("logs", db, &kit.Logs{Follow: true, ServiceNames: []string{"db"}})

Use a func for Events, LoadProject, or options that depend on earlier steps:

flow.Compose("events", db, func(ctx context.Context, app *kit.Compose) error {
	ev, errs, err := app.Events(ctx, &kit.EventsOpt{})
	_ = ev
	_ = errs
	return err
})

Prefer defer flow.Remove for end-of-run teardown so List can still see the stack. Use a *kit.Down step when the graph itself must bring the project down (and Always if that must happen after a failure).

Edges

build.Precede(up)     // build, then up
up.DependsOn(build)   // same edge
test.DependsOn(up.DependsOn(build))

Precede on an If is Then. On a Condition, argument order is the branch index (0, 1, …).

Priority only orders the scheduler queue among ready steps. It does not override edges.

flow.Container("lint", lint, nil).Priority(taskflow.PriorityLow)

PriorityHigh, PriorityNormal, PriorityLow. Zero is ignored.

Failure, Always, Retry

A failed or skipped step skips its successors. Siblings keep running. Run joins every step error and returns after the graph drains.

check ──► publish     // check fails → publish skipped
bundle ──► publish    // bundle still runs; publish still skipped

Always runs a step even when a predecessor failed or was skipped. The upstream failure still propagates: successors of the Always step are skipped as if it had failed. Use it for teardown that must live in the graph.

down := flow.Compose("down-db", db, &kit.Down{RemoveVolumes: true}).Always()

Retry(n) reruns a failing step up to n more times. Each failed attempt emits EventRetry with Attempt (1-based) and Err. Panics and a canceled context are not retried.

flow.Container("integration", integ, nil).Retry(2) // up to 3 attempts

For end-of-run Compose teardown, defer flow.Remove is enough. Always is for a step that must run before later graph nodes see the result (or when you need Down in the middle of the DAG).

If and Condition

If

Boolean branch. Then is true, Else is false. A missing side is a no-op. If a predecessor failed, the If is skipped and neither branch runs.

release := flow.If("release", func(context.Context) bool {
	return os.Getenv("RELEASE") == "1"
})
release.Then(tag)
release.Else(skip) // optional

That is the usual “only tag a green run” gate.

Condition

Indexed branch. The function returns a uint; that number selects the successor in Precede order.

gate := flow.Condition("gate", func(context.Context) uint {
	if ok {
		return 0
	}
	return 1
})
gate.Precede(ship, retry) // 0 = ship, 1 = retry

Unlike If, a Condition still runs after a failed predecessor. That is required for a retry cycle: a non-condition node is the first entry, and the Condition sits on the back edge.

start.Precede(body)
body.Precede(verify)
verify.Precede(done, body) // 0 = leave, 1 = run body again

An out-of-range index, or a Condition with no successors, fails the step instead of panicking. Nothing after it runs.

If Condition
Predicate bool uint index
Edges Then / Else Precede order
Missing branch no-op error
Upstream failed skipped still runs

For retrying one step, Retry(n) is simpler than a Condition loop.

Subflow

A nested graph. Inner constructors match Flow. instantiate runs once, when the subflow starts.

tests := flow.Subflow("tests", func(sf *taskflow.Subflow) {
	sf.Container("unit", unit, nil).Priority(taskflow.PriorityHigh)
	sf.Container("integration", integ, nil).Retry(2)
	sf.Container("lint", lint, nil).Priority(taskflow.PriorityLow)
})

Independent inner steps run in parallel. If any inner step fails, the subflow counts as failed: successors of tests are skipped. Subflow.Err() is only the subflow node's own error (usually nil); inner errors are on those tasks and in the error Run returns.

Step names are not unique across subflows. Compare Event.Task, not Event.Name.

Run.Concurrency defaults to GOMAXPROCS and is raised above the number of known subflows (go-taskflow needs a spare worker to enter a nested graph).

Run

err := flow.Run(ctx, &taskflow.Run{
	Writer:      os.Stdout, // default
	Concurrency: 0,         // GOMAXPROCS, then ≥ subflows+1
	Profile:     os.Stdout, // flamegraph text when the graph finishes
	Trace:       traceFile, // Chrome Trace JSON
	Watch:       func(e taskflow.Event) { fmt.Println(e.Kind, e.Name) },
	Log:         func(step string, p []byte) { /* partial writes */ },
})

Nil *Run is defaults. Run is not reusable: a second call returns ErrAlreadyRun.

Field Meaning
Writer Build output, container logs, Compose output. Nil → os.Stdout.
Watch Step lifecycle. May run on several worker goroutines.
Log Step-scoped bytes (build stream, container logs, compose output). Partial writes. Concurrent.
Profile Flamegraph text after the graph finishes. Nil disables.
Trace Chrome Trace JSON after the graph finishes. Nil disables.
Concurrency Worker count.
Watch
func watch(e taskflow.Event) {
	switch e.Kind {
	case taskflow.EventStart:
	case taskflow.EventDone:
	case taskflow.EventSkip:
	case taskflow.EventRetry:
		fmt.Printf("[%s] attempt %d failed: %v\n", e.Name, e.Attempt, e.Err)
	case taskflow.EventError:
		fmt.Printf("[%s] %v\n", e.Name, e.Err)
	}
}

Event.Task is the step. Event.Err is set for EventError and EventRetry. Event.Attempt is set for EventRetry. Event.Duration is set for finished attempts.

This is the scheduler timeline, not OpenTelemetry. Engine HTTP spans are a client option (client.WithTraceProvider) and nest under whatever span is already on ctx. Taskflow does not start an OTel span per step.

Dump, Profile, Trace

_ = flow.Dump(os.Stdout) // DOT, available before Run

Dump is the static graph. Run.Profile is go-taskflow flamegraph text. Run.Trace is Chrome Trace JSON (chrome://tracing also works). Each event is a complete span (ph: "X") with name, category (static / subflow / condition), start, duration, and optional parent / dependents.

Labels, List, Remove

At Run, Container and Compose steps stamp:

Label Value
containerkit.flow Flow.Name()
containerkit.task step name (up-db for Compose, first Compose step wins)
containerkit.depends nearest predecessor container names (Container steps)
containerkit.precedes nearest successor container names (Container steps)

Compose services are labeled on the project (after WithService), so Up creates containers List can find. You do not set these labels yourself.

listed, err := flow.List(ctx)                 // this flow
listed, err = taskflow.List(ctx, cli, "ci")   // by name, other process

List is docker ps -a filtered by containerkit.flow. Stopped containers count; deleted ones do not. Call it before Remove.

defer flow.Remove(ctx)
err := flow.Run(ctx, nil)
listed, _ := flow.List(ctx) // compose stack still up

Remove:

  1. Downs every *kit.Compose the graph referenced (RemoveVolumes: true)
  2. force-removes Container-step IDs this process created
  3. force-removes any leftover containers with this flow's label

Compose Down is idempotent enough to call again if a *kit.Down step already ran.

Errors

Run returns errors.Join of every failed step's *TaskError.

err := flow.Run(ctx, nil)
var te *taskflow.TaskError
if errors.As(err, &te) {
	fmt.Println(te.Task, te.Err)
}
Sentinel When
ErrAlreadyRun Run called twice on the same Flow
ErrNoClient Container / Build ran, or List / leftover Remove, with a nil client

After Run, Task.Err(), Task.ExitCode(), and Task.ID() (Engine ID for a Container step) are available. Task.Name() is the step name.

A panic in a step becomes task name: panic: … and does not take down siblings.

Worked CI graph

Kit values in one place, edges in another. Teardown is Remove, not a Down step, so List still sees the database.

func newConfig() config {
	df := kit.NewDockerFile().From("alpine:latest").Command("true")
	project := kit.NewProject("ci-db")
	project.WithService(kit.NewService("db",
		kit.NewContainer("ci-db").Image("postgres:16-alpine"),
	))
	return config{
		build: kit.NewBuild().Dockerfile(df),
		db:    kit.NewCompose(project),
		unit:  kit.NewContainer("ci-unit").Image("app:ci").Command("true"),
	}
}

func addTasks(flow *taskflow.Flow, cli *client.Client, cfg config) {
	build := flow.Build("build", cfg.build, &client.ImageBuild{Tags: []string{"app:ci"}})
	up := flow.Compose("up-db", cfg.db, &kit.Up{Detach: true, Wait: true})
	tests := flow.Subflow("tests", func(sf *taskflow.Subflow) {
		sf.Container("unit", cfg.unit, nil)
	})
	release := flow.If("release", func(context.Context) bool {
		return os.Getenv("RELEASE") == "1"
	})
	tag := flow.Task("tag", func(ctx context.Context) error {
		return cli.ImageTag(ctx, "app:ci", "app:release")
	})
	build.Precede(up)
	up.Precede(tests)
	tests.Precede(release)
	release.Then(tag)
}

flow := taskflow.New("ci", cli)
addTasks(flow, cli, newConfig())
defer flow.Remove(ctx)
err := flow.Run(ctx, &taskflow.Run{Writer: os.Stdout, Watch: watch})
listed, _ := flow.List(ctx)
_ = listed

Full program: examples/taskflow.

API

taskflow.New(name, cli) *Flow
taskflow.List(ctx, cli, flowName) ([]response.ContainerSummary, error)

Flow.Name() string
Flow.Task / Build / Container / Compose / Subflow / If / Condition
Flow.Run(ctx, *Run) error          // once
Flow.Dump(w) error                 // DOT
Flow.List(ctx) ([]response.ContainerSummary, error)
Flow.Remove(ctx) error             // Down compose + delete labeled leftovers

Task.Precede / DependsOn / Then / Else
Task.Priority / Always / Retry
Task.Name / ID / ExitCode / Err

Run.{Concurrency, Writer, Profile, Trace, Watch, Log}
ContainerRun.Logs

Event.{Task, Name, Kind, Err, Attempt, Duration}
EventKind: start | done | skip | error | retry

LabelFlow, LabelTask, LabelDepends, LabelPrecedes
ErrNoClient, ErrAlreadyRun, TaskError

Godoc: pkg.go.dev/github.com/aptd3v/containerkit/pkg/taskflow.

Documentation

Overview

Package taskflow is an optional DAG runner for containerkit.

Hand the same *kit.Container, kit.Build, and *kit.Compose values you already have to a Flow, plus ordinary Go functions. kit and client do not import this package; a program that never imports taskflow never downloads go-taskflow.

import "github.com/aptd3v/containerkit/pkg/taskflow"

Graph

New names the graph and holds an optional *client.Client. Add steps, wire edges, then call Run once. Independent steps whose predecessors are done run concurrently. Run.Concurrency is the worker pool (0 means GOMAXPROCS, raised above the number of subflows).

flow := taskflow.New("ci", cli)
build := flow.Build("build", kit.NewBuild().Dockerfile(df), &client.ImageBuild{Tags: []string{"app:ci"}})
up := flow.Compose("up-db", db, &kit.Up{Detach: true, Wait: true})
test := flow.Container("test", kit.NewContainer().Image("app:ci"), nil).Retry(2)
test.DependsOn(up.DependsOn(build))
defer flow.Remove(ctx)
err := flow.Run(ctx, &taskflow.Run{Writer: os.Stdout})

cli may be nil when every step is Task or Compose. Container and Build need a client (ErrNoClient otherwise).

Steps

Flow and Subflow share these constructors. Every one returns *Task.

  • Task(name, fn) — fn(ctx) error
  • Build(name, kit.Build, *client.ImageBuild) — Engine image build
  • Container(name, *kit.Container, *ContainerRun) — remove, create, start, wait for exit
  • Compose(name, *kit.Compose, cmd) — *kit.Up, *kit.Logs, … or func(ctx, app) error
  • Subflow(name, instantiate) — nested graph; instantiate runs when the subflow starts
  • If(name, pred) — boolean branch (Then / Else)
  • Condition(name, pred) — indexed branch (Precede order)

Container is a one-shot job. A non-zero exit fails the step. Restart policies (always, unless-stopped, on-failure) and AutoRemove are rejected: this step records the first exit, then copies logs. Long-running services belong in Compose with *kit.Up; flaky jobs use Retry. The step writes Name and containerkit.* labels onto the *kit.Container you pass.

Compose cmd is a *kit option. The struct is copied; a nil Writer becomes Run.Writer. Use a func for Events, LoadProject, or options that depend on earlier steps.

Edges

a.Precede(b) is b.DependsOn(a): A then B. Priority only orders the ready queue; it does not override edges.

Failure

A failed or skipped step skips its successors. Siblings keep running. Run joins every step error.

Always runs a step even when a predecessor failed (teardown inside the graph). Retry(n) reruns a failing step n more times. Panics and a canceled context are not retried.

If is skipped when a predecessor failed, and then neither branch runs. Condition still runs after a failed predecessor, which is what makes a retry cycle work: a non-condition node is the first entry, and the Condition sits on the back edge. For retrying one step, Retry is simpler than a cycle.

Prefer defer flow.Remove for end-of-run teardown so List can still see the stack. Use a *kit.Down step when the graph itself must bring the project down (and Always if that must happen after a failure).

Observe

Dump writes DOT. Run.Profile is flamegraph text; Run.Trace is Chrome Trace JSON. Run.Watch is step lifecycle (start, done, skip, retry, error). Run.Log is step-scoped bytes (build stream, container logs, compose progress).

Container and Compose steps stamp containerkit.flow / task / depends / precedes. List is docker ps -a for that flow label; call it before Remove. Remove Downs every *kit.Compose the graph referenced, then deletes leftover labeled containers.

The full guide is the README in this directory. A runnable CI graph is examples/taskflow.

Example
package main

import (
	"context"
	"fmt"

	"github.com/aptd3v/containerkit/pkg/taskflow"
)

func main() {
	flow := taskflow.New("demo", nil)
	hello := flow.Task("hello", func(context.Context) error {
		fmt.Println("hello")
		return nil
	})
	done := flow.Task("done", func(context.Context) error {
		fmt.Println("done")
		return nil
	})
	hello.Precede(done)
	if err := flow.Run(context.Background(), nil); err != nil {
		fmt.Println(err)
	}
}
Output:
hello
done

Index

Examples

Constants

View Source
const (
	// LabelFlow is the Docker label for the flow name.
	LabelFlow = "containerkit.flow"
	// LabelTask is the Docker label for the step name.
	LabelTask = "containerkit.task"
	// LabelDepends lists predecessor container names, sorted and comma-separated.
	LabelDepends = "containerkit.depends"
	// LabelPrecedes lists successor container names, sorted and comma-separated.
	LabelPrecedes = "containerkit.precedes"
)

Variables

View Source
var (
	// ErrNoClient is returned when a Container or Build step runs without a client.
	ErrNoClient = errors.New("docker client is nil")
	// ErrAlreadyRun is returned when Run is called a second time on the same Flow.
	ErrAlreadyRun = errors.New("already run")
)

Functions

func List

func List(ctx context.Context, cli *client.Client, flowName string) ([]response.ContainerSummary, error)

List returns containers stamped with LabelFlow equal to flowName, including stopped ones. Flow.List does the same for a flow in hand; this form works across processes.

Types

type ContainerRun

type ContainerRun struct {
	// Logs receives the container's stdout and stderr after it exits. Nil
	// means Run.Writer. Use io.Discard to silence one step.
	Logs io.Writer
}

ContainerRun is options for a Container step. Nil means defaults.

type Event

type Event struct {
	// Task is the step. Step names are not unique across subflows; compare Task.
	Task *Task
	Name string
	Kind EventKind
	// Err is set for EventError and EventRetry.
	Err error
	// Attempt is the 1-based attempt that just failed. Set for EventRetry.
	Attempt  int
	Duration time.Duration
}

Event is one step lifecycle update. Watch may be called from several goroutines.

type EventKind

type EventKind string

EventKind is a step lifecycle signal sent to Run.Watch.

const (
	EventStart EventKind = "start"
	EventDone  EventKind = "done"
	EventSkip  EventKind = "skip"
	EventError EventKind = "error"
	// EventRetry is sent when an attempt failed and another follows.
	EventRetry EventKind = "retry"
)

type Flow

type Flow struct {
	// contains filtered or unexported fields
}

Flow is a named task graph. One Run per Flow.

func New

func New(name string, cli *client.Client) *Flow

New returns a Flow. name is the graph name and the flow label value. cli may be nil when the flow has only Task and Compose steps.

func (*Flow) Build

func (f *Flow) Build(name string, b kit.Build, opt *client.ImageBuild) *Task

Build adds an image build step from a kit.Build.

func (*Flow) Compose

func (f *Flow) Compose(name string, app *kit.Compose, cmd any) *Task

Compose adds a step that runs a compose command. cmd is a *kit option (*kit.Up, *kit.Down, *kit.Logs, …) or a func(context.Context, *kit.Compose) error for Events, LoadProject, or options that depend on earlier steps. The option is copied so the caller's value is not mutated. A nil Writer on the option means Run.Writer. Prefer defer flow.Remove for end-of-run teardown so List can still see the stack; use a *kit.Down step when the graph itself must bring the project down (and Always if that must happen after a failure).

func (*Flow) Condition

func (f *Flow) Condition(name string, predict func(context.Context) uint) *Task

Condition adds an indexed branch. The returned index selects the successor in Precede order. A Condition runs even when a predecessor failed, which is what makes retry cycles work: a cycle needs a non-condition predecessor as the first entry, then a Condition on the back edge. An index with no successor fails the step instead of panicking; nothing runs after it. Use If to gate on a predecessor's success.

func (*Flow) Container

func (f *Flow) Container(name string, ctr *kit.Container, opt *ContainerRun) *Task

Container adds a one-shot container step: remove by name, create, start, wait for exit. A non-zero exit fails the step. Restart policies (always, unless-stopped, on-failure) and AutoRemove are rejected: this step records the first exit, then copies logs; use Compose with *kit.Up for a long-running service and Retry for a flaky job. The step writes to ctr: it sets Name when empty and adds the containerkit.* labels.

func (*Flow) Dump

func (f *Flow) Dump(w io.Writer) error

Dump writes the graph in DOT format.

func (*Flow) If

func (f *Flow) If(name string, pred func(context.Context) bool) *Task

If adds a boolean branch. Then successors run when pred returns true, Else successors when it returns false. A missing branch is a no-op. Unlike Condition, an If is skipped when a predecessor failed, and then neither branch runs.

func (*Flow) List

func (f *Flow) List(ctx context.Context) ([]response.ContainerSummary, error)

List returns containers stamped with this flow's name, including stopped ones.

func (*Flow) Name

func (f *Flow) Name() string

Name is the graph name passed to New.

func (*Flow) Remove

func (f *Flow) Remove(ctx context.Context) error

Remove downs every Compose project the graph referenced, then force-removes Container-step IDs and any leftover containers stamped with this flow's name.

func (*Flow) Run

func (f *Flow) Run(ctx context.Context, opt *Run) error

Run executes the graph. Nil opt uses defaults. The returned error joins every failed step's TaskError.

func (*Flow) Subflow

func (f *Flow) Subflow(name string, instantiate func(*Subflow)) *Task

Subflow adds a nested graph. instantiate runs once, when the subflow starts. Successors of the subflow are skipped when any step inside it failed.

func (*Flow) Task

func (f *Flow) Task(name string, fn func(context.Context) error) *Task

Task adds a function step.

type Priority

type Priority uint

Priority is scheduler queue order. Zero leaves go-taskflow's default.

const (
	PriorityHigh Priority = iota + 1
	PriorityNormal
	PriorityLow
)

type Run

type Run struct {
	// Concurrency is the worker count. Zero means GOMAXPROCS; the value is
	// raised above the number of known subflows.
	Concurrency uint
	// Writer receives build output, container output, and Compose output.
	// Nil means os.Stdout.
	Writer io.Writer
	// Profile receives flamegraph text when the graph finishes. Nil disables the profiler.
	Profile io.Writer
	// Trace receives Chrome Trace JSON when the graph finishes. Nil disables the tracer.
	Trace io.Writer
	// Watch is called from worker goroutines as each step starts, finishes,
	// retries, skips, or fails.
	Watch func(Event)
	// Log is called with step-scoped output (image build streams, container
	// logs, compose output). May be called from worker goroutines with
	// partial writes.
	Log func(step string, p []byte)
}

Run is options for Flow.Run. Nil means defaults.

type Subflow

type Subflow struct {
	// contains filtered or unexported fields
}

Subflow is a nested graph. It has the same step constructors as Flow.

func (*Subflow) Build

func (s *Subflow) Build(name string, b kit.Build, opt *client.ImageBuild) *Task

Build adds an image build step to the subflow.

func (*Subflow) Compose

func (s *Subflow) Compose(name string, app *kit.Compose, cmd any) *Task

Compose adds a Compose step to the subflow. See Flow.Compose.

func (*Subflow) Condition

func (s *Subflow) Condition(name string, predict func(context.Context) uint) *Task

Condition adds an indexed branch inside the subflow. See Flow.Condition.

func (*Subflow) Container

func (s *Subflow) Container(name string, ctr *kit.Container, opt *ContainerRun) *Task

Container adds a one-shot container step to the subflow. See Flow.Container.

func (*Subflow) If

func (s *Subflow) If(name string, pred func(context.Context) bool) *Task

If adds a boolean branch inside the subflow. See Flow.If.

func (*Subflow) Subflow

func (s *Subflow) Subflow(name string, instantiate func(*Subflow)) *Task

Subflow adds a nested graph inside this subflow.

func (*Subflow) Task

func (s *Subflow) Task(name string, fn func(context.Context) error) *Task

Task adds a function step to the subflow.

type Task

type Task struct {
	// contains filtered or unexported fields
}

Task is one node in a Flow or Subflow.

func (*Task) Always

func (t *Task) Always() *Task

Always runs t even when a predecessor failed or was skipped. Use it for teardown. The upstream failure still propagates: successors of t are skipped as if t had failed.

func (*Task) DependsOn

func (t *Task) DependsOn(tasks ...*Task) *Task

DependsOn makes t depend on tasks. It is the inverse of Precede.

func (*Task) Else

func (t *Task) Else(tasks ...*Task) *Task

Else adds successors that run when an If step returns false. On any other step it does nothing.

func (*Task) Err

func (t *Task) Err() error

Err is the step error after Run, if any. For a Subflow it reports only the subflow's own error; inner step errors are on the inner tasks and in the error returned by Run.

func (*Task) ExitCode

func (t *Task) ExitCode() int

ExitCode is the container wait status after a Container step runs.

func (*Task) ID

func (t *Task) ID() string

ID is the Engine container ID after a Container step runs.

func (*Task) Name

func (t *Task) Name() string

Name is the step name.

func (*Task) Precede

func (t *Task) Precede(tasks ...*Task) *Task

Precede makes tasks depend on t. For a Condition, the order is the branch index. For an If, Precede is the same as Then.

func (*Task) Priority

func (t *Task) Priority(p Priority) *Task

Priority sets scheduler queue order. Zero is ignored.

func (*Task) Retry

func (t *Task) Retry(n int) *Task

Retry reruns a failing step up to n more times before it counts as failed. Each rerun emits EventRetry. Panics and a canceled context are not retried.

func (*Task) Then

func (t *Task) Then(tasks ...*Task) *Task

Then adds successors that run when an If step returns true. On any other step it is the same as Precede.

type TaskError

type TaskError struct {
	Task string
	Err  error
}

TaskError is a named step failure. Unwrap returns Err.

func (*TaskError) Error

func (e *TaskError) Error() string

func (*TaskError) Unwrap

func (e *TaskError) Unwrap() error

Jump to

Keyboard shortcuts

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