podman untag: error if tag doesn't exist

Throw an error if a specified tag does not exist.  Also make sure that
the user input is normalized as we already do for `podman tag`.

To prevent regressions, add a set of end-to-end and systemd tests.

Last but not least, update the docs and add bash completions.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg 2020-06-24 14:44:42 +02:00
parent 0d26b8f24b
commit 1c6c12581c
7 changed files with 127 additions and 34 deletions

View File

@ -1572,6 +1572,11 @@ _podman_image_tag() {
_podman_tag _podman_tag
} }
_podman_image_untag() {
_podman_untag
}
_podman_image() { _podman_image() {
local boolean_options=" local boolean_options="
--help --help
@ -1593,6 +1598,7 @@ _podman_image() {
sign sign
tag tag
trust trust
untag
" "
local aliases=" local aliases="
list list
@ -2460,6 +2466,23 @@ _podman_tag() {
esac esac
} }
_podman_untag() {
local options_with_args="
"
local boolean_options="
--help
-h
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__podman_complete_images
;;
esac
}
__podman_top_descriptors() { __podman_top_descriptors() {
podman top --list-descriptors podman top --list-descriptors
} }
@ -3588,6 +3611,7 @@ _podman_podman() {
umount umount
unmount unmount
unpause unpause
untag
varlink varlink
version version
volume volume

View File

@ -4,14 +4,12 @@
podman\-untag - Removes one or more names from a locally-stored image podman\-untag - Removes one or more names from a locally-stored image
## SYNOPSIS ## SYNOPSIS
**podman untag** *image*[:*tag*] [*target-names*[:*tag*]] [*options*] **podman untag** [*options*] *image* [*name*[:*tag*]...]
**podman image untag** *image*[:*tag*] [target-names[:*tag*]] [*options*] **podman image untag** [*options*] *image* [*name*[:*tag*]...]
## DESCRIPTION ## DESCRIPTION
Removes one or all names of an image. A name refers to the entire image name, Remove one or more names from an image in the local storage. The image can be referred to by ID or reference. If a no name is specified, all names are removed the image. If a specified name is a short name and does not include a registry `localhost/` will be prefixed (e.g., `fedora` -> `localhost/fedora`). If a specified name does not include a tag `:latest` will be appended (e.g., `localhost/fedora` -> `localhost/fedora:latest`).
including the optional *tag* after the `:`. If no target image names are
specified, `untag` will remove all tags for the image at once.
## OPTIONS ## OPTIONS

View File

@ -17,6 +17,9 @@ var (
// ErrNoSuchImage indicates the requested image does not exist // ErrNoSuchImage indicates the requested image does not exist
ErrNoSuchImage = image.ErrNoSuchImage ErrNoSuchImage = image.ErrNoSuchImage
// ErrNoSuchTag indicates the requested image tag does not exist
ErrNoSuchTag = image.ErrNoSuchTag
// ErrNoSuchVolume indicates the requested volume does not exist // ErrNoSuchVolume indicates the requested volume does not exist
ErrNoSuchVolume = errors.New("no such volume") ErrNoSuchVolume = errors.New("no such volume")

View File

@ -12,4 +12,6 @@ var (
ErrNoSuchPod = errors.New("no such pod") ErrNoSuchPod = errors.New("no such pod")
// ErrNoSuchImage indicates the requested image does not exist // ErrNoSuchImage indicates the requested image does not exist
ErrNoSuchImage = errors.New("no such image") ErrNoSuchImage = errors.New("no such image")
// ErrNoSuchTag indicates the requested image tag does not exist
ErrNoSuchTag = errors.New("no such tag")
) )

View File

@ -559,15 +559,24 @@ func (i *Image) TagImage(tag string) error {
return nil return nil
} }
// UntagImage removes a tag from the given image // UntagImage removes the specified tag from the image.
// If the tag does not exist, ErrNoSuchTag is returned.
func (i *Image) UntagImage(tag string) error { func (i *Image) UntagImage(tag string) error {
if err := i.reloadImage(); err != nil { if err := i.reloadImage(); err != nil {
return err return err
} }
// Normalize the tag as we do with TagImage.
ref, err := NormalizedTag(tag)
if err != nil {
return err
}
tag = ref.String()
var newTags []string var newTags []string
tags := i.Names() tags := i.Names()
if !util.StringInSlice(tag, tags) { if !util.StringInSlice(tag, tags) {
return nil return errors.Wrapf(ErrNoSuchTag, "%q", tag)
} }
for _, t := range tags { for _, t := range tags {
if tag != t { if tag != t {

View File

@ -23,13 +23,6 @@ var _ = Describe("Podman untag", func() {
podmanTest = PodmanTestCreate(tempdir) podmanTest = PodmanTestCreate(tempdir)
podmanTest.Setup() podmanTest.Setup()
podmanTest.RestoreAllArtifacts() podmanTest.RestoreAllArtifacts()
for _, tag := range []string{"test", "foo", "bar"} {
session := podmanTest.PodmanNoCache([]string{"tag", ALPINE, tag})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
}
}) })
AfterEach(func() { AfterEach(func() {
@ -40,34 +33,63 @@ var _ = Describe("Podman untag", func() {
}) })
It("podman untag all", func() { It("podman untag all", func() {
session := podmanTest.PodmanNoCache([]string{"untag", ALPINE}) tags := []string{ALPINE, "registry.com/foo:bar", "localhost/foo:bar"}
cmd := []string{"tag"}
cmd = append(cmd, tags...)
session := podmanTest.PodmanNoCache(cmd)
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
results := podmanTest.PodmanNoCache([]string{"images", ALPINE}) // Make sure that all tags exists.
results.WaitWithDefaultTimeout() for _, t := range tags {
Expect(results.ExitCode()).To(Equal(0)) session = podmanTest.PodmanNoCache([]string{"image", "exists", t})
Expect(results.OutputToStringArray()).To(HaveLen(1)) session.WaitWithDefaultTimeout()
}) Expect(session.ExitCode()).To(Equal(0))
}
It("podman untag single", func() { // No arguments -> remove all tags.
session := podmanTest.PodmanNoCache([]string{"untag", ALPINE, "localhost/test:latest"}) session = podmanTest.PodmanNoCache([]string{"untag", ALPINE})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))
results := podmanTest.PodmanNoCache([]string{"images"}) // Make sure that none of tags exists anymore.
results.WaitWithDefaultTimeout() for _, t := range tags {
Expect(results.ExitCode()).To(Equal(0)) session = podmanTest.PodmanNoCache([]string{"image", "exists", t})
Expect(results.OutputToStringArray()).To(HaveLen(6)) session.WaitWithDefaultTimeout()
Expect(results.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue()) Expect(session.ExitCode()).To(Equal(1))
Expect(results.LineInOuputStartsWith("localhost/foo")).To(BeTrue()) }
Expect(results.LineInOuputStartsWith("localhost/bar")).To(BeTrue())
Expect(results.LineInOuputStartsWith("localhost/test")).To(BeFalse())
}) })
It("podman untag not enough arguments", func() { It("podman tag/untag - tag normalization", func() {
session := podmanTest.PodmanNoCache([]string{"untag"}) tests := []struct {
session.WaitWithDefaultTimeout() tag, normalized string
Expect(session.ExitCode()).NotTo(Equal(0)) }{
{"registry.com/image:latest", "registry.com/image:latest"},
{"registry.com/image", "registry.com/image:latest"},
{"image:latest", "localhost/image:latest"},
{"image", "localhost/image:latest"},
}
// Make sure that the user input is normalized correctly for
// `podman tag` and `podman untag`.
for _, tt := range tests {
session := podmanTest.PodmanNoCache([]string{"tag", ALPINE, tt.tag})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.PodmanNoCache([]string{"image", "exists", tt.normalized})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.PodmanNoCache([]string{"untag", ALPINE, tt.tag})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.PodmanNoCache([]string{"image", "exists", tt.normalized})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(1))
}
}) })
}) })

35
test/system/020-tag.bats Normal file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env bats
load helpers
# helper function for "podman tag/untag" test
function _tag_and_check() {
local tag_as="$1"
local check_as="$2"
run_podman tag $IMAGE $tag_as
run_podman image exists $check_as
run_podman untag $IMAGE $check_as
run_podman 1 image exists $check_as
}
@test "podman tag/untag" {
# Test a fully-qualified image reference.
_tag_and_check registry.com/image:latest registry.com/image:latest
# Test a reference without tag and make sure ":latest" is appended.
_tag_and_check registry.com/image registry.com/image:latest
# Test a tagged short image and make sure "localhost/" is prepended.
_tag_and_check image:latest localhost/image:latest
# Test a short image without tag and make sure "localhost/" is
# prepended and ":latest" is appended.
_tag_and_check image localhost/image:latest
# Test error case.
run_podman 125 untag $IMAGE registry.com/foo:bar
is "$output" "Error: \"registry.com/foo:bar\": no such tag"
}
# vim: filetype=sh