Documentation
¶
Overview ¶
Package client provides job client operations for NATS JetStream.
Index ¶
- Constants
- Variables
- func ComputeAgentState(events []job.TimelineEvent) string
- type AgentKey
- type AgentKeyStore
- type Client
- func (c *Client) CheckDrainFlag(ctx context.Context, machineID string) bool
- func (c *Client) ConsumeJobs(ctx context.Context, streamName, consumerName string, ...) error
- func (c *Client) CreateJob(ctx context.Context, operationData map[string]interface{}, ...) (*CreateJobResult, error)
- func (c *Client) CreateOrUpdateConsumer(ctx context.Context, streamName string, ...) error
- func (c *Client) DeleteDrainFlag(ctx context.Context, machineID string) error
- func (c *Client) DeleteJob(ctx context.Context, jobID string) error
- func (c *Client) GetAgent(ctx context.Context, target string) (*job.AgentInfo, error)
- func (c *Client) GetAgentTimeline(ctx context.Context, hostname string) ([]job.TimelineEvent, error)
- func (c *Client) GetJobData(ctx context.Context, jobKey string) ([]byte, error)
- func (c *Client) GetJobStatus(ctx context.Context, jobID string) (*job.QueuedJob, error)
- func (c *Client) GetQueueSummary(ctx context.Context) (*job.QueueStats, error)
- func (c *Client) HasJobResponse(ctx context.Context, jobID string, hostname string) (bool, error)
- func (c *Client) ListAgents(ctx context.Context) ([]job.AgentInfo, error)
- func (c *Client) ListJobs(ctx context.Context, statusFilter string, limit int, offset int) (*ListJobsResult, error)
- func (c *Client) Modify(ctx context.Context, target string, category string, ...) (string, *job.Response, error)
- func (c *Client) ModifyBroadcast(ctx context.Context, target string, category string, ...) (string, map[string]*job.Response, error)
- func (c *Client) Query(ctx context.Context, target string, category string, ...) (string, *job.Response, error)
- func (c *Client) QueryBroadcast(ctx context.Context, target string, category string, ...) (string, map[string]*job.Response, error)
- func (c *Client) RetryJob(ctx context.Context, jobID string, targetHostname string) (*CreateJobResult, error)
- func (c *Client) SetAgentKeyStore(store AgentKeyStore)
- func (c *Client) SetDrainFlag(ctx context.Context, machineID string) error
- func (c *Client) SetMachineID(machineID string)
- func (c *Client) SetMeterProvider(mp *sdkmetric.MeterProvider)
- func (c *Client) SetPKISigner(signer PKISigner)
- func (c *Client) WriteAgentTimelineEvent(ctx context.Context, hostname, event, message string) error
- func (c *Client) WriteJobResponse(_ context.Context, jobID, hostname string, responseData []byte, status string, ...) error
- func (c *Client) WriteStatusEvent(_ context.Context, jobID, event, hostname string, data map[string]interface{}) error
- type CreateJobResult
- type JobClient
- type ListJobsResult
- type NATSClient
- type Options
- type PKISigner
Constants ¶
const ( // DefaultPageSize is the default number of jobs per page. DefaultPageSize = 10 // MaxPageSize is the maximum allowed page size. MaxPageSize = 100 )
Variables ¶
var ( // ErrResponseNotSigned means the response carried no signed envelope. // Expected from an agent that has not been upgraded; never accepted // while enforcing. ErrResponseNotSigned = errors.New("response is not a signed envelope") // ErrResponseKeyUnknown means no key is stored for the machine ID the // response claims. Expected during a rollout, before that agent // re-enrolls. ErrResponseKeyUnknown = errors.New("no stored key for responding agent") // never mistaken for "no stored key". ErrResponseStoreUnavailable = errors.New("agent key store unavailable") // ErrResponseSignatureInvalid means a key that is not the stored key // for that agent signed the response. ErrResponseSignatureInvalid = errors.New("invalid agent signature on response") // ErrResponseHostnameMismatch means the signature verified, but the // response claims a hostname the signing agent did not enroll under — // an accepted agent answering for a host that is not its own. ErrResponseHostnameMismatch = errors.New( "response hostname does not match the enrolled hostname", ) )
The causes of a rejected response stay distinct so an operator can tell an agent that has not re-enrolled yet from one whose messages are being forged, and both from a store that cannot be read. None of them ever means "treat as verified".
Functions ¶
func ComputeAgentState ¶
func ComputeAgentState( events []job.TimelineEvent, ) string
ComputeAgentState returns the current state from timeline events.
Types ¶
type AgentKey ¶
type AgentKey struct {
// MachineID is the permanent host identifier the record is keyed by.
MachineID string
// Hostname is the hostname recorded at acceptance. A response claiming
// a different hostname is not this agent's to answer for.
Hostname string
// PublicKey is the key recorded at acceptance.
PublicKey ed25519.PublicKey
// SupersededKey is the key a rotation replaced, kept acceptable until
// SupersededUntil so a message signed moments before the rotation is not
// rejected for arriving late. Absent outside a rotation window.
SupersededKey ed25519.PublicKey
// SupersededUntil is the instant the superseded key stops being accepted.
SupersededUntil time.Time
}
AgentKey is what the controller holds for an accepted agent: the key its messages are verified against, and the hostname it enrolled under.
func (*AgentKey) AcceptableKeys ¶
AcceptableKeys returns the keys a message from this agent may be signed with: the current one, and the key a rotation replaced while it is still inside its grace period.
An expired superseded key is simply not returned, so a signature matching only it fails as a mismatch rather than as its own outcome. That is deliberate: after the window, that key is no longer this agent's.
type AgentKeyStore ¶
type AgentKeyStore interface {
LookupAgentKey(
ctx context.Context,
machineID string,
) (*AgentKey, error)
}
AgentKeyStore looks up an accepted agent's key by machine ID. Nil when the controller is not enforcing response verification, which leaves behaviour exactly as it was before verification existed.
Implemented on the controller by the enrollment watcher, which is the only thing allowed to write the underlying records.
type Client ¶
type Client struct {
// JSONMarshalFn is the function used to marshal JSON. Defaults to json.Marshal.
// Exported for testing.
JSONMarshalFn func(v any) ([]byte, error)
// contains filtered or unexported fields
}
Client provides methods for publishing job requests and retrieving responses.
func (*Client) CheckDrainFlag ¶
CheckDrainFlag returns true if the drain flag exists for the given identifier. Callers pass a machine ID (hex string, no sanitization needed).
func (*Client) ConsumeJobs ¶
func (c *Client) ConsumeJobs( ctx context.Context, streamName, consumerName string, handler func(jetstream.Msg) error, opts *natsclient.ConsumeOptions, ) error
ConsumeJobs sets up message consumption for job processing.
func (*Client) CreateJob ¶
func (c *Client) CreateJob( ctx context.Context, operationData map[string]interface{}, targetHostname string, ) (*CreateJobResult, error)
CreateJob creates a new job from operation data and stores it in the KV bucket.
func (*Client) CreateOrUpdateConsumer ¶
func (c *Client) CreateOrUpdateConsumer( ctx context.Context, streamName string, consumerConfig jetstream.ConsumerConfig, ) error
CreateOrUpdateConsumer creates or updates a JetStream consumer.
func (*Client) DeleteDrainFlag ¶
DeleteDrainFlag removes the drain flag for an agent from the state KV bucket. The agent detects this on heartbeat and resumes accepting jobs. Callers pass a machine ID (hex string, no sanitization needed).
func (*Client) GetAgent ¶
GetAgent reads a single agent's registration from the KV registry. It tries a direct machine ID lookup first, then falls back to scanning all agents by hostname.
func (*Client) GetAgentTimeline ¶
func (c *Client) GetAgentTimeline( ctx context.Context, hostname string, ) ([]job.TimelineEvent, error)
GetAgentTimeline returns sorted timeline events for a hostname.
func (*Client) GetJobData ¶
GetJobData retrieves raw job data from the KV store.
func (*Client) GetJobStatus ¶
GetJobStatus returns information about a specific job.
func (*Client) GetQueueSummary ¶
GetQueueSummary returns job queue statistics derived from KV key names only — no entry reads.
func (*Client) HasJobResponse ¶
func (c *Client) HasJobResponse( ctx context.Context, jobID string, hostname string, ) (bool, error)
HasJobResponse reports whether this agent has already written a response for the given job. A redelivered message must not re-execute an operation this agent has already answered, so the handler checks this before running the operation. The check is scoped to hostname so broadcast jobs, where every targeted agent responds independently, remain unaffected.
func (*Client) ListAgents ¶
ListAgents reads the agent registry KV bucket and returns all registered agents. Agents register via heartbeat, so only live agents appear.
func (*Client) ListJobs ¶
func (c *Client) ListJobs( ctx context.Context, statusFilter string, limit int, offset int, ) (*ListJobsResult, error)
ListJobs returns jobs filtered by status with server-side pagination. Uses a two-pass approach: Pass 1 derives status from key names only (fast), Pass 2 fetches full details for the paginated page only. Jobs are returned newest-first (reverse insertion order).
func (*Client) Modify ¶
func (c *Client) Modify( ctx context.Context, target string, category string, operation job.OperationType, data any, ) (string, *job.Response, error)
Modify publishes a modify job to a single target and waits for the response.
func (*Client) ModifyBroadcast ¶
func (c *Client) ModifyBroadcast( ctx context.Context, target string, category string, operation job.OperationType, data any, ) (string, map[string]*job.Response, error)
ModifyBroadcast publishes a modify job to a broadcast target and collects all responses.
func (*Client) Query ¶
func (c *Client) Query( ctx context.Context, target string, category string, operation job.OperationType, data any, ) (string, *job.Response, error)
Query publishes a query job to a single target and waits for the response.
func (*Client) QueryBroadcast ¶
func (c *Client) QueryBroadcast( ctx context.Context, target string, category string, operation job.OperationType, data any, ) (string, map[string]*job.Response, error)
QueryBroadcast publishes a query job to a broadcast target and collects all responses.
func (*Client) RetryJob ¶
func (c *Client) RetryJob( ctx context.Context, jobID string, targetHostname string, ) (*CreateJobResult, error)
RetryJob creates a new job using the same operation data as an existing job. The original job is preserved. A "retried" status event is written to the original job's timeline linking to the new job.
func (*Client) SetAgentKeyStore ¶
func (c *Client) SetAgentKeyStore( store AgentKeyStore, )
SetAgentKeyStore wires the store used to verify agent responses. See the JobClient interface for why this is separate from construction. Passing nil leaves responses unverified.
func (*Client) SetDrainFlag ¶
SetDrainFlag writes the drain flag for an agent in the state KV bucket. The agent detects this flag on heartbeat and stops accepting jobs. Callers pass a machine ID (hex string, no sanitization needed).
func (*Client) SetMachineID ¶
SetMachineID records the identity stamped on payloads this client signs.
func (*Client) SetMeterProvider ¶
func (c *Client) SetMeterProvider( mp *sdkmetric.MeterProvider, )
SetMeterProvider creates OTEL instruments for job metrics.
func (*Client) SetPKISigner ¶
SetPKISigner wires the signer used to sign outgoing job and response payloads and to verify incoming ones. See the JobClient interface for why this is separate from construction. Passing nil disables signing and verification.
func (*Client) WriteAgentTimelineEvent ¶
func (c *Client) WriteAgentTimelineEvent( ctx context.Context, hostname, event, message string, ) error
WriteAgentTimelineEvent writes an append-only timeline event for an agent state transition.
type CreateJobResult ¶
type CreateJobResult struct {
JobID string `json:"job_id"`
Status string `json:"status"`
Revision uint64 `json:"revision"`
Timestamp string `json:"timestamp"`
}
CreateJobResult represents the result of creating a job.
type JobClient ¶
type JobClient interface {
// Generic dispatch operations — used by API handlers to submit jobs
// without importing typed wrapper methods.
Query(
ctx context.Context,
target string,
category string,
operation job.OperationType,
data any,
) (string, *job.Response, error)
QueryBroadcast(
ctx context.Context,
target string,
category string,
operation job.OperationType,
data any,
) (string, map[string]*job.Response, error)
Modify(
ctx context.Context,
target string,
category string,
operation job.OperationType,
data any,
) (string, *job.Response, error)
ModifyBroadcast(
ctx context.Context,
target string,
category string,
operation job.OperationType,
data any,
) (string, map[string]*job.Response, error)
// Job queue management operations
GetQueueSummary(
ctx context.Context,
) (*job.QueueStats, error)
GetJobStatus(
ctx context.Context,
jobID string,
) (*job.QueuedJob, error)
ListJobs(
ctx context.Context,
statusFilter string,
limit int,
offset int,
) (*ListJobsResult, error)
// Agent discovery
ListAgents(
ctx context.Context,
) ([]job.AgentInfo, error)
GetAgent(
ctx context.Context,
hostname string,
) (*job.AgentInfo, error)
// Agent timeline
WriteAgentTimelineEvent(
ctx context.Context,
hostname, event, message string,
) error
GetAgentTimeline(
ctx context.Context,
hostname string,
) ([]job.TimelineEvent, error)
// Agent drain flag
CheckDrainFlag(
ctx context.Context,
hostname string,
) bool
SetDrainFlag(
ctx context.Context,
hostname string,
) error
DeleteDrainFlag(
ctx context.Context,
hostname string,
) error
// Job deletion
DeleteJob(
ctx context.Context,
jobID string,
) error
// Job retry
RetryJob(
ctx context.Context,
jobID string,
targetHostname string,
) (*CreateJobResult, error)
// Agent operations - used by agents for processing
// HasJobResponse reports whether this agent has already recorded a
// response for the given job. Used to detect a redelivered message
// for a job this agent has already executed, so the operation is not
// run a second time.
HasJobResponse(
ctx context.Context,
jobID string,
hostname string,
) (bool, error)
WriteStatusEvent(
ctx context.Context,
jobID string,
event string,
hostname string,
data map[string]interface{},
) error
WriteJobResponse(
ctx context.Context,
jobID string,
hostname string,
responseData []byte,
status string,
errorMsg string,
changed *bool,
) error
ConsumeJobs(
ctx context.Context,
streamName string,
consumerName string,
handler func(jetstream.Msg) error,
opts *natsclient.ConsumeOptions,
) error
GetJobData(
ctx context.Context,
jobKey string,
) ([]byte, error)
CreateOrUpdateConsumer(
ctx context.Context,
streamName string,
consumerConfig jetstream.ConsumerConfig,
) error
// SetPKISigner wires the signer used to sign outgoing job and response
// payloads and to verify incoming ones. The keypair a signer needs is
// not always available at client construction time, so this is called
// once one is: on the controller when the enrollment watcher loads the
// controller keypair, and on the agent when PKI enrollment loads the
// agent keypair. Passing nil disables signing and verification.
SetPKISigner(
signer PKISigner,
)
// SetAgentKeyStore wires the store used to verify agent responses. Like
// the signer, it is not available at construction: on the controller it
// arrives with the enrollment watcher. Passing nil leaves responses
// unverified, which is the behaviour before this feature.
SetAgentKeyStore(
store AgentKeyStore,
)
// SetMachineID records the identity stamped on payloads this client
// signs, so the verifier knows which stored key to check them against.
// Set on the agent once its identity is resolved; empty on the
// controller, whose payloads agents verify against the controller key.
SetMachineID(
machineID string,
)
}
JobClient defines the interface for interacting with the jobs system.
type ListJobsResult ¶
ListJobsResult represents the result of listing jobs with pagination.
type NATSClient ¶
type NATSClient interface {
Publish(
ctx context.Context,
subject string,
data []byte,
) error
GetStreamInfo(
ctx context.Context,
streamName string,
) (*jetstream.StreamInfo, error)
KVPut(
bucket string,
key string,
value []byte,
) error
ConsumeMessages(
ctx context.Context,
streamName string,
consumerName string,
handler natsclient.JetStreamMessageHandler,
opts *natsclient.ConsumeOptions,
) error
CreateOrUpdateConsumerWithConfig(
ctx context.Context,
streamName string,
consumerConfig jetstream.ConsumerConfig,
) error
}
NATSClient defines the NATS operations needed by the job client.
type Options ¶
type Options struct {
// Timeout for waiting for job responses (default: 30s)
Timeout time.Duration
// KVBucket for job storage (required)
KVBucket jetstream.KeyValue
// RegistryKV is the KV bucket for agent registry (optional).
RegistryKV jetstream.KeyValue
// FactsKV is the KV bucket for agent facts (optional).
FactsKV jetstream.KeyValue
// StateKV is the KV bucket for persistent agent state (drain flags, timeline).
StateKV jetstream.KeyValue
// StreamName is the JetStream stream name (used to derive DLQ name).
StreamName string
// PKISigner signs job payloads when PKI is enabled. Nil when disabled.
PKISigner PKISigner
// TargetResolver resolves hostname targets to machine IDs for NATS
// subject routing. When nil, targets are used as-is.
TargetResolver func(string) string
}
Options configures the jobs client.
type PKISigner ¶
type PKISigner interface {
// Sign signs the given data with the signer's private key.
Sign(data []byte) []byte
// Fingerprint returns the SHA256 fingerprint of the signer's public key.
Fingerprint() string
// ControllerPublicKey returns the controller's public key for verifying
// controller-signed messages. Returns nil if not set.
ControllerPublicKey() ed25519.PublicKey
}
PKISigner signs and verifies payloads. Nil when PKI is disabled.