Skip to content

Commit 7bc7d2a

Browse files
committed
manifest push using yaml file
Instead of: manifest create list image1 image2 manifest annotate list image1 manifest push list You can do: manifiest push --file=true myfile.yaml Signed-off-by: Christy Norman <christy@linux.vnet.ibm.com>
1 parent 8de0753 commit 7bc7d2a

16 files changed

Lines changed: 442 additions & 43 deletions

File tree

cli/command/manifest/annotate.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ func runManifestAnnotate(dockerCli command.Cli, opts annotateOptions) error {
7777
imageManifest.Platform.Variant = opts.variant
7878
}
7979

80-
if !isValidOSArch(imageManifest.Platform.OS, imageManifest.Platform.Architecture) {
81-
return errors.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
80+
if err := validateOSArch(imageManifest.Platform.OS, imageManifest.Platform.Architecture); err != nil {
81+
return err
8282
}
8383
return manifestStore.Save(targetRef, imgRef, imageManifest)
8484
}

cli/command/manifest/push.go

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8+
"io/ioutil"
89

910
"github.com/docker/cli/cli"
1011
"github.com/docker/cli/cli/command"
@@ -14,13 +15,16 @@ import (
1415
"github.com/docker/distribution/manifest/schema2"
1516
"github.com/docker/distribution/reference"
1617
"github.com/docker/docker/registry"
18+
1719
"github.com/pkg/errors"
1820
"github.com/spf13/cobra"
21+
yaml "gopkg.in/yaml.v2"
1922
)
2023

2124
type pushOpts struct {
2225
insecure bool
2326
purge bool
27+
file bool
2428
target string
2529
}
2630

@@ -42,32 +46,48 @@ type pushRequest struct {
4246
insecure bool
4347
}
4448

49+
type yamlManifestList struct {
50+
Image string
51+
Manifests []yamlManifest
52+
}
53+
54+
type yamlManifest struct {
55+
Image string
56+
Platform manifestlist.PlatformSpec
57+
}
58+
4559
func newPushListCommand(dockerCli command.Cli) *cobra.Command {
4660
opts := pushOpts{}
4761

4862
cmd := &cobra.Command{
4963
Use: "push [OPTIONS] MANIFEST_LIST",
50-
Short: "Push a manifest list to a repository",
64+
Short: "Push a manifest list to a repository, either after a create, or from a file",
5165
Args: cli.ExactArgs(1),
5266
RunE: func(cmd *cobra.Command, args []string) error {
5367
opts.target = args[0]
5468
return runPush(dockerCli, opts)
5569
},
5670
}
57-
5871
flags := cmd.Flags()
59-
flags.BoolVarP(&opts.purge, "purge", "p", false, "Remove the local manifest list after push")
60-
flags.BoolVar(&opts.insecure, "insecure", false, "Allow push to an insecure registry")
72+
flags.BoolVarP(&opts.purge, "purge", "p", false, "remove the local manifests after push")
73+
flags.BoolVar(&opts.insecure, "insecure", false, "allow push to an insecure registry")
74+
flags.BoolVar(&opts.file, "file", false, "use a file containing the yaml representation of manifest list")
6175
return cmd
6276
}
6377

6478
func runPush(dockerCli command.Cli, opts pushOpts) error {
79+
if opts.file {
80+
return pushListFromYaml(dockerCli, opts.target, opts.insecure)
81+
}
82+
83+
return pushListFromStore(dockerCli, opts)
84+
}
6585

86+
func pushListFromStore(dockerCli command.Cli, opts pushOpts) error {
6687
targetRef, err := normalizeReference(opts.target)
6788
if err != nil {
6889
return err
6990
}
70-
7191
manifests, err := dockerCli.ManifestStore().GetList(targetRef)
7292
if err != nil {
7393
return err
@@ -271,3 +291,68 @@ func mountBlobs(ctx context.Context, client registryclient.RegistryClient, ref r
271291
}
272292
return nil
273293
}
294+
295+
func pushListFromYaml(dockerCli command.Cli, file string, insecure bool) error {
296+
yamlInput, err := getYamlManifestList(file)
297+
if err != nil {
298+
return err
299+
}
300+
if len(yamlInput.Manifests) == 0 {
301+
return errors.Errorf("no manifests specified in file input")
302+
}
303+
304+
targetRef, err := normalizeReference(yamlInput.Image)
305+
if err != nil {
306+
return err
307+
}
308+
309+
ctx := context.Background()
310+
var manifests []types.ImageManifest
311+
for _, manifest := range yamlInput.Manifests {
312+
imageRef, err := normalizeReference(manifest.Image)
313+
if err != nil {
314+
return err
315+
}
316+
im, err := dockerCli.RegistryClient(insecure).GetManifest(ctx, imageRef)
317+
if err != nil {
318+
return err
319+
}
320+
addYamlAnnotations(&im, manifest)
321+
if err := validateOSArch(im.Platform.OS, im.Platform.Architecture); err != nil {
322+
return err
323+
}
324+
manifests = append(manifests, im)
325+
}
326+
327+
pushRequest, err := buildPushRequest(manifests, targetRef, insecure)
328+
if err != nil {
329+
return err
330+
}
331+
return pushList(ctx, dockerCli, pushRequest)
332+
}
333+
334+
func addYamlAnnotations(manifest *types.ImageManifest, ym yamlManifest) {
335+
336+
if ym.Platform.Variant != "" {
337+
manifest.Platform.Variant = ym.Platform.Variant
338+
}
339+
if ym.Platform.OS != "" {
340+
manifest.Platform.OS = ym.Platform.OS
341+
}
342+
if ym.Platform.Architecture != "" {
343+
manifest.Platform.Architecture = ym.Platform.Architecture
344+
}
345+
if len(ym.Platform.OSFeatures) != 0 {
346+
manifest.Platform.OSFeatures = ym.Platform.OSFeatures
347+
}
348+
}
349+
350+
func getYamlManifestList(yamlFile string) (yamlManifestList, error) {
351+
var yamlInput yamlManifestList
352+
353+
yamlBuf, err := ioutil.ReadFile(yamlFile)
354+
if err != nil {
355+
return yamlManifestList{}, err
356+
}
357+
return yamlInput, yaml.UnmarshalStrict(yamlBuf, &yamlInput)
358+
}

cli/command/manifest/push_test.go

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import (
1515
func newFakeRegistryClient() *fakeRegistryClient {
1616
return &fakeRegistryClient{
1717
getManifestFunc: func(_ context.Context, _ reference.Named) (manifesttypes.ImageManifest, error) {
18-
return manifesttypes.ImageManifest{}, errors.New("")
18+
return manifesttypes.ImageManifest{}, errors.New("getManifestFunc not implemented")
1919
},
2020
getManifestListFunc: func(_ context.Context, _ reference.Named) ([]manifesttypes.ImageManifest, error) {
21-
return nil, errors.Errorf("")
21+
return nil, errors.Errorf("getManifestListFunc not implemented")
2222
},
2323
}
2424
}
@@ -67,3 +67,60 @@ func TestManifestPush(t *testing.T) {
6767
err = cmd.Execute()
6868
assert.NilError(t, err)
6969
}
70+
71+
func TestPushFromYaml(t *testing.T) {
72+
cli := test.NewFakeCli(nil)
73+
cli.SetRegistryClient(&fakeRegistryClient{
74+
getManifestFunc: func(_ context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
75+
return fullImageManifest(t, ref), nil
76+
},
77+
})
78+
79+
cmd := newPushListCommand(cli)
80+
cmd.Flags().Set("file", "true")
81+
cmd.SetArgs([]string{"testdata/test-push.yaml"})
82+
assert.NilError(t, cmd.Execute())
83+
}
84+
85+
func TestManifestPushYamlErrors(t *testing.T) {
86+
testCases := []struct {
87+
flags map[string]string
88+
args []string
89+
expectedError string
90+
}{
91+
{
92+
flags: map[string]string{"file": "true"},
93+
args: []string{"testdata/test-push-fail.yaml"},
94+
expectedError: "manifest entry for image has unsupported os/arch combination: linux/nope",
95+
},
96+
{
97+
flags: map[string]string{"file": "true"},
98+
args: []string{"testdata/test-push-empty.yaml"},
99+
expectedError: "no manifests specified in file input",
100+
},
101+
{
102+
args: []string{"testdata/test-push-empty.yaml"},
103+
expectedError: "No such manifest: docker.io/testdata/test-push-empty.yaml:latest",
104+
},
105+
}
106+
107+
store, sCleanup := newTempManifestStore(t)
108+
defer sCleanup()
109+
for _, tc := range testCases {
110+
cli := test.NewFakeCli(nil)
111+
cli.SetRegistryClient(&fakeRegistryClient{
112+
getManifestFunc: func(_ context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
113+
return fullImageManifest(t, ref), nil
114+
},
115+
})
116+
117+
cli.SetManifestStore(store)
118+
cmd := newPushListCommand(cli)
119+
for k, v := range tc.flags {
120+
cmd.Flags().Set(k, v)
121+
}
122+
cmd.SetArgs(tc.args)
123+
cmd.SetOutput(ioutil.Discard)
124+
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
125+
}
126+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
image: test/hello-world:latest
2+
manifests:
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
image: test/hello-world:latest
2+
manifests:
3+
-
4+
image: test/hello-world-ppc64le:latest
5+
platform:
6+
architecture: nope
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
image: test/hello-world:latest
2+
manifests:
3+
-
4+
image: test/hello-world-ppc64le:latest
5+
platform:
6+
architecture: ppc64le
7+
-
8+
image: test/hello-world-amd64:latest
9+
platform:
10+
architecture: amd64
11+
os: linux
12+
-
13+
image: test/hello-world-s390x:latest
14+
platform:
15+
architecture: s390x
16+
os: linux
17+
osversion: 1.1
18+
variant: xyz
19+
osfeatures: [a,b,c]
20+
-
21+
image: test/hello-world-armv5:latest
22+
platform:
23+
-
24+
image: test/hello-world:armhf
25+
platform:
26+
architecture: arm
27+
os: linux
28+
variant: abc
29+

cli/command/manifest/util.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package manifest
22

33
import (
44
"context"
5+
"fmt"
56

67
"github.com/docker/cli/cli/command"
78
"github.com/docker/cli/cli/manifest/store"
@@ -14,6 +15,18 @@ type osArch struct {
1415
arch string
1516
}
1617

18+
type invalidOSArchErr struct {
19+
osArch
20+
}
21+
22+
func (e *invalidOSArchErr) Error() string {
23+
return fmt.Sprintf("manifest entry for image has unsupported os/arch combination: %s/%s", e.os, e.arch)
24+
}
25+
26+
func newInvalidOSArchErr(os1 string, arch1 string) *invalidOSArchErr {
27+
return &invalidOSArchErr{osArch{os: os1, arch: arch1}}
28+
}
29+
1730
// Remove any unsupported os/arch combo
1831
// list of valid os/arch values (see "Optional Environment Variables" section
1932
// of https://golang.org/doc/install/source
@@ -48,10 +61,13 @@ var validOSArches = map[osArch]bool{
4861
{os: "windows", arch: "amd64"}: true,
4962
}
5063

51-
func isValidOSArch(os string, arch string) bool {
64+
func validateOSArch(os string, arch string) error {
5265
// check for existence of this combo
5366
_, ok := validOSArches[osArch{os, arch}]
54-
return ok
67+
if !ok {
68+
return newInvalidOSArchErr(os, arch)
69+
}
70+
return nil
5571
}
5672

5773
func normalizeReference(ref string) (reference.Named, error) {

vendor.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
8383
google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9
8484
google.golang.org/grpc v1.12.0
8585
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
86-
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
86+
gopkg.in/yaml.v2 d670f9405373e636a5a2765eea47fac0c9bc91a4
8787
k8s.io/api kubernetes-1.8.2
8888
k8s.io/apimachinery kubernetes-1.8.2
8989
k8s.io/client-go kubernetes-1.8.2

0 commit comments

Comments
 (0)