| // Package localregistry implements a read-only OCI Distribution / Docker | 
 | // V2 container image registry backed by local layers. | 
 | package localregistry | 
 |  | 
 | import ( | 
 | 	"bytes" | 
 | 	"encoding/json" | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"log" | 
 | 	"net/http" | 
 | 	"os" | 
 | 	"regexp" | 
 | 	"strconv" | 
 |  | 
 | 	"github.com/docker/distribution" | 
 | 	"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/pkg/localregistry/spec" | 
 | ) | 
 |  | 
 | type Server struct { | 
 | 	manifests map[string][]byte | 
 | 	blobs     map[digest.Digest]blobMeta | 
 | } | 
 |  | 
 | type blobMeta struct { | 
 | 	filePath      string | 
 | 	mediaType     string | 
 | 	contentLength int64 | 
 | } | 
 |  | 
 | func blobFromBazel(s *Server, bd *spec.BlobDescriptor, mediaType string) (distribution.Descriptor, error) { | 
 | 	digestRaw, err := os.ReadFile(bd.DigestPath) | 
 | 	if err != nil { | 
 | 		return distribution.Descriptor{}, fmt.Errorf("while opening digest file: %w", err) | 
 | 	} | 
 | 	stat, err := os.Stat(bd.FilePath) | 
 | 	if err != nil { | 
 | 		return distribution.Descriptor{}, fmt.Errorf("while stat'ing blob file: %w", err) | 
 | 	} | 
 | 	digest := digest.Digest("sha256:" + string(digestRaw)) | 
 | 	s.blobs[digest] = blobMeta{filePath: bd.FilePath, mediaType: mediaType, contentLength: stat.Size()} | 
 | 	return distribution.Descriptor{ | 
 | 		MediaType: mediaType, | 
 | 		Size:      stat.Size(), | 
 | 		Digest:    digest, | 
 | 	}, nil | 
 | } | 
 |  | 
 | func FromBazelManifest(m []byte) (*Server, error) { | 
 | 	var manifest spec.Manifest | 
 | 	if err := prototext.Unmarshal(m, &manifest); err != nil { | 
 | 		log.Fatalf("failed to parse manifest: %v", err) | 
 | 	} | 
 | 	s := Server{ | 
 | 		manifests: make(map[string][]byte), | 
 | 		blobs:     make(map[digest.Digest]blobMeta), | 
 | 	} | 
 | 	for _, i := range manifest.Images { | 
 | 		imageManifest := schema2.Manifest{ | 
 | 			Versioned: schema2.SchemaVersion, | 
 | 		} | 
 | 		var err error | 
 | 		imageManifest.Config, err = blobFromBazel(&s, i.Config, schema2.MediaTypeImageConfig) | 
 | 		if err != nil { | 
 | 			return nil, fmt.Errorf("while creating blob spec for %q: %w", i.Name, err) | 
 | 		} | 
 | 		for _, l := range i.Layers { | 
 | 			ml, err := blobFromBazel(&s, l, schema2.MediaTypeLayer) | 
 | 			if err != nil { | 
 | 				return nil, fmt.Errorf("while creating blob spec for %q: %w", i.Name, err) | 
 | 			} | 
 | 			imageManifest.Layers = append(imageManifest.Layers, ml) | 
 | 		} | 
 | 		s.manifests[i.Name], err = json.Marshal(imageManifest) | 
 | 		if err != nil { | 
 | 			return nil, fmt.Errorf("while marshaling image %q manifest: %w", i.Name, err) | 
 | 		} | 
 | 		// For Digest lookups | 
 | 		s.manifests[string(digest.Canonical.FromBytes(s.manifests[i.Name]))] = s.manifests[i.Name] | 
 | 	} | 
 | 	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) | 
 | 	} | 
 | } |