From 76360979f9b7898c1e331914b6037b01eab79807 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 7 Jul 2026 20:18:18 +0900 Subject: [PATCH] internal/rosa: mirror response write methods For the fs.FS implementation and static publishing. Signed-off-by: Ophestra --- cmd/mbf/cache.go | 2 +- cmd/mbf/main.go | 4 +- internal/rosa/mirror.go | 260 ++++++++++++++++++++--------------- internal/rosa/mirror_test.go | 9 +- 4 files changed, 157 insertions(+), 118 deletions(-) diff --git a/cmd/mbf/cache.go b/cmd/mbf/cache.go index 20dbd855..aea54546 100644 --- a/cmd/mbf/cache.go +++ b/cmd/mbf/cache.go @@ -100,7 +100,7 @@ func (cache *cache) open() (err error) { return } var r rosa.Remote - if r, err = rosa.NewRemote(cache.mirror, pub, http.DefaultClient); err != nil { + if r, err = rosa.NewRemote(http.DefaultClient, cache.mirror, pub); err != nil { cache.c.Close() return err } diff --git a/cmd/mbf/main.go b/cmd/mbf/main.go index b2f2c0d6..471b595a 100644 --- a/cmd/mbf/main.go +++ b/cmd/mbf/main.go @@ -544,7 +544,9 @@ func main() { if base, err := os.OpenRoot(cm.base); err != nil { return err } else { - h = rosa.NewMirror(msg, base.FS(), key) + var mux http.ServeMux + rosa.NewMirror(msg, base.FS(), key).Register(&mux) + h = &mux } server := http.Server{Addr: args[0], Handler: h} diff --git a/internal/rosa/mirror.go b/internal/rosa/mirror.go index 09419396..d4dfba1e 100644 --- a/internal/rosa/mirror.go +++ b/internal/rosa/mirror.go @@ -30,8 +30,8 @@ type Remote struct { c *http.Client } -// NewRemote returns a populated [Remote] -func NewRemote(base string, pub ed25519.PublicKey, c *http.Client) (Remote, error) { +// NewRemote returns a populated [Remote]. +func NewRemote(c *http.Client, base string, pub ed25519.PublicKey) (Remote, error) { u, err := url.Parse(base) return Remote{u, pub, c}, err } @@ -201,14 +201,153 @@ func (r Remote) Status( ), nil } -// NewMirror returns an [http.Handler] for servicing mirror requests. +// Mirror services requests made by [Remote]. +type Mirror struct { + // Cache on-filesystem format. + fsys fs.FS + // Signs payloads. + key ed25519.PrivateKey + // Verbose output. + msg message.Msg +} + +// NewMirror returns a populated [Mirror] for servicing mirror requests. func NewMirror( msg message.Msg, fsys fs.FS, key ed25519.PrivateKey, -) http.Handler { +) Mirror { + return Mirror{fsys, key, msg} +} + +// writeArtifact writes a signed dirArtifact response. +func (m Mirror) writeArtifact( + buf *[2 * len(pkg.Checksum{})]byte, + w io.Writer, + header http.Header, +) error { + ids := pkg.Encode((pkg.Checksum)(buf[:len(pkg.Checksum{})])) + if linkname, err := fs.ReadLink(m.fsys, filepath.Join( + "identifier", + ids, + )); err != nil { + return err + } else if err = pkg.Decode( + (*pkg.Checksum)(buf[len(pkg.Checksum{}):]), + filepath.Base(linkname), + ); err != nil { + return err + } + m.msg.Verbosef("serving artifact %s", ids) + + if header != nil { + header.Set( + "Content-Length", + strconv.Itoa(ed25519.SignatureSize+len(buf)), + ) + } + _, err := w.Write(append(ed25519.Sign(m.key, buf[:]), buf[:]...)) + return err +} + +// writeOutcome writes a dirOutcome response. +func (m Mirror) writeOutcome( + buf *pkg.Checksum, + w io.Writer, + header http.Header, +) error { + checksums := pkg.Encode(*buf) + rel := filepath.Join("checksum", checksums) + if _, err := fs.Lstat(m.fsys, rel); err != nil { + return err + } + m.msg.Verbosef("serving outcome %s", checksums) + + _fsys, err := fs.Sub(m.fsys, rel) + if err != nil { + return err + } + + var gw *gzip.Writer + if gw, err = gzip.NewWriterLevel(w, gzip.BestCompression); err != nil { + return err + } + if header != nil { + header.Set("Content-Encoding", "gzip") + } + if err = pkg.Write(_fsys, ".", gw); err != nil { + _ = gw.Close() + return err + } + return gw.Close() +} + +// writeStatus writes a signed dirStatus response. +func (m Mirror) writeStatus( + buf *[2 * len(pkg.Checksum{})]byte, + w io.Writer, + _ http.Header, +) error { + ids := pkg.Encode((pkg.Checksum)(buf[:len(pkg.Checksum{})])) + f, err := m.fsys.Open(filepath.Join( + "status", + ids, + )) + if err != nil { + return err + } + s, ok := f.(io.Seeker) + if !ok { + return &fs.PathError{Op: "seek", Path: ids, Err: fs.ErrInvalid} + } + m.msg.Verbosef("serving status %s", ids) + + h := sha512.New384() + if _, err = io.Copy(h, f); err != nil { + _ = f.Close() + return err + } + h.Sum(buf[len(pkg.Checksum{}):len(pkg.Checksum{})]) + if _, err = w.Write(append(ed25519.Sign(m.key, buf[:]), buf[:]...)); err != nil { + _ = f.Close() + return err + } else if _, err = s.Seek(0, io.SeekStart); err != nil { + _ = f.Close() + return err + } else if _, err = io.Copy(w, f); err != nil { + _ = f.Close() + return err + } + return f.Close() +} + +// writeF is a helper for writing a mirror response with error handling. +func writeF[B any]( + m Mirror, + w http.ResponseWriter, + buf B, + f func( + buf B, + w io.Writer, + header http.Header, + ) error, +) { + err := f(buf, w, w.Header()) + if err == nil { + return + } + + if errors.Is(err, os.ErrNotExist) { + w.WriteHeader(http.StatusNotFound) + return + } + m.msg.GetLogger().Println(err) + w.WriteHeader(http.StatusInternalServerError) +} + +// Register configures an [http.ServeMux] for servicing mirror requests. +func (m Mirror) Register(mux *http.ServeMux) { const identName = "ident" - var mux http.ServeMux mux.HandleFunc("/"+dirArtifact+"/{"+identName+"}", func( w http.ResponseWriter, @@ -222,38 +361,7 @@ func NewMirror( http.Error(w, err.Error(), http.StatusBadRequest) return } - - ids := pkg.Encode((pkg.Checksum)(buf[:len(pkg.Checksum{})])) - if linkname, err := fs.ReadLink(fsys, filepath.Join( - "identifier", - ids, - )); err != nil { - if errors.Is(err, os.ErrNotExist) { - w.WriteHeader(http.StatusNotFound) - return - } - msg.GetLogger().Println(err) - w.WriteHeader(http.StatusInternalServerError) - return - } else if err = pkg.Decode( - (*pkg.Checksum)(buf[len(pkg.Checksum{}):]), - filepath.Base(linkname), - ); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - msg.Verbosef("serving artifact %s", ids) - - w.Header().Set( - "Content-Length", - strconv.Itoa(ed25519.SignatureSize+len(buf)), - ) - if _, err := w.Write(append( - ed25519.Sign(key, buf[:]), - buf[:]..., - )); err != nil { - msg.Verbose(err) - } + writeF(m, w, &buf, m.writeArtifact) }) mux.HandleFunc("/"+dirOutcome+"/{"+identName+"}", func( @@ -273,40 +381,7 @@ func NewMirror( http.Error(w, err.Error(), http.StatusBadRequest) return } - - checksums := pkg.Encode(buf) - rel := filepath.Join("checksum", checksums) - if _, err := fs.Lstat(fsys, rel); err != nil { - if errors.Is(err, os.ErrNotExist) { - w.WriteHeader(http.StatusNotFound) - return - } - msg.GetLogger().Println(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - msg.Verbosef("serving outcome %s", pkg.Encode(buf)) - - _fsys, err := fs.Sub(fsys, rel) - if err != nil { - msg.GetLogger().Println(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - - var gw *gzip.Writer - if gw, err = gzip.NewWriterLevel(w, gzip.BestCompression); err != nil { - msg.GetLogger().Println(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - w.Header().Set("Content-Encoding", "gzip") - if err = pkg.Write(_fsys, ".", gw); err != nil { - msg.Verbose(err) - } - if err = gw.Close(); err != nil { - msg.GetLogger().Println(err) - } + writeF(m, w, &buf, m.writeOutcome) }) mux.HandleFunc("/"+dirStatus+"/{"+identName+"}", func( @@ -321,47 +396,6 @@ func NewMirror( http.Error(w, err.Error(), http.StatusBadRequest) return } - - ids := pkg.Encode((pkg.Checksum)(buf[:len(pkg.Checksum{})])) - f, err := fsys.Open(filepath.Join( - "status", - ids, - )) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - w.WriteHeader(http.StatusNotFound) - return - } - msg.GetLogger().Println(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - s, ok := f.(io.Seeker) - if !ok { - msg.GetLogger().Println("backing filesystem does not support seek") - w.WriteHeader(http.StatusInternalServerError) - return - } - msg.Verbosef("serving status %s", ids) - - h := sha512.New384() - if _, err = io.Copy(h, f); err != nil { - _ = f.Close() - msg.Verbose(err) - w.WriteHeader(http.StatusInternalServerError) - } - h.Sum(buf[len(pkg.Checksum{}):len(pkg.Checksum{})]) - if _, err = w.Write(append(ed25519.Sign(key, buf[:]), buf[:]...)); err != nil { - msg.Verbose(err) - return - } else if _, err = s.Seek(0, io.SeekStart); err != nil { - msg.GetLogger().Println(err) - return - } else if _, err = io.Copy(w, f); err != nil { - msg.Verbose(err) - return - } + writeF(m, w, &buf, m.writeStatus) }) - - return &mux } diff --git a/internal/rosa/mirror_test.go b/internal/rosa/mirror_test.go index a671cd31..acd45c39 100644 --- a/internal/rosa/mirror_test.go +++ b/internal/rosa/mirror_test.go @@ -4,6 +4,7 @@ import ( "crypto/ed25519" "io/fs" "log" + "net/http" "net/http/httptest" "os" "testing" @@ -52,14 +53,16 @@ func TestMirror(t *testing.T) { } wantChecksums := pkg.Encode(wantChecksum) - server := httptest.NewServer(rosa.NewMirror(msg, fstest.MapFS{ + var mux http.ServeMux + rosa.NewMirror(msg, fstest.MapFS{ "identifier/" + ids: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantChecksums)}, "checksum/" + wantChecksums: {Mode: os.ModeDir | 0500}, - }, priv)) + }, priv).Register(&mux) + server := httptest.NewServer(&mux) t.Cleanup(server.Close) var extern pkg.External - extern, err = rosa.NewRemote("http://example.com:80", pub, server.Client()) + extern, err = rosa.NewRemote(server.Client(), "http://example.com:80", pub) if err != nil { t.Fatal(err) }