Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: improve returned error for invalid arguments #1201

Merged
merged 36 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1e21b65
feat: improve error output of `oras push`
qweeah Dec 6, 2023
5812d73
improve oras cp
qweeah Dec 7, 2023
fbdc31e
bug fix & add e2e test
qweeah Dec 7, 2023
7a5a388
improve discover command
qweeah Dec 7, 2023
7274fcf
test clean
qweeah Dec 7, 2023
c4e3417
improve login
qweeah Dec 7, 2023
bef0c96
improve logout
qweeah Dec 7, 2023
814453b
improve pull
qweeah Dec 7, 2023
9697ef2
improve resolve and tests
qweeah Dec 11, 2023
63c3912
update prompt
qweeah Dec 11, 2023
856a97e
improve tag
qweeah Dec 12, 2023
15132ce
improve version
qweeah Dec 12, 2023
fd31250
add e2e
qweeah Dec 12, 2023
fb1316a
add e2e
qweeah Dec 12, 2023
685544d
improve repo list
qweeah Dec 12, 2023
8638d1d
add repo ls e2e
qweeah Dec 12, 2023
1df2617
add e2e for repo tags
qweeah Dec 12, 2023
7b4dd43
remove focus spec
qweeah Dec 12, 2023
8fb2bb2
improve blob fetch and push with e2e
qweeah Dec 12, 2023
5d3abab
improve manifest commands
qweeah Dec 12, 2023
bcadeab
fix usage path
qweeah Dec 12, 2023
38b58f5
fix error argument output
qweeah Dec 12, 2023
080dac8
add colon
qweeah Dec 12, 2023
de168e8
Merge branch 'main' into errors
qweeah Dec 12, 2023
dbcd8eb
resolve build error
qweeah Dec 12, 2023
b423b5a
add error argument count
qweeah Dec 12, 2023
c3f2549
add square bracket to error arg output
qweeah Dec 12, 2023
2923e26
update usage tests
qweeah Dec 12, 2023
3538dfa
fix e2e failures
qweeah Dec 12, 2023
acd1207
remove argument output
qweeah Dec 12, 2023
0d4ca6d
refactor with new type and wrapper checker
qweeah Dec 21, 2023
3f853cd
Merge remote-tracking branch 'origin_src/main' into errors
qweeah Dec 21, 2023
fd0cee1
doc clean
qweeah Dec 21, 2023
3ab85a6
Merge remote-tracking branch 'origin_src/main' into errors
qweeah Dec 21, 2023
e905e35
bug fix
qweeah Dec 21, 2023
4367ec3
Add unwrap
qweeah Dec 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions cmd/oras/internal/argument/checker.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
39 changes: 39 additions & 0 deletions cmd/oras/internal/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,48 @@ package errors
import (
"fmt"

"github.com/spf13/cobra"
"oras.land/oras-go/v2/registry"
)

// Error is the error type for CLI error messaging.
type Error struct {
qweeah marked this conversation as resolved.
Show resolved Hide resolved
Err error
Usage string
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()
if o.Usage != "" {
ret += fmt.Sprintf("\nUsage: %s", o.Usage)
}
if o.Recommendation != "" {
ret += fmt.Sprintf("\n%s", o.Recommendation)
}
return ret
}

// 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 &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
}
}

// NewErrEmptyTagOrDigest creates a new error based on the reference string.
func NewErrEmptyTagOrDigest(ref registry.Reference) error {
return NewErrEmptyTagOrDigestStr(ref.String())
Expand Down
6 changes: 4 additions & 2 deletions cmd/oras/root/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ 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"
"oras.land/oras/internal/graph"
"oras.land/oras/internal/registryutil"
Expand Down Expand Up @@ -75,8 +77,8 @@ 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.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:]
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/blob/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ 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"
)
Expand Down Expand Up @@ -53,7 +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: cobra.ExactArgs(1),
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 {
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/blob/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ 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"
)

Expand Down Expand Up @@ -67,7 +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: cobra.ExactArgs(1),
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")
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/blob/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ 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"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/file"
)
Expand Down Expand Up @@ -73,7 +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: cobra.ExactArgs(2),
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]
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ 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"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/docker"
"oras.land/oras/internal/graph"
Expand Down Expand Up @@ -83,7 +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: cobra.ExactArgs(2),
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], ",")
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ 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"
"oras.land/oras/internal/tree"
Expand Down Expand Up @@ -73,7 +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: cobra.ExactArgs(1),
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)
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ 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"
"oras.land/oras/internal/io"
Expand Down Expand Up @@ -61,7 +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: cobra.ExactArgs(1),
Args: oerrors.CheckArgs(argument.Exactly(1), "the registry to log in to"),
PreRunE: func(cmd *cobra.Command, args []string) error {
return option.Parse(&opts)
},
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ 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"
)

Expand All @@ -41,7 +43,7 @@ func logoutCmd() *cobra.Command {
Example - Logout:
oras logout localhost:5000
`,
Args: cobra.ExactArgs(1),
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)
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/manifest/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ 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"
)
Expand Down Expand Up @@ -56,7 +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: cobra.ExactArgs(1),
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 {
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/manifest/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ 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"
)

Expand Down Expand Up @@ -69,7 +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: cobra.ExactArgs(1),
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")
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/manifest/fetch_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ 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"
)
Expand Down Expand Up @@ -67,7 +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: cobra.ExactArgs(1),
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")
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/manifest/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ 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"
"oras.land/oras/internal/file"
)
Expand Down Expand Up @@ -80,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: cobra.ExactArgs(2),
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 {
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ 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"
"oras.land/oras/cmd/oras/internal/fileref"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/graph"
Expand Down Expand Up @@ -84,7 +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: cobra.ExactArgs(1),
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)
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ 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"
"oras.land/oras/cmd/oras/internal/fileref"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/contentutil"
Expand Down Expand Up @@ -100,7 +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: cobra.MinimumNArgs(1),
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]
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/repo/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ 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"
)
Expand Down Expand Up @@ -50,7 +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: cobra.ExactArgs(1),
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)
Expand Down
4 changes: 3 additions & 1 deletion cmd/oras/root/repo/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ 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"
)

Expand Down Expand Up @@ -61,7 +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: cobra.ExactArgs(1),
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]
Expand Down
Loading
Loading