Documentation
¶
Index ¶
- Constants
- Variables
- type Artifact
- type ArtifactStore
- type Deliverer
- type DelivererOptions
- type DeliveryResult
- type DeliverySpec
- type Duration
- type FacetSpec
- type Options
- type PauseFlags
- type QuerySpec
- type ReconcileSpec
- type ReportSpec
- type Reporter
- type Run
- type RunFilter
- type Runner
- type RunnerOptions
- type Schedule
- type Service
- func (s *Service) Delete(ctx context.Context, name string) error
- func (s *Service) LoadScheduler(ctx context.Context) error
- func (s *Service) Pause(ctx context.Context, name string, _ PauseFlags) (Schedule, error)
- func (s *Service) RegisterClicky()
- func (s *Service) Resume(ctx context.Context, name string, _ PauseFlags) (Schedule, error)
- func (s *Service) Save(ctx context.Context, body map[string]any, id string) (Schedule, error)
- func (s *Service) SetDeliverer(deliverer Deliverer)
- func (s *Service) TestDelivery(ctx context.Context, name string, options TestDeliveryFlags) ([]DeliveryResult, error)
- func (s *Service) Trigger(ctx context.Context, name string, options TriggerFlags) (*TriggerResult, error)
- type SnapshotFlags
- type Store
- func (s *Store) Control(_ context.Context, id string, action task.ControlAction) error
- func (s *Store) Delete(ctx context.Context, name string) error
- func (s *Store) DeleteSchedule(ctx context.Context, name string) error
- func (s *Store) Get(ctx context.Context, name string) (Schedule, error)
- func (s *Store) GetRun(ctx context.Context, id string) (Run, error)
- func (s *Store) List(ctx context.Context) ([]Schedule, error)
- func (s *Store) ListRuns(ctx context.Context, filter RunFilter) ([]Run, error)
- func (s *Store) ListSchedules(ctx context.Context) ([]task.Schedule, error)
- func (s *Store) RecordFire(ctx context.Context, name string, fire task.Fire) error
- func (s *Store) RecordReport(ctx context.Context, runID, artifactPath string, delivery []DeliveryResult) error
- func (s *Store) Runs(ctx context.Context, filter task.RunFilter) ([]task.RunMeta, error)
- func (s *Store) Save(ctx context.Context, schedule Schedule) error
- func (s *Store) SaveRun(ctx context.Context, groupID string, snapshots []task.TaskSnapshot) error
- func (s *Store) SaveSchedule(ctx context.Context, schedule task.Schedule) error
- func (s *Store) SetEnabled(ctx context.Context, name string, enabled bool) error
- func (s *Store) Snapshot(ctx context.Context, id string) ([]task.TaskSnapshot, error)
- type StoreProvider
- type TestDeliveryFlags
- type TriggerFlags
- type TriggerResult
Constants ¶
const ( FormatFacetHTML = "facet-html" FormatFacetPDF = "facet-pdf" )
Report formats. Everything except the two facet formats is already produced by the profile export path (query/render.go), so a scheduled report and a downloaded one are the same bytes; the facet formats render a TSX template through the facet service.
const Kind = "schedule"
Kind is the task kind every scheduled run carries, and the key the runner is registered under with clicky's scheduler.
Variables ¶
var ErrNotFound = errors.New("not found")
ErrNotFound is returned when a schedule or run does not exist.
Functions ¶
This section is empty.
Types ¶
type ArtifactStore ¶
type ArtifactStore interface {
Save(ctx context.Context, schedule, runID string, artifact Artifact) (Artifact, error)
}
ArtifactStore persists a rendered report where the run history and the delivery link can both reach it.
func NewArtifactStore ¶
func NewArtifactStore(filesystem fs.FilesystemRW, root string) (ArtifactStore, error)
NewArtifactStore stores reports under root on the given filesystem.
type Deliverer ¶
type Deliverer interface {
Deliver(ctx dbcontext.Context, schedule Schedule, artifact Artifact) ([]DeliveryResult, error)
}
Deliverer sends a produced report to the schedule's channels.
func NewDeliverer ¶
func NewDeliverer(options DelivererOptions) Deliverer
NewDeliverer returns the default report deliverer.
type DelivererOptions ¶
type DelivererOptions struct {
// BaseURL is the externally reachable root of this server, used to build
// the artifact links non-SMTP channels receive.
BaseURL string
}
DelivererOptions configure delivery.
type DeliveryResult ¶
type DeliveryResult struct {
Connection string `json:"connection"`
Channel string `json:"channel,omitempty"`
Sent bool `json:"sent"`
Attached bool `json:"attached,omitempty"`
Error string `json:"error,omitempty"`
At time.Time `json:"at"`
}
DeliveryResult records what happened when one channel was sent to.
type DeliverySpec ¶
type DeliverySpec struct {
Connection string `json:"connection" yaml:"connection"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
// Attach embeds the rendered report in the message. Only SMTP carries
// attachments; every other channel gets a link to the stored artifact, and
// asking for an attachment on one is refused rather than silently dropped.
Attach bool `json:"attach,omitempty" yaml:"attach,omitempty"`
// Properties are per-channel overrides, using the same `channel.key` prefix
// convention the notification senders read.
Properties map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"`
}
DeliverySpec sends the result to one channel, named by connection.
type Duration ¶
Duration is a time.Duration that marshals as a human string ("30m") rather than a nanosecond count, because these values are typed by people.
func (Duration) MarshalJSON ¶
func (*Duration) UnmarshalJSON ¶
type FacetSpec ¶
type FacetSpec struct {
Connection string `json:"connection,omitempty" yaml:"connection,omitempty"`
URL string `json:"url,omitempty" yaml:"url,omitempty"`
Timeout Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"`
Header string `json:"header,omitempty" yaml:"header,omitempty"`
TimestampURL string `json:"timestampUrl,omitempty" yaml:"timestampUrl,omitempty"`
}
FacetSpec points at the facet render service. Connection or URL selects it; when neither is set the local facet binary is used instead.
type Options ¶
type Options struct {
Store StoreProvider
Scheduler *task.Scheduler
DecodeBody profiles.BodyDecoder
// Runner and Deliverer power the trigger and test-delivery actions. Both
// are optional; without them those actions refuse rather than pretend.
Runner *Runner
Deliverer Deliverer
// Context supplies the database-backed context delivery needs to resolve
// connections and their secrets.
Context func() dbcontext.Context
}
Options wire the service to its store and the scheduler it drives.
type PauseFlags ¶
type PauseFlags struct{}
PauseFlags carries no options; pausing is the whole instruction.
func (PauseFlags) ClickyActionFlags ¶
func (PauseFlags) ClickyActionFlags()
type QuerySpec ¶
type QuerySpec struct {
Profile string `json:"profile" yaml:"profile"`
Params map[string]string `json:"params,omitempty" yaml:"params,omitempty"`
// Limit caps the rows read. Zero uses the profile's own export ceiling —
// a scheduled report has no user watching to stop a runaway read.
Limit int `json:"limit,omitempty" yaml:"limit,omitempty"`
}
QuerySpec runs one profile. Params are the profile's own filter params, the same key=value pairs `query profiles run --param` takes.
type ReconcileSpec ¶
type ReconcileSpec struct {
Profile string `json:"profile" yaml:"profile"`
profiles.ReconcileFlags `yaml:",inline"`
}
ReconcileSpec joins two profiles. It embeds the same flags the manual `reconcile` action takes, so a scheduled reconciliation and a hand-run one are configured identically rather than by two drifting sets of fields.
type ReportSpec ¶
type ReportSpec struct {
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Format string `json:"format" yaml:"format"`
// Template selects the TSX entry file for the facet formats. Empty uses the
// embedded default.
Template string `json:"template,omitempty" yaml:"template,omitempty"`
Variables map[string]string `json:"variables,omitempty" yaml:"variables,omitempty"`
Facet *FacetSpec `json:"facet,omitempty" yaml:"facet,omitempty"`
}
ReportSpec renders the result. Formats are the ones the profile export already serves — csv, json, yaml, markdown, html, excel, pdf — plus facet-html and facet-pdf, which render a TSX template through facet.
type Reporter ¶
type Reporter interface {
Render(ctx dbcontext.Context, spec ReportSpec, result *query.Result, columns []query.ColumnDef) ([]byte, error)
}
Reporter renders a result to report bytes. It is an interface so the schedule runner does not depend on the facet renderer directly, and a deployment without one still runs its queries.
type Run ¶
type Run struct {
ID string `json:"id" pretty:"label=ID"`
Schedule string `json:"schedule,omitempty" pretty:"label=Schedule"`
Name string `json:"name" pretty:"label=Name"`
Kind string `json:"kind,omitempty" pretty:"label=Kind"`
Status string `json:"status" pretty:"label=Status"`
Total int `json:"total" pretty:"label=Tasks"`
Completed int `json:"completed" pretty:"label=Completed"`
Failed int `json:"failed" pretty:"label=Failed"`
Labels map[string]string `json:"labels,omitempty"`
Owner string `json:"owner,omitempty"`
ArtifactPath string `json:"artifactPath,omitempty" pretty:"label=Artifact"`
Delivery []DeliveryResult `json:"delivery,omitempty"`
StartedAt *time.Time `json:"startedAt,omitempty" pretty:"label=Started"`
FinishedAt *time.Time `json:"finishedAt,omitempty" pretty:"label=Finished"`
}
Run is one row of a schedule's history, as the `schedule run` entity lists it.
type RunFilter ¶
type RunFilter struct {
Schedule string
Kind string
Status string
Labels map[string]string
Limit int
}
RunFilter narrows a run listing. Empty fields match everything.
type Runner ¶
type Runner struct {
// contains filtered or unexported fields
}
Runner executes one schedule inside a clicky task group.
func NewRunner ¶
func NewRunner(options RunnerOptions) (*Runner, error)
func (*Runner) Register ¶
func (r *Runner) Register()
Register wires the runner into clicky's scheduler for this package's kind.
Registering the same kind twice panics by design in clicky, so this is called once at serve time rather than from the constructor, which a test may build more than once in a process.
func (*Runner) Run ¶
Run is the task.Runner for a schedule. Each stage is its own task in the group, so a failure names the stage that failed rather than the schedule.
func (*Runner) SetArtifacts ¶
func (r *Runner) SetArtifacts(store ArtifactStore)
SetArtifacts installs the artifact store.
func (*Runner) SetDeliverer ¶
SetDeliverer installs the notifier once the server knows its own base URL.
type RunnerOptions ¶
type RunnerOptions struct {
Store StoreProvider
Profiles profiles.StoreProvider
Context func() dbcontext.Context
Reconcile *profiles.Service
Reporter Reporter
Deliverer Deliverer
Artifacts ArtifactStore
// BeforeExecute, when set, prepares a scheduled query's data before it is
// read — the same hook the profile service runs, so a scheduled read and a
// hand-run one see the same data. The reconcile mode runs it through
// Reconcile, the profile service's own.
BeforeExecute profiles.BeforeExecuteFunc
}
RunnerOptions wire the runner to everything it needs. Reporter, Deliverer and Artifacts are optional: without them a schedule still runs its query and records the run, it just produces nothing to send.
type Schedule ¶
type Schedule struct {
Name string `json:"name" yaml:"name" clicky:"title=Name,order=1"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty" clicky:"title=Namespace,order=2"`
// Cron is a five-field spec or a descriptor ("@daily", "@every 30m").
Cron string `json:"cron" yaml:"cron" clicky:"type=cron,title=Schedule,order=3"`
Timezone string `json:"timezone,omitempty" yaml:"timezone,omitempty" clicky:"title=Timezone,order=4"`
Enabled bool `json:"enabled" yaml:"enabled" clicky:"title=Enabled,order=5"`
// Overlap and CatchUp are per-schedule because there is no answer that is
// right for both a cheap probe and an expensive report.
Overlap string `json:"overlap,omitempty" yaml:"overlap,omitempty" clicky:"title=On overlap,order=6"`
CatchUp string `json:"catchUp,omitempty" yaml:"catchUp,omitempty" clicky:"title=On missed runs,order=7"`
Timeout Duration `json:"timeout,omitempty" yaml:"timeout,omitempty" clicky:"title=Timeout,order=8"`
// Exactly one of Query and Reconcile says what the schedule runs.
Query *QuerySpec `json:"query,omitempty" yaml:"query,omitempty"`
Reconcile *ReconcileSpec `json:"reconcile,omitempty" yaml:"reconcile,omitempty"`
Report *ReportSpec `json:"report,omitempty" yaml:"report,omitempty"`
Deliver []DeliverySpec `json:"deliver,omitempty" yaml:"deliver,omitempty"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
LastRun *time.Time `json:"lastRun,omitempty" yaml:"-"`
NextRun *time.Time `json:"nextRun,omitempty" yaml:"-"`
}
Schedule is a recurring query or reconciliation, optionally rendered to a report and delivered. It is the stored spec; clicky's task.Schedule is the timing half derived from it.
func (Schedule) Columns ¶
Columns is the schedule listing. It deliberately omits the nested query, reconcile, report and delivery blocks: a listing answers "what is scheduled, is it on, and what does it do", and rendering four nested documents per row answers none of those.
func (Schedule) GetID ¶
GetID and GetName make a Schedule a clicky EntityItem. The name is the id: schedules are addressed by the name their author gave them, like profiles.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the CLI and HTTP surface over schedules and their run history.
func (*Service) LoadScheduler ¶
LoadScheduler registers every stored schedule with the scheduler. It is called at startup, once the database is up.
func (*Service) RegisterClicky ¶
func (s *Service) RegisterClicky()
RegisterClicky registers the schedule entity and, nested under it, the run entity that is its history.
Runs are an entity rather than an action returning a blob, so they arrive with filtering, sorting and paging already built. clicky derives the REST path from the command nesting, so `query schedule run list` and GET /api/v1/schedule/run come from the same declaration.
The consequence is that the fire-now action cannot be called `run` — it would collide with the child command — so it is `trigger`.
func (*Service) Save ¶
Save decodes a schedule document and persists it, then re-registers it with the scheduler so an edit takes effect without a restart.
func (*Service) SetDeliverer ¶
SetDeliverer installs the notifier. It is set at serve time rather than in the constructor because delivery needs the externally reachable base URL, which only the server knows.
func (*Service) TestDelivery ¶
func (s *Service) TestDelivery( ctx context.Context, name string, options TestDeliveryFlags, ) ([]DeliveryResult, error)
TestDelivery sends a sample message to each of a schedule's channels, so a misconfigured connection is found deliberately rather than by a report going missing at 6am.
func (*Service) Trigger ¶
func (s *Service) Trigger(ctx context.Context, name string, options TriggerFlags) (*TriggerResult, error)
Trigger runs a schedule now. It goes through the same runner the scheduler uses, so a manual run and a scheduled one are the same code path and produce the same history — a "run it now" that behaved differently would be worse than no button at all.
type SnapshotFlags ¶
type SnapshotFlags struct{}
SnapshotFlags carries no options.
func (SnapshotFlags) ClickyActionFlags ¶
func (SnapshotFlags) ClickyActionFlags()
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the durable home of schedules and their run history. It implements clicky's task.Store, so the task manager persists finished runs through it and reads evicted ones back out without knowing anything about this schema.
func (*Store) Control ¶
Control is refused for stored runs. A run this process no longer holds has no goroutine to stop, and pretending otherwise would report success for an action that did nothing.
func (*Store) Delete ¶
Delete removes a schedule. Its runs are left behind: history outlives the definition that produced it.
func (*Store) DeleteSchedule ¶
func (*Store) ListSchedules ¶
func (*Store) RecordFire ¶
RecordFire appends one firing decision, including the ones that produced no run. A schedule that keeps skipping is invisible without this.
func (*Store) RecordReport ¶
func (s *Store) RecordReport(ctx context.Context, runID, artifactPath string, delivery []DeliveryResult) error
RecordReport attaches the produced artifact and delivery outcomes to a run.
This runs from inside the run's own task, which is necessarily before the run itself is persisted: a run is written when it reaches a terminal status, and it cannot be terminal while the task recording the report is still going. So this upserts rather than updates — a plain UPDATE would match no row and silently discard the artifact. The placeholder it may insert is completed by the SaveRun that follows, which deliberately leaves these two columns alone.
func (*Store) SaveRun ¶
SaveRun upserts one run keyed by its clicky group id. It is idempotent by design: the terminal-transition write and the later eviction write are the same row, and the newest snapshot wins.
func (*Store) SaveSchedule ¶
SaveSchedule persists the timing half of a schedule. The spec itself is written by Save; this records what the scheduler learned by running it, which is what lets a restart tell that a scheduled time was missed.
func (*Store) SetEnabled ¶
SetEnabled toggles a schedule without rewriting its spec.
type StoreProvider ¶
StoreProvider resolves the store lazily, because the command tree is built before the database exists — a metadata-only invocation must not start PostgreSQL just to list its own subcommands.
func (StoreProvider) TaskScheduleStore ¶
func (p StoreProvider) TaskScheduleStore() task.ScheduleStore
TaskScheduleStore adapts the lazy database provider to Clicky's scheduling persistence without opening PostgreSQL while the command tree is built.
type TestDeliveryFlags ¶
type TestDeliveryFlags struct {
Message string `flag:"message" help:"Body of the test message"`
}
TestDeliveryFlags are the flags of the `test-delivery` action.
func (TestDeliveryFlags) ClickyActionFlags ¶
func (TestDeliveryFlags) ClickyActionFlags()
type TriggerFlags ¶
type TriggerFlags struct {
Wait bool `flag:"wait" help:"Block until the run finishes instead of returning its id"`
}
TriggerFlags are the flags of the schedule `trigger` action.
func (TriggerFlags) ClickyActionFlags ¶
func (TriggerFlags) ClickyActionFlags()
type TriggerResult ¶
type TriggerResult struct {
Schedule string `json:"schedule" pretty:"label=Schedule"`
RunID string `json:"runId" pretty:"label=Run"`
Status string `json:"status" pretty:"label=Status"`
Rows int `json:"rows,omitempty" pretty:"label=Rows"`
Artifact string `json:"artifact,omitempty" pretty:"label=Artifact"`
Error string `json:"error,omitempty" pretty:"label=Error"`
Started time.Time `json:"startedAt" pretty:"label=Started"`
Finished *time.Time `json:"finishedAt,omitempty" pretty:"label=Finished"`
}
TriggerResult names the run a trigger started.
func (TriggerResult) Pretty ¶
func (r TriggerResult) Pretty() api.Text