github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/reference/reference.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package reference
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"net/url"
    23  	"path"
    24  	"regexp"
    25  	"strings"
    26  
    27  	digest "github.com/opencontainers/go-digest"
    28  )
    29  
    30  var (
    31  	// ErrInvalid is returned when there is an invalid reference
    32  	ErrInvalid = errors.New("invalid reference")
    33  	// ErrObjectRequired is returned when the object is required
    34  	ErrObjectRequired = errors.New("object required")
    35  	// ErrHostnameRequired is returned when the hostname is required
    36  	ErrHostnameRequired = errors.New("hostname required")
    37  )
    38  
    39  // Spec defines the main components of a reference specification.
    40  //
    41  // A reference specification is a schema-less URI parsed into common
    42  // components. The two main components, locator and object, are required to be
    43  // supported by remotes. It represents a superset of the naming define in
    44  // docker's reference schema. It aims to be compatible but not prescriptive.
    45  //
    46  // While the interpretation of the components, locator and object, are up to
    47  // the remote, we define a few common parts, accessible via helper methods.
    48  //
    49  // The first is the hostname, which is part of the locator. This doesn't need
    50  // to map to a physical resource, but it must parse as a hostname. We refer to
    51  // this as the namespace.
    52  //
    53  // The other component made accessible by helper method is the digest. This is
    54  // part of the object identifier, always prefixed with an '@'. If present, the
    55  // remote may use the digest portion directly or resolve it against a prefix.
    56  // If the object does not include the `@` symbol, the return value for `Digest`
    57  // will be empty.
    58  type Spec struct {
    59  	// Locator is the host and path portion of the specification. The host
    60  	// portion may refer to an actual host or just a namespace of related
    61  	// images.
    62  	//
    63  	// Typically, the locator may used to resolve the remote to fetch specific
    64  	// resources.
    65  	Locator string
    66  
    67  	// Object contains the identifier for the remote resource. Classically,
    68  	// this is a tag but can refer to anything in a remote. By convention, any
    69  	// portion that may be a partial or whole digest will be preceded by an
    70  	// `@`. Anything preceding the `@` will be referred to as the "tag".
    71  	//
    72  	// In practice, we will see this broken down into the following formats:
    73  	//
    74  	// 1. <tag>
    75  	// 2. <tag>@<digest spec>
    76  	// 3. @<digest spec>
    77  	//
    78  	// We define the tag to be anything except '@' and ':'. <digest spec> may
    79  	// be a full valid digest or shortened version, possibly with elided
    80  	// algorithm.
    81  	Object string
    82  }
    83  
    84  var splitRe = regexp.MustCompile(`[:@]`)
    85  
    86  // Parse parses the string into a structured ref.
    87  func Parse(s string) (Spec, error) {
    88  	u, err := url.Parse("dummy://" + s)
    89  	if err != nil {
    90  		return Spec{}, err
    91  	}
    92  
    93  	if u.Scheme != "dummy" {
    94  		return Spec{}, ErrInvalid
    95  	}
    96  
    97  	if u.Host == "" {
    98  		return Spec{}, ErrHostnameRequired
    99  	}
   100  
   101  	var object string
   102  
   103  	if idx := splitRe.FindStringIndex(u.Path); idx != nil {
   104  		// This allows us to retain the @ to signify digests or shortened digests in
   105  		// the object.
   106  		object = u.Path[idx[0]:]
   107  		if object[:1] == ":" {
   108  			object = object[1:]
   109  		}
   110  		u.Path = u.Path[:idx[0]]
   111  	}
   112  
   113  	return Spec{
   114  		Locator: path.Join(u.Host, u.Path),
   115  		Object:  object,
   116  	}, nil
   117  }
   118  
   119  // Hostname returns the hostname portion of the locator.
   120  //
   121  // Remotes are not required to directly access the resources at this host. This
   122  // method is provided for convenience.
   123  func (r Spec) Hostname() string {
   124  	i := strings.Index(r.Locator, "/")
   125  
   126  	if i < 0 {
   127  		return r.Locator
   128  	}
   129  	return r.Locator[:i]
   130  }
   131  
   132  // Digest returns the digest portion of the reference spec. This may be a
   133  // partial or invalid digest, which may be used to lookup a complete digest.
   134  func (r Spec) Digest() digest.Digest {
   135  	_, dgst := SplitObject(r.Object)
   136  	return dgst
   137  }
   138  
   139  // String returns the normalized string for the ref.
   140  func (r Spec) String() string {
   141  	if r.Object == "" {
   142  		return r.Locator
   143  	}
   144  	if r.Object[:1] == "@" {
   145  		return fmt.Sprintf("%v%v", r.Locator, r.Object)
   146  	}
   147  
   148  	return fmt.Sprintf("%v:%v", r.Locator, r.Object)
   149  }
   150  
   151  // SplitObject provides two parts of the object spec, delimited by an `@`
   152  // symbol.
   153  //
   154  // Either may be empty and it is the callers job to validate them
   155  // appropriately.
   156  func SplitObject(obj string) (tag string, dgst digest.Digest) {
   157  	parts := strings.SplitAfterN(obj, "@", 2)
   158  	if len(parts) < 2 {
   159  		return parts[0], ""
   160  	}
   161  	return parts[0], digest.Digest(parts[1])
   162  }