From bbe42fd1285b302905f7faaedad349c0de5570a6 Mon Sep 17 00:00:00 2001 From: Xiaoxuan Wang <103478229+wangxiaoxuan273@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:20:47 +0800 Subject: [PATCH] chore(ux): improve error message for oras manifest fetch (#1234) Signed-off-by: Xiaoxuan Wang --- cmd/oras/internal/errors/errors.go | 20 ++++++++++++-------- cmd/oras/internal/option/target.go | 14 +++++++++++--- cmd/oras/root/attach.go | 8 ++++---- cmd/oras/root/blob/delete.go | 9 ++++----- cmd/oras/root/blob/fetch.go | 12 +++++------- cmd/oras/root/cp.go | 8 ++++---- cmd/oras/root/discover.go | 8 ++++---- cmd/oras/root/manifest/delete.go | 9 ++++----- cmd/oras/root/manifest/fetch.go | 9 ++++----- cmd/oras/root/manifest/fetch_config.go | 8 ++++---- cmd/oras/root/pull.go | 8 ++++---- cmd/oras/root/resolve.go | 9 ++++----- cmd/oras/root/tag.go | 11 +++++------ test/e2e/suite/command/blob.go | 11 ++++++++--- test/e2e/suite/command/cp.go | 4 ++++ test/e2e/suite/command/discover.go | 2 +- test/e2e/suite/command/manifest.go | 17 +++++++++++------ test/e2e/suite/command/resolve.go | 2 +- 18 files changed, 94 insertions(+), 75 deletions(-) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 8af80303d..d64591275 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -20,7 +20,6 @@ import ( "strings" "github.com/spf13/cobra" - "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote/errcode" ) @@ -120,11 +119,16 @@ func reWrap(errA, errB, errC error) error { } // NewErrEmptyTagOrDigest creates a new error based on the reference string. -func NewErrEmptyTagOrDigest(ref registry.Reference) error { - return NewErrEmptyTagOrDigestStr(ref.String()) -} - -// NewErrEmptyTagOrDigestStr creates a new error based on the reference string. -func NewErrEmptyTagOrDigestStr(ref string) error { - return fmt.Errorf("%q: no tag or digest when expecting ", ref) +func NewErrEmptyTagOrDigest(ref string, cmd *cobra.Command, needsTag bool) error { + form := `"@"` + errMsg := `no digest specified` + if needsTag { + form = fmt.Sprintf(`":" or %s`, form) + errMsg = "no tag or digest specified" + } + return &Error{ + Err: fmt.Errorf(`"%s": %s`, ref, errMsg), + Usage: fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), + Recommendation: fmt.Sprintf(`Please specify a reference in the form of %s. Run "%s -h" for more options and examples`, form, cmd.CommandPath()), + } } diff --git a/cmd/oras/internal/option/target.go b/cmd/oras/internal/option/target.go index d4b8878fc..3d115c7a9 100644 --- a/cmd/oras/internal/option/target.go +++ b/cmd/oras/internal/option/target.go @@ -236,10 +236,10 @@ func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common, logger return nil, fmt.Errorf("unknown target type: %q", opts.Type) } -// EnsureReferenceNotEmpty ensures whether the tag or digest is empty. -func (opts *Target) EnsureReferenceNotEmpty() error { +// EnsureReferenceNotEmpty returns formalized error when the reference is empty. +func (opts *Target) EnsureReferenceNotEmpty(cmd *cobra.Command, needsTag bool) error { if opts.Reference == "" { - return oerrors.NewErrEmptyTagOrDigestStr(opts.RawReference) + return oerrors.NewErrEmptyTagOrDigest(opts.RawReference, cmd, needsTag) } return nil } @@ -298,6 +298,14 @@ type BinaryTarget struct { resolveFlag []string } +// EnsureSourceTargetReferenceNotEmpty ensures that the from target reference is not empty. +func (opts *BinaryTarget) EnsureSourceTargetReferenceNotEmpty(cmd *cobra.Command) error { + if opts.From.Reference == "" { + return oerrors.NewErrEmptyTagOrDigest(opts.From.RawReference, cmd, true) + } + return nil +} + // EnableDistributionSpecFlag set distribution specification flag as applicable. func (opts *BinaryTarget) EnableDistributionSpecFlag() { opts.From.EnableDistributionSpecFlag() diff --git a/cmd/oras/root/attach.go b/cmd/oras/root/attach.go index f16d0fc4d..671418065 100644 --- a/cmd/oras/root/attach.go +++ b/cmd/oras/root/attach.go @@ -87,7 +87,7 @@ Example - Attach file to the manifest tagged 'v1' in an OCI image layout folder return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return runAttach(cmd.Context(), opts) + return runAttach(cmd, opts) }, } @@ -99,8 +99,8 @@ Example - Attach file to the manifest tagged 'v1' in an OCI image layout folder return oerrors.Command(cmd, &opts.Target) } -func runAttach(ctx context.Context, opts attachOptions) error { - ctx, logger := opts.WithContext(ctx) +func runAttach(cmd *cobra.Command, opts attachOptions) error { + ctx, logger := opts.WithContext(cmd.Context()) annotations, err := opts.LoadManifestAnnotations() if err != nil { return err @@ -120,7 +120,7 @@ func runAttach(ctx context.Context, opts attachOptions) error { if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, true); err != nil { return err } // add both pull and push scope hints for dst repository diff --git a/cmd/oras/root/blob/delete.go b/cmd/oras/root/blob/delete.go index 866aaf40f..22d621d3d 100644 --- a/cmd/oras/root/blob/delete.go +++ b/cmd/oras/root/blob/delete.go @@ -16,7 +16,6 @@ limitations under the License. package blob import ( - "context" "errors" "fmt" "os" @@ -64,7 +63,7 @@ Example - Delete a blob and print its descriptor: return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return deleteBlob(cmd.Context(), opts) + return deleteBlob(cmd, opts) }, } @@ -72,13 +71,13 @@ Example - Delete a blob and print its descriptor: return oerrors.Command(cmd, &opts.Target) } -func deleteBlob(ctx context.Context, opts deleteBlobOptions) (err error) { - ctx, logger := opts.WithContext(ctx) +func deleteBlob(cmd *cobra.Command, opts deleteBlobOptions) (err error) { + ctx, logger := opts.WithContext(cmd.Context()) blobs, err := opts.NewBlobDeleter(opts.Common, logger) if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, false); err != nil { return err } diff --git a/cmd/oras/root/blob/fetch.go b/cmd/oras/root/blob/fetch.go index fdfdb7abc..1049eb051 100644 --- a/cmd/oras/root/blob/fetch.go +++ b/cmd/oras/root/blob/fetch.go @@ -18,11 +18,9 @@ package blob import ( "context" "errors" - "fmt" "io" "os" - "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" "oras.land/oras-go/v2" @@ -83,7 +81,7 @@ Example - Fetch and print a blob from OCI image layout archive file 'layout.tar' }, Aliases: []string{"get"}, RunE: func(cmd *cobra.Command, args []string) error { - return fetchBlob(cmd.Context(), opts) + return fetchBlob(cmd, opts) }, } @@ -92,16 +90,16 @@ Example - Fetch and print a blob from OCI image layout archive file 'layout.tar' return oerrors.Command(cmd, &opts.Target) } -func fetchBlob(ctx context.Context, opts fetchBlobOptions) (fetchErr error) { - ctx, logger := opts.WithContext(ctx) +func fetchBlob(cmd *cobra.Command, opts fetchBlobOptions) (fetchErr error) { + ctx, logger := opts.WithContext(cmd.Context()) var target oras.ReadOnlyTarget target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } - if _, err = digest.Parse(opts.Reference); err != nil { - return fmt.Errorf("%s: blob reference must be of the form ", opts.RawReference) + if err := opts.EnsureReferenceNotEmpty(cmd, false); err != nil { + return err } if repo, ok := target.(*remote.Repository); ok { diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 94eb52044..74ae3985a 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -95,7 +95,7 @@ Example - Copy an artifact with multiple tags with concurrency tuned: return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return runCopy(cmd.Context(), opts) + return runCopy(cmd, opts) }, } cmd.Flags().BoolVarP(&opts.recursive, "recursive", "r", false, "[Preview] recursively copy the artifact and its referrer artifacts") @@ -105,15 +105,15 @@ Example - Copy an artifact with multiple tags with concurrency tuned: return oerrors.Command(cmd, &opts.BinaryTarget) } -func runCopy(ctx context.Context, opts copyOptions) error { - ctx, logger := opts.WithContext(ctx) +func runCopy(cmd *cobra.Command, opts copyOptions) error { + ctx, logger := opts.WithContext(cmd.Context()) // Prepare source src, err := opts.From.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } - if err := opts.From.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureSourceTargetReferenceNotEmpty(cmd); err != nil { return err } diff --git a/cmd/oras/root/discover.go b/cmd/oras/root/discover.go index a35db60a2..2bb16c597 100644 --- a/cmd/oras/root/discover.go +++ b/cmd/oras/root/discover.go @@ -81,7 +81,7 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return runDiscover(cmd.Context(), opts) + return runDiscover(cmd, opts) }, } @@ -92,13 +92,13 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout return oerrors.Command(cmd, &opts.Target) } -func runDiscover(ctx context.Context, opts discoverOptions) error { - ctx, logger := opts.WithContext(ctx) +func runDiscover(cmd *cobra.Command, opts discoverOptions) error { + ctx, logger := opts.WithContext(cmd.Context()) repo, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, true); err != nil { return err } diff --git a/cmd/oras/root/manifest/delete.go b/cmd/oras/root/manifest/delete.go index fa4cf75fe..46ef4e2cd 100644 --- a/cmd/oras/root/manifest/delete.go +++ b/cmd/oras/root/manifest/delete.go @@ -16,7 +16,6 @@ limitations under the License. package manifest import ( - "context" "errors" "fmt" "os" @@ -67,7 +66,7 @@ Example - Delete a manifest by digest 'sha256:99e4703fbf30916f549cd6bfa9cdbab614 return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return deleteManifest(cmd.Context(), opts) + return deleteManifest(cmd, opts) }, } @@ -76,13 +75,13 @@ Example - Delete a manifest by digest 'sha256:99e4703fbf30916f549cd6bfa9cdbab614 return oerrors.Command(cmd, &opts.Target) } -func deleteManifest(ctx context.Context, opts deleteOptions) error { - ctx, logger := opts.WithContext(ctx) +func deleteManifest(cmd *cobra.Command, opts deleteOptions) error { + ctx, logger := opts.WithContext(cmd.Context()) manifests, err := opts.NewManifestDeleter(opts.Common, logger) if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, true); err != nil { return err } diff --git a/cmd/oras/root/manifest/fetch.go b/cmd/oras/root/manifest/fetch.go index 4862891b8..8b7d5edd4 100644 --- a/cmd/oras/root/manifest/fetch.go +++ b/cmd/oras/root/manifest/fetch.go @@ -16,7 +16,6 @@ limitations under the License. package manifest import ( - "context" "encoding/json" "errors" "fmt" @@ -81,7 +80,7 @@ Example - Fetch raw manifest from an OCI layout archive file 'layout.tar': }, Aliases: []string{"get"}, RunE: func(cmd *cobra.Command, args []string) error { - return fetchManifest(cmd.Context(), opts) + return fetchManifest(cmd, opts) }, } @@ -91,14 +90,14 @@ Example - Fetch raw manifest from an OCI layout archive file 'layout.tar': return oerrors.Command(cmd, &opts.Target) } -func fetchManifest(ctx context.Context, opts fetchOptions) (fetchErr error) { - ctx, logger := opts.WithContext(ctx) +func fetchManifest(cmd *cobra.Command, opts fetchOptions) (fetchErr error) { + ctx, logger := opts.WithContext(cmd.Context()) target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, true); err != nil { return err } if repo, ok := target.(*remote.Repository); ok { diff --git a/cmd/oras/root/manifest/fetch_config.go b/cmd/oras/root/manifest/fetch_config.go index 2519e491b..28396bf06 100644 --- a/cmd/oras/root/manifest/fetch_config.go +++ b/cmd/oras/root/manifest/fetch_config.go @@ -78,7 +78,7 @@ Example - Fetch and print the prettified descriptor of the config: return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return fetchConfig(cmd.Context(), opts) + return fetchConfig(cmd, opts) }, } @@ -87,14 +87,14 @@ Example - Fetch and print the prettified descriptor of the config: return oerrors.Command(cmd, &opts.Target) } -func fetchConfig(ctx context.Context, opts fetchConfigOptions) (fetchErr error) { - ctx, logger := opts.WithContext(ctx) +func fetchConfig(cmd *cobra.Command, opts fetchConfigOptions) (fetchErr error) { + ctx, logger := opts.WithContext(cmd.Context()) repo, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, true); err != nil { return err } src, err := opts.CachedTarget(repo) diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index d59343002..82cc3264c 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -92,7 +92,7 @@ Example - Pull artifact files from an OCI layout archive 'layout.tar': return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return runPull(cmd.Context(), &opts) + return runPull(cmd, &opts) }, } @@ -106,8 +106,8 @@ Example - Pull artifact files from an OCI layout archive 'layout.tar': return oerrors.Command(cmd, &opts.Target) } -func runPull(ctx context.Context, opts *pullOptions) error { - ctx, logger := opts.WithContext(ctx) +func runPull(cmd *cobra.Command, opts *pullOptions) error { + ctx, logger := opts.WithContext(cmd.Context()) // Copy Options copyOptions := oras.DefaultCopyOptions copyOptions.Concurrency = opts.concurrency @@ -118,7 +118,7 @@ func runPull(ctx context.Context, opts *pullOptions) error { if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, true); err != nil { return err } src, err := opts.CachedTarget(target) diff --git a/cmd/oras/root/resolve.go b/cmd/oras/root/resolve.go index c1d533d34..f71302fa0 100644 --- a/cmd/oras/root/resolve.go +++ b/cmd/oras/root/resolve.go @@ -16,7 +16,6 @@ limitations under the License. package root import ( - "context" "fmt" "github.com/spf13/cobra" @@ -52,7 +51,7 @@ Example - Resolve digest of the target artifact: return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return runResolve(cmd.Context(), opts) + return runResolve(cmd, opts) }, } @@ -61,13 +60,13 @@ Example - Resolve digest of the target artifact: return oerrors.Command(cmd, &opts.Target) } -func runResolve(ctx context.Context, opts resolveOptions) error { - ctx, logger := opts.WithContext(ctx) +func runResolve(cmd *cobra.Command, opts resolveOptions) error { + ctx, logger := opts.WithContext(cmd.Context()) repo, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, true); err != nil { return err } resolveOpts := oras.DefaultResolveOptions diff --git a/cmd/oras/root/tag.go b/cmd/oras/root/tag.go index 51c6d81cb..633447e1a 100644 --- a/cmd/oras/root/tag.go +++ b/cmd/oras/root/tag.go @@ -16,7 +16,6 @@ limitations under the License. package root import ( - "context" "errors" "fmt" @@ -67,7 +66,7 @@ Example - Tag the manifest 'v1.0.1' to 'v1.0.2' in an OCI image layout folder 'l } return &oerrors.Error{ Err: errors.New(`there is no "list" sub-command for "oras tag" command`), - Usage: fmt.Sprintf("%s %s", cmd.CommandPath(), cmd.Use), + Usage: fmt.Sprintf("%s %s", cmd.Parent().CommandPath(), cmd.Use), Recommendation: fmt.Sprintf(`If you want to list available tags in %s, use "oras repo tags"`, container), } } @@ -82,7 +81,7 @@ Example - Tag the manifest 'v1.0.1' to 'v1.0.2' in an OCI image layout folder 'l return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return tagManifest(cmd.Context(), opts) + return tagManifest(cmd, opts) }, } @@ -91,13 +90,13 @@ Example - Tag the manifest 'v1.0.1' to 'v1.0.2' in an OCI image layout folder 'l return oerrors.Command(cmd, &opts.Target) } -func tagManifest(ctx context.Context, opts tagOptions) error { - ctx, logger := opts.WithContext(ctx) +func tagManifest(cmd *cobra.Command, opts tagOptions) error { + ctx, logger := opts.WithContext(cmd.Context()) target, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } - if err := opts.EnsureReferenceNotEmpty(); err != nil { + if err := opts.EnsureReferenceNotEmpty(cmd, true); err != nil { return err } diff --git a/test/e2e/suite/command/blob.go b/test/e2e/suite/command/blob.go index 7f0daf5a0..3ca5cfafa 100644 --- a/test/e2e/suite/command/blob.go +++ b/test/e2e/suite/command/blob.go @@ -115,9 +115,9 @@ var _ = Describe("ORAS beginners:", func() { 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() + It("should fail if no digest is provided", func() { + ORAS("blob", "fetch", "--descriptor", RegistryRef(ZOTHost, ImageRepo, "")). + ExpectFailure().MatchErrKeyWords("Error", "no digest specified", "oras blob fetch").Exec() }) It("should fail if provided digest doesn't exist", func() { @@ -168,6 +168,11 @@ var _ = Describe("ORAS beginners:", func() { ORAS("blob", "delete", fmt.Sprintf("%s/%s@%s", ZOTHost, dstRepo, "test"), "--descriptor", "--force").ExpectFailure().Exec() }) + It("should fail if no digest is provided", func() { + ORAS("blob", "delete", RegistryRef(ZOTHost, ImageRepo, "")). + ExpectFailure().MatchErrKeyWords("Error", "no digest specified", "oras blob delete").Exec() + }) + It("should fail to delete a non-existent blob without force flag set", func() { toDeleteRef := RegistryRef(ZOTHost, ImageRepo, invalidDigest) ORAS("blob", "delete", toDeleteRef). diff --git a/test/e2e/suite/command/cp.go b/test/e2e/suite/command/cp.go index c0bb31f57..e09e6f61f 100644 --- a/test/e2e/suite/command/cp.go +++ b/test/e2e/suite/command/cp.go @@ -56,6 +56,10 @@ var _ = Describe("ORAS beginners:", func() { ORAS("cp", RegistryRef(ZOTHost, ImageRepo, foobar.Tag)).ExpectFailure().MatchErrKeyWords("Error:").Exec() }) + It("should fail when no tag or digest is provided for source target", func() { + ORAS("cp", RegistryRef(ZOTHost, ImageRepo, ""), RegistryRef(ZOTHost, ImageRepo, "dst")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest specified", "oras cp").Exec() + }) + It("should fail when source doesn't exist", func() { ORAS("cp", RegistryRef(ZOTHost, ImageRepo, InvalidTag), RegistryRef(ZOTHost, cpTestRepo("nonexistent-source"), "")).ExpectFailure().MatchErrKeyWords(InvalidTag).Exec() }) diff --git a/test/e2e/suite/command/discover.go b/test/e2e/suite/command/discover.go index fd2f678f5..74be6e27e 100644 --- a/test/e2e/suite/command/discover.go +++ b/test/e2e/suite/command/discover.go @@ -61,7 +61,7 @@ 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() + ORAS("discover", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest specified", "oras discover").Exec() }) It("should fail and show detailed error description if no argument provided", func() { diff --git a/test/e2e/suite/command/manifest.go b/test/e2e/suite/command/manifest.go index 48e29356a..442394a27 100644 --- a/test/e2e/suite/command/manifest.go +++ b/test/e2e/suite/command/manifest.go @@ -93,6 +93,10 @@ var _ = Describe("ORAS beginners:", func() { gomega.Expect(err).Should(gbytes.Say("\n")) gomega.Expect(err).Should(gbytes.Say(`Run "oras manifest fetch -h"`)) }) + + It("should fail with suggestion if no tag or digest is provided", func() { + ORAS("manifest", "fetch", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest specified", "oras manifest fetch [flags] {:|@}", "Please specify a reference").Exec() + }) }) When("running `manifest delete`", func() { @@ -157,7 +161,8 @@ var _ = Describe("ORAS beginners:", func() { It("should fail if no digest provided", func() { dstRepo := fmt.Sprintf(repoFmt, "delete", "no-reference") prepare(RegistryRef(ZOTHost, ImageRepo, foobar.Tag), RegistryRef(ZOTHost, dstRepo, "")) - ORAS("manifest", "delete", RegistryRef(ZOTHost, dstRepo, "")).ExpectFailure().MatchErrKeyWords("name@digest").Exec() + ORAS("manifest", "delete", RegistryRef(ZOTHost, dstRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest specified", "oras manifest delete [flags] {:|@}", "Please specify a reference").Exec() + }) }) When("running `manifest fetch-config`", func() { @@ -284,7 +289,7 @@ var _ = Describe("1.1 registry users:", func() { }) It("should fail if no manifest tag or digest is provided", func() { - ORAS("manifest", "fetch", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest").Exec() + ORAS("manifest", "fetch", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest specified", "oras manifest fetch [flags] {:|@}", "Please specify a reference").Exec() }) }) @@ -347,7 +352,7 @@ var _ = Describe("1.1 registry users:", func() { MatchContent(multi_arch.LinuxAMD64ConfigDesc).Exec() }) It("should fail if no manifest tag or digest is provided", func() { - ORAS("manifest", "fetch-config", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest").Exec() + ORAS("manifest", "fetch-config", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest specified", "oras manifest fetch-config").Exec() }) }) @@ -459,10 +464,10 @@ var _ = Describe("OCI image layout users:", func() { ExpectFailure(). MatchErrKeyWords("Error", "--media-type", "--oci-layout").Exec() }) - It("should fail if no manifest tag or digest is provided", func() { + It("should fail with suggestion if no tag or digest is provided", func() { root := PrepareTempOCI(ImageRepo) ORAS("manifest", "fetch", Flags.Layout, root).ExpectFailure(). - MatchErrKeyWords("Error:", "no tag or digest").Exec() + MatchErrKeyWords("Error:", "no tag or digest specified", "oras manifest fetch [flags] {:|@}", "Please specify a reference").Exec() }) }) @@ -507,7 +512,7 @@ var _ = Describe("OCI image layout users:", func() { }) It("should fail if no manifest tag or digest is provided", func() { root := prepare(foobar.Tag) - ORAS("manifest", "fetch-config", Flags.Layout, root).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest").Exec() + ORAS("manifest", "fetch-config", Flags.Layout, root).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest specified", "oras manifest fetch-config").Exec() }) }) diff --git a/test/e2e/suite/command/resolve.go b/test/e2e/suite/command/resolve.go index 9a345f11e..adbef52ba 100644 --- a/test/e2e/suite/command/resolve.go +++ b/test/e2e/suite/command/resolve.go @@ -44,7 +44,7 @@ var _ = Describe("ORAS beginners:", func() { ORAS("resolve", fmt.Sprintf("%s/%s", ZOTHost, InvalidRepo)).ExpectFailure().MatchErrKeyWords("Error:", fmt.Sprintf("invalid reference: invalid repository %q", InvalidRepo)).Exec() }) It("should fail when no tag or digest provided", func() { - ORAS("resolve", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", "no tag or digest when expecting ").Exec() + ORAS("resolve", RegistryRef(ZOTHost, ImageRepo, "")).ExpectFailure().MatchErrKeyWords("Error:", `no tag or digest specified`, "oras resolve [flags] {:|@}", "Please specify a reference").Exec() }) It("should fail when provided manifest reference is not found", func() { ORAS("resolve", RegistryRef(ZOTHost, ImageRepo, "i-dont-think-this-tag-exists")).ExpectFailure().MatchErrKeyWords(RegistryErrorPrefix, "failed to resolve digest:", "not found").Exec()