package imageparser import ( _ "crypto/sha256" // initialize crypto/sha256 to enable sha256 algorithm _ "crypto/sha512" // initialize crypto/sha512 to enable sha512 algorithm "strings" "github.com/distribution/distribution/v3/reference" ) // Components make up a whole image. // Basically we presume an image can be made of `[domain][:port][path][:tag][@sha256:digest]`, ie: // fictional.registry.example:10443/karmada/karmada-controller-manager:v1.0.0 or // fictional.registry.example:10443/karmada/karmada-controller-manager@sha256:50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c type Components struct { // hostname is the prefix of referencing image, like "registry.k8s.io". // It may optionally be followed by a port number, like "fictional.registry.example:10443". hostname string // repository is the short name of referencing image, like "karmada-controller-manager". // It may be made up of slash-separated components, like "karmada/karmada-controller-manager". repository string // tag is the tag of referencing image. ie: // - latest // - v1.19.1 tag string // digest is the digest of referencing image. ie: // - sha256:50d858e0985ecc7f60418aaf0cc5ab587f42c2570a884095a9e8ccacd0f6545c digest string } // Hostname returns the hostname, as well known as image registry. func (c *Components) Hostname() string { return c.hostname } // SetHostname sets the hostname. func (c *Components) SetHostname(hostname string) { c.hostname = hostname } // RemoveHostname removes the hostname. func (c *Components) RemoveHostname() { c.hostname = "" } // Repository returns the repository without hostname. func (c *Components) Repository() string { return c.repository } // SetRepository sets the repository. func (c *Components) SetRepository(repository string) { c.repository = repository } // RemoveRepository removes the repository. func (c *Components) RemoveRepository() { c.repository = "" } // FullRepository returns the whole repository, including hostname if not empty. func (c *Components) FullRepository() string { if c.hostname == "" { return c.repository } return c.hostname + "/" + c.repository } // String returns the full name of the image, including repository and tag(or digest). func (c Components) String() string { if c.tag != "" { return c.FullRepository() + ":" + c.tag } else if c.digest != "" { return c.FullRepository() + "@" + c.digest } return c.FullRepository() } // Tag returns the tag. func (c *Components) Tag() string { return c.tag } // SetTag sets the tag. func (c *Components) SetTag(tag string) { c.tag = tag } // RemoveTag removes the tag. func (c *Components) RemoveTag() { c.tag = "" } // Digest returns the digest. func (c *Components) Digest() string { return c.digest } // SetDigest sets the digest. func (c *Components) SetDigest(digest string) { c.digest = digest } // RemoveDigest removes the digest. func (c *Components) RemoveDigest() { c.digest = "" } // TagOrDigest returns image tag if not empty otherwise returns image digest. func (c *Components) TagOrDigest() string { if c.tag != "" { return c.tag } return c.digest } // SetTagOrDigest sets image tag or digest by matching the input. func (c *Components) SetTagOrDigest(input string) { if anchoredTagRegexp.MatchString(input) { c.SetTag(input) c.RemoveDigest() return } if anchoredDigestRegexp.MatchString(input) { c.SetDigest(input) c.RemoveTag() } } // RemoveTagOrDigest removes tag or digest. // Since tag and digest don't co-exist, so remove tag if tag not empty, otherwise remove digest. func (c *Components) RemoveTagOrDigest() { if c.tag != "" { c.tag = "" } else if c.digest != "" { c.digest = "" } } // Parse returns a Components of the given image. func Parse(image string) (*Components, error) { ref, err := reference.Parse(image) if err != nil { return nil, err } comp := &Components{} if named, ok := ref.(reference.Named); ok { comp.hostname, comp.repository = SplitHostname(named.Name()) } if tagged, ok := ref.(reference.Tagged); ok { comp.tag = tagged.Tag() } else if digested, ok := ref.(reference.Digested); ok { comp.digest = digested.Digest().String() } return comp, nil } // SplitHostname splits a repository name(ie: registry.k8s.io/kube-apiserver) to hostname(registry.k8s.io) and remotename(kube-apiserver) string. func SplitHostname(name string) (hostname, remoteName string) { i := strings.IndexRune(name, '/') if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { hostname, remoteName = "", name } else { hostname, remoteName = name[:i], name[i+1:] } return }