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

No files matched your search

+1 -1
View File
@@ -299,7 +299,7 @@ func (mfa *CLIApp) checkManifestOperation(
return downloadErr return downloadErr
} }
opts.ManifestReader = bytes.NewReader(manifestData) opts.Manifest = bytes.NewReader(manifestData)
} else { } else {
opts.ManifestPath = manifestPath opts.ManifestPath = manifestPath
} }
+14 -3
View File
@@ -19,6 +19,7 @@ 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"
@@ -553,12 +554,22 @@ 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. The Checker it builds is used only for the // exactly as check does. verifyRequiredSigner takes a Checker, which loads
// manifest's signature, so its base path is never read. // its manifest from a file, so the manifest is handed to it as a file in
// 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{
ManifestReader: bytes.NewReader(manifestData), ManifestPath: manifestPath,
BasePath: "/", BasePath: "/",
Fs: memFs,
}) })
if err != nil { if err != nil {
return fmt.Errorf("load manifest: %w", err) 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 // 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 and fail with an error // to 64 KiB. Each must stop reading at that limit, fail with an error
// naming it. // naming it and leave no temp file.
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()
@@ -519,6 +520,10 @@ 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.ManifestReader. // CheckerOptions.Manifest.
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 ManifestReader is given. // unless Manifest is given.
ManifestPath string ManifestPath string
// ManifestReader, if not nil, is read for the manifest to check against // Manifest, if not nil, is read for the manifest to check against in
// in place of ManifestPath, such as a manifest downloaded into memory. // 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.
ManifestReader io.Reader Manifest 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 reader is given. // nor a manifest is given.
func NewChecker(opts *CheckerOptions) (*Checker, error) { 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 return nil, errManifestPathEmpty
} }
@@ -128,8 +128,8 @@ func NewChecker(opts *CheckerOptions) (*Checker, error) {
err error err error
) )
if opts.ManifestReader != nil { if opts.Manifest != nil {
m, err = NewManifestFromReader(opts.ManifestReader) m, err = NewManifestFromReader(opts.Manifest)
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{
ManifestReader: bytes.NewReader(manifestData), Manifest: bytes.NewReader(manifestData),
BasePath: testDataDir, BasePath: testDataDir,
Fs: fs, Fs: fs,
}) })