1 Commits
Author SHA1 Message Date
sneak 93c66b1c6e Load the manifest check downloads from memory, not a temp file (closes #223)
check / check (push) Canceled after 0s
check given a URL wrote the manifest it downloaded to a file in the temp
directory before loading it, so it failed wherever that directory is
missing or cannot be written, such as the scratch image script/cibuild
builds. It now keeps the manifest in memory, as fetch, list and export
do. NewChecker reads a manifest handed to it as a reader through the new
CheckerOptions.Manifest field, in place of ManifestPath, because it
otherwise reads the manifest from the filesystem holding the files it
checks.

Model: opus-5-5
2026-10-15 18:09:48 +00:00
5 changed files with 37 additions and 21 deletions
+1 -1
View File
@@ -299,7 +299,7 @@ func (mfa *CLIApp) checkManifestOperation(
return downloadErr
}
opts.ManifestReader = bytes.NewReader(manifestData)
opts.Manifest = bytes.NewReader(manifestData)
} else {
opts.ManifestPath = manifestPath
}
+15 -4
View File
@@ -19,6 +19,7 @@ import (
"github.com/dustin/go-humanize"
"github.com/multiformats/go-multihash"
"github.com/spf13/afero"
"github.com/urfave/cli/v3"
"sneak.berlin/go/mfer/internal/log"
"sneak.berlin/go/mfer/mfer"
@@ -553,12 +554,22 @@ func checkNoNameClash(files []*mfer.MFFilePath) error {
}
// verifyFetchedSigner enforces --require-signature on the fetched manifest
// exactly as check does. The Checker it builds is used only for the
// manifest's signature, so its base path is never read.
// exactly as check does. verifyRequiredSigner takes a Checker, which loads
// its manifest from a file, so the manifest is handed to it as a file in
// memory.
func verifyFetchedSigner(manifestData []byte, requiredSigner string) error {
memFs := afero.NewMemMapFs()
manifestPath := "/" + defaultManifestName
err := afero.WriteFile(memFs, manifestPath, manifestData, filePerms)
if err != nil {
return err
}
chk, err := mfer.NewChecker(&mfer.CheckerOptions{
ManifestReader: bytes.NewReader(manifestData),
BasePath: "/",
ManifestPath: manifestPath,
BasePath: "/",
Fs: memFs,
})
if err != nil {
return fmt.Errorf("load manifest: %w", err)
+9 -4
View File
@@ -489,17 +489,18 @@ func TestFetchStopsReadingFilePastListedSize(t *testing.T) {
// TestManifestDownloadStopsAtLimit serves a manifest that never ends to
// fetch and to check, with the most they download of a manifest lowered
// to 64 KiB. Each must stop reading at that limit and fail with an error
// naming it.
// to 64 KiB. Each must stop reading at that limit, fail with an error
// naming it and leave no temp file.
func TestManifestDownloadStopsAtLimit(t *testing.T) {
t.Parallel()
server := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.Copy(w, zeros{})
}))
defer server.Close()
tmpDir := t.TempDir()
t.Setenv("TMPDIR", tmpDir)
mfa := &CLIApp{Fs: afero.NewOsFs(), maxManifestSize: 64 << 10}
fetch := mfa.fetchCommand()
@@ -519,6 +520,10 @@ func TestManifestDownloadStopsAtLimit(t *testing.T) {
"download manifest: file exceeds maximum allowed size of 65536 bytes",
cmd.Name)
}
leftover, err := os.ReadDir(tmpDir)
require.NoError(t, err)
assert.Empty(t, leftover)
}
//nolint:paralleltest // changes the process-global working directory
+9 -9
View File
@@ -81,7 +81,7 @@ type Checker struct {
manifestPaths map[RelFilePath]struct{}
// manifestInfo is the manifest file, which FindExtraFiles leaves out,
// matched with os.SameFile. It is nil for a manifest read from
// CheckerOptions.ManifestReader.
// CheckerOptions.Manifest.
manifestInfo os.FileInfo
// signature info from the manifest
signature []byte
@@ -92,12 +92,12 @@ type Checker struct {
// CheckerOptions configures a Checker.
type CheckerOptions struct {
// ManifestPath is the manifest file to check against. It is required
// unless ManifestReader is given.
// unless Manifest is given.
ManifestPath string
// ManifestReader, if not nil, is read for the manifest to check against
// in place of ManifestPath, such as a manifest downloaded into memory.
// Manifest, if not nil, is read for the manifest to check against in
// place of ManifestPath, such as a manifest downloaded into memory.
// FindExtraFiles then has no manifest file to leave out.
ManifestReader io.Reader
Manifest io.Reader
// BasePath is the directory relative to which manifest paths are
// resolved (required).
BasePath string
@@ -107,9 +107,9 @@ type CheckerOptions struct {
// NewChecker creates a new Checker with the given options. It returns an
// error if opts is nil, the base path is empty, or neither a manifest path
// nor a manifest reader is given.
// nor a manifest is given.
func NewChecker(opts *CheckerOptions) (*Checker, error) {
if opts == nil || (opts.ManifestPath == "" && opts.ManifestReader == nil) {
if opts == nil || (opts.ManifestPath == "" && opts.Manifest == nil) {
return nil, errManifestPathEmpty
}
@@ -128,8 +128,8 @@ func NewChecker(opts *CheckerOptions) (*Checker, error) {
err error
)
if opts.ManifestReader != nil {
m, err = NewManifestFromReader(opts.ManifestReader)
if opts.Manifest != nil {
m, err = NewManifestFromReader(opts.Manifest)
if err != nil {
return nil, err
}
+3 -3
View File
@@ -206,9 +206,9 @@ func TestNewCheckerReadsManifestFromReader(t *testing.T) {
createFilesOnDisk(t, fs, files)
chk, err := NewChecker(&CheckerOptions{
ManifestReader: bytes.NewReader(manifestData),
BasePath: testDataDir,
Fs: fs,
Manifest: bytes.NewReader(manifestData),
BasePath: testDataDir,
Fs: fs,
})
require.NoError(t, err)