reference.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. // Package reference provides a general type to represent any way of referencing images within the registry.
  2. // Its main purpose is to abstract tags and digests (content-addressable hash).
  3. //
  4. // Grammar
  5. //
  6. // reference := name [ ":" tag ] [ "@" digest ]
  7. // name := [hostname '/'] component ['/' component]*
  8. // hostname := hostcomponent ['.' hostcomponent]* [':' port-number]
  9. // hostcomponent := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
  10. // port-number := /[0-9]+/
  11. // component := alpha-numeric [separator alpha-numeric]*
  12. // alpha-numeric := /[a-z0-9]+/
  13. // separator := /[_.]|__|[-]*/
  14. //
  15. // tag := /[\w][\w.-]{0,127}/
  16. //
  17. // digest := digest-algorithm ":" digest-hex
  18. // digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
  19. // digest-algorithm-separator := /[+.-_]/
  20. // digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/
  21. // digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
  22. package reference
  23. import (
  24. "errors"
  25. "fmt"
  26. "path"
  27. "strings"
  28. "github.com/docker/distribution/digest"
  29. )
  30. const (
  31. // NameTotalLengthMax is the maximum total number of characters in a repository name.
  32. NameTotalLengthMax = 255
  33. )
  34. var (
  35. // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
  36. ErrReferenceInvalidFormat = errors.New("invalid reference format")
  37. // ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
  38. ErrTagInvalidFormat = errors.New("invalid tag format")
  39. // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
  40. ErrDigestInvalidFormat = errors.New("invalid digest format")
  41. // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
  42. ErrNameContainsUppercase = errors.New("repository name must be lowercase")
  43. // ErrNameEmpty is returned for empty, invalid repository names.
  44. ErrNameEmpty = errors.New("repository name must have at least one component")
  45. // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
  46. ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
  47. )
  48. // Reference is an opaque object reference identifier that may include
  49. // modifiers such as a hostname, name, tag, and digest.
  50. type Reference interface {
  51. // String returns the full reference
  52. String() string
  53. }
  54. // Field provides a wrapper type for resolving correct reference types when
  55. // working with encoding.
  56. type Field struct {
  57. reference Reference
  58. }
  59. // AsField wraps a reference in a Field for encoding.
  60. func AsField(reference Reference) Field {
  61. return Field{reference}
  62. }
  63. // Reference unwraps the reference type from the field to
  64. // return the Reference object. This object should be
  65. // of the appropriate type to further check for different
  66. // reference types.
  67. func (f Field) Reference() Reference {
  68. return f.reference
  69. }
  70. // MarshalText serializes the field to byte text which
  71. // is the string of the reference.
  72. func (f Field) MarshalText() (p []byte, err error) {
  73. return []byte(f.reference.String()), nil
  74. }
  75. // UnmarshalText parses text bytes by invoking the
  76. // reference parser to ensure the appropriately
  77. // typed reference object is wrapped by field.
  78. func (f *Field) UnmarshalText(p []byte) error {
  79. r, err := Parse(string(p))
  80. if err != nil {
  81. return err
  82. }
  83. f.reference = r
  84. return nil
  85. }
  86. // Named is an object with a full name
  87. type Named interface {
  88. Reference
  89. Name() string
  90. }
  91. // Tagged is an object which has a tag
  92. type Tagged interface {
  93. Reference
  94. Tag() string
  95. }
  96. // NamedTagged is an object including a name and tag.
  97. type NamedTagged interface {
  98. Named
  99. Tag() string
  100. }
  101. // Digested is an object which has a digest
  102. // in which it can be referenced by
  103. type Digested interface {
  104. Reference
  105. Digest() digest.Digest
  106. }
  107. // Canonical reference is an object with a fully unique
  108. // name including a name with hostname and digest
  109. type Canonical interface {
  110. Named
  111. Digest() digest.Digest
  112. }
  113. // SplitHostname splits a named reference into a
  114. // hostname and name string. If no valid hostname is
  115. // found, the hostname is empty and the full value
  116. // is returned as name
  117. func SplitHostname(named Named) (string, string) {
  118. name := named.Name()
  119. match := anchoredNameRegexp.FindStringSubmatch(name)
  120. if len(match) != 3 {
  121. return "", name
  122. }
  123. return match[1], match[2]
  124. }
  125. // Parse parses s and returns a syntactically valid Reference.
  126. // If an error was encountered it is returned, along with a nil Reference.
  127. // NOTE: Parse will not handle short digests.
  128. func Parse(s string) (Reference, error) {
  129. matches := ReferenceRegexp.FindStringSubmatch(s)
  130. if matches == nil {
  131. if s == "" {
  132. return nil, ErrNameEmpty
  133. }
  134. if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil {
  135. return nil, ErrNameContainsUppercase
  136. }
  137. return nil, ErrReferenceInvalidFormat
  138. }
  139. if len(matches[1]) > NameTotalLengthMax {
  140. return nil, ErrNameTooLong
  141. }
  142. ref := reference{
  143. name: matches[1],
  144. tag: matches[2],
  145. }
  146. if matches[3] != "" {
  147. var err error
  148. ref.digest, err = digest.ParseDigest(matches[3])
  149. if err != nil {
  150. return nil, err
  151. }
  152. }
  153. r := getBestReferenceType(ref)
  154. if r == nil {
  155. return nil, ErrNameEmpty
  156. }
  157. return r, nil
  158. }
  159. // ParseNamed parses s and returns a syntactically valid reference implementing
  160. // the Named interface. The reference must have a name, otherwise an error is
  161. // returned.
  162. // If an error was encountered it is returned, along with a nil Reference.
  163. // NOTE: ParseNamed will not handle short digests.
  164. func ParseNamed(s string) (Named, error) {
  165. ref, err := Parse(s)
  166. if err != nil {
  167. return nil, err
  168. }
  169. named, isNamed := ref.(Named)
  170. if !isNamed {
  171. return nil, fmt.Errorf("reference %s has no name", ref.String())
  172. }
  173. return named, nil
  174. }
  175. // WithName returns a named object representing the given string. If the input
  176. // is invalid ErrReferenceInvalidFormat will be returned.
  177. func WithName(name string) (Named, error) {
  178. if len(name) > NameTotalLengthMax {
  179. return nil, ErrNameTooLong
  180. }
  181. if !anchoredNameRegexp.MatchString(name) {
  182. return nil, ErrReferenceInvalidFormat
  183. }
  184. return repository(name), nil
  185. }
  186. // WithTag combines the name from "name" and the tag from "tag" to form a
  187. // reference incorporating both the name and the tag.
  188. func WithTag(name Named, tag string) (NamedTagged, error) {
  189. if !anchoredTagRegexp.MatchString(tag) {
  190. return nil, ErrTagInvalidFormat
  191. }
  192. if canonical, ok := name.(Canonical); ok {
  193. return reference{
  194. name: name.Name(),
  195. tag: tag,
  196. digest: canonical.Digest(),
  197. }, nil
  198. }
  199. return taggedReference{
  200. name: name.Name(),
  201. tag: tag,
  202. }, nil
  203. }
  204. // WithDigest combines the name from "name" and the digest from "digest" to form
  205. // a reference incorporating both the name and the digest.
  206. func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
  207. if !anchoredDigestRegexp.MatchString(digest.String()) {
  208. return nil, ErrDigestInvalidFormat
  209. }
  210. if tagged, ok := name.(Tagged); ok {
  211. return reference{
  212. name: name.Name(),
  213. tag: tagged.Tag(),
  214. digest: digest,
  215. }, nil
  216. }
  217. return canonicalReference{
  218. name: name.Name(),
  219. digest: digest,
  220. }, nil
  221. }
  222. // Match reports whether ref matches the specified pattern.
  223. // See https://godoc.org/path#Match for supported patterns.
  224. func Match(pattern string, ref Reference) (bool, error) {
  225. matched, err := path.Match(pattern, ref.String())
  226. if namedRef, isNamed := ref.(Named); isNamed && !matched {
  227. matched, _ = path.Match(pattern, namedRef.Name())
  228. }
  229. return matched, err
  230. }
  231. // TrimNamed removes any tag or digest from the named reference.
  232. func TrimNamed(ref Named) Named {
  233. return repository(ref.Name())
  234. }
  235. func getBestReferenceType(ref reference) Reference {
  236. if ref.name == "" {
  237. // Allow digest only references
  238. if ref.digest != "" {
  239. return digestReference(ref.digest)
  240. }
  241. return nil
  242. }
  243. if ref.tag == "" {
  244. if ref.digest != "" {
  245. return canonicalReference{
  246. name: ref.name,
  247. digest: ref.digest,
  248. }
  249. }
  250. return repository(ref.name)
  251. }
  252. if ref.digest == "" {
  253. return taggedReference{
  254. name: ref.name,
  255. tag: ref.tag,
  256. }
  257. }
  258. return ref
  259. }
  260. type reference struct {
  261. name string
  262. tag string
  263. digest digest.Digest
  264. }
  265. func (r reference) String() string {
  266. return r.name + ":" + r.tag + "@" + r.digest.String()
  267. }
  268. func (r reference) Name() string {
  269. return r.name
  270. }
  271. func (r reference) Tag() string {
  272. return r.tag
  273. }
  274. func (r reference) Digest() digest.Digest {
  275. return r.digest
  276. }
  277. type repository string
  278. func (r repository) String() string {
  279. return string(r)
  280. }
  281. func (r repository) Name() string {
  282. return string(r)
  283. }
  284. type digestReference digest.Digest
  285. func (d digestReference) String() string {
  286. return d.String()
  287. }
  288. func (d digestReference) Digest() digest.Digest {
  289. return digest.Digest(d)
  290. }
  291. type taggedReference struct {
  292. name string
  293. tag string
  294. }
  295. func (t taggedReference) String() string {
  296. return t.name + ":" + t.tag
  297. }
  298. func (t taggedReference) Name() string {
  299. return t.name
  300. }
  301. func (t taggedReference) Tag() string {
  302. return t.tag
  303. }
  304. type canonicalReference struct {
  305. name string
  306. digest digest.Digest
  307. }
  308. func (c canonicalReference) String() string {
  309. return c.name + "@" + c.digest.String()
  310. }
  311. func (c canonicalReference) Name() string {
  312. return c.name
  313. }
  314. func (c canonicalReference) Digest() digest.Digest {
  315. return c.digest
  316. }