mirror of
https://github.com/containers/podman.git
synced 2025-06-03 03:07:56 +08:00
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:
@ -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`,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
70
cmd/podmanV2/images/rm.go
Normal 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
|
||||
}
|
30
cmd/podmanV2/images/rmi.go
Normal file
30
cmd/podmanV2/images/rmi.go
Normal 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())
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(®istry.EngineOptions.Uri, "remote", "r", "", "URL to access Podman service")
|
||||
rootCmd.PersistentFlags().StringSliceVar(®istry.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() {
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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{}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user