diff --git a/cmd/oras/internal/option/remote.go b/cmd/oras/internal/option/remote.go index 0f9e3fe42..f56b90bf5 100644 --- a/cmd/oras/internal/option/remote.go +++ b/cmd/oras/internal/option/remote.go @@ -25,8 +25,10 @@ import ( "os" "strconv" "strings" + "sync" credentials "github.com/oras-project/oras-credentials-go" + "github.com/sirupsen/logrus" "github.com/spf13/pflag" "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" @@ -53,6 +55,7 @@ type Remote struct { distributionSpec distributionSpec headerFlags []string headers http.Header + warned map[string]*sync.Map } // EnableDistributionSpecFlag set distribution specification flag as applicable. @@ -247,32 +250,51 @@ func (opts *Remote) Credential() auth.Credential { return credential.Credential(opts.Username, opts.Password) } +func (opts *Remote) handleWarning(registry string, logger logrus.FieldLogger) func(warning remote.Warning) { + if opts.warned == nil { + opts.warned = make(map[string]*sync.Map) + } + warned := opts.warned[registry] + if warned == nil { + warned = &sync.Map{} + opts.warned[registry] = warned + } + logger = logger.WithField("registry", registry) + return func(warning remote.Warning) { + if _, loaded := warned.LoadOrStore(warning.WarningValue, struct{}{}); !loaded { + logger.Warn(warning.Text) + } + } +} + // NewRegistry assembles a oras remote registry. -func (opts *Remote) NewRegistry(hostname string, common Common) (reg *remote.Registry, err error) { - reg, err = remote.NewRegistry(hostname) +func (opts *Remote) NewRegistry(registry string, common Common, logger logrus.FieldLogger) (reg *remote.Registry, err error) { + reg, err = remote.NewRegistry(registry) if err != nil { return nil, err } - hostname = reg.Reference.Registry - reg.PlainHTTP = opts.isPlainHttp(hostname) - if reg.Client, err = opts.authClient(hostname, common.Debug); err != nil { + registry = reg.Reference.Registry + reg.PlainHTTP = opts.isPlainHttp(registry) + reg.HandleWarning = opts.handleWarning(registry, logger) + if reg.Client, err = opts.authClient(registry, common.Debug); err != nil { return nil, err } return } // NewRepository assembles a oras remote repository. -func (opts *Remote) NewRepository(reference string, common Common) (repo *remote.Repository, err error) { +func (opts *Remote) NewRepository(reference string, common Common, logger logrus.FieldLogger) (repo *remote.Repository, err error) { repo, err = remote.NewRepository(reference) if err != nil { return nil, err } - hostname := repo.Reference.Registry - repo.PlainHTTP = opts.isPlainHttp(hostname) - repo.SkipReferrersGC = true - if repo.Client, err = opts.authClient(hostname, common.Debug); err != nil { + registry := repo.Reference.Registry + repo.PlainHTTP = opts.isPlainHttp(registry) + repo.HandleWarning = opts.handleWarning(registry, logger) + if repo.Client, err = opts.authClient(registry, common.Debug); err != nil { return nil, err } + repo.SkipReferrersGC = true if opts.distributionSpec.referrersAPI != nil { if err := repo.SetReferrersCapability(*opts.distributionSpec.referrersAPI); err != nil { return nil, err diff --git a/cmd/oras/internal/option/remote_test.go b/cmd/oras/internal/option/remote_test.go index abd1c3029..9c60d939b 100644 --- a/cmd/oras/internal/option/remote_test.go +++ b/cmd/oras/internal/option/remote_test.go @@ -30,6 +30,7 @@ import ( "reflect" "testing" + "github.com/sirupsen/logrus" "github.com/spf13/pflag" "oras.land/oras-go/v2/registry/remote/auth" ) @@ -180,7 +181,7 @@ func TestRemote_NewRegistry(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - reg, err := opts.NewRegistry(uri.Host, opts.Common) + reg, err := opts.NewRegistry(uri.Host, opts.Common, logrus.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -208,7 +209,7 @@ func TestRemote_NewRepository(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common) + repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common, logrus.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -255,7 +256,7 @@ func TestRemote_NewRepository_Retry(t *testing.T) { if err != nil { t.Fatalf("unexpected error: %v", err) } - repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common) + repo, err := opts.NewRepository(uri.Host+"/"+testRepo, opts.Common, logrus.New()) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/cmd/oras/internal/option/target.go b/cmd/oras/internal/option/target.go index 9494e1789..36290204e 100644 --- a/cmd/oras/internal/option/target.go +++ b/cmd/oras/internal/option/target.go @@ -21,7 +21,9 @@ import ( "fmt" "os" "strings" + "sync" + "github.com/sirupsen/logrus" "github.com/spf13/pflag" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content/oci" @@ -111,7 +113,7 @@ func parseOCILayoutReference(raw string) (path string, ref string, err error) { } // NewTarget generates a new target based on opts. -func (opts *Target) NewTarget(common Common) (oras.GraphTarget, error) { +func (opts *Target) NewTarget(common Common, logger logrus.FieldLogger) (oras.GraphTarget, error) { switch opts.Type { case TargetTypeOCILayout: var err error @@ -121,7 +123,7 @@ func (opts *Target) NewTarget(common Common) (oras.GraphTarget, error) { } return oci.New(opts.Path) case TargetTypeRemote: - repo, err := opts.NewRepository(opts.RawReference, common) + repo, err := opts.NewRepository(opts.RawReference, common, logger) if err != nil { return nil, err } @@ -142,7 +144,7 @@ type ReadOnlyGraphTagFinderTarget interface { } // NewReadonlyTargets generates a new read only target based on opts. -func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common) (ReadOnlyGraphTagFinderTarget, error) { +func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common, logger logrus.FieldLogger) (ReadOnlyGraphTagFinderTarget, error) { switch opts.Type { case TargetTypeOCILayout: var err error @@ -159,7 +161,7 @@ func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common) (ReadO } return oci.NewFromTar(ctx, opts.Path) case TargetTypeRemote: - repo, err := opts.NewRepository(opts.RawReference, common) + repo, err := opts.NewRepository(opts.RawReference, common, logger) if err != nil { return nil, err } @@ -203,6 +205,8 @@ func (opts *BinaryTarget) ApplyFlags(fs *pflag.FlagSet) { // Parse parses user-provided flags and arguments into option struct. func (opts *BinaryTarget) Parse() error { + opts.From.warned = make(map[string]*sync.Map) + opts.To.warned = opts.From.warned // resolve are parsed in array order, latter will overwrite former opts.From.resolveFlag = append(opts.resolveFlag, opts.From.resolveFlag...) opts.To.resolveFlag = append(opts.resolveFlag, opts.To.resolveFlag...) diff --git a/cmd/oras/root/attach.go b/cmd/oras/root/attach.go index 7560e9c1e..b67ca0c99 100644 --- a/cmd/oras/root/attach.go +++ b/cmd/oras/root/attach.go @@ -93,7 +93,7 @@ Example - Attach file to the manifest tagged 'v1' in an OCI image layout folder } func runAttach(ctx context.Context, opts attachOptions) error { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) annotations, err := opts.LoadManifestAnnotations() if err != nil { return err @@ -109,7 +109,7 @@ func runAttach(ctx context.Context, opts attachOptions) error { } defer store.Close() - dst, err := opts.NewTarget(opts.Common) + dst, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/blob/delete.go b/cmd/oras/root/blob/delete.go index 0f55d044b..e1fbb09ac 100644 --- a/cmd/oras/root/blob/delete.go +++ b/cmd/oras/root/blob/delete.go @@ -71,8 +71,8 @@ Example - Delete a blob and print its descriptor: } func deleteBlob(ctx context.Context, opts deleteBlobOptions) (err error) { - ctx, _ = opts.WithContext(ctx) - repo, err := opts.NewRepository(opts.targetRef, opts.Common) + ctx, logger := opts.WithContext(ctx) + repo, err := opts.NewRepository(opts.targetRef, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/blob/fetch.go b/cmd/oras/root/blob/fetch.go index 7f53873a4..d43ae382d 100644 --- a/cmd/oras/root/blob/fetch.go +++ b/cmd/oras/root/blob/fetch.go @@ -90,9 +90,9 @@ Example - Fetch and print a blob from OCI image layout archive file 'layout.tar' } func fetchBlob(ctx context.Context, opts fetchBlobOptions) (fetchErr error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) var target oras.ReadOnlyTarget - target, err := opts.NewReadonlyTarget(ctx, opts.Common) + target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/blob/push.go b/cmd/oras/root/blob/push.go index 9de6c45fe..ea3bffaee 100644 --- a/cmd/oras/root/blob/push.go +++ b/cmd/oras/root/blob/push.go @@ -96,9 +96,9 @@ Example - Push blob 'hi.txt' into an OCI image layout folder 'layout-dir': } func pushBlob(ctx context.Context, opts pushBlobOptions) (err error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) - repo, err := opts.NewTarget(opts.Common) + repo, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index 224de53f7..90f7cb090 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -97,10 +97,10 @@ Example - Copy an artifact with multiple tags with concurrency tuned: } func runCopy(ctx context.Context, opts copyOptions) error { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) // Prepare source - src, err := opts.From.NewReadonlyTarget(ctx, opts.Common) + src, err := opts.From.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } @@ -109,7 +109,7 @@ func runCopy(ctx context.Context, opts copyOptions) error { } // Prepare destination - dst, err := opts.To.NewTarget(opts.Common) + dst, err := opts.To.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/discover.go b/cmd/oras/root/discover.go index b7dd372ae..801710ead 100644 --- a/cmd/oras/root/discover.go +++ b/cmd/oras/root/discover.go @@ -91,8 +91,8 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout } func runDiscover(ctx context.Context, opts discoverOptions) error { - ctx, _ = opts.WithContext(ctx) - repo, err := opts.NewReadonlyTarget(ctx, opts.Common) + ctx, logger := opts.WithContext(ctx) + repo, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } @@ -189,7 +189,7 @@ func printDiscoveredReferrersTable(refs []ocispec.Descriptor, verbose bool) erro print(ref.ArtifactType, ref.Digest) if verbose { if err := printJSON(ref); err != nil { - return fmt.Errorf("Error printing JSON: %w", err) + return fmt.Errorf("error printing JSON: %w", err) } } } diff --git a/cmd/oras/root/login.go b/cmd/oras/root/login.go index aa862fd4d..d6cc65d5d 100644 --- a/cmd/oras/root/login.go +++ b/cmd/oras/root/login.go @@ -75,7 +75,7 @@ Example - Log in with username and password in an interactive terminal and no TL } func runLogin(ctx context.Context, opts loginOptions) (err error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) // prompt for credential if opts.Password == "" { @@ -108,7 +108,7 @@ func runLogin(ctx context.Context, opts loginOptions) (err error) { if err != nil { return err } - remote, err := opts.Remote.NewRegistry(opts.Hostname, opts.Common) + remote, err := opts.Remote.NewRegistry(opts.Hostname, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/manifest/delete.go b/cmd/oras/root/manifest/delete.go index 8ece10400..4458fae77 100644 --- a/cmd/oras/root/manifest/delete.go +++ b/cmd/oras/root/manifest/delete.go @@ -76,8 +76,8 @@ Example - Delete a manifest by digest 'sha256:99e4703fbf30916f549cd6bfa9cdbab614 } func deleteManifest(ctx context.Context, opts deleteOptions) error { - ctx, _ = opts.WithContext(ctx) - repo, err := opts.NewRepository(opts.targetRef, opts.Common) + ctx, logger := opts.WithContext(ctx) + repo, err := opts.NewRepository(opts.targetRef, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/manifest/fetch.go b/cmd/oras/root/manifest/fetch.go index 850ac915f..52ec2f80b 100644 --- a/cmd/oras/root/manifest/fetch.go +++ b/cmd/oras/root/manifest/fetch.go @@ -90,9 +90,9 @@ Example - Fetch raw manifest from an OCI layout archive file 'layout.tar': } func fetchManifest(ctx context.Context, opts fetchOptions) (fetchErr error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) - target, err := opts.NewReadonlyTarget(ctx, opts.Common) + target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/manifest/fetch_config.go b/cmd/oras/root/manifest/fetch_config.go index a909a4d38..e0f871f34 100644 --- a/cmd/oras/root/manifest/fetch_config.go +++ b/cmd/oras/root/manifest/fetch_config.go @@ -86,9 +86,9 @@ Example - Fetch and print the prettified descriptor of the config: } func fetchConfig(ctx context.Context, opts fetchConfigOptions) (fetchErr error) { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) - repo, err := opts.NewReadonlyTarget(ctx, opts.Common) + repo, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/manifest/push.go b/cmd/oras/root/manifest/push.go index c932e8e3f..d9e737918 100644 --- a/cmd/oras/root/manifest/push.go +++ b/cmd/oras/root/manifest/push.go @@ -104,10 +104,10 @@ Example - Push a manifest to an OCI image layout folder 'layout-dir' and tag wit } func pushManifest(ctx context.Context, opts pushOptions) error { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) var target oras.Target var err error - target, err = opts.NewTarget(opts.Common) + target, err = opts.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index c10f330a4..6baf93091 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -103,7 +103,7 @@ Example - Pull artifact files from an OCI layout archive 'layout.tar': } func runPull(ctx context.Context, opts pullOptions) error { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) // Copy Options var printed sync.Map copyOptions := oras.DefaultCopyOptions @@ -182,7 +182,7 @@ func runPull(ctx context.Context, opts pullOptions) error { return ret, nil } - target, err := opts.NewReadonlyTarget(ctx, opts.Common) + target, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/push.go b/cmd/oras/root/push.go index c3cefdd80..5bad2955e 100644 --- a/cmd/oras/root/push.go +++ b/cmd/oras/root/push.go @@ -126,7 +126,7 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t } func runPush(ctx context.Context, opts pushOptions) error { - ctx, _ = opts.WithContext(ctx) + ctx, logger := opts.WithContext(ctx) annotations, err := opts.LoadManifestAnnotations() if err != nil { return err @@ -172,7 +172,7 @@ func runPush(ctx context.Context, opts pushOptions) error { } // prepare push - dst, err := opts.NewTarget(opts.Common) + dst, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/repo/ls.go b/cmd/oras/root/repo/ls.go index f2da5e5f3..cea8dd334 100644 --- a/cmd/oras/root/repo/ls.go +++ b/cmd/oras/root/repo/ls.go @@ -69,8 +69,8 @@ Example - List the repositories under the registry that include values lexically } func listRepository(ctx context.Context, opts repositoryOptions) error { - ctx, _ = opts.WithContext(ctx) - reg, err := opts.Remote.NewRegistry(opts.hostname, opts.Common) + ctx, logger := opts.WithContext(ctx) + reg, err := opts.Remote.NewRegistry(opts.hostname, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/repo/tags.go b/cmd/oras/root/repo/tags.go index 2e7bde777..45e37dba9 100644 --- a/cmd/oras/root/repo/tags.go +++ b/cmd/oras/root/repo/tags.go @@ -79,7 +79,7 @@ Example - [Experimental] Show tags associated with a digest: func showTags(ctx context.Context, opts showTagsOptions) error { ctx, logger := opts.WithContext(ctx) - finder, err := opts.NewReadonlyTarget(ctx, opts.Common) + finder, err := opts.NewReadonlyTarget(ctx, opts.Common, logger) if err != nil { return err } diff --git a/cmd/oras/root/tag.go b/cmd/oras/root/tag.go index a6540ea80..4524d4fb9 100644 --- a/cmd/oras/root/tag.go +++ b/cmd/oras/root/tag.go @@ -76,8 +76,8 @@ Example - Tag the manifest 'v1.0.1' to 'v1.0.2' in an OCI image layout folder 'l } func tagManifest(ctx context.Context, opts tagOptions) error { - ctx, _ = opts.WithContext(ctx) - target, err := opts.NewTarget(opts.Common) + ctx, logger := opts.WithContext(ctx) + target, err := opts.NewTarget(opts.Common, logger) if err != nil { return err } diff --git a/internal/credential/store_test.go b/internal/credential/store_test.go index c7f94569b..3a873ab14 100644 --- a/internal/credential/store_test.go +++ b/internal/credential/store_test.go @@ -26,10 +26,12 @@ import ( func TestNewStoreError(t *testing.T) { tmpDir := t.TempDir() filename := path.Join(tmpDir, "testfile.txt") - _, err := os.Create(filename) + file, err := os.Create(filename) if err != nil { t.Errorf("error: cannot create file : %v", err) } + defer file.Close() + err = os.Chmod(filename, 000) if err != nil { t.Errorf("error: cannot change file permissions: %v", err)