Skip to content

Commit 9aca94c

Browse files
committed
Add prune command to remove layer references
Signed-off-by: Derek McGowan <[email protected]>
1 parent 643bb9b commit 9aca94c

2 files changed

Lines changed: 137 additions & 0 deletions

File tree

cmd/ctr/commands/content/content.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ var (
5353
listCommand,
5454
pushObjectCommand,
5555
setLabelsCommand,
56+
pruneCommand,
5657
},
5758
}
5859

cmd/ctr/commands/content/prune.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package content
18+
19+
import (
20+
"strings"
21+
"time"
22+
"unicode"
23+
24+
"github.com/containerd/containerd/cmd/ctr/commands"
25+
"github.com/containerd/containerd/content"
26+
"github.com/containerd/containerd/leases"
27+
"github.com/containerd/containerd/log"
28+
"github.com/sirupsen/logrus"
29+
"github.com/urfave/cli"
30+
)
31+
32+
const (
33+
layerPrefix = "containerd.io/gc.ref.content.l."
34+
contentPrefix = "containerd.io/gc.ref.content."
35+
)
36+
37+
var pruneFlags = []cli.Flag{
38+
cli.BoolFlag{
39+
Name: "async",
40+
Usage: "allow garbage collection to cleanup asynchronously",
41+
},
42+
cli.BoolFlag{
43+
Name: "dry",
44+
Usage: "just show updates without applying (enables debug logging)",
45+
},
46+
}
47+
48+
var pruneCommand = cli.Command{
49+
Name: "prune",
50+
Usage: "prunes content from the content store",
51+
Subcommands: cli.Commands{
52+
pruneReferencesCommand,
53+
},
54+
}
55+
56+
var pruneReferencesCommand = cli.Command{
57+
Name: "references",
58+
Usage: "prunes preference labels from the content store (layers only by default)",
59+
Flags: pruneFlags,
60+
Action: func(clicontext *cli.Context) error {
61+
client, ctx, cancel, err := commands.NewClient(clicontext)
62+
if err != nil {
63+
return err
64+
}
65+
defer cancel()
66+
67+
dryRun := clicontext.Bool("dry")
68+
if dryRun {
69+
log.G(ctx).Logger.SetLevel(logrus.DebugLevel)
70+
log.G(ctx).Debug("dry run, no changes will be applied")
71+
}
72+
73+
var deleteOpts []leases.DeleteOpt
74+
if !clicontext.Bool("async") {
75+
deleteOpts = append(deleteOpts, leases.SynchronousDelete)
76+
}
77+
78+
cs := client.ContentStore()
79+
if err := cs.Walk(ctx, func(info content.Info) error {
80+
var fields []string
81+
82+
for k := range info.Labels {
83+
if isLayerLabel(k) {
84+
log.G(ctx).WithField("dgst", info.Digest).WithField("label", k).Debug("Removing label")
85+
if dryRun {
86+
continue
87+
}
88+
fields = append(fields, "labels."+k)
89+
delete(info.Labels, k)
90+
}
91+
}
92+
93+
if len(fields) == 0 {
94+
return nil
95+
}
96+
97+
_, err := cs.Update(ctx, info, fields...)
98+
return err
99+
}); err != nil {
100+
return err
101+
}
102+
103+
ls := client.LeasesService()
104+
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(time.Hour))
105+
if err != nil {
106+
return err
107+
}
108+
return ls.Delete(ctx, l, deleteOpts...)
109+
},
110+
}
111+
112+
func isLayerLabel(key string) bool {
113+
if strings.HasPrefix(key, layerPrefix) {
114+
return true
115+
}
116+
if !strings.HasPrefix(key, contentPrefix) {
117+
return false
118+
}
119+
120+
// handle legacy labels which used content prefix and index (0 always for config)
121+
key = key[len(contentPrefix):]
122+
if isInteger(key) && key != "0" {
123+
return true
124+
}
125+
126+
return false
127+
}
128+
129+
func isInteger(key string) bool {
130+
for _, r := range key {
131+
if !unicode.IsDigit(r) {
132+
return false
133+
}
134+
}
135+
return true
136+
}

0 commit comments

Comments
 (0)