2023-10-03 13:49:18 +02:00
|
|
|
package http
|
2022-11-19 21:34:49 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
|
2023-10-17 22:41:33 +02:00
|
|
|
castorev1pb "code.tvl.fyi/tvix/castore-go"
|
2023-10-03 11:57:03 +02:00
|
|
|
"code.tvl.fyi/tvix/nar-bridge/pkg/importer"
|
2022-11-19 21:34:49 +01:00
|
|
|
"github.com/go-chi/chi/v5"
|
2023-10-11 13:56:12 +02:00
|
|
|
mh "github.com/multiformats/go-multihash/core"
|
2022-11-19 21:34:49 +01:00
|
|
|
nixhash "github.com/nix-community/go-nix/pkg/hash"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
func registerNarPut(s *Server) {
|
|
|
|
s.handler.Put(narUrl, func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
// parse the narhash sent in the request URL
|
|
|
|
narHashFromUrl, err := parseNarHashFromUrl(chi.URLParamFromCtx(ctx, "narhash"))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).WithField("url", r.URL).Error("unable to decode nar hash from url")
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
_, err := w.Write([]byte("unable to decode nar hash from url"))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("unable to write error message to client")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
log := log.WithField("narhash_url", narHashFromUrl.SRIString())
|
|
|
|
|
2023-10-03 13:42:59 +02:00
|
|
|
directoriesUploader := importer.NewDirectoriesUploader(ctx, s.directoryServiceClient)
|
2022-11-19 21:34:49 +01:00
|
|
|
defer directoriesUploader.Done() //nolint:errcheck
|
|
|
|
|
2023-10-11 12:28:10 +02:00
|
|
|
rootNode, narSize, narSha256, err := importer.Import(
|
2022-11-19 21:34:49 +01:00
|
|
|
ctx,
|
2023-10-03 11:57:03 +02:00
|
|
|
// buffer the body by 10MiB
|
|
|
|
bufio.NewReaderSize(r.Body, 10*1024*1024),
|
2023-10-03 13:40:58 +02:00
|
|
|
importer.GenBlobUploaderCb(ctx, s.blobServiceClient),
|
2023-10-03 12:59:13 +02:00
|
|
|
func(directory *castorev1pb.Directory) ([]byte, error) {
|
2022-11-19 21:34:49 +01:00
|
|
|
return directoriesUploader.Put(directory)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("error during NAR import: %v", err)
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
_, err := w.Write([]byte(fmt.Sprintf("error during NAR import: %v", err)))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Errorf("unable to write error message to client")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-18 11:04:59 +02:00
|
|
|
log.Debug("closing the stream")
|
2022-11-19 21:34:49 +01:00
|
|
|
|
|
|
|
// Close the directories uploader
|
|
|
|
directoriesPutResponse, err := directoriesUploader.Done()
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("error during directory upload")
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
_, err := w.Write([]byte("error during directory upload"))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Errorf("unable to write error message to client")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// If we uploaded directories (so directoriesPutResponse doesn't return null),
|
|
|
|
// the RootDigest field in directoriesPutResponse should match the digest
|
|
|
|
// returned in the PathInfo struct returned by the `Import` call.
|
|
|
|
// This check ensures the server-side came up with the same root hash.
|
|
|
|
|
|
|
|
if directoriesPutResponse != nil {
|
2023-10-11 12:28:10 +02:00
|
|
|
rootDigestPathInfo := rootNode.GetDirectory().GetDigest()
|
2022-11-19 21:34:49 +01:00
|
|
|
rootDigestDirectoriesPutResponse := directoriesPutResponse.GetRootDigest()
|
|
|
|
|
|
|
|
log := log.WithFields(logrus.Fields{
|
|
|
|
"root_digest_pathinfo": rootDigestPathInfo,
|
|
|
|
"root_digest_directories_put_resp": rootDigestDirectoriesPutResponse,
|
|
|
|
})
|
|
|
|
if !bytes.Equal(rootDigestPathInfo, rootDigestDirectoriesPutResponse) {
|
|
|
|
log.Errorf("returned root digest doesn't match what's calculated")
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
_, err := w.Write([]byte("error in root digest calculation"))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Error("unable to write error message to client")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compare the nar hash specified in the URL with the one that has been
|
2023-10-11 12:52:33 +02:00
|
|
|
// calculated while processing the NAR file.
|
2023-10-11 13:56:12 +02:00
|
|
|
narHash, err := nixhash.FromHashTypeAndDigest(mh.SHA2_256, narSha256)
|
2022-11-19 21:34:49 +01:00
|
|
|
if err != nil {
|
|
|
|
panic("must parse nixbase32")
|
|
|
|
}
|
|
|
|
|
2023-10-11 12:28:10 +02:00
|
|
|
if !bytes.Equal(narHashFromUrl.Digest(), narHash.Digest()) {
|
2022-11-19 21:34:49 +01:00
|
|
|
log := log.WithFields(logrus.Fields{
|
2023-10-11 12:28:10 +02:00
|
|
|
"narhash_received_sha256": narHash.SRIString(),
|
|
|
|
"narsize": narSize,
|
2022-11-19 21:34:49 +01:00
|
|
|
})
|
|
|
|
log.Error("received bytes don't match narhash from URL")
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
_, err := w.Write([]byte("received bytes don't match narHash specified in URL"))
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Errorf("unable to write error message to client")
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insert the partial pathinfo structs into our lookup map,
|
|
|
|
// so requesting the NAR file will be possible.
|
|
|
|
// The same might exist already, but it'll have the same contents (so
|
|
|
|
// replacing will be a no-op), except maybe the root node Name field value, which
|
|
|
|
// is safe to ignore (as not part of the NAR).
|
2023-10-11 12:28:10 +02:00
|
|
|
s.narDbMu.Lock()
|
|
|
|
s.narDb[narHash.SRIString()] = &narData{
|
|
|
|
rootNode: rootNode,
|
|
|
|
narSize: narSize,
|
|
|
|
}
|
|
|
|
s.narDbMu.Unlock()
|
2022-11-19 21:34:49 +01:00
|
|
|
|
|
|
|
// Done!
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|