Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: adding more required functionality #5

Merged
merged 7 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 11 additions & 67 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package cli
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"reflect"
Expand All @@ -19,24 +20,27 @@ import (
"github.com/notaryproject/notation-plugin-framework-go/plugin"
)

// CLI struct is used to create an executable for plugin.
type CLI struct {
name string
pl plugin.Plugin
logger log.Logger
}

// New creates a new CLI using given plugin
func New(executableName string, pl plugin.Plugin) *CLI {
return NewWithLogger(executableName, pl, &discardLogger{})
func New(pl plugin.Plugin) (*CLI, error) {
return NewWithLogger(pl, &discardLogger{})
}

// NewWithLogger creates a new CLI using given plugin and logger
func NewWithLogger(executableName string, pl plugin.Plugin, l log.Logger) *CLI {
func NewWithLogger(pl plugin.Plugin, l log.Logger) (*CLI, error) {
if pl == nil {
return nil, errors.New("plugin cannot be nil")
}

return &CLI{
name: executableName,
pl: pl,
logger: l,
}
}, nil
priteshbandi marked this conversation as resolved.
Show resolved Hide resolved
}

// Execute is main controller that reads/validates commands, parses input, executes relevant plugin functions
Expand Down Expand Up @@ -113,7 +117,7 @@ func (c *CLI) printVersion(ctx context.Context) {
func (c *CLI) validateArgs(ctx context.Context, args []string) {
md := c.getMetadata(ctx, c.pl)
if !(len(args) == 2 && slices.Contains(getValidArgs(md), args[1])) {
deliverError(fmt.Sprintf("Invalid command, valid choices are: %s %s", c.name, getValidArgsString(md)))
deliverError(fmt.Sprintf("Invalid command, valid commands are: %s", getValidArgsString(md)))
}
}

Expand Down Expand Up @@ -155,63 +159,3 @@ func (c *CLI) marshalResponse(response any, err error) (string, *plugin.Error) {
c.logger.Debugf("%s response: %s", reflect.TypeOf(response), jsonResponse)
return string(jsonResponse), nil
}

// deferStdout is used to make sure that nothing get emitted to stdout and stderr until intentionally rescued.
// This is required to make sure that the plugin or its dependency doesn't interfere with notation <-> plugin communication
func deferStdout() func() {
// Ignoring error because we don't want plugin to fail if `os.DevNull` is misconfigured.
null, _ := os.Open(os.DevNull)
sout := os.Stdout
serr := os.Stderr
os.Stdout = null
os.Stderr = null

return func() {
err := null.Close()
if err != nil {
return
}
os.Stdout = sout
os.Stderr = serr
}
}

// discardLogger implements Logger but logs nothing. It is used when user
// disenabled logging option in notation, i.e. loggerKey is not in the context.
type discardLogger struct{}

func (dl *discardLogger) Debug(_ ...interface{}) {
}

func (dl *discardLogger) Debugf(_ string, _ ...interface{}) {
}

func (dl *discardLogger) Debugln(_ ...interface{}) {
}

func (dl *discardLogger) Info(_ ...interface{}) {
}

func (dl *discardLogger) Infof(_ string, _ ...interface{}) {
}

func (dl *discardLogger) Infoln(_ ...interface{}) {
}

func (dl *discardLogger) Warn(_ ...interface{}) {
}

func (dl *discardLogger) Warnf(_ string, _ ...interface{}) {
}

func (dl *discardLogger) Warnln(_ ...interface{}) {
}

func (dl *discardLogger) Error(_ ...interface{}) {
}

func (dl *discardLogger) Errorf(_ string, _ ...interface{}) {
}

func (dl *discardLogger) Errorln(_ ...interface{}) {
}
10 changes: 5 additions & 5 deletions cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/notaryproject/notation-plugin-framework-go/plugin"
)

var cli = New("mockCli", &mockPlugin{})
var cli, _ = New(&mockPlugin{})

func TestMarshalResponse(t *testing.T) {
res := plugin.GenerateEnvelopeResponse{
Expand All @@ -36,13 +36,13 @@ func TestMarshalResponse(t *testing.T) {
func TestMarshalResponseError(t *testing.T) {

_, err := cli.marshalResponse(nil, fmt.Errorf("expected error thrown"))
assertErr(t, err, plugin.CodeGeneric)
assertErr(t, err, plugin.ErrorCodeGeneric)

_, err = cli.marshalResponse(nil, plugin.NewValidationError("expected validation error thrown"))
assertErr(t, err, plugin.CodeValidation)
assertErr(t, err, plugin.ErrorCodeValidation)

_, err = cli.marshalResponse(make(chan int), nil)
assertErr(t, err, plugin.CodeGeneric)
assertErr(t, err, plugin.ErrorCodeGeneric)
}

func TestUnmarshalRequest(t *testing.T) {
Expand Down Expand Up @@ -129,7 +129,7 @@ func setupReader(content string) func() {
}
}

func assertErr(t *testing.T, err error, code plugin.Code) {
func assertErr(t *testing.T, err error, code plugin.ErrorCode) {
if plgErr, ok := err.(*plugin.Error); ok {
if reflect.DeepEqual(code, plgErr.ErrCode) {
return
Expand Down
75 changes: 67 additions & 8 deletions cli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package cli
import (
"fmt"
"os"
"sort"
"strings"

"github.com/notaryproject/notation-plugin-framework-go/internal/slices"
Expand All @@ -16,27 +15,87 @@ func getValidArgsString(md *plugin.GetMetadataResponse) string {

// getValidArgs returns list of valid arguments depending upon the plugin capabilities
func getValidArgs(md *plugin.GetMetadataResponse) []string {
opts := []string{
"get-plugin-metadata", "version",
args := []string{
string(plugin.CommandGetMetadata), string(plugin.Version),
}

if slices.Contains(md.Capabilities, plugin.CapabilitySignatureGenerator) {
opts = append(opts, "generate-signature", "describe-key")
args = append(args, string(plugin.CommandGenerateSignature), string(plugin.CommandDescribeKey))
}

if slices.Contains(md.Capabilities, plugin.CapabilityEnvelopeGenerator) {
opts = append(opts, "generate-envelope")
args = append(args, string(plugin.CommandGenerateEnvelope))
}

if slices.Contains(md.Capabilities, plugin.CapabilityTrustedIdentityVerifier) || slices.Contains(md.Capabilities, plugin.CapabilityRevocationCheckVerifier) {
opts = append(opts, "verify-signature")
args = append(args, string(plugin.CommandVerifySignature))
}
sort.Strings(opts)
return opts

return args
}

// deliverError print to standard error and then return nonzero exit code
func deliverError(message string) {
_, _ = fmt.Fprintln(os.Stderr, message)
os.Exit(1)
}

// deferStdout is used to make sure that nothing get emitted to stdout and stderr until intentionally rescued.
// This is required to make sure that the plugin or its dependency doesn't interfere with notation <-> plugin communication
func deferStdout() func() {
// Ignoring error because we don't want plugin to fail if `os.DevNull` is misconfigured.
null, _ := os.Open(os.DevNull)
sout := os.Stdout
serr := os.Stderr
os.Stdout = null
os.Stderr = null

return func() {
err := null.Close()
if err != nil {
return
}
os.Stdout = sout
os.Stderr = serr
}
}

// discardLogger implements Logger but logs nothing. It is used when user
// disenabled logging option in notation, i.e. loggerKey is not in the context.
type discardLogger struct{}

func (dl *discardLogger) Debug(_ ...interface{}) {
}

func (dl *discardLogger) Debugf(_ string, _ ...interface{}) {
}

func (dl *discardLogger) Debugln(_ ...interface{}) {
}

func (dl *discardLogger) Info(_ ...interface{}) {
}

func (dl *discardLogger) Infof(_ string, _ ...interface{}) {
}

func (dl *discardLogger) Infoln(_ ...interface{}) {
}

func (dl *discardLogger) Warn(_ ...interface{}) {
}

func (dl *discardLogger) Warnf(_ string, _ ...interface{}) {
}

func (dl *discardLogger) Warnln(_ ...interface{}) {
}

func (dl *discardLogger) Error(_ ...interface{}) {
}

func (dl *discardLogger) Errorf(_ string, _ ...interface{}) {
}

func (dl *discardLogger) Errorln(_ ...interface{}) {
}
Binary file added example/example
Binary file not shown.
9 changes: 7 additions & 2 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ func main() {
// Initialize plugin
plugin, err := NewExamplePlugin()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Failed to initialize plugin")
_, _ = fmt.Fprintf(os.Stderr, "failed to initialize plugin: %v\n", err)
os.Exit(2)
}

// Create executable
cli.New("example", plugin).Execute(ctx, os.Args)
pluginCli, err := cli.New(plugin)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to create executable: %v\n", err)
os.Exit(3)
}
pluginCli.Execute(ctx, os.Args)
}
34 changes: 18 additions & 16 deletions plugin/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import (
"fmt"
)

type Code string
type ErrorCode string

const (
CodeValidation Code = "VALIDATION_ERROR"
CodeUnsupportedContractVersion Code = "UNSUPPORTED_CONTRACT_VERSION"
CodeAccessDenied Code = "ACCESS_DENIED"
CodeThrottled Code = "THROTTLED"
CodeGeneric Code = "ERROR"
ErrorCodeValidation ErrorCode = "VALIDATION_ERROR"
ErrorCodeUnsupportedContractVersion ErrorCode = "UNSUPPORTED_CONTRACT_VERSION"
ErrorCodeAccessDenied ErrorCode = "ACCESS_DENIED"
ErrorCodeTimeout ErrorCode = "TIMEOUT"
ErrorCodeThrottled ErrorCode = "THROTTLED"
ErrorCodeGeneric ErrorCode = "ERROR"
)

const (
Expand All @@ -23,39 +24,40 @@ const (
// Error is used when the signature associated is no longer
// valid.
type Error struct {
ErrCode Code `json:"errorCode"`
Msg string `json:"errorMessage"`
ErrCode ErrorCode `json:"errorCode"`
Message string `json:"errorMessage,omitempty"`
Metadata map[string]string `json:"errorMetadata,omitempty"`
}

func NewError(code Code, msg string) *Error {
func NewError(code ErrorCode, msg string) *Error {
return &Error{
ErrCode: code,
Msg: msg,
Message: msg,
}
}

func NewGenericError(msg string) *Error {
return NewError(CodeGeneric, msg)
return NewError(ErrorCodeGeneric, msg)
}

func NewGenericErrorf(format string, msg string) *Error {
return NewError(CodeGeneric, fmt.Sprintf(format, msg))
return NewError(ErrorCodeGeneric, fmt.Sprintf(format, msg))
}

func NewUnsupportedError(msg string) *Error {
return NewError(CodeValidation, msg+" is not supported")
return NewError(ErrorCodeValidation, msg+" is not supported")
}

func NewValidationError(msg string) *Error {
return NewError(CodeValidation, msg)
return NewError(ErrorCodeValidation, msg)
}

func NewValidationErrorf(format string, msg string) *Error {
return NewError(CodeValidation, fmt.Sprintf(format, msg))
return NewError(ErrorCodeValidation, fmt.Sprintf(format, msg))
}

func NewUnsupportedContractVersionError(version string) *Error {
return NewError(CodeUnsupportedContractVersion, fmt.Sprintf("%q is not a supported notary plugin contract version", version))
return NewError(ErrorCodeUnsupportedContractVersion, fmt.Sprintf("%q is not a supported notary plugin contract version", version))
}

func NewJSONParsingError(msg string) *Error {
Expand Down
11 changes: 11 additions & 0 deletions plugin/proto.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
// and notation external plugin.
package plugin

// BinaryPrefix is the prefix required on all plugin binary names.
const BinaryPrefix = "notation-"

// ContractVersion is the <major>.<minor> version of the plugin contract.
const ContractVersion = "1.0"

// Capability is a feature available in the plugin contract.
type Capability string

Expand All @@ -26,6 +32,11 @@ const (
// Command is a CLI command available in the plugin contract.
type Command string

// Request defines a plugin request, which is always associated to a command.
type Request interface {
Command() Command
}
priteshbandi marked this conversation as resolved.
Show resolved Hide resolved

priteshbandi marked this conversation as resolved.
Show resolved Hide resolved
const (
// CommandGetMetadata is the name of the plugin command
// which must be supported by every plugin and returns the
Expand Down