117 lines
5.2 KiB
Go
117 lines
5.2 KiB
Go
// Package reference holds the form of an artifact's reference once a registry seat published it: `<seat>/<module>/<artifact>@<digest>`
|
|
// (novox/hq ADR 0605 §9), and the older `artifact-store://<path>` form read beside it. It is the Delivery domain's rule over
|
|
// the names of the two registry seats that front the artifact store; it names no seat itself (ADR 0634 §4, ADR 0635 §4): the
|
|
// caller gives the seats' names from their contracts (Seats), so a package here cannot drift from a contract.
|
|
package reference
|
|
|
|
import "strings"
|
|
|
|
// ArtifactStoreScheme marks a reference to something in the mesh's artifact store, kept without the store's address.
|
|
const ArtifactStoreScheme = "artifact-store://"
|
|
|
|
// UpstreamPrefix opens the holder's own name for an upstream copy it keeps: `upstream/<host>/<path>@<digest>`, after
|
|
// `<image seat>/` in the seat's form and after `artifact-store://` in the older one.
|
|
const UpstreamPrefix = "upstream/"
|
|
|
|
// Seats are the names of the two registry seats a reference of the registry form names first: Image the seat that fronts
|
|
// images and upstream copies (container-image-registry), Object the seat that fronts bundles and archives
|
|
// (oci-artifact-registry). The caller reads them from the seats' contracts.
|
|
type Seats struct{ Image, Object string }
|
|
|
|
// All is the two names.
|
|
func (s Seats) All() []string { return []string{s.Image, s.Object} }
|
|
|
|
// Recorded is a reference as the mesh records it: the artifact store's address, if the builder wrote one, taken off.
|
|
func (s Seats) Recorded(reference string) string {
|
|
if _, _, seat := s.InRegistrySeat(reference); seat || strings.HasPrefix(reference, ArtifactStoreScheme) {
|
|
return reference
|
|
}
|
|
if rest, isURL := strings.CutPrefix(reference, "http://"); isURL {
|
|
if _, path, ok := strings.Cut(rest, "/v2/"); ok && strings.Contains(path, "/blobs/") {
|
|
return ArtifactStoreScheme + path
|
|
}
|
|
return reference
|
|
}
|
|
host, path, ok := strings.Cut(reference, "/")
|
|
if !ok || !isRegistryHost(host) || !strings.Contains(path, "@sha256:") {
|
|
return reference
|
|
}
|
|
return ArtifactStoreScheme + path
|
|
}
|
|
|
|
// isRegistryHost is the runtime's own rule for reading the first component of a reference as a registry rather than a
|
|
// namespace: it has a dot or a port in it, or it is localhost.
|
|
func isRegistryHost(component string) bool {
|
|
return component == "localhost" || strings.ContainsAny(component, ".:")
|
|
}
|
|
|
|
// InArtifactStore reports whether a reference is a kept one, and what it names there: `<module>/<artifact>@<digest>` for an
|
|
// image, `<module>/<artifact>/blobs/<digest>` for a bundle or an archive. It reads both forms (ADR 0605 §9).
|
|
func (s Seats) InArtifactStore(reference string) (path string, kept bool) {
|
|
if seat, rest, ok := s.InRegistrySeat(reference); ok {
|
|
if seat == s.Object {
|
|
name, digest, _ := strings.Cut(rest, "@")
|
|
return name + "/blobs/" + digest, true
|
|
}
|
|
return rest, true
|
|
}
|
|
return strings.CutPrefix(reference, ArtifactStoreScheme)
|
|
}
|
|
|
|
// SeatReference is the reference a build names an image, bundle, archive or upstream copy by once it published it through
|
|
// a registry seat: `<seat>/<module>/<artifact>@sha256:<digest>`, as InRegistrySeat reads it back.
|
|
func SeatReference(seat, module, artifact, digest string) string {
|
|
return seat + "/" + module + "/" + artifact + "@" + digest
|
|
}
|
|
|
|
// InRegistrySeat reads a reference in the form a registry seat answers: the seat it came through and the rest. A seat's
|
|
// name holds no dot or port, so no image of an outside registry reads as one, and a kept `artifact-store://` reference is
|
|
// not one. It also reads the holder's own naming of an upstream copy through the image seat, whose rest InUpstream reads.
|
|
func (s Seats) InRegistrySeat(reference string) (seat, path string, ok bool) {
|
|
first, rest, found := strings.Cut(reference, "/")
|
|
if !found || !strings.Contains(rest, "@sha256:") {
|
|
return "", "", false
|
|
}
|
|
if _, _, upstream := InUpstream(rest); upstream && first == s.Image {
|
|
return first, rest, true
|
|
}
|
|
if strings.Count(rest, "/") != 1 {
|
|
return "", "", false
|
|
}
|
|
for _, name := range s.All() {
|
|
if first == name {
|
|
return name, rest, true
|
|
}
|
|
}
|
|
return "", "", false
|
|
}
|
|
|
|
// InUpstream reads a path in the holder's naming of an upstream copy, `upstream/<host>/<path>@sha256:<digest>`: the
|
|
// upstream registry host and the image's path there, with its digest.
|
|
func InUpstream(path string) (host, image string, ok bool) {
|
|
rest, found := strings.CutPrefix(path, UpstreamPrefix)
|
|
if !found {
|
|
return "", "", false
|
|
}
|
|
host, image, found = strings.Cut(rest, "/")
|
|
name, digest, pinned := strings.Cut(image, "@")
|
|
if !found || !isRegistryHost(host) || name == "" || strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") ||
|
|
strings.Contains(name, "//") || !pinned || !strings.HasPrefix(digest, "sha256:") || strings.Contains(digest, "/") {
|
|
return "", "", false
|
|
}
|
|
return host, image, true
|
|
}
|
|
|
|
// Routed is a kept reference as a machine fetches it, through the artifact store at `address` (host:port). A reference
|
|
// that is not a kept one is what it was.
|
|
func (s Seats) Routed(reference, address string) string {
|
|
path, kept := s.InArtifactStore(reference)
|
|
if !kept {
|
|
return reference
|
|
}
|
|
if strings.Contains(path, "/blobs/") {
|
|
return "http://" + address + "/v2/" + path
|
|
}
|
|
return address + "/" + path
|
|
}
|