manifest tests

create, annotate, & push

Signed-off-by: Christy Perez <christy@linux.vnet.ibm.com>
Signed-off-by: Christopher Jones <tophj@linux.vnet.ibm.com>
This commit is contained in:
Christy Perez 2017-09-26 17:15:04 -05:00 committed by Christy Norman Perez
parent 02719bdbb5
commit db6d87216d
11 changed files with 357 additions and 16 deletions

View File

@ -47,11 +47,11 @@ func newAnnotateCommand(dockerCli command.Cli) *cobra.Command {
func runManifestAnnotate(dockerCli command.Cli, opts annotateOptions) error { func runManifestAnnotate(dockerCli command.Cli, opts annotateOptions) error {
targetRef, err := normalizeReference(opts.target) targetRef, err := normalizeReference(opts.target)
if err != nil { if err != nil {
return errors.Wrapf(err, "annotate: Error parsing name for manifest list (%s): %s", opts.target) return errors.Wrapf(err, "annotate: error parsing name for manifest list %s", opts.target)
} }
imgRef, err := normalizeReference(opts.image) imgRef, err := normalizeReference(opts.image)
if err != nil { if err != nil {
return errors.Wrapf(err, "annotate: Error parsing name for manifest (%s): %s:", opts.image) return errors.Wrapf(err, "annotate: error parsing name for manifest %s", opts.image)
} }
manifestStore := dockerCli.ManifestStore() manifestStore := dockerCli.ManifestStore()

View File

@ -0,0 +1,78 @@
package manifest
import (
"io/ioutil"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestManifestAnnotateError(t *testing.T) {
testCases := []struct {
args []string
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"th!si'sa/fa!ke/li$t/name", "example.com/alpine:3.0"},
expectedError: "error parsing name for manifest list",
},
{
args: []string{"example.com/list:v1", "th!si'sa/fa!ke/im@ge/nam32"},
expectedError: "error parsing name for manifest",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(nil)
cmd := newAnnotateCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestManifestAnnotate(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newAnnotateCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/fake:0.0"})
cmd.SetOutput(ioutil.Discard)
expectedError := "manifest for image example.com/fake:0.0 does not exist"
testutil.ErrorContains(t, cmd.Execute(), expectedError)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
cmd.Flags().Set("os", "freebsd")
cmd.Flags().Set("arch", "fake")
cmd.Flags().Set("os-features", "feature1")
cmd.Flags().Set("variant", "v7")
expectedError = "manifest entry for image has unsupported os/arch combination"
testutil.ErrorContains(t, cmd.Execute(), expectedError)
cmd.Flags().Set("arch", "arm")
require.NoError(t, cmd.Execute())
cmd = newInspectCommand(cli)
err = cmd.Flags().Set("verbose", "true")
require.NoError(t, err)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
require.NoError(t, cmd.Execute())
actual := cli.OutBuffer()
expected := golden.Get(t, "inspect-annotate.golden")
assert.Equal(t, string(expected), actual.String())
}

View File

@ -3,7 +3,9 @@ package manifest
import ( import (
manifesttypes "github.com/docker/cli/cli/manifest/types" manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/cli/registry/client" "github.com/docker/cli/cli/registry/client"
"github.com/docker/distribution"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -11,6 +13,8 @@ type fakeRegistryClient struct {
client.RegistryClient client.RegistryClient
getManifestFunc func(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) getManifestFunc func(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error)
getManifestListFunc func(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) getManifestListFunc func(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error)
mountBlobFunc func(ctx context.Context, source reference.Canonical, target reference.Named) error
putManifestFunc func(ctx context.Context, source reference.Named, mf distribution.Manifest) (digest.Digest, error)
} }
func (c *fakeRegistryClient) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) { func (c *fakeRegistryClient) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
@ -26,3 +30,17 @@ func (c *fakeRegistryClient) GetManifestList(ctx context.Context, ref reference.
} }
return nil, nil return nil, nil
} }
func (c *fakeRegistryClient) MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error {
if c.mountBlobFunc != nil {
return c.mountBlobFunc(ctx, source, target)
}
return nil
}
func (c *fakeRegistryClient) PutManifest(ctx context.Context, ref reference.Named, mf distribution.Manifest) (digest.Digest, error) {
if c.putManifestFunc != nil {
return c.putManifestFunc(ctx, ref, mf)
}
return digest.Digest(""), nil
}

View File

@ -39,12 +39,12 @@ func createManifestList(dockerCli command.Cli, args []string, opts createOpts) e
newRef := args[0] newRef := args[0]
targetRef, err := normalizeReference(newRef) targetRef, err := normalizeReference(newRef)
if err != nil { if err != nil {
return errors.Wrapf(err, "error parsing name for manifest list (%s): %v", newRef) return errors.Wrapf(err, "error parsing name for manifest list %s", newRef)
} }
_, err = registry.ParseRepositoryInfo(targetRef) _, err = registry.ParseRepositoryInfo(targetRef)
if err != nil { if err != nil {
return errors.Wrapf(err, "error parsing repository name for manifest list (%s): %v", newRef) return errors.Wrapf(err, "error parsing repository name for manifest list %s", newRef)
} }
manifestStore := dockerCli.ManifestStore() manifestStore := dockerCli.ManifestStore()

View File

@ -0,0 +1,117 @@
package manifest
import (
"io/ioutil"
"testing"
manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/distribution/reference"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestManifestCreateErrors(t *testing.T) {
testCases := []struct {
args []string
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires at least 2 arguments",
},
{
args: []string{"th!si'sa/fa!ke/li$t/name", "example.com/alpine:3.0"},
expectedError: "error parsing name for manifest list",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(nil)
cmd := newCreateListCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
// create a manifest list, then overwrite it, and inspect to see if the old one is still there
func TestManifestCreateAmend(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
namedRef = ref(t, "alpine:3.1")
imageManifest = fullImageManifest(t, namedRef)
err = store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newCreateListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.1"})
cmd.Flags().Set("amend", "true")
cmd.SetOutput(ioutil.Discard)
err = cmd.Execute()
require.NoError(t, err)
// make a new cli to clear the buffers
cli = test.NewFakeCli(nil)
cli.SetManifestStore(store)
inspectCmd := newInspectCommand(cli)
inspectCmd.SetArgs([]string{"example.com/list:v1"})
require.NoError(t, inspectCmd.Execute())
actual := cli.OutBuffer()
expected := golden.Get(t, "inspect-manifest-list.golden")
assert.Equal(t, string(expected), actual.String())
}
// attempt to overwrite a saved manifest and get refused
func TestManifestCreateRefuseAmend(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newCreateListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
cmd.SetOutput(ioutil.Discard)
err = cmd.Execute()
assert.EqualError(t, err, "refusing to amend an existing manifest list with no --amend flag")
}
// attempt to make a manifest list without valid images
func TestManifestCreateNoManifest(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
cli.SetRegistryClient(&fakeRegistryClient{
getManifestFunc: func(_ context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, errors.Errorf("No such image: %v", ref)
},
getManifestListFunc: func(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, errors.Errorf("No such manifest: %s", ref)
},
})
cmd := newCreateListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
cmd.SetOutput(ioutil.Discard)
err := cmd.Execute()
assert.EqualError(t, err, "No such image: example.com/alpine:3.0")
}

View File

@ -51,7 +51,7 @@ func fullImageManifest(t *testing.T, ref reference.Named) types.ImageManifest {
}) })
require.NoError(t, err) require.NoError(t, err)
// TODO: include image data for verbose inspect // TODO: include image data for verbose inspect
return types.NewImageManifest(ref, digest.Digest("abcd"), types.Image{}, man) return types.NewImageManifest(ref, digest.Digest("sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd"), types.Image{OS: "linux", Architecture: "amd64"}, man)
} }
func TestInspectCommandLocalManifestNotFound(t *testing.T) { func TestInspectCommandLocalManifestNotFound(t *testing.T) {

View File

@ -181,7 +181,7 @@ func buildManifestDescriptor(targetRepo *registry.RepositoryInfo, imageManifest
if err = manifest.Descriptor.Digest.Validate(); err != nil { if err = manifest.Descriptor.Digest.Validate(); err != nil {
return manifestlist.ManifestDescriptor{}, errors.Wrapf(err, return manifestlist.ManifestDescriptor{}, errors.Wrapf(err,
"digest parse of image %q failed with error: %v", imageManifest.Ref) "digest parse of image %q failed", imageManifest.Ref)
} }
return manifest, nil return manifest, nil
@ -200,6 +200,7 @@ func buildBlobRequestList(imageManifest types.ImageManifest, repoName reference.
return blobReqs, nil return blobReqs, nil
} }
// nolint: interfacer
func buildPutManifestRequest(imageManifest types.ImageManifest, targetRef reference.Named) (mountRequest, error) { func buildPutManifestRequest(imageManifest types.ImageManifest, targetRef reference.Named) (mountRequest, error) {
refWithoutTag, err := reference.WithName(targetRef.Name()) refWithoutTag, err := reference.WithName(targetRef.Name())
if err != nil { if err != nil {

View File

@ -0,0 +1,73 @@
package manifest
import (
"io/ioutil"
"testing"
manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/distribution/reference"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func newFakeRegistryClient(t *testing.T) *fakeRegistryClient {
require.NoError(t, nil)
return &fakeRegistryClient{
getManifestFunc: func(_ context.Context, _ reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, errors.New("")
},
getManifestListFunc: func(_ context.Context, _ reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, errors.Errorf("")
},
}
}
func TestManifestPushErrors(t *testing.T) {
testCases := []struct {
args []string
expectedError string
}{
{
args: []string{"one-arg", "extra-arg"},
expectedError: "requires exactly 1 argument",
},
{
args: []string{"th!si'sa/fa!ke/li$t/-name"},
expectedError: "invalid reference format",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(nil)
cmd := newPushListCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
// store a one-image manifest list and puah it
func TestManifestPush(t *testing.T) {
store, sCleanup := newTempManifestStore(t)
defer sCleanup()
registry := newFakeRegistryClient(t)
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
cli.SetRegistryClient(registry)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newPushListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1"})
err = cmd.Execute()
require.NoError(t, err)
}

View File

@ -0,0 +1,28 @@
{
"Ref": "example.com/alpine:3.0",
"Digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"SchemaV2Manifest": {
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1520,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d624560"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 1990402,
"digest": "sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926"
}
]
},
"Platform": {
"architecture": "arm",
"os": "freebsd",
"os.features": [
"feature1"
],
"variant": "v7"
}
}

View File

@ -0,0 +1,24 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 428,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 428,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
]
}

View File

@ -9,9 +9,9 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/trust"
manifeststore "github.com/docker/cli/cli/manifest/store" manifeststore "github.com/docker/cli/cli/manifest/store"
registryclient "github.com/docker/cli/cli/registry/client" registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/cli/cli/trust"
"github.com/docker/docker/client" "github.com/docker/docker/client"
notaryclient "github.com/theupdateframework/notary/client" notaryclient "github.com/theupdateframework/notary/client"
) )
@ -22,16 +22,17 @@ type clientInfoFuncType func() command.ClientInfo
// FakeCli emulates the default DockerCli // FakeCli emulates the default DockerCli
type FakeCli struct { type FakeCli struct {
command.DockerCli command.DockerCli
client client.APIClient client client.APIClient
configfile *configfile.ConfigFile configfile *configfile.ConfigFile
out *command.OutStream out *command.OutStream
outBuffer *bytes.Buffer outBuffer *bytes.Buffer
err *bytes.Buffer err *bytes.Buffer
in *command.InStream in *command.InStream
server command.ServerInfo server command.ServerInfo
clientInfoFunc clientInfoFuncType
notaryClientFunc notaryClientFuncType notaryClientFunc notaryClientFuncType
manifestStore manifeststore.Store manifestStore manifeststore.Store
registryClient registryclient.RegistryClient registryClient registryclient.RegistryClient
} }
// NewFakeCli returns a fake for the command.Cli interface // NewFakeCli returns a fake for the command.Cli interface
@ -127,6 +128,7 @@ func (c *FakeCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []st
return c.notaryClientFunc(imgRefAndAuth, actions) return c.notaryClientFunc(imgRefAndAuth, actions)
} }
return nil, fmt.Errorf("no notary client available unless defined") return nil, fmt.Errorf("no notary client available unless defined")
}
// ManifestStore returns a fake store used for testing // ManifestStore returns a fake store used for testing
func (c *FakeCli) ManifestStore() manifeststore.Store { func (c *FakeCli) ManifestStore() manifeststore.Store {