Skip to content

Commit

Permalink
Fix duplicate image entries in k8s.io namespaces
Browse files Browse the repository at this point in the history
Signed-off-by: fengwei0328 <[email protected]>
  • Loading branch information
fengwei0328 committed Dec 10, 2024
1 parent 1f81225 commit 8a3158e
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 47 deletions.
18 changes: 9 additions & 9 deletions cmd/nerdctl/image/image_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,42 +92,42 @@ func historyAction(cmd *cobra.Command, args []string) error {

walker := &imagewalker.ImageWalker{
Client: client,
OnFound: func(ctx context.Context, found imagewalker.Found) error {
OnFound: func(ctx context.Context, found imagewalker.Found) (error, bool) {
if found.MatchCount > 1 {
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req), false
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
img := containerd.NewImage(client, found.Image)
imageConfig, _, err := imgutil.ReadImageConfig(ctx, img)
if err != nil {
return fmt.Errorf("failed to ReadImageConfig: %w", err)
return fmt.Errorf("failed to ReadImageConfig: %w", err), false
}
configHistories := imageConfig.History
layerCounter := 0
diffIDs, err := img.RootFS(ctx)
if err != nil {
return fmt.Errorf("failed to get diffIDS: %w", err)
return fmt.Errorf("failed to get diffIDS: %w", err), false
}
var historys []historyPrintable
for _, h := range configHistories {
var size int64
var snapshotName string
if !h.EmptyLayer {
if len(diffIDs) <= layerCounter {
return fmt.Errorf("too many non-empty layers in History section")
return fmt.Errorf("too many non-empty layers in History section"), false
}
diffIDs := diffIDs[0 : layerCounter+1]
chainID := identity.ChainID(diffIDs).String()

s := client.SnapshotService(globalOptions.Snapshotter)
stat, err := s.Stat(ctx, chainID)
if err != nil {
return fmt.Errorf("failed to get stat: %w", err)
return fmt.Errorf("failed to get stat: %w", err), false
}
use, err := s.Usage(ctx, chainID)
if err != nil {
return fmt.Errorf("failed to get usage: %w", err)
return fmt.Errorf("failed to get usage: %w", err), false
}
size = use.Size
snapshotName = stat.Name
Expand All @@ -147,9 +147,9 @@ func historyAction(cmd *cobra.Command, args []string) error {
}
err = printHistory(cmd, historys)
if err != nil {
return fmt.Errorf("failed printHistory: %w", err)
return fmt.Errorf("failed printHistory: %w", err), false
}
return nil
return nil, false
},
}

Expand Down
8 changes: 3 additions & 5 deletions cmd/nerdctl/image/image_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Properties:
})
imagesCommand.Flags().Bool("digests", false, "Show digests (compatible with Docker, unlike ID)")
imagesCommand.Flags().Bool("names", false, "Show image names")
imagesCommand.Flags().BoolP("all", "a", true, "(unimplemented yet, always true)")
imagesCommand.Flags().BoolP("all", "a", false, "Show all images repo, include imageID, repoTAg, repoDigest")

return imagesCommand
}
Expand Down Expand Up @@ -110,6 +110,7 @@ func processImageListOptions(cmd *cobra.Command, args []string) (*types.ImageLis
if err != nil {
return nil, err
}
all, err := cmd.Flags().GetBool("all")

Check failure on line 113 in cmd/nerdctl/image/image_list.go

View workflow job for this annotation

GitHub Actions / go | freebsd |

ineffectual assignment to err (ineffassign)
return &types.ImageListOptions{
GOptions: globalOptions,
Quiet: quiet,
Expand All @@ -119,7 +120,7 @@ func processImageListOptions(cmd *cobra.Command, args []string) (*types.ImageLis
NameAndRefFilter: filters,
Digests: digests,
Names: names,
All: true,
All: all,
Stdout: cmd.OutOrStdout(),
}, nil

Expand All @@ -130,9 +131,6 @@ func imagesAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if !options.All {
options.All = true
}

client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cmd/nerdctl/inspect/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ func inspectAction(cmd *cobra.Command, args []string) error {

imagewalker := &imagewalker.ImageWalker{
Client: client,
OnFound: func(ctx context.Context, found imagewalker.Found) error {
return nil
OnFound: func(ctx context.Context, found imagewalker.Found) (error, bool) {
return nil, false
},
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/containerd/accelerated-container-image v1.2.3
github.com/containerd/cgroups/v3 v3.0.4
github.com/containerd/console v1.0.4
github.com/containerd/containerd v1.7.23
github.com/containerd/containerd/api v1.8.0
github.com/containerd/containerd/v2 v2.0.0
github.com/containerd/continuity v0.4.5
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ github.com/containerd/cgroups/v3 v3.0.4 h1:2fs7l3P0Qxb1nKWuJNFiwhp2CqiKzho71DQkD
github.com/containerd/cgroups/v3 v3.0.4/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ=
github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw=
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=
github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc=
github.com/containerd/containerd/v2 v2.0.0 h1:qLDdFaAykQrIyLiqwQrNLLz95wiC36bAZVwioUwqShM=
Expand Down
18 changes: 17 additions & 1 deletion pkg/cmd/image/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"text/template"
"time"

"github.com/distribution/reference"
"github.com/docker/go-units"
"github.com/opencontainers/image-spec/identity"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand Down Expand Up @@ -128,6 +129,21 @@ type imagePrintable struct {

func printImages(ctx context.Context, client *containerd.Client, imageList []images.Image, options *types.ImageListOptions) error {
w := options.Stdout
var ImageList []images.Image
if options.GOptions.Namespace != "k8s.io" || options.All {
ImageList = imageList
} else {
for _, ima := range imageList {
parsed, err := reference.ParseAnyReference(ima.Name)
if err != nil {
continue
}
if _, ok := parsed.(reference.Tagged); !ok {
continue
}
ImageList = append(ImageList, ima)
}
}
digestsFlag := options.Digests
if options.Format == "wide" {
digestsFlag = true
Expand Down Expand Up @@ -174,7 +190,7 @@ func printImages(ctx context.Context, client *containerd.Client, imageList []ima
snapshotter: containerdutil.SnapshotService(client, options.GOptions.Snapshotter),
}

for _, img := range imageList {
for _, img := range ImageList {
if err := printer.printImage(ctx, img); err != nil {
log.G(ctx).Warn(err)
}
Expand Down
20 changes: 10 additions & 10 deletions pkg/cmd/image/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,37 +64,37 @@ func Remove(ctx context.Context, client *containerd.Client, args []string, optio

walker := &imagewalker.ImageWalker{
Client: client,
OnFound: func(ctx context.Context, found imagewalker.Found) error {
OnFound: func(ctx context.Context, found imagewalker.Found) (error, bool) {
if found.NameMatchIndex == -1 {
// if found multiple images, return error unless in force-mode and
// there is only 1 unique image.
if found.MatchCount > 1 && !(options.Force && found.UniqueImages == 1) {
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req), false
}
} else if found.NameMatchIndex != found.MatchIndex {
// when there is an image with a name matching the argument but the argument is a digest short id,
// the deletion process is not performed.
return nil
return nil, false
}

if cid, ok := runningImages[found.Image.Name]; ok {
if options.Force {
if err = is.Delete(ctx, found.Image.Name); err != nil {
return err
return err, false
}
fmt.Fprintf(options.Stdout, "Untagged: %s\n", found.Image.Name)
fmt.Fprintf(options.Stdout, "Untagged: %s\n", found.Image.Target.Digest.String())

found.Image.Name = ":"
if _, err = is.Create(ctx, found.Image); err != nil {
return err
return err, false
}
return nil
return nil, false
}
return fmt.Errorf("conflict: unable to delete %s (cannot be forced) - image is being used by running container %s", found.Req, cid)
return fmt.Errorf("conflict: unable to delete %s (cannot be forced) - image is being used by running container %s", found.Req, cid), false
}
if cid, ok := usedImages[found.Image.Name]; ok && !options.Force {
return fmt.Errorf("conflict: unable to delete %s (must be forced) - image is being used by stopped container %s", found.Req, cid)
return fmt.Errorf("conflict: unable to delete %s (must be forced) - image is being used by stopped container %s", found.Req, cid), false
}
// digests is used only for emulating human-readable output of `docker rmi`
digests, err := found.Image.RootFS(ctx, cs, platforms.DefaultStrict())
Expand All @@ -103,13 +103,13 @@ func Remove(ctx context.Context, client *containerd.Client, args []string, optio
}

if err := is.Delete(ctx, found.Image.Name, delOpts...); err != nil {
return err
return err, false
}
fmt.Fprintf(options.Stdout, "Untagged: %s@%s\n", found.Image.Name, found.Image.Target.Digest)
for _, digest := range digests {
fmt.Fprintf(options.Stdout, "Deleted: %s\n", digest)
}
return nil
return nil, true
},
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/cmd/image/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ func Save(ctx context.Context, client *containerd.Client, images []string, optio
savedImages := make(map[string]struct{})
walker := &imagewalker.ImageWalker{
Client: client,
OnFound: func(ctx context.Context, found imagewalker.Found) error {
OnFound: func(ctx context.Context, found imagewalker.Found) (error, bool) {
if found.UniqueImages > 1 {
return fmt.Errorf("ambiguous digest ID: multiple IDs found with provided prefix %s", found.Req)
return fmt.Errorf("ambiguous digest ID: multiple IDs found with provided prefix %s", found.Req), false
}

// Ensure all the layers are here: https://github.com/containerd/nerdctl/issues/3425
err = EnsureAllContent(ctx, client, found.Image.Name, options.GOptions)
if err != nil {
return err
return err, false
}

imgName := found.Image.Name
Expand All @@ -61,7 +61,7 @@ func Save(ctx context.Context, client *containerd.Client, images []string, optio
savedImages[imgDigest] = struct{}{}
exportOpts = append(exportOpts, archive.WithImage(imageStore, imgName))
}
return nil
return nil, false
},
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/cmd/image/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ func Tag(ctx context.Context, client *containerd.Client, options types.ImageTagO
var srcName string
walker := &imagewalker.ImageWalker{
Client: client,
OnFound: func(ctx context.Context, found imagewalker.Found) error {
OnFound: func(ctx context.Context, found imagewalker.Found) (error, bool) {
if srcName == "" {
srcName = found.Image.Name
}
return nil
return nil, false
},
}
matchCount, err := walker.Walk(ctx, options.Source)
Expand Down
66 changes: 59 additions & 7 deletions pkg/idutil/imagewalker/imagewalker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"regexp"
"strings"

"github.com/distribution/reference"
"github.com/opencontainers/go-digest"

containerd "github.com/containerd/containerd/v2/client"
Expand All @@ -39,7 +40,7 @@ type Found struct {
NameMatchIndex int // Image index with a name matching the argument for `nerdctl rmi`.
}

type OnFound func(ctx context.Context, found Found) error
type OnFound func(ctx context.Context, found Found) (error, bool)

type ImageWalker struct {
Client *containerd.Client
Expand All @@ -52,37 +53,69 @@ type ImageWalker struct {
func (w *ImageWalker) Walk(ctx context.Context, req string) (int, error) {
var filters []string
var parsedReferenceStr string
var images_tag, images_repo []images.Image

Check failure on line 56 in pkg/idutil/imagewalker/imagewalker.go

View workflow job for this annotation

GitHub Actions / go | freebsd |

var-naming: don't use underscores in Go names; var images_tag should be imagesTag (revive)
var tag_num int

Check failure on line 57 in pkg/idutil/imagewalker/imagewalker.go

View workflow job for this annotation

GitHub Actions / go | freebsd |

var-naming: don't use underscores in Go names; var tag_num should be tagNum (revive)
var repo string

parsedReference, err := referenceutil.Parse(req)
if err == nil {
parsedReferenceStr = parsedReference.String()
filters = append(filters, fmt.Sprintf("name==%s", parsedReferenceStr))
}
//Get the image ID , if reg == imageTag use
image, err := w.Client.GetImage(ctx, parsedReferenceStr)
if err != nil {
repo = req
} else {
repo = strings.Split(image.Target().Digest.String(), ":")[1][:12]
}

filters = append(filters,
fmt.Sprintf("name==%s", req),
fmt.Sprintf("target.digest~=^sha256:%s.*$", regexp.QuoteMeta(req)),
fmt.Sprintf("target.digest~=^%s.*$", regexp.QuoteMeta(req)),
fmt.Sprintf("target.digest~=^sha256:%s.*$", regexp.QuoteMeta(repo)),
fmt.Sprintf("target.digest~=^%s.*$", regexp.QuoteMeta(repo)),
)

images, err := w.Client.ImageService().List(ctx, filters...)
if err != nil {
return -1, err
}

matchCount := len(images)
//Distinguish between tag and non-tag
for _, ima := range images {
ref := ima.Name
parsed, err := reference.ParseAnyReference(ref)
if err != nil {
continue
}
switch parsed.(type) {
case reference.Canonical, reference.Digested:
images_repo = append(images_repo, ima)
case reference.Tagged:
images_tag = append(images_tag, ima)
tag_num++
}
}

matchCount := len(images_tag)
// to handle the `rmi -f` case where returned images are different but
// have the same short prefix.
uniqueImages := make(map[digest.Digest]bool)
nameMatchIndex := -1
for i, image := range images {
for i, image := range images_tag {
uniqueImages[image.Target.Digest] = true
// to get target image index for `nerdctl rmi <short digest ids of another images>`.
if (parsedReferenceStr != "" && image.Name == parsedReferenceStr) || image.Name == req {
nameMatchIndex = i
}
}

for i, img := range images {
//The matchCount count is only required if it is passed in as an image ID
if nameMatchIndex != -1 || matchCount < 1 {
matchCount = 1
}

for i, img := range images_tag {
f := Found{
Image: img,
Req: req,
Expand All @@ -91,8 +124,27 @@ func (w *ImageWalker) Walk(ctx context.Context, req string) (int, error) {
UniqueImages: len(uniqueImages),
NameMatchIndex: nameMatchIndex,
}
if e := w.OnFound(ctx, f); e != nil {
e, ok := w.OnFound(ctx, f)
if e != nil {
return -1, e
} else if ok {
tag_num = tag_num - 1
}
}
//If the corresponding imageTag does not exist, delete the repoDigests
if tag_num == 0 {
for i, img := range images_repo {
f := Found{
Image: img,
Req: req,
MatchIndex: i,
MatchCount: 1,
UniqueImages: len(uniqueImages),
NameMatchIndex: -1,
}
if e, _ := w.OnFound(ctx, f); e != nil {
return -1, e
}
}
}
return matchCount, nil
Expand Down
Loading

0 comments on commit 8a3158e

Please sign in to comment.