diff --git a/pkg/registry/reference.go b/pkg/registry/reference.go index f2b57b48b..567dc28de 100644 --- a/pkg/registry/reference.go +++ b/pkg/registry/reference.go @@ -17,11 +17,18 @@ limitations under the License. package registry // import "k8s.io/helm/pkg/registry" import ( + "errors" + "regexp" "strings" "github.com/containerd/containerd/reference" ) +var ( + validPortRegEx = regexp.MustCompile("^([1-9]\\d{0,3}|0|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$") // adapted from https://stackoverflow.com/a/12968117 + tooManyColonsError = errors.New("ref may only contain a single colon character (:) unless specifying a port number") +) + type ( // Reference defines the main components of a reference specification Reference struct { @@ -35,11 +42,67 @@ func ParseReference(s string) (*Reference, error) { if err != nil { return nil, err } + + // convert to our custom type and make necessary mods ref := Reference{&spec} + ref.fix() + + // ensure the reference is valid + err = ref.validate() + if err != nil { + return nil, err + } + return &ref, nil } -// Repo returns a reference's repo minus the hostname -func (ref *Reference) Repo() string { - return strings.TrimPrefix(strings.TrimPrefix(ref.Locator, ref.Hostname()), "/") +// fix modifies references that were potentially not parsed properly +func (spec *Reference) fix() { + spec.fixNoTag() +} + +// fixNoTag is a fix for ref strings such as "mychart:1.0.0", which result in missing tag (object) +func (spec *Reference) fixNoTag() { + if spec.Object == "" { + parts := strings.Split(spec.Locator, ":") + numParts := len(parts) + if 0 < numParts { + lastIndex := numParts - 1 + lastPart := parts[lastIndex] + if !strings.Contains(lastPart, "/") { + spec.Locator = strings.Join(parts[:lastIndex], ":") + spec.Object = lastPart + } + } + } +} + +// validate makes sure the ref meets our criteria +func (spec *Reference) validate() error { + return spec.validateColons() +} + +// validateColons verifies the ref only contains one colon max +// (or two, there might be a port number specified i.e. :5000) +func (spec *Reference) validateColons() error { + if strings.Contains(spec.Object, ":") { + return tooManyColonsError + } + locParts := strings.Split(spec.Locator, ":") + locLastIndex := len(locParts) - 1 + if 1 < locLastIndex { + return tooManyColonsError + } + if 0 < locLastIndex { + port := strings.Split(locParts[locLastIndex], "/")[0] + if !isValidPort(port) { + return tooManyColonsError + } + } + return nil +} + +// isValidPort returns whether or not a string looks like a valid port +func isValidPort(s string) bool { + return validPortRegEx.MatchString(s) } diff --git a/pkg/registry/reference_test.go b/pkg/registry/reference_test.go index f4cb78862..90b7eecbd 100644 --- a/pkg/registry/reference_test.go +++ b/pkg/registry/reference_test.go @@ -25,25 +25,53 @@ import ( func TestReference(t *testing.T) { is := assert.New(t) - // bad ref + // bad refs s := "" _, err := ParseReference(s) - is.Error(err) + is.Error(err, "empty ref") + + s = "my:bad:ref" + _, err = ParseReference(s) + is.Error(err, "ref contains too many colons (2)") + + s = "my:really:bad:ref" + _, err = ParseReference(s) + is.Error(err, "ref contains too many colons (3)") // good refs - s = "localhost:5000/mychart:latest" + s = "mychart:1.5.0" ref, err := ParseReference(s) is.NoError(err) - is.Equal("localhost:5000", ref.Hostname()) - is.Equal("mychart", ref.Repo()) + is.Equal("mychart", ref.Locator) + is.Equal("1.5.0", ref.Object) + + s = "myrepo/mychart:1.5.0" + ref, err = ParseReference(s) + is.NoError(err) + is.Equal("myrepo/mychart", ref.Locator) + is.Equal("1.5.0", ref.Object) + + s = "mychart:5001:1.5.0" + ref, err = ParseReference(s) + is.NoError(err) + is.Equal("mychart:5001", ref.Locator) + is.Equal("1.5.0", ref.Object) + + s = "myrepo:5001/mychart:1.5.0" + ref, err = ParseReference(s) + is.NoError(err) + is.Equal("myrepo:5001/mychart", ref.Locator) + is.Equal("1.5.0", ref.Object) + + s = "localhost:5000/mychart:latest" + ref, err = ParseReference(s) + is.NoError(err) is.Equal("localhost:5000/mychart", ref.Locator) is.Equal("latest", ref.Object) s = "my.host.com/my/nested/repo:1.2.3" ref, err = ParseReference(s) is.NoError(err) - is.Equal("my.host.com", ref.Hostname()) - is.Equal("my/nested/repo", ref.Repo()) is.Equal("my.host.com/my/nested/repo", ref.Locator) is.Equal("1.2.3", ref.Object) }