Skip to content
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

feat: support --format for oras pull #1293

Merged
merged 58 commits into from
Apr 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
c89e678
refactor: use handler in oras pull command
qweeah Mar 13, 2024
b7b76de
refactor: remove option update for pull
qweeah Mar 14, 2024
e08a5a5
add meta output
qweeah Mar 15, 2024
1f6c55e
add e2e test
qweeah Mar 18, 2024
29bf5f1
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Mar 18, 2024
ba7120e
add test for tracked target
qweeah Mar 18, 2024
8e28e99
fix race
qweeah Mar 18, 2024
49f9ad9
add json test
qweeah Mar 18, 2024
7f130a4
add coverage
qweeah Mar 18, 2024
d8b87d8
add comment
qweeah Mar 18, 2024
2b8b8bb
resolve comment
qweeah Mar 25, 2024
63d664b
change to io.Closer
qweeah Mar 25, 2024
a83297b
resolve comments
qweeah Mar 25, 2024
4c2c148
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Mar 25, 2024
cd5c4a4
resolve comments
qweeah Mar 25, 2024
8f8f9a4
bug fix
qweeah Mar 25, 2024
07d4a8e
add printer handle output
qweeah Mar 25, 2024
a879b17
refactor printer
qweeah Mar 25, 2024
eeabec7
doc clean
qweeah Mar 25, 2024
61e0241
move outputable to view
qweeah Mar 26, 2024
b82375f
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Mar 28, 2024
6a3d9df
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Mar 29, 2024
5f398fe
use new printer
qweeah Mar 29, 2024
a8289aa
align status output
qweeah Mar 29, 2024
c10a161
doc clean
qweeah Mar 29, 2024
a3f5f02
revert changes
qweeah Mar 29, 2024
d839760
code clean
qweeah Mar 29, 2024
cd93b0e
remove unused fields
qweeah Mar 29, 2024
67d17a7
code clean
qweeah Apr 1, 2024
d62f9b0
code clean
qweeah Apr 1, 2024
6cfd9bb
refactor
qweeah Apr 1, 2024
6b22b08
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Apr 1, 2024
625c61b
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Apr 2, 2024
2b52250
add used descriptor
qweeah Apr 2, 2024
47eea86
resolve comment
qweeah Apr 2, 2024
17df57b
resolve comment
qweeah Apr 2, 2024
bdbf73d
resolve comment
qweeah Apr 3, 2024
7a21bdc
bug fix
qweeah Apr 3, 2024
da5fa03
return error for OnFilePulled
qweeah Apr 3, 2024
a20c600
resolve comment
qweeah Apr 7, 2024
f20d61b
resolve commment
qweeah Apr 7, 2024
a4791db
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Apr 7, 2024
54cfd51
e2e: cover invalid abs path
qweeah Apr 7, 2024
ea80183
add e2e coverage
qweeah Apr 7, 2024
7b59561
increase coverage
qweeah Apr 7, 2024
6bf012f
add e2e test
qweeah Apr 7, 2024
8e32d5e
add e2e
qweeah Apr 7, 2024
98c7faf
revert attach change
qweeah Apr 7, 2024
a1b0026
exclude abs path
qweeah Apr 7, 2024
7079ffa
refactor stop tracked target
qweeah Apr 7, 2024
a38b975
code clean
qweeah Apr 7, 2024
7def480
code clean
qweeah Apr 7, 2024
3a9aa4f
add stop track function
qweeah Apr 7, 2024
9c801bb
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Apr 7, 2024
71fee7a
Merge remote-tracking branch 'upstream/main' into format-pull
qweeah Apr 7, 2024
bfe910f
fix lint
qweeah Apr 7, 2024
1b32187
fix lint
qweeah Apr 7, 2024
1e11eb4
fix lint
qweeah Apr 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
default:
metadataHandler = template.NewPushHandler(out, format)
}

return statusHandler, metadataHandler
}

Expand All @@ -70,6 +69,28 @@
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)

Check warning on line 79 in cmd/oras/internal/display/handler.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/internal/display/handler.go#L79

Added line #L79 was not covered by tests
} 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)

Check warning on line 43 in cmd/oras/internal/display/metadata/model/pull.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/internal/display/metadata/model/pull.go#L43

Added line #L43 was not covered by tests
}
} else {
path = filepath.Clean(path)

Check warning on line 46 in cmd/oras/internal/display/metadata/model/pull.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/internal/display/metadata/model/pull.go#L45-L46

Added lines #L45 - L46 were not covered by tests
}
if desc.Annotations[file.AnnotationUnpack] == "true" {
path += string(filepath.Separator)

Check warning on line 49 in cmd/oras/internal/display/metadata/model/pull.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/internal/display/metadata/model/pull.go#L49

Added line #L49 was not covered by tests
qweeah marked this conversation as resolved.
Show resolved Hide resolved
}
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

Check warning on line 91 in cmd/oras/internal/display/metadata/model/pull.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/internal/display/metadata/model/pull.go#L91

Added line #L91 was not covered by tests
}
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
Loading