Skip to content

Commit

Permalink
add Tag implementation to omit default docker registry and namespace,
Browse files Browse the repository at this point in the history
- New tag implementation is used to determine imageName passed to lifecycle phases

Signed-off-by: dwillist <[email protected]>
  • Loading branch information
dwillist committed Aug 26, 2020
1 parent d7503fd commit 51a12be
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 16 deletions.
2 changes: 1 addition & 1 deletion common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
27 changes: 14 additions & 13 deletions common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ 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() {
logger = ilogging.NewLogWithWriters(&outBuf, &outBuf)

var err error
subject, err = NewClient(WithLogger(logger))
h.AssertNil(t, err)
assert.Nil(err)

defaultRegistry = "default.registry.io"
runImageName = "stack/run"
Expand All @@ -60,59 +61,59 @@ 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() {
configMirrors := map[string][]string{
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() {
configMirrors := map[string][]string{
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")
})
})

// If publish is false, we are using the local daemon, and want to match to the builder registry
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() {
configMirrors := map[string][]string{
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() {
configMirrors := map[string][]string{
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")
})
})
})
Expand Down
4 changes: 2 additions & 2 deletions project/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
65 changes: 65 additions & 0 deletions tag.go
Original file line number Diff line number Diff line change
@@ -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:
// <registry>/<namespace>/<name>:<tag>
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 <tag> section.
func (pr Tag) Identifier() string {
return pr.Tag.Identifier()
}

// Name returns the fully-qualified reference name, this is the entire
// <registry>/<namespace>/<name>:<tag> string.
// If <registry> or <namespace> 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)
}
109 changes: 109 additions & 0 deletions tag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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("no registry prefix", 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("keeps index.docker.io/*/ prefix is specified", func() {
it("keeps the index.docker.io/library/ 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")
})

it("keeps the index.docker.io/* 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: ")
})
})
})
}
24 changes: 24 additions & 0 deletions testhelpers/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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()

Expand Down

0 comments on commit 51a12be

Please sign in to comment.