140 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    Copyright The containerd 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 content
 | |
| 
 | |
| import (
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 	"unicode"
 | |
| 
 | |
| 	"github.com/containerd/containerd/cmd/ctr/commands"
 | |
| 	"github.com/containerd/containerd/content"
 | |
| 	"github.com/containerd/containerd/leases"
 | |
| 	"github.com/containerd/containerd/log"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| 	"github.com/urfave/cli"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	layerPrefix   = "containerd.io/gc.ref.content.l."
 | |
| 	contentPrefix = "containerd.io/gc.ref.content."
 | |
| )
 | |
| 
 | |
| var pruneFlags = []cli.Flag{
 | |
| 	cli.BoolFlag{
 | |
| 		Name:  "async",
 | |
| 		Usage: "Allow garbage collection to cleanup asynchronously",
 | |
| 	},
 | |
| 	cli.BoolFlag{
 | |
| 		Name:  "dry",
 | |
| 		Usage: "Just show updates without applying (enables debug logging)",
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var pruneCommand = cli.Command{
 | |
| 	Name:  "prune",
 | |
| 	Usage: "Prunes content from the content store",
 | |
| 	Subcommands: cli.Commands{
 | |
| 		pruneReferencesCommand,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var pruneReferencesCommand = cli.Command{
 | |
| 	Name:  "references",
 | |
| 	Usage: "Prunes preference labels from the content store (layers only by default)",
 | |
| 	Flags: pruneFlags,
 | |
| 	Action: func(clicontext *cli.Context) error {
 | |
| 		client, ctx, cancel, err := commands.NewClient(clicontext)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		defer cancel()
 | |
| 
 | |
| 		dryRun := clicontext.Bool("dry")
 | |
| 		if dryRun {
 | |
| 			log.G(ctx).Logger.SetLevel(logrus.DebugLevel)
 | |
| 			log.G(ctx).Debug("dry run, no changes will be applied")
 | |
| 		}
 | |
| 
 | |
| 		var deleteOpts []leases.DeleteOpt
 | |
| 		if !clicontext.Bool("async") {
 | |
| 			deleteOpts = append(deleteOpts, leases.SynchronousDelete)
 | |
| 		}
 | |
| 
 | |
| 		cs := client.ContentStore()
 | |
| 		if err := cs.Walk(ctx, func(info content.Info) error {
 | |
| 			var fields []string
 | |
| 
 | |
| 			for k := range info.Labels {
 | |
| 				if isLayerLabel(k) {
 | |
| 					log.G(ctx).WithFields(log.Fields{
 | |
| 						"digest": info.Digest,
 | |
| 						"label":  k,
 | |
| 					}).Debug("Removing label")
 | |
| 					if dryRun {
 | |
| 						continue
 | |
| 					}
 | |
| 					fields = append(fields, "labels."+k)
 | |
| 					delete(info.Labels, k)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if len(fields) == 0 {
 | |
| 				return nil
 | |
| 			}
 | |
| 
 | |
| 			_, err := cs.Update(ctx, info, fields...)
 | |
| 			return err
 | |
| 		}); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		ls := client.LeasesService()
 | |
| 		l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(time.Hour))
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		return ls.Delete(ctx, l, deleteOpts...)
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func isLayerLabel(key string) bool {
 | |
| 	if strings.HasPrefix(key, layerPrefix) {
 | |
| 		return true
 | |
| 	}
 | |
| 	if !strings.HasPrefix(key, contentPrefix) {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// handle legacy labels which used content prefix and index (0 always for config)
 | |
| 	key = key[len(contentPrefix):]
 | |
| 	if isInteger(key) && key != "0" {
 | |
| 		return true
 | |
| 	}
 | |
| 
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func isInteger(key string) bool {
 | |
| 	for _, r := range key {
 | |
| 		if !unicode.IsDigit(r) {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | 
