-
Notifications
You must be signed in to change notification settings - Fork 177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enrich discover --verbose command tree output with additional data (Layers, data from Descriptor) #1501
base: main
Are you sure you want to change the base?
Enrich discover --verbose command tree output with additional data (Layers, data from Descriptor) #1501
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,13 +16,20 @@ limitations under the License. | |
package tree | ||
|
||
import ( | ||
"cmp" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"gopkg.in/yaml.v3" | ||
"io" | ||
"oras.land/oras-go/v2" | ||
"oras.land/oras/cmd/oras/internal/option" | ||
"sort" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/opencontainers/go-digest" | ||
ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||
"gopkg.in/yaml.v3" | ||
"oras.land/oras/cmd/oras/internal/display/metadata" | ||
"oras.land/oras/internal/tree" | ||
) | ||
|
@@ -56,7 +63,7 @@ func (h *discoverHandler) MultiLevelSupported() bool { | |
} | ||
|
||
// OnDiscovered implements metadata.DiscoverHandler. | ||
func (h *discoverHandler) OnDiscovered(referrer, subject ocispec.Descriptor) error { | ||
func (h *discoverHandler) OnDiscovered(referrer, subject ocispec.Descriptor, ctx context.Context, target oras.ReadOnlyTarget, platform option.Platform) error { | ||
node, ok := h.nodes[subject.Digest] | ||
if !ok { | ||
return fmt.Errorf("unexpected subject descriptor: %v", subject) | ||
|
@@ -66,18 +73,118 @@ func (h *discoverHandler) OnDiscovered(referrer, subject ocispec.Descriptor) err | |
} | ||
referrerNode := node.AddPath(referrer.ArtifactType, referrer.Digest) | ||
if h.verbose { | ||
for k, v := range referrer.Annotations { | ||
addDescriptorInfo(referrer, referrerNode) | ||
|
||
if err := addAnnotationsInfo(referrer, referrerNode); err != nil { | ||
return err | ||
} | ||
|
||
if err := addLayersInfo(referrer, referrerNode, ctx, target, platform); err != nil { | ||
return err | ||
} | ||
} | ||
h.nodes[referrer.Digest] = referrerNode | ||
return nil | ||
} | ||
|
||
func addDescriptorInfo(referrer ocispec.Descriptor, referrerNode *tree.Node) { | ||
descriptorNode := referrerNode.AddPath("Descriptor") | ||
descriptorNode.AddPath(strings.TrimSpace("MediaType: " + referrer.MediaType)) | ||
descriptorNode.AddPath(strings.TrimSpace("Size: " + strconv.FormatInt(referrer.Size, 10))) | ||
} | ||
|
||
func addAnnotationsInfo(referrer ocispec.Descriptor, referrerNode *tree.Node) error { | ||
if len(referrer.Annotations) != 0 { | ||
annotationsNode := referrerNode.AddPath("Annotations") | ||
annotationsKeys := sortedKeys(referrer.Annotations) | ||
for _, k := range annotationsKeys { | ||
v := referrer.Annotations[k] | ||
bytes, err := yaml.Marshal(map[string]string{k: v}) | ||
if err != nil { | ||
return err | ||
} | ||
referrerNode.AddPath(strings.TrimSpace(string(bytes))) | ||
annotationsNode.AddPath(strings.TrimSpace(string(bytes))) | ||
} | ||
} | ||
h.nodes[referrer.Digest] = referrerNode | ||
return nil | ||
} | ||
|
||
func addLayersInfo(referrer ocispec.Descriptor, referrerNode *tree.Node, ctx context.Context, target oras.ReadOnlyTarget, platform option.Platform) error { | ||
layersNode := tree.New("Layers") | ||
layersDumped, ok := dumpLayers(referrer.Digest, layersNode, ctx, target, platform) | ||
if ok != nil { | ||
return ok | ||
} else if layersDumped { | ||
referrerNode.AddNode(layersNode) | ||
} | ||
return nil | ||
} | ||
|
||
func dumpLayers(digest digest.Digest, node *tree.Node, ctx context.Context, target oras.ReadOnlyTarget, platform option.Platform) (bool, error) { | ||
fetchOpts := oras.DefaultFetchBytesOptions | ||
fetchOpts.TargetPlatform = platform.Platform | ||
_, content, err := oras.FetchBytes(ctx, target, digest.String(), fetchOpts) | ||
if err != nil { | ||
return false, fmt.Errorf("failed to fetch the content of %q: %w", "", err) | ||
} | ||
|
||
var jsonMap map[string]any | ||
if err = json.Unmarshal(content, &jsonMap); err != nil { | ||
return false, err | ||
} | ||
|
||
if layers, ok := jsonMap["layers"].([]interface{}); ok { | ||
if len(layers) != 0 { | ||
Comment on lines
+124
to
+137
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This whole thing seems to me like a method fetchLayers or something which returns layers or error |
||
for idx, item := range layers { | ||
layerNode := node.AddPath(fmt.Sprintf("Layer[%d]", idx)) | ||
|
||
if layer, ok := item.(map[string]interface{}); ok { | ||
|
||
layerKeys := sortedKeys(layer) | ||
for _, k := range layerKeys { | ||
v := layer[k] | ||
if k == "annotations" { | ||
annotationsNode := layerNode.AddPath(k) | ||
if annotationsMap, ok := v.(map[string]interface{}); ok { | ||
annotationsMapKeys := sortedKeys(annotationsMap) | ||
for _, k := range annotationsMapKeys { | ||
v := annotationsMap[k] | ||
bytes, err := yaml.Marshal(map[string]interface{}{k: v}) | ||
if err != nil { | ||
continue | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is too deep, something needs to be broken up here |
||
} | ||
annotationsNode.AddPath(strings.TrimSpace(string(bytes))) | ||
} | ||
} | ||
} else { | ||
bytes, err := yaml.Marshal(map[string]interface{}{k: v}) | ||
if err != nil { | ||
continue | ||
} | ||
layerNode.AddPath(strings.TrimSpace(string(bytes))) | ||
} | ||
} | ||
} | ||
} | ||
return true, nil | ||
} | ||
} | ||
|
||
return false, nil | ||
|
||
} | ||
|
||
func sortedKeys[K cmp.Ordered, V any](m map[K]V) []K { | ||
keys := make([]K, len(m)) | ||
i := 0 | ||
for k := range m { | ||
keys[i] = k | ||
i++ | ||
} | ||
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) | ||
return keys | ||
} | ||
|
||
// OnCompleted implements metadata.DiscoverHandler. | ||
func (h *discoverHandler) OnCompleted() error { | ||
return tree.NewPrinter(h.out).Print(h.root) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,7 +135,7 @@ func runDiscover(cmd *cobra.Command, opts *discoverOptions) error { | |
return err | ||
} | ||
if handler.MultiLevelSupported() { | ||
if err := fetchAllReferrers(ctx, repo, desc, opts.artifactType, handler); err != nil { | ||
if err := fetchAllReferrers(ctx, repo, desc, opts.artifactType, handler, opts.Platform); err != nil { | ||
return err | ||
} | ||
} else { | ||
|
@@ -144,29 +144,29 @@ func runDiscover(cmd *cobra.Command, opts *discoverOptions) error { | |
return err | ||
} | ||
for _, ref := range refs { | ||
if err := handler.OnDiscovered(ref, desc); err != nil { | ||
if err := handler.OnDiscovered(ref, desc, ctx, repo, opts.Platform); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
return handler.OnCompleted() | ||
} | ||
|
||
func fetchAllReferrers(ctx context.Context, repo oras.ReadOnlyGraphTarget, desc ocispec.Descriptor, artifactType string, handler metadata.DiscoverHandler) error { | ||
func fetchAllReferrers(ctx context.Context, repo oras.ReadOnlyGraphTarget, desc ocispec.Descriptor, artifactType string, handler metadata.DiscoverHandler, platform option.Platform) error { | ||
results, err := registry.Referrers(ctx, repo, desc, artifactType) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, r := range results { | ||
if err := handler.OnDiscovered(r, desc); err != nil { | ||
if err := handler.OnDiscovered(r, desc, ctx, repo, platform); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not loving passing ctx in the middle here. I haven't looked everything over, but seems like this should be passing down a fetch method around here |
||
return err | ||
} | ||
if err := fetchAllReferrers(ctx, repo, ocispec.Descriptor{ | ||
Digest: r.Digest, | ||
Size: r.Size, | ||
MediaType: r.MediaType, | ||
}, artifactType, handler); err != nil { | ||
}, artifactType, handler, platform); err != nil { | ||
return err | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it would be easier to unit test this stuff if it was a wrapper for referrerNode and these were addDescriptor, addAnnotations, etc.