Skip to content

Commit

Permalink
fix struct fields with json tags
Browse files Browse the repository at this point in the history
Signed-off-by: Billy Zha <[email protected]>
  • Loading branch information
qweeah committed Apr 2, 2024
1 parent eb5f62d commit ce6bc89
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 81 deletions.
18 changes: 16 additions & 2 deletions cmd/oras/internal/display/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"oras.land/oras/cmd/oras/internal/display/metadata/json"
"oras.land/oras/cmd/oras/internal/display/metadata/template"
"oras.land/oras/cmd/oras/internal/display/metadata/text"
"oras.land/oras/cmd/oras/internal/display/raw"
"oras.land/oras/cmd/oras/internal/display/status"
)

Expand Down Expand Up @@ -75,6 +76,19 @@ func NewAttachHandler(format string, tty *os.File, out io.Writer, verbose bool)
}

// NewManifestFetchHandler returns a manifest fetch handler.
func NewManifestFetchHandler(out io.Writer, format string) metadata.ManifestFetchHandler {
return template.NewManifestFetchHandler(out, format)
func NewManifestFetchHandler(out io.Writer, outputPath string, format string, pretty bool) (metadata.ManifestFetchHandler, raw.ManifestFetchHandler) {
var metadataHandler metadata.ManifestFetchHandler
var rawContentHandler raw.ManifestFetchHandler
switch format {
case "raw":
metadataHandler = metadata.NewDiscardHandler()
rawContentHandler = raw.NewManifestFetchHandler(out, pretty)
case "json":
metadataHandler = json.NewManifestFetchHandler(out)
rawContentHandler = raw.NewDiscardHandler()
default:
metadataHandler = template.NewManifestFetchHandler(out, format)
rawContentHandler = raw.NewDiscardHandler()
}
return metadataHandler, rawContentHandler
}
28 changes: 28 additions & 0 deletions cmd/oras/internal/display/metadata/discard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
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 metadata

import ocispec "github.com/opencontainers/image-spec/specs-go/v1"

type discard struct{}

// NewDiscardHandler creates a new handler that discards output for all events.
func NewDiscardHandler() ManifestFetchHandler {
return discard{}
}

// OnFetched implements ManifestFetchHandler.
func (discard) OnFetched([]byte, ocispec.Descriptor) error { return nil }
3 changes: 2 additions & 1 deletion cmd/oras/internal/display/metadata/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ type AttachHandler interface {

// ManifestFetchHandler handles metadata output for manifest fetch events.
type ManifestFetchHandler interface {
OnFetched(manifest ocispec.Manifest) error
// OnFetched is called after the manifest content is fetched.
OnFetched([]byte, ocispec.Descriptor) error
}
54 changes: 54 additions & 0 deletions cmd/oras/internal/display/metadata/json/manifest_fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
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 json

import (
"encoding/json"
"fmt"
"io"
"reflect"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras/cmd/oras/internal/display/metadata"
"oras.land/oras/cmd/oras/internal/display/metadata/model"
"oras.land/oras/internal/docker"
)

// ManifestFetchHandler handles JSON metadata output for manifest fetch events.
type ManifestFetchHandler struct {
out io.Writer
}

// NewManifestFetchHandler creates a new handler for manifest fetch events.
func NewManifestFetchHandler(out io.Writer) metadata.ManifestFetchHandler {
return &ManifestFetchHandler{
out: out,
}
}

// OnFetched is called after the manifest fetch is completed.
func (h *ManifestFetchHandler) OnFetched(content []byte, desc ocispec.Descriptor) error {
switch desc.MediaType {
case ocispec.MediaTypeImageManifest, docker.MediaTypeManifest:
var manifest ocispec.Manifest
if err := json.Unmarshal(content, &manifest); err != nil {
return err
}
return printJSON(h.out, model.ToMappable(reflect.ValueOf(manifest)))
default:
return fmt.Errorf("cannot apply template: unsupported media type %s", desc.MediaType)
}
}
28 changes: 17 additions & 11 deletions cmd/oras/internal/display/metadata/model/mappable.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,8 @@ import (
func ToMappable(v reflect.Value) any {
switch v.Kind() {
case reflect.Struct:
t := v.Type()
numField := t.NumField()
ret := make(map[string]any)
for i := 0; i < numField; i++ {
fv := v.Field(i)
tag := t.Field(i).Tag.Get("json")
if tag == "" {
continue
}
key, _, _ := strings.Cut(tag, ",")
ret[key] = ToMappable(fv)
}
addToMap(ret, v)
return ret
case reflect.Slice, reflect.Array:
ret := make([]any, v.Len())
Expand All @@ -53,3 +43,19 @@ func ToMappable(v reflect.Value) any {
}
return v.Interface()
}

func addToMap(ret map[string]any, v reflect.Value) {
t := v.Type()
numField := t.NumField()
for i := 0; i < numField; i++ {
fv := v.Field(i)
ft := t.Field(i)
tag := ft.Tag.Get("json")
if tag == "" {
addToMap(ret, fv)
} else {
key, _, _ := strings.Cut(tag, ",")
ret[key] = ToMappable(fv)
}
}
}
16 changes: 14 additions & 2 deletions cmd/oras/internal/display/metadata/template/manifest_fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ limitations under the License.
package template

import (
"encoding/json"
"fmt"
"io"
"reflect"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras/cmd/oras/internal/display/metadata"
"oras.land/oras/cmd/oras/internal/display/metadata/model"
"oras.land/oras/internal/docker"
)

// ManifestFetchHandler handles JSON metadata output for manifest fetch events.
Expand All @@ -39,6 +42,15 @@ func NewManifestFetchHandler(out io.Writer, template string) metadata.ManifestFe
}

// OnFetched is called after the manifest fetch is completed.
func (h *ManifestFetchHandler) OnFetched(manifest ocispec.Manifest) error {
return parseAndWrite(h.out, model.ToMappable(reflect.ValueOf(manifest)), h.template)
func (h *ManifestFetchHandler) OnFetched(content []byte, desc ocispec.Descriptor) error {
switch desc.MediaType {
case ocispec.MediaTypeImageManifest, docker.MediaTypeManifest:
var manifest ocispec.Manifest
if err := json.Unmarshal(content, &manifest); err != nil {
return err
}
return parseAndWrite(h.out, model.ToMappable(reflect.ValueOf(manifest)), h.template)
default:
return fmt.Errorf("cannot apply template: unsupported media type %s", desc.MediaType)
}
}
33 changes: 33 additions & 0 deletions cmd/oras/internal/display/raw/discard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
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 raw

import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

type discard struct{}

// OnContentFetched implements ManifestFetchHandler.
func (discard) OnContentFetched(string, []byte) error { return nil }

// OnDescriptorFetched implements ManifestFetchHandler.
func (discard) OnDescriptorFetched(desc ocispec.Descriptor) error { return nil }

// NewManifestFetchHandler creates a new handler.
func NewDiscardHandler() ManifestFetchHandler {
return discard{}
}
29 changes: 29 additions & 0 deletions cmd/oras/internal/display/raw/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
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 raw

import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

// ManifestFetchHandler handles raw output for manifest fetch events.
type ManifestFetchHandler interface {
// OnFetched is called after the manifest content is fetched.
OnContentFetched(outputPath string, content []byte) error
// OnDescriptorFetched is called after the manifest descriptor is
// fetched.
OnDescriptorFetched(desc ocispec.Descriptor) error
}
76 changes: 76 additions & 0 deletions cmd/oras/internal/display/raw/manifest_fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
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 raw

import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

// RawManifestFetch handles raw content output.
type RawManifestFetch struct {
pretty bool
stdout io.Writer
}

// OnContentFetched implements ManifestFetchHandler.
func (h *RawManifestFetch) OnContentFetched(outputPath string, manifest []byte) error {
out := h.stdout
if outputPath != "-" && outputPath != "" {
f, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return fmt.Errorf("failed to open %q: %w", outputPath, err)
}
defer f.Close()
}
return h.output(out, manifest)
}

// OnDescriptorFetched implements ManifestFetchHandler.
func (h *RawManifestFetch) OnDescriptorFetched(desc ocispec.Descriptor) error {
descBytes, err := json.Marshal(desc)
if err != nil {
return fmt.Errorf("invalid descriptor: %w", err)
}
return h.output(h.stdout, descBytes)
}

// NewManifestFetchHandler creates a new handler.
func NewManifestFetchHandler(out io.Writer, pretty bool) ManifestFetchHandler {
return &RawManifestFetch{
pretty: pretty,
stdout: out,
}
}

// OnFetched is called after the content is fetched.
func (h *RawManifestFetch) output(out io.Writer, data []byte) error {
if h.pretty {
buf := bytes.NewBuffer(nil)
if err := json.Indent(buf, data, "", " "); err != nil {
return fmt.Errorf("failed to prettify: %w", err)
}
buf.WriteByte('\n')
data = buf.Bytes()
}
_, err := out.Write(data)
return err
}
Loading

0 comments on commit ce6bc89

Please sign in to comment.