Documentation
¶
Overview ¶
Package automations is the one place an Automation's steps are stored, validated and its lifecycle changed (ADR 0016). /site and /api handlers stay thin adapters over it; the step executor (internal/jobs) decodes the same Step format, so the storage JSON has a single definition. The steps live in versions (ADR 0049).
Index ¶
- Constants
- Variables
- func Advance(ctx context.Context, s *ent.Scoped, runID int64, step int, resumeAt *time.Time) error
- func CancelPending(ctx context.Context, s *ent.Scoped, contactID int64, purger events.QueuePurger) error
- func Complete(ctx context.Context, s *ent.Scoped, runID int64) error
- func Encode(steps []Step) (string, error)
- func Enroll(ctx context.Context, s *ent.Scoped, automationID, contactID int64) (runID int64, enrolled bool, err error)
- func Exit(ctx context.Context, s *ent.Scoped, automationID, contactID int64) (bool, error)
- func ExitRun(ctx context.Context, s *ent.Scoped, runID int64) error
- func Fail(ctx context.Context, s *ent.Scoped, runID int64, f Failure) error
- func IsActive(ctx context.Context, s *ent.Scoped, runID int64) (bool, error)
- func Loaded(ctx context.Context, s *ent.Scoped, preds ...predicate.Automation) ([]*ent.Automation, error)
- func RenderBody(body string, c *ent.Contact) (string, error)
- func Stale(err error) bool
- func UsingTag(ctx context.Context, s *ent.Scoped, name string) ([]*ent.Automation, error)
- type CreateInput
- type DryRunResult
- type Failure
- type Fencer
- type Header
- type Module
- func (m *Module) Activate(ctx context.Context, s *ent.Scoped, id int64) (*ent.Automation, error)
- func (m *Module) Archive(ctx context.Context, s *ent.Scoped, id int64) error
- func (m *Module) Create(ctx context.Context, s *ent.Scoped, in CreateInput) (*ent.Automation, error)
- func (m *Module) Deactivate(ctx context.Context, s *ent.Scoped, id int64) (*ent.Automation, error)
- func (m *Module) Delete(ctx context.Context, s *ent.Scoped, id int64) error
- func (m *Module) DryRun(ctx context.Context, s *ent.Scoped, contactID int64, body string) (DryRunResult, error)
- func (m *Module) Enrollment(ctx context.Context, s *ent.Scoped, automationID, id int64) (*ent.AutomationRun, error)
- func (m *Module) Enrollments(ctx context.Context, s *ent.Scoped, automationID int64, status string, ...) (pagination.Page[*ent.AutomationRun], error)
- func (m *Module) Get(ctx context.Context, s *ent.Scoped, id int64) (*ent.Automation, error)
- func (m *Module) List(ctx context.Context, s *ent.Scoped, p pagination.Params, archived bool) (pagination.Page[*ent.Automation], error)
- func (m *Module) OpenHTTP(s Step) (OpenedHTTP, error)
- func (m *Module) Unarchive(ctx context.Context, s *ent.Scoped, id int64) error
- func (m *Module) Update(ctx context.Context, s *ent.Scoped, id int64, in UpdateInput) (*ent.Automation, error)
- type OpenedHTTP
- type Step
- type UpdateInput
Constants ¶
const ( StepEmail = "email" StepWait = "wait" StepApplyTag = "apply_tag" StepRemoveTag = "remove_tag" StepHTTP = "http" )
Step kinds.
const ( MaxHTTPURLBytes = 2 << 10 MaxHTTPBodyBytes = 64 << 10 MaxHTTPHeaders = 5 MaxHTTPStepsPerAutomation = 10 )
Limits of an HTTP step (ADR 0039): they keep one step from exhausting the queue.
const ErrorKindInvalidBody = "invalid_body"
ErrorKindInvalidBody is the closed error kind of a body that does not render to valid JSON (a template error, invalid JSON or an oversized body).
Variables ¶
var ( // ErrNotFound: no such Automation in this Workspace. ErrNotFound = errors.New("automations: automation not found") // ErrInvalidStep: a step has an unknown type, a negative wait, or a tag step // without a tag name, or an email step whose content cannot render. ErrInvalidStep = errors.New("automations: invalid step") // ErrNoVersion: the Automation has no version yet. Only an Automation made before versions // existed can be in that state, until the backfill job (internal/jobs) has run. ErrNoVersion = errors.New("automations: automation has no version yet") )
Domain errors. Callers match with errors.Is.
var ( // ErrContactNotFound: no such Contact in this Workspace. ErrContactNotFound = errors.New("automations: contact not found") // ErrInvalidBody: the body did not render to valid JSON within the size limit. ErrInvalidBody = errors.New("automations: body does not render to valid JSON") )
Functions ¶
func Advance ¶
Advance moves an active Enrollment to step, parking it until resumeAt (nil: no wait). It is a compare-and-swap on `active` like every move, so a step cannot overwrite an exit: lifecycle.ErrInvalidTransition when the Enrollment is no longer active.
func CancelPending ¶
func CancelPending(ctx context.Context, s *ent.Scoped, contactID int64, purger events.QueuePurger) error
CancelPending cancels a Contact's pending Automation work (Erasure, ADR 0021): the queued step and HTTP-step jobs of the Contact's runs and the queued trigger evaluations that name the Contact. The runs themselves are Erasure's to delete (a failure's response excerpt sits on the run). It runs inside the caller's transaction (s and purger come from the same one); a job that is already running is not purged.
func Complete ¶
Complete ends the Enrollment `completed`. lifecycle.ErrInvalidTransition when it is no longer active (an exit won), which the step executor treats as a finished step.
func Encode ¶
Encode serializes steps into the stored definition, dropping the fields that do not belong to each step's type, trimming tag names and sanitizing an email step's body.
func Enroll ¶
func Enroll(ctx context.Context, s *ent.Scoped, automationID, contactID int64) (runID int64, enrolled bool, err error)
Enroll starts an Enrollment of the Contact in the Automation and returns its run id. enrolled is false when the Contact has been enrolled before (the common trigger-replay path) or another request won the race; nothing is created then.
func Exit ¶
Exit ends the Contact's active Enrollment in the Automation (status exited, no pending resume). It reports whether an active Enrollment was ended; a finished or absent one is left alone, and so is one another writer moved first.
func ExitRun ¶
ExitRun ends one Enrollment by run id, for the step executor that already holds the run (an ineligible destination exits it rather than walk on skipping mail). lifecycle.ErrInvalidTransition when it is no longer active.
func Fail ¶
Fail ends the Enrollment `failed`, keeping the reason. lifecycle.ErrInvalidTransition when it is no longer active.
func IsActive ¶
IsActive reports, from a fresh read through the lifecycle table, whether the Enrollment can still move: a step that finds it exited, completed or failed stops quietly. A run that no longer exists (erased with its Contact) is not active either.
func Loaded ¶
func Loaded(ctx context.Context, s *ent.Scoped, preds ...predicate.Automation) ([]*ent.Automation, error)
Loaded returns the Workspace's Automations matching preds, each carrying its current steps (CurrentSteps). Archived Automations are not loaded: they enroll nobody and use nothing. A live-dependency check reads them to see which Tag, Integration or Sending domain an Automation's steps use (ADR 0049).
func RenderBody ¶
RenderBody renders an HTTP step's Liquid body for a Contact (nil: no bindings) and checks the result is valid JSON within the size limit. The Contact's strings are JSON-escaped in the bindings themselves, so a value holding quotes cannot change the structure.
func Stale ¶
Stale reports that err means the Enrollment is no longer there to move: another writer took it out of `active` (an exit won) or it was erased with its Contact. A step that gets it finishes quietly; nil and every other error are not stale.
Types ¶
type CreateInput ¶
CreateInput is the data for a new Automation. It is always created as a draft: activating is a separate, deliberate operation.
type DryRunResult ¶
DryRunResult is a body rendered for one Contact, or the kind of error that stopped it. Nothing is called.
type Failure ¶
type Failure struct {
Kind automationrun.FailureKind
Status int
Excerpt string
}
Failure is what an Enrollment keeps of the step that failed it. The zero value records nothing beyond the `failed` status.
type Fencer ¶
type Fencer func(ctx context.Context, s *ent.Scoped, runID int64, effect func(context.Context) error) error
Fencer runs a step's side effect (a mail, an HTTP call) so that it is ordered against an exit of the Enrollment: automations.Stale(err) when the Enrollment was no longer active when the effect would have started (then effect did not run), otherwise effect's own error.
func FenceOn ¶
FenceOn is the Fencer of a database with real connections (ADR 0046): effect runs while a share lock is held on the Enrollment's row, taken only if the row is still `active`. Every move out of `active` is an UPDATE of that row, and an UPDATE waits for a share lock, so
- an exit that arrives while the effect is in flight returns only after it finished: the effect is ordered before the exit, never after it;
- an exit committed before the lock is seen under it (the lock's WHERE is re-checked once a concurrent update commits), so the effect never starts.
The lock lives on a transaction of its own that does nothing else, and effect runs on the pool's other connections as it always did: the step's compare-and-swap that follows (Advance, Complete, Fail) is made after the lock is released, so it cannot wait on itself, and the outbound claim, lease and idempotency key are untouched. The hold lasts as long as effect runs, which the job's context, the provider's and the HTTP client's timeouts bound; the transaction ends when effect returns or ctx is done. Only the lock is read-only, so there is nothing to commit.
The lock needs one pool connection beyond the effect's own for the effect's duration.
type Header ¶
Header is one request header of an HTTP step. In a stored step Value is ciphertext. In a saved step, an empty Value keeps the stored value of the header with the same name.
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module is the automations module.
func New ¶
New builds the automations module. It holds no client: every call takes the Workspace's scoped client (ADR 0017). The cipher seals an HTTP step's secrets; the bus opens the transaction that saves an Automation together with its steps.
func (*Module) Archive ¶
Archive discards an Automation (ADR 0049): it leaves the lists and enrolls nobody, and its Enrollments stay. An Automation with active Enrollments is refused with an *inuse.Error carrying their count. Archiving an archived Automation changes nothing.
func (*Module) Create ¶
func (m *Module) Create(ctx context.Context, s *ent.Scoped, in CreateInput) (*ent.Automation, error)
Create stores a new draft Automation with its first version.
func (*Module) Deactivate ¶
Deactivate stops new enrollments; in-flight Enrollments finish.
func (*Module) Delete ¶
Delete purges an Automation that has no Enrollments, with its versions (ADR 0049). One with any Enrollment is history and is archived instead: an *inuse.Error carrying their count.
func (*Module) DryRun ¶
func (m *Module) DryRun(ctx context.Context, s *ent.Scoped, contactID int64, body string) (DryRunResult, error)
DryRun renders a body for a Contact of the Workspace and validates it without calling anything.
func (*Module) Enrollment ¶
func (m *Module) Enrollment(ctx context.Context, s *ent.Scoped, automationID, id int64) (*ent.AutomationRun, error)
Enrollment returns one Enrollment of an Automation. ErrNotFound when the Workspace has no such Automation, or the Automation no such Enrollment.
func (*Module) Enrollments ¶
func (m *Module) Enrollments(ctx context.Context, s *ent.Scoped, automationID int64, status string, p pagination.Params) (pagination.Page[*ent.AutomationRun], error)
Enrollments returns one page of an Automation's Enrollments, newest first, optionally only those in one status (an empty status means all). ErrNotFound when the Workspace has no such Automation.
func (*Module) Get ¶
Get returns one Automation of the Workspace, archived or not: an archived Automation is still addressable (viewed, restored, its Enrollments listed).
func (*Module) List ¶
func (m *Module) List(ctx context.Context, s *ent.Scoped, p pagination.Params, archived bool) (pagination.Page[*ent.Automation], error)
List returns one page of the Workspace's Automations, newest first: the kept ones, or with archived the archived ones.
func (*Module) OpenHTTP ¶
func (m *Module) OpenHTTP(s Step) (OpenedHTTP, error)
OpenHTTP decrypts a stored HTTP step's URL, header values and secret.
type OpenedHTTP ¶
OpenedHTTP is an HTTP step's secrets in the clear, for the executor.
type Step ¶
type Step struct {
Type string `json:"type"`
Subject string `json:"subject,omitempty"`
Body string `json:"body,omitempty"`
// Doc is the editor's JSON document an email step's Body was built from, and EditorVersion
// the editor that rendered it (ADR 0040). A step written as HTML has neither. A step being
// saved carries a Doc or a Body, not both: the save renders the Doc to the Body.
Doc json.RawMessage `json:"doc,omitempty"`
EditorVersion string `json:"editorVersion,omitempty"`
Seconds int `json:"seconds,omitempty"`
Tag string `json:"tag,omitempty"`
// HTTP steps (ADR 0039) hold ID, URL, Headers, Body (a Liquid JSON template) and
// Secret. Stored, URL, header values and Secret are ciphertext, sealed per field;
// in a step being saved, an empty one keeps the stored value of the step with the
// same ID, and ClearSecret drops the signing secret.
ID string `json:"id,omitempty"`
URL string `json:"url,omitempty"`
Headers []Header `json:"headers,omitempty"`
Secret string `json:"secret,omitempty"`
ClearSecret bool `json:"-"`
}
Step is one node of an Automation's linear sequence, in the executor's on-disk JSON form. Email steps carry Subject and Body (HTML), wait steps Seconds, tag steps Tag (the Tag name).
func CurrentSteps ¶
func CurrentSteps(a *ent.Automation) ([]Step, error)
CurrentSteps decodes the steps of the Automation's latest version, which the Automations this module returns carry. An Automation that was loaded some other way has none.
type UpdateInput ¶
UpdateInput changes an Automation; nil fields are left alone.