All checks were successful
Test / Sandbox (push) Successful in 2m30s
Test / Hakurei (push) Successful in 2m35s
Test / ShareFS (push) Successful in 3m40s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 5m52s
Test / Hpkg (push) Successful in 4m21s
Test / Create distribution (push) Successful in 43s
Test / Flake checks (push) Successful in 1m52s
This allows for more flexibility during implementation. The use case that required this was for expanding single directory tarballs. Signed-off-by: Ophestra <cat@gensokyo.uk>
131 lines
3.3 KiB
Go
131 lines
3.3 KiB
Go
package pkg
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha512"
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"hakurei.app/container/check"
|
|
)
|
|
|
|
// An httpArtifact is an [Artifact] backed by a [http] url string. The method is
|
|
// hardcoded as [http.MethodGet]. Request body is not allowed because it cannot
|
|
// be deterministically represented by Params.
|
|
type httpArtifact struct {
|
|
// Caller-supplied context.
|
|
ctx context.Context
|
|
// Caller-supplied url string.
|
|
url string
|
|
|
|
// Caller-supplied checksum of the response body. This is validated during
|
|
// curing and the first call to Data.
|
|
checksum Checksum
|
|
|
|
// doFunc is the Do method of [http.Client] supplied by the caller.
|
|
doFunc func(req *http.Request) (*http.Response, error)
|
|
|
|
// Response body read to EOF.
|
|
data []byte
|
|
|
|
// Synchronises access to data.
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewHTTPGet returns a new [File] backed by the supplied client. A GET request
|
|
// is set up for url. If c is nil, [http.DefaultClient] is used instead.
|
|
func NewHTTPGet(
|
|
ctx context.Context,
|
|
c *http.Client,
|
|
url string,
|
|
checksum Checksum,
|
|
) File {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if c == nil {
|
|
c = http.DefaultClient
|
|
}
|
|
return &httpArtifact{ctx: ctx, url: url, checksum: checksum, doFunc: c.Do}
|
|
}
|
|
|
|
// Kind returns the hardcoded [Kind] constant.
|
|
func (a *httpArtifact) Kind() Kind { return KindHTTPGet }
|
|
|
|
// Params returns the backing url string. Context is not represented as it does
|
|
// not affect [Cache.Cure] outcome.
|
|
func (a *httpArtifact) Params() []byte { return []byte(a.url) }
|
|
|
|
// Dependencies returns a nil slice.
|
|
func (a *httpArtifact) Dependencies() []Artifact { return nil }
|
|
|
|
// Checksum returns the address to the caller-supplied checksum.
|
|
func (a *httpArtifact) Checksum() *Checksum { return &a.checksum }
|
|
|
|
// ResponseStatusError is returned for a response returned by an [http.Client]
|
|
// with a status code other than [http.StatusOK].
|
|
type ResponseStatusError int
|
|
|
|
func (e ResponseStatusError) Error() string {
|
|
return "the requested URL returned non-OK status: " + http.StatusText(int(e))
|
|
}
|
|
|
|
// do sends the caller-supplied request on the caller-supplied [http.Client]
|
|
// and reads its response body to EOF and returns the resulting bytes.
|
|
func (a *httpArtifact) do() (data []byte, err error) {
|
|
var req *http.Request
|
|
req, err = http.NewRequestWithContext(a.ctx, http.MethodGet, a.url, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var resp *http.Response
|
|
if resp, err = a.doFunc(req); err != nil {
|
|
return
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
_ = resp.Body.Close()
|
|
return nil, ResponseStatusError(resp.StatusCode)
|
|
}
|
|
|
|
if data, err = io.ReadAll(resp.Body); err != nil {
|
|
_ = resp.Body.Close()
|
|
return
|
|
}
|
|
|
|
err = resp.Body.Close()
|
|
return
|
|
}
|
|
|
|
// Cure returns syscall.ENOTSUP. Callers should use Data instead.
|
|
func (a *httpArtifact) Cure(*check.Absolute, *check.Absolute, CacheDataFunc) error {
|
|
return syscall.ENOTSUP
|
|
}
|
|
|
|
// Data completes the http request and returns the resulting response body read
|
|
// to EOF. Data does not interact with the filesystem.
|
|
func (a *httpArtifact) Data() (data []byte, err error) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
if a.data != nil {
|
|
// validated by cache or a previous call to Data
|
|
return a.data, nil
|
|
}
|
|
|
|
if data, err = a.do(); err != nil {
|
|
return
|
|
}
|
|
|
|
h := sha512.New384()
|
|
h.Write(data)
|
|
if got := (Checksum)(h.Sum(nil)); got != a.checksum {
|
|
return nil, &ChecksumMismatchError{got, a.checksum}
|
|
}
|
|
a.data = data
|
|
return
|
|
}
|