reference.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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. "github.com/docker/distribution/digest"
  27. )
  28. const (
  29. // NameTotalLengthMax is the maximum total number of characters in a repository name.
  30. NameTotalLengthMax = 255
  31. )
  32. var (
  33. // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
  34. ErrReferenceInvalidFormat = errors.New("invalid reference format")
  35. // ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
  36. ErrTagInvalidFormat = errors.New("invalid tag format")
  37. // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
  38. ErrDigestInvalidFormat = errors.New("invalid digest format")
  39. // ErrNameEmpty is returned for empty, invalid repository names.
  40. ErrNameEmpty = errors.New("repository name must have at least one component")
  41. // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
  42. ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
  43. )
  44. // Reference is an opaque object reference identifier that may include
  45. // modifiers such as a hostname, name, tag, and digest.
  46. type Reference interface {
  47. // String returns the full reference
  48. String() string
  49. }
  50. // Field provides a wrapper type for resolving correct reference types when
  51. // working with encoding.
  52. type Field struct {
  53. reference Reference
  54. }
  55. // AsField wraps a reference in a Field for encoding.
  56. func AsField(reference Reference) Field {
  57. return Field{reference}
  58. }
  59. // Reference unwraps the reference type from the field to
  60. // return the Reference object. This object should be
  61. // of the appropriate type to further check for different
  62. // reference types.
  63. func (f Field) Reference() Reference {
  64. return f.reference
  65. }
  66. // MarshalText serializes the field to byte text which
  67. // is the string of the reference.
  68. func (f Field) MarshalText() (p []byte, err error) {
  69. return []byte(f.reference.String()), nil
  70. }
  71. // UnmarshalText parses text bytes by invoking the
  72. // reference parser to ensure the appropriately
  73. // typed reference object is wrapped by field.
  74. func (f *Field) UnmarshalText(p []byte) error {
  75. r, err := Parse(string(p))
  76. if err != nil {
  77. return err
  78. }
  79. f.reference = r
  80. return nil
  81. }
  82. // Named is an object with a full name
  83. type Named interface {
  84. Reference
  85. Name() string
  86. }
  87. // Tagged is an object which has a tag
  88. type Tagged interface {
  89. Reference
  90. Tag() string
  91. }
  92. // NamedTagged is an object including a name and tag.
  93. type NamedTagged interface {
  94. Named
  95. Tag() string
  96. }
  97. // Digested is an object which has a digest
  98. // in which it can be referenced by
  99. type Digested interface {
  100. Reference
  101. Digest() digest.Digest
  102. }
  103. // Canonical reference is an object with a fully unique
  104. // name including a name with hostname and digest
  105. type Canonical interface {
  106. Named
  107. Digest() digest.Digest
  108. }
  109. // SplitHostname splits a named reference into a
  110. // hostname and name string. If no valid hostname is
  111. // found, the hostname is empty and the full value
  112. // is returned as name
  113. func SplitHostname(named Named) (string, string) {
  114. name := named.Name()
  115. match := anchoredNameRegexp.FindStringSubmatch(name)
  116. if match == nil || len(match) != 3 {
  117. return "", name
  118. }
  119. return match[1], match[2]
  120. }
  121. // Parse parses s and returns a syntactically valid Reference.
  122. // If an error was encountered it is returned, along with a nil Reference.
  123. // NOTE: Parse will not handle short digests.
  124. func Parse(s string) (Reference, error) {
  125. matches := ReferenceRegexp.FindStringSubmatch(s)
  126. if matches == nil {
  127. if s == "" {
  128. return nil, ErrNameEmpty
  129. }
  130. // TODO(dmcgowan): Provide more specific and helpful error
  131. return nil, ErrReferenceInvalidFormat
  132. }
  133. if len(matches[1]) > NameTotalLengthMax {
  134. return nil, ErrNameTooLong
  135. }
  136. ref := reference{
  137. name: matches[1],
  138. tag: matches[2],
  139. }
  140. if matches[3] != "" {
  141. var err error
  142. ref.digest, err = digest.ParseDigest(matches[3])
  143. if err != nil {
  144. return nil, err
  145. }
  146. }
  147. r := getBestReferenceType(ref)
  148. if r == nil {
  149. return nil, ErrNameEmpty
  150. }
  151. return r, nil
  152. }
  153. // ParseNamed parses s and returns a syntactically valid reference implementing
  154. // the Named interface. The reference must have a name, otherwise an error is
  155. // returned.
  156. // If an error was encountered it is returned, along with a nil Reference.
  157. // NOTE: ParseNamed will not handle short digests.
  158. func ParseNamed(s string) (Named, error) {
  159. ref, err := Parse(s)
  160. if err != nil {
  161. return nil, err
  162. }
  163. named, isNamed := ref.(Named)
  164. if !isNamed {
  165. return nil, fmt.Errorf("reference %s has no name", ref.String())
  166. }
  167. return named, nil
  168. }
  169. // WithName returns a named object representing the given string. If the input
  170. // is invalid ErrReferenceInvalidFormat will be returned.
  171. func WithName(name string) (Named, error) {
  172. if len(name) > NameTotalLengthMax {
  173. return nil, ErrNameTooLong
  174. }
  175. if !anchoredNameRegexp.MatchString(name) {
  176. return nil, ErrReferenceInvalidFormat
  177. }
  178. return repository(name), nil
  179. }
  180. // WithTag combines the name from "name" and the tag from "tag" to form a
  181. // reference incorporating both the name and the tag.
  182. func WithTag(name Named, tag string) (NamedTagged, error) {
  183. if !anchoredTagRegexp.MatchString(tag) {
  184. return nil, ErrTagInvalidFormat
  185. }
  186. return taggedReference{
  187. name: name.Name(),
  188. tag: tag,
  189. }, nil
  190. }
  191. // WithDigest combines the name from "name" and the digest from "digest" to form
  192. // a reference incorporating both the name and the digest.
  193. func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
  194. if !anchoredDigestRegexp.MatchString(digest.String()) {
  195. return nil, ErrDigestInvalidFormat
  196. }
  197. return canonicalReference{
  198. name: name.Name(),
  199. digest: digest,
  200. }, nil
  201. }
  202. func getBestReferenceType(ref reference) Reference {
  203. if ref.name == "" {
  204. // Allow digest only references
  205. if ref.digest != "" {
  206. return digestReference(ref.digest)
  207. }
  208. return nil
  209. }
  210. if ref.tag == "" {
  211. if ref.digest != "" {
  212. return canonicalReference{
  213. name: ref.name,
  214. digest: ref.digest,
  215. }
  216. }
  217. return repository(ref.name)
  218. }
  219. if ref.digest == "" {
  220. return taggedReference{
  221. name: ref.name,
  222. tag: ref.tag,
  223. }
  224. }
  225. return ref
  226. }
  227. type reference struct {
  228. name string
  229. tag string
  230. digest digest.Digest
  231. }
  232. func (r reference) String() string {
  233. return r.name + ":" + r.tag + "@" + r.digest.String()
  234. }
  235. func (r reference) Name() string {
  236. return r.name
  237. }
  238. func (r reference) Tag() string {
  239. return r.tag
  240. }
  241. func (r reference) Digest() digest.Digest {
  242. return r.digest
  243. }
  244. type repository string
  245. func (r repository) String() string {
  246. return string(r)
  247. }
  248. func (r repository) Name() string {
  249. return string(r)
  250. }
  251. type digestReference digest.Digest
  252. func (d digestReference) String() string {
  253. return d.String()
  254. }
  255. func (d digestReference) Digest() digest.Digest {
  256. return digest.Digest(d)
  257. }
  258. type taggedReference struct {
  259. name string
  260. tag string
  261. }
  262. func (t taggedReference) String() string {
  263. return t.name + ":" + t.tag
  264. }
  265. func (t taggedReference) Name() string {
  266. return t.name
  267. }
  268. func (t taggedReference) Tag() string {
  269. return t.tag
  270. }
  271. type canonicalReference struct {
  272. name string
  273. digest digest.Digest
  274. }
  275. func (c canonicalReference) String() string {
  276. return c.name + "@" + c.digest.String()
  277. }
  278. func (c canonicalReference) Name() string {
  279. return c.name
  280. }
  281. func (c canonicalReference) Digest() digest.Digest {
  282. return c.digest
  283. }