forked from toolshed/abra
cli
cmd
pkg
scripts
tests
vendor
coopcloud.tech
dario.cat
git.coopcloud.tech
github.com
AlecAivazis
Azure
BurntSushi
Microsoft
ProtonMail
aymanbagabas
beorn7
cenkalti
cespare
charmbracelet
cloudflare
containerd
containers
image
docker
policyconfiguration
reference
README.md
helpers.go
normalize.go
reference.go
regexp.go
cache.go
docker_client.go
docker_image.go
docker_image_dest.go
docker_image_src.go
docker_transport.go
lookaside.go
wwwauthenticate.go
image
manifest
pkg
transports
types
LICENSE
cpuguy83
cyphar
davecgh
decentral1se
distribution
docker
emirpasic
felixge
fvbommel
ghodss
go-git
go-logfmt
go-logr
go-viper
gogo
golang
google
gorilla
grpc-ecosystem
hashicorp
inconshreveable
jbenet
kballard
kevinburke
klauspost
lucasb-eyer
mattn
mgutz
miekg
mitchellh
mmcloughlin
moby
morikuni
muesli
munnerz
opencontainers
pjbgf
pkg
pmezard
prometheus
rivo
russross
schollz
sergi
sirupsen
skeema
spf13
stretchr
theupdateframework
xanzy
xeipuuv
go.opentelemetry.io
golang.org
google.golang.org
gopkg.in
gotest.tools
modules.txt
.dockerignore
.drone.yml
.envrc.sample
.gitignore
.goreleaser.yml
AUTHORS.md
Dockerfile
LICENSE
Makefile
README.md
go.mod
go.sum
renovate.json
182 lines
5.3 KiB
Go
182 lines
5.3 KiB
Go
package reference
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/opencontainers/go-digest"
|
|
)
|
|
|
|
var (
|
|
legacyDefaultDomain = "index.docker.io"
|
|
defaultDomain = "docker.io"
|
|
officialRepoName = "library"
|
|
defaultTag = "latest"
|
|
)
|
|
|
|
// normalizedNamed represents a name which has been
|
|
// normalized and has a familiar form. A familiar name
|
|
// is what is used in Docker UI. An example normalized
|
|
// name is "docker.io/library/ubuntu" and corresponding
|
|
// familiar name of "ubuntu".
|
|
type normalizedNamed interface {
|
|
Named
|
|
Familiar() Named
|
|
}
|
|
|
|
// ParseNormalizedNamed parses a string into a named reference
|
|
// transforming a familiar name from Docker UI to a fully
|
|
// qualified reference. If the value may be an identifier
|
|
// use ParseAnyReference.
|
|
func ParseNormalizedNamed(s string) (Named, error) {
|
|
if ok := anchoredIdentifierRegexp.MatchString(s); ok {
|
|
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
|
|
}
|
|
domain, remainder := splitDockerDomain(s)
|
|
var remoteName string
|
|
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
|
|
remoteName = remainder[:tagSep]
|
|
} else {
|
|
remoteName = remainder
|
|
}
|
|
if strings.ToLower(remoteName) != remoteName {
|
|
return nil, errors.New("invalid reference format: repository name must be lowercase")
|
|
}
|
|
|
|
ref, err := Parse(domain + "/" + remainder)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
named, isNamed := ref.(Named)
|
|
if !isNamed {
|
|
return nil, fmt.Errorf("reference %s has no name", ref.String())
|
|
}
|
|
return named, nil
|
|
}
|
|
|
|
// ParseDockerRef normalizes the image reference following the docker convention. This is added
|
|
// mainly for backward compatibility.
|
|
// The reference returned can only be either tagged or digested. For reference contains both tag
|
|
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
|
|
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
|
|
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
|
|
func ParseDockerRef(ref string) (Named, error) {
|
|
named, err := ParseNormalizedNamed(ref)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, ok := named.(NamedTagged); ok {
|
|
if canonical, ok := named.(Canonical); ok {
|
|
// The reference is both tagged and digested, only
|
|
// return digested.
|
|
newNamed, err := WithName(canonical.Name())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newCanonical, err := WithDigest(newNamed, canonical.Digest())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newCanonical, nil
|
|
}
|
|
}
|
|
return TagNameOnly(named), nil
|
|
}
|
|
|
|
// splitDockerDomain splits a repository name to domain and remotename string.
|
|
// If no valid domain is found, the default domain is used. Repository name
|
|
// needs to be already validated before.
|
|
func splitDockerDomain(name string) (domain, remainder string) {
|
|
i := strings.IndexRune(name, '/')
|
|
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
|
|
domain, remainder = defaultDomain, name
|
|
} else {
|
|
domain, remainder = name[:i], name[i+1:]
|
|
}
|
|
if domain == legacyDefaultDomain {
|
|
domain = defaultDomain
|
|
}
|
|
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
|
|
remainder = officialRepoName + "/" + remainder
|
|
}
|
|
return
|
|
}
|
|
|
|
// familiarizeName returns a shortened version of the name familiar
|
|
// to to the Docker UI. Familiar names have the default domain
|
|
// "docker.io" and "library/" repository prefix removed.
|
|
// For example, "docker.io/library/redis" will have the familiar
|
|
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
|
|
// Returns a familiarized named only reference.
|
|
func familiarizeName(named namedRepository) repository {
|
|
repo := repository{
|
|
domain: named.Domain(),
|
|
path: named.Path(),
|
|
}
|
|
|
|
if repo.domain == defaultDomain {
|
|
repo.domain = ""
|
|
// Handle official repositories which have the pattern "library/<official repo name>"
|
|
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
|
|
repo.path = split[1]
|
|
}
|
|
}
|
|
return repo
|
|
}
|
|
|
|
func (r reference) Familiar() Named {
|
|
return reference{
|
|
namedRepository: familiarizeName(r.namedRepository),
|
|
tag: r.tag,
|
|
digest: r.digest,
|
|
}
|
|
}
|
|
|
|
func (r repository) Familiar() Named {
|
|
return familiarizeName(r)
|
|
}
|
|
|
|
func (t taggedReference) Familiar() Named {
|
|
return taggedReference{
|
|
namedRepository: familiarizeName(t.namedRepository),
|
|
tag: t.tag,
|
|
}
|
|
}
|
|
|
|
func (c canonicalReference) Familiar() Named {
|
|
return canonicalReference{
|
|
namedRepository: familiarizeName(c.namedRepository),
|
|
digest: c.digest,
|
|
}
|
|
}
|
|
|
|
// TagNameOnly adds the default tag "latest" to a reference if it only has
|
|
// a repo name.
|
|
func TagNameOnly(ref Named) Named {
|
|
if IsNameOnly(ref) {
|
|
namedTagged, err := WithTag(ref, defaultTag)
|
|
if err != nil {
|
|
// Default tag must be valid, to create a NamedTagged
|
|
// type with non-validated input the WithTag function
|
|
// should be used instead
|
|
panic(err)
|
|
}
|
|
return namedTagged
|
|
}
|
|
return ref
|
|
}
|
|
|
|
// ParseAnyReference parses a reference string as a possible identifier,
|
|
// full digest, or familiar name.
|
|
func ParseAnyReference(ref string) (Reference, error) {
|
|
if ok := anchoredIdentifierRegexp.MatchString(ref); ok {
|
|
return digestReference("sha256:" + ref), nil
|
|
}
|
|
if dgst, err := digest.Parse(ref); err == nil {
|
|
return digestReference(dgst), nil
|
|
}
|
|
|
|
return ParseNormalizedNamed(ref)
|
|
}
|