From 812bb59b9b6332df1ae51c8ee65809c7cdb3dbe4 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Fri, 19 Jan 2024 17:18:17 +0800 Subject: [PATCH] chore(ux): give recommendation when empty creds found during basic auth (#1235) Signed-off-by: Billy Zha --- cmd/oras/internal/errors/errors.go | 38 +++++++++++++++---- cmd/oras/internal/option/remote.go | 38 +++++++++++++++++-- cmd/oras/internal/option/target.go | 7 +++- cmd/oras/root/attach.go | 4 +- cmd/oras/root/blob/delete.go | 4 +- cmd/oras/root/blob/fetch.go | 4 +- cmd/oras/root/blob/push.go | 4 +- cmd/oras/root/cp.go | 6 +-- cmd/oras/root/cp_test.go | 2 +- cmd/oras/root/discover.go | 6 +-- cmd/oras/root/login.go | 2 +- cmd/oras/root/logout.go | 2 +- cmd/oras/root/manifest/delete.go | 4 +- cmd/oras/root/manifest/fetch.go | 4 +- cmd/oras/root/manifest/fetch_config.go | 4 +- cmd/oras/root/push.go | 4 +- cmd/oras/root/repo/ls.go | 4 +- cmd/oras/root/repo/tags.go | 4 +- cmd/oras/root/resolve.go | 4 +- cmd/oras/root/tag.go | 4 +- go.mod | 3 +- go.sum | 6 +-- internal/credential/store.go | 2 +- test/e2e/internal/utils/testdata.go | 3 +- test/e2e/suite/auth/auth.go | 26 ++++++++++++- test/e2e/testdata/empty.registry.config | 1 + .../{legacy.config => legacy.registry.config} | 0 27 files changed, 136 insertions(+), 54 deletions(-) create mode 100644 test/e2e/testdata/empty.registry.config rename test/e2e/testdata/{legacy.config => legacy.registry.config} (100%) diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index d64591275..f333e4d2f 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -16,10 +16,12 @@ limitations under the License. package errors import ( + "errors" "fmt" "strings" "github.com/spf13/cobra" + "oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/errcode" ) @@ -83,8 +85,8 @@ func Command(cmd *cobra.Command, handler Modifier) *cobra.Command { return cmd } -// Trim tries to trim toTrim from err. -func Trim(err error, toTrim error) error { +// TrimErrResp tries to trim toTrim from err. +func TrimErrResp(err error, toTrim error) error { var inner error if errResp, ok := toTrim.(*errcode.ErrorResponse); ok { if len(errResp.Errors) == 0 { @@ -94,14 +96,36 @@ func Trim(err error, toTrim error) error { } else { return err } + return reWrap(err, toTrim, inner) +} - if rewrapped := reWrap(err, toTrim, inner); rewrapped != nil { - return rewrapped +// TrimErrBasicCredentialNotFound trims the credentials from err. +// Caller should make sure the err is auth.ErrBasicCredentialNotFound. +func TrimErrBasicCredentialNotFound(err error) error { + toTrim := err + inner := err + for { + switch x := inner.(type) { + case interface{ Unwrap() error }: + toTrim = inner + inner = x.Unwrap() + continue + case interface{ Unwrap() []error }: + for _, errItem := range x.Unwrap() { + if errors.Is(errItem, auth.ErrBasicCredentialNotFound) { + toTrim = errItem + inner = errItem + break + } + } + continue + } + break } - return inner + return reWrap(err, toTrim, auth.ErrBasicCredentialNotFound) } -// reWrap re-wraps errA to errC and trims out errB, returns nil if scrub fails. +// reWrap re-wraps errA to errC and trims out errB, returns errC if scrub fails. // +---------- errA ----------+ // | +---- errB ----+ | +---- errA ----+ // | | errC | | => | errC | @@ -115,7 +139,7 @@ func reWrap(errA, errB, errC error) error { if idx := strings.Index(contentA, contentB); idx > 0 { return fmt.Errorf("%s%w", contentA[:idx], errC) } - return nil + return errC } // NewErrEmptyTagOrDigest creates a new error based on the reference string. diff --git a/cmd/oras/internal/option/remote.go b/cmd/oras/internal/option/remote.go index 255393934..fa28bfafd 100644 --- a/cmd/oras/internal/option/remote.go +++ b/cmd/oras/internal/option/remote.go @@ -28,13 +28,13 @@ import ( "strings" "sync" - credentials "github.com/oras-project/oras-credentials-go" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras-go/v2/registry/remote/credentials" "oras.land/oras-go/v2/registry/remote/errcode" "oras.land/oras-go/v2/registry/remote/retry" oerrors "oras.land/oras/cmd/oras/internal/errors" @@ -62,6 +62,7 @@ type Remote struct { headers http.Header warned map[string]*sync.Map plainHTTP func() (plainHTTP bool, enforced bool) + store credentials.Store } // EnableDistributionSpecFlag set distribution specification flag as applicable. @@ -226,15 +227,27 @@ func (opts *Remote) authClient(registry string, debug bool) (client *auth.Client return cred, nil } } else { - store, err := credential.NewStore(opts.Configs...) + var err error + opts.store, err = credential.NewStore(opts.Configs...) if err != nil { return nil, err } - client.Credential = credentials.Credential(store) + client.Credential = credentials.Credential(opts.store) } return } +// ConfigPath returns the config path of the credential store. +func (opts *Remote) ConfigPath() (string, error) { + if opts.store == nil { + return "", errors.New("no credential store initialized") + } + if ds, ok := opts.store.(*credentials.DynamicStore); ok { + return ds.ConfigPath(), nil + } + return "", errors.New("store doesn't support getting config path") +} + func (opts *Remote) parseCustomHeaders() error { if len(opts.headerFlags) != 0 { headers := map[string][]string{} @@ -330,11 +343,28 @@ func (opts *Remote) isPlainHttp(registry string) bool { // Modify modifies error during cmd execution. func (opts *Remote) Modify(cmd *cobra.Command, err error) (error, bool) { var errResp *errcode.ErrorResponse + + if errors.Is(err, auth.ErrBasicCredentialNotFound) { + return opts.DecorateCredentialError(err), true + } + if errors.As(err, &errResp) { cmd.SetErrPrefix(oerrors.RegistryErrorPrefix) return &oerrors.Error{ - Err: oerrors.Trim(err, errResp), + Err: oerrors.TrimErrResp(err, errResp), }, true } return err, false } + +// DecorateCredentialError decorate error with recommendation. +func (opts *Remote) DecorateCredentialError(err error) *oerrors.Error { + configPath := " " + if path, pathErr := opts.ConfigPath(); pathErr == nil { + configPath += fmt.Sprintf("at %q ", path) + } + return &oerrors.Error{ + Err: oerrors.TrimErrBasicCredentialNotFound(err), + Recommendation: fmt.Sprintf(`Please check whether the registry credential stored in the authentication file%sis correct`, configPath), + } +} diff --git a/cmd/oras/internal/option/target.go b/cmd/oras/internal/option/target.go index 3d115c7a9..5251468f3 100644 --- a/cmd/oras/internal/option/target.go +++ b/cmd/oras/internal/option/target.go @@ -35,6 +35,7 @@ import ( "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/errcode" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/fileref" @@ -250,6 +251,10 @@ func (opts *Target) Modify(cmd *cobra.Command, err error) (error, bool) { return err, false } + if errors.Is(err, auth.ErrBasicCredentialNotFound) { + return opts.DecorateCredentialError(err), true + } + if errors.Is(err, errdef.ErrNotFound) { cmd.SetErrPrefix(oerrors.RegistryErrorPrefix) return err, true @@ -274,7 +279,7 @@ func (opts *Target) Modify(cmd *cobra.Command, err error) (error, bool) { cmd.SetErrPrefix(oerrors.RegistryErrorPrefix) ret := &oerrors.Error{ - Err: oerrors.Trim(err, errResp), + Err: oerrors.TrimErrResp(err, errResp), } if ref.Registry == "docker.io" && errResp.StatusCode == http.StatusUnauthorized { diff --git a/cmd/oras/root/attach.go b/cmd/oras/root/attach.go index 671418065..eb757cc94 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, opts) + return runAttach(cmd, &opts) }, } @@ -99,7 +99,7 @@ Example - Attach file to the manifest tagged 'v1' in an OCI image layout folder return oerrors.Command(cmd, &opts.Target) } -func runAttach(cmd *cobra.Command, opts attachOptions) error { +func runAttach(cmd *cobra.Command, opts *attachOptions) error { ctx, logger := opts.WithContext(cmd.Context()) annotations, err := opts.LoadManifestAnnotations() if err != nil { diff --git a/cmd/oras/root/blob/delete.go b/cmd/oras/root/blob/delete.go index 22d621d3d..8fdd1bfe5 100644 --- a/cmd/oras/root/blob/delete.go +++ b/cmd/oras/root/blob/delete.go @@ -63,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, opts) + return deleteBlob(cmd, &opts) }, } @@ -71,7 +71,7 @@ Example - Delete a blob and print its descriptor: return oerrors.Command(cmd, &opts.Target) } -func deleteBlob(cmd *cobra.Command, opts deleteBlobOptions) (err error) { +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 { diff --git a/cmd/oras/root/blob/fetch.go b/cmd/oras/root/blob/fetch.go index 1049eb051..b1dc87a67 100644 --- a/cmd/oras/root/blob/fetch.go +++ b/cmd/oras/root/blob/fetch.go @@ -81,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, opts) + return fetchBlob(cmd, &opts) }, } @@ -90,7 +90,7 @@ Example - Fetch and print a blob from OCI image layout archive file 'layout.tar' return oerrors.Command(cmd, &opts.Target) } -func fetchBlob(cmd *cobra.Command, opts fetchBlobOptions) (fetchErr error) { +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) diff --git a/cmd/oras/root/blob/push.go b/cmd/oras/root/blob/push.go index 1f892a6c2..849c79573 100644 --- a/cmd/oras/root/blob/push.go +++ b/cmd/oras/root/blob/push.go @@ -90,7 +90,7 @@ Example - Push blob 'hi.txt' into an OCI image layout folder 'layout-dir': return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return pushBlob(cmd.Context(), opts) + return pushBlob(cmd.Context(), &opts) }, } @@ -100,7 +100,7 @@ Example - Push blob 'hi.txt' into an OCI image layout folder 'layout-dir': return oerrors.Command(cmd, &opts.Target) } -func pushBlob(ctx context.Context, opts pushBlobOptions) (err error) { +func pushBlob(ctx context.Context, opts *pushBlobOptions) (err error) { ctx, logger := opts.WithContext(ctx) target, err := opts.NewTarget(opts.Common, logger) diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 74ae3985a..1ab2a78b7 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, opts) + return runCopy(cmd, &opts) }, } cmd.Flags().BoolVarP(&opts.recursive, "recursive", "r", false, "[Preview] recursively copy the artifact and its referrer artifacts") @@ -105,7 +105,7 @@ Example - Copy an artifact with multiple tags with concurrency tuned: return oerrors.Command(cmd, &opts.BinaryTarget) } -func runCopy(cmd *cobra.Command, opts copyOptions) error { +func runCopy(cmd *cobra.Command, opts *copyOptions) error { ctx, logger := opts.WithContext(cmd.Context()) // Prepare source @@ -148,7 +148,7 @@ func runCopy(cmd *cobra.Command, opts copyOptions) error { return nil } -func doCopy(ctx context.Context, src oras.ReadOnlyGraphTarget, dst oras.GraphTarget, opts copyOptions) (ocispec.Descriptor, error) { +func doCopy(ctx context.Context, src oras.ReadOnlyGraphTarget, dst oras.GraphTarget, opts *copyOptions) (ocispec.Descriptor, error) { // Prepare copy options committed := &sync.Map{} extendedCopyOptions := oras.DefaultExtendedCopyOptions diff --git a/cmd/oras/root/cp_test.go b/cmd/oras/root/cp_test.go index 53161772c..2ef53ba11 100644 --- a/cmd/oras/root/cp_test.go +++ b/cmd/oras/root/cp_test.go @@ -66,7 +66,7 @@ func Test_doCopy(t *testing.T) { opts.From.Reference = desc.Digest.String() dst := memory.New() // test - _, err = doCopy(context.Background(), src, dst, opts) + _, err = doCopy(context.Background(), src, dst, &opts) if err != nil { t.Fatal(err) } diff --git a/cmd/oras/root/discover.go b/cmd/oras/root/discover.go index 2bb16c597..d21b8fb7e 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, opts) + return runDiscover(cmd, &opts) }, } @@ -92,7 +92,7 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout return oerrors.Command(cmd, &opts.Target) } -func runDiscover(cmd *cobra.Command, opts discoverOptions) error { +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 { @@ -112,7 +112,7 @@ func runDiscover(cmd *cobra.Command, opts discoverOptions) error { if opts.outputType == "tree" { root := tree.New(fmt.Sprintf("%s@%s", opts.Path, desc.Digest)) - err = fetchAllReferrers(ctx, repo, desc, opts.artifactType, root, &opts) + err = fetchAllReferrers(ctx, repo, desc, opts.artifactType, root, opts) if err != nil { return err } diff --git a/cmd/oras/root/login.go b/cmd/oras/root/login.go index 07e3f43e0..10f3530b5 100644 --- a/cmd/oras/root/login.go +++ b/cmd/oras/root/login.go @@ -22,9 +22,9 @@ import ( "os" "strings" - credentials "github.com/oras-project/oras-credentials-go" "github.com/spf13/cobra" "golang.org/x/term" + "oras.land/oras-go/v2/registry/remote/credentials" "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/cmd/oras/internal/option" diff --git a/cmd/oras/root/logout.go b/cmd/oras/root/logout.go index fe6241860..d1f853183 100644 --- a/cmd/oras/root/logout.go +++ b/cmd/oras/root/logout.go @@ -18,9 +18,9 @@ package root import ( "context" - credentials "github.com/oras-project/oras-credentials-go" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "oras.land/oras-go/v2/registry/remote/credentials" "oras.land/oras/cmd/oras/internal/argument" oerrors "oras.land/oras/cmd/oras/internal/errors" "oras.land/oras/internal/credential" diff --git a/cmd/oras/root/manifest/delete.go b/cmd/oras/root/manifest/delete.go index 46ef4e2cd..fcb4e1f4b 100644 --- a/cmd/oras/root/manifest/delete.go +++ b/cmd/oras/root/manifest/delete.go @@ -66,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, opts) + return deleteManifest(cmd, &opts) }, } @@ -75,7 +75,7 @@ Example - Delete a manifest by digest 'sha256:99e4703fbf30916f549cd6bfa9cdbab614 return oerrors.Command(cmd, &opts.Target) } -func deleteManifest(cmd *cobra.Command, opts deleteOptions) error { +func deleteManifest(cmd *cobra.Command, opts *deleteOptions) error { ctx, logger := opts.WithContext(cmd.Context()) manifests, err := opts.NewManifestDeleter(opts.Common, logger) if err != nil { diff --git a/cmd/oras/root/manifest/fetch.go b/cmd/oras/root/manifest/fetch.go index 8b7d5edd4..5726552a4 100644 --- a/cmd/oras/root/manifest/fetch.go +++ b/cmd/oras/root/manifest/fetch.go @@ -80,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, opts) + return fetchManifest(cmd, &opts) }, } @@ -90,7 +90,7 @@ Example - Fetch raw manifest from an OCI layout archive file 'layout.tar': return oerrors.Command(cmd, &opts.Target) } -func fetchManifest(cmd *cobra.Command, opts fetchOptions) (fetchErr error) { +func fetchManifest(cmd *cobra.Command, opts *fetchOptions) (fetchErr error) { ctx, logger := opts.WithContext(cmd.Context()) target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) diff --git a/cmd/oras/root/manifest/fetch_config.go b/cmd/oras/root/manifest/fetch_config.go index 28396bf06..8eadcf25c 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, opts) + return fetchConfig(cmd, &opts) }, } @@ -87,7 +87,7 @@ Example - Fetch and print the prettified descriptor of the config: return oerrors.Command(cmd, &opts.Target) } -func fetchConfig(cmd *cobra.Command, opts fetchConfigOptions) (fetchErr error) { +func fetchConfig(cmd *cobra.Command, opts *fetchConfigOptions) (fetchErr error) { ctx, logger := opts.WithContext(cmd.Context()) repo, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) diff --git a/cmd/oras/root/push.go b/cmd/oras/root/push.go index c4825de09..5616e6086 100644 --- a/cmd/oras/root/push.go +++ b/cmd/oras/root/push.go @@ -124,7 +124,7 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return runPush(cmd.Context(), opts) + return runPush(cmd.Context(), &opts) }, } cmd.Flags().StringVarP(&opts.manifestConfigRef, "config", "", "", "`path` of image config file") @@ -135,7 +135,7 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t return oerrors.Command(cmd, &opts.Target) } -func runPush(ctx context.Context, opts pushOptions) error { +func runPush(ctx context.Context, opts *pushOptions) error { ctx, logger := opts.WithContext(ctx) annotations, err := opts.LoadManifestAnnotations() if err != nil { diff --git a/cmd/oras/root/repo/ls.go b/cmd/oras/root/repo/ls.go index 84ffe2e06..31d8493f3 100644 --- a/cmd/oras/root/repo/ls.go +++ b/cmd/oras/root/repo/ls.go @@ -62,7 +62,7 @@ Example - List the repositories under the registry that include values lexically if opts.hostname, opts.namespace, err = repository.ParseRepoPath(args[0]); err != nil { return fmt.Errorf("could not parse repository path: %w", err) } - return listRepository(cmd.Context(), opts) + return listRepository(cmd.Context(), &opts) }, } @@ -71,7 +71,7 @@ Example - List the repositories under the registry that include values lexically return oerrors.Command(cmd, &opts.Remote) } -func listRepository(ctx context.Context, opts repositoryOptions) error { +func listRepository(ctx context.Context, opts *repositoryOptions) error { ctx, logger := opts.WithContext(ctx) reg, err := opts.Remote.NewRegistry(opts.hostname, opts.Common, logger) if err != nil { diff --git a/cmd/oras/root/repo/tags.go b/cmd/oras/root/repo/tags.go index eacb87594..7588eaced 100644 --- a/cmd/oras/root/repo/tags.go +++ b/cmd/oras/root/repo/tags.go @@ -70,7 +70,7 @@ Example - [Experimental] Show tags associated with a digest: return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - return showTags(cmd.Context(), opts) + return showTags(cmd.Context(), &opts) }, } cmd.Flags().StringVar(&opts.last, "last", "", "start after the tag specified by `last`") @@ -79,7 +79,7 @@ Example - [Experimental] Show tags associated with a digest: return oerrors.Command(cmd, &opts.Target) } -func showTags(ctx context.Context, opts showTagsOptions) error { +func showTags(ctx context.Context, opts *showTagsOptions) error { ctx, logger := opts.WithContext(ctx) finder, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { diff --git a/cmd/oras/root/resolve.go b/cmd/oras/root/resolve.go index f71302fa0..433cf097b 100644 --- a/cmd/oras/root/resolve.go +++ b/cmd/oras/root/resolve.go @@ -51,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, opts) + return runResolve(cmd, &opts) }, } @@ -60,7 +60,7 @@ Example - Resolve digest of the target artifact: return oerrors.Command(cmd, &opts.Target) } -func runResolve(cmd *cobra.Command, opts resolveOptions) error { +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 { diff --git a/cmd/oras/root/tag.go b/cmd/oras/root/tag.go index 633447e1a..7eaba777b 100644 --- a/cmd/oras/root/tag.go +++ b/cmd/oras/root/tag.go @@ -81,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, opts) + return tagManifest(cmd, &opts) }, } @@ -90,7 +90,7 @@ 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(cmd *cobra.Command, opts tagOptions) error { +func tagManifest(cmd *cobra.Command, opts *tagOptions) error { ctx, logger := opts.WithContext(cmd.Context()) target, err := opts.NewTarget(opts.Common, logger) if err != nil { diff --git a/go.mod b/go.mod index cc69096f8..8e145e10a 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,13 @@ require ( github.com/morikuni/aec v1.0.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc5 - github.com/oras-project/oras-credentials-go v0.3.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 golang.org/x/sync v0.6.0 golang.org/x/term v0.16.0 gopkg.in/yaml.v3 v3.0.1 - oras.land/oras-go/v2 v2.3.1-0.20231227022511-1d9ad6c409b3 + oras.land/oras-go/v2 v2.3.1-0.20240117093352-d8783fe25d71 ) require ( diff --git a/go.sum b/go.sum index eb5dc9f77..1d4bfb01c 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= -github.com/oras-project/oras-credentials-go v0.3.1 h1:sfGqZ8sjPifEaOtjHOQTPr8D+Tql4bpw58Dd9wjmm9w= -github.com/oras-project/oras-credentials-go v0.3.1/go.mod h1:fFCebDQo0Do+gnM96uV9YUnRay0pwuRQupypvofsp4s= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -39,5 +37,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.3.1-0.20231227022511-1d9ad6c409b3 h1:cF1ErV93cAw4i5/MNF4pW6ULHa27ig3oYGlxouzymY8= -oras.land/oras-go/v2 v2.3.1-0.20231227022511-1d9ad6c409b3/go.mod h1:W2rj9/rGtsTh9lmU3S0uq3iwvR6wNvUuD8nmOFmWBUY= +oras.land/oras-go/v2 v2.3.1-0.20240117093352-d8783fe25d71 h1:h7uU+S5uT4nJJr0dhgkARkid0XcAwcxveqBSSeYMR3A= +oras.land/oras-go/v2 v2.3.1-0.20240117093352-d8783fe25d71/go.mod h1:JE7nRv2IHCiuu7kbH3UmDCp7bYD38yfnt1sl0lkLrwk= diff --git a/internal/credential/store.go b/internal/credential/store.go index 5c61d8726..798eb367d 100644 --- a/internal/credential/store.go +++ b/internal/credential/store.go @@ -16,7 +16,7 @@ limitations under the License. package credential import ( - credentials "github.com/oras-project/oras-credentials-go" + "oras.land/oras-go/v2/registry/remote/credentials" ) // NewStore generates a store based on the passed-in config file paths. diff --git a/test/e2e/internal/utils/testdata.go b/test/e2e/internal/utils/testdata.go index e3f3d37fd..5416de653 100644 --- a/test/e2e/internal/utils/testdata.go +++ b/test/e2e/internal/utils/testdata.go @@ -23,7 +23,8 @@ const ( ArtifactRepo = "command/artifacts" Namespace = "command" InvalidRepo = "INVALID" - LegacyConfigName = "legacy.config" + LegacyConfigName = "legacy.registry.config" + EmptyConfigName = "empty.registry.config" // env RegHostKey = "ORAS_REGISTRY_HOST" FallbackRegHostKey = "ORAS_REGISTRY_FALLBACK_HOST" diff --git a/test/e2e/suite/auth/auth.go b/test/e2e/suite/auth/auth.go index 5d192dab6..a31c7fec6 100644 --- a/test/e2e/suite/auth/auth.go +++ b/test/e2e/suite/auth/auth.go @@ -66,6 +66,23 @@ var _ = Describe("Common registry user", func() { }) }) + When("credential for basic auth not found in the config file", func() { + It("should fail with registry error", func() { + RunWithEmptyRegistryConfig("attach", ZOTHost+"/repo:tag", "-a", "test=true", "--artifact-type", "doc/example") + RunWithEmptyRegistryConfig("discover", ZOTHost+"/repo:tag") + RunWithEmptyRegistryConfig("push", "-a", "key=value", ZOTHost+"/repo:tag") + RunWithEmptyRegistryConfig("pull", ZOTHost+"/repo:tag") + RunWithEmptyRegistryConfig("manifest", "fetch", ZOTHost+"/repo:tag") + RunWithEmptyRegistryConfig("blob", "delete", ZOTHost+"/repo@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + RunWithEmptyRegistryConfig("blob", "push", ZOTHost+"/repo", WriteTempFile("blob", "test")) + RunWithEmptyRegistryConfig("tag", ZOTHost+"/repo:tag", "tag1") + RunWithEmptyRegistryConfig("resolve", ZOTHost+"/repo:tag") + RunWithEmptyRegistryConfig("repo", "ls", ZOTHost) + RunWithEmptyRegistryConfig("repo", "tags", RegistryRef(ZOTHost, "repo", "")) + RunWithEmptyRegistryConfig("manifest", "fetch-config", ZOTHost+"/repo:tag") + }) + }) + When("logging in", func() { tmpConfigName := "test.config" It("should succeed to use basic auth", func() { @@ -151,7 +168,7 @@ var _ = Describe("Common registry user", func() { func RunWithoutLogin(args ...string) { ORAS(args...).ExpectFailure(). - MatchErrKeyWords("Error:", "credential required"). + MatchErrKeyWords("Error:", "basic credential not found"). WithDescription("fail without logging in").Exec() } @@ -160,3 +177,10 @@ func RunWithInvalidCreds(args ...string) { MatchErrKeyWords(RegistryErrorPrefix). WithDescription("fail with invalid credentials").Exec() } + +func RunWithEmptyRegistryConfig(args ...string) { + ORAS(append(args, "--registry-config", EmptyConfigName)...).ExpectFailure(). + MatchErrKeyWords("Error: ", fmt.Sprintf(`Please check whether the registry credential stored in the authentication file at %q is correct`, EmptyConfigName)). + WithDescription("fail with empty registry config"). + Exec() +} diff --git a/test/e2e/testdata/empty.registry.config b/test/e2e/testdata/empty.registry.config new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/test/e2e/testdata/empty.registry.config @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/e2e/testdata/legacy.config b/test/e2e/testdata/legacy.registry.config similarity index 100% rename from test/e2e/testdata/legacy.config rename to test/e2e/testdata/legacy.registry.config