rules

package
v0.17.1 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package rules holds the declarative catalogue the sweep loads at runtime. A rule names a detection signal, one action of the closed remedy vocabulary and the evidence written on the PR. Guards live in the action, so a rule can add a refusal and never remove one.

Index

Constants

View Source
const (
	DefaultOwner = "giantswarm"
	DefaultRepo  = "marge"
	DefaultDir   = "rules"
	DefaultRef   = "main"
)

The catalogue lives in marge's own repository and is read from its default branch at the start of every sweep, so a merged rule is live on the next run without a release.

View Source
const ScenarioDir = "testdata"

ScenarioDir holds the fixtures, one directory per rule.

Variables

This section is empty.

Functions

func Remediable

func Remediable(state pr.StatusState) bool

Remediable reports whether a rule may act on a classification.

Types

type Action

type Action struct {
	Name remedy.Name `yaml:"name"`
}

Action names the remedy the rule selects.

type BaseHead

type BaseHead string

BaseHead is what the base branch head reported for the checks failing on the PR. Every failing check the rule selected must report it, so a PR that carries one transient failure and one real failure matches neither green nor red. Absent is its own value: a check the base never ran is not green.

const (
	BaseAny    BaseHead = ""
	BaseGreen  BaseHead = "green"
	BaseRed    BaseHead = "red"
	BaseAbsent BaseHead = "absent"
)

type Catalogue

type Catalogue struct {
	// Rules are ordered by how much they ask of a PR, the most specific
	// first, and by name among equals. The first rule that matches wins, so
	// a narrow rule is never shadowed by a broad one that happens to sort
	// earlier. The order is part of the contract and scenarios pin it.
	Rules []*Rule
	// Digest identifies the exact catalogue this sweep ran, and is written
	// into the evidence of every action a rule selected.
	Digest string
	// Source says where the catalogue came from.
	Source string
	// Ref is the branch the catalogue was read from, empty for a local
	// directory.
	Ref string
	// Skipped names the documents that failed to parse, with the reason.
	// They are left out; the rest of the catalogue still runs.
	Skipped []Skipped
}

Catalogue is the loaded rule set of one sweep.

func (*Catalogue) Available

func (c *Catalogue) Available() bool

Available reports whether the sweep may run remedies. A catalogue that could not be read at all is not available and every remedy is refused.

func (*Catalogue) Match

func (c *Catalogue) Match(subject *Subject) *Hit

Match returns the first rule of the catalogue that matches the subject, or nil. The catalogue is ordered before it is used, so the outcome does not depend on how it was read; Catalogue.Rules states the order.

type CheckMatch

type CheckMatch struct {
	// Name is a glob over the failing check names.
	Name string `yaml:"name"`
}

CheckMatch matches the name of a failing check. A check name alone never justifies a write, so a rule whose action writes also needs a log signal.

type CheckState

type CheckState string

CheckState is what the base branch head reported for one check name.

const (
	CheckGreen  CheckState = "green"
	CheckRed    CheckState = "red"
	CheckAbsent CheckState = "absent"
)

type Coverage added in v0.16.0

type Coverage struct {
	NoPositive []string
	NoNegative []string
	// Orphans are scenario directories that name no rule of the catalogue.
	Orphans []string
}

Coverage reports the rules that have no scenario matching them and the rules that have no scenario refusing them. A rule needs both: one that shows it fires on its real signal, and one that shows it does not fire on a failure it must leave alone.

func CheckCoverage added in v0.16.0

func CheckCoverage(catalogue *Catalogue, scenarios []*Scenario) Coverage

CheckCoverage matches the catalogue against its scenarios.

func (Coverage) Covered added in v0.16.0

func (c Coverage) Covered() bool

Covered reports whether every rule has both cases.

type Evidence

type Evidence struct {
	// Reason is the line written on the PR under the action name.
	Reason string `yaml:"reason"`
}

Evidence is the marker the sweep writes when the action applied. The marker's outcome is the action name, so a later sweep recognises what ran on this change whichever rule selected it.

type Hit

type Hit struct {
	Rule *Rule
	// Check is the failing check the signal matched, or "" for a rule whose
	// signal is PR metadata alone.
	Check string
	// Excerpt is the log excerpt the pattern matched, empty for a rule with
	// no log signal.
	Excerpt string
	// LogMatched reports whether a log excerpt decided the match. An action
	// that writes is guarded on it.
	LogMatched bool
	// MissingContexts are the required contexts the protection signal
	// selected. An action that rewrites a protection touches these and no
	// other.
	MissingContexts []string
}

Hit is the rule that matched, with what made it match.

type Loader

type Loader struct {
	Client *github.Client
	Owner  string
	Repo   string
	// Dir is the directory inside the repository that holds the documents.
	Dir string
	Ref string

	// LocalPath reads the catalogue from this directory instead of GitHub.
	LocalPath string
}

Loader reads the catalogue from a repository, or from LocalPath when that is set.

func (Loader) Load

func (l Loader) Load(ctx context.Context, reg *remedy.Registry) (*Catalogue, error)

Load reads and validates every document of the catalogue. A document that fails to parse is skipped and reported; a catalogue that cannot be read at all is an error, and the caller runs the sweep without remedies.

type LogMatch

type LogMatch struct {
	// Source selects where the excerpt comes from.
	Source LogSource `yaml:"source"`
	// Pattern is an RE2 expression matched against the excerpt.
	Pattern string `yaml:"pattern"`
	// MaxBytes bounds the excerpt read from the tail of the log. Zero uses
	// the default.
	MaxBytes int `yaml:"maxBytes"`
}

LogMatch matches an excerpt of the failing step's log.

type LogSource

type LogSource string

LogSource names a log the sweep can fetch.

const (
	LogActions  LogSource = "actions"
	LogCircleCI LogSource = "circleci"
)

type Match

type Match struct {
	// States names the classifications the rule applies to, from the
	// remediable set. Empty applies to every remediable state.
	States []string `yaml:"states"`
	// Kinds names the bot PR kinds. Empty applies to every trusted kind.
	Kinds []string `yaml:"kinds"`

	Check      *CheckMatch      `yaml:"check"`
	Log        *LogMatch        `yaml:"log"`
	PR         *PRMatch         `yaml:"pr"`
	Protection *ProtectionMatch `yaml:"protection"`
}

Match is the detection signal. Every field present must hold; a rule with no signal beyond states and kinds is refused, because it would match every PR in that classification.

type PRMatch

type PRMatch struct {
	// BaseHead states what the base head must report for the failing checks.
	BaseHead BaseHead `yaml:"baseHead"`
	// TitlePattern is an RE2 expression matched against the PR title.
	TitlePattern string `yaml:"titlePattern"`
	// Files are globs; every one of them must match a file of the diff.
	Files []string `yaml:"files"`
}

PRMatch matches metadata of the pull request itself.

type ProtectionMatch

type ProtectionMatch struct {
	// MissingContexts are globs over the required contexts the head never
	// reported. They are alternatives: the rule matches when any of them
	// selects a context, and the action drops only what they selected.
	MissingContexts []string `yaml:"missingContexts"`
}

ProtectionMatch reads the base branch's protection rather than the pull request. A PR waiting on a context nobody reported carries no failing check, so no signal of the PR itself reaches it.

type Rule

type Rule struct {
	// Name identifies the rule and matches its file name.
	Name string `yaml:"name"`
	// Summary says in one line what the rule recognises.
	Summary string `yaml:"summary"`
	// Source cites where the pattern comes from, for instance the runbook
	// row it was imported from.
	Source string `yaml:"source"`

	Match    Match    `yaml:"match"`
	Action   Action   `yaml:"action"`
	Refuse   []string `yaml:"refuse"`
	Evidence Evidence `yaml:"evidence"`
	// contains filtered or unexported fields
}

Rule is one document of the catalogue.

func Parse

func Parse(fileName string, data []byte, reg *remedy.Registry) (*Rule, error)

Parse decodes and validates one rule document. Decoding is strict: an unknown key is an error, so no document can carry an option the schema does not define. fileName is the document's path, checked against the rule name. The error names no file: the caller knows which one it read, and reports it once.

func (*Rule) CheckPattern

func (r *Rule) CheckPattern() *regexp.Regexp

CheckPattern returns the compiled check-name glob, or nil when the rule has no check signal.

func (*Rule) FilePatterns

func (r *Rule) FilePatterns() []*regexp.Regexp

FilePatterns returns the compiled file globs. Every one of them must match a file of the diff.

func (*Rule) Guards

func (r *Rule) Guards() []remedy.Guard

Guards returns the refusals the rule adds to its action's own guards.

func (*Rule) Kinds

func (r *Rule) Kinds() map[pr.Kind]bool

Kinds reports the bot PR kinds the rule applies to. An empty set in the document means every trusted kind.

func (*Rule) LogBytes

func (r *Rule) LogBytes() int

LogBytes is the excerpt size the rule reads.

func (*Rule) LogPattern

func (r *Rule) LogPattern() *regexp.Regexp

LogPattern returns the compiled log expression, or nil when the rule has no log signal.

func (*Rule) MissingContextPatterns

func (r *Rule) MissingContextPatterns() []*regexp.Regexp

MissingContextPatterns returns the compiled globs over the required contexts the head never reported, or nil when the rule reads no protection.

func (*Rule) States

func (r *Rule) States() map[pr.StatusState]bool

States reports the classifications the rule applies to. An empty set in the document means every remediable state.

func (*Rule) TitlePattern

func (r *Rule) TitlePattern() *regexp.Regexp

TitlePattern returns the compiled title expression, or nil.

type Scenario added in v0.16.0

type Scenario struct {
	// Name says what the case shows, in the words of a test name.
	Name string `yaml:"name"`
	// Subject is the classified PR as the sweep saw it.
	Subject ScenarioSubject `yaml:"subject"`
	// Expect names the rule that must match, or "" for a case that must
	// match nothing.
	Expect ScenarioExpect `yaml:"expect"`

	// Path is where the fixture was read from.
	Path string `yaml:"-"`
	// Rule is the rule the fixture belongs to, from its directory name.
	Rule string `yaml:"-"`
}

Scenario replays one recorded PR against the catalogue and states which rule must match it. A rule without both a scenario that matches and one that does not cannot land.

func LoadScenarios added in v0.16.0

func LoadScenarios(dir string) ([]*Scenario, error)

LoadScenarios reads every fixture under dir, which holds one directory per rule. A fixture that cannot be read is an error: a catalogue with an unreadable test is not a tested catalogue.

func (*Scenario) Run added in v0.16.0

func (s *Scenario) Run(catalogue *Catalogue) string

Run replays the scenario against the catalogue and reports what went wrong, or "" when the outcome is the expected one.

type ScenarioExpect added in v0.16.0

type ScenarioExpect struct {
	// Rule is the rule that must match, or "" for no match.
	Rule string `yaml:"rule"`
	// Check is the failing check the signal must have matched. Empty skips
	// the assertion.
	Check string `yaml:"check"`
}

ScenarioExpect is the outcome the fixture asserts.

type ScenarioSubject added in v0.16.0

type ScenarioSubject struct {
	State     string                `yaml:"state"`
	Kind      string                `yaml:"kind"`
	Title     string                `yaml:"title"`
	Failing   []string              `yaml:"failing"`
	BaseState map[string]CheckState `yaml:"baseState"`
	// MissingContexts records the required contexts of the base branch that
	// the head never reported.
	MissingContexts []string          `yaml:"missingContexts"`
	Files           []string          `yaml:"files"`
	Logs            map[string]string `yaml:"logs"`
}

ScenarioSubject is the recorded PR. Logs are keyed "<source>:<check>" and hold the excerpt verbatim, as the runbook recorded it.

type Skipped

type Skipped struct {
	Path   string
	Reason string
}

Skipped is one document the catalogue could not use.

type Subject

type Subject struct {
	State pr.StatusState
	Kind  pr.Kind
	Title string
	// Failing names the red checks that produced a verdict, in the order the
	// classification found them.
	Failing []string
	// BaseState says what the base head reported for each failing check.
	// A name absent from the map is CheckAbsent, and absent is not green.
	BaseState map[string]CheckState
	// MissingContexts names the required contexts of the base branch that
	// the head never reported.
	MissingContexts []string
	// Files returns the paths of the PR diff. It is called only for a rule
	// that carries a file signal, so a catalogue without one costs no
	// comparison. Nil returns no files, and such a rule does not match.
	Files func() []string
	// Log returns a bounded excerpt of one check's log. A rule with a log
	// signal never matches when Log is nil or reports false: an unreadable
	// log leaves the failure as it was classified.
	Log func(source LogSource, check string, maxBytes int) (string, bool)
}

Subject is the classified PR a rule is matched against. The engine fills it once per PR; matching performs no request of its own except the log excerpt, which it asks Log for.

Jump to

Keyboard shortcuts

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