From 1e21b659b6169e71d28595bcd4cf76e2f12076a9 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 6 Dec 2023 10:43:59 +0000 Subject: [PATCH 01/33] feat: improve error output of `oras push` Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 24 ++++++++++++++++++++++++ cmd/oras/root/push.go | 12 +++++++++++- test/e2e/suite/command/push.go | 8 ++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 2c3fd8bf5..5ac6b6ec1 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -21,6 +21,30 @@ import ( "oras.land/oras-go/v2/registry" ) +type output struct { + description, usage, suggestion string +} + +func (o *output) Error() string { + ret := o.description + if o.usage != "" { + ret += fmt.Sprintf("\nUsage: %s", o.usage) + } + if o.suggestion != "" { + ret += fmt.Sprintf("\n%s", o.suggestion) + } + return ret +} + +// NewOuput creates a new error for CLI output. +func NewOuput(description, usage, suggestion string) error { + return &output{ + description: description, + usage: usage, + suggestion: suggestion, + } +} + // NewErrEmptyTagOrDigest creates a new error based on the reference string. func NewErrEmptyTagOrDigest(ref registry.Reference) error { return NewErrEmptyTagOrDigestStr(ref.String()) diff --git a/cmd/oras/root/push.go b/cmd/oras/root/push.go index c9dc0a7f7..3091df057 100644 --- a/cmd/oras/root/push.go +++ b/cmd/oras/root/push.go @@ -32,6 +32,7 @@ import ( "oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/track" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/fileref" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/contentutil" @@ -100,7 +101,16 @@ Example - Push file "hi.txt" with multiple tags and concurrency level tuned: Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with tag 'test': oras push --oci-layout layout-dir:test hi.txt `, - Args: cobra.MinimumNArgs(1), + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return oerrors.NewOuput( + fmt.Sprintf(`%q requires at least 1 argument`, cmd.CommandPath()), + cmd.Use, + fmt.Sprintf(`You need to at least specify one arguments as the destination for pushing. Run "%s -h" for more options and examples`, cmd.CommandPath()), + ) + } + return nil + }, PreRunE: func(cmd *cobra.Command, args []string) error { refs := strings.Split(args[0], ",") opts.RawReference = refs[0] diff --git a/test/e2e/suite/command/push.go b/test/e2e/suite/command/push.go index 81bd2ddad..adb1684b8 100644 --- a/test/e2e/suite/command/push.go +++ b/test/e2e/suite/command/push.go @@ -41,6 +41,14 @@ var _ = Describe("ORAS beginners:", func() { gomega.Expect(out).Should(gbytes.Say("--image-spec string\\s+%s", regexp.QuoteMeta(feature.Experimental.Mark))) }) + It("should show detailed error description if no argument provided", func() { + err := ORAS("push").Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: push")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras push -h"`)) + }) + It("should fail to use --config and --artifact-type at the same time for OCI spec v1.0 registry", func() { tempDir := PrepareTempFiles() repo := pushTestRepo("no-mediatype") From 5812d73e29a181ed7e4edaff6740ec643131f42a Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 7 Dec 2023 06:01:44 +0000 Subject: [PATCH 02/33] improve oras cp Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 15 +++++++++++++++ cmd/oras/root/attach.go | 8 ++++++-- cmd/oras/root/cp.go | 5 ++++- cmd/oras/root/push.go | 13 +++---------- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 5ac6b6ec1..a939b2c30 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -18,6 +18,7 @@ package errors import ( "fmt" + "github.com/spf13/cobra" "oras.land/oras-go/v2/registry" ) @@ -45,6 +46,20 @@ func NewOuput(description, usage, suggestion string) error { } } +// ArgsChecker checks the args with the checker function. +func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra.PositionalArgs { + return func(cmd *cobra.Command, args []string) error { + if ok, text := checker(args); !ok { + return NewOuput( + fmt.Sprintf(`%q requires %s but got %d`, cmd.CommandPath(), text, len(args)), + cmd.Use, + fmt.Sprintf(`You need to specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), + ) + } + return nil + } +} + // NewErrEmptyTagOrDigest creates a new error based on the reference string. func NewErrEmptyTagOrDigest(ref registry.Reference) error { return NewErrEmptyTagOrDigestStr(ref.String()) diff --git a/cmd/oras/root/attach.go b/cmd/oras/root/attach.go index c485c1927..8bb62026c 100644 --- a/cmd/oras/root/attach.go +++ b/cmd/oras/root/attach.go @@ -28,6 +28,7 @@ import ( "oras.land/oras-go/v2/content/file" "oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras/cmd/oras/internal/display/track" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/graph" "oras.land/oras/internal/registryutil" @@ -75,8 +76,11 @@ Example - Attach file 'hi.txt' and export the pushed manifest to 'manifest.json' Example - Attach file to the manifest tagged 'v1' in an OCI image layout folder 'layout-dir': oras attach --oci-layout --artifact-type doc/example layout-dir:v1 hi.txt - `, - Args: cobra.MinimumNArgs(1), +`, + Args: oerrors.ArgsChecker( + func(args []string) (bool, string) { + return len(args) >= 1, "at least 1 argument" + }, "the destination artifact for attaching."), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] opts.FileRefs = args[1:] diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 2bd463b8c..eb7db94cf 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -31,6 +31,7 @@ import ( "oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/track" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/docker" "oras.land/oras/internal/graph" @@ -83,7 +84,9 @@ Example - Copy an artifact with multiple tags: Example - Copy an artifact with multiple tags with concurrency tuned: oras cp --concurrency 10 localhost:5000/net-monitor:v1 localhost:5000/net-monitor-copy:tag1,tag2,tag3 `, - Args: cobra.ExactArgs(2), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) != 2, "exactly 2 arguments" + }, "the source and destination for copying."), PreRunE: func(cmd *cobra.Command, args []string) error { opts.From.RawReference = args[0] refs := strings.Split(args[1], ",") diff --git a/cmd/oras/root/push.go b/cmd/oras/root/push.go index 3091df057..25a6ba191 100644 --- a/cmd/oras/root/push.go +++ b/cmd/oras/root/push.go @@ -101,16 +101,9 @@ Example - Push file "hi.txt" with multiple tags and concurrency level tuned: Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with tag 'test': oras push --oci-layout layout-dir:test hi.txt `, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return oerrors.NewOuput( - fmt.Sprintf(`%q requires at least 1 argument`, cmd.CommandPath()), - cmd.Use, - fmt.Sprintf(`You need to at least specify one arguments as the destination for pushing. Run "%s -h" for more options and examples`, cmd.CommandPath()), - ) - } - return nil - }, + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) >= 1, "at least 1 argument" + }, "the destination for pushing"), PreRunE: func(cmd *cobra.Command, args []string) error { refs := strings.Split(args[0], ",") opts.RawReference = refs[0] From fbdc31ec06f4d7bca367c9e630eb3a01aeaeeb73 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 7 Dec 2023 06:25:17 +0000 Subject: [PATCH 03/33] bug fix & add e2e test Signed-off-by: Billy Zha --- cmd/oras/root/cp.go | 4 ++-- test/e2e/suite/command/attach.go | 8 ++++++++ test/e2e/suite/command/cp.go | 8 ++++++++ test/e2e/suite/command/push.go | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index eb7db94cf..17fd35956 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -85,8 +85,8 @@ Example - Copy an artifact with multiple tags with concurrency tuned: oras cp --concurrency 10 localhost:5000/net-monitor:v1 localhost:5000/net-monitor-copy:tag1,tag2,tag3 `, Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) != 2, "exactly 2 arguments" - }, "the source and destination for copying."), + return len(args) == 2, "exactly 2 arguments" + }, "the source and destination for copying"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.From.RawReference = args[0] refs := strings.Split(args[1], ",") diff --git a/test/e2e/suite/command/attach.go b/test/e2e/suite/command/attach.go index ef5c51d2e..aec6fb248 100644 --- a/test/e2e/suite/command/attach.go +++ b/test/e2e/suite/command/attach.go @@ -64,6 +64,14 @@ var _ = Describe("ORAS beginners:", func() { ORAS("attach", "--artifact-type", "oras/test", RegistryRef(ZOTHost, ImageRepo, foobar.Tag), "--distribution-spec", "???"). ExpectFailure().MatchErrKeyWords("unknown distribution specification flag").Exec() }) + + It("should show detailed error description if no argument provided", func() { + err := ORAS("attach").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: attach")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras attach -h"`)) + }) }) }) diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go index 4191589bd..f577dd492 100644 --- a/test/e2e/suite/command/cp.go +++ b/test/e2e/suite/command/cp.go @@ -59,6 +59,14 @@ var _ = Describe("ORAS beginners:", func() { It("should fail when source doesn't exist", func() { ORAS("cp", RegistryRef(ZOTHost, ImageRepo, "i-dont-think-this-tag-exists"), RegistryRef(ZOTHost, cpTestRepo("nonexistent-source"), "")).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) + + It("should show detailed error description if no argument provided", func() { + err := ORAS("cp").ExpectFailure().Exec().Err + Expect(err).Should(gbytes.Say("Error")) + Expect(err).Should(gbytes.Say("\nUsage: cp")) + Expect(err).Should(gbytes.Say("\n")) + Expect(err).Should(gbytes.Say(`Run "oras cp -h"`)) + }) }) }) diff --git a/test/e2e/suite/command/push.go b/test/e2e/suite/command/push.go index adb1684b8..6f7fd0cf0 100644 --- a/test/e2e/suite/command/push.go +++ b/test/e2e/suite/command/push.go @@ -42,7 +42,7 @@ var _ = Describe("ORAS beginners:", func() { }) It("should show detailed error description if no argument provided", func() { - err := ORAS("push").Exec().Err + err := ORAS("push").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) gomega.Expect(err).Should(gbytes.Say("\nUsage: push")) gomega.Expect(err).Should(gbytes.Say("\n")) From 7a5a38838abb683781e3c3b6549534747d2c770c Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 7 Dec 2023 06:50:54 +0000 Subject: [PATCH 04/33] improve discover command Signed-off-by: Billy Zha --- cmd/oras/root/discover.go | 5 ++++- test/e2e/suite/command/cp.go | 8 ++++++++ test/e2e/suite/command/discover.go | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/cmd/oras/root/discover.go b/cmd/oras/root/discover.go index 801710ead..1800ce793 100644 --- a/cmd/oras/root/discover.go +++ b/cmd/oras/root/discover.go @@ -28,6 +28,7 @@ import ( "gopkg.in/yaml.v3" "oras.land/oras-go/v2" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/graph" "oras.land/oras/internal/tree" @@ -73,7 +74,9 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout oras discover --oci-layout layout-dir:v1 oras discover --oci-layout -v -o tree layout-dir:v1 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 arguments" + }, "the target artifact to discover referrers from"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] return option.Parse(&opts) diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go index f577dd492..0d85daa98 100644 --- a/test/e2e/suite/command/cp.go +++ b/test/e2e/suite/command/cp.go @@ -67,6 +67,14 @@ var _ = Describe("ORAS beginners:", func() { Expect(err).Should(gbytes.Say("\n")) Expect(err).Should(gbytes.Say(`Run "oras cp -h"`)) }) + + It("should show detailed error description if more than 2 arguments are provided", func() { + err := ORAS("cp", "foo", "bar", "buz").ExpectFailure().Exec().Err + Expect(err).Should(gbytes.Say("Error")) + Expect(err).Should(gbytes.Say("\nUsage: cp")) + Expect(err).Should(gbytes.Say("\n")) + Expect(err).Should(gbytes.Say(`Run "oras cp -h"`)) + }) }) }) diff --git a/test/e2e/suite/command/discover.go b/test/e2e/suite/command/discover.go index 42c59da07..9e6440726 100644 --- a/test/e2e/suite/command/discover.go +++ b/test/e2e/suite/command/discover.go @@ -63,6 +63,22 @@ var _ = Describe("ORAS beginners:", func() { It("should fail when no tag or digest found in provided subject reference", func() { ORAS("discover", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest").Exec() }) + + It("should show detailed error description if no argument provided", func() { + err := ORAS("discover").ExpectFailure().Exec().Err + Expect(err).Should(gbytes.Say("Error")) + Expect(err).Should(gbytes.Say("\nUsage: discover")) + Expect(err).Should(gbytes.Say("\n")) + Expect(err).Should(gbytes.Say(`Run "oras discover -h"`)) + }) + + It("should show detailed error description if more than 1 arguments are provided", func() { + err := ORAS("discover", "foo", "bar").ExpectFailure().Exec().Err + Expect(err).Should(gbytes.Say("Error")) + Expect(err).Should(gbytes.Say("\nUsage: discover")) + Expect(err).Should(gbytes.Say("\n")) + Expect(err).Should(gbytes.Say(`Run "oras discover -h"`)) + }) }) }) From 7274fcfe6cac5625ea644c584da10f450f861a8a Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 7 Dec 2023 07:01:09 +0000 Subject: [PATCH 05/33] test clean Signed-off-by: Billy Zha --- test/e2e/suite/command/attach.go | 2 +- test/e2e/suite/command/cp.go | 4 ++-- test/e2e/suite/command/discover.go | 4 ++-- test/e2e/suite/command/push.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/e2e/suite/command/attach.go b/test/e2e/suite/command/attach.go index aec6fb248..5bd28b8b1 100644 --- a/test/e2e/suite/command/attach.go +++ b/test/e2e/suite/command/attach.go @@ -65,7 +65,7 @@ var _ = Describe("ORAS beginners:", func() { ExpectFailure().MatchErrKeyWords("unknown distribution specification flag").Exec() }) - It("should show detailed error description if no argument provided", func() { + It("should fail and show detailed error description if no argument provided", func() { err := ORAS("attach").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) gomega.Expect(err).Should(gbytes.Say("\nUsage: attach")) diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go index 0d85daa98..800f6339d 100644 --- a/test/e2e/suite/command/cp.go +++ b/test/e2e/suite/command/cp.go @@ -60,7 +60,7 @@ var _ = Describe("ORAS beginners:", func() { ORAS("cp", RegistryRef(ZOTHost, ImageRepo, "i-dont-think-this-tag-exists"), RegistryRef(ZOTHost, cpTestRepo("nonexistent-source"), "")).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) - It("should show detailed error description if no argument provided", func() { + It("should fail and show detailed error description if no argument provided", func() { err := ORAS("cp").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) Expect(err).Should(gbytes.Say("\nUsage: cp")) @@ -68,7 +68,7 @@ var _ = Describe("ORAS beginners:", func() { Expect(err).Should(gbytes.Say(`Run "oras cp -h"`)) }) - It("should show detailed error description if more than 2 arguments are provided", func() { + It("should fail and show detailed error description if more than 2 arguments are provided", func() { err := ORAS("cp", "foo", "bar", "buz").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) Expect(err).Should(gbytes.Say("\nUsage: cp")) diff --git a/test/e2e/suite/command/discover.go b/test/e2e/suite/command/discover.go index 9e6440726..69315373e 100644 --- a/test/e2e/suite/command/discover.go +++ b/test/e2e/suite/command/discover.go @@ -64,7 +64,7 @@ var _ = Describe("ORAS beginners:", func() { ORAS("discover", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest").Exec() }) - It("should show detailed error description if no argument provided", func() { + It("should fail and show detailed error description if no argument provided", func() { err := ORAS("discover").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) Expect(err).Should(gbytes.Say("\nUsage: discover")) @@ -72,7 +72,7 @@ var _ = Describe("ORAS beginners:", func() { Expect(err).Should(gbytes.Say(`Run "oras discover -h"`)) }) - It("should show detailed error description if more than 1 arguments are provided", func() { + It("should fail and show detailed error description if more than 1 arguments are provided", func() { err := ORAS("discover", "foo", "bar").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) Expect(err).Should(gbytes.Say("\nUsage: discover")) diff --git a/test/e2e/suite/command/push.go b/test/e2e/suite/command/push.go index 6f7fd0cf0..97317f683 100644 --- a/test/e2e/suite/command/push.go +++ b/test/e2e/suite/command/push.go @@ -41,7 +41,7 @@ var _ = Describe("ORAS beginners:", func() { gomega.Expect(out).Should(gbytes.Say("--image-spec string\\s+%s", regexp.QuoteMeta(feature.Experimental.Mark))) }) - It("should show detailed error description if no argument provided", func() { + It("should fail and show detailed error description if no argument provided", func() { err := ORAS("push").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) gomega.Expect(err).Should(gbytes.Say("\nUsage: push")) From c4e3417a84a40e7e585e1d0a73ff90a747f2d3b7 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 7 Dec 2023 07:01:40 +0000 Subject: [PATCH 06/33] improve login Signed-off-by: Billy Zha --- cmd/oras/root/login.go | 5 ++++- test/e2e/suite/auth/auth.go | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cmd/oras/root/login.go b/cmd/oras/root/login.go index d6cc65d5d..1825550e6 100644 --- a/cmd/oras/root/login.go +++ b/cmd/oras/root/login.go @@ -25,6 +25,7 @@ import ( credentials "github.com/oras-project/oras-credentials-go" "github.com/spf13/cobra" "golang.org/x/term" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/credential" "oras.land/oras/internal/io" @@ -61,7 +62,9 @@ Example - Log in with username and password in an interactive terminal: Example - Log in with username and password in an interactive terminal and no TLS check: oras login --insecure localhost:5000 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 arguments" + }, "the registry to log in to"), PreRunE: func(cmd *cobra.Command, args []string) error { return option.Parse(&opts) }, diff --git a/test/e2e/suite/auth/auth.go b/test/e2e/suite/auth/auth.go index 7852c7873..5490a580b 100644 --- a/test/e2e/suite/auth/auth.go +++ b/test/e2e/suite/auth/auth.go @@ -21,6 +21,8 @@ import ( "time" . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" . "oras.land/oras/test/e2e/internal/utils" ) @@ -58,6 +60,14 @@ var _ = Describe("Common registry user", Ordered, func() { MatchErrKeyWords("WARNING", "Using --password via the CLI is insecure", "Use --password-stdin").Exec() }) + It("should show detailed error description if no argument provided", func() { + err := ORAS("login").ExpectFailure().Exec().Err + Expect(err).Should(gbytes.Say("Error")) + Expect(err).Should(gbytes.Say("\nUsage: login")) + Expect(err).Should(gbytes.Say("\n")) + Expect(err).Should(gbytes.Say(`Run "oras login -h"`)) + }) + It("should fail if no username input", func() { ORAS("login", ZOTHost, "--registry-config", AuthConfigPath). WithTimeOut(20 * time.Second). From bef0c9682305908e6d249345142caa6664f543fc Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 7 Dec 2023 07:04:10 +0000 Subject: [PATCH 07/33] improve logout Signed-off-by: Billy Zha --- cmd/oras/root/logout.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/oras/root/logout.go b/cmd/oras/root/logout.go index bdb39d1f5..4094bfd41 100644 --- a/cmd/oras/root/logout.go +++ b/cmd/oras/root/logout.go @@ -21,6 +21,7 @@ import ( credentials "github.com/oras-project/oras-credentials-go" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/internal/credential" ) @@ -41,7 +42,9 @@ func logoutCmd() *cobra.Command { Example - Logout: oras logout localhost:5000 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 arguments" + }, "the registry you want to log out"), RunE: func(cmd *cobra.Command, args []string) error { opts.hostname = args[0] return runLogout(cmd.Context(), opts) From 814453bf78b3592422e31d1a7877d6a63c62f972 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 7 Dec 2023 07:12:11 +0000 Subject: [PATCH 08/33] improve pull Signed-off-by: Billy Zha --- cmd/oras/root/pull.go | 5 ++++- test/e2e/suite/command/pull.go | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index e98b03691..297b06e60 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -30,6 +30,7 @@ import ( "oras.land/oras-go/v2/content/file" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/track" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/fileref" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/graph" @@ -84,7 +85,9 @@ Example - Pull artifact files from an OCI image layout folder 'layout-dir': Example - Pull artifact files from an OCI layout archive 'layout.tar': oras pull --oci-layout layout.tar:v1 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 arguments" + }, "the artifact reference you want to pull"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] return option.Parse(&opts) diff --git a/test/e2e/suite/command/pull.go b/test/e2e/suite/command/pull.go index 06accb879..a2d77b646 100644 --- a/test/e2e/suite/command/pull.go +++ b/test/e2e/suite/command/pull.go @@ -66,6 +66,14 @@ var _ = Describe("ORAS beginners:", func() { out := ORAS("pull", ref).WithWorkDir(tempDir).Exec().Out gomega.Expect(out).ShouldNot(gbytes.Say(hintMsg(ref))) }) + + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("pull").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: pull")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras pull -h"`)) + }) }) }) From 9697ef2844851ff3193a7bb6d7c5b4ad26992f55 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Mon, 11 Dec 2023 09:12:38 +0000 Subject: [PATCH 09/33] improve resolve and tests Signed-off-by: Billy Zha --- cmd/oras/root/resolve.go | 5 ++++- test/e2e/suite/command/resolve.go | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd/oras/root/resolve.go b/cmd/oras/root/resolve.go index b98e67144..25a050ab0 100644 --- a/cmd/oras/root/resolve.go +++ b/cmd/oras/root/resolve.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" ) @@ -43,7 +44,9 @@ func resolveCmd() *cobra.Command { Example - Resolve digest of the target artifact: oras resolve localhost:5000/hello-world:v1 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 arguments" + }, "the target artifact reference to resolve"), Aliases: []string{"digest"}, PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] diff --git a/test/e2e/suite/command/resolve.go b/test/e2e/suite/command/resolve.go index f3ae46c2b..a8a56ba6e 100644 --- a/test/e2e/suite/command/resolve.go +++ b/test/e2e/suite/command/resolve.go @@ -50,6 +50,13 @@ var _ = Describe("ORAS beginners:", func() { ORAS("resolve", RegistryRef(ZOTHost, ImageRepo, "i-dont-think-this-tag-exists")).ExpectFailure().MatchErrKeyWords("Error: failed to resolve digest:", "not found").Exec() }) + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("resolve").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: resolve")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras resolve -h"`)) + }) }) }) From 63c39120c1621ceccbe844b24889e55c03d05353 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Mon, 11 Dec 2023 09:13:03 +0000 Subject: [PATCH 10/33] update prompt Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index a939b2c30..3aa4370ba 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -17,6 +17,7 @@ package errors import ( "fmt" + "strings" "github.com/spf13/cobra" "oras.land/oras-go/v2/registry" @@ -51,9 +52,9 @@ func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra return func(cmd *cobra.Command, args []string) error { if ok, text := checker(args); !ok { return NewOuput( - fmt.Sprintf(`%q requires %s but got %d`, cmd.CommandPath(), text, len(args)), + fmt.Sprintf(`%q requires %s but got %q`, cmd.CommandPath(), text, strings.Join(args, ",")), cmd.Use, - fmt.Sprintf(`You need to specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), + fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), ) } return nil From 856a97ec95ac9ca49cbdb3ff16c36991740b4332 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 01:28:18 +0000 Subject: [PATCH 11/33] improve tag Signed-off-by: Billy Zha --- cmd/oras/root/discover.go | 2 +- cmd/oras/root/login.go | 2 +- cmd/oras/root/logout.go | 2 +- cmd/oras/root/pull.go | 2 +- cmd/oras/root/resolve.go | 2 +- cmd/oras/root/tag.go | 5 ++++- test/e2e/suite/command/discover.go | 2 +- test/e2e/suite/command/tag.go | 8 ++++++++ 8 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cmd/oras/root/discover.go b/cmd/oras/root/discover.go index 1800ce793..5f09c1bfd 100644 --- a/cmd/oras/root/discover.go +++ b/cmd/oras/root/discover.go @@ -75,7 +75,7 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout oras discover --oci-layout -v -o tree layout-dir:v1 `, Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 arguments" + return len(args) == 1, "exactly 1 argument" }, "the target artifact to discover referrers from"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] diff --git a/cmd/oras/root/login.go b/cmd/oras/root/login.go index 1825550e6..4e8dd4320 100644 --- a/cmd/oras/root/login.go +++ b/cmd/oras/root/login.go @@ -63,7 +63,7 @@ Example - Log in with username and password in an interactive terminal and no TL oras login --insecure localhost:5000 `, Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 arguments" + return len(args) == 1, "exactly 1 argument" }, "the registry to log in to"), PreRunE: func(cmd *cobra.Command, args []string) error { return option.Parse(&opts) diff --git a/cmd/oras/root/logout.go b/cmd/oras/root/logout.go index 4094bfd41..61c5bf49f 100644 --- a/cmd/oras/root/logout.go +++ b/cmd/oras/root/logout.go @@ -43,7 +43,7 @@ Example - Logout: oras logout localhost:5000 `, Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 arguments" + return len(args) == 1, "exactly 1 argument" }, "the registry you want to log out"), RunE: func(cmd *cobra.Command, args []string) error { opts.hostname = args[0] diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index 297b06e60..588a90cff 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -86,7 +86,7 @@ Example - Pull artifact files from an OCI layout archive 'layout.tar': oras pull --oci-layout layout.tar:v1 `, Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 arguments" + return len(args) == 1, "exactly 1 argument" }, "the artifact reference you want to pull"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] diff --git a/cmd/oras/root/resolve.go b/cmd/oras/root/resolve.go index 25a050ab0..4773eb7e4 100644 --- a/cmd/oras/root/resolve.go +++ b/cmd/oras/root/resolve.go @@ -45,7 +45,7 @@ Example - Resolve digest of the target artifact: oras resolve localhost:5000/hello-world:v1 `, Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 arguments" + return len(args) == 1, "exactly 1 argument" }, "the target artifact reference to resolve"), Aliases: []string{"digest"}, PreRunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/oras/root/tag.go b/cmd/oras/root/tag.go index 4524d4fb9..670fdeb61 100644 --- a/cmd/oras/root/tag.go +++ b/cmd/oras/root/tag.go @@ -23,6 +23,7 @@ import ( "oras.land/oras-go/v2" "oras.land/oras-go/v2/registry" "oras.land/oras/cmd/oras/internal/display" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" ) @@ -56,7 +57,9 @@ Example - Tag the manifest 'v1.0.1' in 'localhost:5000/hello' to 'v1.0.1', 'v1.0 Example - Tag the manifest 'v1.0.1' to 'v1.0.2' in an OCI image layout folder 'layout-dir': oras tag layout-dir:v1.0.1 v1.0.2 `, - Args: cobra.MinimumNArgs(2), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) >= 2, "at least 2 argument" + }, "the to-be-retage artifact and the tags to be added"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] if _, err := registry.ParseReference(opts.RawReference); err != nil { diff --git a/test/e2e/suite/command/discover.go b/test/e2e/suite/command/discover.go index 69315373e..9771b0074 100644 --- a/test/e2e/suite/command/discover.go +++ b/test/e2e/suite/command/discover.go @@ -72,7 +72,7 @@ var _ = Describe("ORAS beginners:", func() { Expect(err).Should(gbytes.Say(`Run "oras discover -h"`)) }) - It("should fail and show detailed error description if more than 1 arguments are provided", func() { + It("should fail and show detailed error description if more than 1 argument are provided", func() { err := ORAS("discover", "foo", "bar").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) Expect(err).Should(gbytes.Say("\nUsage: discover")) diff --git a/test/e2e/suite/command/tag.go b/test/e2e/suite/command/tag.go index 3895d0424..5ee04fc52 100644 --- a/test/e2e/suite/command/tag.go +++ b/test/e2e/suite/command/tag.go @@ -39,6 +39,14 @@ var _ = Describe("ORAS beginners:", func() { It("should fail when provided invalid reference", func() { ORAS("tag", "list", "tagged").ExpectFailure().MatchErrKeyWords("Error:", "'list'").Exec() }) + + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("tag").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: tag")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras tag -h"`)) + }) }) }) From 15132cef2f3d7760222e16c75323ccf752c54014 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 01:32:13 +0000 Subject: [PATCH 12/33] improve version Signed-off-by: Billy Zha --- cmd/oras/root/version.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cmd/oras/root/version.go b/cmd/oras/root/version.go index b91659afa..fae9d28bf 100644 --- a/cmd/oras/root/version.go +++ b/cmd/oras/root/version.go @@ -17,6 +17,7 @@ package root import ( "fmt" + "os" "runtime" "strings" @@ -34,7 +35,15 @@ func versionCmd() *cobra.Command { Example - print version: oras version `, - Args: cobra.NoArgs, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + _, err := fmt.Fprintf(os.Stderr, "warning: `oras version` requires no argument, %q will be ignored\n", strings.Join(args, ",")) + if err != nil { + return err + } + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { return runVersion() }, From fd31250443c993dbec8c1928b18fa8cf27c81e80 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 01:34:47 +0000 Subject: [PATCH 13/33] add e2e Signed-off-by: Billy Zha --- test/e2e/suite/command/version.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/e2e/suite/command/version.go b/test/e2e/suite/command/version.go index cbc8f71d5..e302b6b21 100644 --- a/test/e2e/suite/command/version.go +++ b/test/e2e/suite/command/version.go @@ -25,5 +25,9 @@ var _ = Describe("ORAS user:", func() { It("should run version command", func() { ORAS("version").Exec() }) + + It("should run version command and ignore extra arguments with warning", func() { + ORAS("version", "foo", "bar").MatchErrKeyWords("warning:").Exec() + }) }) }) From fb1316acca028aa31cf722fd1c96c456550a564f Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 01:36:51 +0000 Subject: [PATCH 14/33] add e2e Signed-off-by: Billy Zha --- test/e2e/suite/command/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/suite/command/version.go b/test/e2e/suite/command/version.go index e302b6b21..bc2a652d3 100644 --- a/test/e2e/suite/command/version.go +++ b/test/e2e/suite/command/version.go @@ -27,7 +27,7 @@ var _ = Describe("ORAS user:", func() { }) It("should run version command and ignore extra arguments with warning", func() { - ORAS("version", "foo", "bar").MatchErrKeyWords("warning:").Exec() + ORAS("version", "foo", "bar").MatchErrKeyWords("foo", "bar", "warning:").Exec() }) }) }) From 685544d82fab6131fa6d6500039fd677c33f62a5 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 02:24:34 +0000 Subject: [PATCH 15/33] improve repo list Signed-off-by: Billy Zha --- cmd/oras/root/repo/ls.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/oras/root/repo/ls.go b/cmd/oras/root/repo/ls.go index 0d6c3a7c5..dbf27cf48 100644 --- a/cmd/oras/root/repo/ls.go +++ b/cmd/oras/root/repo/ls.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/spf13/cobra" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/repository" ) @@ -50,7 +51,9 @@ Example - List the repositories under a namespace in the registry: Example - List the repositories under the registry that include values lexically after last: oras repo ls --last "last_repo" localhost:5000 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 argument" + }, "the target registry to list repositories from"), Aliases: []string{"list"}, PreRunE: func(cmd *cobra.Command, args []string) error { return option.Parse(&opts) From 8638d1d820e8d1f3acf68e03b2cfb7399dae9ad8 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 02:27:44 +0000 Subject: [PATCH 16/33] add repo ls e2e Signed-off-by: Billy Zha --- test/e2e/suite/command/repo.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/e2e/suite/command/repo.go b/test/e2e/suite/command/repo.go index 3cbe628cf..58b1d7da7 100644 --- a/test/e2e/suite/command/repo.go +++ b/test/e2e/suite/command/repo.go @@ -21,6 +21,7 @@ import ( "strings" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" "oras.land/oras/test/e2e/internal/testdata/feature" @@ -41,9 +42,16 @@ var _ = Describe("ORAS beginners:", func() { }) It("should fail listing repositories if wrong registry provided", func() { - ORAS("repo", "ls").ExpectFailure().MatchErrKeyWords("Error:").Exec() ORAS("repo", "ls", RegistryRef(ZOTHost, ImageRepo, "some-tag")).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) + + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("repo", "ls").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: ls")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras repo ls -h"`)) + }) }) When("running `repo tags`", func() { It("should show help description with feature flags", func() { From 1df26170b41f1fcdc4ac25909f585cab1bc477ff Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 03:21:23 +0000 Subject: [PATCH 17/33] add e2e for repo tags Signed-off-by: Billy Zha --- cmd/oras/root/repo/tags.go | 5 ++++- test/e2e/suite/command/repo.go | 10 +++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cmd/oras/root/repo/tags.go b/cmd/oras/root/repo/tags.go index 45e37dba9..2ca2a49fb 100644 --- a/cmd/oras/root/repo/tags.go +++ b/cmd/oras/root/repo/tags.go @@ -22,6 +22,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/spf13/cobra" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" ) @@ -61,7 +62,9 @@ Example - [Experimental] Show tags associated with a particular tagged resource: Example - [Experimental] Show tags associated with a digest: oras repo tags localhost:5000/hello@sha256:c551125a624189cece9135981621f3f3144564ddabe14b523507bf74c2281d9b `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 argument" + }, "the target repository to list tags from"), Aliases: []string{"show-tags"}, PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] diff --git a/test/e2e/suite/command/repo.go b/test/e2e/suite/command/repo.go index 58b1d7da7..1b9492f89 100644 --- a/test/e2e/suite/command/repo.go +++ b/test/e2e/suite/command/repo.go @@ -45,7 +45,7 @@ var _ = Describe("ORAS beginners:", func() { ORAS("repo", "ls", RegistryRef(ZOTHost, ImageRepo, "some-tag")).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) - It("should fail and show detailed error description if no argument provided", func() { + It("should fail and show detailed error description if no argument provided", Focus, func() { err := ORAS("repo", "ls").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) gomega.Expect(err).Should(gbytes.Say("\nUsage: ls")) @@ -68,6 +68,14 @@ var _ = Describe("ORAS beginners:", func() { ORAS("repo", "tags", ZOTHost).ExpectFailure().MatchErrKeyWords("Error:").Exec() ORAS("repo", "tags", RegistryRef(ZOTHost, ImageRepo, "some-tag")).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) + + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("repo", "tags").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: tags")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras repo tags -h"`)) + }) }) }) }) From 7b4dd439accc6f2655a2bba64561bd89b03279d4 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 03:24:34 +0000 Subject: [PATCH 18/33] remove focus spec Signed-off-by: Billy Zha --- test/e2e/suite/command/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/suite/command/repo.go b/test/e2e/suite/command/repo.go index 1b9492f89..bfdce5f8f 100644 --- a/test/e2e/suite/command/repo.go +++ b/test/e2e/suite/command/repo.go @@ -45,7 +45,7 @@ var _ = Describe("ORAS beginners:", func() { ORAS("repo", "ls", RegistryRef(ZOTHost, ImageRepo, "some-tag")).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) - It("should fail and show detailed error description if no argument provided", Focus, func() { + It("should fail and show detailed error description if no argument provided", func() { err := ORAS("repo", "ls").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) gomega.Expect(err).Should(gbytes.Say("\nUsage: ls")) From 8fb2bb28e6e28d2ab948b8750c9fe0da7256765d Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:05:20 +0000 Subject: [PATCH 19/33] improve blob fetch and push with e2e Signed-off-by: Billy Zha --- cmd/oras/root/blob/delete.go | 5 ++++- cmd/oras/root/blob/fetch.go | 5 ++++- cmd/oras/root/blob/push.go | 5 ++++- test/e2e/suite/command/blob.go | 20 ++++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cmd/oras/root/blob/delete.go b/cmd/oras/root/blob/delete.go index 419a1277d..93abab459 100644 --- a/cmd/oras/root/blob/delete.go +++ b/cmd/oras/root/blob/delete.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry/remote/auth" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/registryutil" ) @@ -55,7 +56,9 @@ Example - Delete a blob without prompting confirmation: Example - Delete a blob and print its descriptor: oras blob delete --descriptor --force localhost:5000/hello@sha256:9a201d228ebd966211f7d1131be19f152be428bd373a92071c71d8deaf83b3e5 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 argument" + }, "the target blob to delete"), PreRunE: func(cmd *cobra.Command, args []string) error { if opts.OutputDescriptor && !opts.Force { return errors.New("must apply --force to confirm the deletion if the descriptor is outputted") diff --git a/cmd/oras/root/blob/fetch.go b/cmd/oras/root/blob/fetch.go index a7c1d3f48..f049afb85 100644 --- a/cmd/oras/root/blob/fetch.go +++ b/cmd/oras/root/blob/fetch.go @@ -29,6 +29,7 @@ import ( "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/registry/remote" "oras.land/oras/cmd/oras/internal/display/track" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" ) @@ -67,7 +68,9 @@ Example - Fetch and print a blob from OCI image layout folder 'layout-dir': Example - Fetch and print a blob from OCI image layout archive file 'layout.tar': oras blob fetch --oci-layout --output - layout.tar@sha256:9a201d228ebd966211f7d1131be19f152be428bd373a92071c71d8deaf83b3e5 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 argument" + }, "the target blob to fetch"), PreRunE: func(cmd *cobra.Command, args []string) error { if opts.outputPath == "" && !opts.OutputDescriptor { return errors.New("either `--output` or `--descriptor` must be provided") diff --git a/cmd/oras/root/blob/push.go b/cmd/oras/root/blob/push.go index b13692c26..5515cf5de 100644 --- a/cmd/oras/root/blob/push.go +++ b/cmd/oras/root/blob/push.go @@ -27,6 +27,7 @@ import ( "oras.land/oras-go/v2" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/track" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/file" ) @@ -73,7 +74,9 @@ Example - Push blob without TLS: Example - Push blob 'hi.txt' into an OCI image layout folder 'layout-dir': oras blob push --oci-layout layout-dir hi.txt `, - Args: cobra.ExactArgs(2), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 2, "exactly 2 arguments" + }, "the destination to push to and the file to read blob content from"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] opts.fileRef = args[1] diff --git a/test/e2e/suite/command/blob.go b/test/e2e/suite/command/blob.go index 770a0a78c..1cfd63897 100644 --- a/test/e2e/suite/command/blob.go +++ b/test/e2e/suite/command/blob.go @@ -22,6 +22,8 @@ import ( "strings" . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" "oras.land/oras/test/e2e/internal/testdata/foobar" . "oras.land/oras/test/e2e/internal/utils" ) @@ -105,6 +107,14 @@ var _ = Describe("ORAS beginners:", func() { ExpectFailure().Exec() }) + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("blob", "fetch").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: fetch")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras blob fetch -h"`)) + }) + It("should fail if no digest provided", func() { ORAS("blob", "fetch", RegistryRef(ZOTHost, ImageRepo, "")). ExpectFailure().Exec() @@ -124,6 +134,16 @@ var _ = Describe("ORAS beginners:", func() { ORAS("blob", "fetch").ExpectFailure().Exec() }) }) + + When("running `blob delete`", func() { + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("blob", "delete").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: delete")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras blob delete -h"`)) + }) + }) }) When("running `blob delete`", func() { From 5d3abab9899a24870b5f741b157e2037308bfc09 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:13:02 +0000 Subject: [PATCH 20/33] improve manifest commands Signed-off-by: Billy Zha --- cmd/oras/root/manifest/delete.go | 4 ++- cmd/oras/root/manifest/fetch.go | 5 +++- cmd/oras/root/manifest/fetch_config.go | 5 +++- cmd/oras/root/manifest/push.go | 5 +++- test/e2e/suite/command/manifest.go | 40 ++++++++++++++++++-------- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/cmd/oras/root/manifest/delete.go b/cmd/oras/root/manifest/delete.go index 467ef04f4..b45e24d6d 100644 --- a/cmd/oras/root/manifest/delete.go +++ b/cmd/oras/root/manifest/delete.go @@ -59,7 +59,9 @@ Example - Delete a manifest and print its descriptor: Example - Delete a manifest by digest 'sha256:99e4703fbf30916f549cd6bfa9cdbab614b5392fbe64fdee971359a77073cdf9' from repository 'localhost:5000/hello': oras manifest delete localhost:5000/hello@sha:99e4703fbf30916f549cd6bfa9cdbab614b5392fbe64fdee971359a77073cdf9 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 argument" + }, "the manifest to delete"), PreRunE: func(cmd *cobra.Command, args []string) error { if opts.OutputDescriptor && !opts.Force { return errors.New("must apply --force to confirm the deletion if the descriptor is outputted") diff --git a/cmd/oras/root/manifest/fetch.go b/cmd/oras/root/manifest/fetch.go index 52ec2f80b..d4acbfde7 100644 --- a/cmd/oras/root/manifest/fetch.go +++ b/cmd/oras/root/manifest/fetch.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2" "oras.land/oras-go/v2/registry/remote" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" ) @@ -69,7 +70,9 @@ Example - Fetch raw manifest from an OCI image layout folder 'layout-dir': Example - Fetch raw manifest from an OCI layout archive file 'layout.tar': oras manifest fetch --oci-layout layout.tar:v1 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 argument" + }, "the manifest to fetch"), PreRunE: func(cmd *cobra.Command, args []string) error { if opts.outputPath == "-" && opts.OutputDescriptor { return errors.New("`--output -` cannot be used with `--descriptor` at the same time") diff --git a/cmd/oras/root/manifest/fetch_config.go b/cmd/oras/root/manifest/fetch_config.go index e0f871f34..6e23685b4 100644 --- a/cmd/oras/root/manifest/fetch_config.go +++ b/cmd/oras/root/manifest/fetch_config.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/descriptor" ) @@ -67,7 +68,9 @@ Example - Fetch the descriptor of the config: Example - Fetch and print the prettified descriptor of the config: oras manifest fetch-config --descriptor --pretty localhost:5000/hello:v1 `, - Args: cobra.ExactArgs(1), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 1, "exactly 1 argument" + }, "the manifest config to fetch"), PreRunE: func(cmd *cobra.Command, args []string) error { if opts.outputPath == "-" && opts.OutputDescriptor { return errors.New("`--output -` cannot be used with `--descriptor` at the same time") diff --git a/cmd/oras/root/manifest/push.go b/cmd/oras/root/manifest/push.go index d9e737918..f0bfe3ac5 100644 --- a/cmd/oras/root/manifest/push.go +++ b/cmd/oras/root/manifest/push.go @@ -29,6 +29,7 @@ import ( "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry/remote" "oras.land/oras/cmd/oras/internal/display" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/file" ) @@ -80,7 +81,9 @@ Example - Push a manifest to repository 'localhost:5000/hello' and tag with 'tag Example - Push a manifest to an OCI image layout folder 'layout-dir' and tag with 'v1': oras manifest push --oci-layout layout-dir:v1 manifest.json `, - Args: cobra.ExactArgs(2), + Args: oerrors.ArgsChecker(func(args []string) (bool, string) { + return len(args) == 2, "exactly 2 arguments" + }, "the destination to push to and the file to read manifest content from"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.fileRef = args[1] if opts.fileRef == "-" && opts.PasswordFromStdin { diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index bf8950c92..f27f95ad5 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.go @@ -63,11 +63,12 @@ var _ = Describe("ORAS beginners:", func() { Exec() }) - It("should fail pushing without reference provided", func() { - ORAS("manifest", "push"). - ExpectFailure(). - MatchErrKeyWords("Error:"). - Exec() + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("manifest", "push").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: push")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest push -h"`)) }) It("should fail pushing with a manifest from stdin without media type flag", func() { @@ -84,19 +85,30 @@ var _ = Describe("ORAS beginners:", func() { MatchKeyWords(ExampleDesc). Exec() }) - It("should fail fetching manifest without reference provided", func() { - ORAS("manifest", "fetch"). - ExpectFailure(). - MatchErrKeyWords("Error:"). - Exec() + + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("manifest", "fetch").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: fetch")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest fetch -h"`)) }) }) + When("running `manifest delete`", func() { It("should show help doc with feature flags", func() { out := ORAS("manifest", "delete", "--help").MatchKeyWords(ExampleDesc).Exec() gomega.Expect(out).Should(gbytes.Say("--distribution-spec string\\s+%s", regexp.QuoteMeta(feature.Preview.Mark))) }) + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("manifest", "delete").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: delete")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest delete -h"`)) + }) + tempTag := "to-delete" It("should cancel deletion without confirmation", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "no-confirm") @@ -154,8 +166,12 @@ var _ = Describe("ORAS beginners:", func() { MatchKeyWords(ExampleDesc, "\nUsage:").Exec() }) - It("should fail if no manifest reference provided", func() { - ORAS("manifest", "fetch-config").ExpectFailure().Exec() + It("should fail and show detailed error description if no argument provided", func() { + err := ORAS("manifest", "fetch-config").ExpectFailure().Exec().Err + gomega.Expect(err).Should(gbytes.Say("Error")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: fetch-config")) + gomega.Expect(err).Should(gbytes.Say("\n")) + gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest fetch-config -h"`)) }) It("should fail if provided reference does not exist", func() { From bcadeaba3b2113a10eecc31a3c7dadd831fd0832 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:21:26 +0000 Subject: [PATCH 21/33] fix usage path Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 3aa4370ba..171a0ba64 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -53,7 +53,7 @@ func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra if ok, text := checker(args); !ok { return NewOuput( fmt.Sprintf(`%q requires %s but got %q`, cmd.CommandPath(), text, strings.Join(args, ",")), - cmd.Use, + fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), ) } From 38b58f5a41fcc621d7c31748f9d51004be42f6f6 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:25:55 +0000 Subject: [PATCH 22/33] fix error argument output Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 171a0ba64..77a8cbe26 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -52,7 +52,7 @@ func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra return func(cmd *cobra.Command, args []string) error { if ok, text := checker(args); !ok { return NewOuput( - fmt.Sprintf(`%q requires %s but got %q`, cmd.CommandPath(), text, strings.Join(args, ",")), + fmt.Sprintf(`%q requires %s but got %s`, cmd.CommandPath(), text, strings.Join(args, " ")), fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), ) From 080dac82ebce433ce31716940cba9c6d2bcf4296 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:26:44 +0000 Subject: [PATCH 23/33] add colon Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 77a8cbe26..245f86a04 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -52,7 +52,7 @@ func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra return func(cmd *cobra.Command, args []string) error { if ok, text := checker(args); !ok { return NewOuput( - fmt.Sprintf(`%q requires %s but got %s`, cmd.CommandPath(), text, strings.Join(args, " ")), + fmt.Sprintf(`%q requires %s but got: %s`, cmd.CommandPath(), text, strings.Join(args, " ")), fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), ) From dbcd8eb9128a92fa42b54e02f1c7306f73f893da Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:28:40 +0000 Subject: [PATCH 24/33] resolve build error Signed-off-by: Billy Zha --- cmd/oras/root/manifest/delete.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/oras/root/manifest/delete.go b/cmd/oras/root/manifest/delete.go index e68c91b54..22d9fc7d4 100644 --- a/cmd/oras/root/manifest/delete.go +++ b/cmd/oras/root/manifest/delete.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry/remote/auth" + oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/registryutil" ) From b423b5a4d91389cb0682e6dbcbed3fb1137bc964 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:30:29 +0000 Subject: [PATCH 25/33] add error argument count Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 245f86a04..6816cd551 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -52,7 +52,7 @@ func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra return func(cmd *cobra.Command, args []string) error { if ok, text := checker(args); !ok { return NewOuput( - fmt.Sprintf(`%q requires %s but got: %s`, cmd.CommandPath(), text, strings.Join(args, " ")), + fmt.Sprintf(`%q requires %s but got %d: %s`, cmd.CommandPath(), text, len(args), strings.Join(args, " ")), fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), ) From c3f254985c0d3a988995dce95e8244fa3a6db73f Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:32:12 +0000 Subject: [PATCH 26/33] add square bracket to error arg output Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 6816cd551..7330af922 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -52,7 +52,7 @@ func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra return func(cmd *cobra.Command, args []string) error { if ok, text := checker(args); !ok { return NewOuput( - fmt.Sprintf(`%q requires %s but got %d: %s`, cmd.CommandPath(), text, len(args), strings.Join(args, " ")), + fmt.Sprintf(`%q requires %s but got %d: [%s]`, cmd.CommandPath(), text, len(args), strings.Join(args, " ")), fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), ) From 2923e268b549268aac9905f8d0e38f48d153908e Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 05:36:04 +0000 Subject: [PATCH 27/33] update usage tests Signed-off-by: Billy Zha --- test/e2e/suite/auth/auth.go | 2 +- test/e2e/suite/command/attach.go | 2 +- test/e2e/suite/command/blob.go | 4 ++-- test/e2e/suite/command/cp.go | 4 ++-- test/e2e/suite/command/discover.go | 4 ++-- test/e2e/suite/command/manifest.go | 6 +++--- test/e2e/suite/command/pull.go | 2 +- test/e2e/suite/command/push.go | 2 +- test/e2e/suite/command/repo.go | 4 ++-- test/e2e/suite/command/resolve.go | 2 +- test/e2e/suite/command/tag.go | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/test/e2e/suite/auth/auth.go b/test/e2e/suite/auth/auth.go index 5490a580b..b9093a2ab 100644 --- a/test/e2e/suite/auth/auth.go +++ b/test/e2e/suite/auth/auth.go @@ -63,7 +63,7 @@ var _ = Describe("Common registry user", Ordered, func() { It("should show detailed error description if no argument provided", func() { err := ORAS("login").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) - Expect(err).Should(gbytes.Say("\nUsage: login")) + Expect(err).Should(gbytes.Say("\nUsage: oras login")) Expect(err).Should(gbytes.Say("\n")) Expect(err).Should(gbytes.Say(`Run "oras login -h"`)) }) diff --git a/test/e2e/suite/command/attach.go b/test/e2e/suite/command/attach.go index 5bd28b8b1..63fc1fea4 100644 --- a/test/e2e/suite/command/attach.go +++ b/test/e2e/suite/command/attach.go @@ -68,7 +68,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("attach").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: attach")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras attach")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras attach -h"`)) }) diff --git a/test/e2e/suite/command/blob.go b/test/e2e/suite/command/blob.go index d647f76a0..82c2824bd 100644 --- a/test/e2e/suite/command/blob.go +++ b/test/e2e/suite/command/blob.go @@ -110,7 +110,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("blob", "fetch").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: fetch")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras fetch")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras blob fetch -h"`)) }) @@ -139,7 +139,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("blob", "delete").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: delete")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras delete")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras blob delete -h"`)) }) diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go index 800f6339d..45718fa29 100644 --- a/test/e2e/suite/command/cp.go +++ b/test/e2e/suite/command/cp.go @@ -63,7 +63,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("cp").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) - Expect(err).Should(gbytes.Say("\nUsage: cp")) + Expect(err).Should(gbytes.Say("\nUsage: oras cp")) Expect(err).Should(gbytes.Say("\n")) Expect(err).Should(gbytes.Say(`Run "oras cp -h"`)) }) @@ -71,7 +71,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if more than 2 arguments are provided", func() { err := ORAS("cp", "foo", "bar", "buz").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) - Expect(err).Should(gbytes.Say("\nUsage: cp")) + Expect(err).Should(gbytes.Say("\nUsage: oras cp")) Expect(err).Should(gbytes.Say("\n")) Expect(err).Should(gbytes.Say(`Run "oras cp -h"`)) }) diff --git a/test/e2e/suite/command/discover.go b/test/e2e/suite/command/discover.go index 9771b0074..fd2f678f5 100644 --- a/test/e2e/suite/command/discover.go +++ b/test/e2e/suite/command/discover.go @@ -67,7 +67,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("discover").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) - Expect(err).Should(gbytes.Say("\nUsage: discover")) + Expect(err).Should(gbytes.Say("\nUsage: oras discover")) Expect(err).Should(gbytes.Say("\n")) Expect(err).Should(gbytes.Say(`Run "oras discover -h"`)) }) @@ -75,7 +75,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if more than 1 argument are provided", func() { err := ORAS("discover", "foo", "bar").ExpectFailure().Exec().Err Expect(err).Should(gbytes.Say("Error")) - Expect(err).Should(gbytes.Say("\nUsage: discover")) + Expect(err).Should(gbytes.Say("\nUsage: oras discover")) Expect(err).Should(gbytes.Say("\n")) Expect(err).Should(gbytes.Say(`Run "oras discover -h"`)) }) diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index d5befa952..557f8884c 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.go @@ -66,7 +66,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("manifest", "push").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: push")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras manifest push")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest push -h"`)) }) @@ -89,7 +89,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("manifest", "fetch").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: fetch")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras manifest fetch")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest fetch -h"`)) }) @@ -169,7 +169,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("manifest", "fetch-config").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: fetch-config")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras manifest fetch-config")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest fetch-config -h"`)) }) diff --git a/test/e2e/suite/command/pull.go b/test/e2e/suite/command/pull.go index a2d77b646..c841820a9 100644 --- a/test/e2e/suite/command/pull.go +++ b/test/e2e/suite/command/pull.go @@ -70,7 +70,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("pull").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: pull")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras pull")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras pull -h"`)) }) diff --git a/test/e2e/suite/command/push.go b/test/e2e/suite/command/push.go index 97317f683..123f6a0bd 100644 --- a/test/e2e/suite/command/push.go +++ b/test/e2e/suite/command/push.go @@ -44,7 +44,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("push").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: push")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras push")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras push -h"`)) }) diff --git a/test/e2e/suite/command/repo.go b/test/e2e/suite/command/repo.go index bfdce5f8f..0aab9b2bc 100644 --- a/test/e2e/suite/command/repo.go +++ b/test/e2e/suite/command/repo.go @@ -48,7 +48,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("repo", "ls").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: ls")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras repo ls")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras repo ls -h"`)) }) @@ -72,7 +72,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("repo", "tags").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: tags")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras repo tags")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras repo tags -h"`)) }) diff --git a/test/e2e/suite/command/resolve.go b/test/e2e/suite/command/resolve.go index a8a56ba6e..fac8266b4 100644 --- a/test/e2e/suite/command/resolve.go +++ b/test/e2e/suite/command/resolve.go @@ -53,7 +53,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("resolve").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: resolve")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras resolve")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras resolve -h"`)) }) diff --git a/test/e2e/suite/command/tag.go b/test/e2e/suite/command/tag.go index 5ee04fc52..0daf4e840 100644 --- a/test/e2e/suite/command/tag.go +++ b/test/e2e/suite/command/tag.go @@ -43,7 +43,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("tag").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: tag")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras tag")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras tag -h"`)) }) From 3538dfa194a0fe8d2fe132d40ead13c65c768063 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 06:02:45 +0000 Subject: [PATCH 28/33] fix e2e failures Signed-off-by: Billy Zha --- test/e2e/suite/command/blob.go | 4 ++-- test/e2e/suite/command/manifest.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/suite/command/blob.go b/test/e2e/suite/command/blob.go index 82c2824bd..7f0daf5a0 100644 --- a/test/e2e/suite/command/blob.go +++ b/test/e2e/suite/command/blob.go @@ -110,7 +110,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("blob", "fetch").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: oras fetch")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras blob fetch")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras blob fetch -h"`)) }) @@ -139,7 +139,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("blob", "delete").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: oras delete")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras blob delete")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras blob delete -h"`)) }) diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index 557f8884c..6f7c10ebc 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.go @@ -104,7 +104,7 @@ var _ = Describe("ORAS beginners:", func() { It("should fail and show detailed error description if no argument provided", func() { err := ORAS("manifest", "delete").ExpectFailure().Exec().Err gomega.Expect(err).Should(gbytes.Say("Error")) - gomega.Expect(err).Should(gbytes.Say("\nUsage: delete")) + gomega.Expect(err).Should(gbytes.Say("\nUsage: oras manifest delete")) gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest delete -h"`)) }) From acd120706a1cf174add84fa05c6828dc7dfe08e0 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 12 Dec 2023 06:27:44 +0000 Subject: [PATCH 29/33] remove argument output Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 7330af922..5a02b5d2e 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -17,7 +17,6 @@ package errors import ( "fmt" - "strings" "github.com/spf13/cobra" "oras.land/oras-go/v2/registry" @@ -52,7 +51,7 @@ func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra return func(cmd *cobra.Command, args []string) error { if ok, text := checker(args); !ok { return NewOuput( - fmt.Sprintf(`%q requires %s but got %d: [%s]`, cmd.CommandPath(), text, len(args), strings.Join(args, " ")), + fmt.Sprintf(`%q requires %s but got %d`, cmd.CommandPath(), text, len(args)), fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), ) From 0d4ca6d460c02ccf5cb270b8341f204ad78c3541 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 21 Dec 2023 07:01:37 +0000 Subject: [PATCH 30/33] refactor with new type and wrapper checker Signed-off-by: Billy Zha --- cmd/oras/internal/argument/checker.go | 32 ++++++++++++++++++++ cmd/oras/internal/errors/errors.go | 41 +++++++++++--------------- cmd/oras/root/attach.go | 6 ++-- cmd/oras/root/blob/delete.go | 5 ++-- cmd/oras/root/blob/fetch.go | 5 ++-- cmd/oras/root/blob/push.go | 5 ++-- cmd/oras/root/cp.go | 5 ++-- cmd/oras/root/discover.go | 5 ++-- cmd/oras/root/login.go | 5 ++-- cmd/oras/root/logout.go | 5 ++-- cmd/oras/root/manifest/delete.go | 5 ++-- cmd/oras/root/manifest/fetch.go | 5 ++-- cmd/oras/root/manifest/fetch_config.go | 5 ++-- cmd/oras/root/manifest/push.go | 5 ++-- cmd/oras/root/pull.go | 5 ++-- cmd/oras/root/push.go | 5 ++-- cmd/oras/root/repo/ls.go | 5 ++-- cmd/oras/root/repo/tags.go | 5 ++-- cmd/oras/root/resolve.go | 5 ++-- cmd/oras/root/tag.go | 5 ++-- 20 files changed, 85 insertions(+), 79 deletions(-) create mode 100644 cmd/oras/internal/argument/checker.go diff --git a/cmd/oras/internal/argument/checker.go b/cmd/oras/internal/argument/checker.go new file mode 100644 index 000000000..e187ee1e8 --- /dev/null +++ b/cmd/oras/internal/argument/checker.go @@ -0,0 +1,32 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package argument + +import "fmt" + +// Exactly checks if the number of arguments is exactly cnt. +func Exactly(cnt int) func(args []string) (bool, string) { + return func(args []string) (bool, string) { + return len(args) == cnt, fmt.Sprintf("exactly %d argument", cnt) + } +} + +// AtLeast checks if the number of arguments is larger or equal to cnt. +func AtLeast(cnt int) func(args []string) (bool, string) { + return func(args []string) (bool, string) { + return len(args) >= cnt, fmt.Sprintf("at least %d argument", cnt) + } +} diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 5a02b5d2e..dd4375259 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -22,39 +22,32 @@ import ( "oras.land/oras-go/v2/registry" ) -type output struct { - description, usage, suggestion string +type Error struct { + Err error + Usage string + Recommendation string } -func (o *output) Error() string { - ret := o.description - if o.usage != "" { - ret += fmt.Sprintf("\nUsage: %s", o.usage) +func (o *Error) Error() string { + ret := o.Err.Error() + if o.Usage != "" { + ret += fmt.Sprintf("\nUsage: %s", o.Usage) } - if o.suggestion != "" { - ret += fmt.Sprintf("\n%s", o.suggestion) + if o.Recommendation != "" { + ret += fmt.Sprintf("\n%s", o.Recommendation) } return ret } -// NewOuput creates a new error for CLI output. -func NewOuput(description, usage, suggestion string) error { - return &output{ - description: description, - usage: usage, - suggestion: suggestion, - } -} - -// ArgsChecker checks the args with the checker function. -func ArgsChecker(checker func(args []string) (bool, string), usage string) cobra.PositionalArgs { +// CheckArgs checks the args with the checker function. +func CheckArgs(checker func(args []string) (bool, string), Usage string) cobra.PositionalArgs { return func(cmd *cobra.Command, args []string) error { if ok, text := checker(args); !ok { - return NewOuput( - fmt.Sprintf(`%q requires %s but got %d`, cmd.CommandPath(), text, len(args)), - fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), - fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, usage, cmd.CommandPath()), - ) + return &Error{ + Err: fmt.Errorf(`%q requires %s but got %d`, cmd.CommandPath(), text, len(args)), + Usage: fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), + Recommendation: fmt.Sprintf(`Please specify %s as %s. Run "%s -h" for more options and examples`, text, Usage, cmd.CommandPath()), + } } return nil } diff --git a/cmd/oras/root/attach.go b/cmd/oras/root/attach.go index 8bb62026c..a8c055bdd 100644 --- a/cmd/oras/root/attach.go +++ b/cmd/oras/root/attach.go @@ -27,6 +27,7 @@ import ( "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/content/file" "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/display/track" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" @@ -77,10 +78,7 @@ Example - Attach file 'hi.txt' and export the pushed manifest to 'manifest.json' Example - Attach file to the manifest tagged 'v1' in an OCI image layout folder 'layout-dir': oras attach --oci-layout --artifact-type doc/example layout-dir:v1 hi.txt `, - Args: oerrors.ArgsChecker( - func(args []string) (bool, string) { - return len(args) >= 1, "at least 1 argument" - }, "the destination artifact for attaching."), + Args: oerrors.CheckArgs(argument.AtLeast(1), "the destination artifact for attaching."), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] opts.FileRefs = args[1:] diff --git a/cmd/oras/root/blob/delete.go b/cmd/oras/root/blob/delete.go index 60e3dccd4..fe22a1389 100644 --- a/cmd/oras/root/blob/delete.go +++ b/cmd/oras/root/blob/delete.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/registryutil" @@ -54,9 +55,7 @@ Example - Delete a blob without prompting confirmation: Example - Delete a blob and print its descriptor: oras blob delete --descriptor --force localhost:5000/hello@sha256:9a201d228ebd966211f7d1131be19f152be428bd373a92071c71d8deaf83b3e5 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the target blob to delete"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the target blob to delete"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] if opts.OutputDescriptor && !opts.Force { diff --git a/cmd/oras/root/blob/fetch.go b/cmd/oras/root/blob/fetch.go index f049afb85..3ebb4187f 100644 --- a/cmd/oras/root/blob/fetch.go +++ b/cmd/oras/root/blob/fetch.go @@ -28,6 +28,7 @@ import ( "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/registry/remote" + "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/display/track" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" @@ -68,9 +69,7 @@ Example - Fetch and print a blob from OCI image layout folder 'layout-dir': Example - Fetch and print a blob from OCI image layout archive file 'layout.tar': oras blob fetch --oci-layout --output - layout.tar@sha256:9a201d228ebd966211f7d1131be19f152be428bd373a92071c71d8deaf83b3e5 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the target blob to fetch"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the target blob to fetch"), PreRunE: func(cmd *cobra.Command, args []string) error { if opts.outputPath == "" && !opts.OutputDescriptor { return errors.New("either `--output` or `--descriptor` must be provided") diff --git a/cmd/oras/root/blob/push.go b/cmd/oras/root/blob/push.go index 5515cf5de..801874048 100644 --- a/cmd/oras/root/blob/push.go +++ b/cmd/oras/root/blob/push.go @@ -25,6 +25,7 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" "oras.land/oras-go/v2" + "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/track" oerrors "oras.land/oras/cmd/oras/internal/errors" @@ -74,9 +75,7 @@ Example - Push blob without TLS: Example - Push blob 'hi.txt' into an OCI image layout folder 'layout-dir': oras blob push --oci-layout layout-dir hi.txt `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 2, "exactly 2 arguments" - }, "the destination to push to and the file to read blob content from"), + Args: oerrors.CheckArgs(argument.Exactly(2), "the destination to push to and the file to read blob content from"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] opts.fileRef = args[1] diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 17fd35956..31d63a85e 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -29,6 +29,7 @@ import ( "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/track" oerrors "oras.land/oras/cmd/oras/internal/errors" @@ -84,9 +85,7 @@ Example - Copy an artifact with multiple tags: Example - Copy an artifact with multiple tags with concurrency tuned: oras cp --concurrency 10 localhost:5000/net-monitor:v1 localhost:5000/net-monitor-copy:tag1,tag2,tag3 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 2, "exactly 2 arguments" - }, "the source and destination for copying"), + Args: oerrors.CheckArgs(argument.Exactly(2), "the source and destination for copying"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.From.RawReference = args[0] refs := strings.Split(args[1], ",") diff --git a/cmd/oras/root/discover.go b/cmd/oras/root/discover.go index 5f09c1bfd..ec67d839c 100644 --- a/cmd/oras/root/discover.go +++ b/cmd/oras/root/discover.go @@ -28,6 +28,7 @@ import ( "gopkg.in/yaml.v3" "oras.land/oras-go/v2" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/graph" @@ -74,9 +75,7 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout oras discover --oci-layout layout-dir:v1 oras discover --oci-layout -v -o tree layout-dir:v1 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the target artifact to discover referrers from"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the target artifact to discover referrers from"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] return option.Parse(&opts) diff --git a/cmd/oras/root/login.go b/cmd/oras/root/login.go index 4e8dd4320..4eca66986 100644 --- a/cmd/oras/root/login.go +++ b/cmd/oras/root/login.go @@ -25,6 +25,7 @@ import ( credentials "github.com/oras-project/oras-credentials-go" "github.com/spf13/cobra" "golang.org/x/term" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/credential" @@ -62,9 +63,7 @@ Example - Log in with username and password in an interactive terminal: Example - Log in with username and password in an interactive terminal and no TLS check: oras login --insecure localhost:5000 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the registry to log in to"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the registry to log in to"), PreRunE: func(cmd *cobra.Command, args []string) error { return option.Parse(&opts) }, diff --git a/cmd/oras/root/logout.go b/cmd/oras/root/logout.go index 61c5bf49f..fe6241860 100644 --- a/cmd/oras/root/logout.go +++ b/cmd/oras/root/logout.go @@ -21,6 +21,7 @@ import ( credentials "github.com/oras-project/oras-credentials-go" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/internal/credential" ) @@ -42,9 +43,7 @@ func logoutCmd() *cobra.Command { Example - Logout: oras logout localhost:5000 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the registry you want to log out"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the registry you want to log out"), RunE: func(cmd *cobra.Command, args []string) error { opts.hostname = args[0] return runLogout(cmd.Context(), opts) diff --git a/cmd/oras/root/manifest/delete.go b/cmd/oras/root/manifest/delete.go index 22d9fc7d4..b8e43f0c6 100644 --- a/cmd/oras/root/manifest/delete.go +++ b/cmd/oras/root/manifest/delete.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/registryutil" @@ -57,9 +58,7 @@ Example - Delete a manifest and print its descriptor: Example - Delete a manifest by digest 'sha256:99e4703fbf30916f549cd6bfa9cdbab614b5392fbe64fdee971359a77073cdf9' from repository 'localhost:5000/hello': oras manifest delete localhost:5000/hello@sha:99e4703fbf30916f549cd6bfa9cdbab614b5392fbe64fdee971359a77073cdf9 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the manifest to delete"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the manifest to delete"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] if opts.OutputDescriptor && !opts.Force { diff --git a/cmd/oras/root/manifest/fetch.go b/cmd/oras/root/manifest/fetch.go index d4acbfde7..dad6917c1 100644 --- a/cmd/oras/root/manifest/fetch.go +++ b/cmd/oras/root/manifest/fetch.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2" "oras.land/oras-go/v2/registry/remote" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" ) @@ -70,9 +71,7 @@ Example - Fetch raw manifest from an OCI image layout folder 'layout-dir': Example - Fetch raw manifest from an OCI layout archive file 'layout.tar': oras manifest fetch --oci-layout layout.tar:v1 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the manifest to fetch"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the manifest to fetch"), PreRunE: func(cmd *cobra.Command, args []string) error { if opts.outputPath == "-" && opts.OutputDescriptor { return errors.New("`--output -` cannot be used with `--descriptor` at the same time") diff --git a/cmd/oras/root/manifest/fetch_config.go b/cmd/oras/root/manifest/fetch_config.go index 6e23685b4..2ce4a8e6b 100644 --- a/cmd/oras/root/manifest/fetch_config.go +++ b/cmd/oras/root/manifest/fetch_config.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/descriptor" @@ -68,9 +69,7 @@ Example - Fetch the descriptor of the config: Example - Fetch and print the prettified descriptor of the config: oras manifest fetch-config --descriptor --pretty localhost:5000/hello:v1 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the manifest config to fetch"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the manifest config to fetch"), PreRunE: func(cmd *cobra.Command, args []string) error { if opts.outputPath == "-" && opts.OutputDescriptor { return errors.New("`--output -` cannot be used with `--descriptor` at the same time") diff --git a/cmd/oras/root/manifest/push.go b/cmd/oras/root/manifest/push.go index f0bfe3ac5..a961b9d4c 100644 --- a/cmd/oras/root/manifest/push.go +++ b/cmd/oras/root/manifest/push.go @@ -28,6 +28,7 @@ import ( "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry/remote" + "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/display" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" @@ -81,9 +82,7 @@ Example - Push a manifest to repository 'localhost:5000/hello' and tag with 'tag Example - Push a manifest to an OCI image layout folder 'layout-dir' and tag with 'v1': oras manifest push --oci-layout layout-dir:v1 manifest.json `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 2, "exactly 2 arguments" - }, "the destination to push to and the file to read manifest content from"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the destination to push to and the file to read manifest content from"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.fileRef = args[1] if opts.fileRef == "-" && opts.PasswordFromStdin { diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index 588a90cff..c96c60ffa 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -28,6 +28,7 @@ import ( "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/content/file" + "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/track" oerrors "oras.land/oras/cmd/oras/internal/errors" @@ -85,9 +86,7 @@ Example - Pull artifact files from an OCI image layout folder 'layout-dir': Example - Pull artifact files from an OCI layout archive 'layout.tar': oras pull --oci-layout layout.tar:v1 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the artifact reference you want to pull"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the artifact reference you want to pull"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] return option.Parse(&opts) diff --git a/cmd/oras/root/push.go b/cmd/oras/root/push.go index 25a6ba191..c9bda91de 100644 --- a/cmd/oras/root/push.go +++ b/cmd/oras/root/push.go @@ -30,6 +30,7 @@ import ( "oras.land/oras-go/v2/content/file" "oras.land/oras-go/v2/content/memory" "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/display" "oras.land/oras/cmd/oras/internal/display/track" oerrors "oras.land/oras/cmd/oras/internal/errors" @@ -101,9 +102,7 @@ Example - Push file "hi.txt" with multiple tags and concurrency level tuned: Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with tag 'test': oras push --oci-layout layout-dir:test hi.txt `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) >= 1, "at least 1 argument" - }, "the destination for pushing"), + Args: oerrors.CheckArgs(argument.AtLeast(1), "the destination for pushing"), PreRunE: func(cmd *cobra.Command, args []string) error { refs := strings.Split(args[0], ",") opts.RawReference = refs[0] diff --git a/cmd/oras/root/repo/ls.go b/cmd/oras/root/repo/ls.go index dbf27cf48..ea64b1968 100644 --- a/cmd/oras/root/repo/ls.go +++ b/cmd/oras/root/repo/ls.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/spf13/cobra" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" "oras.land/oras/internal/repository" @@ -51,9 +52,7 @@ Example - List the repositories under a namespace in the registry: Example - List the repositories under the registry that include values lexically after last: oras repo ls --last "last_repo" localhost:5000 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the target registry to list repositories from"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the target registry to list repositories from"), Aliases: []string{"list"}, PreRunE: func(cmd *cobra.Command, args []string) error { return option.Parse(&opts) diff --git a/cmd/oras/root/repo/tags.go b/cmd/oras/root/repo/tags.go index 2ca2a49fb..6eddc504b 100644 --- a/cmd/oras/root/repo/tags.go +++ b/cmd/oras/root/repo/tags.go @@ -22,6 +22,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/spf13/cobra" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" ) @@ -62,9 +63,7 @@ Example - [Experimental] Show tags associated with a particular tagged resource: Example - [Experimental] Show tags associated with a digest: oras repo tags localhost:5000/hello@sha256:c551125a624189cece9135981621f3f3144564ddabe14b523507bf74c2281d9b `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the target repository to list tags from"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the target repository to list tags from"), Aliases: []string{"show-tags"}, PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] diff --git a/cmd/oras/root/resolve.go b/cmd/oras/root/resolve.go index 4773eb7e4..976fac3cc 100644 --- a/cmd/oras/root/resolve.go +++ b/cmd/oras/root/resolve.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2" + "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" ) @@ -44,9 +45,7 @@ func resolveCmd() *cobra.Command { Example - Resolve digest of the target artifact: oras resolve localhost:5000/hello-world:v1 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) == 1, "exactly 1 argument" - }, "the target artifact reference to resolve"), + Args: oerrors.CheckArgs(argument.Exactly(1), "the target artifact reference to resolve"), Aliases: []string{"digest"}, PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] diff --git a/cmd/oras/root/tag.go b/cmd/oras/root/tag.go index 670fdeb61..e4f65f830 100644 --- a/cmd/oras/root/tag.go +++ b/cmd/oras/root/tag.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra" "oras.land/oras-go/v2" "oras.land/oras-go/v2/registry" + "oras.land/oras/cmd/oras/internal/argument" "oras.land/oras/cmd/oras/internal/display" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" @@ -57,9 +58,7 @@ Example - Tag the manifest 'v1.0.1' in 'localhost:5000/hello' to 'v1.0.1', 'v1.0 Example - Tag the manifest 'v1.0.1' to 'v1.0.2' in an OCI image layout folder 'layout-dir': oras tag layout-dir:v1.0.1 v1.0.2 `, - Args: oerrors.ArgsChecker(func(args []string) (bool, string) { - return len(args) >= 2, "at least 2 argument" - }, "the to-be-retage artifact and the tags to be added"), + Args: oerrors.CheckArgs(argument.AtLeast(1), "the to-be-retage artifact and the tags to be added"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.RawReference = args[0] if _, err := registry.ParseReference(opts.RawReference); err != nil { From fd0cee110b7c96bf86301c0a290b048a98d58934 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 21 Dec 2023 07:02:35 +0000 Subject: [PATCH 31/33] doc clean Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index dd4375259..8972c228a 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -22,12 +22,14 @@ import ( "oras.land/oras-go/v2/registry" ) +// Error is the error type for CLI error messaging. type Error struct { Err error Usage string Recommendation string } +// Error implements the error interface. func (o *Error) Error() string { ret := o.Err.Error() if o.Usage != "" { From e905e356d8963cac7e3fbebde375817af5a091ed Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Thu, 21 Dec 2023 07:11:34 +0000 Subject: [PATCH 32/33] bug fix Signed-off-by: Billy Zha --- cmd/oras/root/manifest/push.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/root/manifest/push.go b/cmd/oras/root/manifest/push.go index a961b9d4c..d001ffd2f 100644 --- a/cmd/oras/root/manifest/push.go +++ b/cmd/oras/root/manifest/push.go @@ -82,7 +82,7 @@ Example - Push a manifest to repository 'localhost:5000/hello' and tag with 'tag Example - Push a manifest to an OCI image layout folder 'layout-dir' and tag with 'v1': oras manifest push --oci-layout layout-dir:v1 manifest.json `, - Args: oerrors.CheckArgs(argument.Exactly(1), "the destination to push to and the file to read manifest content from"), + Args: oerrors.CheckArgs(argument.Exactly(2), "the destination to push to and the file to read manifest content from"), PreRunE: func(cmd *cobra.Command, args []string) error { opts.fileRef = args[1] if opts.fileRef == "-" && opts.PasswordFromStdin { From 4367ec32cc067c7988856ff207746a30a6c5260e Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Fri, 22 Dec 2023 04:10:29 +0000 Subject: [PATCH 33/33] Add unwrap Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 8972c228a..b93144428 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -29,6 +29,11 @@ type Error struct { Recommendation string } +// Unwrap implements the errors.Wrapper interface. +func (o *Error) Unwrap() error { + return o.Err +} + // Error implements the error interface. func (o *Error) Error() string { ret := o.Err.Error()