package catalogue import ( "strings" contractseats "git.novox.be/novox/mesh-contracts/go/seats" ) // ArtifactStoreScheme marks a reference to something in the mesh's artifact store, kept without // the store's address. const ArtifactStoreScheme = ArtifactStoreProvision + "://" // Recorded is a reference as the mesh records it: the artifact store's address, if the builder wrote // one, taken off. // // For a reference the builder announced — one it pushed to the store — which is the only kind // this is called on. An image the builder named `/@sha256:…` is kept as its path; an // archive it named `http:///v2//blobs/` likewise. A reference with no address // in it — an image id from a genesis build that had nowhere to publish, a package version — is // what it was. func Recorded(reference string) string { if _, _, seat := 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 as 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: `/@` // for an image, `//blobs/` for a bundle or an archive. **It reads both forms** (novox/hq ADR // 0605 §9): the older `artifact-store://`, and the form a registry seat answers, `//@` // through container-image-registry or oci-artifact-registry, whose blob it names the way the older form did. func InArtifactStore(reference string) (path string, kept bool) { if seat, rest, ok := InRegistrySeat(reference); ok { if seat == OciArtifactRegistrySeat { name, digest, _ := strings.Cut(rest, "@") return name + "/blobs/" + digest, true } return rest, true } return strings.CutPrefix(reference, ArtifactStoreScheme) } // RegistrySeats are the seats a reference of the registry form names first, the two that front the artifact store // (novox/hq ADR 0605 §1, §9). var RegistrySeats = []string{ContainerImageRegistrySeat, OciArtifactRegistrySeat} // The four registry seats (novox/hq ADR 0605 §1), named here so the rest of mesh-sdk takes them from the seat set // through catalogue alone (ADR 0625 §3). const ( NpmPackageRegistrySeat = contractseats.NpmPackageRegistry GoPackageRegistrySeat = contractseats.GoPackageRegistry ContainerImageRegistrySeat = contractseats.ContainerImageRegistry OciArtifactRegistrySeat = contractseats.OciArtifactRegistry ) // PublishSeats are the seats an artifact is published through (ADR 0605 §1). var PublishSeats = []string{NpmPackageRegistrySeat, GoPackageRegistrySeat, ContainerImageRegistrySeat, OciArtifactRegistrySeat} // The seats of the vault, the provisioner, the hand acts' record and the conditions' keeper, as the seat set names them, // for the rest of mesh-sdk (ADR 0625 §3). const ( VaultSeat = contractseats.MeshVault ProvisionerSeat = contractseats.MeshProvisioner ) // SeatReference is the reference a build names an image, bundle, archive or upstream copy by once it published it // through a registry seat (novox/hq ADR 0605 §9): `//@sha256:`, as InRegistrySeat reads // it back. The store's address is left out, as in the older form: a reader routes it (Routed). func SeatReference(seat, module, artifact, digest string) string { return seat + "/" + module + "/" + artifact + "@" + digest } // InRegistrySeat reads a reference in the form a registry seat answers, `//@sha256:`: // 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 container-image-registry, // `container-image-registry/upstream//@sha256:`, whose rest InUpstream reads. func 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 == ContainerImageRegistrySeat { return first, rest, true } if strings.Count(rest, "/") != 1 { return "", "", false } for _, s := range RegistrySeats { if first == s { return s, rest, true } } return "", "", false } // UpstreamPrefix opens the holder's own name for an upstream copy it keeps: `upstream//@`, after // `container-image-registry/` in the seat's form and after `artifact-store://` in the older one. const UpstreamPrefix = "upstream/" // InUpstream reads a path in the holder's naming of an upstream copy, `upstream//@sha256:` (the path // InRegistrySeat or InArtifactStore gives back): the upstream registry host and the image's path there, with its digest. // A host is read as the runtime reads one (a dot, a port, or localhost). 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 Routed(reference, address string) string { path, kept := InArtifactStore(reference) if !kept { return reference } if strings.Contains(path, "/blobs/") { return "http://" + address + "/v2/" + path } return address + "/" + path }