107 lines
2.7 KiB
Go
107 lines
2.7 KiB
Go
|
// SPDX-FileCopyrightText: 2023 Luke Granger-Brown <depot@lukegb.com>
|
||
|
//
|
||
|
// SPDX-License-Identifier: Apache-2.0
|
||
|
|
||
|
package nixbuild
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"os"
|
||
|
"regexp"
|
||
|
|
||
|
"github.com/numtide/go-nix/nixbase32"
|
||
|
"hg.lukegb.com/lukegb/depot/go/nix/nar/narinfo"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
hashExtractRegexp = regexp.MustCompile(`(^|/)([0-9a-df-np-sv-z]{32})([-.].*)?$`)
|
||
|
)
|
||
|
|
||
|
func hashExtract(s string) string {
|
||
|
res := hashExtractRegexp.FindStringSubmatch(s)
|
||
|
if len(res) == 0 {
|
||
|
return ""
|
||
|
}
|
||
|
return res[2]
|
||
|
}
|
||
|
|
||
|
func keyForPath(storePath string) (string, error) {
|
||
|
fileHash := hashExtract(storePath)
|
||
|
if fileHash == "" {
|
||
|
return "", fmt.Errorf("store path %v seems to be invalid: couldn't extract hash", storePath)
|
||
|
}
|
||
|
|
||
|
return fmt.Sprintf("%s.narinfo", fileHash), nil
|
||
|
}
|
||
|
|
||
|
type HTTPCacheResolver struct {
|
||
|
Endpoint string
|
||
|
HTTPClient *http.Client
|
||
|
Priority int
|
||
|
}
|
||
|
|
||
|
var _ Resolver = ((*HTTPCacheResolver)(nil))
|
||
|
|
||
|
func (r *HTTPCacheResolver) RelativePriority() int { return r.Priority }
|
||
|
|
||
|
func (r *HTTPCacheResolver) FetchNarInfo(ctx context.Context, path string) (*narinfo.NarInfo, error) {
|
||
|
key, err := keyForPath(path)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%v/%v", r.Endpoint, key), nil)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("constructing request for %v/%v: %v", r.Endpoint, key, err)
|
||
|
}
|
||
|
|
||
|
resp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("making request for %v/%v: %v", r.Endpoint, key, err)
|
||
|
}
|
||
|
defer resp.Body.Close()
|
||
|
|
||
|
switch resp.StatusCode {
|
||
|
case http.StatusNotFound:
|
||
|
return nil, os.ErrNotExist
|
||
|
case http.StatusOK:
|
||
|
// continue with function
|
||
|
default:
|
||
|
return nil, fmt.Errorf("%v/%v: HTTP %v", r.Endpoint, key, resp.Status)
|
||
|
}
|
||
|
|
||
|
return narinfo.ParseNarInfo(resp.Body)
|
||
|
}
|
||
|
|
||
|
func (r *HTTPCacheResolver) FetchNar(ctx context.Context, ni *narinfo.NarInfo) (io.ReadCloser, error) {
|
||
|
compressionSuffix, err := ni.Compression.FileSuffix()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("determining filename: %w", err)
|
||
|
}
|
||
|
key := fmt.Sprintf("nar/%s.nar%s", nixbase32.EncodeToString(ni.FileHash.Hash), compressionSuffix)
|
||
|
|
||
|
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%v/%v", r.Endpoint, key), nil)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("constructing request for %v/%v: %v", r.Endpoint, key, err)
|
||
|
}
|
||
|
|
||
|
resp, err := http.DefaultClient.Do(req)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("making request for %v/%v: %v", r.Endpoint, key, err)
|
||
|
}
|
||
|
|
||
|
switch resp.StatusCode {
|
||
|
case http.StatusNotFound:
|
||
|
resp.Body.Close()
|
||
|
return nil, os.ErrNotExist
|
||
|
case http.StatusOK:
|
||
|
return resp.Body, nil
|
||
|
default:
|
||
|
resp.Body.Close()
|
||
|
return nil, fmt.Errorf("downloading %v/%v: HTTP status code %v", r.Endpoint, key, resp.Status)
|
||
|
}
|
||
|
}
|