Skip to content

Commit

Permalink
Merge pull request #1931 from suyanhanx/fix-1922
Browse files Browse the repository at this point in the history
[Refactor] refactor image prune flag
  • Loading branch information
Zheaoli authored Feb 7, 2023
2 parents 875560e + d0e36b6 commit 513f279
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 69 deletions.
89 changes: 22 additions & 67 deletions cmd/nerdctl/image_prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,12 @@
package main

import (
"context"
"fmt"
"strings"

"github.com/containerd/containerd"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms"
"github.com/containerd/nerdctl/pkg/api/types"
"github.com/containerd/nerdctl/pkg/clientutil"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
"github.com/containerd/nerdctl/pkg/cmd/image"
"github.com/spf13/cobra"
)

Expand All @@ -45,28 +41,36 @@ func newImagePruneCommand() *cobra.Command {
return imagePruneCommand
}

func imagePruneAction(cmd *cobra.Command, _ []string) error {
func processImagePruneOptions(cmd *cobra.Command) (types.ImagePruneOptions, error) {
globalOptions, err := processRootCmdFlags(cmd)
if err != nil {
return err
return types.ImagePruneOptions{}, err
}
all, err := cmd.Flags().GetBool("all")
if err != nil {
return err
return types.ImagePruneOptions{}, err
}

if !all {
logrus.Warn("Currently, `nerdctl image prune` requires --all to be specified. Skip pruning.")
// NOP
return nil
force, err := cmd.Flags().GetBool("force")
if err != nil {
return types.ImagePruneOptions{}, err
}

force, err := cmd.Flags().GetBool("force")
return types.ImagePruneOptions{
Stdout: cmd.OutOrStdout(),
GOptions: globalOptions,
All: all,
Force: force,
}, err
}

func imagePruneAction(cmd *cobra.Command, _ []string) error {
options, err := processImagePruneOptions(cmd)
if err != nil {
return err
}

if !force {
if !options.Force {
var confirm string
msg := "This will remove all images without at least one container associated to them."
msg += "\nAre you sure you want to continue? [y/N] "
Expand All @@ -78,61 +82,12 @@ func imagePruneAction(cmd *cobra.Command, _ []string) error {
return nil
}
}
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address)
if err != nil {
return err
}
defer cancel()

return imagePrune(ctx, cmd, client)
}

func imagePrune(ctx context.Context, cmd *cobra.Command, client *containerd.Client) error {
var (
imageStore = client.ImageService()
contentStore = client.ContentStore()
containerStore = client.ContainerService()
)
imageList, err := imageStore.List(ctx)
if err != nil {
return err
}
containerList, err := containerStore.List(ctx)
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)
if err != nil {
return err
}
usedImages := make(map[string]struct{})
for _, container := range containerList {
usedImages[container.Image] = struct{}{}
}

delOpts := []images.DeleteOpt{images.SynchronousDelete()}
removedImages := make(map[string][]digest.Digest)
for _, image := range imageList {
if _, ok := usedImages[image.Name]; ok {
continue
}

digests, err := image.RootFS(ctx, contentStore, platforms.DefaultStrict())
if err != nil {
logrus.WithError(err).Warnf("failed to enumerate rootfs")
}
if err := imageStore.Delete(ctx, image.Name, delOpts...); err != nil {
logrus.WithError(err).Warnf("failed to delete image %s", image.Name)
continue
}
removedImages[image.Name] = digests
}
defer cancel()

if len(removedImages) > 0 {
fmt.Fprintln(cmd.OutOrStdout(), "Deleted Images:")
for image, digests := range removedImages {
fmt.Fprintf(cmd.OutOrStdout(), "Untagged: %s\n", image)
for _, digest := range digests {
fmt.Fprintf(cmd.OutOrStdout(), "deleted: %s\n", digest)
}
}
fmt.Fprintln(cmd.OutOrStdout(), "")
}
return nil
return image.Prune(ctx, client, options)
}
7 changes: 6 additions & 1 deletion cmd/nerdctl/system_prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/containerd/nerdctl/pkg/api/types"
"github.com/containerd/nerdctl/pkg/buildkitutil"
"github.com/containerd/nerdctl/pkg/clientutil"
"github.com/containerd/nerdctl/pkg/cmd/image"
"github.com/containerd/nerdctl/pkg/cmd/network"
"github.com/containerd/nerdctl/pkg/cmd/volume"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -121,7 +122,11 @@ func systemPruneAction(cmd *cobra.Command, args []string) error {
return err
}
}
if err := imagePrune(ctx, cmd, client); err != nil {
if err := image.Prune(ctx, client, types.ImagePruneOptions{
Stdout: cmd.OutOrStdout(),
GOptions: globalOptions,
All: true,
}); err != nil {
return nil
}
prunedObjects, err := buildCachePrune(ctx, cmd, all, globalOptions.Namespace)
Expand Down
13 changes: 12 additions & 1 deletion pkg/api/types/image_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,21 @@ type ImageRemoveOptions struct {
GOptions GlobalCommandOptions
// Force removal of the image
Force bool
// Asynchronous mode
// Async asynchronous mode or not
Async bool
}

// ImagePruneOptions specifies options for `nerdctl image prune` and `nerdctl image rm`.
type ImagePruneOptions struct {
Stdout io.Writer
// GOptions is the global options.
GOptions GlobalCommandOptions
// All Remove all unused images, not just dangling ones.
All bool
// Force will not prompt for confirmation.
Force bool
}

// ImageSaveOptions specifies options for `nerdctl (image) save`.
type ImageSaveOptions struct {
Stdout io.Writer
Expand Down
80 changes: 80 additions & 0 deletions pkg/cmd/image/prune.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
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 image

import (
"context"
"fmt"

"github.com/containerd/containerd"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms"
"github.com/containerd/nerdctl/pkg/api/types"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)

// Prune will remove all dangling images. If all is specified, will also remove all images not referenced by any container.
func Prune(ctx context.Context, client *containerd.Client, options types.ImagePruneOptions) error {
var (
imageStore = client.ImageService()
contentStore = client.ContentStore()
containerStore = client.ContainerService()
)

imageList, err := imageStore.List(ctx)
if err != nil {
return err
}
containerList, err := containerStore.List(ctx)
if err != nil {
return err
}
usedImages := make(map[string]struct{})
for _, container := range containerList {
usedImages[container.Image] = struct{}{}
}

delOpts := []images.DeleteOpt{images.SynchronousDelete()}
removedImages := make(map[string][]digest.Digest)
for _, image := range imageList {
if _, ok := usedImages[image.Name]; ok {
continue
}
digests, err := image.RootFS(ctx, contentStore, platforms.DefaultStrict())
if err != nil {
logrus.WithError(err).Warnf("failed to enumerate rootfs")
}
if err := imageStore.Delete(ctx, image.Name, delOpts...); err != nil {
logrus.WithError(err).Warnf("failed to delete image %s", image.Name)
continue
}
removedImages[image.Name] = digests
}

if len(removedImages) > 0 {
fmt.Fprintln(options.Stdout, "Deleted Images:")
for image, digests := range removedImages {
fmt.Fprintf(options.Stdout, "Untagged: %s\n", image)
for _, digest := range digests {
fmt.Fprintf(options.Stdout, "deleted: %s\n", digest)
}
}
fmt.Fprintln(options.Stdout, "")
}
return nil
}

0 comments on commit 513f279

Please sign in to comment.