Skip to content

Commit

Permalink
feat: support --format for oras pull (oras-project#1293)
Browse files Browse the repository at this point in the history
Signed-off-by: Billy Zha <jinzha1@microsoft.com>
  • Loading branch information
qweeah authored and FeynmanZhou committed May 11, 2024
1 parent 8151f9c commit 416feed
Show file tree
Hide file tree
Showing 18 changed files with 684 additions and 181 deletions.
23 changes: 22 additions & 1 deletion cmd/oras/internal/display/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ func NewPushHandler(format string, tty *os.File, out io.Writer, verbose bool) (s
default:
metadataHandler = template.NewPushHandler(out, format)
}

return statusHandler, metadataHandler
}

Expand All @@ -70,6 +69,28 @@ func NewAttachHandler(format string, tty *os.File, out io.Writer, verbose bool)
default:
metadataHandler = template.NewAttachHandler(out, format)
}
return statusHandler, metadataHandler
}

// 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) {
var statusHandler status.PullHandler
if tty != nil {
statusHandler = status.NewTTYPullHandler(tty)
} else if format == "" {
statusHandler = status.NewTextPullHandler(out, verbose)
} else {
statusHandler = status.NewDiscardHandler()
}

var metadataHandler metadata.PullHandler
switch format {
case "":
metadataHandler = text.NewPullHandler(out)
case "json":
metadataHandler = json.NewPullHandler(out, path)
default:
metadataHandler = template.NewPullHandler(out, path, format)
}
return statusHandler, metadataHandler
}
10 changes: 10 additions & 0 deletions cmd/oras/internal/display/metadata/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,13 @@ type PushHandler interface {
type AttachHandler interface {
OnCompleted(opts *option.Target, root, subject ocispec.Descriptor) error
}

// PullHandler handles metadata output for pull events.
type PullHandler interface {
// OnLayerSkipped is called when a layer is skipped.
OnLayerSkipped(ocispec.Descriptor) error
// OnFilePulled is called after a file is pulled.
OnFilePulled(name string, outputDir string, desc ocispec.Descriptor, descPath string) error
// OnCompleted is called when the pull cmd execution is completed.
OnCompleted(opts *option.Target, desc ocispec.Descriptor) error
}
55 changes: 55 additions & 0 deletions cmd/oras/internal/display/metadata/json/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
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 (
"io"

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/cmd/oras/internal/option"
)

// PullHandler handles JSON metadata output for pull events.
type PullHandler struct {
path string
pulled model.Pulled
out io.Writer
}

// OnLayerSkipped implements metadata.PullHandler.
func (ph *PullHandler) OnLayerSkipped(ocispec.Descriptor) error {
return nil
}

// NewPullHandler returns a new handler for Pull events.
func NewPullHandler(out io.Writer, path string) metadata.PullHandler {
return &PullHandler{
out: out,
path: path,
}
}

// OnFilePulled implements metadata.PullHandler.
func (ph *PullHandler) OnFilePulled(name string, outputDir string, desc ocispec.Descriptor, descPath string) error {
return ph.pulled.Add(name, outputDir, desc, descPath)
}

// OnCompleted implements metadata.PullHandler.
func (ph *PullHandler) OnCompleted(opts *option.Target, desc ocispec.Descriptor) error {
return printJSON(ph.out, model.NewPull(ph.path+"@"+desc.Digest.String(), ph.pulled.Files()))
}
95 changes: 95 additions & 0 deletions cmd/oras/internal/display/metadata/model/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
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 (
"fmt"
"path/filepath"
"slices"
"sync"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content/file"
)

// File records metadata of a pulled file.
type File struct {
// Path is the absolute path of the pulled file.
Path string
Descriptor
}

// newFile creates a new file metadata.
func newFile(name string, outputDir string, desc ocispec.Descriptor, descPath string) (File, error) {
path := name
if !filepath.IsAbs(name) {
var err error
path, err = filepath.Abs(filepath.Join(outputDir, name))
// not likely to go wrong since the file has already be written to file store
if err != nil {
return File{}, fmt.Errorf("failed to get absolute path of pulled file %s: %w", name, err)
}
} else {
path = filepath.Clean(path)
}
if desc.Annotations[file.AnnotationUnpack] == "true" {
path += string(filepath.Separator)
}
return File{
Path: path,
Descriptor: FromDescriptor(descPath, desc),
}, nil
}

type pull struct {
DigestReference
Files []File `json:"Files"`
}

// NewPull creates a new metadata struct for pull command.
func NewPull(digestReference string, files []File) any {
return pull{
DigestReference: DigestReference{
Ref: digestReference,
},
Files: files,
}
}

// Pulled records all pulled files.
type Pulled struct {
lock sync.Mutex
files []File
}

// Files returns all pulled files.
func (p *Pulled) Files() []File {
p.lock.Lock()
defer p.lock.Unlock()
return slices.Clone(p.files)
}

// Add adds a pulled file.
func (p *Pulled) Add(name string, outputDir string, desc ocispec.Descriptor, descPath string) error {
p.lock.Lock()
defer p.lock.Unlock()
file, err := newFile(name, outputDir, desc, descPath)
if err != nil {
return err
}
p.files = append(p.files, file)
return nil
}
57 changes: 57 additions & 0 deletions cmd/oras/internal/display/metadata/template/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
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 template

import (
"io"

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/cmd/oras/internal/option"
)

// PullHandler handles text metadata output for pull events.
type PullHandler struct {
template string
path string
out io.Writer
pulled model.Pulled
}

// OnCompleted implements metadata.PullHandler.
func (ph *PullHandler) OnCompleted(opts *option.Target, desc ocispec.Descriptor) error {
return parseAndWrite(ph.out, model.NewPull(ph.path+"@"+desc.Digest.String(), ph.pulled.Files()), ph.template)
}

// OnFilePulled implements metadata.PullHandler.
func (ph *PullHandler) OnFilePulled(name string, outputDir string, desc ocispec.Descriptor, descPath string) error {
return ph.pulled.Add(name, outputDir, desc, descPath)
}

// OnLayerSkipped implements metadata.PullHandler.
func (ph *PullHandler) OnLayerSkipped(ocispec.Descriptor) error {
return nil
}

// NewPullHandler returns a new handler for pull events.
func NewPullHandler(out io.Writer, path string, template string) metadata.PullHandler {
return &PullHandler{
path: path,
template: template,
out: out,
}
}
61 changes: 61 additions & 0 deletions cmd/oras/internal/display/metadata/text/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
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 text

import (
"fmt"
"io"
"sync/atomic"

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

// PullHandler handles text metadata output for pull events.
type PullHandler struct {
out io.Writer
layerSkipped atomic.Bool
}

// OnCompleted implements metadata.PullHandler.
func (p *PullHandler) OnCompleted(opts *option.Target, desc ocispec.Descriptor) error {
if p.layerSkipped.Load() {
_, _ = fmt.Fprintf(p.out, "Skipped pulling layers without file name in %q\n", ocispec.AnnotationTitle)
_, _ = fmt.Fprintf(p.out, "Use 'oras copy %s --to-oci-layout <layout-dir>' to pull all layers.\n", opts.RawReference)
} else {
_, _ = fmt.Fprintln(p.out, "Pulled", opts.AnnotatedReference())
_, _ = fmt.Fprintln(p.out, "Digest:", desc.Digest)
}
return nil
}

func (p *PullHandler) OnFilePulled(name string, outputDir string, desc ocispec.Descriptor, descPath string) error {
return nil
}

// OnLayerSkipped implements metadata.PullHandler.
func (ph *PullHandler) OnLayerSkipped(ocispec.Descriptor) error {
ph.layerSkipped.Store(true)
return nil
}

// NewPullHandler returns a new handler for Pull events.
func NewPullHandler(out io.Writer) metadata.PullHandler {
return &PullHandler{
out: out,
}
}
36 changes: 33 additions & 3 deletions cmd/oras/internal/display/status/discard.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ limitations under the License.
package status

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

func discardStopTrack() error {
return nil
}

// DiscardHandler is a no-op handler that discards all status updates.
type DiscardHandler struct{}

Expand All @@ -38,10 +43,35 @@ func (DiscardHandler) OnEmptyArtifact() error {
return nil
}

// TrackTarget returns a target with status tracking
func (DiscardHandler) TrackTarget(gt oras.GraphTarget) (oras.GraphTarget, error) {
return gt, nil
// TrackTarget returns a target with status tracking.
func (DiscardHandler) TrackTarget(gt oras.GraphTarget) (oras.GraphTarget, StopTrackTargetFunc, error) {
return gt, discardStopTrack, nil
}

// UpdateCopyOptions updates the copy options for the artifact push.
func (DiscardHandler) UpdateCopyOptions(opts *oras.CopyGraphOptions, fetcher content.Fetcher) {}

// OnNodeDownloading implements PullHandler.
func (DiscardHandler) OnNodeDownloading(desc ocispec.Descriptor) error {
return nil
}

// OnNodeDownloaded implements PullHandler.
func (DiscardHandler) OnNodeDownloaded(desc ocispec.Descriptor) error {
return nil
}

// OnNodeRestored implements PullHandler.
func (DiscardHandler) OnNodeRestored(_ ocispec.Descriptor) error {
return nil
}

// OnNodeProcessing implements PullHandler.
func (DiscardHandler) OnNodeProcessing(desc ocispec.Descriptor) error {
return nil
}

// OnNodeProcessing implements PullHandler.
func (DiscardHandler) OnNodeSkipped(desc ocispec.Descriptor) error {
return nil
}
Loading

0 comments on commit 416feed

Please sign in to comment.