github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/reference/docker/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 docker provides a general type to represent any way of referencing images within the registry. 18 // Its main purpose is to abstract tags and digests (content-addressable hash). 19 // 20 // Grammar 21 // 22 // reference := name [ ":" tag ] [ "@" digest ] 23 // name := [domain '/'] path-component ['/' path-component]* 24 // domain := domain-component ['.' domain-component]* [':' port-number] 25 // domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ 26 // port-number := /[0-9]+/ 27 // path-component := alpha-numeric [separator alpha-numeric]* 28 // alpha-numeric := /[a-z0-9]+/ 29 // separator := /[_.]|__|[-]*/ 30 // 31 // tag := /[\w][\w.-]{0,127}/ 32 // 33 // digest := digest-algorithm ":" digest-hex 34 // digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]* 35 // digest-algorithm-separator := /[+.-_]/ 36 // digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ 37 // digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value 38 // 39 // identifier := /[a-f0-9]{64}/ 40 // short-identifier := /[a-f0-9]{6,64}/ 41 package docker 42 43 import ( 44 "errors" 45 "fmt" 46 "path" 47 "regexp" 48 "strings" 49 50 "github.com/opencontainers/go-digest" 51 ) 52 53 const ( 54 // NameTotalLengthMax is the maximum total number of characters in a repository name. 55 NameTotalLengthMax = 255 56 ) 57 58 var ( 59 // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. 60 ErrReferenceInvalidFormat = errors.New("invalid reference format") 61 62 // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. 63 ErrTagInvalidFormat = errors.New("invalid tag format") 64 65 // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. 66 ErrDigestInvalidFormat = errors.New("invalid digest format") 67 68 // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. 69 ErrNameContainsUppercase = errors.New("repository name must be lowercase") 70 71 // ErrNameEmpty is returned for empty, invalid repository names. 72 ErrNameEmpty = errors.New("repository name must have at least one component") 73 74 // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax. 75 ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) 76 77 // ErrNameNotCanonical is returned when a name is not canonical. 78 ErrNameNotCanonical = errors.New("repository name must be canonical") 79 ) 80 81 // Reference is an opaque object reference identifier that may include 82 // modifiers such as a hostname, name, tag, and digest. 83 type Reference interface { 84 // String returns the full reference 85 String() string 86 } 87 88 // Field provides a wrapper type for resolving correct reference types when 89 // working with encoding. 90 type Field struct { 91 reference Reference 92 } 93 94 // AsField wraps a reference in a Field for encoding. 95 func AsField(reference Reference) Field { 96 return Field{reference} 97 } 98 99 // Reference unwraps the reference type from the field to 100 // return the Reference object. This object should be 101 // of the appropriate type to further check for different 102 // reference types. 103 func (f Field) Reference() Reference { 104 return f.reference 105 } 106 107 // MarshalText serializes the field to byte text which 108 // is the string of the reference. 109 func (f Field) MarshalText() (p []byte, err error) { 110 return []byte(f.reference.String()), nil 111 } 112 113 // UnmarshalText parses text bytes by invoking the 114 // reference parser to ensure the appropriately 115 // typed reference object is wrapped by field. 116 func (f *Field) UnmarshalText(p []byte) error { 117 r, err := Parse(string(p)) 118 if err != nil { 119 return err 120 } 121 122 f.reference = r 123 return nil 124 } 125 126 // Named is an object with a full name 127 type Named interface { 128 Reference 129 Name() string 130 } 131 132 // Tagged is an object which has a tag 133 type Tagged interface { 134 Reference 135 Tag() string 136 } 137 138 // NamedTagged is an object including a name and tag. 139 type NamedTagged interface { 140 Named 141 Tag() string 142 } 143 144 // Digested is an object which has a digest 145 // in which it can be referenced by 146 type Digested interface { 147 Reference 148 Digest() digest.Digest 149 } 150 151 // Canonical reference is an object with a fully unique 152 // name including a name with domain and digest 153 type Canonical interface { 154 Named 155 Digest() digest.Digest 156 } 157 158 // namedRepository is a reference to a repository with a name. 159 // A namedRepository has both domain and path components. 160 type namedRepository interface { 161 Named 162 Domain() string 163 Path() string 164 } 165 166 // Domain returns the domain part of the Named reference 167 func Domain(named Named) string { 168 if r, ok := named.(namedRepository); ok { 169 return r.Domain() 170 } 171 domain, _ := splitDomain(named.Name()) 172 return domain 173 } 174 175 // Path returns the name without the domain part of the Named reference 176 func Path(named Named) (name string) { 177 if r, ok := named.(namedRepository); ok { 178 return r.Path() 179 } 180 _, path := splitDomain(named.Name()) 181 return path 182 } 183 184 func splitDomain(name string) (string, string) { 185 match := anchoredNameRegexp.FindStringSubmatch(name) 186 if len(match) != 3 { 187 return "", name 188 } 189 return match[1], match[2] 190 } 191 192 // SplitHostname splits a named reference into a 193 // hostname and name string. If no valid hostname is 194 // found, the hostname is empty and the full value 195 // is returned as name 196 // DEPRECATED: Use Domain or Path 197 func SplitHostname(named Named) (string, string) { 198 if r, ok := named.(namedRepository); ok { 199 return r.Domain(), r.Path() 200 } 201 return splitDomain(named.Name()) 202 } 203 204 // Parse parses s and returns a syntactically valid Reference. 205 // If an error was encountered it is returned, along with a nil Reference. 206 // NOTE: Parse will not handle short digests. 207 func Parse(s string) (Reference, error) { 208 matches := ReferenceRegexp.FindStringSubmatch(s) 209 if matches == nil { 210 if s == "" { 211 return nil, ErrNameEmpty 212 } 213 if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { 214 return nil, ErrNameContainsUppercase 215 } 216 return nil, ErrReferenceInvalidFormat 217 } 218 219 if len(matches[1]) > NameTotalLengthMax { 220 return nil, ErrNameTooLong 221 } 222 223 var repo repository 224 225 nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) 226 if len(nameMatch) == 3 { 227 repo.domain = nameMatch[1] 228 repo.path = nameMatch[2] 229 } else { 230 repo.domain = "" 231 repo.path = matches[1] 232 } 233 234 ref := reference{ 235 namedRepository: repo, 236 tag: matches[2], 237 } 238 if matches[3] != "" { 239 var err error 240 ref.digest, err = digest.Parse(matches[3]) 241 if err != nil { 242 return nil, err 243 } 244 } 245 246 r := getBestReferenceType(ref) 247 if r == nil { 248 return nil, ErrNameEmpty 249 } 250 251 return r, nil 252 } 253 254 // ParseNamed parses s and returns a syntactically valid reference implementing 255 // the Named interface. The reference must have a name and be in the canonical 256 // form, otherwise an error is returned. 257 // If an error was encountered it is returned, along with a nil Reference. 258 // NOTE: ParseNamed will not handle short digests. 259 func ParseNamed(s string) (Named, error) { 260 named, err := ParseNormalizedNamed(s) 261 if err != nil { 262 return nil, err 263 } 264 if named.String() != s { 265 return nil, ErrNameNotCanonical 266 } 267 return named, nil 268 } 269 270 // WithName returns a named object representing the given string. If the input 271 // is invalid ErrReferenceInvalidFormat will be returned. 272 func WithName(name string) (Named, error) { 273 if len(name) > NameTotalLengthMax { 274 return nil, ErrNameTooLong 275 } 276 277 match := anchoredNameRegexp.FindStringSubmatch(name) 278 if match == nil || len(match) != 3 { 279 return nil, ErrReferenceInvalidFormat 280 } 281 return repository{ 282 domain: match[1], 283 path: match[2], 284 }, nil 285 } 286 287 // WithTag combines the name from "name" and the tag from "tag" to form a 288 // reference incorporating both the name and the tag. 289 func WithTag(name Named, tag string) (NamedTagged, error) { 290 if !anchoredTagRegexp.MatchString(tag) { 291 return nil, ErrTagInvalidFormat 292 } 293 var repo repository 294 if r, ok := name.(namedRepository); ok { 295 repo.domain = r.Domain() 296 repo.path = r.Path() 297 } else { 298 repo.path = name.Name() 299 } 300 if canonical, ok := name.(Canonical); ok { 301 return reference{ 302 namedRepository: repo, 303 tag: tag, 304 digest: canonical.Digest(), 305 }, nil 306 } 307 return taggedReference{ 308 namedRepository: repo, 309 tag: tag, 310 }, nil 311 } 312 313 // WithDigest combines the name from "name" and the digest from "digest" to form 314 // a reference incorporating both the name and the digest. 315 func WithDigest(name Named, digest digest.Digest) (Canonical, error) { 316 if !anchoredDigestRegexp.MatchString(digest.String()) { 317 return nil, ErrDigestInvalidFormat 318 } 319 var repo repository 320 if r, ok := name.(namedRepository); ok { 321 repo.domain = r.Domain() 322 repo.path = r.Path() 323 } else { 324 repo.path = name.Name() 325 } 326 if tagged, ok := name.(Tagged); ok { 327 return reference{ 328 namedRepository: repo, 329 tag: tagged.Tag(), 330 digest: digest, 331 }, nil 332 } 333 return canonicalReference{ 334 namedRepository: repo, 335 digest: digest, 336 }, nil 337 } 338 339 // TrimNamed removes any tag or digest from the named reference. 340 func TrimNamed(ref Named) Named { 341 domain, path := SplitHostname(ref) 342 return repository{ 343 domain: domain, 344 path: path, 345 } 346 } 347 348 func getBestReferenceType(ref reference) Reference { 349 if ref.Name() == "" { 350 // Allow digest only references 351 if ref.digest != "" { 352 return digestReference(ref.digest) 353 } 354 return nil 355 } 356 if ref.tag == "" { 357 if ref.digest != "" { 358 return canonicalReference{ 359 namedRepository: ref.namedRepository, 360 digest: ref.digest, 361 } 362 } 363 return ref.namedRepository 364 } 365 if ref.digest == "" { 366 return taggedReference{ 367 namedRepository: ref.namedRepository, 368 tag: ref.tag, 369 } 370 } 371 372 return ref 373 } 374 375 type reference struct { 376 namedRepository 377 tag string 378 digest digest.Digest 379 } 380 381 func (r reference) String() string { 382 return r.Name() + ":" + r.tag + "@" + r.digest.String() 383 } 384 385 func (r reference) Tag() string { 386 return r.tag 387 } 388 389 func (r reference) Digest() digest.Digest { 390 return r.digest 391 } 392 393 type repository struct { 394 domain string 395 path string 396 } 397 398 func (r repository) String() string { 399 return r.Name() 400 } 401 402 func (r repository) Name() string { 403 if r.domain == "" { 404 return r.path 405 } 406 return r.domain + "/" + r.path 407 } 408 409 func (r repository) Domain() string { 410 return r.domain 411 } 412 413 func (r repository) Path() string { 414 return r.path 415 } 416 417 type digestReference digest.Digest 418 419 func (d digestReference) String() string { 420 return digest.Digest(d).String() 421 } 422 423 func (d digestReference) Digest() digest.Digest { 424 return digest.Digest(d) 425 } 426 427 type taggedReference struct { 428 namedRepository 429 tag string 430 } 431 432 func (t taggedReference) String() string { 433 return t.Name() + ":" + t.tag 434 } 435 436 func (t taggedReference) Tag() string { 437 return t.tag 438 } 439 440 type canonicalReference struct { 441 namedRepository 442 digest digest.Digest 443 } 444 445 func (c canonicalReference) String() string { 446 return c.Name() + "@" + c.digest.String() 447 } 448 449 func (c canonicalReference) Digest() digest.Digest { 450 return c.digest 451 } 452 453 var ( 454 // alphaNumericRegexp defines the alpha numeric atom, typically a 455 // component of names. This only allows lower case characters and digits. 456 alphaNumericRegexp = match(`[a-z0-9]+`) 457 458 // separatorRegexp defines the separators allowed to be embedded in name 459 // components. This allow one period, one or two underscore and multiple 460 // dashes. 461 separatorRegexp = match(`(?:[._]|__|[-]*)`) 462 463 // nameComponentRegexp restricts registry path component names to start 464 // with at least one letter or number, with following parts able to be 465 // separated by one period, one or two underscore and multiple dashes. 466 nameComponentRegexp = expression( 467 alphaNumericRegexp, 468 optional(repeated(separatorRegexp, alphaNumericRegexp))) 469 470 // domainComponentRegexp restricts the registry domain component of a 471 // repository name to start with a component as defined by DomainRegexp 472 // and followed by an optional port. 473 domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) 474 475 // DomainRegexp defines the structure of potential domain components 476 // that may be part of image names. This is purposely a subset of what is 477 // allowed by DNS to ensure backwards compatibility with Docker image 478 // names. 479 DomainRegexp = expression( 480 domainComponentRegexp, 481 optional(repeated(literal(`.`), domainComponentRegexp)), 482 optional(literal(`:`), match(`[0-9]+`))) 483 484 // TagRegexp matches valid tag names. From docker/docker:graph/tags.go. 485 TagRegexp = match(`[\w][\w.-]{0,127}`) 486 487 // anchoredTagRegexp matches valid tag names, anchored at the start and 488 // end of the matched string. 489 anchoredTagRegexp = anchored(TagRegexp) 490 491 // DigestRegexp matches valid digests. 492 DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`) 493 494 // anchoredDigestRegexp matches valid digests, anchored at the start and 495 // end of the matched string. 496 anchoredDigestRegexp = anchored(DigestRegexp) 497 498 // NameRegexp is the format for the name component of references. The 499 // regexp has capturing groups for the domain and name part omitting 500 // the separating forward slash from either. 501 NameRegexp = expression( 502 optional(DomainRegexp, literal(`/`)), 503 nameComponentRegexp, 504 optional(repeated(literal(`/`), nameComponentRegexp))) 505 506 // anchoredNameRegexp is used to parse a name value, capturing the 507 // domain and trailing components. 508 anchoredNameRegexp = anchored( 509 optional(capture(DomainRegexp), literal(`/`)), 510 capture(nameComponentRegexp, 511 optional(repeated(literal(`/`), nameComponentRegexp)))) 512 513 // ReferenceRegexp is the full supported format of a reference. The regexp 514 // is anchored and has capturing groups for name, tag, and digest 515 // components. 516 ReferenceRegexp = anchored(capture(NameRegexp), 517 optional(literal(":"), capture(TagRegexp)), 518 optional(literal("@"), capture(DigestRegexp))) 519 520 // IdentifierRegexp is the format for string identifier used as a 521 // content addressable identifier using sha256. These identifiers 522 // are like digests without the algorithm, since sha256 is used. 523 IdentifierRegexp = match(`([a-f0-9]{64})`) 524 525 // ShortIdentifierRegexp is the format used to represent a prefix 526 // of an identifier. A prefix may be used to match a sha256 identifier 527 // within a list of trusted identifiers. 528 ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`) 529 530 // anchoredIdentifierRegexp is used to check or match an 531 // identifier value, anchored at start and end of string. 532 anchoredIdentifierRegexp = anchored(IdentifierRegexp) 533 ) 534 535 // match compiles the string to a regular expression. 536 var match = regexp.MustCompile 537 538 // literal compiles s into a literal regular expression, escaping any regexp 539 // reserved characters. 540 func literal(s string) *regexp.Regexp { 541 re := match(regexp.QuoteMeta(s)) 542 543 if _, complete := re.LiteralPrefix(); !complete { 544 panic("must be a literal") 545 } 546 547 return re 548 } 549 550 // expression defines a full expression, where each regular expression must 551 // follow the previous. 552 func expression(res ...*regexp.Regexp) *regexp.Regexp { 553 var s string 554 for _, re := range res { 555 s += re.String() 556 } 557 558 return match(s) 559 } 560 561 // optional wraps the expression in a non-capturing group and makes the 562 // production optional. 563 func optional(res ...*regexp.Regexp) *regexp.Regexp { 564 return match(group(expression(res...)).String() + `?`) 565 } 566 567 // repeated wraps the regexp in a non-capturing group to get one or more 568 // matches. 569 func repeated(res ...*regexp.Regexp) *regexp.Regexp { 570 return match(group(expression(res...)).String() + `+`) 571 } 572 573 // group wraps the regexp in a non-capturing group. 574 func group(res ...*regexp.Regexp) *regexp.Regexp { 575 return match(`(?:` + expression(res...).String() + `)`) 576 } 577 578 // capture wraps the expression in a capturing group. 579 func capture(res ...*regexp.Regexp) *regexp.Regexp { 580 return match(`(` + expression(res...).String() + `)`) 581 } 582 583 // anchored anchors the regular expression by adding start and end delimiters. 584 func anchored(res ...*regexp.Regexp) *regexp.Regexp { 585 return match(`^` + expression(res...).String() + `$`) 586 } 587 588 var ( 589 legacyDefaultDomain = "index.docker.io" 590 defaultDomain = "docker.io" 591 officialRepoName = "library" 592 defaultTag = "latest" 593 ) 594 595 // normalizedNamed represents a name which has been 596 // normalized and has a familiar form. A familiar name 597 // is what is used in Docker UI. An example normalized 598 // name is "docker.io/library/ubuntu" and corresponding 599 // familiar name of "ubuntu". 600 type normalizedNamed interface { 601 Named 602 Familiar() Named 603 } 604 605 // ParseNormalizedNamed parses a string into a named reference 606 // transforming a familiar name from Docker UI to a fully 607 // qualified reference. If the value may be an identifier 608 // use ParseAnyReference. 609 func ParseNormalizedNamed(s string) (Named, error) { 610 if ok := anchoredIdentifierRegexp.MatchString(s); ok { 611 return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) 612 } 613 domain, remainder := splitDockerDomain(s) 614 var remoteName string 615 if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { 616 remoteName = remainder[:tagSep] 617 } else { 618 remoteName = remainder 619 } 620 if strings.ToLower(remoteName) != remoteName { 621 return nil, errors.New("invalid reference format: repository name must be lowercase") 622 } 623 624 ref, err := Parse(domain + "/" + remainder) 625 if err != nil { 626 return nil, err 627 } 628 named, isNamed := ref.(Named) 629 if !isNamed { 630 return nil, fmt.Errorf("reference %s has no name", ref.String()) 631 } 632 return named, nil 633 } 634 635 // ParseDockerRef normalizes the image reference following the docker convention. This is added 636 // mainly for backward compatibility. 637 // The reference returned can only be either tagged or digested. For reference contains both tag 638 // and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@ 639 // sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as 640 // docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa. 641 func ParseDockerRef(ref string) (Named, error) { 642 named, err := ParseNormalizedNamed(ref) 643 if err != nil { 644 return nil, err 645 } 646 if _, ok := named.(NamedTagged); ok { 647 if canonical, ok := named.(Canonical); ok { 648 // The reference is both tagged and digested, only 649 // return digested. 650 newNamed, err := WithName(canonical.Name()) 651 if err != nil { 652 return nil, err 653 } 654 newCanonical, err := WithDigest(newNamed, canonical.Digest()) 655 if err != nil { 656 return nil, err 657 } 658 return newCanonical, nil 659 } 660 } 661 return TagNameOnly(named), nil 662 } 663 664 // splitDockerDomain splits a repository name to domain and remotename string. 665 // If no valid domain is found, the default domain is used. Repository name 666 // needs to be already validated before. 667 func splitDockerDomain(name string) (domain, remainder string) { 668 i := strings.IndexRune(name, '/') 669 if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { 670 domain, remainder = defaultDomain, name 671 } else { 672 domain, remainder = name[:i], name[i+1:] 673 } 674 if domain == legacyDefaultDomain { 675 domain = defaultDomain 676 } 677 if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { 678 remainder = officialRepoName + "/" + remainder 679 } 680 return 681 } 682 683 // familiarizeName returns a shortened version of the name familiar 684 // to to the Docker UI. Familiar names have the default domain 685 // "docker.io" and "library/" repository prefix removed. 686 // For example, "docker.io/library/redis" will have the familiar 687 // name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp". 688 // Returns a familiarized named only reference. 689 func familiarizeName(named namedRepository) repository { 690 repo := repository{ 691 domain: named.Domain(), 692 path: named.Path(), 693 } 694 695 if repo.domain == defaultDomain { 696 repo.domain = "" 697 // Handle official repositories which have the pattern "library/<official repo name>" 698 if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName { 699 repo.path = split[1] 700 } 701 } 702 return repo 703 } 704 705 func (r reference) Familiar() Named { 706 return reference{ 707 namedRepository: familiarizeName(r.namedRepository), 708 tag: r.tag, 709 digest: r.digest, 710 } 711 } 712 713 func (r repository) Familiar() Named { 714 return familiarizeName(r) 715 } 716 717 func (t taggedReference) Familiar() Named { 718 return taggedReference{ 719 namedRepository: familiarizeName(t.namedRepository), 720 tag: t.tag, 721 } 722 } 723 724 func (c canonicalReference) Familiar() Named { 725 return canonicalReference{ 726 namedRepository: familiarizeName(c.namedRepository), 727 digest: c.digest, 728 } 729 } 730 731 // TagNameOnly adds the default tag "latest" to a reference if it only has 732 // a repo name. 733 func TagNameOnly(ref Named) Named { 734 if IsNameOnly(ref) { 735 namedTagged, err := WithTag(ref, defaultTag) 736 if err != nil { 737 // Default tag must be valid, to create a NamedTagged 738 // type with non-validated input the WithTag function 739 // should be used instead 740 panic(err) 741 } 742 return namedTagged 743 } 744 return ref 745 } 746 747 // ParseAnyReference parses a reference string as a possible identifier, 748 // full digest, or familiar name. 749 func ParseAnyReference(ref string) (Reference, error) { 750 if ok := anchoredIdentifierRegexp.MatchString(ref); ok { 751 return digestReference("sha256:" + ref), nil 752 } 753 if dgst, err := digest.Parse(ref); err == nil { 754 return digestReference(dgst), nil 755 } 756 757 return ParseNormalizedNamed(ref) 758 } 759 760 // IsNameOnly returns true if reference only contains a repo name. 761 func IsNameOnly(ref Named) bool { 762 if _, ok := ref.(NamedTagged); ok { 763 return false 764 } 765 if _, ok := ref.(Canonical); ok { 766 return false 767 } 768 return true 769 } 770 771 // FamiliarName returns the familiar name string 772 // for the given named, familiarizing if needed. 773 func FamiliarName(ref Named) string { 774 if nn, ok := ref.(normalizedNamed); ok { 775 return nn.Familiar().Name() 776 } 777 return ref.Name() 778 } 779 780 // FamiliarString returns the familiar string representation 781 // for the given reference, familiarizing if needed. 782 func FamiliarString(ref Reference) string { 783 if nn, ok := ref.(normalizedNamed); ok { 784 return nn.Familiar().String() 785 } 786 return ref.String() 787 } 788 789 // FamiliarMatch reports whether ref matches the specified pattern. 790 // See https://godoc.org/path#Match for supported patterns. 791 func FamiliarMatch(pattern string, ref Reference) (bool, error) { 792 matched, err := path.Match(pattern, FamiliarString(ref)) 793 if namedRef, isNamed := ref.(Named); isNamed && !matched { 794 matched, _ = path.Match(pattern, FamiliarName(namedRef)) 795 } 796 return matched, err 797 }