Skip to content

Commit

Permalink
manifest push using yaml file
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
clnperez committed Jun 6, 2018
1 parent 90f8ce8 commit b25349f
Show file tree
Hide file tree
Showing 16 changed files with 442 additions and 43 deletions.
4 changes: 2 additions & 2 deletions cli/command/manifest/annotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ func runManifestAnnotate(dockerCli command.Cli, opts annotateOptions) error {
imageManifest.Platform.Variant = opts.variant
}

if !isValidOSArch(imageManifest.Platform.OS, imageManifest.Platform.Architecture) {
return errors.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
if err := validateOSArch(imageManifest.Platform.OS, imageManifest.Platform.Architecture); err != nil {
return err
}
return manifestStore.Save(targetRef, imgRef, imageManifest)
}
Expand Down
95 changes: 90 additions & 5 deletions cli/command/manifest/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
Expand All @@ -14,13 +15,16 @@ import (
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference"
"github.com/docker/docker/registry"

"github.com/pkg/errors"
"github.com/spf13/cobra"
yaml "gopkg.in/yaml.v2"
)

type pushOpts struct {
insecure bool
purge bool
file bool
target string
}

Expand All @@ -42,32 +46,48 @@ type pushRequest struct {
insecure bool
}

type yamlManifestList struct {
Image string
Manifests []yamlManifest
}

type yamlManifest struct {
Image string
Platform manifestlist.PlatformSpec
}

func newPushListCommand(dockerCli command.Cli) *cobra.Command {
opts := pushOpts{}

cmd := &cobra.Command{
Use: "push [OPTIONS] MANIFEST_LIST",
Short: "Push a manifest list to a repository",
Short: "Push a manifest list to a repository, either after a create, or from a file",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.target = args[0]
return runPush(dockerCli, opts)
},
}

flags := cmd.Flags()
flags.BoolVarP(&opts.purge, "purge", "p", false, "Remove the local manifest list after push")
flags.BoolVar(&opts.insecure, "insecure", false, "Allow push to an insecure registry")
flags.BoolVarP(&opts.purge, "purge", "p", false, "remove the local manifests after push")
flags.BoolVar(&opts.insecure, "insecure", false, "allow push to an insecure registry")
flags.BoolVar(&opts.file, "file", false, "use a file containing the yaml representation of manifest list")
return cmd
}

func runPush(dockerCli command.Cli, opts pushOpts) error {
if opts.file {
return pushListFromYaml(dockerCli, opts.target, opts.insecure)
}

return pushListFromStore(dockerCli, opts)
}

func pushListFromStore(dockerCli command.Cli, opts pushOpts) error {
targetRef, err := normalizeReference(opts.target)
if err != nil {
return err
}

manifests, err := dockerCli.ManifestStore().GetList(targetRef)
if err != nil {
return err
Expand Down Expand Up @@ -271,3 +291,68 @@ func mountBlobs(ctx context.Context, client registryclient.RegistryClient, ref r
}
return nil
}

func pushListFromYaml(dockerCli command.Cli, file string, insecure bool) error {
yamlInput, err := getYamlManifestList(file)
if err != nil {
return err
}
if len(yamlInput.Manifests) == 0 {
return errors.Errorf("no manifests specified in file input")
}

targetRef, err := normalizeReference(yamlInput.Image)
if err != nil {
return err
}

ctx := context.Background()
var manifests []types.ImageManifest
for _, manifest := range yamlInput.Manifests {
imageRef, err := normalizeReference(manifest.Image)
if err != nil {
return err
}
im, err := dockerCli.RegistryClient(insecure).GetManifest(ctx, imageRef)
if err != nil {
return err
}
addYamlAnnotations(&im, manifest)
if err := validateOSArch(im.Platform.OS, im.Platform.Architecture); err != nil {
return err
}
manifests = append(manifests, im)
}

pushRequest, err := buildPushRequest(manifests, targetRef, insecure)
if err != nil {
return err
}
return pushList(ctx, dockerCli, pushRequest)
}

func addYamlAnnotations(manifest *types.ImageManifest, ym yamlManifest) {

if ym.Platform.Variant != "" {
manifest.Platform.Variant = ym.Platform.Variant
}
if ym.Platform.OS != "" {
manifest.Platform.OS = ym.Platform.OS
}
if ym.Platform.Architecture != "" {
manifest.Platform.Architecture = ym.Platform.Architecture
}
if len(ym.Platform.OSFeatures) != 0 {
manifest.Platform.OSFeatures = ym.Platform.OSFeatures
}
}

func getYamlManifestList(yamlFile string) (yamlManifestList, error) {
var yamlInput yamlManifestList

yamlBuf, err := ioutil.ReadFile(yamlFile)
if err != nil {
return yamlManifestList{}, err
}
return yamlInput, yaml.UnmarshalStrict(yamlBuf, &yamlInput)
}
61 changes: 59 additions & 2 deletions cli/command/manifest/push_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import (
func newFakeRegistryClient() *fakeRegistryClient {
return &fakeRegistryClient{
getManifestFunc: func(_ context.Context, _ reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, errors.New("")
return manifesttypes.ImageManifest{}, errors.New("getManifestFunc not implemented")
},
getManifestListFunc: func(_ context.Context, _ reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, errors.Errorf("")
return nil, errors.Errorf("getManifestListFunc not implemented")
},
}
}
Expand Down Expand Up @@ -67,3 +67,60 @@ func TestManifestPush(t *testing.T) {
err = cmd.Execute()
assert.NilError(t, err)
}

func TestPushFromYaml(t *testing.T) {
cli := test.NewFakeCli(nil)
cli.SetRegistryClient(&fakeRegistryClient{
getManifestFunc: func(_ context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
return fullImageManifest(t, ref), nil
},
})

cmd := newPushListCommand(cli)
cmd.Flags().Set("file", "true")
cmd.SetArgs([]string{"testdata/test-push.yaml"})
assert.NilError(t, cmd.Execute())
}

func TestManifestPushYamlErrors(t *testing.T) {
testCases := []struct {
flags map[string]string
args []string
expectedError string
}{
{
flags: map[string]string{"file": "true"},
args: []string{"testdata/test-push-fail.yaml"},
expectedError: "manifest entry for image has unsupported os/arch combination: linux/nope",
},
{
flags: map[string]string{"file": "true"},
args: []string{"testdata/test-push-empty.yaml"},
expectedError: "no manifests specified in file input",
},
{
args: []string{"testdata/test-push-empty.yaml"},
expectedError: "No such manifest: docker.io/testdata/test-push-empty.yaml:latest",
},
}

store, sCleanup := newTempManifestStore(t)
defer sCleanup()
for _, tc := range testCases {
cli := test.NewFakeCli(nil)
cli.SetRegistryClient(&fakeRegistryClient{
getManifestFunc: func(_ context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
return fullImageManifest(t, ref), nil
},
})

cli.SetManifestStore(store)
cmd := newPushListCommand(cli)
for k, v := range tc.flags {
cmd.Flags().Set(k, v)
}
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
2 changes: 2 additions & 0 deletions cli/command/manifest/testdata/test-push-empty.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
image: test/hello-world:latest
manifests:
6 changes: 6 additions & 0 deletions cli/command/manifest/testdata/test-push-fail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
image: test/hello-world:latest
manifests:
-
image: test/hello-world-ppc64le:latest
platform:
architecture: nope
29 changes: 29 additions & 0 deletions cli/command/manifest/testdata/test-push.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
image: test/hello-world:latest
manifests:
-
image: test/hello-world-ppc64le:latest
platform:
architecture: ppc64le
-
image: test/hello-world-amd64:latest
platform:
architecture: amd64
os: linux
-
image: test/hello-world-s390x:latest
platform:
architecture: s390x
os: linux
osversion: 1.1
variant: xyz
osfeatures: [a,b,c]
-
image: test/hello-world-armv5:latest
platform:
-
image: test/hello-world:armhf
platform:
architecture: arm
os: linux
variant: abc

20 changes: 18 additions & 2 deletions cli/command/manifest/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package manifest

import (
"context"
"fmt"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
Expand All @@ -14,6 +15,18 @@ type osArch struct {
arch string
}

type invalidOSArchErr struct {
osArch
}

func (e *invalidOSArchErr) Error() string {
return fmt.Sprintf("manifest entry for image has unsupported os/arch combination: %s/%s", e.os, e.arch)
}

func newInvalidOSArchErr(os1 string, arch1 string) *invalidOSArchErr {
return &invalidOSArchErr{osArch{os: os1, arch: arch1}}
}

// Remove any unsupported os/arch combo
// list of valid os/arch values (see "Optional Environment Variables" section
// of https://golang.org/doc/install/source
Expand Down Expand Up @@ -48,10 +61,13 @@ var validOSArches = map[osArch]bool{
{os: "windows", arch: "amd64"}: true,
}

func isValidOSArch(os string, arch string) bool {
func validateOSArch(os string, arch string) error {
// check for existence of this combo
_, ok := validOSArches[osArch{os, arch}]
return ok
if !ok {
return newInvalidOSArchErr(os, arch)
}
return nil
}

func normalizeReference(ref string) (reference.Named, error) {
Expand Down
2 changes: 1 addition & 1 deletion vendor.conf
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
google.golang.org/grpc v1.3.0
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
gopkg.in/yaml.v2 d670f9405373e636a5a2765eea47fac0c9bc91a4
k8s.io/api kubernetes-1.8.2
k8s.io/apimachinery kubernetes-1.8.2
k8s.io/client-go kubernetes-1.8.2
Expand Down
Loading

0 comments on commit b25349f

Please sign in to comment.