Skip to content

Commit 169808e

Browse files
committed
feat: implement prune flag
1 parent dbcd01c commit 169808e

File tree

2 files changed

+106
-3
lines changed

2 files changed

+106
-3
lines changed

cmd/crane/cmd/pull.go

Lines changed: 11 additions & 3 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 bool
33+
cachePath, format string
34+
annotateRef, prune bool
3535
)
3636

3737
cmd := &cobra.Command{
@@ -124,6 +124,13 @@ 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+
127134
default:
128135
return fmt.Errorf("unexpected --format: %q (valid values are: tarball, legacy, and oci)", format)
129136
}
@@ -133,6 +140,7 @@ func NewCmdPull(options *[]crane.Option) *cobra.Command {
133140
cmd.Flags().StringVarP(&cachePath, "cache_path", "c", "", "Path to cache image layers")
134141
cmd.Flags().StringVar(&format, "format", "tarball", fmt.Sprintf("Format in which to save images (%q, %q, or %q)", "tarball", "legacy", "oci"))
135142
cmd.Flags().BoolVar(&annotateRef, "annotate-ref", false, "Preserves image reference used to pull as an annotation when used with --format=oci")
136-
143+
cmd.Flags().BoolVar(&prune, "prune", false, "Removes orphan blobs from the oci-layout after pull")
144+
cmd.Flags().MarkHidden("prune")
137145
return cmd
138146
}

pkg/v1/layout/write.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"fmt"
2222
"io"
23+
"io/fs"
2324
"os"
2425
"path/filepath"
2526

@@ -37,6 +38,100 @@ var layoutFile = `{
3738
"imageLayoutVersion": "1.0.0"
3839
}`
3940

41+
// GarbageCollect removes unreferenced blobs from the oci-layout
42+
func (l Path) GarbageCollect() error {
43+
idx, err := l.ImageIndex()
44+
if err != nil {
45+
return err
46+
}
47+
blobsToKeep := map[string]bool{}
48+
if err := l.garbageCollectImageIndex(idx, blobsToKeep); err != nil {
49+
return err
50+
}
51+
blobsDir := l.path("blobs")
52+
53+
if err := filepath.WalkDir(blobsDir, func(path string, d fs.DirEntry, err error) error {
54+
if d.IsDir() {
55+
return nil
56+
}
57+
58+
rel, err := filepath.Rel(blobsDir, path)
59+
if err != nil {
60+
return err
61+
}
62+
if ok := blobsToKeep[rel]; !ok {
63+
if err := os.Remove(path); err != nil {
64+
return err
65+
}
66+
}
67+
return nil
68+
}); err != nil {
69+
return err
70+
}
71+
72+
return nil
73+
}
74+
75+
func (l Path) garbageCollectImageIndex(index v1.ImageIndex, blobsToKeep map[string]bool) error {
76+
idxm, err := index.IndexManifest()
77+
if err != nil {
78+
return err
79+
}
80+
if h, err := index.Digest(); err != nil {
81+
return err
82+
} else {
83+
blobsToKeep[fmt.Sprintf("%s/%s", h.Algorithm, h.Hex)] = true
84+
}
85+
for _, descriptor := range idxm.Manifests {
86+
if descriptor.MediaType.IsImage() {
87+
img, err := index.Image(descriptor.Digest)
88+
if err != nil {
89+
return err
90+
}
91+
if err := l.garbageCollectImage(img, blobsToKeep); err != nil {
92+
return err
93+
}
94+
} else if descriptor.MediaType.IsIndex() {
95+
idx, err := index.ImageIndex(descriptor.Digest)
96+
if err != nil {
97+
return err
98+
}
99+
if err := l.garbageCollectImageIndex(idx, blobsToKeep); err != nil {
100+
return err
101+
}
102+
}
103+
}
104+
return nil
105+
}
106+
107+
func (l Path) garbageCollectImage(image v1.Image, blobsToKeep map[string]bool) error {
108+
109+
if h, err := image.Digest(); err != nil {
110+
return err
111+
} else {
112+
blobsToKeep[fmt.Sprintf("%s/%s", h.Algorithm, h.Hex)] = true
113+
}
114+
115+
if h, err := image.ConfigName(); err != nil {
116+
return err
117+
} else {
118+
blobsToKeep[fmt.Sprintf("%s/%s", h.Algorithm, h.Hex)] = true
119+
}
120+
121+
ls, err := image.Layers()
122+
if err != nil {
123+
return err
124+
}
125+
for _, l := range ls {
126+
if h, err := l.Digest(); err != nil {
127+
return err
128+
} else {
129+
blobsToKeep[fmt.Sprintf("%s/%s", h.Algorithm, h.Hex)] = true
130+
}
131+
}
132+
return nil
133+
}
134+
40135
// AppendImage writes a v1.Image to the Path and updates
41136
// the index.json to reference it.
42137
func (l Path) AppendImage(img v1.Image, options ...Option) error {

0 commit comments

Comments
 (0)