1 Commits
Author SHA1 Message Date
sneak 59b3cbd1dd 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.ManifestReader field, in place of ManifestPath, because it
otherwise reads the manifest from the filesystem holding the files it
checks. fetch now hands its downloaded manifest to the Checker the same
way, not through an in-memory filesystem.

Model: opus-5-5
2026-10-15 18:48:23 +00:00
5 changed files with 21 additions and 37 deletions
+1 -1
View File
@@ -299,7 +299,7 @@ func (mfa *CLIApp) checkManifestOperation(
return downloadErr
}
opts.Manifest = bytes.NewReader(manifestData)
opts.ManifestReader = bytes.NewReader(manifestData)
} else {
opts.ManifestPath = manifestPath
}
+4 -15
View File
@@ -19,7 +19,6 @@ 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"
@@ -554,22 +553,12 @@ func checkNoNameClash(files []*mfer.MFFilePath) error {
}
// verifyFetchedSigner enforces --require-signature on the fetched manifest
// 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.
// exactly as check does. The Checker it builds is used only for the
// manifest's signature, so its base path is never read.
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{
ManifestPath: manifestPath,
BasePath: "/",
Fs: memFs,
ManifestReader: bytes.NewReader(manifestData),
BasePath: "/",
})
if err != nil {
return fmt.Errorf("load manifest: %w", err)
+4 -9
View File
@@ -489,18 +489,17 @@ 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, fail with an error
// naming it and leave no temp file.
// to 64 KiB. Each must stop reading at that limit and fail with an error
// naming it.
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()
@@ -520,10 +519,6 @@ 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.Manifest.
// CheckerOptions.ManifestReader.
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 Manifest is given.
// unless ManifestReader is given.
ManifestPath string
// Manifest, if not nil, is read for the manifest to check against in
// place of ManifestPath, such as a manifest downloaded into memory.
// ManifestReader, 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.
Manifest io.Reader
ManifestReader 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 is given.
// nor a manifest reader is given.
func NewChecker(opts *CheckerOptions) (*Checker, error) {
if opts == nil || (opts.ManifestPath == "" && opts.Manifest == nil) {
if opts == nil || (opts.ManifestPath == "" && opts.ManifestReader == nil) {
return nil, errManifestPathEmpty
}
@@ -128,8 +128,8 @@ func NewChecker(opts *CheckerOptions) (*Checker, error) {
err error
)
if opts.Manifest != nil {
m, err = NewManifestFromReader(opts.Manifest)
if opts.ManifestReader != nil {
m, err = NewManifestFromReader(opts.ManifestReader)
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{
Manifest: bytes.NewReader(manifestData),
BasePath: testDataDir,
Fs: fs,
ManifestReader: bytes.NewReader(manifestData),
BasePath: testDataDir,
Fs: fs,
})
require.NoError(t, err)