From 6447e8e60c4b9ee55b5afdcf8098934a5549bf4f Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Tue, 17 Jan 2023 12:01:53 +0800 Subject: [PATCH] feat(oci-layout): support in `oras cp` (#748) Related to #378 Signed-off-by: Billy Zha --- cmd/oras/cp.go | 71 +++---- cmd/oras/internal/display/print.go | 33 +++- cmd/oras/internal/errors/errors.go | 5 + cmd/oras/internal/fileref/unix.go | 8 +- cmd/oras/internal/fileref/windows.go | 14 +- cmd/oras/internal/fileref/windows_test.go | 3 +- cmd/oras/internal/option/target.go | 187 ++++++++++++++++++ cmd/oras/internal/option/target_test.go | 77 ++++++++ .../internal/option/target_windows_test.go | 52 +++++ cmd/oras/manifest/push.go | 2 +- cmd/oras/push.go | 2 +- cmd/oras/tag/tag.go | 3 +- go.mod | 2 +- go.sum | 4 +- 14 files changed, 406 insertions(+), 57 deletions(-) create mode 100644 cmd/oras/internal/option/target.go create mode 100644 cmd/oras/internal/option/target_test.go create mode 100644 cmd/oras/internal/option/target_windows_test.go diff --git a/cmd/oras/cp.go b/cmd/oras/cp.go index 7fc485d02..1a81fdcd2 100644 --- a/cmd/oras/cp.go +++ b/cmd/oras/cp.go @@ -30,15 +30,12 @@ import ( ) type copyOptions struct { - src option.Remote - dst option.Remote option.Common option.Platform - recursive bool + option.BinaryTarget + recursive bool concurrency int - srcRef string - dstRef string extraRefs []string } @@ -52,40 +49,45 @@ func copyCmd() *cobra.Command { ** This command is in preview and under development. ** -Example - Copy the artifact tagged with 'v1' from repository 'localhost:5000/net-monitor' to repository 'localhost:5000/net-monitor-copy' - oras cp localhost:5000/net-monitor:v1 localhost:5000/net-monitor-copy:v1 +Example - Copy an artifact between registries: + oras cp localhost:5000/net-monitor:v1 localhost:6000/net-monitor-copy:v1 -Example - Copy the artifact tagged with 'v1' and its referrers from repository 'localhost:5000/net-monitor' to 'localhost:5000/net-monitor-copy' - oras cp -r localhost:5000/net-monitor:v1 localhost:5000/net-monitor-copy:v1 +Example - Download an artifact into an OCI layout folder: + oras cp --to-oci-layout localhost:5000/net-monitor:v1 ./downloaded:v1 -Example - Copy the artifact tagged with 'v1' from repository 'localhost:5000/net-monitor' to 'localhost:5000/net-monitor-copy' with certain platform - oras cp --platform linux/arm/v5 localhost:5000/net-monitor:v1 localhost:5000/net-monitor-copy:v1 +Example - Upload an artifact from an OCI layout folder: + oras cp --from-oci-layout ./to-upload:v1 localhost:5000/net-monitor:v1 -Example - Copy the artifact tagged with 'v1' from repository 'localhost:5000/net-monitor' to 'localhost:5000/net-monitor-copy' with multiple tags - oras cp localhost:5000/net-monitor:v1 localhost:5000/net-monitor-copy:v1,tag2,tag3 +Example - Upload an artifact from an OCI layout tar archive: + oras cp --from-oci-layout ./to-upload.tar:v1 localhost:5000/net-monitor:v1 -Example - Copy the artifact tagged with 'v1' from repository 'localhost:5000/net-monitor' to 'localhost:5000/net-monitor-copy' with multiple tags and concurrency level tuned - oras cp --concurrency 6 localhost:5000/net-monitor:v1 localhost:5000/net-monitor-copy:v1,tag2,tag3 +Example - Copy an artifact and its referrers: + oras cp -r localhost:5000/net-monitor:v1 localhost:6000/net-monitor-copy:v1 + +Example - Copy certain platform of an artifact: + oras cp --platform linux/arm/v5 localhost:5000/net-monitor:v1 localhost:6000/net-monitor-copy:v1 + +Example - Copy an artifact with multiple tags: + oras cp localhost:5000/net-monitor:v1 localhost:6000/net-monitor-copy:tag1,tag2,tag3 + +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), PreRunE: func(cmd *cobra.Command, args []string) error { + opts.From.RawReference = args[0] + refs := strings.Split(args[1], ",") + opts.To.RawReference = refs[0] + opts.extraRefs = refs[1:] return option.Parse(&opts) }, RunE: func(cmd *cobra.Command, args []string) error { - opts.srcRef = args[0] - refs := strings.Split(args[1], ",") - opts.dstRef = refs[0] - opts.extraRefs = refs[1:] return runCopy(opts) }, } - cmd.Flags().BoolVarP(&opts.recursive, "recursive", "r", false, "recursively copy the artifact and its referrer artifacts") - opts.src.ApplyFlagsWithPrefix(cmd.Flags(), "from", "source") - opts.dst.ApplyFlagsWithPrefix(cmd.Flags(), "to", "destination") cmd.Flags().IntVarP(&opts.concurrency, "concurrency", "", 3, "concurrency level") option.ApplyFlags(&opts, cmd.Flags()) - return cmd } @@ -93,13 +95,16 @@ func runCopy(opts copyOptions) error { ctx, _ := opts.SetLoggerLevel() // Prepare source - src, err := opts.src.NewRepository(opts.srcRef, opts.Common) + src, err := opts.From.NewReadonlyTarget(ctx, opts.Common) if err != nil { return err } + if opts.From.Reference == "" { + return errors.NewErrInvalidReferenceStr(opts.From.RawReference) + } // Prepare destination - dst, err := opts.dst.NewRepository(opts.dstRef, opts.Common) + dst, err := opts.To.NewTarget(opts.Common) if err != nil { return err } @@ -121,14 +126,10 @@ func runCopy(opts copyOptions) error { return display.PrintStatus(desc, "Exists ", opts.Verbose) } - if src.Reference.Reference == "" { - return errors.NewErrInvalidReference(src.Reference) - } - var desc ocispec.Descriptor - if ref := dst.Reference.Reference; ref == "" { + if ref := opts.To.Reference; ref == "" { // push to the destination with digest only if no tag specified - desc, err = src.Resolve(ctx, src.Reference.Reference) + desc, err = src.Resolve(ctx, opts.From.Reference) if err != nil { return err } @@ -139,7 +140,7 @@ func runCopy(opts copyOptions) error { } } else { if opts.recursive { - desc, err = oras.ExtendedCopy(ctx, src, opts.srcRef, dst, opts.dstRef, extendedCopyOptions) + desc, err = oras.ExtendedCopy(ctx, src, opts.From.Reference, dst, opts.To.Reference, extendedCopyOptions) } else { copyOptions := oras.CopyOptions{ CopyGraphOptions: extendedCopyOptions.CopyGraphOptions, @@ -147,19 +148,19 @@ func runCopy(opts copyOptions) error { if opts.Platform.Platform != nil { copyOptions.WithTargetPlatform(opts.Platform.Platform) } - desc, err = oras.Copy(ctx, src, opts.srcRef, dst, opts.dstRef, copyOptions) + desc, err = oras.Copy(ctx, src, opts.From.Reference, dst, opts.To.Reference, copyOptions) } } if err != nil { return err } - fmt.Println("Copied", opts.srcRef, "=>", opts.dstRef) + fmt.Println("Copied", opts.From.AnnotatedReference(), "=>", opts.To.AnnotatedReference()) if len(opts.extraRefs) != 0 { tagNOpts := oras.DefaultTagNOptions tagNOpts.Concurrency = opts.concurrency - if err = oras.TagN(ctx, &display.TagManifestStatusPrinter{Repository: dst}, opts.dstRef, opts.extraRefs, tagNOpts); err != nil { + if _, err = oras.TagN(ctx, display.NewTagManifestStatusPrinter(dst), opts.To.Reference, opts.extraRefs, tagNOpts); err != nil { return err } } diff --git a/cmd/oras/internal/display/print.go b/cmd/oras/internal/display/print.go index 94d5953b3..675f4915e 100644 --- a/cmd/oras/internal/display/print.go +++ b/cmd/oras/internal/display/print.go @@ -22,8 +22,9 @@ import ( "sync" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" - "oras.land/oras-go/v2/registry/remote" + "oras.land/oras-go/v2/registry" ) var printLock sync.Mutex @@ -74,14 +75,38 @@ func PrintSuccessorStatus(ctx context.Context, desc ocispec.Descriptor, status s return nil } -type TagManifestStatusPrinter struct { - *remote.Repository +// NewTagManifestStatusPrinter creates a wrapper type for printing tag status. +func NewTagManifestStatusPrinter(target oras.Target) oras.Target { + if repo, ok := target.(registry.Repository); ok { + return &tagManifestStatusForRepo{ + Repository: repo, + } + } + return &tagManifestStatusForTarget{ + Target: target, + } +} + +type tagManifestStatusForRepo struct { + registry.Repository } // PushReference overrides Repository.PushReference method to print off which tag(s) were added successfully. -func (p *TagManifestStatusPrinter) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error { +func (p *tagManifestStatusForRepo) PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error { if err := p.Repository.PushReference(ctx, expected, content, reference); err != nil { return err } return Print("Tagged", reference) } + +type tagManifestStatusForTarget struct { + oras.Target +} + +// Tag tags a descriptor with a reference string. +func (p *tagManifestStatusForTarget) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error { + if err := p.Target.Tag(ctx, desc, reference); err != nil { + return err + } + return Print("Tagged", reference) +} diff --git a/cmd/oras/internal/errors/errors.go b/cmd/oras/internal/errors/errors.go index 2f211d150..e3c6e4c43 100644 --- a/cmd/oras/internal/errors/errors.go +++ b/cmd/oras/internal/errors/errors.go @@ -23,5 +23,10 @@ import ( // NewErrInvalidReference creates a new error based on the reference string. func NewErrInvalidReference(ref registry.Reference) error { + return NewErrInvalidReferenceStr(ref.String()) +} + +// NewErrInvalidReferenceStr creates a new error based on the reference string. +func NewErrInvalidReferenceStr(ref string) error { return fmt.Errorf("%s: invalid image reference, expecting ", ref) } diff --git a/cmd/oras/internal/fileref/unix.go b/cmd/oras/internal/fileref/unix.go index 82494f0e9..66d6b7934 100644 --- a/cmd/oras/internal/fileref/unix.go +++ b/cmd/oras/internal/fileref/unix.go @@ -23,15 +23,15 @@ import ( ) // Parse parses file reference on unix. -func Parse(reference string, defaultMediaType string) (filePath, mediaType string, err error) { +func Parse(reference string, defaultMetadata string) (filePath, metadata string, err error) { i := strings.LastIndex(reference, ":") if i < 0 { - filePath, mediaType = reference, defaultMediaType + filePath, metadata = reference, defaultMetadata } else { - filePath, mediaType = reference[:i], reference[i+1:] + filePath, metadata = reference[:i], reference[i+1:] } if filePath == "" { return "", "", fmt.Errorf("found empty file path in %q", reference) } - return filePath, mediaType, nil + return filePath, metadata, nil } diff --git a/cmd/oras/internal/fileref/windows.go b/cmd/oras/internal/fileref/windows.go index 9ea05976f..19609cdd1 100644 --- a/cmd/oras/internal/fileref/windows.go +++ b/cmd/oras/internal/fileref/windows.go @@ -23,24 +23,24 @@ import ( "unicode" ) -// Parse parses file reference on windows. -func Parse(reference string, defaultMediaType string) (filePath, mediaType string, err error) { - filePath, mediaType = doParse(reference, defaultMediaType) +// Parse parses file reference into filePath and metadata. +func Parse(reference string, defaultMetadata string) (filePath, metadata string, err error) { + filePath, metadata = doParse(reference, defaultMetadata) if filePath == "" { return "", "", fmt.Errorf("found empty file path in %q", reference) } - if strings.ContainsAny(filePath, `<>:"|?*`) { + if strings.ContainsAny(filePath, `<>"|?*`) { // Reference: https://learn.microsoft.com/windows/win32/fileio/naming-a-file#naming-conventions return "", "", fmt.Errorf("reserved characters found in the file path: %s", filePath) } - return filePath, mediaType, nil + return filePath, metadata, nil } -func doParse(reference string, mediaType string) (filePath, mediatype string) { +func doParse(reference string, defaultMetadata string) (filePath, metadata string) { i := strings.LastIndex(reference, ":") if i < 0 || (i == 1 && len(reference) > 2 && unicode.IsLetter(rune(reference[0])) && reference[2] == '\\') { // Relative file path with disk prefix is NOT supported, e.g. `c:file1` - return reference, mediaType + return reference, defaultMetadata } return reference[:i], reference[i+1:] } diff --git a/cmd/oras/internal/fileref/windows_test.go b/cmd/oras/internal/fileref/windows_test.go index 3cd60b220..c82840491 100644 --- a/cmd/oras/internal/fileref/windows_test.go +++ b/cmd/oras/internal/fileref/windows_test.go @@ -82,6 +82,8 @@ func TestParse(t *testing.T) { wantMediatype string wantErr bool }{ + {"valid file name", args{`c:\some-folder\test`, ""}, `c:\some-folder\test`, "", false}, + {"valid file name and media type", args{`c:\some-folder\test`, "type"}, `c:\some-folder\test`, "type", false}, {"no input", args{"", ""}, "", "", true}, {"empty file name", args{":", ""}, "", "", true}, {"reserved character1 in file name", args{"<", "a"}, "", "", true}, @@ -97,7 +99,6 @@ func TestParse(t *testing.T) { {"reserved character4 in file name, with media type", args{`":`, "a"}, "", "", true}, {"reserved character5 in file name, with media type", args{"|:", "a"}, "", "", true}, {"reserved character6 in file name, with media type", args{"?:", "a"}, "", "", true}, - {"reserved character7 in file name, with media type", args{"::", "a"}, "", "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/oras/internal/option/target.go b/cmd/oras/internal/option/target.go new file mode 100644 index 000000000..0f5829e2d --- /dev/null +++ b/cmd/oras/internal/option/target.go @@ -0,0 +1,187 @@ +/* +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 option + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/spf13/pflag" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content/oci" + "oras.land/oras-go/v2/registry" + "oras.land/oras/cmd/oras/internal/fileref" +) + +const ( + TargetTypeRemote = "registry" + TargetTypeOCILayout = "oci-layout" +) + +// Target struct contains flags and arguments specifying one registry or image +// layout. +type Target struct { + Remote + RawReference string + Type string + Reference string //contains tag or digest + + isOCILayout bool +} + +// ApplyFlags applies flags to a command flag set for unary target +func (opts *Target) ApplyFlags(fs *pflag.FlagSet) { + opts.applyFlagsWithPrefix(fs, "", "") + opts.Remote.ApplyFlags(fs) +} + +// AnnotatedReference returns full printable reference. +func (opts *Target) AnnotatedReference() string { + return fmt.Sprintf("[%s] %s", opts.Type, opts.RawReference) +} + +// applyFlagsWithPrefix applies flags to fs with prefix and description. +// The complete form of the `target` flag is designed to be +// +// --target type=[[,=][...]] +// +// For better UX, the boolean flag `--oci-layout` is introduced as an alias of +// `--target type=oci-layout`. +// Since there is only one target type besides the default `registry` type, +// the full form is not implemented until a new type comes in. +func (opts *Target) applyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description string) { + var ( + flagPrefix string + noteSuffix string + ) + if prefix != "" { + flagPrefix = prefix + "-" + noteSuffix = description + " " + } + fs.BoolVarP(&opts.isOCILayout, flagPrefix+"oci-layout", "", false, "Set "+noteSuffix+"target as an OCI image layout.") +} + +// ApplyFlagsWithPrefix applies flags to a command flag set with a prefix string. +// Commonly used for non-unary remote targets. +func (opts *Target) ApplyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description string) { + opts.applyFlagsWithPrefix(fs, prefix, description) + opts.Remote.ApplyFlagsWithPrefix(fs, prefix, description) +} + +// Parse gets target options from user input. +func (opts *Target) Parse() error { + switch { + case opts.isOCILayout: + opts.Type = TargetTypeOCILayout + default: + opts.Type = TargetTypeRemote + } + return nil +} + +// parseOCILayoutReference parses the raw in format of [:|@] +func parseOCILayoutReference(raw string) (path string, ref string, err error) { + if idx := strings.LastIndex(raw, "@"); idx != -1 { + // `digest` found + path = raw[:idx] + ref = raw[idx+1:] + } else { + // find `tag` + path, ref, err = fileref.Parse(raw, "") + } + return +} + +// NewTarget generates a new target based on opts. +func (opts *Target) NewTarget(common Common) (oras.GraphTarget, error) { + switch opts.Type { + case TargetTypeOCILayout: + var path string + var err error + path, opts.Reference, err = parseOCILayoutReference(opts.RawReference) + if err != nil { + return nil, err + } + return oci.New(path) + case TargetTypeRemote: + repo, err := opts.NewRepository(opts.RawReference, common) + if err != nil { + return nil, err + } + opts.Reference = repo.Reference.Reference + return repo, nil + } + return nil, fmt.Errorf("unknown target type: %q", opts.Type) +} + +// ReadOnlyGraphTagFinderTarget represents a read-only graph target with tag +// finder capability. +type ReadOnlyGraphTagFinderTarget interface { + oras.ReadOnlyGraphTarget + registry.TagLister +} + +// NewReadonlyTargets generates a new read only target based on opts. +func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common) (ReadOnlyGraphTagFinderTarget, error) { + switch opts.Type { + case TargetTypeOCILayout: + var path string + var err error + path, opts.Reference, err = parseOCILayoutReference(opts.RawReference) + if err != nil { + return nil, err + } + info, err := os.Stat(path) + if err != nil { + return nil, err + } + if info.IsDir() { + return oci.NewFromFS(ctx, os.DirFS(path)) + } + return oci.NewFromTar(ctx, path) + case TargetTypeRemote: + repo, err := opts.NewRepository(opts.RawReference, common) + if err != nil { + return nil, err + } + opts.Reference = repo.Reference.Reference + return repo, nil + } + return nil, fmt.Errorf("unknown target type: %q", opts.Type) +} + +// BinaryTarget struct contains flags and arguments specifying two registries or +// image layouts. +type BinaryTarget struct { + From Target + To Target +} + +// ApplyFlags applies flags to a command flag set fs. +func (opts *BinaryTarget) ApplyFlags(fs *pflag.FlagSet) { + opts.From.ApplyFlagsWithPrefix(fs, "from", "source") + opts.To.ApplyFlagsWithPrefix(fs, "to", "destination") +} + +// Parse parses user-provided flags and arguments into option struct. +func (opts *BinaryTarget) Parse() error { + if err := opts.From.Parse(); err != nil { + return err + } + return opts.To.Parse() +} diff --git a/cmd/oras/internal/option/target_test.go b/cmd/oras/internal/option/target_test.go new file mode 100644 index 000000000..d01eeadde --- /dev/null +++ b/cmd/oras/internal/option/target_test.go @@ -0,0 +1,77 @@ +/* +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 option + +import ( + "testing" +) + +func TestTarget_Parse_oci(t *testing.T) { + opts := Target{isOCILayout: true} + + if err := opts.Parse(); err != nil { + t.Errorf("Target.Parse() error = %v", err) + } + if opts.Type != TargetTypeOCILayout { + t.Errorf("Target.Parse() failed, got %q, want %q", opts.Type, TargetTypeOCILayout) + } +} + +func TestTarget_Parse_remote(t *testing.T) { + opts := Target{isOCILayout: false} + if err := opts.Parse(); err != nil { + t.Errorf("Target.Parse() error = %v", err) + } + if opts.Type != TargetTypeRemote { + t.Errorf("Target.Parse() failed, got %q, want %q", opts.Type, TargetTypeRemote) + } +} + +func Test_parseOCILayoutReference(t *testing.T) { + type args struct { + raw string + } + tests := []struct { + name string + args args + want string + want1 string + wantErr bool + }{ + {"Empty input", args{raw: ""}, "", "", true}, + {"Empty path and tag", args{raw: ":"}, "", "", true}, + {"Empty path and digest", args{raw: "@"}, "", "", false}, + {"Empty digest", args{raw: "path@"}, "path", "", false}, + {"Empty tag", args{raw: "path:"}, "path", "", false}, + {"path and digest", args{raw: "path@digest"}, "path", "digest", false}, + {"path and tag", args{raw: "path:tag"}, "path", "tag", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := parseOCILayoutReference(tt.args.raw) + if (err != nil) != tt.wantErr { + t.Errorf("parseOCILayoutReference() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("parseOCILayoutReference() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("parseOCILayoutReference() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/cmd/oras/internal/option/target_windows_test.go b/cmd/oras/internal/option/target_windows_test.go new file mode 100644 index 000000000..8066408ca --- /dev/null +++ b/cmd/oras/internal/option/target_windows_test.go @@ -0,0 +1,52 @@ +//go:build windows + +/* +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 option + +import ( + "testing" +) + +func Test_parseOCILayoutReference_windows(t *testing.T) { + type args struct { + raw string + } + tests := []struct { + name string + args args + want string + want1 string + wantErr bool + }{ + {"path and tag", args{raw: `C:\some-folder:tag`}, `C:\some-folder`, "tag", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := parseOCILayoutReference(tt.args.raw) + if (err != nil) != tt.wantErr { + t.Errorf("parseOCILayoutReference() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("parseOCILayoutReference() got = %v, want %v", got, tt.want) + } + if got1 != tt.want1 { + t.Errorf("parseOCILayoutReference() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} diff --git a/cmd/oras/manifest/push.go b/cmd/oras/manifest/push.go index 0c172ade5..f9b17e3ba 100644 --- a/cmd/oras/manifest/push.go +++ b/cmd/oras/manifest/push.go @@ -168,7 +168,7 @@ func pushManifest(opts pushOptions) error { } display.Print("Pushed", opts.targetRef) if len(opts.extraRefs) != 0 { - if _, err = oras.TagBytesN(ctx, &display.TagManifestStatusPrinter{Repository: repo}, mediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { + if _, err = oras.TagBytesN(ctx, display.NewTagManifestStatusPrinter(repo), mediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { return err } } diff --git a/cmd/oras/push.go b/cmd/oras/push.go index c4f64b4f9..3de067198 100644 --- a/cmd/oras/push.go +++ b/cmd/oras/push.go @@ -199,7 +199,7 @@ func runPush(opts pushOptions) error { } tagBytesNOpts := oras.DefaultTagBytesNOptions tagBytesNOpts.Concurrency = opts.concurrency - if _, err = oras.TagBytesN(ctx, &display.TagManifestStatusPrinter{Repository: dst}, root.MediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { + if _, err = oras.TagBytesN(ctx, display.NewTagManifestStatusPrinter(dst), root.MediaType, contentBytes, opts.extraRefs, tagBytesNOpts); err != nil { return err } } diff --git a/cmd/oras/tag/tag.go b/cmd/oras/tag/tag.go index bcad5f796..d5ad5db58 100644 --- a/cmd/oras/tag/tag.go +++ b/cmd/oras/tag/tag.go @@ -82,5 +82,6 @@ func tagManifest(opts tagOptions) error { tagNOpts := oras.DefaultTagNOptions tagNOpts.Concurrency = opts.concurrency - return oras.TagN(ctx, &display.TagManifestStatusPrinter{Repository: repo}, opts.srcRef, opts.targetRefs, tagNOpts) + _, err = oras.TagN(ctx, display.NewTagManifestStatusPrinter(repo), opts.srcRef, opts.targetRefs, tagNOpts) + return err } diff --git a/go.mod b/go.mod index 72f96c1b7..1e022afc5 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 gopkg.in/yaml.v3 v3.0.1 - oras.land/oras-go/v2 v2.0.0-rc.6 + oras.land/oras-go/v2 v2.0.0-20230112153040-29509026fb7f ) require ( diff --git a/go.sum b/go.sum index df3f86184..68d3bd053 100644 --- a/go.sum +++ b/go.sum @@ -64,5 +64,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -oras.land/oras-go/v2 v2.0.0-rc.6 h1:jGWysqm8flq+X0Vj8bZ6rkASAqTab5k18Mx9hEjFc8g= -oras.land/oras-go/v2 v2.0.0-rc.6/go.mod h1:iVExH1NxrccIxjsiq17L91WCZ4KIw6jVQyCLsZsu1gc= +oras.land/oras-go/v2 v2.0.0-20230112153040-29509026fb7f h1:A28WmSZajK0Mo48Ts6hjF0Tu6vwvK8Tw9CeFdCaQU+s= +oras.land/oras-go/v2 v2.0.0-20230112153040-29509026fb7f/go.mod h1:iVExH1NxrccIxjsiq17L91WCZ4KIw6jVQyCLsZsu1gc=