diff --git a/cmd/oras/root/blob/delete.go b/cmd/oras/root/blob/delete.go index 8fdd1bfe5..b5e08bebb 100644 --- a/cmd/oras/root/blob/delete.go +++ b/cmd/oras/root/blob/delete.go @@ -82,13 +82,14 @@ func deleteBlob(cmd *cobra.Command, opts *deleteBlobOptions) (err error) { } // add both pull and delete scope hints for dst repository to save potential delete-scope token requests during deleting + outWriter := cmd.OutOrStdout() ctx = registryutil.WithScopeHint(ctx, blobs, auth.ActionPull, auth.ActionDelete) desc, err := blobs.Resolve(ctx, opts.Reference) if err != nil { if errors.Is(err, errdef.ErrNotFound) { if opts.Force && !opts.OutputDescriptor { // ignore nonexistent - fmt.Println("Missing", opts.RawReference) + fmt.Fprintln(outWriter, "Missing", opts.RawReference) return nil } return fmt.Errorf("%s: the specified blob does not exist", opts.RawReference) @@ -117,7 +118,7 @@ func deleteBlob(cmd *cobra.Command, opts *deleteBlobOptions) (err error) { return opts.Output(os.Stdout, descJSON) } - fmt.Println("Deleted", opts.AnnotatedReference()) + fmt.Fprintln(outWriter, "Deleted", opts.AnnotatedReference()) return nil } diff --git a/cmd/oras/root/blob/push.go b/cmd/oras/root/blob/push.go index 7f5536e36..c82ed1e5f 100644 --- a/cmd/oras/root/blob/push.go +++ b/cmd/oras/root/blob/push.go @@ -137,8 +137,9 @@ func pushBlob(cmd *cobra.Command, opts *pushBlobOptions) (err error) { return opts.Output(os.Stdout, descJSON) } - fmt.Println("Pushed", opts.AnnotatedReference()) - fmt.Println("Digest:", desc.Digest) + outWriter := cmd.OutOrStdout() + fmt.Fprintln(outWriter, "Pushed", opts.AnnotatedReference()) + fmt.Fprintln(outWriter, "Digest:", desc.Digest) return nil } diff --git a/cmd/oras/root/cp.go b/cmd/oras/root/cp.go index a08ce4695..a5f76b48c 100644 --- a/cmd/oras/root/cp.go +++ b/cmd/oras/root/cp.go @@ -134,7 +134,8 @@ func runCopy(cmd *cobra.Command, opts *copyOptions) error { // correct source digest opts.From.RawReference = fmt.Sprintf("%s@%s", opts.From.Path, desc.Digest.String()) } - fmt.Println("Copied", opts.From.AnnotatedReference(), "=>", opts.To.AnnotatedReference()) + outWriter := cmd.OutOrStdout() + fmt.Fprintln(outWriter, "Copied", opts.From.AnnotatedReference(), "=>", opts.To.AnnotatedReference()) if len(opts.extraRefs) != 0 { tagNOpts := oras.DefaultTagNOptions @@ -144,7 +145,7 @@ func runCopy(cmd *cobra.Command, opts *copyOptions) error { } } - fmt.Println("Digest:", desc.Digest) + fmt.Fprintln(outWriter, "Digest:", desc.Digest) return nil } diff --git a/cmd/oras/root/discover.go b/cmd/oras/root/discover.go index d21b8fb7e..a9d71d3f8 100644 --- a/cmd/oras/root/discover.go +++ b/cmd/oras/root/discover.go @@ -19,6 +19,7 @@ import ( "context" "encoding/json" "fmt" + "io" "os" "strings" @@ -127,15 +128,16 @@ func runDiscover(cmd *cobra.Command, opts *discoverOptions) error { return printDiscoveredReferrersJSON(desc, refs) } + outWriter := cmd.OutOrStdout() if n := len(refs); n > 1 { - fmt.Println("Discovered", n, "artifacts referencing", opts.Reference) + fmt.Fprintln(outWriter, "Discovered", n, "artifacts referencing", opts.Reference) } else { - fmt.Println("Discovered", n, "artifact referencing", opts.Reference) + fmt.Fprintln(outWriter, "Discovered", n, "artifact referencing", opts.Reference) } - fmt.Println("Digest:", desc.Digest) + fmt.Fprintln(outWriter, "Digest:", desc.Digest) if len(refs) > 0 { - fmt.Println() - _ = printDiscoveredReferrersTable(refs, opts.Verbose) + fmt.Fprintln(outWriter) + _ = printDiscoveredReferrersTable(outWriter, refs, opts.Verbose) } return nil } @@ -173,7 +175,7 @@ func fetchAllReferrers(ctx context.Context, repo oras.ReadOnlyGraphTarget, desc return nil } -func printDiscoveredReferrersTable(refs []ocispec.Descriptor, verbose bool) error { +func printDiscoveredReferrersTable(outWriter io.Writer, refs []ocispec.Descriptor, verbose bool) error { typeNameTitle := "Artifact Type" typeNameLength := len(typeNameTitle) for _, ref := range refs { @@ -183,7 +185,7 @@ func printDiscoveredReferrersTable(refs []ocispec.Descriptor, verbose bool) erro } print := func(key string, value interface{}) { - fmt.Println(key, strings.Repeat(" ", typeNameLength-len(key)+1), value) + fmt.Fprintln(outWriter, key, strings.Repeat(" ", typeNameLength-len(key)+1), value) } print(typeNameTitle, "Digest") diff --git a/cmd/oras/root/login.go b/cmd/oras/root/login.go index cb871e530..bbf551f07 100644 --- a/cmd/oras/root/login.go +++ b/cmd/oras/root/login.go @@ -18,6 +18,7 @@ package root import ( "errors" "fmt" + "io" "os" "strings" @@ -28,7 +29,7 @@ import ( 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" + orasio "oras.land/oras/internal/io" ) type loginOptions struct { @@ -77,12 +78,13 @@ Example - Log in with username and password in an interactive terminal and no TL func runLogin(cmd *cobra.Command, opts loginOptions) (err error) { ctx, logger := opts.WithContext(cmd.Context()) + outWriter := cmd.OutOrStdout() // prompt for credential if opts.Password == "" { if opts.Username == "" { // prompt for username - username, err := readLine("Username: ", false) + username, err := readLine(outWriter, "Username: ", false) if err != nil { return err } @@ -90,14 +92,14 @@ func runLogin(cmd *cobra.Command, opts loginOptions) (err error) { } if opts.Username == "" { // prompt for token - if opts.Password, err = readLine("Token: ", true); err != nil { + if opts.Password, err = readLine(outWriter, "Token: ", true); err != nil { return err } else if opts.Password == "" { return errors.New("token required") } } else { // prompt for password - if opts.Password, err = readLine("Password: ", true); err != nil { + if opts.Password, err = readLine(outWriter, "Password: ", true); err != nil { return err } else if opts.Password == "" { return errors.New("password required") @@ -116,21 +118,21 @@ func runLogin(cmd *cobra.Command, opts loginOptions) (err error) { if err = credentials.Login(ctx, store, remote, opts.Credential()); err != nil { return err } - fmt.Println("Login Succeeded") + fmt.Fprintln(outWriter, "Login Succeeded") return nil } -func readLine(prompt string, silent bool) (string, error) { - fmt.Print(prompt) +func readLine(outWriter io.Writer, prompt string, silent bool) (string, error) { + fmt.Fprint(outWriter, prompt) fd := int(os.Stdin.Fd()) var bytes []byte var err error if silent && term.IsTerminal(fd) { if bytes, err = term.ReadPassword(fd); err == nil { - _, err = fmt.Println() + _, err = fmt.Fprintln(outWriter) } } else { - bytes, err = io.ReadLine(os.Stdin) + bytes, err = orasio.ReadLine(os.Stdin) } if err != nil { return "", err diff --git a/cmd/oras/root/manifest/delete.go b/cmd/oras/root/manifest/delete.go index fcb4e1f4b..e2b0833f4 100644 --- a/cmd/oras/root/manifest/delete.go +++ b/cmd/oras/root/manifest/delete.go @@ -91,13 +91,14 @@ func deleteManifest(cmd *cobra.Command, opts *deleteOptions) error { // possibly needed when adding a new referrers index hints = append(hints, auth.ActionPush) } + outWriter := cmd.OutOrStdout() ctx = registryutil.WithScopeHint(ctx, manifests, hints...) desc, err := manifests.Resolve(ctx, opts.Reference) if err != nil { if errors.Is(err, errdef.ErrNotFound) { if opts.Force && !opts.OutputDescriptor { // ignore nonexistent - fmt.Println("Missing", opts.RawReference) + fmt.Fprintln(outWriter, "Missing", opts.RawReference) return nil } return fmt.Errorf("%s: the specified manifest does not exist", opts.RawReference) @@ -126,7 +127,7 @@ func deleteManifest(cmd *cobra.Command, opts *deleteOptions) error { return opts.Output(os.Stdout, descJSON) } - fmt.Println("Deleted", opts.AnnotatedReference()) + fmt.Fprintln(outWriter, "Deleted", opts.AnnotatedReference()) return nil } diff --git a/cmd/oras/root/manifest/push.go b/cmd/oras/root/manifest/push.go index 8afee2b0b..5d30cdbda 100644 --- a/cmd/oras/root/manifest/push.go +++ b/cmd/oras/root/manifest/push.go @@ -191,7 +191,7 @@ func pushManifest(cmd *cobra.Command, opts pushOptions) error { } } - fmt.Println("Digest:", desc.Digest) + fmt.Fprintln(cmd.OutOrStdout(), "Digest:", desc.Digest) return nil } diff --git a/cmd/oras/root/pull.go b/cmd/oras/root/pull.go index da2b99d6c..6d0dc0b57 100644 --- a/cmd/oras/root/pull.go +++ b/cmd/oras/root/pull.go @@ -143,12 +143,13 @@ func runPull(cmd *cobra.Command, opts *pullOptions) error { } // suggest oras copy for pulling layers without annotation + outWriter := cmd.OutOrStdout() if layerSkipped { - fmt.Printf("Skipped pulling layers without file name in %q\n", ocispec.AnnotationTitle) - fmt.Printf("Use 'oras copy %s --to-oci-layout ' to pull all layers.\n", opts.RawReference) + fmt.Fprintf(outWriter, "Skipped pulling layers without file name in %q\n", ocispec.AnnotationTitle) + fmt.Fprintf(outWriter, "Use 'oras copy %s --to-oci-layout ' to pull all layers.\n", opts.RawReference) } else { - fmt.Println("Pulled", opts.AnnotatedReference()) - fmt.Println("Digest:", desc.Digest) + fmt.Fprintln(outWriter, "Pulled", opts.AnnotatedReference()) + fmt.Fprintln(outWriter, "Digest:", desc.Digest) } return nil } diff --git a/cmd/oras/root/repo/ls.go b/cmd/oras/root/repo/ls.go index dbe77e82e..aba613ed8 100644 --- a/cmd/oras/root/repo/ls.go +++ b/cmd/oras/root/repo/ls.go @@ -77,9 +77,10 @@ func listRepository(cmd *cobra.Command, opts *repositoryOptions) error { return err } err = reg.Repositories(ctx, opts.last, func(repos []string) error { + outWriter := cmd.OutOrStdout() for _, repo := range repos { if subRepo, found := strings.CutPrefix(repo, opts.namespace); found { - fmt.Println(subRepo) + fmt.Fprintln(outWriter, subRepo) } } return nil diff --git a/cmd/oras/root/repo/tags.go b/cmd/oras/root/repo/tags.go index d174f30f4..311ef6e71 100644 --- a/cmd/oras/root/repo/tags.go +++ b/cmd/oras/root/repo/tags.go @@ -98,6 +98,7 @@ func showTags(cmd *cobra.Command, opts *showTagsOptions) error { } logger.Warnf("[Experimental] querying tags associated to %s, it may take a while...\n", filter) } + outWriter := cmd.OutOrStdout() return finder.Tags(ctx, opts.last, func(tags []string) error { for _, tag := range tags { if opts.excludeDigestTag && isDigestTag(tag) { @@ -105,7 +106,7 @@ func showTags(cmd *cobra.Command, opts *showTagsOptions) error { } if filter != "" { if tag == opts.Reference { - fmt.Println(tag) + fmt.Fprintln(outWriter, tag) continue } desc, err := finder.Resolve(ctx, tag) @@ -116,7 +117,7 @@ func showTags(cmd *cobra.Command, opts *showTagsOptions) error { continue } } - fmt.Println(tag) + fmt.Fprintln(outWriter, tag) } return nil }) diff --git a/cmd/oras/root/resolve.go b/cmd/oras/root/resolve.go index 433cf097b..966725fad 100644 --- a/cmd/oras/root/resolve.go +++ b/cmd/oras/root/resolve.go @@ -77,10 +77,11 @@ func runResolve(cmd *cobra.Command, opts *resolveOptions) error { return fmt.Errorf("failed to resolve digest: %w", err) } + outWriter := cmd.OutOrStdout() if opts.fullRef { - fmt.Printf("%s@%s\n", opts.Path, desc.Digest) + fmt.Fprintf(outWriter, "%s@%s\n", opts.Path, desc.Digest) } else { - fmt.Println(desc.Digest.String()) + fmt.Fprintln(outWriter, desc.Digest.String()) } return nil diff --git a/cmd/oras/root/version.go b/cmd/oras/root/version.go index fae9d28bf..a071744fc 100644 --- a/cmd/oras/root/version.go +++ b/cmd/oras/root/version.go @@ -17,6 +17,7 @@ package root import ( "fmt" + "io" "os" "runtime" "strings" @@ -45,14 +46,15 @@ Example - print version: return nil }, RunE: func(cmd *cobra.Command, args []string) error { - return runVersion() + outWriter := cmd.OutOrStdout() + return runVersion(outWriter) }, } return cmd } -func runVersion() error { +func runVersion(outWriter io.Writer) error { items := [][]string{ {"Version", version.GetVersion()}, {"Go version", runtime.Version()}, @@ -71,7 +73,7 @@ func runVersion() error { } } for _, item := range items { - fmt.Println(item[0] + ": " + strings.Repeat(" ", size-len(item[0])) + item[1]) + fmt.Fprintln(outWriter, item[0]+": "+strings.Repeat(" ", size-len(item[0]))+item[1]) } return nil