Writing tasks
What are tasks?
Tasks are the primary way to add executable functionality to Jackadi. Each task:
- Can be triggered manually via the CLI or API*.
- Has defined inputs/outputs.
- Executes on agents.
- Can implement complex workflows (like setting up a webserver).
- Can reuse or alias functions from other plugins.
Task function structure
A task in Jackadi is a Go function that follows a flexible signature pattern:
func Task([ctx context.Context], [opts *OptionsType], [arg <type>...]) (<type>, error)
Components:
- Context Parameter (optional):
- If present, must be the first parameter.
- Allows for cancellation and timeout handling.
- Options Parameter (optional):
- If present, must be the first parameter (if no context) or second parameter (if context is present).
- Allows for passing key-value pairs.
- Must be a pointer to a struct that implements the Options interface.
- Optional Positional Arguments: Zero or more arguments for positional CLI parameters.
- Return Values:
- Any task must return two values:
(<type>, error)
. <type>
must be serializable.
- Any task must return two values:
Writing a task
- Import
github.com/jackadi-io/jackadi/sdk
:
package main
import "github.com/jackadi-io/jackadi/sdk"
- If needed, define
Options
implementingsdk.Options
:
// Define an options structure
type GreetOptions struct {
Name string
Age int
}
// Implement SetDefaults method (required by Options interface)
func (o *GreetOptions) SetDefaults() {
o.Name = "Guest"
o.Age = 30
}
- Create a task function:
func GreetTask(ctx context.Context, options *GreetOptions, greeting string) (string, error) {
return fmt.Sprintf("%s, %s! You are %d years old.", greeting, options.Name, options.Age), nil
}
- Register a task and serve:
plugin := sdk.New("my-plugin")
// Basic registration
plugin.MustRegisterTask("task-name", MyTaskFunction)
// With additional metadata
plugin.MustRegisterTask("advanced-task", MyAdvancedFunction).
WithSummary("Short summary").
WithLockMode(sdk.WriteLock).
WithDescription("Longer description of what the task does").
WithArg("argName", "argType", "defaultValue").
WithFlags(sdk.Deprecated) // Optional flags like Deprecated or NotImplemented
sdk.MustServe(plugin)
Full example
package main
import (
"context"
"fmt"
"github.com/jackadi-io/jackadi/sdk"
)
// Define an options structure
type GreetOptions struct {
Name string
Age int
}
// Implement SetDefaults method (required by Options interface)
func (o *GreetOptions) SetDefaults() {
o.Name = "Guest"
o.Age = 30
}
// Define the task function
func GreetTask(ctx context.Context, options *GreetOptions, greeting string) (string, error) {
return fmt.Sprintf("%s, %s! You are %d years old.", greeting, options.Name, options.Age), nil
}
func main() {
plugin := sdk.New("my-plugin")
// Basic registration
plugin.MustRegisterTask("task-name", MyTaskFunction)
// With additional metadata
plugin.MustRegisterTask("advanced-task", MyAdvancedFunction).
WithSummary("Short summary").
WithLockMode(sdk.WriteLock).
WithDescription("Longer description of what the task does").
WithArg("argName", "argType", "defaultValue").
WithFlags(sdk.Deprecated) // Optional flags like Deprecated or NotImplemented
sdk.MustServe(plugin)
}
Reference
Options interface
Task options are defined as a struct that implements the Options
interface:
type Options interface {
SetDefaults()
}
The options struct:
- Provides a structured way to pass parameters to tasks.
- Fields are automatically mapped from CLI flags (e.g.,
--name="John"
). - Must include a
SetDefaults()
method to set default values. - Supports nested structures and various data types.
Supported arguments, options and output types
- Basic types: string, int, bool, float, etc..
- Complex types: structs, maps, slices, arrays.
- Pointers to any of the above types.
- Custom types with proper JSON marshaling/unmarshaling.
Reusing functions from other plugins
Import and wrap functions from other plugins:
import "github.com/other-org/cert-plugin/tasks"
func SetupSSL(ctx context.Context, options *SSLOptions) (Result, error) {
// Convert options and call external function
certOptions := &tasks.CertOptions{
Domain: options.Domain,
Email: options.Email,
}
return tasks.GenerateCert(ctx, certOptions)
}
func main() {
// ...
// This task reuse a task from an another plugin
plugin.MustRegisterTask("ssl-setup", SetupSSL)
// You can also export the task from an another plugin directly
plugin.MustRegisterTask("cert-alias", tasks.GenerateCert)
// ...
}
Task metadata methods
When registering tasks, you can use several fluent methods to configure task metadata and behavior:
plugin.MustRegisterTask("my-task", MyTaskFunction).
WithSummary("Short description of the task").
WithDescription("Longer description explaining what the task does and how to use it").
WithArg("filename", "string", "config.json").
WithArg("port", "int", "8080").
WithFlags(sdk.Deprecated, sdk.NotImplemented).
WithLockMode(sdk.WriteLock)
WithSummary
Command: WithSummary(text string)
- Sets a short description that appears in task listings.
- Should be concise and descriptive.
WithDescription
Command: WithDescription(text string)
- Sets a longer description with detailed usage information.
- Can include examples and detailed explanations.
WithArg
Command: WithArg(name, argType, example string)
- Documents positional arguments for the task.
name
: argument name for documentation.argType
: type description (e.g., “string”, “int”, “[]string”).example
: example value to show users.
WithFlags
Command: WithFlags(flags ...Flag)
- Adds metadata flags to the task.
- Available flags:
sdk.Deprecated
,sdk.NotImplemented
. - Multiple flags can be specified.
WithLockMode
Command: WithLockMode(lockMode LockMode)
- Sets the default lock mode (see next section).
Lock modes
When registering tasks, you can specify a default lock mode that controls how tasks execute concurrently:
plugin.MustRegisterTask("get-status", GetStatusTask).
WithLockMode(sdk.NoLock)
sdk.NoLock
: Default, useful for read-only operations.sdk.WriteLock
: For tasks that modify system state but can safely run alongside read-only tasks (single writer pattern).sdk.ExclusiveLock
: To be used carefully: usually for jackadi management tasks like plugin sync, it blocks every requests including specs refresh.
Users can always override your default lock mode via CLI:
# Use plugin's default lock mode
jack run agent1 my-plugin:update-config
# Override with exclusive lock
jack run --lock-mode exclusive agent1 my-plugin:update-config
Best practices
- Error Handling: Return meaningful error messages that help diagnose issues
- Context Awareness: Honor context cancellation for graceful termination
- Documentation: Use summary and description when registering tasks
- Lock Modes: Specify appropriate default lock modes for your tasks