Skip to content

Commit d5a14e9

Browse files
authored
feat: add oras restore command (#1792)
Signed-off-by: Lixia (Sylvia) Lei <[email protected]>
1 parent edace6e commit d5a14e9

File tree

22 files changed

+1941
-41
lines changed

22 files changed

+1941
-41
lines changed

cmd/oras/internal/display/handler.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,14 @@ func NewBackupHandler(printer *output.Printer, tty *os.File, repo string, fetche
238238
return status.NewTextBackupHandler(printer, fetcher), text.NewBackupHandler(repo, printer)
239239
}
240240

241+
// NewRestoreHandler returns restore handlers.
242+
func NewRestoreHandler(printer *output.Printer, tty *os.File, fetcher fetcher.Fetcher, dryRun bool) (status.RestoreHandler, metadata.RestoreHandler) {
243+
if tty != nil {
244+
return status.NewTTYRestoreHandler(tty, fetcher), text.NewRestoreHandler(printer, dryRun)
245+
}
246+
return status.NewTextRestoreHandler(printer, fetcher), text.NewRestoreHandler(printer, dryRun)
247+
}
248+
241249
// NewBlobPushHandler returns blob push handlers.
242250
func NewBlobPushHandler(printer *output.Printer, outputDescriptor bool, pretty bool, desc ocispec.Descriptor, tty *os.File) (status.BlobPushHandler, metadata.BlobPushHandler) {
243251
if outputDescriptor {

cmd/oras/internal/display/handler_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,28 @@ func TestNewBackupHandler(t *testing.T) {
161161
}
162162
})
163163
}
164+
165+
func TestNewRestoreHandler(t *testing.T) {
166+
printer := output.NewPrinter(os.Stdout, os.Stderr)
167+
mockFetcher := testutils.NewMockFetcher()
168+
169+
t.Run("with TTY", func(t *testing.T) {
170+
statusHandler, metadataHandler := NewRestoreHandler(printer, os.Stdout, mockFetcher.Fetcher, false)
171+
if _, ok := statusHandler.(*status.TTYRestoreHandler); !ok {
172+
t.Errorf("expected *status.TTYRestoreHandler actual %v", reflect.TypeOf(statusHandler))
173+
}
174+
if _, ok := metadataHandler.(*text.RestoreHandler); !ok {
175+
t.Errorf("expected *text.RestoreHandler actual %v", reflect.TypeOf(metadataHandler))
176+
}
177+
})
178+
179+
t.Run("without TTY", func(t *testing.T) {
180+
statusHandler, metadataHandler := NewRestoreHandler(printer, nil, mockFetcher.Fetcher, false)
181+
if _, ok := statusHandler.(*status.TextRestoreHandler); !ok {
182+
t.Errorf("expected *status.TextRestoreHandler actual %v", reflect.TypeOf(statusHandler))
183+
}
184+
if _, ok := metadataHandler.(*text.RestoreHandler); !ok {
185+
t.Errorf("expected *text.RestoreHandler actual %v", reflect.TypeOf(metadataHandler))
186+
}
187+
})
188+
}

cmd/oras/internal/display/metadata/interface.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ type BackupHandler interface {
119119
OnBackupCompleted(tagsCount int, path string, duration time.Duration) error
120120
}
121121

122+
// RestoreHandler handles metadata output for restore events.
123+
type RestoreHandler interface {
124+
Renderer
125+
126+
OnTarLoaded(path string, size int64) error
127+
OnTagsFound(tags []string) error
128+
OnArtifactPushed(tag string, referrerCount int) error
129+
OnRestoreCompleted(tagsCount int, repo string, duration time.Duration) error
130+
}
131+
122132
// BlobPushHandler handles metadata output for blob push events.
123133
type BlobPushHandler interface {
124134
Renderer

cmd/oras/internal/display/metadata/text/backup.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,28 @@ func (bh *BackupHandler) OnTarExporting(path string) error {
5656
// OnArtifactPulled implements metadata.BackupHandler.
5757
func (bh *BackupHandler) OnArtifactPulled(tag string, referrerCount int) error {
5858
// represent duration in a human-readable format
59-
return bh.printer.Printf("Pulled tag %s and %d referrer(s)\n", tag, referrerCount)
59+
return bh.printer.Printf("Pulled tag %s with %d referrer(s)\n", tag, referrerCount)
6060
}
6161

6262
// OnTagsFound implements metadata.BackupHandler.
6363
func (bh *BackupHandler) OnTagsFound(tags []string) error {
6464
if len(tags) == 0 {
6565
return bh.printer.Printf("No tags found in %s\n", bh.repo)
6666
}
67-
return bh.printer.Printf("Found %d tag(s) in %s: %s\n", len(tags), bh.repo, strings.Join(tags, ", "))
67+
if len(tags) <= 5 {
68+
// print small number of tags in one line
69+
return bh.printer.Printf("Found %d tag(s) in %s: %s\n", len(tags), bh.repo, strings.Join(tags, ", "))
70+
}
71+
// print large number of tags line by line
72+
if err := bh.printer.Printf("Found %d tag(s) in %s:\n", len(tags), bh.repo); err != nil {
73+
return err
74+
}
75+
for _, tag := range tags {
76+
if err := bh.printer.Println(tag); err != nil {
77+
return err
78+
}
79+
}
80+
return nil
6881
}
6982

7083
// Render implements metadata.BackupHandler.

cmd/oras/internal/display/metadata/text/backup_test.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func TestBackupHandler_OnArtifactPulled(t *testing.T) {
112112
name: "good path",
113113
out: &bytes.Buffer{},
114114
wantErr: false,
115-
want: fmt.Sprintf("Pulled tag %s and %d referrer(s)\n", tag, referrerCount),
115+
want: fmt.Sprintf("Pulled tag %s with %d referrer(s)\n", tag, referrerCount),
116116
},
117117
}
118118
for _, tt := range tests {
@@ -142,12 +142,26 @@ func TestBackupHandler_OnTagsFound(t *testing.T) {
142142
want string
143143
}{
144144
{
145-
name: "good path with tags",
145+
name: "good path with few tags",
146146
tags: []string{"t1", "t2"},
147147
out: &bytes.Buffer{},
148148
wantErr: false,
149149
want: fmt.Sprintf("Found 2 tag(s) in %s: t1, t2\n", repo),
150150
},
151+
{
152+
name: "good path with exactly 5 tags",
153+
tags: []string{"t1", "t2", "t3", "t4", "t5"},
154+
out: &bytes.Buffer{},
155+
wantErr: false,
156+
want: fmt.Sprintf("Found 5 tag(s) in %s: t1, t2, t3, t4, t5\n", repo),
157+
},
158+
{
159+
name: "good path with more than 5 tags",
160+
tags: []string{"t1", "t2", "t3", "t4", "t5", "t6"},
161+
out: &bytes.Buffer{},
162+
wantErr: false,
163+
want: fmt.Sprintf("Found 6 tag(s) in %s:\nt1\nt2\nt3\nt4\nt5\nt6\n", repo),
164+
},
151165
{
152166
name: "good path with no tags",
153167
tags: []string{},
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
Copyright The ORAS Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
package text
17+
18+
import (
19+
"strings"
20+
"time"
21+
22+
"oras.land/oras/cmd/oras/internal/display/status/progress/humanize"
23+
"oras.land/oras/cmd/oras/internal/output"
24+
)
25+
26+
// RestoreHandler handles text metadata output for restore command.
27+
type RestoreHandler struct {
28+
printer *output.Printer
29+
dryRun bool
30+
}
31+
32+
// NewRestoreHandler creates a new RestoreHandler.
33+
func NewRestoreHandler(printer *output.Printer, dryRun bool) *RestoreHandler {
34+
return &RestoreHandler{
35+
printer: printer,
36+
dryRun: dryRun,
37+
}
38+
}
39+
40+
// OnTarLoaded implements metadata.RestoreHandler.
41+
func (rh *RestoreHandler) OnTarLoaded(path string, size int64) error {
42+
return rh.printer.Printf("Loaded backup archive: %s (%s)\n", path, humanize.ToBytes(size))
43+
}
44+
45+
// OnTagsFound implements metadata.RestoreHandler.
46+
func (rh *RestoreHandler) OnTagsFound(tags []string) error {
47+
if len(tags) == 0 {
48+
return rh.printer.Printf("No tags found in the backup\n")
49+
}
50+
if len(tags) <= 5 {
51+
// print small number of tags in one line
52+
return rh.printer.Printf("Found %d tag(s) in the backup: %s\n", len(tags), strings.Join(tags, ", "))
53+
}
54+
// print large number of tags line by line
55+
if err := rh.printer.Printf("Found %d tag(s) in the backup:\n", len(tags)); err != nil {
56+
return err
57+
}
58+
for _, tag := range tags {
59+
if err := rh.printer.Println(tag); err != nil {
60+
return err
61+
}
62+
}
63+
return nil
64+
}
65+
66+
// OnArtifactPushed implements metadata.RestoreHandler.
67+
func (rh *RestoreHandler) OnArtifactPushed(tag string, referrerCount int) error {
68+
if rh.dryRun {
69+
return rh.printer.Printf("Dry run: would push tag %s with %d referrer(s)\n", tag, referrerCount)
70+
}
71+
return rh.printer.Printf("Pushed tag %s with %d referrer(s)\n", tag, referrerCount)
72+
}
73+
74+
// OnRestoreCompleted implements metadata.RestoreHandler.
75+
func (rh *RestoreHandler) OnRestoreCompleted(tagsCount int, repo string, duration time.Duration) error {
76+
if rh.dryRun {
77+
return rh.printer.Printf("Dry run complete: %d tag(s) would be restored to %q (no data pushed)\n", tagsCount, repo)
78+
}
79+
return rh.printer.Printf("Successfully restored %d tag(s) to %q in %s\n", tagsCount, repo, humanize.FormatDuration(duration))
80+
}
81+
82+
// Render implements metadata.RestoreHandler.
83+
func (rh *RestoreHandler) Render() error {
84+
return nil
85+
}

0 commit comments

Comments
 (0)