Skip to content

Commit

Permalink
Add option to push all tags
Browse files Browse the repository at this point in the history
This seeks to mirror `docker push --all-tags IMAGE`.

Because tags are appended to the destination, this will only
work with the docker transport. Otherwise you'd get directories
overwriting or with weird names.

Also note that if AllTags is true then the user must provide the
name of the image only; providing a tag will crash.

Docker has this behavior:
```
tag can't be used with --all-tags/-a
```

Requirement for containers/podman#14949

Signed-off-by: Brian Yarbrough <[email protected]>
  • Loading branch information
byarbrough committed Jul 21, 2022
1 parent 8a240c0 commit 951cdd6
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 7 deletions.
55 changes: 48 additions & 7 deletions libimage/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package libimage

import (
"context"
"fmt"
"strings"
"time"

dockerTransport "github.com/containers/image/v5/docker"
dockerArchiveTransport "github.com/containers/image/v5/docker/archive"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/transports/alltransports"
Expand All @@ -13,6 +16,7 @@ import (
// PushOptions allows for custommizing image pushes.
type PushOptions struct {
CopyOptions
AllTags bool
}

// Push pushes the specified source which must refer to an image in the local
Expand All @@ -36,11 +40,6 @@ func (r *Runtime) Push(ctx context.Context, source, destination string, options
return nil, err
}

srcRef, err := image.StorageReference()
if err != nil {
return nil, err
}

// Make sure we have a proper destination, and parse it into an image
// reference for copying.
if destination == "" {
Expand All @@ -50,7 +49,44 @@ func (r *Runtime) Push(ctx context.Context, source, destination string, options
destination = resolvedSource
}

logrus.Debugf("Pushing image %s to %s", source, destination)
// If specified to push --all-tags, look them up and iterate.
if options.AllTags {

// Do not allow : for tags, other than specifying transport
d := strings.TrimPrefix(destination, "docker://")
if strings.ContainsAny(d, ":") {
return nil, fmt.Errorf("tag can't be used with --all-tags/-a")
}

namedRepoTags, err := image.NamedTaggedRepoTags()
if err != nil {
return nil, err
}

logrus.Debugf("Flag --all-tags true, found: %s", namedRepoTags)

for _, tag := range namedRepoTags {
fullNamedTag := fmt.Sprintf("%s:%s", destination, tag.Tag())
_, err = pushImage(ctx, fullNamedTag, options, image, r)
if err != nil {
return nil, err
}
}
} else {
// No --all-tags, so just push just the single image.
return pushImage(ctx, destination, options, image, r)
}

return nil, nil
}

func pushImage(ctx context.Context, destination string, options *PushOptions, image *Image, r *Runtime) ([]byte, error) {
srcRef, err := image.StorageReference()
if err != nil {
return nil, err
}

logrus.Debugf("Pushing image %s to %s", srcRef, destination)

destRef, err := alltransports.ParseImageName(destination)
if err != nil {
Expand All @@ -63,14 +99,19 @@ func (r *Runtime) Push(ctx context.Context, source, destination string, options
destRef = dockerRef
}

// If using --all-tags, must push to registry
if destRef.Transport().Name() != dockerTransport.Transport.Name() && options.AllTags {
return nil, fmt.Errorf("--all-tags can only be used with docker transport")
}

if r.eventChannel != nil {
defer r.writeEvent(&Event{ID: image.ID(), Name: destination, Time: time.Now(), Type: EventTypeImagePush})
}

// Buildah compat: Make sure to tag the destination image if it's a
// Docker archive. This way, we preserve the image name.
if destRef.Transport().Name() == dockerArchiveTransport.Transport.Name() {
if named, err := reference.ParseNamed(resolvedSource); err == nil {
if named, err := reference.ParseNamed(destination); err == nil {
tagged, isTagged := named.(reference.NamedTagged)
if isTagged {
options.dockerArchiveAdditionalTags = []reference.NamedTagged{tagged}
Expand Down
56 changes: 56 additions & 0 deletions libimage/push_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,62 @@ func TestPush(t *testing.T) {
}
}

func TestPushAllTags(t *testing.T) {
runtime, cleanup := testNewRuntime(t)
defer cleanup()
ctx := context.Background()

// Prefetch alpine.
pullOptions := &PullOptions{}
pullOptions.Writer = os.Stdout
_, err := runtime.Pull(ctx, "docker.io/library/alpine:latest", config.PullPolicyAlways, pullOptions)
require.NoError(t, err)

pushOptions := &PushOptions{}
pushOptions.AllTags = true
pushOptions.Writer = os.Stdout

workdir, err := ioutil.TempDir("", "libimagepush")
require.NoError(t, err)
defer os.RemoveAll(workdir)

// tag image with alternates
lookupOptions := &LookupImageOptions{}
img, _, err := runtime.LookupImage("alpine", lookupOptions)
require.NoError(t, err)
img.Tag("01")
img.Tag("02")

for _, test := range []struct {
source string
destination string
expectError bool
}{
{"alpine", "dir:" + workdir + "/dir", true},
{"alpine", "containers-storage:localhost/another:alpine", true},
{"alpine", "docker://docker.io/library/alpine:latest", true},
{"alpine", "docker://docker.io/library/alpine", false},
{"alpine", "docker.io/library/alpine", false},
} {
_, err := runtime.Push(ctx, test.source, test.destination, pushOptions)
if test.expectError {
require.Error(t, err, "%v", test)
continue
}
require.NoError(t, err, "%v", test)
pullOptions.AllTags = true
pulledImages, err := runtime.Pull(ctx, test.destination, config.PullPolicyAlways, pullOptions)
require.NoError(t, err, "%v", test)
require.Len(t, pulledImages, 2, "%v", test)
}

// And now remove all of them.
rmReports, rmErrors := runtime.RemoveImages(ctx, nil, nil)
require.Len(t, rmErrors, 0)
require.Len(t, rmReports, 3)

}

func TestPushOtherPlatform(t *testing.T) {
runtime, cleanup := testNewRuntime(t)
defer cleanup()
Expand Down

0 comments on commit 951cdd6

Please sign in to comment.