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 18 additions and 34 deletions

No files matched your search

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