1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
package http
import (
"bufio"
"bytes"
"fmt"
"net/http"
castorev1pb "code.tvl.fyi/tvix/castore/protos"
"code.tvl.fyi/tvix/nar-bridge/pkg/importer"
"github.com/go-chi/chi/v5"
nixhash "github.com/nix-community/go-nix/pkg/hash"
"github.com/nix-community/go-nix/pkg/nixbase32"
"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())
directoriesUploader := importer.NewDirectoriesUploader(ctx, s.directoryServiceClient)
defer directoriesUploader.Done() //nolint:errcheck
rootNode, narSize, narSha256, err := importer.Import(
ctx,
// buffer the body by 10MiB
bufio.NewReaderSize(r.Body, 10*1024*1024),
importer.GenBlobUploaderCb(ctx, s.blobServiceClient),
func(directory *castorev1pb.Directory) ([]byte, error) {
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
}
log.Debug("closing the stream")
// 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 {
rootDigestPathInfo := rootNode.GetDirectory().GetDigest()
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
// calculated while processing the NAR file
// TODO: bump go-nix and remove the parsing
narHash, err := nixhash.ParseNixBase32(
"sha256:" + nixbase32.EncodeToString(narSha256),
)
if err != nil {
panic("must parse nixbase32")
}
if !bytes.Equal(narHashFromUrl.Digest(), narHash.Digest()) {
log := log.WithFields(logrus.Fields{
"narhash_received_sha256": narHash.SRIString(),
"narsize": narSize,
})
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).
s.narDbMu.Lock()
s.narDb[narHash.SRIString()] = &narData{
rootNode: rootNode,
narSize: narSize,
}
s.narDbMu.Unlock()
// Done!
})
}
|