pluginkit

package
v1.32.0 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: Apache-2.0 Imports: 20 Imported by: 3

Documentation

Overview

Package pluginkit provides the core plugin kit functionality for building Privateer plugins.

Package pluginkit provides the core plugin kit functionality for building Privateer plugins.

Each error factory wraps one of the two category sentinels below so ExitCodeFor can classify it into the right exit code via errors.Is.

Index

Constants

View Source
const BenchmarkFileName = "benchmark.json"

BenchmarkFileName is the report's fixed name, written into <write-directory>/<service>/ and located by the harness after a run.

View Source
const BenchmarkSchema = "privateer-benchmark/v1"

BenchmarkSchema identifies the benchmark report format for machine consumers.

View Source
const PublishManifestCommand = "publish-manifest"

PublishManifestCommand is the plugin subcommand that emits the grc.store publish manifest as JSON. command.NewPluginCommands wires it onto every plugin, and `pvtr publish` execs it on the built binary to read the plugin's coordinate and evaluated catalogs — rather than taking them as flags a non-owner could forge. Shared here so the producer (publish) and the plugin command that emits it can't drift on the name.

Variables

View Source
var (
	CORRUPTION_FOUND = func(mod string) error {
		return wrap(ErrRuntime, "target state may be corrupted! Halting to prevent futher damage. See logs for more information", mod)
	}
	NO_EVALUATION_SUITES = func(mod string) error {
		return wrap(ErrDevBug, "no control evaluations provided by the plugin", mod)
	}
	EVAL_NAME_MISSING = func(mod string) error {
		return wrap(ErrDevBug, "evaluationSuite name must not be empty", mod)
	}
	CONFIG_NOT_INITIALIZED = func(mod string) error {
		return wrap(ErrDevBug, "configuration not initialized", mod)
	}
	NO_ASSESSMENT_STEPS_PROVIDED = func(mod string) error {
		return wrap(ErrDevBug, "assessment steps not provided", mod)
	}
	NO_ASSESSMENT_REQS_PROVIDED = func(mod string) error {
		return wrap(ErrDevBug, "assessment requirements not provided", mod)
	}
	EVAL_SUITE_CRASHED = func(mod string) error {
		return wrap(ErrDevBug, "evaluation suite crashed", mod)
	}
)

Error functions that require no parameters.

View Source
var (
	EVALUATION_ORCHESTRATOR_NAMES_NOT_SET = func(serviceName, pluginName string, mod string) error {
		return wrap(ErrDevBug, fmt.Sprintf("expected service and plugin names to be set. ServiceName='%s' PluginName='%s'", serviceName, pluginName), mod)
	}
	WRITE_FAILED = func(name, err string, mod string) error {
		return wrap(ErrRuntime, fmt.Sprintf("failed to write results for evaluation suite. name: %s, error: %s", name, err), mod)
	}
	BAD_LOADER = func(pluginName string, err error, mod string) error {
		return wrap(ErrRuntime, fmt.Sprintf("failed to load payload for %s: %s", pluginName, err), mod)
	}
	BAD_CATALOG = func(pluginName string, errMsg string, mod string) error {
		return wrap(ErrDevBug, fmt.Sprintf("malformed data in catalog for %s: %s", pluginName, errMsg), mod)
	}
	BAD_EVAL_LOG = func(err error, mod string) error {
		return wrap(ErrDevBug, fmt.Sprintf("failed to setup evaluation log: %s", err), mod)
	}
	BAD_ASSESSMENT_REQS = func(err error, mod string) error {
		return wrap(ErrDevBug, fmt.Sprintf("failed to load assessment requirements from catalog: %s", err), mod)
	}
	BAD_CONFIG = func(err error, mod string) error {
		return wrap(ErrRuntime, fmt.Sprintf("failed to setup config: %s", err), mod)
	}
	NO_MATCHING_CATALOGS = func(requested []string, available []string, mod string) error {
		return wrap(ErrDevBug, fmt.Sprintf("no requested catalogs matched available suites. requested=%v available=%v", requested, available), mod)
	}
	BENCHMARK_WRITE_FAILED = func(err error, mod string) error {
		return wrap(ErrRuntime, fmt.Sprintf("failed to write benchmark report: %s", err), mod)
	}
)

Error functions that require parameters.

View Source
var ErrDevBug = errors.New("privateer plugin development error")

ErrDevBug classifies SDK misuse or malformed plugin data (missing catalogs, unset names, missing assessment steps). Maps to BadUsage.

View Source
var ErrRuntime = errors.New("privateer plugin runtime error")

ErrRuntime classifies failures during plugin execution that aren't the plugin author's fault (loader, write, RPC, corruption). Maps to InternalError.

Functions

func AddEvaluationSuiteTyped added in v1.32.0

func AddEvaluationSuiteTyped[S ~func(T) (gemara.Result, string, gemara.ConfidenceLevel), T any](
	v *EvaluationOrchestrator, catalogId string, loader DataLoader, steps map[string][]S,
) error

AddEvaluationSuiteTyped registers an evaluation suite whose steps take a concrete payload type T rather than an untyped any. The SDK performs the payload type assertion once, so plugins need neither a per-step payload guard nor their own adapter — and because the adaptation happens here, each step's real function name survives into the benchmark report.

It is a package-level function rather than a method because Go does not allow type parameters on methods.

All steps in one call share the payload type T; register a second suite for a step family that consumes a different payload.

func AddEvaluationSuiteTypedForAllCatalogs added in v1.32.0

func AddEvaluationSuiteTypedForAllCatalogs[S ~func(T) (gemara.Result, string, gemara.ConfidenceLevel), T any](
	v *EvaluationOrchestrator, loader DataLoader, steps map[string][]S,
) error

AddEvaluationSuiteTypedForAllCatalogs is AddEvaluationSuiteTyped applied to every reference catalog loaded via AddReferenceCatalogs, mirroring AddEvaluationSuiteForAllCatalogs.

func ExitCodeFor added in v1.24.0

func ExitCodeFor(orch *EvaluationOrchestrator, mobilizeErr error) int

ExitCodeFor maps the outcome of EvaluationOrchestrator.Mobilize into a canonical privateer exit code. Plugin authors call this from Start:

func (p *Plugin) Start() (int, error) {
    err := p.orchestrator.Mobilize()
    return pluginkit.ExitCodeFor(p.orchestrator, err), err
}

A non-nil error wrapping ErrRuntime → InternalError; ErrDevBug → BadUsage; any other non-nil error → InternalError. With nil error, suite results containing Failed/NeedsReview/Unknown → TestFail; otherwise TestPass.

func FuncName added in v1.32.0

func FuncName(fn any) string

FuncName resolves a function value's symbol name, as gemara names steps. Exported so plugins can record step identity themselves; the typed registration helpers below already do this on the caller's behalf.

The name can only be resolved while the function is still a symbol. Once a value has been captured into a closure — as happens when a plugin adapts its own steps to gemara.AssessmentStep — every closure produced by that literal shares one code pointer, and the original name is unrecoverable. That is why the adaptation below lives in the SDK: it is the last point at which the plugin's function is still nameable.

Types

type APICallCounter added in v1.32.0

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

APICallCounter tallies outbound HTTP requests, letting a plugin's payload satisfy APICallReporter without hand-rolling a RoundTripper. Embed a *APICallCounter in the payload and APICallCount is promoted onto it:

type Payload struct {
    // ...
    *pluginkit.APICallCounter
}

counter := &pluginkit.APICallCounter{}
httpClient := counter.WrapClient(oauth2.NewClient(ctx, src))
return Payload{APICallCounter: counter /* ... */}

Embed the pointer, not the value: payloads are commonly used by value, and a value-embedded counter would be copied so the tallies diverge. Copying is a `go vet` copylocks error rather than a silent miscount, because the tally is an atomic.Int64.

The tally counts HTTP round trips, which is a close proxy for API calls but not identical: retries and redirects each increment it, while one request batching several logical queries (a GraphQL document, say) counts once. It is a rate-limit budget, not an exact call ledger. All hosts share one tally; a plugin calling two rate-limited services sees their sum.

The zero value is ready to use, and a nil *APICallCounter reports zero.

func (*APICallCounter) APICallCount added in v1.32.0

func (c *APICallCounter) APICallCount() int

APICallCount implements APICallReporter. It is safe on a nil receiver so a payload built without a counter reports zero rather than panicking.

func (*APICallCounter) Wrap added in v1.32.0

Wrap returns base decorated to count every round trip through it. A nil base means http.DefaultTransport, matching http.Client's own behavior. A nil counter returns base undecorated.

func (*APICallCounter) WrapClient added in v1.32.0

func (c *APICallCounter) WrapClient(client *http.Client) *http.Client

WrapClient decorates client's transport in place and returns client, so it composes with a client an auth library already built. A nil client yields a new one.

type APICallReporter added in v1.32.0

type APICallReporter interface {
	APICallCount() int
}

APICallReporter is an optional interface a plugin may implement to report its cumulative external API-call count.

type ApplyFunc added in v1.7.0

type ApplyFunc func(interface{}) (interface{}, error)

ApplyFunc is a prepared function to apply a change.

type BenchmarkReport added in v1.32.0

type BenchmarkReport struct {
	Schema        string `json:"schema" yaml:"schema"`
	PluginName    string `json:"plugin-name" yaml:"plugin-name"`
	PluginVersion string `json:"plugin-version" yaml:"plugin-version"`
	ServiceName   string `json:"service-name" yaml:"service-name"`

	Loaders         []LoaderTiming `json:"loaders" yaml:"loaders"`
	Suites          []SuiteTiming  `json:"suites" yaml:"suites"`
	TotalDurationNs int64          `json:"total-duration-ns" yaml:"total-duration-ns"`
	APICalls        *int           `json:"api-calls,omitempty" yaml:"api-calls,omitempty"`
	WallClockNs     int64          `json:"wall-clock-ns,omitempty" yaml:"wall-clock-ns,omitempty"`
}

type Change

type Change struct {
	// TargetName is the name or ID of the resource or configuration that is to be changed
	TargetName string `yaml:"target-name"`
	// Description is a human-readable description of the change
	Description string `yaml:"description"`

	// TargetObject is an optional representation of the object that is being changed
	TargetObject interface{} `yaml:"target-object,omitempty"`
	// Applied is true if the change was successfully applied at least once
	Applied bool `yaml:"applied,omitempty"`
	// Reverted is true if the change was successfully reverted and not applied again
	Reverted bool `yaml:"reverted,omitempty"`
	// Error is used if any error occurred during the change
	Error error `yaml:"error,omitempty"`
	// CorruptedState is true if something went wrong during apply or revert, indicating that the system may be in a bad state
	CorruptedState bool `yaml:"bad-state,omitempty"`
	// contains filtered or unexported fields
}

Change is a struct that contains the data and functions associated with a single change to a target resource.

func (*Change) AddFunctions added in v1.7.0

func (c *Change) AddFunctions(applyFunc ApplyFunc, revertFunc RevertFunc)

AddFunctions sets the apply and revert functions for a change.

type ChangeManager added in v1.7.0

type ChangeManager struct {
	// Changes is a map of change names to Change objects, so that multiple changes can be tracked and reused.
	Changes map[string]*Change `yaml:"changes"`
	// Allowed must be set to true before any change can be applied.
	Allowed bool `yaml:"allowed,omitempty"`
	// CorruptedState is true if any change has failed to apply or revert, indicating that the system may be in a bad state.
	CorruptedState bool `yaml:"bad-state,omitempty"`
}

ChangeManager manages changes that can be applied and reverted during evaluation.

func (*ChangeManager) AddChange added in v1.7.0

func (cm *ChangeManager) AddChange(changeName string, change Change)

AddChange adds a change to the change manager.

func (*ChangeManager) Allow added in v1.7.0

func (cm *ChangeManager) Allow()

Allow marks changes as allowed to be applied.

func (*ChangeManager) Apply added in v1.7.0

func (cm *ChangeManager) Apply(changeName string, targetName string, changeInput any) (success bool, target any)

Apply executes the prepared function for the change. It will not apply the change if it is not allowed, or if it has already been applied and not reverted.

func (*ChangeManager) Revert added in v1.7.0

func (cm *ChangeManager) Revert(changeName string)

Revert reverts a specific change by name.

func (*ChangeManager) RevertAll added in v1.7.0

func (cm *ChangeManager) RevertAll()

RevertAll reverts all changes managed by the change manager.

type DataLoader added in v1.0.1

type DataLoader func(*config.Config) (any, error)

DataLoader is a function type for loading plugin data from configuration.

type EvaluatesDeclaration added in v1.31.0

type EvaluatesDeclaration struct {
	Catalog        string   `json:"catalog"`         // "<namespace>/<catalog_id>"
	CatalogVersion string   `json:"catalog_version"` //nolint:tagliatelle // wire contract
	RequirementIDs []string `json:"requirement_ids"` //nolint:tagliatelle // wire contract
}

EvaluatesDeclaration is one control-catalog linkage in the publish manifest. The JSON shape matches the OCI config blob's evaluates entry so the publish command can map it across without translation noise.

type EvaluationOrchestrator added in v1.3.0

type EvaluationOrchestrator struct {
	ServiceName   string `json:"service-name" yaml:"service-name"`
	PluginName    string `json:"plugin-name" yaml:"plugin-name"`
	PluginUri     string `json:"plugin-uri" yaml:"plugin-uri"`
	PluginVersion string `json:"plugin-version" yaml:"plugin-version"`
	// Publisher is the grc.store author/org id that owns this plugin (and, by
	// convention, the catalogs it evaluates). With PluginName it forms the publish
	// coordinate <Publisher>/<PluginName>, and it is the namespace of every
	// evaluated catalog in the publish manifest. Inert at run time; required only
	// to publish (a plugin runs without it, but cannot be published without it).
	Publisher string `json:"publisher,omitempty" yaml:"publisher,omitempty"`
	// License is the plugin's publication license as an SPDX expression
	// (e.g. "Apache-2.0", or "Apache-2.0 OR MIT"; use a LicenseRef-… token for a
	// custom license). grc.store requires it on every publication and it is
	// recorded in the signed plugin config. Like Publisher it is inert at run time
	// and required only to publish; pvtr validates and canonicalizes it (via
	// grc-store-protocol/spdx) at publish time, so the plugin only declares the
	// raw string here.
	License string `json:"license,omitempty" yaml:"license,omitempty"`
	// CatalogNamespaces optionally maps a reference-catalog id to the grc.store
	// namespace that owns it, for plugins that evaluate catalogs published by
	// someone else (e.g. a community plugin evaluating ossf/osps-baseline).
	// Catalogs not listed here are assumed to live under Publisher.
	CatalogNamespaces map[string]string  `json:"catalog-namespaces,omitempty" yaml:"catalog-namespaces,omitempty"`
	Payload           any                `json:"payload,omitempty" yaml:"payload,omitempty"`
	Evaluation_Suites []*EvaluationSuite `json:"evaluation-suites" yaml:"evaluation-suites"` // EvaluationSuite is a map of evaluations to their catalog names
	// contains filtered or unexported fields
}

EvaluationOrchestrator gets the plugin in position to execute the specified evaluation suites.

func (*EvaluationOrchestrator) AddEvaluationSuite added in v1.3.0

func (v *EvaluationOrchestrator) AddEvaluationSuite(catalogId string, loader DataLoader, steps map[string][]gemara.AssessmentStep) error

AddEvaluationSuite adds an evaluation suite for the given catalog ID.

Steps registered this way are named by symbol lookup at report time, which only resolves while the step is a plain function value. Plugins that adapt their steps through a closure should register via AddEvaluationSuiteTyped so the SDK captures each step's name before it is captured.

func (*EvaluationOrchestrator) AddEvaluationSuiteForAllCatalogs added in v1.22.0

func (v *EvaluationOrchestrator) AddEvaluationSuiteForAllCatalogs(loader DataLoader, steps map[string][]gemara.AssessmentStep) error

AddEvaluationSuiteForAllCatalogs registers the provided steps for every reference catalog that has been loaded via AddReferenceCatalogs. This allows plugin developers to define their step implementations once and have them automatically applied to all catalog versions.

func (*EvaluationOrchestrator) AddLoader added in v1.10.0

func (v *EvaluationOrchestrator) AddLoader(loader DataLoader)

AddLoader sets the data loader function for the orchestrator.

func (*EvaluationOrchestrator) AddReferenceCatalogs added in v1.10.0

func (v *EvaluationOrchestrator) AddReferenceCatalogs(dataDir string, files embed.FS) error

AddReferenceCatalogs loads reference catalogs from the embedded file system.

func (*EvaluationOrchestrator) AddRequiredVars added in v1.7.0

func (v *EvaluationOrchestrator) AddRequiredVars(vars []string)

AddRequiredVars sets the required configuration variables for the orchestrator.

func (*EvaluationOrchestrator) AddTargetBuilder added in v1.31.3

func (v *EvaluationOrchestrator) AddTargetBuilder(builder TargetBuilder)

AddTargetBuilder sets the function that identifies the evaluated resource in each emitted EvaluationLog. Fields left empty by the builder are backfilled from the service name so the log always identifies its target.

func (*EvaluationOrchestrator) Mobilize added in v1.3.0

func (v *EvaluationOrchestrator) Mobilize() error

Mobilize initializes the orchestrator and executes all evaluation suites.

func (*EvaluationOrchestrator) PublishManifest added in v1.31.0

func (v *EvaluationOrchestrator) PublishManifest() (PublishManifest, error)

PublishManifest assembles the publish descriptor from the orchestrator's Publisher + PluginName and its embedded reference catalogs: the coordinate is <Publisher>/<PluginName>, and each evaluated catalog is namespaced under its own owner (metadata.author.id), with id/version/control-ids read from the catalog itself. It fails closed (no manifest) when Publisher or PluginName is unset, when no catalogs are loaded, when a catalog has no version or controls, or when a catalog has no author id and no CatalogNamespaces override (we will not synthesize an owner — claiming an unattributed catalog under any namespace is a false claim). Every case is a "this plugin cannot be published yet" error the author fixes in code or catalog metadata. A plugin RUNS without these; it cannot be PUBLISHED without them.

func (*EvaluationOrchestrator) WriteResults added in v1.3.0

func (v *EvaluationOrchestrator) WriteResults() error

WriteResults writes the evaluation results to files in the configured output format.

type EvaluationSuite added in v1.0.0

type EvaluationSuite struct {
	Name   string        `json:"name" yaml:"name"`     // Name is the name of the suite
	Result gemara.Result `json:"result" yaml:"result"` // Result is Passed if all evaluations in the suite passed

	CatalogId string `json:"catalog-id" yaml:"catalog-id"` // CatalogId associates this suite with a catalog
	StartTime string `json:"start-time" yaml:"start-time"` // StartTime is the time the plugin started
	EndTime   string `json:"end-time" yaml:"end-time"`     // EndTime is the time the plugin ended

	CorruptedState bool `json:"corrupted-state" yaml:"corrupted-state"` // CorruptedState is true if any testSet failed to revert at the end of the evaluation

	EvaluationLog gemara.EvaluationLog `json:"control-evaluations" yaml:"control-evaluations"` // EvaluationLog is a slice of evaluations to be executed
	// contains filtered or unexported fields
}

EvaluationSuite contains the results of all EvaluationLog executions. Exported fields will be used in the final YAML or JSON output documents.

func (*EvaluationSuite) AddChangeManager added in v1.7.0

func (e *EvaluationSuite) AddChangeManager(cm *ChangeManager)

AddChangeManager sets up the change manager for the evaluation suite.

func (*EvaluationSuite) Evaluate added in v1.0.0

func (e *EvaluationSuite) Evaluate(serviceName string) error

Evaluate executes a list of EvaluationLog provided by a Plugin and customized by user config. Name is an arbitrary string that will be used to identify the EvaluationSuite.

func (*EvaluationSuite) GetAssessmentRequirements added in v1.7.0

func (e *EvaluationSuite) GetAssessmentRequirements() (map[string]*gemara.AssessmentRequirement, error)

GetAssessmentRequirements retrieves all assessment requirements from the catalog.

type LoaderTiming added in v1.32.0

type LoaderTiming struct {
	// Scope is "orchestrator" or "suite:<catalog-id>".
	Scope      string `json:"scope" yaml:"scope"`
	Func       string `json:"func" yaml:"func"`
	DurationNs int64  `json:"duration-ns" yaml:"duration-ns"`
}

LoaderTiming times one DataLoader invocation.

type PublishManifest added in v1.31.0

type PublishManifest struct {
	// Coordinate is the plugin's grc.store coordinate "<publisher>/<plugin_id>".
	Coordinate string `json:"coordinate"`
	// License is the plugin's declared publication license, the raw SPDX
	// expression from orchestrator.License. It is carried verbatim here; pvtr
	// validates and canonicalizes it at publish time (grc-store-protocol/spdx is
	// not imported into the plugin binary).
	License string `json:"license"`
	// Evaluates is the control-catalog linkage, deterministically ordered.
	Evaluates []EvaluatesDeclaration `json:"evaluates"`
}

PublishManifest is the machine-readable descriptor `pvtr publish` reads from a built plugin (via the publish-manifest subcommand) in place of CLI flags. The plugin coordinate is Publisher + PluginName. Each evaluated catalog's coordinate is the catalog's OWN owner — metadata.author.id — plus its id, so a plugin that evaluates someone else's catalog links to the real owner instead of falsely claiming it under the plugin's own namespace. A CatalogNamespaces override is available for the rare case where a catalog's author.id does not match the namespace it is published under on grc.store.

type RevertFunc added in v1.7.0

type RevertFunc func(interface{}) error

RevertFunc is a prepared function to revert a change after it has been applied.

type StepTiming added in v1.32.0

type StepTiming struct {
	ControlId     string `json:"control-id" yaml:"control-id"`
	RequirementId string `json:"requirement-id" yaml:"requirement-id"`
	StepIndex     int    `json:"step-index" yaml:"step-index"`
	Step          string `json:"step" yaml:"step"`
	Result        string `json:"result" yaml:"result"`
	DurationNs    int64  `json:"duration-ns" yaml:"duration-ns"`
}

StepTiming times one executed assessment step; steps that never ran produce no entry.

type SuiteTiming added in v1.32.0

type SuiteTiming struct {
	CatalogId  string       `json:"catalog-id" yaml:"catalog-id"`
	DurationNs int64        `json:"duration-ns" yaml:"duration-ns"`
	Steps      []StepTiming `json:"steps" yaml:"steps"`
}

SuiteTiming times one evaluation suite and its executed steps.

type TargetBuilder added in v1.31.3

type TargetBuilder func(*config.Config) gemara.Resource

TargetBuilder describes the resource a run evaluated, for the target field of the emitted gemara EvaluationLog. It receives the resolved config so it can derive real-world identity from service vars (e.g. "owner/repo" for a GitHub repository plugin). When no builder is registered the target falls back to the service name from the run configuration.

type TestSet

type TestSet func() (result gemara.ControlEvaluation)

TestSet is a function type that returns a control evaluation result.

type TypedAssessmentStep added in v1.32.0

type TypedAssessmentStep[T any] func(T) (gemara.Result, string, gemara.ConfidenceLevel)

TypedAssessmentStep is the shape of a payload-typed assessment step: the same contract as gemara.AssessmentStep, but receiving a concrete payload type T instead of an untyped any.

The type parameter S on the registration helpers below is constrained to ~TypedAssessmentStep[T] so a plugin may keep its own named step type (e.g. `type TypedStep func(data.Payload) (...)`) and pass its existing step maps unchanged.

Jump to

Keyboard shortcuts

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