internal/rosa: mirror response write methods
Test / Create distribution (push) Successful in 49s
Test / Sandbox (push) Successful in 2m52s
Test / ShareFS (push) Successful in 3m54s
Test / Hakurei (push) Successful in 4m4s
Test / Sandbox (race detector) (push) Successful in 5m39s
Test / Hakurei (race detector) (push) Successful in 6m42s
Test / Flake checks (push) Successful in 1m12s
Test / Create distribution (push) Successful in 49s
Test / Sandbox (push) Successful in 2m52s
Test / ShareFS (push) Successful in 3m54s
Test / Hakurei (push) Successful in 4m4s
Test / Sandbox (race detector) (push) Successful in 5m39s
Test / Hakurei (race detector) (push) Successful in 6m42s
Test / Flake checks (push) Successful in 1m12s
For the fs.FS implementation and static publishing. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
+1
-1
@@ -100,7 +100,7 @@ func (cache *cache) open() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var r rosa.Remote
|
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()
|
cache.c.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -544,7 +544,9 @@ func main() {
|
|||||||
if base, err := os.OpenRoot(cm.base); err != nil {
|
if base, err := os.OpenRoot(cm.base); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} 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}
|
server := http.Server{Addr: args[0], Handler: h}
|
||||||
|
|||||||
+147
-113
@@ -30,8 +30,8 @@ type Remote struct {
|
|||||||
c *http.Client
|
c *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRemote returns a populated [Remote]
|
// NewRemote returns a populated [Remote].
|
||||||
func NewRemote(base string, pub ed25519.PublicKey, c *http.Client) (Remote, error) {
|
func NewRemote(c *http.Client, base string, pub ed25519.PublicKey) (Remote, error) {
|
||||||
u, err := url.Parse(base)
|
u, err := url.Parse(base)
|
||||||
return Remote{u, pub, c}, err
|
return Remote{u, pub, c}, err
|
||||||
}
|
}
|
||||||
@@ -201,14 +201,153 @@ func (r Remote) Status(
|
|||||||
), nil
|
), 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(
|
func NewMirror(
|
||||||
msg message.Msg,
|
msg message.Msg,
|
||||||
fsys fs.FS,
|
fsys fs.FS,
|
||||||
key ed25519.PrivateKey,
|
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"
|
const identName = "ident"
|
||||||
var mux http.ServeMux
|
|
||||||
|
|
||||||
mux.HandleFunc("/"+dirArtifact+"/{"+identName+"}", func(
|
mux.HandleFunc("/"+dirArtifact+"/{"+identName+"}", func(
|
||||||
w http.ResponseWriter,
|
w http.ResponseWriter,
|
||||||
@@ -222,38 +361,7 @@ func NewMirror(
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
writeF(m, w, &buf, m.writeArtifact)
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/"+dirOutcome+"/{"+identName+"}", func(
|
mux.HandleFunc("/"+dirOutcome+"/{"+identName+"}", func(
|
||||||
@@ -273,40 +381,7 @@ func NewMirror(
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
writeF(m, w, &buf, m.writeOutcome)
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/"+dirStatus+"/{"+identName+"}", func(
|
mux.HandleFunc("/"+dirStatus+"/{"+identName+"}", func(
|
||||||
@@ -321,47 +396,6 @@ func NewMirror(
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
writeF(m, w, &buf, m.writeStatus)
|
||||||
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
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return &mux
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -52,14 +53,16 @@ func TestMirror(t *testing.T) {
|
|||||||
}
|
}
|
||||||
wantChecksums := pkg.Encode(wantChecksum)
|
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)},
|
"identifier/" + ids: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantChecksums)},
|
||||||
"checksum/" + wantChecksums: {Mode: os.ModeDir | 0500},
|
"checksum/" + wantChecksums: {Mode: os.ModeDir | 0500},
|
||||||
}, priv))
|
}, priv).Register(&mux)
|
||||||
|
server := httptest.NewServer(&mux)
|
||||||
t.Cleanup(server.Close)
|
t.Cleanup(server.Close)
|
||||||
|
|
||||||
var extern pkg.External
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user