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",
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.`,
Example: `podman image exists ID
podman image exists IMAGE && podman pull IMAGE`,
Args: cobra.ExactArgs(1),
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 {
_, err := registry.NewImageEngine(cmd, args)
if _, err := registry.NewImageEngine(cmd, args); err != nil {
return err
}
return nil
}

View File

@ -15,7 +15,7 @@ var (
Args: listCmd.Args,
Short: listCmd.Short,
Long: listCmd.Long,
PersistentPreRunE: preRunE,
PreRunE: listCmd.PreRunE,
RunE: listCmd.RunE,
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
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,22 +19,74 @@ 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
}
results, err := ir.Libpod.RemoveImage(ctx, image, 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{}
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
}
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) {

View File

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