From 36a4cc864d1de57cd822a81d01f0b5f1b40eaa7d Mon Sep 17 00:00:00 2001
From: Jhon Honce <jhonce@redhat.com>
Date: Tue, 24 Mar 2020 17:53:28 -0700
Subject: V2 podman image

* Exists()

Signed-off-by: Jhon Honce <jhonce@redhat.com>
---
 pkg/domain/entities/engine_image.go | 1 +
 pkg/domain/infra/abi/images.go      | 7 +++++++
 pkg/domain/infra/tunnel/images.go   | 5 +++++
 3 files changed, 13 insertions(+)

(limited to 'pkg')

diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index d44fdaf53..0294c7129 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -6,6 +6,7 @@ import (
 
 type ImageEngine interface {
 	Delete(ctx context.Context, nameOrId string, opts ImageDeleteOptions) (*ImageDeleteReport, error)
+	Exists(ctx context.Context, nameOrId string) (*BoolReport, error)
 	History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
 	List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error)
 	Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 6e9d7f566..39a03ef6c 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -10,6 +10,13 @@ import (
 	"github.com/containers/libpod/pkg/domain/utils"
 )
 
+func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) {
+	if _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId); err != nil {
+		return &entities.BoolReport{}, nil
+	}
+	return &entities.BoolReport{Value: true}, nil
+}
+
 func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
 	image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
 	if err != nil {
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 60df40498..921e4a6e3 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -9,6 +9,11 @@ import (
 	"github.com/containers/libpod/pkg/domain/utils"
 )
 
+func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) {
+	found, err := images.Exists(ir.ClientCxt, nameOrId)
+	return &entities.BoolReport{Value: found}, err
+}
+
 func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
 	results, err := images.Remove(ir.ClientCxt, nameOrId, &opts.Force)
 	if err != nil {
-- 
cgit v1.2.3-54-g00ecf


From f38a26bfa0621e06ad8401ded3e10d7ea834819e Mon Sep 17 00:00:00 2001
From: Jhon Honce <jhonce@redhat.com>
Date: Wed, 25 Mar 2020 14:00:31 -0700
Subject: V2 podman image rm | podman rmi [IMAGE]

* Add support for rm and rmi commands
* Support for registry.ExitCode
* Support for N-errors from domain layer

* Add log-level support
* Add syslog support

Signed-off-by: Jhon Honce <jhonce@redhat.com>
---
 cmd/podmanV2/images/exists.go            |  4 +-
 cmd/podmanV2/images/image.go             |  6 ++-
 cmd/podmanV2/images/images.go            | 14 +++---
 cmd/podmanV2/images/rm.go                | 70 ++++++++++++++++++++++++++++
 cmd/podmanV2/images/rmi.go               | 30 ++++++++++++
 cmd/podmanV2/main.go                     |  8 ----
 cmd/podmanV2/registry/registry.go        |  2 +
 cmd/podmanV2/root.go                     | 72 +++++++++++++++++++++++++----
 pkg/api/handlers/compat/images_remove.go | 22 +++++----
 pkg/domain/entities/engine_image.go      |  2 +-
 pkg/domain/entities/images.go            | 12 +++--
 pkg/domain/infra/abi/images.go           | 78 +++++++++++++++++++++++++++-----
 pkg/domain/infra/tunnel/images.go        | 30 ++++++------
 13 files changed, 281 insertions(+), 69 deletions(-)
 create mode 100644 cmd/podmanV2/images/rm.go
 create mode 100644 cmd/podmanV2/images/rmi.go

(limited to 'pkg')

diff --git a/cmd/podmanV2/images/exists.go b/cmd/podmanV2/images/exists.go
index 9d909394d..d35d6825e 100644
--- a/cmd/podmanV2/images/exists.go
+++ b/cmd/podmanV2/images/exists.go
@@ -13,10 +13,10 @@ var (
 		Use:   "exists IMAGE",
 		Short: "Check if an image exists in local storage",
 		Long:  `If the named image exists in local storage, podman image exists exits with 0, otherwise the exit code will be 1.`,
+		Args:  cobra.ExactArgs(1),
+		RunE:  exists,
 		Example: `podman image exists ID
   podman image exists IMAGE && podman pull IMAGE`,
-		Args: cobra.ExactArgs(1),
-		RunE: exists,
 	}
 )
 
diff --git a/cmd/podmanV2/images/image.go b/cmd/podmanV2/images/image.go
index a15c3e826..9fc7b21d1 100644
--- a/cmd/podmanV2/images/image.go
+++ b/cmd/podmanV2/images/image.go
@@ -28,6 +28,8 @@ func init() {
 }
 
 func preRunE(cmd *cobra.Command, args []string) error {
-	_, err := registry.NewImageEngine(cmd, args)
-	return err
+	if _, err := registry.NewImageEngine(cmd, args); err != nil {
+		return err
+	}
+	return nil
 }
diff --git a/cmd/podmanV2/images/images.go b/cmd/podmanV2/images/images.go
index 719846b4c..f248aa65f 100644
--- a/cmd/podmanV2/images/images.go
+++ b/cmd/podmanV2/images/images.go
@@ -11,13 +11,13 @@ import (
 var (
 	// podman _images_  Alias for podman image _list_
 	imagesCmd = &cobra.Command{
-		Use:               strings.Replace(listCmd.Use, "list", "images", 1),
-		Args:              listCmd.Args,
-		Short:             listCmd.Short,
-		Long:              listCmd.Long,
-		PersistentPreRunE: preRunE,
-		RunE:              listCmd.RunE,
-		Example:           strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
+		Use:     strings.Replace(listCmd.Use, "list", "images", 1),
+		Args:    listCmd.Args,
+		Short:   listCmd.Short,
+		Long:    listCmd.Long,
+		PreRunE: listCmd.PreRunE,
+		RunE:    listCmd.RunE,
+		Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
 	}
 )
 
diff --git a/cmd/podmanV2/images/rm.go b/cmd/podmanV2/images/rm.go
new file mode 100644
index 000000000..bb5880de3
--- /dev/null
+++ b/cmd/podmanV2/images/rm.go
@@ -0,0 +1,70 @@
+package images
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/containers/libpod/cmd/podmanV2/registry"
+	"github.com/containers/libpod/pkg/domain/entities"
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+)
+
+var (
+	rmDescription = "Removes one or more previously pulled or locally created images."
+	rmCmd         = &cobra.Command{
+		Use:     "rm [flags] IMAGE [IMAGE...]",
+		Short:   "Removes one or more images from local storage",
+		Long:    rmDescription,
+		PreRunE: preRunE,
+		RunE:    rm,
+		Example: `podman image rm imageID
+  podman image rm --force alpine
+  podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
+	}
+
+	imageOpts = entities.ImageDeleteOptions{}
+)
+
+func init() {
+	registry.Commands = append(registry.Commands, registry.CliCommand{
+		Mode:    []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+		Command: rmCmd,
+		Parent:  imageCmd,
+	})
+
+	flags := rmCmd.Flags()
+	flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images")
+	flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image")
+}
+
+func rm(cmd *cobra.Command, args []string) error {
+
+	if len(args) < 1 && !imageOpts.All {
+		return errors.Errorf("image name or ID must be specified")
+	}
+	if len(args) > 0 && imageOpts.All {
+		return errors.Errorf("when using the --all switch, you may not pass any images names or IDs")
+	}
+
+	report, err := registry.ImageEngine().Delete(registry.GetContext(), args, imageOpts)
+	if err != nil {
+		switch {
+		case report != nil && report.ImageNotFound != nil:
+			fmt.Fprintln(os.Stderr, err.Error())
+			registry.SetExitCode(2)
+		case report != nil && report.ImageInUse != nil:
+			fmt.Fprintln(os.Stderr, err.Error())
+		default:
+			return err
+		}
+	}
+
+	for _, u := range report.Untagged {
+		fmt.Println("Untagged: " + u)
+	}
+	for _, d := range report.Deleted {
+		fmt.Println("Deleted: " + d)
+	}
+	return nil
+}
diff --git a/cmd/podmanV2/images/rmi.go b/cmd/podmanV2/images/rmi.go
new file mode 100644
index 000000000..7f9297bc9
--- /dev/null
+++ b/cmd/podmanV2/images/rmi.go
@@ -0,0 +1,30 @@
+package images
+
+import (
+	"strings"
+
+	"github.com/containers/libpod/cmd/podmanV2/registry"
+	"github.com/containers/libpod/pkg/domain/entities"
+	"github.com/spf13/cobra"
+)
+
+var (
+	rmiCmd = &cobra.Command{
+		Use:     strings.Replace(rmCmd.Use, "rm ", "rmi ", 1),
+		Args:    rmCmd.Args,
+		Short:   rmCmd.Short,
+		Long:    rmCmd.Long,
+		PreRunE: rmCmd.PreRunE,
+		RunE:    rmCmd.RunE,
+		Example: strings.Replace(rmCmd.Example, "podman image rm", "podman rmi", -1),
+	}
+)
+
+func init() {
+	registry.Commands = append(registry.Commands, registry.CliCommand{
+		Mode:    []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+		Command: rmiCmd,
+	})
+	rmiCmd.SetHelpTemplate(registry.HelpTemplate())
+	rmiCmd.SetUsageTemplate(registry.UsageTemplate())
+}
diff --git a/cmd/podmanV2/main.go b/cmd/podmanV2/main.go
index dc96c26d0..bd9fbb25e 100644
--- a/cmd/podmanV2/main.go
+++ b/cmd/podmanV2/main.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"fmt"
 	"os"
 	"reflect"
 	"runtime"
@@ -16,7 +15,6 @@ import (
 	"github.com/containers/libpod/libpod"
 	"github.com/containers/libpod/pkg/domain/entities"
 	"github.com/sirupsen/logrus"
-	"github.com/spf13/cobra"
 )
 
 func init() {
@@ -24,10 +22,7 @@ func init() {
 		logrus.Errorf(err.Error())
 		os.Exit(1)
 	}
-	initCobra()
-}
 
-func initCobra() {
 	switch runtime.GOOS {
 	case "darwin":
 		fallthrough
@@ -46,12 +41,9 @@ func initCobra() {
 			registry.EngineOptions.EngineMode = entities.TunnelMode
 		}
 	}
-
-	cobra.OnInitialize(func() {})
 }
 
 func main() {
-	fmt.Fprintf(os.Stderr, "Number of commands: %d\n", len(registry.Commands))
 	for _, c := range registry.Commands {
 		if Contains(registry.EngineOptions.EngineMode, c.Mode) {
 			parent := rootCmd
diff --git a/cmd/podmanV2/registry/registry.go b/cmd/podmanV2/registry/registry.go
index f0650a7cf..5cdb8a840 100644
--- a/cmd/podmanV2/registry/registry.go
+++ b/cmd/podmanV2/registry/registry.go
@@ -10,6 +10,8 @@ import (
 	"github.com/spf13/cobra"
 )
 
+type CobraFuncs func(cmd *cobra.Command, args []string) error
+
 type CliCommand struct {
 	Mode    []entities.EngineMode
 	Command *cobra.Command
diff --git a/cmd/podmanV2/root.go b/cmd/podmanV2/root.go
index 68e8b4531..cb4cb4e00 100644
--- a/cmd/podmanV2/root.go
+++ b/cmd/podmanV2/root.go
@@ -2,24 +2,35 @@ package main
 
 import (
 	"fmt"
+	"log/syslog"
 	"os"
 	"path"
 
 	"github.com/containers/libpod/cmd/podmanV2/registry"
 	"github.com/containers/libpod/libpod/define"
+	"github.com/containers/libpod/pkg/domain/entities"
 	"github.com/containers/libpod/version"
+	"github.com/sirupsen/logrus"
+	logrusSyslog "github.com/sirupsen/logrus/hooks/syslog"
 	"github.com/spf13/cobra"
 )
 
-var rootCmd = &cobra.Command{
-	Use:              path.Base(os.Args[0]),
-	Long:             "Manage pods, containers and images",
-	SilenceUsage:     true,
-	SilenceErrors:    true,
-	TraverseChildren: true,
-	RunE:             registry.SubCommandExists,
-	Version:          version.Version,
-}
+var (
+	rootCmd = &cobra.Command{
+		Use:               path.Base(os.Args[0]),
+		Long:              "Manage pods, containers and images",
+		SilenceUsage:      true,
+		SilenceErrors:     true,
+		TraverseChildren:  true,
+		PersistentPreRunE: preRunE,
+		RunE:              registry.SubCommandExists,
+		Version:           version.Version,
+	}
+
+	logLevels = entities.NewStringSet("debug", "info", "warn", "error", "fatal", "panic")
+	logLevel  = "error"
+	useSyslog bool
+)
 
 func init() {
 	// Override default --help information of `--version` global flag}
@@ -28,6 +39,49 @@ func init() {
 	rootCmd.PersistentFlags().BoolVar(&dummyVersion, "version", false, "Version of Podman")
 	rootCmd.PersistentFlags().StringVarP(&registry.EngineOptions.Uri, "remote", "r", "", "URL to access Podman service")
 	rootCmd.PersistentFlags().StringSliceVar(&registry.EngineOptions.Identities, "identity", []string{}, "path to SSH identity file")
+	rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "error", fmt.Sprintf("Log messages above specified level (%s)", logLevels.String()))
+	rootCmd.PersistentFlags().BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)")
+
+	cobra.OnInitialize(
+		logging,
+		syslogHook,
+	)
+}
+
+func preRunE(cmd *cobra.Command, args []string) error {
+	cmd.SetHelpTemplate(registry.HelpTemplate())
+	cmd.SetUsageTemplate(registry.UsageTemplate())
+	return nil
+}
+
+func logging() {
+	if !logLevels.Contains(logLevel) {
+		fmt.Fprintf(os.Stderr, "Log Level \"%s\" is not supported, choose from: %s\n", logLevel, logLevels.String())
+		os.Exit(1)
+	}
+
+	level, err := logrus.ParseLevel(logLevel)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+	logrus.SetLevel(level)
+
+	if logrus.IsLevelEnabled(logrus.InfoLevel) {
+		logrus.Infof("%s filtering at log level %s", os.Args[0], logrus.GetLevel())
+	}
+}
+
+func syslogHook() {
+	if useSyslog {
+		hook, err := logrusSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
+		if err != nil {
+			logrus.WithError(err).Error("Failed to initialize syslog hook")
+		}
+		if err == nil {
+			logrus.AddHook(hook)
+		}
+	}
 }
 
 func Execute() {
diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go
index 3d346543e..ed0153529 100644
--- a/pkg/api/handlers/compat/images_remove.go
+++ b/pkg/api/handlers/compat/images_remove.go
@@ -36,17 +36,23 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	_, err = runtime.RemoveImage(r.Context(), newImage, query.Force)
+	results, err := runtime.RemoveImage(r.Context(), newImage, query.Force)
 	if err != nil {
 		utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
 		return
 	}
-	// TODO
-	// This will need to be fixed for proper response, like Deleted: and Untagged:
-	m := make(map[string]string)
-	m["Deleted"] = newImage.ID()
-	foo := []map[string]string{}
-	foo = append(foo, m)
-	utils.WriteResponse(w, http.StatusOK, foo)
+
+	response := make([]map[string]string, 0, len(results.Untagged)+1)
+	deleted := make(map[string]string, 1)
+	deleted["Deleted"] = results.Deleted
+	response = append(response, deleted)
+
+	for _, u := range results.Untagged {
+		untagged := make(map[string]string, 1)
+		untagged["Untagged"] = u
+		response = append(response, untagged)
+	}
+
+	utils.WriteResponse(w, http.StatusOK, response)
 
 }
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index 0294c7129..d0c860a04 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -5,7 +5,7 @@ import (
 )
 
 type ImageEngine interface {
-	Delete(ctx context.Context, nameOrId string, opts ImageDeleteOptions) (*ImageDeleteReport, error)
+	Delete(ctx context.Context, nameOrId []string, opts ImageDeleteOptions) (*ImageDeleteReport, error)
 	Exists(ctx context.Context, nameOrId string) (*BoolReport, error)
 	History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
 	List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error)
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index f04317e37..4a51b3de4 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -81,14 +81,18 @@ func (i *ImageSummary) IsDangling() bool {
 }
 
 type ImageDeleteOptions struct {
+	All   bool
 	Force bool
 }
 
-// ImageDeleteResponse is the response for removing an image from storage and containers
-// what was untagged vs actually removed
+// ImageDeleteResponse is the response for removing one or more image(s) from storage
+// and containers what was untagged vs actually removed
 type ImageDeleteReport struct {
-	Untagged []string `json:"untagged"`
-	Deleted  string   `json:"deleted"`
+	Untagged      []string `json:",omitempty"`
+	Deleted       []string `json:",omitempty"`
+	Errors        []error
+	ImageNotFound error
+	ImageInUse    error
 }
 
 type ImageHistoryOptions struct{}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 39a03ef6c..203f14987 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -4,10 +4,12 @@ package abi
 
 import (
 	"context"
+	"fmt"
 
 	libpodImage "github.com/containers/libpod/libpod/image"
 	"github.com/containers/libpod/pkg/domain/entities"
-	"github.com/containers/libpod/pkg/domain/utils"
+	"github.com/containers/storage"
+	"github.com/pkg/errors"
 )
 
 func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) {
@@ -17,24 +19,76 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
 	return &entities.BoolReport{Value: true}, nil
 }
 
-func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
-	image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
-	if err != nil {
-		return nil, err
-	}
+func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
+	report := entities.ImageDeleteReport{}
 
-	results, err := ir.Libpod.RemoveImage(ctx, image, opts.Force)
-	if err != nil {
-		return nil, err
+	if opts.All {
+		var previousTargets []*libpodImage.Image
+	repeatRun:
+		targets, err := ir.Libpod.ImageRuntime().GetRWImages()
+		if err != nil {
+			return &report, errors.Wrapf(err, "unable to query local images")
+		}
+
+		if len(targets) > 0 && len(targets) == len(previousTargets) {
+			return &report, errors.New("unable to delete all images; re-run the rmi command again.")
+		}
+		previousTargets = targets
+
+		for _, img := range targets {
+			isParent, err := img.IsParent(ctx)
+			if err != nil {
+				return &report, err
+			}
+			if isParent {
+				continue
+			}
+			err = ir.deleteImage(ctx, img, opts, report)
+			report.Errors = append(report.Errors, err)
+		}
+		if len(targets) >= 0 || len(previousTargets) != 1 {
+			goto repeatRun
+		}
+		return &report, nil
 	}
 
-	report := entities.ImageDeleteReport{}
-	if err := utils.DeepCopy(&report, results); err != nil {
-		return nil, err
+	for _, id := range nameOrId {
+		image, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
+		if err != nil {
+			return nil, err
+		}
+
+		err = ir.deleteImage(ctx, image, opts, report)
+		if err != nil {
+			return &report, err
+		}
 	}
 	return &report, nil
 }
 
+func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, opts entities.ImageDeleteOptions, report entities.ImageDeleteReport) error {
+	results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
+	switch errors.Cause(err) {
+	case nil:
+		break
+	case storage.ErrImageUsedByContainer:
+		report.ImageInUse = errors.New(
+			fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()))
+		return nil
+	case libpodImage.ErrNoSuchImage:
+		report.ImageNotFound = err
+		return nil
+	default:
+		return err
+	}
+
+	report.Deleted = append(report.Deleted, results.Deleted)
+	for _, e := range results.Untagged {
+		report.Untagged = append(report.Untagged, e)
+	}
+	return nil
+}
+
 func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
 	results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{})
 	if err != nil {
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 921e4a6e3..6a241641e 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -14,27 +14,25 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
 	return &entities.BoolReport{Value: found}, err
 }
 
-func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
-	results, err := images.Remove(ir.ClientCxt, nameOrId, &opts.Force)
-	if err != nil {
-		return nil, err
-	}
+func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
+	report := entities.ImageDeleteReport{}
 
-	report := entities.ImageDeleteReport{
-		Untagged: nil,
-		Deleted:  "",
-	}
-
-	for _, e := range results {
-		if a, ok := e["Deleted"]; ok {
-			report.Deleted = a
+	for _, id := range nameOrId {
+		results, err := images.Remove(ir.ClientCxt, id, &opts.Force)
+		if err != nil {
+			return nil, err
 		}
+		for _, e := range results {
+			if a, ok := e["Deleted"]; ok {
+				report.Deleted = append(report.Deleted, a)
+			}
 
-		if a, ok := e["Untagged"]; ok {
-			report.Untagged = append(report.Untagged, a)
+			if a, ok := e["Untagged"]; ok {
+				report.Untagged = append(report.Untagged, a)
+			}
 		}
 	}
-	return &report, err
+	return &report, nil
 }
 
 func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) {
-- 
cgit v1.2.3-54-g00ecf