Skip to content

Commit

Permalink
feat: add pushed tags to metadata (#1346)
Browse files Browse the repository at this point in the history
Signed-off-by: Billy Zha <[email protected]>
  • Loading branch information
qweeah authored May 8, 2024
1 parent 9ab9e56 commit c179d0a
Show file tree
Hide file tree
Showing 20 changed files with 164 additions and 47 deletions.
6 changes: 3 additions & 3 deletions cmd/oras/internal/display/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
)

// NewPushHandler returns status and metadata handlers for push command.
func NewPushHandler(format string, tty *os.File, out io.Writer, verbose bool) (status.PushHandler, metadata.PushHandler) {
func NewPushHandler(out io.Writer, format string, tty *os.File, verbose bool) (status.PushHandler, metadata.PushHandler) {
var statusHandler status.PushHandler
if tty != nil {
statusHandler = status.NewTTYPushHandler(tty)
Expand All @@ -56,7 +56,7 @@ func NewPushHandler(format string, tty *os.File, out io.Writer, verbose bool) (s
}

// NewAttachHandler returns status and metadata handlers for attach command.
func NewAttachHandler(format string, tty *os.File, out io.Writer, verbose bool) (status.AttachHandler, metadata.AttachHandler) {
func NewAttachHandler(out io.Writer, format string, tty *os.File, verbose bool) (status.AttachHandler, metadata.AttachHandler) {
var statusHandler status.AttachHandler
if tty != nil {
statusHandler = status.NewTTYAttachHandler(tty)
Expand All @@ -79,7 +79,7 @@ func NewAttachHandler(format string, tty *os.File, out io.Writer, verbose bool)
}

// NewPullHandler returns status and metadata handlers for pull command.
func NewPullHandler(format string, path string, tty *os.File, out io.Writer, verbose bool) (status.PullHandler, metadata.PullHandler) {
func NewPullHandler(out io.Writer, format string, path string, tty *os.File, verbose bool) (status.PullHandler, metadata.PullHandler) {
var statusHandler status.PullHandler
if tty != nil {
statusHandler = status.NewTTYPullHandler(tty)
Expand Down
2 changes: 1 addition & 1 deletion cmd/oras/internal/display/metadata/json/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ func NewAttachHandler(out io.Writer) metadata.AttachHandler {

// OnCompleted is called when the attach command is completed.
func (ah *AttachHandler) OnCompleted(opts *option.Target, root, subject ocispec.Descriptor) error {
return utils.PrintPrettyJSON(ah.out, model.NewPush(root, opts.Path))
return utils.PrintPrettyJSON(ah.out, model.NewAttach(root, opts.Path))
}
12 changes: 9 additions & 3 deletions cmd/oras/internal/display/metadata/json/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ import (
"oras.land/oras/cmd/oras/internal/display/metadata/model"
"oras.land/oras/cmd/oras/internal/display/utils"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/contentutil"
)

// PushHandler handles JSON metadata output for push events.
type PushHandler struct {
path string
out io.Writer
path string
out io.Writer
tagged model.Tagged
}

// NewPushHandler creates a new handler for push events.
Expand All @@ -40,16 +42,20 @@ func NewPushHandler(out io.Writer) metadata.PushHandler {

// OnTagged implements metadata.TagHandler.
func (ph *PushHandler) OnTagged(desc ocispec.Descriptor, tag string) error {
ph.tagged.AddTag(tag)
return nil
}

// OnCopied is called after files are copied.
func (ph *PushHandler) OnCopied(opts *option.Target) error {
if opts.RawReference != "" && !contentutil.IsDigest(opts.Reference) {
ph.tagged.AddTag(opts.Reference)
}
ph.path = opts.Path
return nil
}

// OnCompleted is called after the push is completed.
func (ph *PushHandler) OnCompleted(root ocispec.Descriptor) error {
return utils.PrintPrettyJSON(ph.out, model.NewPush(root, ph.path))
return utils.PrintPrettyJSON(ph.out, model.NewPush(root, ph.path, ph.tagged.Tags()))
}
28 changes: 28 additions & 0 deletions cmd/oras/internal/display/metadata/model/attach.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 model

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

// attach contains metadata formatted by oras attach.
type attach struct {
Descriptor
}

// NewAttach returns a metadata getter for attach command.
func NewAttach(desc ocispec.Descriptor, path string) any {
return attach{FromDescriptor(path, desc)}
}
4 changes: 2 additions & 2 deletions cmd/oras/internal/display/metadata/model/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import (

// DigestReference is a reference to an artifact with digest.
type DigestReference struct {
Ref string `json:"ref"`
Reference string `json:"reference"`
}

// NewDigestReference creates a new digest reference.
func NewDigestReference(name string, digest string) DigestReference {
return DigestReference{
Ref: name + "@" + digest,
Reference: name + "@" + digest,
}
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/oras/internal/display/metadata/model/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ type pull struct {
func NewPull(digestReference string, files []File) any {
return pull{
DigestReference: DigestReference{
Ref: digestReference,
Reference: digestReference,
},
Files: files,
}
Expand Down
16 changes: 13 additions & 3 deletions cmd/oras/internal/display/metadata/model/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,24 @@ limitations under the License.

package model

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

// push contains metadata formatted by oras push.
type push struct {
Descriptor
ReferenceAsTags []string `json:"referenceAsTags"`
}

// NewPush returns a metadata getter for push command.
func NewPush(desc ocispec.Descriptor, path string) any {
return push{FromDescriptor(path, desc)}
func NewPush(desc ocispec.Descriptor, path string, tags []string) any {
var refAsTags []string
for _, tag := range tags {
refAsTags = append(refAsTags, path+":"+tag)
}
return push{
Descriptor: FromDescriptor(path, desc),
ReferenceAsTags: refAsTags,
}
}
43 changes: 43 additions & 0 deletions cmd/oras/internal/display/metadata/model/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
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 model

import (
"slices"
"sync"
)

// Tagged contains metadata formatted by oras Tagged.
type Tagged struct {
tags []string
lock sync.RWMutex
}

// AddTag adds a tag to the metadata.
func (tag *Tagged) AddTag(t string) {
tag.lock.Lock()
defer tag.lock.Unlock()

tag.tags = append(tag.tags, t)
}

// Tags returns the tags.
func (tag *Tagged) Tags() []string {
tag.lock.RLock()
defer tag.lock.RUnlock()
slices.Sort(tag.tags)
return tag.tags
}
2 changes: 1 addition & 1 deletion cmd/oras/internal/display/metadata/template/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ func NewAttachHandler(out io.Writer, template string) metadata.AttachHandler {

// OnCompleted formats the metadata of attach command.
func (ah *AttachHandler) OnCompleted(opts *option.Target, root, subject ocispec.Descriptor) error {
return parseAndWrite(ah.out, model.NewPush(root, opts.Path), ah.template)
return parseAndWrite(ah.out, model.NewAttach(root, opts.Path), ah.template)
}
8 changes: 7 additions & 1 deletion cmd/oras/internal/display/metadata/template/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import (
"oras.land/oras/cmd/oras/internal/display/metadata"
"oras.land/oras/cmd/oras/internal/display/metadata/model"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/contentutil"
)

// PushHandler handles go-template metadata output for push events.
type PushHandler struct {
template string
path string
tagged model.Tagged
out io.Writer
}

Expand All @@ -41,16 +43,20 @@ func NewPushHandler(out io.Writer, template string) metadata.PushHandler {

// OnTagged implements metadata.TagHandler.
func (ph *PushHandler) OnTagged(desc ocispec.Descriptor, tag string) error {
ph.tagged.AddTag(tag)
return nil
}

// OnStarted is called after files are copied.
func (ph *PushHandler) OnCopied(opts *option.Target) error {
if opts.RawReference != "" && !contentutil.IsDigest(opts.Reference) {
ph.tagged.AddTag(opts.Reference)
}
ph.path = opts.Path
return nil
}

// OnCompleted is called after the push is completed.
func (ph *PushHandler) OnCompleted(root ocispec.Descriptor) error {
return parseAndWrite(ph.out, model.NewPush(root, ph.path), ph.template)
return parseAndWrite(ph.out, model.NewPush(root, ph.path, ph.tagged.Tags()), ph.template)
}
2 changes: 1 addition & 1 deletion cmd/oras/root/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func runAttach(cmd *cobra.Command, opts *attachOptions) error {
Recommendation: `To attach to an existing artifact, please provide files via argument or annotations via flag "--annotation". Run "oras attach -h" for more options and examples`,
}
}
displayStatus, displayMetadata := display.NewAttachHandler(opts.Template, opts.TTY, cmd.OutOrStdout(), opts.Verbose)
displayStatus, displayMetadata := display.NewAttachHandler(cmd.OutOrStdout(), opts.Template, opts.TTY, opts.Verbose)

// prepare manifest
store, err := file.New("")
Expand Down
4 changes: 2 additions & 2 deletions cmd/oras/root/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ Example - Discover referrers of the manifest tagged 'v1' in an OCI image layout

cmd.Flags().StringVarP(&opts.artifactType, "artifact-type", "", "", "artifact type")
cmd.Flags().StringVarP(&opts.Template, "output", "o", "tree", "[Deprecated] format in which to display referrers (table, json, or tree). tree format will also show indirect referrers")
cmd.Flags().StringVarP(&opts.Template, "format", "", "tree", `[Experimental] Format output using a custom template:
cmd.Flags().StringVarP(&opts.Template, "format", "", "", `[Experimental] Format output using a custom template:
'tree': Get referrers recursively and print in tree format (default)
'table': Get direct referrers and output in table format
'json': Get direct referrers and output in JSON format
'$TEMPLATE': Print direct referrers using the given Go template.`)
'$TEMPLATE': Print direct referrers using the given Go template`)
opts.EnableDistributionSpecFlag()
option.ApplyFlags(&opts, cmd.Flags())
return oerrors.Command(cmd, &opts.Target)
Expand Down
2 changes: 1 addition & 1 deletion cmd/oras/root/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func runPull(cmd *cobra.Command, opts *pullOptions) error {
dst.AllowPathTraversalOnWrite = opts.PathTraversal
dst.DisableOverwrite = opts.KeepOldFiles

statusHandler, metadataHandler := display.NewPullHandler(opts.Template, opts.Path, opts.TTY, cmd.OutOrStdout(), opts.Verbose)
statusHandler, metadataHandler := display.NewPullHandler(cmd.OutOrStdout(), opts.Template, opts.Path, opts.TTY, opts.Verbose)
desc, err := doPull(ctx, src, dst, copyOptions, metadataHandler, statusHandler, opts)
if err != nil {
if errors.Is(err, file.ErrPathTraversalDisallowed) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/oras/root/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error {
if err != nil {
return err
}
displayStatus, displayMetadata := display.NewPushHandler(opts.Template, opts.TTY, cmd.OutOrStdout(), opts.Verbose)
displayStatus, displayMetadata := display.NewPushHandler(cmd.OutOrStdout(), opts.Template, opts.TTY, opts.Verbose)

// prepare pack
packOpts := oras.PackManifestOptions{
Expand Down
4 changes: 2 additions & 2 deletions cmd/oras/root/repo/tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"oras.land/oras/cmd/oras/internal/command"
oerrors "oras.land/oras/cmd/oras/internal/errors"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/contentutil"
)

type showTagsOptions struct {
Expand Down Expand Up @@ -87,8 +88,7 @@ func showTags(cmd *cobra.Command, opts *showTagsOptions) error {
}
filter := ""
if opts.Reference != "" {
_, err := digest.Parse(opts.Reference)
if err == nil {
if contentutil.IsDigest(opts.Reference) {
filter = opts.Reference
} else {
desc, err := finder.Resolve(ctx, opts.Reference)
Expand Down
24 changes: 24 additions & 0 deletions internal/contentutil/reference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
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 contentutil

import "github.com/opencontainers/go-digest"

// IsDigest checks if the given string is a valid digest.
func IsDigest(tagOrDigest string) bool {
_, err := digest.Parse(tagOrDigest)
return err == nil
}
Loading

0 comments on commit c179d0a

Please sign in to comment.