Skip to content

Commit 72a5470

Browse files
committed
address changes
1 parent 169808e commit 72a5470

19 files changed

+342
-110
lines changed

cmd/crane/cmd/gc.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2018 Google LLC All Rights Reserved.
2+
//
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+
package cmd
16+
17+
import (
18+
"fmt"
19+
"os"
20+
21+
"github.com/google/go-containerregistry/pkg/v1/layout"
22+
"github.com/spf13/cobra"
23+
)
24+
25+
// NewCmdGc creates a new cobra.Command for the pull subcommand.
26+
func NewCmdGc() *cobra.Command {
27+
cmd := &cobra.Command{
28+
Use: "gc OCI-LAYOUT",
29+
Short: "Garbage collect unreferenced blobs in a local oci-layout",
30+
Args: cobra.ExactArgs(1),
31+
Hidden: true, // TODO: promote to public once theres some milage
32+
RunE: func(_ *cobra.Command, args []string) error {
33+
path := args[0]
34+
35+
p, err := layout.FromPath(path)
36+
37+
if err != nil {
38+
return err
39+
}
40+
41+
blobs, err := p.GarbageCollect()
42+
if err != nil {
43+
return err
44+
}
45+
46+
for _, blob := range blobs {
47+
if err := p.RemoveBlob(blob); err != nil {
48+
return err
49+
}
50+
fmt.Fprintf(os.Stderr, "garbage collecting: %s\n", blob.String())
51+
}
52+
53+
return nil
54+
},
55+
}
56+
57+
return cmd
58+
}

cmd/crane/cmd/pull.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import (
3030
// NewCmdPull creates a new cobra.Command for the pull subcommand.
3131
func NewCmdPull(options *[]crane.Option) *cobra.Command {
3232
var (
33-
cachePath, format string
34-
annotateRef, prune bool
33+
cachePath, format string
34+
annotateRef bool
3535
)
3636

3737
cmd := &cobra.Command{
@@ -124,13 +124,6 @@ func NewCmdPull(options *[]crane.Option) *cobra.Command {
124124
return err
125125
}
126126
}
127-
128-
if prune {
129-
if err := p.GarbageCollect(); err != nil {
130-
return err
131-
}
132-
}
133-
134127
default:
135128
return fmt.Errorf("unexpected --format: %q (valid values are: tarball, legacy, and oci)", format)
136129
}
@@ -140,7 +133,6 @@ func NewCmdPull(options *[]crane.Option) *cobra.Command {
140133
cmd.Flags().StringVarP(&cachePath, "cache_path", "c", "", "Path to cache image layers")
141134
cmd.Flags().StringVar(&format, "format", "tarball", fmt.Sprintf("Format in which to save images (%q, %q, or %q)", "tarball", "legacy", "oci"))
142135
cmd.Flags().BoolVar(&annotateRef, "annotate-ref", false, "Preserves image reference used to pull as an annotation when used with --format=oci")
143-
cmd.Flags().BoolVar(&prune, "prune", false, "Removes orphan blobs from the oci-layout after pull")
144-
cmd.Flags().MarkHidden("prune")
136+
145137
return cmd
146138
}

cmd/crane/cmd/root.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ func New(use, short string, options []crane.Option) *cobra.Command {
129129
NewCmdTag(&options),
130130
NewCmdValidate(&options),
131131
NewCmdVersion(),
132-
newCmdRegistry(),
132+
NewCmdRegistry(),
133+
NewCmdGc(),
133134
)
134135

135136
root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable debug logs")

cmd/crane/cmd/serve.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"github.com/google/go-containerregistry/pkg/registry"
2929
)
3030

31-
func newCmdRegistry() *cobra.Command {
31+
func NewCmdRegistry() *cobra.Command {
3232
cmd := &cobra.Command{
3333
Use: "registry",
3434
}

pkg/v1/layout/gc.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// This is an EXPERIMENTAL package, and may change in arbitrary ways without notice.
2+
package layout
3+
4+
import (
5+
"fmt"
6+
"io/fs"
7+
"path/filepath"
8+
"strings"
9+
10+
v1 "github.com/google/go-containerregistry/pkg/v1"
11+
)
12+
13+
// GarbageCollect removes unreferenced blobs from the oci-layout
14+
//
15+
// This is an experimental api, and not subject to any stability guarantees
16+
// We may abandon it at any time, without prior notice.
17+
// Deprecated: Use it at your own risk!
18+
func (l Path) GarbageCollect() ([]v1.Hash, error) {
19+
idx, err := l.ImageIndex()
20+
if err != nil {
21+
return nil, err
22+
}
23+
blobsToKeep := map[string]bool{}
24+
if err := l.garbageCollectImageIndex(idx, blobsToKeep); err != nil {
25+
return nil, err
26+
}
27+
blobsDir := l.path("blobs")
28+
removedBlobs := []v1.Hash{}
29+
30+
err = filepath.WalkDir(blobsDir, func(path string, d fs.DirEntry, err error) error {
31+
if err != nil {
32+
return err
33+
}
34+
35+
if d.IsDir() {
36+
return nil
37+
}
38+
39+
rel, err := filepath.Rel(blobsDir, path)
40+
if err != nil {
41+
return err
42+
}
43+
hashString := strings.Replace(rel, "/", ":", 1)
44+
if present := blobsToKeep[hashString]; !present {
45+
h, err := v1.NewHash(hashString)
46+
if err != nil {
47+
return err
48+
}
49+
removedBlobs = append(removedBlobs, h)
50+
}
51+
return nil
52+
})
53+
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
return removedBlobs, nil
59+
}
60+
61+
func (l Path) garbageCollectImageIndex(index v1.ImageIndex, blobsToKeep map[string]bool) error {
62+
idxm, err := index.IndexManifest()
63+
if err != nil {
64+
return err
65+
}
66+
67+
h, err := index.Digest()
68+
if err != nil {
69+
return err
70+
}
71+
72+
blobsToKeep[h.String()] = true
73+
74+
for _, descriptor := range idxm.Manifests {
75+
if descriptor.MediaType.IsImage() {
76+
img, err := index.Image(descriptor.Digest)
77+
if err != nil {
78+
return err
79+
}
80+
if err := l.garbageCollectImage(img, blobsToKeep); err != nil {
81+
return err
82+
}
83+
} else if descriptor.MediaType.IsIndex() {
84+
idx, err := index.ImageIndex(descriptor.Digest)
85+
if err != nil {
86+
return err
87+
}
88+
if err := l.garbageCollectImageIndex(idx, blobsToKeep); err != nil {
89+
return err
90+
}
91+
} else {
92+
return fmt.Errorf("gc: unknown media type: %s", descriptor.MediaType)
93+
}
94+
}
95+
return nil
96+
}
97+
98+
func (l Path) garbageCollectImage(image v1.Image, blobsToKeep map[string]bool) error {
99+
h, err := image.Digest()
100+
if err != nil {
101+
return err
102+
}
103+
blobsToKeep[h.String()] = true
104+
105+
h, err = image.ConfigName()
106+
if err != nil {
107+
return err
108+
}
109+
blobsToKeep[h.String()] = true
110+
111+
ls, err := image.Layers()
112+
if err != nil {
113+
return err
114+
}
115+
for _, l := range ls {
116+
h, err := l.Digest()
117+
if err != nil {
118+
return err
119+
}
120+
blobsToKeep[h.String()] = true
121+
}
122+
return nil
123+
}

pkg/v1/layout/gc_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package layout
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
)
7+
8+
var (
9+
gcIndexPath = filepath.Join("testdata", "test_gc_index")
10+
gcIndexBlobHash = "sha256:492b89b9dd3cda4596f94916d17f6901455fb8bd7f4c5a2a90df8d39c90f48a0"
11+
gcUnknownMediaTypePath = filepath.Join("testdata", "test_gc_image_unknown_mediatype")
12+
gcUnknownMediaTypeErr = "gc: unknown media type: application/vnd.oci.descriptor.v1+json"
13+
gcTestOneImagePath = filepath.Join("testdata", "test_index_one_image")
14+
gcTestIndexMediaTypePath = filepath.Join("testdata", "test_index_media_type")
15+
)
16+
17+
func TestGcIndex(t *testing.T) {
18+
lp, err := FromPath(gcIndexPath)
19+
if err != nil {
20+
t.Fatalf("FromPath() = %v", err)
21+
}
22+
23+
removed, err := lp.GarbageCollect()
24+
if err != nil {
25+
t.Fatalf("GarbageCollect() = %v", err)
26+
}
27+
28+
if len(removed) != 1 {
29+
t.Fatalf("expected to have only one gc-able blob")
30+
}
31+
if removed[0].String() != gcIndexBlobHash {
32+
t.Fatalf("wrong blob is gc-ed: expected '%s', got '%s'", gcIndexBlobHash, removed[0].String())
33+
}
34+
}
35+
36+
func TestGcOneImage(t *testing.T) {
37+
lp, err := FromPath(gcTestOneImagePath)
38+
if err != nil {
39+
t.Fatalf("FromPath() = %v", err)
40+
}
41+
42+
removed, err := lp.GarbageCollect()
43+
if err != nil {
44+
t.Fatalf("GarbageCollect() = %v", err)
45+
}
46+
47+
if len(removed) != 0 {
48+
t.Fatalf("expected to have to gc-able blobs")
49+
}
50+
}
51+
52+
func TestGcIndexMediaType(t *testing.T) {
53+
lp, err := FromPath(gcTestIndexMediaTypePath)
54+
if err != nil {
55+
t.Fatalf("FromPath() = %v", err)
56+
}
57+
58+
removed, err := lp.GarbageCollect()
59+
if err != nil {
60+
t.Fatalf("GarbageCollect() = %v", err)
61+
}
62+
63+
if len(removed) != 0 {
64+
t.Fatalf("expected to have to gc-able blobs")
65+
}
66+
}
67+
68+
func TestGcUnknownMediaType(t *testing.T) {
69+
lp, err := FromPath(gcUnknownMediaTypePath)
70+
if err != nil {
71+
t.Fatalf("FromPath() = %v", err)
72+
}
73+
74+
_, err = lp.GarbageCollect()
75+
if err == nil {
76+
t.Fatalf("expected GarbageCollect to return err but did not")
77+
}
78+
79+
if err.Error() != gcUnknownMediaTypeErr {
80+
t.Fatalf("expected error '%s', got '%s'", gcUnknownMediaTypeErr, err.Error())
81+
}
82+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"schemaVersion": 2,
3+
"manifests": [
4+
{
5+
"mediaType": "application/vnd.oci.descriptor.v1+json",
6+
"size": 423,
7+
"digest": "sha256:32589985702551b6c56033bb3334432a0a513bf9d6aceda0f67c42b003850720"
8+
}
9+
]
10+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"imageLayoutVersion": "1.0.0"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"schemaVersion": 2,
3+
"manifests": [
4+
{
5+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
6+
"size": 423,
7+
"digest": "sha256:eebff607b1628d67459b0596643fc07de70d702eccf030f0bc7bb6fc2b278650",
8+
"annotations": {
9+
"org.opencontainers.image.ref.name": "1"
10+
}
11+
}
12+
]
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"schemaVersion": 2,
3+
"manifests": [
4+
{
5+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
6+
"size": 423,
7+
"digest": "sha256:eebff607b1628d67459b0596643fc07de70d702eccf030f0bc7bb6fc2b278650",
8+
"annotations": {
9+
"org.opencontainers.image.ref.name": "4"
10+
}
11+
}
12+
]
13+
}

0 commit comments

Comments
 (0)