pkg

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 12, 2026 License: BSD-2-Clause Imports: 33 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrConfigNotFound = stderrors.New("config not found")

ErrConfigNotFound is returned by ConfigResolver.Resolve when no Config in the store has a matching Spec.Assignee.

Functions

func DefaultCRDClientBuilder

func DefaultCRDClientBuilder(c *rest.Config) (apiextensionsclient.Interface, error)

DefaultCRDClientBuilder wraps apiextensionsclient.NewForConfig to satisfy CRDClientBuilder.

func EffectiveTaskTypes

func EffectiveTaskTypes(taskType string, taskTypes []string) []string

EffectiveTaskTypes computes the union of a singular taskType and a taskTypes list. The singular taskType is first in the result when non-empty. Duplicates are removed while preserving order. The result is the set an agent accepts.

func IsJobFailed added in v0.4.7

func IsJobFailed(job *batchv1.Job) bool

IsJobFailed reports whether the Job carries a true JobFailed condition.

Conditions, not the pod counters, are the source of truth: an activeDeadlineSeconds kill sets this condition while leaving .status.failed and .status.active at zero. Any caller deciding whether a Job is still running must use this (see spawner.IsJobActive) — reading the counters alone reports a deadline-killed Job as active forever.

func IsJobSucceeded added in v0.4.7

func IsJobSucceeded(job *batchv1.Job) bool

IsJobSucceeded reports whether the Job carries a true JobComplete condition.

func NewResourceEventHandlerConfig

func NewResourceEventHandlerConfig(
	ctx context.Context,
	handler EventHandlerConfig,
) cache.ResourceEventHandler

NewResourceEventHandlerConfig adapts an EventHandlerConfig to the cache.ResourceEventHandler the informer expects.

func TaskTypeInSet

func TaskTypeInSet(taskType string, effectiveTypes []string) bool

TaskTypeInSet reports whether taskType is in the effectiveTypes set. Empty taskType never matches — strict semantics, no bypass for legacy tasks.

Types

type AgentConfiguration

type AgentConfiguration struct {
	// Assignee is the task frontmatter assignee value that routes to this agent.
	Assignee string
	// TaskType is the singular task_type value from ConfigSpec.TaskType.
	// Deprecated in favour of TaskTypes; stays functional.
	TaskType string
	// TaskTypes is the list of task_type values from ConfigSpec.TaskTypes.
	// Nil when the CRD only sets the singular TaskType field.
	TaskTypes []string
	// Image is the container image base name (without tag). Tag is appended at runtime from branch.
	Image string
	// Env holds per-agent environment variables (e.g. API keys, config).
	// These are merged with shared env vars (TASK_CONTENT, TASK_ID, KAFKA_BROKERS, BRANCH)
	// when spawning the K8s Job.
	Env map[string]string
	// VolumeClaim is the name of an existing PVC to mount into the container.
	// Empty means no volume mount.
	VolumeClaim string
	// VolumeMountPath is the container path where the PVC is mounted.
	// Required when VolumeClaim is set.
	VolumeMountPath string
	// SecretName is the name of a K8s Secret to mount as envFrom on the container.
	// Empty means no secret is mounted.
	SecretName string
	// Resources declares optional resource requests and limits for the agent container.
	// Nil means "do not set, keep the k8s builder default".
	Resources *agentv1.AgentResources
	// MaxConcurrentJobs caps how many Jobs this agent may run at once.
	// 0 means unlimited.
	MaxConcurrentJobs int
	// PriorityClassName is the Kubernetes PriorityClass name to stamp onto spawned Job PodTemplates.
	PriorityClassName string
	// ImagePullSecret is the name of the K8s Secret used for image pulls.
	// Empty uses the cluster default (typically "docker").
	ImagePullSecret string
	// Trigger declares the per-agent phase and status conditions under which the executor spawns a Job.
	Trigger *agentv1.Trigger
	// ZombieJobTimeoutSeconds mirrors ConfigSpec.ZombieJobTimeoutSeconds. The
	// spawner stamps this value onto Job.Spec.ActiveDeadlineSeconds; the sweeper
	// uses it as the elapsed-time threshold. nil means "use the default
	// DefaultZombieJobTimeoutSeconds from the CRD types package".
	ZombieJobTimeoutSeconds *int32
}

AgentConfiguration defines the container image and environment for one agent type.

func (AgentConfiguration) EffectiveZombieJobTimeoutSeconds

func (a AgentConfiguration) EffectiveZombieJobTimeoutSeconds() int32

EffectiveZombieJobTimeoutSeconds returns the effective deadline in seconds: the configured value when non-nil, else agentv1.DefaultZombieJobTimeoutSeconds.

type AgentConfigurations

type AgentConfigurations []AgentConfiguration

AgentConfigurations is a list of agent configurations.

func (AgentConfigurations) FindByAssignee

func (a AgentConfigurations) FindByAssignee(assignee string) (AgentConfiguration, bool)

FindByAssignee returns the configuration for the given assignee name. Returns the config and true if found, zero value and false otherwise.

func (AgentConfigurations) TaggedConfigurations

func (a AgentConfigurations) TaggedConfigurations(branch string) AgentConfigurations

TaggedConfigurations returns a new AgentConfigurations with the branch appended to each untagged image as a tag (e.g. "registry/image" + ":" + "dev" → "registry/image:dev"). Images that already carry a tag or digest are left as-is (see appendBranchTag), so semver-pinned images are not double-tagged.

type CRDClientBuilder

type CRDClientBuilder func(*rest.Config) (apiextensionsclient.Interface, error)

CRDClientBuilder constructs the apiextensions clientset from a rest.Config. Injected to allow fake clientsets in tests.

type ConfigResolver

type ConfigResolver interface {
	Resolve(ctx context.Context, assignee string) (AgentConfiguration, error)
}

ConfigResolver looks up the AgentConfiguration for an assignee by iterating the in-memory Config store and converting the matching entry.

func NewConfigResolver

func NewConfigResolver(
	provider k8s.Provider[agentv1.Config],
	branch base.Branch,
) ConfigResolver

NewConfigResolver returns a ConfigResolver backed by the given typed store. The branch is captured here and appended as the image tag at resolution time.

type CronScheduler

type CronScheduler interface {
	Run(ctx context.Context) error
}

CronScheduler runs a cron expression on a schedule.

type EventHandlerConfig

type EventHandlerConfig k8s.EventHandler[agentv1.Config]

EventHandlerConfig is the typed in-memory event handler / store for Config resources. Backed by github.com/bborbe/k8s generics.

func NewEventHandlerConfig

func NewEventHandlerConfig() EventHandlerConfig

NewEventHandlerConfig returns an empty EventHandlerConfig.

type JobWatcher

type JobWatcher interface {
	// Run starts the Job and Pod informers and blocks until ctx is cancelled.
	Run(ctx context.Context) error
	// HandleJob processes a single Job (invoked by the informer event handlers
	// and by unit tests directly, avoiding the need for a fake informer).
	HandleJob(ctx context.Context, job *batchv1.Job)
	// HandlePod processes a single Pod (invoked by the Pod informer event handler
	// and by unit tests directly).
	HandlePod(ctx context.Context, pod *corev1.Pod)
	// PodLister returns the Pod lister backed by the shared informer cache, for
	// use by the deadline sweeper. The returned lister is safe for concurrent
	// read access.
	PodLister() corev1listers.PodLister
}

JobWatcher watches batch/v1 Jobs and their Pods in the executor's namespace and publishes synthetic failure results for terminal-state objects that belong to spawned tasks.

func NewJobWatcher

func NewJobWatcher(
	kubeClient kubernetes.Interface,
	namespace libk8s.Namespace,
	taskStore *TaskStore,
	publisher ResultPublisher,
) JobWatcher

NewJobWatcher creates a JobWatcher.

type K8sConnector

type K8sConnector interface {
	SetupCustomResourceDefinition(ctx context.Context) error
	Listen(
		ctx context.Context,
		namespace libk8s.Namespace,
		handler cache.ResourceEventHandler,
	) error
}

K8sConnector installs the Config CRD and starts an informer.

func NewK8sConnector

func NewK8sConnector(config *rest.Config, crdBuilder CRDClientBuilder) K8sConnector

NewK8sConnector returns a K8sConnector using the given rest config and builder. Production callers pass DefaultCRDClientBuilder as the builder.

type ResultPublisher

type ResultPublisher interface {
	// PublishSpawnNotification publishes current_job, job_started_at, and
	// spawn_notification without touching any other frontmatter keys.
	PublishSpawnNotification(ctx context.Context, task lib.Task, jobName string) error
	// PublishFailure publishes a zombie failure: clears current_job and atomically
	// bumps trigger_count by 1 via a paired IncrementFrontmatterCommand. Leaves
	// phase, status, and assignee untouched so the existing trigger_count retry
	// cap (applyTriggerCap in task/controller/pkg/result/result_writer.go) handles
	// eventual operator-inbox escalation. Idempotent per current_job via a TTL'd
	// LRU; concurrent classifications for the same job emit one event.
	PublishFailure(ctx context.Context, task lib.Task, jobName string, reason string) error
	// PublishIncrementTriggerCount sends an IncrementFrontmatterCommand that atomically
	// increments trigger_count by 1. Must complete before SpawnJob is called.
	PublishIncrementTriggerCount(ctx context.Context, task lib.Task) error
	// PublishTypeMismatchFailure publishes a synthetic failure when the task's task_type
	// is not in the agent's effective type set. Clears assignee and current_job so the
	// task surfaces in the operator inbox via assignee=="" filter. Does not bump
	// trigger_count or retry_count.
	PublishTypeMismatchFailure(ctx context.Context, task lib.Task, reason string) error
	// PublishRaw publishes a raw payload for testing error paths.
	PublishRaw(ctx context.Context, operation base.CommandOperation, payload interface{}) error
}

ResultPublisher publishes agent-task-v1-request commands to Kafka so the controller writes them to the vault task file.

func NewResultPublisher

func NewResultPublisher(
	syncProducer libkafka.SyncProducer,
	topicPrefix base.TopicPrefix,
	currentDateTime libtime.CurrentDateTimeGetter,
) ResultPublisher

NewResultPublisher creates a ResultPublisher.

type TaskStore

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

TaskStore is a thread-safe map from TaskIdentifier to Task. It is populated when the executor spawns a Job and consumed by the job informer when publishing synthetic failure results.

func NewTaskStore

func NewTaskStore() *TaskStore

NewTaskStore creates an empty TaskStore.

func (*TaskStore) Delete

func (s *TaskStore) Delete(id lib.TaskIdentifier)

Delete removes the task for the given identifier (called on job termination).

func (*TaskStore) Load

func (s *TaskStore) Load(id lib.TaskIdentifier) (lib.Task, bool)

Load retrieves the task for the given identifier.

func (*TaskStore) Snapshot

func (s *TaskStore) Snapshot() map[lib.TaskIdentifier]lib.Task

Snapshot returns a shallow copy of the current task map for read-only iteration. Safe to call concurrently with Store/Delete; the returned map is owned by the caller.

func (*TaskStore) Store

func (s *TaskStore) Store(id lib.TaskIdentifier, task lib.Task)

Store saves the task for the given identifier (called on job spawn).

type ZombieReason

type ZombieReason string

ZombieReason is the closed set of machine-readable reason strings emitted in the ## Failure body section. Operators grep on these values to triage. Adding a new value requires updating this list and the documentation; renaming or removing a value is a breaking change to the on-disk task body contract.

const (
	ZombieReasonImagePullBackOff  ZombieReason = "image_pull_backoff"
	ZombieReasonPodEvicted        ZombieReason = "pod_evicted"
	ZombieReasonDeadlineExceeded  ZombieReason = "deadline_exceeded"
	ZombieReasonPodNotScheduled   ZombieReason = "pod_not_scheduled"
	ZombieReasonPodCrashNoStdout  ZombieReason = "pod_crash_no_stdout"
	ZombieReasonExecutorWatchLost ZombieReason = "executor_watch_lost"
	ZombieReasonTypeMismatch      ZombieReason = "type_mismatch"
)

func JobFailureReason

func JobFailureReason(job *batchv1.Job) ZombieReason

JobFailureReason maps a failed Job's conditions to a ZombieReason. Returns ZombieReasonDeadlineExceeded when any Failed condition has Reason "DeadlineExceeded" or "BackoffLimitExceeded" (kubelet killed the pod for running past activeDeadlineSeconds or exhausting BackoffLimit). Returns ZombieReasonPodCrashNoStdout for any other Failed condition (the pod terminated non-zero and no AgentResult was observed; the Job-condition informer only fires AFTER terminal state, so absence of an AgentResult is implicit at this point).

func (ZombieReason) String

func (r ZombieReason) String() string

String returns the reason as a string (for use with PublishFailure).

type ZombieSweeper

type ZombieSweeper interface {
	// Run blocks until ctx is cancelled. Each tick (interval sourced from the
	// first non-nil ConfigSpec.ZombieSweeperIntervalSeconds across the resolver's
	// configs, else DefaultZombieSweeperIntervalSeconds) it calls SweepOnce.
	Run(ctx context.Context) error
	// SweepOnce performs a single sweep pass. Exposed for unit tests so they
	// do not have to manage tickers. Returns an error only on context
	// cancellation paths; per-task classification errors are logged.
	SweepOnce(ctx context.Context) error
}

ZombieSweeper is a background goroutine that periodically classifies stuck tasks as zombies and emits failure events. It is the safety net for the informer-driven paths in JobWatcher (which handle the cases k8s notifies us about). The sweeper handles: pods unschedulable beyond a grace window, executor restart losing watch on a Job, and any deadline path the informer misses (Job-condition deferred indefinitely, informer cache drift).

func NewZombieSweeper

func NewZombieSweeper(
	jobWatcher JobWatcher,
	namespace libk8s.Namespace,
	taskStore *TaskStore,
	publisher ResultPublisher,
	configProvider EventHandlerConfig,
	currentDateTime libtime.CurrentDateTimeGetter,
) ZombieSweeper

NewZombieSweeper creates a ZombieSweeper. The JobWatcher is held by reference (not its lister) because the lister is only populated after JobWatcher.Run completes its cache sync; service.Run starts all components concurrently, so extracting the lister at wiring time would capture nil. SweepOnce resolves the lister lazily on every tick and skips the tick if it is not yet ready.

Directories

Path Synopsis
mocks
Code generated by counterfeiter.
Code generated by counterfeiter.

Jump to

Keyboard shortcuts

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