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>
This commit is contained in:
Jhon Honce
2020-03-25 14:00:31 -07:00
parent 36a4cc864d
commit f38a26bfa0
13 changed files with 283 additions and 71 deletions

View File

@ -13,10 +13,10 @@ var (
Use: "exists IMAGE", Use: "exists IMAGE",
Short: "Check if an image exists in local storage", 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.`, Long: `If the named image exists in local storage, podman image exists exits with 0, otherwise the exit code will be 1.`,
Example: `podman image exists ID
podman image exists IMAGE && podman pull IMAGE`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: exists, RunE: exists,
Example: `podman image exists ID
podman image exists IMAGE && podman pull IMAGE`,
} }
) )

View File

@ -28,6 +28,8 @@ func init() {
} }
func preRunE(cmd *cobra.Command, args []string) error { func preRunE(cmd *cobra.Command, args []string) error {
_, err := registry.NewImageEngine(cmd, args) if _, err := registry.NewImageEngine(cmd, args); err != nil {
return err return err
} }
return nil
}

View File

@ -15,7 +15,7 @@ var (
Args: listCmd.Args, Args: listCmd.Args,
Short: listCmd.Short, Short: listCmd.Short,
Long: listCmd.Long, Long: listCmd.Long,
PersistentPreRunE: preRunE, PreRunE: listCmd.PreRunE,
RunE: listCmd.RunE, RunE: listCmd.RunE,
Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1), Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
} }

70
cmd/podmanV2/images/rm.go Normal file
View File

@ -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
}

View File

@ -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())
}

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"os" "os"
"reflect" "reflect"
"runtime" "runtime"
@ -16,7 +15,6 @@ import (
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/entities"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
) )
func init() { func init() {
@ -24,10 +22,7 @@ func init() {
logrus.Errorf(err.Error()) logrus.Errorf(err.Error())
os.Exit(1) os.Exit(1)
} }
initCobra()
}
func initCobra() {
switch runtime.GOOS { switch runtime.GOOS {
case "darwin": case "darwin":
fallthrough fallthrough
@ -46,12 +41,9 @@ func initCobra() {
registry.EngineOptions.EngineMode = entities.TunnelMode registry.EngineOptions.EngineMode = entities.TunnelMode
} }
} }
cobra.OnInitialize(func() {})
} }
func main() { func main() {
fmt.Fprintf(os.Stderr, "Number of commands: %d\n", len(registry.Commands))
for _, c := range registry.Commands { for _, c := range registry.Commands {
if Contains(registry.EngineOptions.EngineMode, c.Mode) { if Contains(registry.EngineOptions.EngineMode, c.Mode) {
parent := rootCmd parent := rootCmd

View File

@ -10,6 +10,8 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
type CobraFuncs func(cmd *cobra.Command, args []string) error
type CliCommand struct { type CliCommand struct {
Mode []entities.EngineMode Mode []entities.EngineMode
Command *cobra.Command Command *cobra.Command

View File

@ -2,25 +2,36 @@ package main
import ( import (
"fmt" "fmt"
"log/syslog"
"os" "os"
"path" "path"
"github.com/containers/libpod/cmd/podmanV2/registry" "github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/version" "github.com/containers/libpod/version"
"github.com/sirupsen/logrus"
logrusSyslog "github.com/sirupsen/logrus/hooks/syslog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var rootCmd = &cobra.Command{ var (
rootCmd = &cobra.Command{
Use: path.Base(os.Args[0]), Use: path.Base(os.Args[0]),
Long: "Manage pods, containers and images", Long: "Manage pods, containers and images",
SilenceUsage: true, SilenceUsage: true,
SilenceErrors: true, SilenceErrors: true,
TraverseChildren: true, TraverseChildren: true,
PersistentPreRunE: preRunE,
RunE: registry.SubCommandExists, RunE: registry.SubCommandExists,
Version: version.Version, Version: version.Version,
} }
logLevels = entities.NewStringSet("debug", "info", "warn", "error", "fatal", "panic")
logLevel = "error"
useSyslog bool
)
func init() { func init() {
// Override default --help information of `--version` global flag} // Override default --help information of `--version` global flag}
var dummyVersion bool var dummyVersion bool
@ -28,6 +39,49 @@ func init() {
rootCmd.PersistentFlags().BoolVar(&dummyVersion, "version", false, "Version of Podman") rootCmd.PersistentFlags().BoolVar(&dummyVersion, "version", false, "Version of Podman")
rootCmd.PersistentFlags().StringVarP(&registry.EngineOptions.Uri, "remote", "r", "", "URL to access Podman service") 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().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() { func Execute() {

View File

@ -36,17 +36,23 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) {
return return
} }
_, err = runtime.RemoveImage(r.Context(), newImage, query.Force) results, err := runtime.RemoveImage(r.Context(), newImage, query.Force)
if err != nil { if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return return
} }
// TODO
// This will need to be fixed for proper response, like Deleted: and Untagged: response := make([]map[string]string, 0, len(results.Untagged)+1)
m := make(map[string]string) deleted := make(map[string]string, 1)
m["Deleted"] = newImage.ID() deleted["Deleted"] = results.Deleted
foo := []map[string]string{} response = append(response, deleted)
foo = append(foo, m)
utils.WriteResponse(w, http.StatusOK, foo) for _, u := range results.Untagged {
untagged := make(map[string]string, 1)
untagged["Untagged"] = u
response = append(response, untagged)
}
utils.WriteResponse(w, http.StatusOK, response)
} }

View File

@ -5,7 +5,7 @@ import (
) )
type ImageEngine interface { 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) Exists(ctx context.Context, nameOrId string) (*BoolReport, error)
History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error) History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error) List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error)

View File

@ -81,14 +81,18 @@ func (i *ImageSummary) IsDangling() bool {
} }
type ImageDeleteOptions struct { type ImageDeleteOptions struct {
All bool
Force bool Force bool
} }
// ImageDeleteResponse is the response for removing an image from storage and containers // ImageDeleteResponse is the response for removing one or more image(s) from storage
// what was untagged vs actually removed // and containers what was untagged vs actually removed
type ImageDeleteReport struct { type ImageDeleteReport struct {
Untagged []string `json:"untagged"` Untagged []string `json:",omitempty"`
Deleted string `json:"deleted"` Deleted []string `json:",omitempty"`
Errors []error
ImageNotFound error
ImageInUse error
} }
type ImageHistoryOptions struct{} type ImageHistoryOptions struct{}

View File

@ -4,10 +4,12 @@ package abi
import ( import (
"context" "context"
"fmt"
libpodImage "github.com/containers/libpod/libpod/image" libpodImage "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/domain/entities" "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) { 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 return &entities.BoolReport{Value: true}, nil
} }
func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { 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
}
results, err := ir.Libpod.RemoveImage(ctx, image, opts.Force)
if err != nil {
return nil, err
}
report := entities.ImageDeleteReport{} report := entities.ImageDeleteReport{}
if err := utils.DeepCopy(&report, results); 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 return &report, nil
} }
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) { func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{}) results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{})
if err != nil { if err != nil {

View File

@ -14,27 +14,25 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
return &entities.BoolReport{Value: found}, err return &entities.BoolReport{Value: found}, err
} }
func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
results, err := images.Remove(ir.ClientCxt, nameOrId, &opts.Force) report := entities.ImageDeleteReport{}
for _, id := range nameOrId {
results, err := images.Remove(ir.ClientCxt, id, &opts.Force)
if err != nil { if err != nil {
return nil, err return nil, err
} }
report := entities.ImageDeleteReport{
Untagged: nil,
Deleted: "",
}
for _, e := range results { for _, e := range results {
if a, ok := e["Deleted"]; ok { if a, ok := e["Deleted"]; ok {
report.Deleted = a report.Deleted = append(report.Deleted, a)
} }
if a, ok := e["Untagged"]; ok { if a, ok := e["Untagged"]; ok {
report.Untagged = append(report.Untagged, a) 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) { func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) {