You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
helm/pkg/registry/reference.go

135 lines
3.4 KiB

/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry // import "helm.sh/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
errEmptyRepo = errors.New("parsed repo was empty")
errTooManyColons = 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 {
*reference.Spec
Tag string
Repo string
}
)
// ParseReference converts a string to a Reference
func ParseReference(s string) (*Reference, error) {
spec, err := reference.Parse(s)
if err != nil {
return nil, err
}
// convert to our custom type and make necessary mods
ref := Reference{Spec: &spec}
ref.setExtraFields()
// ensure the reference is valid
err = ref.validate()
if err != nil {
return nil, err
}
return &ref, nil
}
// setExtraFields adds the Repo and Tag fields to a Reference
func (ref *Reference) setExtraFields() {
ref.Tag = ref.Object
ref.Repo = ref.Locator
ref.fixNoTag()
ref.fixNoRepo()
}
// fixNoTag is a fix for ref strings such as "mychart:1.0.0", which result in missing tag
func (ref *Reference) fixNoTag() {
if ref.Tag == "" {
parts := strings.Split(ref.Repo, ":")
numParts := len(parts)
if 0 < numParts {
lastIndex := numParts - 1
lastPart := parts[lastIndex]
if !strings.Contains(lastPart, "/") {
ref.Repo = strings.Join(parts[:lastIndex], ":")
ref.Tag = lastPart
}
}
}
}
// fixNoRepo is a fix for ref strings such as "mychart", which have the repo swapped with tag
func (ref *Reference) fixNoRepo() {
if ref.Repo == "" {
ref.Repo = ref.Tag
ref.Tag = ""
}
}
// validate makes sure the ref meets our criteria
func (ref *Reference) validate() error {
err := ref.validateRepo()
if err != nil {
return err
}
return ref.validateNumColons()
}
// validateRepo checks that the Repo field is non-empty
func (ref *Reference) validateRepo() error {
if ref.Repo == "" {
return errEmptyRepo
}
return nil
}
// validateNumColon ensures the ref only contains a single colon character (:)
// (or potentially two, there might be a port number specified i.e. :5000)
func (ref *Reference) validateNumColons() error {
if strings.Contains(ref.Tag, ":") {
return errTooManyColons
}
parts := strings.Split(ref.Repo, ":")
lastIndex := len(parts) - 1
if 1 < lastIndex {
return errTooManyColons
}
if 0 < lastIndex {
port := strings.Split(parts[lastIndex], "/")[0]
if !isValidPort(port) {
return errTooManyColons
}
}
return nil
}
// isValidPort returns whether or not a string looks like a valid port
func isValidPort(s string) bool {
return validPortRegEx.MatchString(s)
}