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

update: updated notation verify command based on spec #418

Merged
merged 20 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from 16 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
6 changes: 3 additions & 3 deletions cmd/notation/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var (
flagUsername = &pflag.Flag{
Name: "username",
Shorthand: "u",
Usage: "username for registry operations (if not specified, defaults to $NOTATION_USERNAME)",
Usage: "username for registry operations (default to $NOTATION_USERNAME if not specified)",
}
setflagUsername = func(fs *pflag.FlagSet, p *string) {
fs.StringVarP(p, flagUsername.Name, flagUsername.Shorthand, "", flagUsername.Usage)
Expand All @@ -25,15 +25,15 @@ var (
flagPassword = &pflag.Flag{
Name: "password",
Shorthand: "p",
Usage: "password for registry operations (if not specified, defaults to $NOTATION_PASSWORD)",
Usage: "password for registry operations (default to $NOTATION_PASSWORD if not specified)",
}
setFlagPassword = func(fs *pflag.FlagSet, p *string) {
fs.StringVarP(p, flagPassword.Name, flagPassword.Shorthand, "", flagPassword.Usage)
}

flagPlainHTTP = &pflag.Flag{
Name: "plain-http",
Usage: "Registry access via plain HTTP",
Usage: "registry access via plain HTTP",
DefValue: "false",
}
setFlagPlainHTTP = func(fs *pflag.FlagSet, p *bool) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/notation/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type keyAddOpts struct {
name string
plugin string
id string
pluginConfig string
pluginConfig []string
isDefault bool
keyPath string
certPath string
Expand Down
6 changes: 3 additions & 3 deletions cmd/notation/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ func TestKeyAddCommand_BasicArgs(t *testing.T) {
id: "pluginid",
keyPath: "keypath",
certPath: "certpath",
pluginConfig: "pluginconfig",
pluginConfig: []string{"pluginconfig"},
}
if err := cmd.ParseFlags([]string{
"-n", expected.name,
"--plugin", expected.plugin,
"--id", expected.id,
"-c", expected.pluginConfig,
"-c", "pluginconfig",
expected.keyPath, expected.certPath}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := cmd.Args(cmd, cmd.Flags().Args()); err != nil {
t.Fatalf("Parse Args failed: %v", err)
}
if *expected != *opts {
if !reflect.DeepEqual(*expected, *opts) {
t.Fatalf("Expect key add opts: %v, got: %v", expected, opts)
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/notation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func main() {
pluginCommand(),
loginCommand(nil),
logoutCommand(nil))
cmd.PersistentFlags().Bool(flagPlainHTTP.Name, false, flagPlainHTTP.Usage)

if err := cmd.Execute(); err != nil {
log.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/notation/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type signOpts struct {
timestamp string
expiry time.Duration
originReference string
pluginConfig string
pluginConfig []string
reference string
}

Expand Down
28 changes: 17 additions & 11 deletions cmd/notation/sign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"reflect"
"testing"
"time"

Expand Down Expand Up @@ -29,20 +30,22 @@ func TestSignCommand_BasicArgs(t *testing.T) {
CertFile: "certfile",
EnvelopeType: envelope.JWS,
},
pluginConfig: []string{"key0=val0"},
}
if err := command.ParseFlags([]string{
expected.reference,
"-u", expected.Username,
"--password", expected.Password,
"--key", expected.Key,
"--key-file", expected.KeyFile,
"--cert-file", expected.CertFile}); err != nil {
"--cert-file", expected.CertFile,
"--plugin-config", "key0=val0"}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
t.Fatalf("Parse args failed: %v", err)
}
if *expected != *opts {
if !reflect.DeepEqual(*expected, *opts) {
t.Fatalf("Expect sign opts: %v, got: %v", expected, opts)
}
}
Expand All @@ -69,7 +72,8 @@ func TestSignCommand_MoreArgs(t *testing.T) {
CertFile: "certfile",
EnvelopeType: envelope.COSE,
},
expiry: 24 * time.Hour,
expiry: 24 * time.Hour,
pluginConfig: []string{"key0=val0"},
}
if err := command.ParseFlags([]string{
expected.reference,
Expand All @@ -82,13 +86,14 @@ func TestSignCommand_MoreArgs(t *testing.T) {
"--media-type", expected.MediaType,
"-l",
"--envelope-type", expected.SignerFlagOpts.EnvelopeType,
"--expiry", expected.expiry.String()}); err != nil {
"--expiry", expected.expiry.String(),
"--plugin-config", "key0=val0"}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
t.Fatalf("Parse args failed: %v", err)
}
if *expected != *opts {
if !reflect.DeepEqual(*expected, *opts) {
t.Fatalf("Expect sign opts: %v, got: %v", expected, opts)
}
}
Expand All @@ -111,7 +116,7 @@ func TestSignCommand_CorrectConfig(t *testing.T) {
EnvelopeType: envelope.JWS,
},
expiry: 365 * 24 * time.Hour,
pluginConfig: "key0=val0,key1=val1,key2=val2",
pluginConfig: []string{"key0=val0", "key1=val1"},
originReference: "originref",
}
if err := command.ParseFlags([]string{
Expand All @@ -124,23 +129,24 @@ func TestSignCommand_CorrectConfig(t *testing.T) {
"--local",
"--envelope-type", expected.SignerFlagOpts.EnvelopeType,
"--expiry", expected.expiry.String(),
"--pluginConfig", expected.pluginConfig}); err != nil {
"--plugin-config", "key0=val0",
"--plugin-config", "key1=val1"}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
t.Fatalf("Parse args failed: %v", err)
}
if *expected != *opts {
if !reflect.DeepEqual(*expected, *opts) {
t.Fatalf("Expect sign opts: %v, got: %v", expected, opts)
}
config, err := cmd.ParseFlagPluginConfig(opts.pluginConfig)
if err != nil {
t.Fatalf("Parse plugin Config flag failed: %v", err)
}
if len(config) != 3 {
t.Fatalf("Expect plugin config number: %v, got: %v ", 3, len(config))
if len(config) != 2 {
t.Fatalf("Expect plugin config number: %v, got: %v ", 2, len(config))
}
for i := 0; i < 3; i++ {
for i := 0; i < 2; i++ {
key, val := fmt.Sprintf("key%v", i), fmt.Sprintf("val%v", i)
configVal, ok := config[key]
if !ok {
Expand Down
10 changes: 5 additions & 5 deletions cmd/notation/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
type verifyOpts struct {
SecureFlagOpts
reference string
pluginConfig string
pluginConfig []string
}

func verifyCommand(opts *verifyOpts) *cobra.Command {
Expand All @@ -26,12 +26,12 @@ func verifyCommand(opts *verifyOpts) *cobra.Command {
}
command := &cobra.Command{
Use: "verify [flags] <reference>",
Short: "Verifies OCI Artifacts",
Long: `Verifies OCI Artifacts
Short: "Verify Artifacts",
Long: `Verify signatures associated with the artifact.

Prerequisite: a trusted certificate needs to be generated or added using the command "notation cert".

notation verify [--plugin-config <key>=<value>,...] [--username <username>] [--password <password>] <reference>`,
notation verify [--plugin-config <key>=<value>...] [--username <username>] [--password <password>] <reference>`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing reference")
Expand All @@ -44,7 +44,7 @@ notation verify [--plugin-config <key>=<value>,...] [--username <username>] [--p
},
}
opts.ApplyFlags(command.Flags())
command.Flags().StringVarP(&opts.pluginConfig, "plugin-config", "c", "", "list of comma-separated {key}={value} pairs that are passed as is to the plugin")
command.Flags().StringArrayVarP(&opts.pluginConfig, "plugin-config", "c", []string{}, "{key}={value} pairs that are passed as is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values")
return command
}

Expand Down
9 changes: 6 additions & 3 deletions cmd/notation/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ func TestVerifyCommand_BasicArgs(t *testing.T) {
Username: "user",
Password: "password",
},
pluginConfig: []string{"key1=val1"},
}
if err := command.ParseFlags([]string{
expected.reference,
"--username", expected.Username,
"--password", expected.Password}); err != nil {
"--password", expected.Password,
"--plugin-config", "key1=val1"}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
Expand All @@ -37,12 +39,13 @@ func TestVerifyCommand_MoreArgs(t *testing.T) {
SecureFlagOpts: SecureFlagOpts{
PlainHTTP: true,
},
pluginConfig: "key1=val1,key2=val2",
pluginConfig: []string{"key1=val1", "key2=val2"},
}
if err := command.ParseFlags([]string{
expected.reference,
"--plain-http",
"--plugin-config", expected.pluginConfig}); err != nil {
"--plugin-config", "key1=val1",
"--plugin-config", "key2=val2"}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
Expand Down
105 changes: 12 additions & 93 deletions internal/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
package cmd

import (
"errors"
"fmt"
"strings"
"time"
Expand Down Expand Up @@ -73,12 +72,12 @@ var (
}

PflagPluginConfig = &pflag.Flag{
Name: "pluginConfig",
Name: "plugin-config",
Shorthand: "c",
Usage: "list of comma-separated {key}={value} pairs that are passed as is to the plugin, refer plugin documentation to set appropriate values",
Usage: "{key}={value} pairs that are passed as is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values",
}
SetPflagPluginConfig = func(fs *pflag.FlagSet, p *string) {
fs.StringVarP(p, PflagPluginConfig.Name, PflagPluginConfig.Shorthand, "", PflagPluginConfig.Usage)
SetPflagPluginConfig = func(fs *pflag.FlagSet, p *[]string) {
fs.StringArrayVarP(p, PflagPluginConfig.Name, PflagPluginConfig.Shorthand, []string{}, PflagPluginConfig.Usage)
patrickzheng200 marked this conversation as resolved.
Show resolved Hide resolved
}
)

Expand All @@ -88,94 +87,14 @@ type KeyValueSlice interface {
String() string
}

func ParseFlagPluginConfig(config string) (map[string]string, error) {
pluginConfig, err := ParseKeyValueListFlag(config)
if err != nil {
return nil, fmt.Errorf("could not parse %q as value for flag %s: %s", pluginConfig, PflagPluginConfig.Name, err)
}
return pluginConfig, nil
}

func ParseKeyValueListFlag(val string) (map[string]string, error) {
if val == "" {
return nil, nil
}
flags, err := splitQuoted(val)
if err != nil {
return nil, err
}
m := make(map[string]string, len(flags))
for _, c := range flags {
c := strings.TrimSpace(c)
if c == "" {
return nil, fmt.Errorf("empty entry: %q", c)
}
if k, v, ok := strings.Cut(c, "="); ok {
k := strings.TrimSpace(k)
v := strings.TrimSpace(v)
if k == "" || v == "" {
return nil, errors.New("empty key value")
}
if _, exist := m[k]; exist {
return nil, fmt.Errorf("duplicated key: %q", k)
}
m[k] = v
} else {
return nil, fmt.Errorf("malformed entry: %q", c)
func ParseFlagPluginConfig(config []string) (map[string]string, error) {
pluginConfig := make(map[string]string, len(config))
for _, pair := range config {
index := strings.Index(pair, "=")
patrickzheng200 marked this conversation as resolved.
Show resolved Hide resolved
if index <= 0 {
return nil, fmt.Errorf("could not parse flag %s: key-value pair requires \"=\" as separator", PflagPluginConfig.Name)
}
pluginConfig[pair[:index]] = pair[index+1:]
}
return m, nil
}

// splitQuoted splits the string s around each instance of one or more consecutive
// comma characters while taking into account quotes and escaping, and
// returns an array of substrings of s or an empty list if s is empty.
// Single quotes and double quotes are recognized to prevent splitting within the
// quoted region, and are removed from the resulting substrings. If a quote in s
// isn't closed err will be set and r will have the unclosed argument as the
// last element. The backslash is used for escaping.
//
// For example, the following string:
//
// `a,b:"c,d",'e''f',,"g\""`
//
// Would be parsed as:
//
// []string{"a", "b:c,d", "ef", "", `g"`}
func splitQuoted(s string) (r []string, err error) {
var args []string
arg := make([]rune, len(s))
escaped := false
quote := '\x00'
i := 0
for _, r := range s {
switch {
case escaped:
escaped = false
case r == '\\':
escaped = true
continue
case quote != 0:
if r == quote {
quote = 0
continue
}
case r == '"' || r == '\'':
quote = r
continue
case r == ',':
args = append(args, string(arg[:i]))
i = 0
continue
}
arg[i] = r
i++
}
args = append(args, string(arg[:i]))
if quote != 0 {
err = errors.New("unclosed quote")
} else if escaped {
err = errors.New("unfinished escaping")
}
return args, err
return pluginConfig, nil
}
Loading