Skip to content

Commit

Permalink
plugin: document framework
Browse files Browse the repository at this point in the history
  • Loading branch information
FiloSottile committed Jun 18, 2024
1 parent 35b060b commit f80cf4d
Showing 1 changed file with 57 additions and 0 deletions.
57 changes: 57 additions & 0 deletions plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ import (
"filippo.io/age/internal/format"
)

// TODO: implement interaction methods.
//
// // Can only be used during a Wrap or Unwrap invoked by Plugin.
// func (*Plugin) DisplayMessage(message string) error
// func (*Plugin) RequestValue(prompt string, secret bool) (string, error)
// func (*Plugin) Confirm(prompt, yes, no string) (choseYes bool, err error)

// TODO: add examples.

// Plugin is a framework for writing age plugins. It allows exposing regular
// [age.Recipient] and [age.Identity] implementations as plugins, and handles
// all the protocol details.
type Plugin struct {
name string
fs *flag.FlagSet
Expand All @@ -22,14 +34,24 @@ type Plugin struct {
identity func([]byte) (age.Identity, error)
}

// New creates a new Plugin with the given name.
//
// For example, a plugin named "frood" would be invoked as "age-plugin-frood".
func New(name string) (*Plugin, error) {
return &Plugin{name: name}, nil
}

// Name returns the name of the plugin.
func (p *Plugin) Name() string {
return p.name
}

// RegisterFlags registers the plugin's flags with the given [flag.FlagSet], or
// with the default [flag.CommandLine] if fs is nil. It must be called before
// [flag.Parse] and [Plugin.Main].
//
// This allows the plugin to expose additional flags when invoked manually, for
// example to implement a keygen mode.
func (p *Plugin) RegisterFlags(fs *flag.FlagSet) {
if fs == nil {
fs = flag.CommandLine
Expand All @@ -38,27 +60,54 @@ func (p *Plugin) RegisterFlags(fs *flag.FlagSet) {
p.sm = fs.String("age-plugin", "", "age-plugin state machine")
}

// HandleRecipient registers a function to parse recipients of the form
// age1name1... into [age.Recipient] values. data is the decoded Bech32 payload.
//
// If the returned Recipient implements [age.RecipientWithLabels], Plugin will
// use it and enforce consistency across every returned stanza in an execution.
// If the client supports labels, they will be passed through the protocol.
//
// It must be called before [Plugin.Main], and can be called at most once.
func (p *Plugin) HandleRecipient(f func(data []byte) (age.Recipient, error)) {
if p.recipient != nil {
panic("HandleRecipient called twice")
}
p.recipient = f
}

// HandleIdentityAsRecipient registers a function to parse identities of the
// form AGE-PLUGIN-NAME-1... into [age.Recipient] values, for when identities
// are used as recipients. data is the decoded Bech32 payload.
//
// If the returned Recipient implements [age.RecipientWithLabels], Plugin will
// use it and enforce consistency across every returned stanza in an execution.
// If the client supports labels, they will be passed through the protocol.
//
// It must be called before [Plugin.Main], and can be called at most once.
func (p *Plugin) HandleIdentityAsRecipient(f func(data []byte) (age.Recipient, error)) {
if p.idAsRecipient != nil {
panic("HandleIdentityAsRecipient called twice")
}
p.idAsRecipient = f
}

// HandleIdentity registers a function to parse identities of the form
// AGE-PLUGIN-NAME-1... into [age.Identity] values. data is the decoded Bech32
// payload.
//
// It must be called before [Plugin.Main], and can be called at most once.
func (p *Plugin) HandleIdentity(f func(data []byte) (age.Identity, error)) {
if p.identity != nil {
panic("HandleIdentity called twice")
}
p.identity = f
}

// Main runs the plugin protocol over stdin/stdout, and writes errors to stderr.
// It returns an exit code to pass to os.Exit.
//
// It automatically calls [Plugin.RegisterFlags] and [flag.Parse] if they were
// not called before.
func (p *Plugin) Main() int {
if p.fs == nil {
p.RegisterFlags(nil)
Expand All @@ -75,6 +124,10 @@ func (p *Plugin) Main() int {
return fatalf("unknown state machine %q", *p.sm)
}

// RecipientV1 implements the recipient-v1 state machine over stdin/stdout, and
// writes errors to stderr. It returns an exit code to pass to os.Exit.
//
// Most plugins should call [Plugin.Main] instead of this method.
func (p *Plugin) RecipientV1() int {
if p.recipient == nil && p.idAsRecipient == nil {
return fatalf("recipient-v1 not supported")
Expand Down Expand Up @@ -241,6 +294,10 @@ func checkLabels(ll, labels []string) error {
return nil
}

// IdentityV1 implements the identity-v1 state machine over stdin/stdout, and
// writes errors to stderr. It returns an exit code to pass to os.Exit.
//
// Most plugins should call [Plugin.Main] instead of this method.
func (p *Plugin) IdentityV1() int {
if p.identity == nil {
return fatalf("identity-v1 not supported")
Expand Down

0 comments on commit f80cf4d

Please sign in to comment.