mirror of
https://github.com/containers/podman.git
synced 2025-06-26 04:46:57 +08:00
podman rmi: refactor logic
While this commit was initially meant to fix #5847, it has turned into a bigger refactoring which I did not manage to break into smaller pieces: * Fix #5847 by refactoring the image-removal logic. * Make the api handler for image-removal use the ABI code. This way, both (i.e., ABI and Tunnel) end up using the same code. Achieving this code share required to move some code around to prevent circular dependencies. * Everything in pkg/api (excluding pkg/api/types) must now only be accessed from code using `ABISupport`. * Avoid imports from entities on handlers to prevent circular dependencies. * Move `podman system service` logic into `cmd` to prevent circular dependencies - it depends on pkg/api. * Also remove the build header from infra/abi files. It will otherwise confuse swagger and other tools; errors we cannot fix as go doesn't expose a build-tag env variable. Fixes: #5847 Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
@ -28,6 +28,7 @@ linters:
|
|||||||
- misspell
|
- misspell
|
||||||
- prealloc
|
- prealloc
|
||||||
- unparam
|
- unparam
|
||||||
|
- nakedret
|
||||||
linters-settings:
|
linters-settings:
|
||||||
errcheck:
|
errcheck:
|
||||||
check-blank: false
|
check-blank: false
|
||||||
|
@ -122,7 +122,7 @@ func run(cmd *cobra.Command, args []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if runRmi {
|
if runRmi {
|
||||||
_, err := registry.ImageEngine().Delete(registry.GetContext(), []string{args[0]}, entities.ImageDeleteOptions{})
|
_, err := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("%s", errors.Wrapf(err, "failed removing image"))
|
logrus.Errorf("%s", errors.Wrapf(err, "failed removing image"))
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package images
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/containers/libpod/cmd/podman/registry"
|
"github.com/containers/libpod/cmd/podman/registry"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
@ -23,7 +22,7 @@ var (
|
|||||||
podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
|
podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
|
||||||
}
|
}
|
||||||
|
|
||||||
imageOpts = entities.ImageDeleteOptions{}
|
imageOpts = entities.ImageRemoveOptions{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -40,32 +39,25 @@ func imageRemoveFlagSet(flags *pflag.FlagSet) {
|
|||||||
flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images")
|
flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images")
|
||||||
flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image")
|
flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image")
|
||||||
}
|
}
|
||||||
func rm(cmd *cobra.Command, args []string) error {
|
|
||||||
|
|
||||||
|
func rm(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) < 1 && !imageOpts.All {
|
if len(args) < 1 && !imageOpts.All {
|
||||||
return errors.Errorf("image name or ID must be specified")
|
return errors.Errorf("image name or ID must be specified")
|
||||||
}
|
}
|
||||||
if len(args) > 0 && imageOpts.All {
|
if len(args) > 0 && imageOpts.All {
|
||||||
return errors.Errorf("when using the --all switch, you may not pass any images names or IDs")
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
report, err := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts)
|
||||||
|
if report != nil {
|
||||||
for _, u := range report.Untagged {
|
for _, u := range report.Untagged {
|
||||||
fmt.Println("Untagged: " + u)
|
fmt.Println("Untagged: " + u)
|
||||||
}
|
}
|
||||||
for _, d := range report.Deleted {
|
for _, d := range report.Deleted {
|
||||||
fmt.Println("Deleted: " + d)
|
fmt.Println("Deleted: " + d)
|
||||||
}
|
}
|
||||||
return nil
|
registry.SetExitCode(report.ExitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,8 @@ func service(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
logrus.Warn("This function is EXPERIMENTAL")
|
logrus.Warn("This function is EXPERIMENTAL")
|
||||||
fmt.Fprintf(os.Stderr, "This function is EXPERIMENTAL.\n")
|
fmt.Fprintf(os.Stderr, "This function is EXPERIMENTAL.\n")
|
||||||
return registry.ContainerEngine().RestService(registry.GetContext(), opts)
|
|
||||||
|
return restService(opts, cmd.Flags(), registry.PodmanConfig())
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveApiURI(_url []string) (string, error) {
|
func resolveApiURI(_url []string) (string, error) {
|
||||||
|
57
cmd/podman/system/service_abi.go
Normal file
57
cmd/podman/system/service_abi.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// +build ABISupport
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
api "github.com/containers/libpod/pkg/api/server"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/containers/libpod/pkg/domain/infra"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error {
|
||||||
|
var (
|
||||||
|
listener *net.Listener
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if opts.URI != "" {
|
||||||
|
fields := strings.Split(opts.URI, ":")
|
||||||
|
if len(fields) == 1 {
|
||||||
|
return errors.Errorf("%s is an invalid socket destination", opts.URI)
|
||||||
|
}
|
||||||
|
address := strings.Join(fields[1:], ":")
|
||||||
|
l, err := net.Listen(fields[0], address)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "unable to create socket %s", opts.URI)
|
||||||
|
}
|
||||||
|
listener = &l
|
||||||
|
}
|
||||||
|
|
||||||
|
rt, err := infra.GetRuntime(context.Background(), flags, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := api.NewServerWithSettings(rt, opts.Timeout, listener)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := server.Shutdown(); err != nil {
|
||||||
|
logrus.Warnf("Error when stopping API service: %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = server.Serve()
|
||||||
|
if listener != nil {
|
||||||
|
_ = (*listener).Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
14
cmd/podman/system/service_unsupported.go
Normal file
14
cmd/podman/system/service_unsupported.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// +build !ABISupport
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error {
|
||||||
|
return errors.New("not supported")
|
||||||
|
}
|
@ -3,13 +3,22 @@
|
|||||||
# Need to run linter twice to cover all the build tags code paths
|
# Need to run linter twice to cover all the build tags code paths
|
||||||
|
|
||||||
declare -A BUILD_TAGS
|
declare -A BUILD_TAGS
|
||||||
|
# TODO: add systemd tag
|
||||||
BUILD_TAGS[default]="apparmor,seccomp,selinux"
|
BUILD_TAGS[default]="apparmor,seccomp,selinux"
|
||||||
BUILD_TAGS[abi]="${BUILD_TAGS[default]},ABISupport,varlink,!remoteclient"
|
BUILD_TAGS[abi]="${BUILD_TAGS[default]},ABISupport,varlink,!remoteclient"
|
||||||
BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},!ABISupport,!varlink,remoteclient"
|
BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},!ABISupport,varlink,remoteclient"
|
||||||
|
|
||||||
|
declare -A SKIP_DIRS
|
||||||
|
SKIP_DIRS[abi]=""
|
||||||
|
# TODO: add "ABISupport" build tag to pkg/api
|
||||||
|
SKIP_DIRS[tunnel]="pkg/api"
|
||||||
|
|
||||||
[[ $1 == run ]] && shift
|
[[ $1 == run ]] && shift
|
||||||
|
|
||||||
for i in tunnel abi; do
|
for i in tunnel abi; do
|
||||||
echo Build Tags: ${BUILD_TAGS[$i]}
|
echo ""
|
||||||
golangci-lint run --build-tags=${BUILD_TAGS[$i]} "$@"
|
echo Running golangci-lint for "$i"
|
||||||
|
echo Build Tags "$i": ${BUILD_TAGS[$i]}
|
||||||
|
echo Skipped directories "$i": ${SKIP_DIRS[$i]}
|
||||||
|
golangci-lint run --build-tags=${BUILD_TAGS[$i]} --skip-dirs=${SKIP_DIRS[$i]} "$@"
|
||||||
done
|
done
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/libpod/events"
|
"github.com/containers/libpod/libpod/events"
|
||||||
"github.com/containers/libpod/pkg/api/handlers"
|
|
||||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -70,7 +70,7 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
|
|||||||
coder.SetEscapeHTML(true)
|
coder.SetEscapeHTML(true)
|
||||||
|
|
||||||
for event := range eventChannel {
|
for event := range eventChannel {
|
||||||
e := handlers.EventToApiEvent(event)
|
e := entities.ConvertToEntitiesEvent(*event)
|
||||||
if err := coder.Encode(e); err != nil {
|
if err := coder.Encode(e); err != nil {
|
||||||
logrus.Errorf("unable to write json: %q", err)
|
logrus.Errorf("unable to write json: %q", err)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/containers/libpod/pkg/api/handlers"
|
"github.com/containers/libpod/pkg/api/handlers"
|
||||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
|
"github.com/containers/libpod/pkg/domain/infra/abi"
|
||||||
"github.com/containers/libpod/pkg/util"
|
"github.com/containers/libpod/pkg/util"
|
||||||
utils2 "github.com/containers/libpod/utils"
|
utils2 "github.com/containers/libpod/utils"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
@ -698,3 +699,30 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
utils.WriteResponse(w, http.StatusOK, reports)
|
utils.WriteResponse(w, http.StatusOK, reports)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImagesRemove is the endpoint for image removal.
|
||||||
|
func ImagesRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||||
|
query := struct {
|
||||||
|
All bool `schema:"all"`
|
||||||
|
Force bool `schema:"force"`
|
||||||
|
Images []string `schema:"images"`
|
||||||
|
}{
|
||||||
|
All: false,
|
||||||
|
Force: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||||
|
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
|
||||||
|
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force}
|
||||||
|
|
||||||
|
imageEngine := abi.ImageEngine{Libpod: runtime}
|
||||||
|
rmReport, rmError := imageEngine.Remove(r.Context(), query.Images, opts)
|
||||||
|
report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Error: rmError.Error()}
|
||||||
|
utils.WriteResponse(w, http.StatusOK, report)
|
||||||
|
}
|
||||||
|
@ -49,6 +49,13 @@ type swagLibpodImagesPullResponse struct {
|
|||||||
Body handlers.LibpodImagesPullReport
|
Body handlers.LibpodImagesPullReport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove response
|
||||||
|
// swagger:response DocsLibpodImagesRemoveResponse
|
||||||
|
type swagLibpodImagesRemoveResponse struct {
|
||||||
|
// in:body
|
||||||
|
Body handlers.LibpodImagesRemoveReport
|
||||||
|
}
|
||||||
|
|
||||||
// Delete response
|
// Delete response
|
||||||
// swagger:response DocsImageDeleteResponse
|
// swagger:response DocsImageDeleteResponse
|
||||||
type swagImageDeleteResponse struct {
|
type swagImageDeleteResponse struct {
|
||||||
|
@ -4,16 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/image/v5/manifest"
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/containers/libpod/libpod/events"
|
|
||||||
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"
|
||||||
docker "github.com/docker/docker/api/types"
|
docker "github.com/docker/docker/api/types"
|
||||||
dockerContainer "github.com/docker/docker/api/types/container"
|
dockerContainer "github.com/docker/docker/api/types/container"
|
||||||
dockerEvents "github.com/docker/docker/api/types/events"
|
|
||||||
dockerNetwork "github.com/docker/docker/api/types/network"
|
dockerNetwork "github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -39,6 +36,14 @@ type LibpodImagesPullReport struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LibpodImagesRemoveReport is the return type for image removal via the rest
|
||||||
|
// api.
|
||||||
|
type LibpodImagesRemoveReport struct {
|
||||||
|
entities.ImageRemoveReport
|
||||||
|
// Image removal requires is to return data and an error.
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
|
||||||
type ContainersPruneReport struct {
|
type ContainersPruneReport struct {
|
||||||
docker.ContainersPruneReport
|
docker.ContainersPruneReport
|
||||||
}
|
}
|
||||||
@ -143,10 +148,6 @@ type PodCreateConfig struct {
|
|||||||
Share string `json:"share"`
|
Share string `json:"share"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
dockerEvents.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
type HistoryResponse struct {
|
type HistoryResponse struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
Created int64 `json:"Created"`
|
Created int64 `json:"Created"`
|
||||||
@ -173,49 +174,6 @@ type ExecCreateResponse struct {
|
|||||||
docker.IDResponse
|
docker.IDResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Event) ToLibpodEvent() *events.Event {
|
|
||||||
exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"])
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
status, err := events.StringToStatus(e.Action)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t, err := events.StringToType(e.Type)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
lp := events.Event{
|
|
||||||
ContainerExitCode: exitCode,
|
|
||||||
ID: e.Actor.ID,
|
|
||||||
Image: e.Actor.Attributes["image"],
|
|
||||||
Name: e.Actor.Attributes["name"],
|
|
||||||
Status: status,
|
|
||||||
Time: time.Unix(e.Time, e.TimeNano),
|
|
||||||
Type: t,
|
|
||||||
}
|
|
||||||
return &lp
|
|
||||||
}
|
|
||||||
|
|
||||||
func EventToApiEvent(e *events.Event) *Event {
|
|
||||||
return &Event{dockerEvents.Message{
|
|
||||||
Type: e.Type.String(),
|
|
||||||
Action: e.Status.String(),
|
|
||||||
Actor: dockerEvents.Actor{
|
|
||||||
ID: e.ID,
|
|
||||||
Attributes: map[string]string{
|
|
||||||
"image": e.Image,
|
|
||||||
"name": e.Name,
|
|
||||||
"containerExitCode": strconv.Itoa(e.ContainerExitCode),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: "local",
|
|
||||||
Time: e.Time.Unix(),
|
|
||||||
TimeNano: e.Time.UnixNano(),
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
|
func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
|
||||||
containers, err := l.Containers()
|
containers, err := l.Containers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -822,6 +822,38 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
|
|||||||
// 500:
|
// 500:
|
||||||
// $ref: '#/responses/InternalError'
|
// $ref: '#/responses/InternalError'
|
||||||
r.Handle(VersionedPath("/libpod/images/import"), s.APIHandler(libpod.ImagesImport)).Methods(http.MethodPost)
|
r.Handle(VersionedPath("/libpod/images/import"), s.APIHandler(libpod.ImagesImport)).Methods(http.MethodPost)
|
||||||
|
// swagger:operation GET /libpod/images/remove libpod libpodImagesRemove
|
||||||
|
// ---
|
||||||
|
// tags:
|
||||||
|
// - images
|
||||||
|
// summary: Remove one or more images from the storage.
|
||||||
|
// description: Remove one or more images from the storage.
|
||||||
|
// parameters:
|
||||||
|
// - in: query
|
||||||
|
// name: images
|
||||||
|
// description: Images IDs or names to remove.
|
||||||
|
// type: array
|
||||||
|
// items:
|
||||||
|
// type: string
|
||||||
|
// - in: query
|
||||||
|
// name: all
|
||||||
|
// description: Remove all images.
|
||||||
|
// type: boolean
|
||||||
|
// default: true
|
||||||
|
// - in: query
|
||||||
|
// name: force
|
||||||
|
// description: Force image removal (including containers using the images).
|
||||||
|
// type: boolean
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// $ref: "#/responses/DocsLibpodImagesRemoveResponse"
|
||||||
|
// 400:
|
||||||
|
// $ref: "#/responses/BadParamError"
|
||||||
|
// 500:
|
||||||
|
// $ref: '#/responses/InternalError'
|
||||||
|
r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesRemove)).Methods(http.MethodGet)
|
||||||
// swagger:operation POST /libpod/images/pull libpod libpodImagesPull
|
// swagger:operation POST /libpod/images/pull libpod libpodImagesPull
|
||||||
// ---
|
// ---
|
||||||
// tags:
|
// tags:
|
||||||
|
9
pkg/api/types/types.go
Normal file
9
pkg/api/types/types.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultAPIVersion is the version of the API the server defaults to.
|
||||||
|
DefaultAPIVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
|
||||||
|
|
||||||
|
// DefaultAPIVersion is the minimal required version of the API.
|
||||||
|
MinimalAPIVersion = "1.24"
|
||||||
|
)
|
@ -15,7 +15,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containers/libpod/pkg/api/handlers"
|
"github.com/containers/libpod/pkg/api/types"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -27,7 +27,7 @@ var (
|
|||||||
basePath = &url.URL{
|
basePath = &url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: "d",
|
Host: "d",
|
||||||
Path: "/v" + handlers.MinimalApiVersion + "/libpod",
|
Path: "/v" + types.MinimalAPIVersion + "/libpod",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -109,23 +109,34 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe
|
|||||||
return &report, response.Process(&report)
|
return &report, response.Process(&report)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove deletes an image from local storage. The optional force parameter will forcibly remove
|
// Remove deletes an image from local storage. The optional force parameter
|
||||||
// the image by removing all all containers, including those that are Running, first.
|
// will forcibly remove the image by removing all all containers, including
|
||||||
func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]string, error) {
|
// those that are Running, first.
|
||||||
var deletes []map[string]string
|
func Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
|
||||||
|
var report handlers.LibpodImagesRemoveReport
|
||||||
conn, err := bindings.GetClient(ctx)
|
conn, err := bindings.GetClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
if force != nil {
|
params.Set("all", strconv.FormatBool(opts.All))
|
||||||
params.Set("force", strconv.FormatBool(*force))
|
params.Set("force", strconv.FormatBool(opts.Force))
|
||||||
|
for _, i := range images {
|
||||||
|
params.Add("images", i)
|
||||||
}
|
}
|
||||||
response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID)
|
|
||||||
|
response, err := conn.DoRequest(nil, http.MethodGet, "/images/remove", params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return deletes, response.Process(&deletes)
|
if err := response.Process(&report); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var rmError error
|
||||||
|
if report.Error != "" {
|
||||||
|
rmError = errors.New(report.Error)
|
||||||
|
}
|
||||||
|
return &report.ImageRemoveReport, rmError
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export saves an image from local storage as a tarball or image archive. The optional format
|
// Export saves an image from local storage as a tarball or image archive. The optional format
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/containers/libpod/pkg/api/handlers"
|
|
||||||
"github.com/containers/libpod/pkg/bindings"
|
"github.com/containers/libpod/pkg/bindings"
|
||||||
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -16,7 +16,7 @@ import (
|
|||||||
// Events allows you to monitor libdpod related events like container creation and
|
// Events allows you to monitor libdpod related events like container creation and
|
||||||
// removal. The events are then passed to the eventChan provided. The optional cancelChan
|
// removal. The events are then passed to the eventChan provided. The optional cancelChan
|
||||||
// can be used to cancel the read of events and close down the HTTP connection.
|
// can be used to cancel the read of events and close down the HTTP connection.
|
||||||
func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
|
func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
|
||||||
conn, err := bindings.GetClient(ctx)
|
conn, err := bindings.GetClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -48,7 +48,7 @@ func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan cha
|
|||||||
}
|
}
|
||||||
dec := json.NewDecoder(response.Body)
|
dec := json.NewDecoder(response.Body)
|
||||||
for {
|
for {
|
||||||
e := handlers.Event{}
|
e := entities.Event{}
|
||||||
if err := dec.Decode(&e); err != nil {
|
if err := dec.Decode(&e); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
|
@ -54,7 +54,6 @@ type ContainerEngine interface {
|
|||||||
PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error)
|
PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error)
|
||||||
PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error)
|
PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error)
|
||||||
PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error)
|
PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error)
|
||||||
RestService(ctx context.Context, opts ServiceOptions) error
|
|
||||||
SetupRootless(ctx context.Context, cmd *cobra.Command) error
|
SetupRootless(ctx context.Context, cmd *cobra.Command) error
|
||||||
VarlinkService(ctx context.Context, opts ServiceOptions) error
|
VarlinkService(ctx context.Context, opts ServiceOptions) error
|
||||||
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error)
|
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error)
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
type ImageEngine interface {
|
type ImageEngine interface {
|
||||||
Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error)
|
Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error)
|
||||||
Config(ctx context.Context) (*config.Config, error)
|
Config(ctx context.Context) (*config.Config, error)
|
||||||
Delete(ctx context.Context, nameOrId []string, opts ImageDeleteOptions) (*ImageDeleteReport, error)
|
|
||||||
Diff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error)
|
Diff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, 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)
|
||||||
@ -20,6 +19,7 @@ type ImageEngine interface {
|
|||||||
Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
|
Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
|
||||||
Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
|
Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
|
||||||
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
|
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
|
||||||
|
Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, error)
|
||||||
Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error
|
Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error
|
||||||
Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
|
Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
|
||||||
Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error
|
Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error
|
||||||
|
61
pkg/domain/entities/events.go
Normal file
61
pkg/domain/entities/events.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
libpodEvents "github.com/containers/libpod/libpod/events"
|
||||||
|
dockerEvents "github.com/docker/docker/api/types/events"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event combines various event-related data such as time, event type, status
|
||||||
|
// and more.
|
||||||
|
type Event struct {
|
||||||
|
// TODO: it would be nice to have full control over the types at some
|
||||||
|
// point and fork such Docker types.
|
||||||
|
dockerEvents.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToLibpodEvent converts an entities event to a libpod one.
|
||||||
|
func ConvertToLibpodEvent(e Event) *libpodEvents.Event {
|
||||||
|
exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"])
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
status, err := libpodEvents.StringToStatus(e.Action)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
t, err := libpodEvents.StringToType(e.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &libpodEvents.Event{
|
||||||
|
ContainerExitCode: exitCode,
|
||||||
|
ID: e.Actor.ID,
|
||||||
|
Image: e.Actor.Attributes["image"],
|
||||||
|
Name: e.Actor.Attributes["name"],
|
||||||
|
Status: status,
|
||||||
|
Time: time.Unix(e.Time, e.TimeNano),
|
||||||
|
Type: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToEntitiesEvent converts a libpod event to an entities one.
|
||||||
|
func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
|
||||||
|
return &Event{dockerEvents.Message{
|
||||||
|
Type: e.Type.String(),
|
||||||
|
Action: e.Status.String(),
|
||||||
|
Actor: dockerEvents.Actor{
|
||||||
|
ID: e.ID,
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"image": e.Image,
|
||||||
|
"name": e.Name,
|
||||||
|
"containerExitCode": strconv.Itoa(e.ContainerExitCode),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: "local",
|
||||||
|
Time: e.Time.Unix(),
|
||||||
|
TimeNano: e.Time.UnixNano(),
|
||||||
|
}}
|
||||||
|
}
|
@ -82,19 +82,24 @@ func (i *ImageSummary) IsDangling() bool {
|
|||||||
return i.Dangling
|
return i.Dangling
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageDeleteOptions struct {
|
// ImageRemoveOptions can be used to alter image removal.
|
||||||
|
type ImageRemoveOptions struct {
|
||||||
|
// All will remove all images.
|
||||||
All bool
|
All bool
|
||||||
|
// Foce will force image removal including containers using the images.
|
||||||
Force bool
|
Force bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageDeleteResponse is the response for removing one or more image(s) from storage
|
// ImageRemoveResponse is the response for removing one or more image(s) from storage
|
||||||
// and containers what was untagged vs actually removed
|
// and containers what was untagged vs actually removed.
|
||||||
type ImageDeleteReport struct {
|
type ImageRemoveReport struct {
|
||||||
Untagged []string `json:",omitempty"`
|
// Deleted images.
|
||||||
Deleted []string `json:",omitempty"`
|
Deleted []string `json:",omitempty"`
|
||||||
Errors []error
|
// Untagged images. Can be longer than Deleted.
|
||||||
ImageNotFound error
|
Untagged []string `json:",omitempty"`
|
||||||
ImageInUse error
|
// ExitCode describes the exit codes as described in the `podman rmi`
|
||||||
|
// man page.
|
||||||
|
ExitCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageHistoryOptions struct{}
|
type ImageHistoryOptions struct{}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
//+build ABISupport
|
|
||||||
|
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -23,6 +21,7 @@ import (
|
|||||||
domainUtils "github.com/containers/libpod/pkg/domain/utils"
|
domainUtils "github.com/containers/libpod/pkg/domain/utils"
|
||||||
"github.com/containers/libpod/pkg/util"
|
"github.com/containers/libpod/pkg/util"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -36,76 +35,6 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
|
|||||||
return &entities.BoolReport{Value: err == nil}, nil
|
return &entities.BoolReport{Value: err == nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
|
|
||||||
report := entities.ImageDeleteReport{}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return &report, nil
|
|
||||||
}
|
|
||||||
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(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)
|
|
||||||
report.Untagged = append(report.Untagged, results.Untagged...)
|
|
||||||
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, opts.Filter)
|
results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -488,3 +417,135 @@ func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.
|
|||||||
}
|
}
|
||||||
return &entities.ImageTreeReport{Tree: results}, nil
|
return &entities.ImageTreeReport{Tree: results}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove removes one or more images from local storage.
|
||||||
|
func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, finalError error) {
|
||||||
|
var (
|
||||||
|
// noSuchImageErrors indicates that at least one image was not found.
|
||||||
|
noSuchImageErrors bool
|
||||||
|
// inUseErrors indicates that at least one image is being used by a
|
||||||
|
// container.
|
||||||
|
inUseErrors bool
|
||||||
|
// otherErrors indicates that at least one error other than the two
|
||||||
|
// above occured.
|
||||||
|
otherErrors bool
|
||||||
|
// deleteError is a multierror to conveniently collect errors during
|
||||||
|
// removal. We really want to delete as many images as possible and not
|
||||||
|
// error out immediately.
|
||||||
|
deleteError *multierror.Error
|
||||||
|
)
|
||||||
|
|
||||||
|
report = &entities.ImageRemoveReport{}
|
||||||
|
|
||||||
|
// Set the removalCode and the error after all work is done.
|
||||||
|
defer func() {
|
||||||
|
switch {
|
||||||
|
// 2
|
||||||
|
case inUseErrors:
|
||||||
|
// One of the specified images has child images or is
|
||||||
|
// being used by a container.
|
||||||
|
report.ExitCode = 2
|
||||||
|
// 1
|
||||||
|
case noSuchImageErrors && !(otherErrors || inUseErrors):
|
||||||
|
// One of the specified images did not exist, and no other
|
||||||
|
// failures.
|
||||||
|
report.ExitCode = 1
|
||||||
|
// 0
|
||||||
|
default:
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
if deleteError != nil {
|
||||||
|
// go-multierror has a trailing new line which we need to remove to normalize the string.
|
||||||
|
finalError = deleteError.ErrorOrNil()
|
||||||
|
finalError = errors.New(strings.TrimSpace(finalError.Error()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// deleteImage is an anonymous function to conveniently delete an image
|
||||||
|
// withouth having to pass all local data around.
|
||||||
|
deleteImage := func(img *image.Image) error {
|
||||||
|
results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
|
||||||
|
switch errors.Cause(err) {
|
||||||
|
case nil:
|
||||||
|
break
|
||||||
|
case storage.ErrImageUsedByContainer:
|
||||||
|
inUseErrors = true // Important for exit codes in Podman.
|
||||||
|
return 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()))
|
||||||
|
default:
|
||||||
|
otherErrors = true // Important for exit codes in Podman.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
report.Deleted = append(report.Deleted, results.Deleted)
|
||||||
|
report.Untagged = append(report.Untagged, results.Untagged...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all images from the local storage.
|
||||||
|
if opts.All {
|
||||||
|
previousImages := 0
|
||||||
|
// Remove all images one-by-one.
|
||||||
|
for {
|
||||||
|
storageImages, err := ir.Libpod.ImageRuntime().GetRWImages()
|
||||||
|
if err != nil {
|
||||||
|
deleteError = multierror.Append(deleteError,
|
||||||
|
errors.Wrapf(err, "unable to query local images"))
|
||||||
|
otherErrors = true // Important for exit codes in Podman.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// No images (left) to remove, so we're done.
|
||||||
|
if len(storageImages) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Prevent infinity loops by making a delete-progress check.
|
||||||
|
if previousImages == len(storageImages) {
|
||||||
|
otherErrors = true // Important for exit codes in Podman.
|
||||||
|
deleteError = multierror.Append(deleteError,
|
||||||
|
errors.New("unable to delete all images, check errors and re-run image removal if needed"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
previousImages = len(storageImages)
|
||||||
|
// Delete all "leaves" (i.e., images without child images).
|
||||||
|
for _, img := range storageImages {
|
||||||
|
isParent, err := img.IsParent(ctx)
|
||||||
|
if err != nil {
|
||||||
|
otherErrors = true // Important for exit codes in Podman.
|
||||||
|
deleteError = multierror.Append(deleteError, err)
|
||||||
|
}
|
||||||
|
// Skip parent images.
|
||||||
|
if isParent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := deleteImage(img); err != nil {
|
||||||
|
deleteError = multierror.Append(deleteError, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete only the specified images.
|
||||||
|
for _, id := range images {
|
||||||
|
img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
|
||||||
|
switch errors.Cause(err) {
|
||||||
|
case nil:
|
||||||
|
break
|
||||||
|
case image.ErrNoSuchImage:
|
||||||
|
noSuchImageErrors = true // Important for exit codes in Podman.
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
deleteError = multierror.Append(deleteError, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = deleteImage(img)
|
||||||
|
if err != nil {
|
||||||
|
otherErrors = true // Important for exit codes in Podman.
|
||||||
|
deleteError = multierror.Append(deleteError, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
api "github.com/containers/libpod/pkg/api/server"
|
|
||||||
"github.com/containers/libpod/pkg/cgroups"
|
"github.com/containers/libpod/pkg/cgroups"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/containers/libpod/pkg/rootless"
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
@ -33,42 +28,6 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
|
|||||||
return ic.Libpod.Info()
|
return ic.Libpod.Info()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) RestService(_ context.Context, opts entities.ServiceOptions) error {
|
|
||||||
var (
|
|
||||||
listener *net.Listener
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if opts.URI != "" {
|
|
||||||
fields := strings.Split(opts.URI, ":")
|
|
||||||
if len(fields) == 1 {
|
|
||||||
return errors.Errorf("%s is an invalid socket destination", opts.URI)
|
|
||||||
}
|
|
||||||
address := strings.Join(fields[1:], ":")
|
|
||||||
l, err := net.Listen(fields[0], address)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "unable to create socket %s", opts.URI)
|
|
||||||
}
|
|
||||||
listener = &l
|
|
||||||
}
|
|
||||||
|
|
||||||
server, err := api.NewServerWithSettings(ic.Libpod, opts.Timeout, listener)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := server.Shutdown(); err != nil {
|
|
||||||
logrus.Warnf("Error when stopping API service: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = server.Serve()
|
|
||||||
if listener != nil {
|
|
||||||
_ = (*listener).Close()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error {
|
func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error {
|
||||||
var varlinkInterfaces = []*iopodman.VarlinkInterface{
|
var varlinkInterfaces = []*iopodman.VarlinkInterface{
|
||||||
iopodmanAPI.New(opts.Command, ic.Libpod),
|
iopodmanAPI.New(opts.Command, ic.Libpod),
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package terminal
|
package terminal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package terminal
|
package terminal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
// +build ABISupport
|
|
||||||
|
|
||||||
package terminal
|
package terminal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/libpod/pkg/api/handlers"
|
|
||||||
"github.com/containers/libpod/pkg/bindings/system"
|
"github.com/containers/libpod/pkg/bindings/system"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -21,10 +20,10 @@ func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptio
|
|||||||
filters[split[0]] = append(filters[split[0]], strings.Join(split[1:], "="))
|
filters[split[0]] = append(filters[split[0]], strings.Join(split[1:], "="))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binChan := make(chan handlers.Event)
|
binChan := make(chan entities.Event)
|
||||||
go func() {
|
go func() {
|
||||||
for e := range binChan {
|
for e := range binChan {
|
||||||
opts.EventChan <- e.ToLibpodEvent()
|
opts.EventChan <- entities.ConvertToLibpodEvent(e)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters)
|
return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters)
|
||||||
|
@ -19,25 +19,8 @@ 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) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
|
||||||
report := entities.ImageDeleteReport{}
|
return images.Remove(ir.ClientCxt, imagesArg, opts)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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) {
|
||||||
|
@ -14,10 +14,6 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
|
|||||||
return system.Info(ic.ClientCxt)
|
return system.Info(ic.ClientCxt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) RestService(_ context.Context, _ entities.ServiceOptions) error {
|
|
||||||
panic(errors.New("rest service is not supported when tunneling"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *ContainerEngine) VarlinkService(_ context.Context, _ entities.ServiceOptions) error {
|
func (ic *ContainerEngine) VarlinkService(_ context.Context, _ entities.ServiceOptions) error {
|
||||||
panic(errors.New("varlink service is not supported when tunneling"))
|
panic(errors.New("varlink service is not supported when tunneling"))
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user