From 85d228344b2e967084c260e55ef97f484cbda683 Mon Sep 17 00:00:00 2001 From: suyanhanx Date: Fri, 20 Jan 2023 20:27:27 +0800 Subject: [PATCH] refactor image remove command flag Signed-off-by: suyanhanx --- cmd/nerdctl/rmi.go | 103 ++++++-------------------------- pkg/api/types/image_types.go | 11 ++++ pkg/cmd/image/remove.go | 110 +++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 84 deletions(-) create mode 100644 pkg/cmd/image/remove.go diff --git a/cmd/nerdctl/rmi.go b/cmd/nerdctl/rmi.go index c1814289f7a..f71d7db1c84 100644 --- a/cmd/nerdctl/rmi.go +++ b/cmd/nerdctl/rmi.go @@ -17,16 +17,8 @@ package main import ( - "context" - "fmt" - "strings" - - "github.com/containerd/containerd/images" - "github.com/containerd/containerd/platforms" - "github.com/containerd/nerdctl/pkg/clientutil" - "github.com/containerd/nerdctl/pkg/formatter" - "github.com/containerd/nerdctl/pkg/idutil/imagewalker" - "github.com/sirupsen/logrus" + "github.com/containerd/nerdctl/pkg/api/types" + "github.com/containerd/nerdctl/pkg/cmd/image" "github.com/spf13/cobra" ) @@ -46,93 +38,36 @@ func newRmiCommand() *cobra.Command { return rmiCommand } -func rmiAction(cmd *cobra.Command, args []string) error { +func processImageRemoveOptions(cmd *cobra.Command) (types.ImageRemoveOptions, error) { globalOptions, err := processRootCmdFlags(cmd) if err != nil { - return err + return types.ImageRemoveOptions{}, err } + force, err := cmd.Flags().GetBool("force") if err != nil { - return err - } - - var delOpts []images.DeleteOpt - if async, err := cmd.Flags().GetBool("async"); err != nil { - return err - } else if !async { - delOpts = append(delOpts, images.SynchronousDelete()) + return types.ImageRemoveOptions{}, err } - client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address) + async, err := cmd.Flags().GetBool("async") if err != nil { - return err + return types.ImageRemoveOptions{}, err } - defer cancel() - cs := client.ContentStore() - is := client.ImageService() - containerList, err := client.Containers(ctx) + return types.ImageRemoveOptions{ + Stdout: cmd.OutOrStdout(), + GOptions: globalOptions, + Force: force, + Async: async, + }, nil +} + +func rmiAction(cmd *cobra.Command, args []string) error { + options, err := processImageRemoveOptions(cmd) if err != nil { return err } - usedImages := make(map[string]struct{}) - runningImages := make(map[string]struct{}) - for _, container := range containerList { - image, err := container.Image(ctx) - if err != nil { - return err - } - cStatus := formatter.ContainerStatus(ctx, container) - if strings.HasPrefix(cStatus, "Up") { - runningImages[image.Name()] = struct{}{} - } else { - usedImages[image.Name()] = struct{}{} - } - } - - walker := &imagewalker.ImageWalker{ - Client: client, - OnFound: func(ctx context.Context, found imagewalker.Found) error { - // if found multiple images, return error unless in force-mode and - // there is only 1 unique image. - if found.MatchCount > 1 && !(force && found.UniqueImages == 1) { - return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req) - } - if _, ok := runningImages[found.Image.Name]; ok { - return fmt.Errorf("image %s is running, can't be forced removed", found.Image.Name) - } - if _, ok := usedImages[found.Image.Name]; ok && !force { - return fmt.Errorf("conflict: unable to remove repository reference %q (must force)", found.Req) - } - // digests is used only for emulating human-readable output of `docker rmi` - digests, err := found.Image.RootFS(ctx, cs, platforms.DefaultStrict()) - if err != nil { - logrus.WithError(err).Warning("failed to enumerate rootfs") - } - if err := is.Delete(ctx, found.Image.Name, delOpts...); err != nil { - return err - } - fmt.Fprintf(cmd.OutOrStdout(), "Untagged: %s@%s\n", found.Image.Name, found.Image.Target.Digest) - for _, digest := range digests { - fmt.Fprintf(cmd.OutOrStdout(), "Deleted: %s\n", digest) - } - return nil - }, - } - for _, req := range args { - n, err := walker.Walk(ctx, req) - if err == nil && n == 0 { - err = fmt.Errorf("no such image %s", req) - } - if err != nil { - if force { - logrus.Error(err) - } else { - return err - } - } - } - return nil + return image.Remove(cmd.Context(), args, options) } func rmiShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/pkg/api/types/image_types.go b/pkg/api/types/image_types.go index 7f9028c34f1..00ea418ab1e 100644 --- a/pkg/api/types/image_types.go +++ b/pkg/api/types/image_types.go @@ -178,3 +178,14 @@ type ImageTagOptions struct { // Target is the image to be created. Target string } + +// ImageRemoveOptions specifies options for `nerdctl rmi` and `nerdctl image rm`. +type ImageRemoveOptions struct { + Stdout io.Writer + // GOptions is the global options + GOptions GlobalCommandOptions + // Force removal of the image + Force bool + // Asynchronous mode + Async bool +} diff --git a/pkg/cmd/image/remove.go b/pkg/cmd/image/remove.go new file mode 100644 index 00000000000..6a0c971002d --- /dev/null +++ b/pkg/cmd/image/remove.go @@ -0,0 +1,110 @@ +/* + 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" + "strings" + + "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/containerd/nerdctl/pkg/formatter" + "github.com/containerd/nerdctl/pkg/idutil/imagewalker" + "github.com/sirupsen/logrus" +) + +// Remove removes a list of `images`. +func Remove(ctx context.Context, args []string, options types.ImageRemoveOptions) error { + var delOpts []images.DeleteOpt + if !options.Async { + delOpts = append(delOpts, images.SynchronousDelete()) + } + client, ctx, cancel, err := clientutil.NewClient(ctx, options.GOptions.Namespace, options.GOptions.Address) + if err != nil { + return err + } + defer cancel() + + cs := client.ContentStore() + is := client.ImageService() + containerList, err := client.Containers(ctx) + if err != nil { + return err + } + usedImages := make(map[string]struct{}) + runningImages := make(map[string]struct{}) + for _, container := range containerList { + image, err := container.Image(ctx) + if err != nil { + return err + } + cStatus := formatter.ContainerStatus(ctx, container) + if strings.HasPrefix(cStatus, "Up") { + runningImages[image.Name()] = struct{}{} + } else { + usedImages[image.Name()] = struct{}{} + } + } + + walker := &imagewalker.ImageWalker{ + Client: client, + OnFound: func(ctx context.Context, found imagewalker.Found) error { + // 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) + } + if _, ok := runningImages[found.Image.Name]; ok { + return fmt.Errorf("image %s is running, can't be forced removed", found.Image.Name) + } + if _, ok := usedImages[found.Image.Name]; ok && !options.Force { + return fmt.Errorf("conflict: unable to remove repository reference %q (must force)", found.Req) + } + // digests is used only for emulating human-readable output of `docker rmi` + digests, err := found.Image.RootFS(ctx, cs, platforms.DefaultStrict()) + if err != nil { + logrus.WithError(err).Warning("failed to enumerate rootfs") + } + + if err := is.Delete(ctx, found.Image.Name, delOpts...); err != nil { + return err + } + 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 + }, + } + for _, req := range args { + n, err := walker.Walk(ctx, req) + if err == nil && n == 0 { + err = fmt.Errorf("no such image %s", req) + } + if err != nil { + if options.Force { + logrus.Error(err) + } else { + return err + } + } + } + return nil +}