actions

package
v0.0.0-...-e4cf005 Latest Latest
Warning

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

Go to latest
Published: Apr 8, 2025 License: BSD-3-Clause Imports: 14 Imported by: 0

Documentation

Overview

Package actions implements a log of actions in the database. An action is anything that affects the outside world, such as edits to GitHub or Gerrit.

The action log uses database keys beginning with "action.Log" and an "action kind" string that describes the rest of the key and the format of the action and its result. The caller provides the rest of the key. For example, GitHub issue keys look like

["action.Log", "githubIssue", project, issue]

Action log values are described by the Entry type. Values include the parts of the key, an encoded action that provides enough information to perform it, and the result of the action after it is carried out. There are also fields for approval, discussed below.

Components that wish to use the action log must call Register to install a unique action kind along with a function that will run the action. Register returns a "before function" to call to add the action to the log before it is run. By choosing a key for the action that corresponds to an activity that it wishes to perform only once, a component can avoid duplicate executions of an action. For example, if a component wants to edit a GitHub comment only once, the key can be the URL for that comment. The before function will not write an action to the log if its key is already present. It returns false in this case, but the component is free to ignore this value.

Once it has called the before function (typically in its Run method), the component has nothing more to do at that time. At some later time, this package's Run function will be called to execute all pending actions (those added to the log but not yet run). Run will use the action kind of the pending action to dispatch to the registered run function, returning control to the component that logged the action.

Approvals

Some actions require approval before they can be executed. [Entry.ApprovalRequired] represents that, and [Entry.Decisions] records whether the action was approved or denied, by whom, and when. An action may be approved or denied multiple times. Approval is denied if there is at least one denial.

Other DB entries

This package stores other relationships in the database besides the log entries.

Keys beginning with "action.Pending" store the list of pending actions. The rest of the key is the action's key, and the value is nil. Actions are added to the pending list when they are first logged, and removed when they have been approved (if needed) and executed. (We cannot use a timed.Watcher for this purpose, because approvals can happen out of order.)

Keys beginning with "action.Wallclock" map wall clock times (time.Time values) to DBTimes. The mapping facilitates common log queries, like "show me the last hour of logs." The keys have the form

["action.Wallclock", time.Time.UnixNanos, DBTime]

The values are nil. Storing both times in the key permits multiple DBTimes for the same wall clock time.

Index

Constants

View Source
const RequiresApproval = true

RequiresApproval can be passed as the last argument to a BeforeFunc for clarity.

Variables

This section is empty.

Functions

func AddDecision

func AddDecision(db storage.DB, actionKind string, key []byte, d Decision)

AddDecision adds a Decision to the action referred to by actionKind, key and u. It panics if the action does not exist or does not require approval.

func ClearLogForTesting

func ClearLogForTesting(_ *testing.T, db storage.DB)

ClearLogForTesting deletes the entire action log. It is intended only for tests.

func ReRunAction

func ReRunAction(ctx context.Context, lg *slog.Logger, db storage.DB, actionKind string, key []byte) (err error)

ReRunAction attempts to re-run a single action denoted by the given kind and key. Only actions that have previously failed can be re-run.

func Run

func Run(ctx context.Context, lg *slog.Logger, db storage.DB) error

Run runs all actions that are ready to run, in the order they were added. An action is ready to run if it is approved and has not already run. Run returns the errors of all failed actions.

func Scan

func Scan(db storage.DB, start, end []byte) iter.Seq[*Entry]

Scan returns an iterator over action log entries with start ≤ key ≤ end. Keys begin with the actionKind string, followed by the key provided to [Before], followed by the uint64 returned by Before.

func ScanAfter

func ScanAfter(lg *slog.Logger, db storage.DB, t time.Time, filter func(actionKind string, key []byte) bool) iter.Seq[*Entry]

ScanAfter returns an iterator over action log entries that were started after time t. If filter is non-nil, ScanAfter omits entries for which filter(actionKind, key) returns false.

func ScanAfterDBTime

func ScanAfterDBTime(lg *slog.Logger, db storage.DB, t timed.DBTime, filter func(actionKind string, key []byte) bool) iter.Seq[*Entry]

ScanAfterDBTime returns an iterator over action log entries that were started after DBTime t. If filter is non-nil, ScanAfterDBTime omits entries for which filter(actionKind, key) returns false.

Types

type Actioner

type Actioner interface {
	// Run deserializes the action, executes it, then return
	// the serialized result and error.
	Run(context.Context, []byte) ([]byte, error)
	// ForDisplay returns a string describing the action in a way that is suitable
	// for display on web pages and by command-line tools.
	// The action is provided in serialized form, as with Run.
	ForDisplay([]byte) string
}

An Actioner works with actions. Actioners are registered with Register

type BeforeFunc

type BeforeFunc func(db storage.DB, key, action []byte, requiresApproval bool) (added bool)

BeforeFunc is the type of functions that are called to log an action before it is run. It writes an entry to db's action log with the given key and a representation of the action. The key must be created with ordered.Encode. The action should be JSON-encoded so tools can process it.

The function reports whether the action was added to the DB, or is a duplicate (has the same key) of an action that is already in the log.

func Register

func Register(actionKind string, a Actioner) BeforeFunc

Register associates the given action kind and Actioner. Only Actioner may be registered for each kind, except during testing, when Register always registers its arguments.

Register returns a function that should be called to log an action before it is run.

type Decision

type Decision struct {
	Name     string    // name of person or system making the decision
	Time     time.Time // time of the decision
	Approved bool      // true if approved, false if denied
}

A Decision describes the approval or denial of an action.

type Entry

type Entry struct {
	Created time.Time    // time of the Before call
	Kind    string       // determines the format of Key, Action and Result
	Key     []byte       // user-provided part of the key; arg to Before and After
	ModTime timed.DBTime // set by Get and ScanAfter, used to resume scan
	Action  []byte       // encoded action
	// Fields set by After
	Done   time.Time // time of the After call, or 0 if not called
	Result []byte    // encoded result
	Error  string    // error from attempted action, "" on success
	// Fields for approval
	ApprovalRequired bool
	Decisions        []Decision // approval decisions
}

An Entry is one entry in the action log.

func Get

func Get(db storage.DB, actionKind string, key []byte) (*Entry, bool)

Get looks up the Entry associated with the given arguments. If there is no entry for key in the database, Get returns nil, false. Otherwise it returns the entry and true.

func (*Entry) ActionForDisplay

func (e *Entry) ActionForDisplay() string

ActionForDisplay looks up the action associated with e and calls [Actioner.StringForDisplay] on it.

func (*Entry) Approved

func (e *Entry) Approved() bool

Approved reports whether the Entry represents an action that can be be executed. It returns true for actions that do not require approval and for those that do with at least one Decision and no denials. (In other words, a single denial vetoes the action.)

func (*Entry) IsDone

func (e *Entry) IsDone() bool

IsDone reports whether e is done.

func (*Entry) String

func (e *Entry) String() string

type RunReport

type RunReport struct {
	Completed int     // the number of actions successfully completed
	Skipped   int     // the number of actions skipped
	Errors    []error // the errors returned by actions that failed
}

A RunReport contains information about an action log run.

func RunWithReport

func RunWithReport(ctx context.Context, lg *slog.Logger, db storage.DB) *RunReport

RunWithReport is like Run, except it returns a report with information about the run.

Jump to

Keyboard shortcuts

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