m/test/localregistry: use osbase/oci/registry
This replaces the localregistry implementation with a small wrapper
around the new registry package.
The images attribute of the Bazel rule was changed from a list to a
dict, which makes the repository and tag independent from the file path.
Change-Id: I1f6213dd67f7bdcf2373fe136958caa68b9f4d10
Reviewed-on: https://review.monogon.dev/c/monogon/+/4089
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/test/localregistry/localregistry.go b/metropolis/test/localregistry/localregistry.go
index 1f648e0..694476e 100644
--- a/metropolis/test/localregistry/localregistry.go
+++ b/metropolis/test/localregistry/localregistry.go
@@ -6,170 +6,36 @@
package localregistry
import (
- "bytes"
- "encoding/json"
"fmt"
- "io"
- "log"
- "net/http"
- "os"
- "path/filepath"
- "regexp"
- "strconv"
+ "path"
"github.com/bazelbuild/rules_go/go/runfiles"
- "github.com/docker/distribution"
- "github.com/docker/distribution/manifest/manifestlist"
- "github.com/docker/distribution/manifest/ocischema"
- "github.com/docker/distribution/manifest/schema2"
- "github.com/docker/distribution/reference"
- "github.com/opencontainers/go-digest"
"google.golang.org/protobuf/encoding/prototext"
"source.monogon.dev/metropolis/test/localregistry/spec"
+ "source.monogon.dev/osbase/oci"
+ "source.monogon.dev/osbase/oci/registry"
)
-type Server struct {
- manifests map[string][]byte
- blobs map[digest.Digest]blobMeta
-}
-
-type blobMeta struct {
- filePath string
- mediaType string
- contentLength int64
-}
-
-func manifestDescriptorFromBazel(image *spec.Image) (manifestlist.ManifestDescriptor, error) {
- indexPath, err := runfiles.Rlocation(filepath.Join("_main", image.Path, "index.json"))
- if err != nil {
- return manifestlist.ManifestDescriptor{}, fmt.Errorf("while locating manifest list file: %w", err)
- }
-
- manifestListRaw, err := os.ReadFile(indexPath)
- if err != nil {
- return manifestlist.ManifestDescriptor{}, fmt.Errorf("while opening manifest list file: %w", err)
- }
-
- var imageManifestList manifestlist.ManifestList
- if err := json.Unmarshal(manifestListRaw, &imageManifestList); err != nil {
- return manifestlist.ManifestDescriptor{}, fmt.Errorf("while unmarshaling manifest list for %q: %w", image.Name, err)
- }
-
- if len(imageManifestList.Manifests) != 1 {
- return manifestlist.ManifestDescriptor{}, fmt.Errorf("unexpected manifest list length > 1")
- }
-
- return imageManifestList.Manifests[0], nil
-}
-
-func manifestFromBazel(s *Server, image *spec.Image, md manifestlist.ManifestDescriptor) (ocischema.Manifest, error) {
- manifestPath, err := runfiles.Rlocation(filepath.Join("_main", image.Path, "blobs", md.Digest.Algorithm().String(), md.Digest.Hex()))
- if err != nil {
- return ocischema.Manifest{}, fmt.Errorf("while locating manifest file: %w", err)
- }
- manifestRaw, err := os.ReadFile(manifestPath)
- if err != nil {
- return ocischema.Manifest{}, fmt.Errorf("while opening manifest file: %w", err)
- }
-
- var imageManifest ocischema.Manifest
- if err := json.Unmarshal(manifestRaw, &imageManifest); err != nil {
- return ocischema.Manifest{}, fmt.Errorf("while unmarshaling manifest for %q: %w", image.Name, err)
- }
-
- // For Digest lookups
- s.manifests[image.Name] = manifestRaw
- s.manifests[md.Digest.String()] = manifestRaw
-
- return imageManifest, nil
-}
-
-func addBazelBlobFromDescriptor(s *Server, image *spec.Image, dd distribution.Descriptor) error {
- path, err := runfiles.Rlocation(filepath.Join("_main", image.Path, "blobs", dd.Digest.Algorithm().String(), dd.Digest.Hex()))
- if err != nil {
- return fmt.Errorf("while locating blob: %w", err)
- }
- s.blobs[dd.Digest] = blobMeta{filePath: path, mediaType: dd.MediaType, contentLength: dd.Size}
- return nil
-}
-
-func FromBazelManifest(mb []byte) (*Server, error) {
+func FromBazelManifest(mb []byte) (*registry.Server, error) {
var bazelManifest spec.Manifest
if err := prototext.Unmarshal(mb, &bazelManifest); err != nil {
- log.Fatalf("failed to parse manifest: %v", err)
+ return nil, fmt.Errorf("failed to parse manifest: %w", err)
}
- s := Server{
- manifests: make(map[string][]byte),
- blobs: make(map[digest.Digest]blobMeta),
- }
+ s := registry.NewServer()
for _, i := range bazelManifest.Images {
- md, err := manifestDescriptorFromBazel(i)
+ resolvedPath, err := runfiles.Rlocation(path.Join("_main", i.Path))
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to resolve image path %q: %w", i.Path, err)
}
-
- if err := addBazelBlobFromDescriptor(&s, i, md.Descriptor); err != nil {
- return nil, err
- }
-
- m, err := manifestFromBazel(&s, i, md)
+ image, err := oci.ReadLayout(resolvedPath)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to read image from %q: %w", i.Path, err)
}
-
- if err := addBazelBlobFromDescriptor(&s, i, m.Config); err != nil {
- return nil, err
- }
- for _, l := range m.Layers {
- if err := addBazelBlobFromDescriptor(&s, i, l); err != nil {
- return nil, err
- }
+ err = s.AddImage(i.Repository, i.Tag, image)
+ if err != nil {
+ return nil, fmt.Errorf("failed to add image: %w", err)
}
}
- return &s, nil
-}
-
-var (
- versionCheckEp = regexp.MustCompile(`^/v2/$`)
- manifestEp = regexp.MustCompile("^/v2/(" + reference.NameRegexp.String() + ")/manifests/(" + reference.TagRegexp.String() + "|" + digest.DigestRegexp.String() + ")$")
- blobEp = regexp.MustCompile("^/v2/(" + reference.NameRegexp.String() + ")/blobs/(" + digest.DigestRegexp.String() + ")$")
-)
-
-func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- if req.Method != http.MethodGet && req.Method != http.MethodHead {
- w.WriteHeader(http.StatusMethodNotAllowed)
- fmt.Fprintf(w, "Registry is read-only, only GET and HEAD are allowed\n")
- return
- }
- w.Header().Set("Content-Type", "application/json")
- if versionCheckEp.MatchString(req.URL.Path) {
- w.WriteHeader(http.StatusOK)
- fmt.Fprintf(w, "{}")
- return
- } else if matches := manifestEp.FindStringSubmatch(req.URL.Path); len(matches) > 0 {
- name := matches[1]
- manifest, ok := s.manifests[name]
- if !ok {
- w.WriteHeader(http.StatusNotFound)
- fmt.Fprintf(w, "Image not found")
- return
- }
- w.Header().Set("Content-Type", schema2.MediaTypeManifest)
- w.Header().Set("Content-Length", strconv.FormatInt(int64(len(manifest)), 10))
- w.WriteHeader(http.StatusOK)
- io.Copy(w, bytes.NewReader(manifest))
- } else if matches := blobEp.FindStringSubmatch(req.URL.Path); len(matches) > 0 {
- bm, ok := s.blobs[digest.Digest(matches[2])]
- if !ok {
- w.WriteHeader(http.StatusNotFound)
- fmt.Fprintf(w, "Blob not found")
- return
- }
- w.Header().Set("Content-Type", bm.mediaType)
- w.Header().Set("Content-Length", strconv.FormatInt(bm.contentLength, 10))
- http.ServeFile(w, req, bm.filePath)
- } else {
- w.WriteHeader(http.StatusNotFound)
- }
+ return s, nil
}