add struct response for removal of images

when removing an image from storage, we should return a struct that
details what was untagged vs deleted.  this replaces the simple
println's used previously and assists in API development.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2019-12-23 09:33:29 -06:00
parent fcd48db4d2
commit 4f09cfdacc
8 changed files with 89 additions and 22 deletions

18
API.md
View File

@ -149,6 +149,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[func RemoveImage(name: string, force: bool) string](#RemoveImage)
[func RemoveImageWithResponse(name: string, force: bool) RemoveImageResponse](#RemoveImageWithResponse)
[func RemovePod(name: string, force: bool) string](#RemovePod)
[func Reset() ](#Reset)
@ -257,6 +259,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[type PsOpts](#PsOpts)
[type RemoveImageResponse](#RemoveImageResponse)
[type Runlabel](#Runlabel)
[type Sockets](#Sockets)
@ -1052,6 +1056,13 @@ varlink call -m unix:/run/podman/io.podman/io.podman.RemoveImage '{"name": "regi
"image": "426866d6fa419873f97e5cbd320eeb22778244c1dfffa01c944db3114f55772e"
}
~~~
### <a name="RemoveImageWithResponse"></a>func RemoveImageWithResponse
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
method RemoveImageWithResponse(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool)) [RemoveImageResponse](#RemoveImageResponse)</div>
RemoveImageWithResponse takes the name or ID of an image as well as a boolean that determines if containers using that image
should be deleted. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error will be returned. The reponse is
in the form of a RemoveImageResponse .
### <a name="RemovePod"></a>func RemovePod
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
@ -2026,6 +2037,13 @@ size [?bool](#?bool)
sort [?string](#?string)
sync [?bool](#?bool)
### <a name="RemoveImageResponse"></a>type RemoveImageResponse
untagged [[]string](#[]string)
deleted [string](https://godoc.org/builtin#string)
### <a name="Runlabel"></a>type Runlabel
Runlabel describes the required input for container runlabel

View File

@ -68,7 +68,7 @@ func rmiCmd(c *cliconfig.RmiValues) error {
images := args[:]
removeImage := func(img *adapter.ContainerImage) {
msg, err := runtime.RemoveImage(ctx, img, c.Force)
response, err := runtime.RemoveImage(ctx, img, c.Force)
if err != nil {
if errors.Cause(err) == storage.ErrImageUsedByContainer {
fmt.Printf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())
@ -83,7 +83,14 @@ func rmiCmd(c *cliconfig.RmiValues) error {
lastError = err
return
}
fmt.Println(msg)
// Iterate if any images tags were deleted
for _, i := range response.Untagged {
fmt.Printf("Untagged: %s\n", i)
}
// Make sure an image was deleted (and not just untagged); else print it
if len(response.Deleted) > 0 {
fmt.Printf("Deleted: %s\n", response.Deleted)
}
}
if removeAll {

View File

@ -18,6 +18,11 @@ type StringResponse (
message: string
)
type RemoveImageResponse (
untagged: []string,
deleted: string
)
type LogLine (
device: string,
parseLogType : string,
@ -867,6 +872,11 @@ method TagImage(name: string, tagged: string) -> (image: string)
# ~~~
method RemoveImage(name: string, force: bool) -> (image: string)
# RemoveImageWithResponse takes the name or ID of an image as well as a boolean that determines if containers using that image
# should be deleted. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error will be returned. The reponse is
# in the form of a RemoveImageResponse .
method RemoveImageWithResponse(name: string, force: bool) -> (response: RemoveImageResponse)
# SearchImages searches available registries for images that contain the
# contents of "query" in their name. If "limit" is given, limits the amount of
# search results per registry.

8
libpod/image/config.go Normal file
View File

@ -0,0 +1,8 @@
package image
// ImageDeleteResponse is the response for removing an image from storage and containers
// what was untagged vs actually removed
type ImageDeleteResponse struct {
Untagged []string `json:"untagged"`
Deleted string `json:"deleted"`
}

View File

@ -27,19 +27,19 @@ import (
// RemoveImage deletes an image from local storage
// Images being used by running containers can only be removed if force=true
func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) (string, error) {
var returnMessage string
func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool) (*image.ImageDeleteResponse, error) {
response := image.ImageDeleteResponse{}
r.lock.Lock()
defer r.lock.Unlock()
if !r.valid {
return "", define.ErrRuntimeStopped
return nil, define.ErrRuntimeStopped
}
// Get all containers, filter to only those using the image, and remove those containers
ctrs, err := r.state.AllContainers()
if err != nil {
return "", err
return nil, err
}
imageCtrs := []*Container{}
for _, ctr := range ctrs {
@ -51,17 +51,17 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
if force {
for _, ctr := range imageCtrs {
if err := r.removeContainer(ctx, ctr, true, false, false); err != nil {
return "", errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID())
return nil, errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID())
}
}
} else {
return "", fmt.Errorf("could not remove image %s as it is being used by %d containers", img.ID(), len(imageCtrs))
return nil, fmt.Errorf("could not remove image %s as it is being used by %d containers", img.ID(), len(imageCtrs))
}
}
hasChildren, err := img.IsParent(ctx)
if err != nil {
return "", err
return nil, err
}
if (len(img.Names()) > 1 && !img.InputIsID()) || hasChildren {
@ -70,19 +70,20 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
// to and untag it.
repoName, err := img.MatchRepoTag(img.InputName)
if hasChildren && errors.Cause(err) == image.ErrRepoTagNotFound {
return "", errors.Errorf("unable to delete %q (cannot be forced) - image has dependent child images", img.ID())
return nil, errors.Errorf("unable to delete %q (cannot be forced) - image has dependent child images", img.ID())
}
if err != nil {
return "", err
return nil, err
}
if err := img.UntagImage(repoName); err != nil {
return "", err
return nil, err
}
return fmt.Sprintf("Untagged: %s", repoName), nil
response.Untagged = append(response.Untagged, repoName)
return &response, nil
} else if len(img.Names()) > 1 && img.InputIsID() && !force {
// If the user requests to delete an image by ID and the image has multiple
// reponames and no force is applied, we error out.
return "", fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID())
return nil, fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID())
}
err = img.Remove(ctx, force)
if err != nil && errors.Cause(err) == storage.ErrImageUsedByContainer {
@ -94,11 +95,9 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
err = errStorage
}
}
for _, name := range img.Names() {
returnMessage = returnMessage + fmt.Sprintf("Untagged: %s\n", name)
}
returnMessage = returnMessage + fmt.Sprintf("Deleted: %s", img.ID())
return returnMessage, err
response.Untagged = append(response.Untagged, img.Names()...)
response.Deleted = img.ID()
return &response, err
}
// Remove containers that are in storage rather than Podman.

View File

@ -155,7 +155,7 @@ func (r *LocalRuntime) New(ctx context.Context, name, signaturePolicyPath, authf
}
// RemoveImage calls into local storage and removes an image
func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (string, error) {
func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (*image.ImageDeleteResponse, error) {
return r.Runtime.RemoveImage(ctx, img.Image, force)
}

View File

@ -414,8 +414,15 @@ func (ci *ContainerImage) TagImage(tag string) error {
}
// RemoveImage calls varlink to remove an image
func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (string, error) {
return iopodman.RemoveImage().Call(r.Conn, img.InputName, force)
func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, force bool) (*image.ImageDeleteResponse, error) {
ir := image.ImageDeleteResponse{}
response, err := iopodman.RemoveImageWithResponse().Call(r.Conn, img.InputName, force)
if err != nil {
return nil, err
}
ir.Deleted = response.Deleted
ir.Untagged = append(ir.Untagged, response.Untagged...)
return &ir, nil
}
// History returns the history of an image and its layers

View File

@ -465,6 +465,24 @@ func (i *LibpodAPI) RemoveImage(call iopodman.VarlinkCall, name string, force bo
return call.ReplyRemoveImage(newImage.ID())
}
// RemoveImageWithResponse accepts an image name and force bool. It returns details about what
// was done in removeimageresponse struct.
func (i *LibpodAPI) RemoveImageWithResponse(call iopodman.VarlinkCall, name string, force bool) error {
ir := iopodman.RemoveImageResponse{}
ctx := getContext()
newImage, err := i.Runtime.ImageRuntime().NewFromLocal(name)
if err != nil {
return call.ReplyImageNotFound(name, err.Error())
}
response, err := i.Runtime.RemoveImage(ctx, newImage, force)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
ir.Untagged = append(ir.Untagged, response.Untagged...)
ir.Deleted = response.Deleted
return call.ReplyRemoveImageWithResponse(ir)
}
// SearchImages searches all registries configured in /etc/containers/registries.conf for an image
// Requires an image name and a search limit as int
func (i *LibpodAPI) SearchImages(call iopodman.VarlinkCall, query string, limit *int64, filter iopodman.ImageSearchFilter) error {