Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 990c55f

Browse files
author
Jean-Christophe Sirot
committed
Add --all-platforms flag to push all architectures. Default is now pushing only amd64. Both flags --all-platforms and —platform are mutually exclusive to avoid any ambiguous commands.
Add a e2e test to validate. Signed-off-by: Jean-Christophe Sirot <[email protected]>
1 parent 98b71d2 commit 990c55f

File tree

4 files changed

+169
-6
lines changed

4 files changed

+169
-6
lines changed

Gopkg.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e/pushpull_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
package e2e
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"math/rand"
67
"net"
8+
"net/http"
79
"path/filepath"
810
"strconv"
911
"strings"
1012
"testing"
1113
"time"
1214

1315
"github.com/docker/app/internal"
16+
"github.com/docker/cnab-to-oci/converter"
17+
"github.com/docker/distribution/manifest/manifestlist"
18+
"github.com/opencontainers/go-digest"
19+
v1 "github.com/opencontainers/image-spec/specs-go/v1"
1420
"gotest.tools/assert"
1521
"gotest.tools/assert/cmp"
1622
"gotest.tools/fs"
@@ -87,6 +93,92 @@ func runWithDindSwarmAndRegistry(t *testing.T, todo func(dindSwarmAndRegistryInf
8793

8894
}
8995

96+
func TestPushArchs(t *testing.T) {
97+
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
98+
testCases := []struct {
99+
name string
100+
args []string
101+
expectedPlatforms []manifestlist.PlatformSpec
102+
}{
103+
{
104+
name: "default",
105+
args: []string{},
106+
expectedPlatforms: []manifestlist.PlatformSpec{
107+
{
108+
OS: "linux",
109+
Architecture: "amd64",
110+
},
111+
},
112+
},
113+
{
114+
name: "all-platforms",
115+
args: []string{"--all-platforms"},
116+
expectedPlatforms: []manifestlist.PlatformSpec{
117+
{
118+
OS: "linux",
119+
Architecture: "amd64",
120+
},
121+
{
122+
OS: "linux",
123+
Architecture: "386",
124+
},
125+
{
126+
OS: "linux",
127+
Architecture: "ppc64le",
128+
},
129+
{
130+
OS: "linux",
131+
Architecture: "s390x",
132+
},
133+
{
134+
OS: "linux",
135+
Architecture: "arm",
136+
Variant: "v5",
137+
},
138+
{
139+
OS: "linux",
140+
Architecture: "arm",
141+
Variant: "v6",
142+
},
143+
{
144+
OS: "linux",
145+
Architecture: "arm",
146+
Variant: "v7",
147+
},
148+
{
149+
OS: "linux",
150+
Architecture: "arm64",
151+
Variant: "v8",
152+
},
153+
},
154+
},
155+
}
156+
157+
for _, testCase := range testCases {
158+
t.Run(testCase.name, func(t *testing.T) {
159+
cmd := info.configuredCmd
160+
ref := info.registryAddress + "/test/push-pull:1"
161+
args := []string{"app", "push", "--tag", ref, "--insecure-registries=" + info.registryAddress}
162+
args = append(args, testCase.args...)
163+
args = append(args, filepath.Join("testdata", "push-pull", "push-pull.dockerapp"))
164+
cmd.Command = dockerCli.Command(args...)
165+
icmd.RunCmd(cmd).Assert(t, icmd.Success)
166+
167+
var index v1.Index
168+
httpGet(t, "http://"+info.registryAddress+"/v2/test/push-pull/manifests/1", &index)
169+
digest, err := getManifestListDigest(index)
170+
assert.NilError(t, err)
171+
var manifestList manifestlist.ManifestList
172+
httpGet(t, "http://"+info.registryAddress+"/v2/test/push-pull/manifests/"+digest.String(), &manifestList)
173+
assert.Equal(t, len(manifestList.Manifests), len(testCase.expectedPlatforms), "Unexpected number of platforms")
174+
for _, m := range manifestList.Manifests {
175+
assert.Assert(t, cmp.Contains(testCase.expectedPlatforms, m.Platform), "Platform expected but not found: %s", m.Platform)
176+
}
177+
})
178+
}
179+
})
180+
}
181+
90182
func TestPushInstall(t *testing.T) {
91183
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
92184
cmd := info.configuredCmd
@@ -198,3 +290,21 @@ func isPortAvailable(port int) bool {
198290
defer l.Close()
199291
return true
200292
}
293+
294+
func httpGet(t *testing.T, url string, obj interface{}) {
295+
r, err := http.Get(url)
296+
assert.NilError(t, err)
297+
defer r.Body.Close()
298+
assert.Equal(t, r.StatusCode, 200)
299+
err = json.NewDecoder(r.Body).Decode(obj)
300+
assert.NilError(t, err)
301+
}
302+
303+
func getManifestListDigest(index v1.Index) (digest.Digest, error) {
304+
for _, m := range index.Manifests {
305+
if m.Annotations[converter.CNABDescriptorTypeAnnotation] == "component" {
306+
return m.Digest, nil
307+
}
308+
}
309+
return "", fmt.Errorf("Service image not found")
310+
}

internal/commands/push.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
ocischemav1 "github.com/opencontainers/image-spec/specs-go/v1"
2525
"github.com/pkg/errors"
2626
"github.com/spf13/cobra"
27+
"github.com/spf13/pflag"
2728
)
2829

2930
const ( // Docker specific annotations and values
@@ -39,9 +40,10 @@ const ( // Docker specific annotations and values
3940
)
4041

4142
type pushOptions struct {
42-
registry registryOptions
43-
tag string
44-
platforms []string
43+
registry registryOptions
44+
tag string
45+
platforms []string
46+
allPlatforms bool
4547
}
4648

4749
func pushCmd(dockerCli command.Cli) *cobra.Command {
@@ -51,13 +53,17 @@ func pushCmd(dockerCli command.Cli) *cobra.Command {
5153
Short: "Push an application package to a registry",
5254
Example: `$ docker app push myapp --tag myrepo/myapp:mytag`,
5355
Args: cli.RequiresMaxArgs(1),
56+
PreRunE: func(cmd *cobra.Command, args []string) error {
57+
return checkFlags(cmd.Flags(), opts)
58+
},
5459
RunE: func(cmd *cobra.Command, args []string) error {
5560
return runPush(dockerCli, firstOrEmpty(args), opts)
5661
},
5762
}
5863
flags := cmd.Flags()
5964
flags.StringVarP(&opts.tag, "tag", "t", "", "Target registry reference (default: <name>:<version> from metadata)")
60-
flags.StringSliceVar(&opts.platforms, "platform", nil, "For multi-arch service images, only push the specified platforms")
65+
flags.StringSliceVar(&opts.platforms, "platform", []string{"linux/amd64"}, "For multi-arch service images, push the specified platforms")
66+
flags.BoolVar(&opts.allPlatforms, "all-platforms", false, "If present, push all platforms")
6167
opts.registry.addFlags(flags)
6268
return cmd
6369
}
@@ -117,8 +123,8 @@ func runPush(dockerCli command.Cli, name string, opts pushOptions) error {
117123
fixupOptions := []remotes.FixupOption{
118124
remotes.WithEventCallback(display.onEvent),
119125
}
120-
if len(opts.platforms) > 0 {
121-
fixupOptions = append(fixupOptions, remotes.WithComponentImagePlatforms(opts.platforms))
126+
if platforms := platformFilter(opts); len(platforms) > 0 {
127+
fixupOptions = append(fixupOptions, remotes.WithComponentImagePlatforms(platforms))
122128
}
123129
// bundle fixup
124130
err = remotes.FixupBundle(context.Background(), bndl, retag.cnabRef, resolverConfig, fixupOptions...)
@@ -144,6 +150,13 @@ func withAppAnnotations(index *ocischemav1.Index) error {
144150
return nil
145151
}
146152

153+
func platformFilter(opts pushOptions) []string {
154+
if opts.allPlatforms {
155+
return nil
156+
}
157+
return opts.platforms
158+
}
159+
147160
func retagInvocationImage(dockerCli command.Cli, bndl *bundle.Bundle, newName string) error {
148161
err := dockerCli.Client().ImageTag(context.Background(), bndl.InvocationImages[0].Image, newName)
149162
if err != nil {
@@ -319,3 +332,10 @@ func (r *plainDisplay) onEvent(ev remotes.FixupEvent) {
319332
}
320333
}
321334
}
335+
336+
func checkFlags(flags *pflag.FlagSet, opts pushOptions) error {
337+
if opts.allPlatforms && flags.Changed("all-platforms") && flags.Changed("platform") {
338+
return fmt.Errorf("--all-plaforms and --plaform flags cannot be used at the same time")
339+
}
340+
return nil
341+
}

internal/commands/push_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,33 @@ func TestInvocationImageRetag(t *testing.T) {
102102
})
103103
}
104104
}
105+
106+
func TestPlatformFilter(t *testing.T) {
107+
cases := []struct {
108+
name string
109+
opts pushOptions
110+
expected []string
111+
}{
112+
{
113+
name: "filtered-platforms",
114+
opts: pushOptions{
115+
allPlatforms: false,
116+
platforms: []string{"linux/amd64", "linux/arm64"},
117+
},
118+
expected: []string{"linux/amd64", "linux/arm64"},
119+
},
120+
{
121+
name: "all-platforms",
122+
opts: pushOptions{
123+
allPlatforms: true,
124+
platforms: []string{"linux/amd64"},
125+
},
126+
expected: nil,
127+
},
128+
}
129+
for _, c := range cases {
130+
t.Run(c.name, func(t *testing.T) {
131+
assert.DeepEqual(t, platformFilter(c.opts), c.expected)
132+
})
133+
}
134+
}

0 commit comments

Comments
 (0)