Skip to content

Commit

Permalink
feat: support JSON output for attach and push
Browse files Browse the repository at this point in the history
Signed-off-by: Billy Zha <[email protected]>
  • Loading branch information
qweeah committed Nov 10, 2023
1 parent 599c78e commit 2a2ca53
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 41 deletions.
53 changes: 53 additions & 0 deletions cmd/oras/internal/option/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
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 (
"encoding/json"
"fmt"

"github.com/spf13/pflag"
"oras.land/oras/cmd/oras/internal/output/format"
)

// Format option struct.
type Format struct {
format.Flag
}

func (opts *Format) ApplyFlags(fs *pflag.FlagSet) {
fs.Var(&opts.Flag, "format-stdout", fmt.Sprintf("[Preview] summary output to stdout in the specified format (default %q)", format.Plain))
}

// Print prints the Output as JSON.
func (opts *Format) Print(data interface{}, prettify bool) error {
var err error
switch opts.Flag {
case format.Json:
var content []byte
if prettify {
content, err = json.MarshalIndent(data, "", " ")
} else {
content, err = json.Marshal(data)
}
if err != nil {
return err
}
_, err = fmt.Println(string(content))
return err

}
return nil
}
26 changes: 24 additions & 2 deletions cmd/oras/internal/output/display/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"fmt"
"io"
"os"
"sync"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand All @@ -33,10 +34,31 @@ var printLock sync.Mutex
type PrintFunc func(ocispec.Descriptor) error

// Print objects to display concurrent-safely.
func Print(a ...any) error {
var Print func(a ...any) error

func init() {
Print = printStdout
}

// SwitchToStderr switches Print to stderr.
func SwitchToStderr() {
Print = printStderr
}

// printStdout displays objects concurrent-safely.
func printStdout(a ...any) error {
return printTo(os.Stderr, a...)
}

// printStderr displays objects concurrent-safely.
func printStderr(a ...any) error {
return printTo(os.Stderr, a...)
}

func printTo(w io.Writer, a ...any) error {
printLock.Lock()
defer printLock.Unlock()
_, err := fmt.Println(a...)
_, err := fmt.Fprintln(w, a...)
return err
}

Expand Down
58 changes: 58 additions & 0 deletions cmd/oras/internal/output/format/format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
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 format

import (
"errors"
"strings"
)

const (
Plain = "plain"
Json = "json"
)

// Printer is the interface that wraps the basic String method.
type Printer interface {
Print(prettify bool) error
}

// Define a custom value type that implements the pflag.Value interface.
type Flag string

// Set must have pointer receiver so it doesn't change the value of a copy.
func (f *Flag) Set(v string) error {
switch v {
case "":
// default
*f = Plain
case Plain, Json:
*f = Json
default:
return errors.New("invalid format flag, expecting " + f.Type())
}
return nil
}

// String is used by pflag to print the default value of a flag.
func (f *Flag) String() string {
return string(*f)
}

// Type provides optional value used in help text.
func (c *Flag) Type() string {
return strings.Join([]string{Plain, Json}, "|")
}
47 changes: 47 additions & 0 deletions cmd/oras/internal/output/format/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
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 format

import (
"encoding/json"
"os"
)

// JSON is a Output that prints the data as JSON.
type JSON struct {
Data interface{}
}

// NewJSON creates a new Output with the given data
func NewJSON(data interface{}) *JSON {
return &JSON{Data: data}
}

// Print prints the Output as JSON.
func Print(data interface{}, prettify bool) error {
var content []byte
var err error
if prettify {
content, err = json.MarshalIndent(data, "", " ")
} else {
content, err = json.Marshal(data)
}
if err != nil {
return err
}
_, err = os.Stdout.Write(content)
return err
}
31 changes: 0 additions & 31 deletions cmd/oras/internal/output/pipe/output.go

This file was deleted.

13 changes: 10 additions & 3 deletions cmd/oras/root/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"oras.land/oras-go/v2/content/file"
"oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/cmd/oras/internal/output/display"
"oras.land/oras/cmd/oras/internal/output/display/track"
"oras.land/oras/internal/graph"
"oras.land/oras/internal/registryutil"
Expand All @@ -37,6 +38,7 @@ type attachOptions struct {
option.Common
option.Packer
option.Target
option.Format

artifactType string
concurrency int
Expand Down Expand Up @@ -83,6 +85,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 {
display.SwitchToStderr()
return runAttach(cmd.Context(), opts)
},
}
Expand Down Expand Up @@ -177,9 +180,13 @@ func runAttach(ctx context.Context, opts attachOptions) error {
if !strings.HasSuffix(opts.RawReference, digest) {
opts.RawReference = fmt.Sprintf("%s@%s", opts.Path, subject.Digest)
}
fmt.Println("Attached to", opts.AnnotatedReference())
fmt.Println("Digest:", root.Digest)
display.Print("Attached to", opts.AnnotatedReference())
display.Print("Digest:", root.Digest)

// Export manifest
return opts.ExportManifest(ctx, store, root)
if err = opts.ExportManifest(ctx, store, root); err != nil {
return err
}
// TODO: schema, PRD needed
return opts.Print(root, opts.TTY != nil)
}
3 changes: 2 additions & 1 deletion cmd/oras/root/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content/file"
"oras.land/oras/cmd/oras/internal/fileref"
"oras.land/oras/cmd/oras/internal/output/display"
)

func loadFiles(ctx context.Context, store *file.Store, annotations map[string]map[string]string, fileRefs []string, verbose bool) ([]ocispec.Descriptor, error) {
Expand Down Expand Up @@ -58,7 +59,7 @@ func loadFiles(ctx context.Context, store *file.Store, annotations map[string]ma
files = append(files, file)
}
if len(files) == 0 {
fmt.Println("Uploading empty artifact")
display.Print("Uploading empty artifact")
}
return files, nil
}
12 changes: 8 additions & 4 deletions cmd/oras/root/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package root
import (
"context"
"errors"
"fmt"
"os"
"strings"
"sync"
Expand All @@ -43,6 +42,7 @@ type pushOptions struct {
option.Packer
option.ImageSpec
option.Target
option.Format

extraRefs []string
manifestConfigRef string
Expand Down Expand Up @@ -122,6 +122,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 {
display.SwitchToStderr()
return runPush(cmd.Context(), opts)
},
}
Expand Down Expand Up @@ -211,7 +212,7 @@ func runPush(ctx context.Context, opts pushOptions) error {
if err != nil {
return err
}
fmt.Println("Pushed", opts.AnnotatedReference())
display.Print("Pushed", opts.AnnotatedReference())

if len(opts.extraRefs) != 0 {
contentBytes, err := content.FetchAll(ctx, memoryStore, root)
Expand All @@ -225,10 +226,13 @@ func runPush(ctx context.Context, opts pushOptions) error {
}
}

fmt.Println("Digest:", root.Digest)
display.Print("Digest:", root.Digest)

// Export manifest
return opts.ExportManifest(ctx, memoryStore, root)
if err = opts.ExportManifest(ctx, memoryStore, root); err != nil {
return nil
}
return opts.Print(root, opts.TTY != nil)
}

func doPush(dst oras.Target, pack packFunc, copy copyFunc) (ocispec.Descriptor, error) {
Expand Down

0 comments on commit 2a2ca53

Please sign in to comment.