From 34af4cc0a1e3eb815c3673ee4428ebf9784c67b3 Mon Sep 17 00:00:00 2001 From: dwillist Date: Wed, 26 Aug 2020 19:10:07 -0400 Subject: [PATCH] add Tag implementation to omit default docker registry and namespace, - New tag implementation is used to determine imageName passed to lifecycle phases Signed-off-by: dwillist --- common.go | 2 +- common_test.go | 27 +++++----- project/project_test.go | 4 +- tag.go | 65 ++++++++++++++++++++++ tag_test.go | 111 ++++++++++++++++++++++++++++++++++++++ testhelpers/assertions.go | 24 +++++++++ 6 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 tag.go create mode 100644 tag_test.go diff --git a/common.go b/common.go index e69d0278b..cee01f3bb 100644 --- a/common.go +++ b/common.go @@ -20,7 +20,7 @@ func (c *Client) parseTagReference(imageName string) (name.Reference, error) { if _, err := name.ParseReference(imageName, name.WeakValidation); err != nil { return nil, err } - ref, err := name.NewTag(imageName, name.WeakValidation) + ref, err := NewTag(imageName, name.WeakValidation) if err != nil { return nil, fmt.Errorf("'%s' is not a tag reference", imageName) } diff --git a/common_test.go b/common_test.go index 62e6d5de1..90273c78a 100644 --- a/common_test.go +++ b/common_test.go @@ -32,6 +32,7 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { gcrRegistry string gcrRunMirror string stackInfo builder.StackMetadata + assert = h.NewAssertionManager(t) ) it.Before(func() { @@ -39,7 +40,7 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { var err error subject, err = NewClient(WithLogger(logger)) - h.AssertNil(t, err) + assert.Nil(err) defaultRegistry = "default.registry.io" runImageName = "stack/run" @@ -60,14 +61,14 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { it("selects that run image", func() { runImgFlag := "flag/passed-run-image" runImageName := subject.resolveRunImage(runImgFlag, defaultRegistry, "", stackInfo, nil, false) - h.AssertEq(t, runImageName, runImgFlag) + assert.Equal(runImageName, runImgFlag) }) }) when("publish is true", func() { it("defaults to run-image in registry publishing to", func() { runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo, nil, true) - h.AssertEq(t, runImageName, gcrRunMirror) + assert.Equal(runImageName, gcrRunMirror) }) it("prefers config defined run image mirror to stack defined run image mirror", func() { @@ -75,8 +76,8 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { runImageName: []string{defaultRegistry + "/unique-run-img"}, } runImageName := subject.resolveRunImage("", defaultRegistry, "", stackInfo, configMirrors, true) - h.AssertNotEq(t, runImageName, defaultMirror) - h.AssertEq(t, runImageName, defaultRegistry+"/unique-run-img") + assert.NotEqual(runImageName, defaultMirror) + assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) it("returns a config mirror if no match to target registry", func() { @@ -84,8 +85,8 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { runImageName: []string{defaultRegistry + "/unique-run-img"}, } runImageName := subject.resolveRunImage("", "test.registry.io", "", stackInfo, configMirrors, true) - h.AssertNotEq(t, runImageName, defaultMirror) - h.AssertEq(t, runImageName, defaultRegistry+"/unique-run-img") + assert.NotEqual(runImageName, defaultMirror) + assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) }) @@ -93,8 +94,8 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { when("publish is false", func() { it("defaults to run-image in registry publishing to", func() { runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo, nil, false) - h.AssertEq(t, runImageName, defaultMirror) - h.AssertNotEq(t, runImageName, gcrRunMirror) + assert.Equal(runImageName, defaultMirror) + assert.NotEqual(runImageName, gcrRunMirror) }) it("prefers config defined run image mirror to stack defined run image mirror", func() { @@ -102,8 +103,8 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { runImageName: []string{defaultRegistry + "/unique-run-img"}, } runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo, configMirrors, false) - h.AssertNotEq(t, runImageName, defaultMirror) - h.AssertEq(t, runImageName, defaultRegistry+"/unique-run-img") + assert.NotEqual(runImageName, defaultMirror) + assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) it("returns a config mirror if no match to target registry", func() { @@ -111,8 +112,8 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { runImageName: []string{defaultRegistry + "/unique-run-img"}, } runImageName := subject.resolveRunImage("", defaultRegistry, "test.registry.io", stackInfo, configMirrors, false) - h.AssertNotEq(t, runImageName, defaultMirror) - h.AssertEq(t, runImageName, defaultRegistry+"/unique-run-img") + assert.NotEqual(runImageName, defaultMirror) + assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) }) }) diff --git a/project/project_test.go b/project/project_test.go index 6270aaa3c..0c2ee4c23 100644 --- a/project/project_test.go +++ b/project/project_test.go @@ -123,8 +123,8 @@ name = "gallant" expected := 0 if len(projectDescriptor.Build.Env) != 0 { - t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", - expected, string(len(projectDescriptor.Build.Env))) + t.Fatalf("Expected\n-----\n%d\n-----\nbut got\n-----\n%d\n", + expected, len(projectDescriptor.Build.Env)) } for _, envVar := range projectDescriptor.Build.Env { diff --git a/tag.go b/tag.go new file mode 100644 index 000000000..b3eff16d7 --- /dev/null +++ b/tag.go @@ -0,0 +1,65 @@ +package pack + +import ( + "fmt" + "strings" + + "github.com/google/go-containerregistry/pkg/name" +) + +// Default Namespace used by docker +const defaultNamespace = "library" + +// Tag stores a docker tag name with the following form: +// //: +type Tag struct { + name.Tag + dockerPrefixSpecified bool +} + +// NewTag creates a new for the given tagName. +// This tag will be validated against strictness defined by the opts. +func NewTag(tagName string, opts ...name.Option) (Tag, error) { + minPrefix := fmt.Sprintf("%s/", name.DefaultRegistry) + tag, err := name.NewTag(tagName, opts...) + if err != nil { + return Tag{}, fmt.Errorf("error creating tag: %q", err) + } + return Tag{ + Tag: tag, + dockerPrefixSpecified: strings.HasPrefix(tagName, minPrefix), + }, nil +} + +// Context accesses the Repository context of the reference. +func (pr Tag) Context() name.Repository { + return pr.Tag.Context() +} + +// Identifier accesses the type-specific portion of the reference. +// This corresponds to the section. +func (pr Tag) Identifier() string { + return pr.Tag.Identifier() +} + +// Name returns the fully-qualified reference name, this is the entire +// //: string. +// If or were omitted during the creation of this Tag, they will be missing from +// the string returned by this function. +func (pr Tag) Name() string { + minPrefix := fmt.Sprintf("%s/", name.DefaultRegistry) + maxPrefix := fmt.Sprintf("%s%s/", minPrefix, defaultNamespace) + + result := pr.Tag.Name() + + if pr.Registry.RegistryStr() == name.DefaultRegistry && !pr.dockerPrefixSpecified { + result = strings.TrimPrefix(result, maxPrefix) + result = strings.TrimPrefix(result, minPrefix) + } + return result +} + +// Scope is the scope needed to access this reference. +func (pr Tag) Scope(action string) string { + return pr.Tag.Scope(action) +} diff --git a/tag_test.go b/tag_test.go new file mode 100644 index 000000000..2c9980d70 --- /dev/null +++ b/tag_test.go @@ -0,0 +1,111 @@ +package pack_test + +import ( + "testing" + + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestTag(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testTag, spec.Report(report.Terminal{})) +} + +func testTag(t *testing.T, when spec.G, it spec.S) { + var ( + tag pack.Tag + err error + assert = h.NewAssertionManager(t) + ) + when("Tag", func() { + it.Before(func() { + tag, err = pack.NewTag("gcr.io/repo/image-name:tag-name") + assert.Nil(err) + }) + + when("#Identifier", func() { + it("returns specified tag", func() { + assert.Equal(tag.Identifier(), "tag-name") + }) + it("defaults to latest if no tag is specified", func() { + tag, err = pack.NewTag("gcr.io/repo/image-name") + assert.Nil(err) + assert.Equal(tag.Identifier(), "latest") + }) + }) + + when("#Scope", func() { + it("returns scope required to access this reference", func() { + h.AssertEq( + t, + tag.Scope("some-action"), + "repository:repo/image-name:some-action", + ) + }) + }) + + when("#Context", func() { + when("registry is omitted", func() { + it.Before(func() { + tag, err = pack.NewTag("image-name:tag-name") + assert.Nil(err) + }) + it("keeps index.docker.io/library prefix", func() { + assert.Equal(tag.Context().String(), "index.docker.io/library/image-name") + }) + }) + when("registry is not omitted", func() { + it("keeps registry context", func() { + assert.Equal(tag.Context().String(), "gcr.io/repo/image-name") + }) + }) + }) + + when("#Name", func() { + when("registry prefix is omitted", func() { + it.Before(func() { + tag, err = pack.NewTag("image-name:tag-name") + assert.Nil(err) + }) + it("omits index.docker.io/library/ prefix", func() { + assert.Equal(tag.Name(), "image-name:tag-name") + }) + }) + + when("registry prefix is provided", func() { + it("keeps specified prefix", func() { + assert.Equal(tag.Name(), "gcr.io/repo/image-name:tag-name") + }) + + when("index.docker.io/library/ prefix is specified", func() { + it("keeps the prefix", func() { + tag, err = pack.NewTag("index.docker.io/library/image-name:tag-name") + assert.Nil(err) + assert.Equal(tag.Name(), "index.docker.io/library/image-name:tag-name") + }) + + when("index.docker.io prefix is specified", func() { + it("keeps the prefix", func() { + tag, err = pack.NewTag("index.docker.io/other/image-name:tag-name") + assert.Nil(err) + assert.Equal(tag.Name(), "index.docker.io/other/image-name:tag-name") + }) + }) + }) + }) + }) + + when("Error cases", func() { + it("passed malformed tagName", func() { + _, err := pack.NewTag((":::")) + assert.ErrorContains(err, "error creating tag: ") + }) + }) + }) +} diff --git a/testhelpers/assertions.go b/testhelpers/assertions.go index cfeedf30e..8aeab8cfe 100644 --- a/testhelpers/assertions.go +++ b/testhelpers/assertions.go @@ -65,6 +65,24 @@ func (a AssertionManager) Equal(actual, expected interface{}) { } } +func (a AssertionManager) NotEqual(actual, expected interface{}) { + a.testObject.Helper() + + if diff := cmp.Diff(actual, expected); diff == "" { + a.testObject.Fatalf(diff) + } +} + +func (a AssertionManager) ErrorContains(actual error, expected string) { + a.testObject.Helper() + + if actual == nil { + a.testObject.Fatal("Expected an error but got nil") + } + + a.Contains(actual.Error(), expected) +} + func (a AssertionManager) Nil(actual interface{}) { a.testObject.Helper() @@ -73,6 +91,12 @@ func (a AssertionManager) Nil(actual interface{}) { } } +func (a AssertionManager) Succeeds(actual interface{}) { + a.testObject.Helper() + + a.Nil(actual) +} + func (a AssertionManager) NilWithMessage(actual interface{}, message string) { a.testObject.Helper()