chore: archive memory and disk limits of 0 mean 0

Signed-off-by: Keith Zantow <keith.zantow@anchore.com>
This commit is contained in:
Keith Zantow
2026-09-30 15:57:48 -04:00
parent d95abbf6b5
commit fda2fe4fcc
7 changed files with 37 additions and 30 deletions
+3 -2
View File
@@ -28,8 +28,9 @@ note: this only applies to the java package cataloger, and is ignored when neste
these packages are removed if an overlap with a non-synthetic package is found`)
descriptions.Add(&o.NestedArchiveMaxDepth, `maximum depth to recursively catalog nested archives (0 = disabled, -1 = as deep as 16 levels)
note: when enabled this replaces the java cataloger's own unbounded jar-in-jar recursion, so archives deeper than this are reported as unknowns rather than cataloged`)
descriptions.Add(&o.NestedArchiveMaxMemoryBytes, `maximum bytes of nested archive content held in memory at once (0 = default, -1 = unbounded)`)
descriptions.Add(&o.NestedArchiveMaxDiskBytes, `maximum bytes of nested archive content on disk at once (0 = default, -1 = unbounded)`)
descriptions.Add(&o.NestedArchiveMaxMemoryBytes, `maximum bytes of nested archive content held in memory at once (0 = none, -1 = unbounded)`)
descriptions.Add(&o.NestedArchiveMaxDiskBytes, `maximum bytes of nested archive content on disk at once (0 = none, -1 = unbounded)
note: this and nested-archive-max-memory-bytes cannot both be 0 when nested-archive-max-depth is not 0`)
}
func defaultPackageConfig() packageConfig {
+1 -1
View File
@@ -433,7 +433,7 @@ func Test_mixedFamilyNesting_diskLimitTruncatesTheLevelItCannotAdmit(t *testing.
func Test_mixedFamilyNesting_unboundedLimitsEnforceNothing(t *testing.T) {
// a caller can opt out of one bound without the other, and opting out of both must still find the
// deepest leaf. Both opt out with a negative value, since zero means the default.
// deepest leaf. Opting out is a negative value; zero forbids the resource.
for _, row := range nestingRows() {
t.Run(row.name, func(t *testing.T) {
scanDir := t.TempDir()
+1 -6
View File
@@ -41,13 +41,8 @@ func NewArchiveCatalogerTask(cfg cataloging.ArchiveSearchConfig, subPipeline []T
return newArchiveCatalogerTask(cfg.MaxDepth, archiveLimits(cfg), subPipeline, exclusions)
}
// archiveLimits maps the configured limits onto the limiter's. Zero means the default, so the limiter's
// own zero (which forbids the resource) is not reachable from configuration.
func archiveLimits(cfg cataloging.ArchiveSearchConfig) archive.Limits {
return archive.Limits{
MaxMemoryBytes: cmp.Or(cfg.MaxMemoryBytes, int64(cataloging.DefaultArchiveMaxMemoryBytes)),
MaxDiskBytes: cmp.Or(cfg.MaxDiskBytes, int64(cataloging.DefaultArchiveMaxDiskBytes)),
}
return archive.Limits{MaxMemoryBytes: cfg.MaxMemoryBytes, MaxDiskBytes: cfg.MaxDiskBytes}
}
func newArchiveCatalogerTask(maxDepth int, limits archive.Limits, subPipeline []Task, exclusions []string) Task {
-17
View File
@@ -172,23 +172,6 @@ func Test_archiveCataloger_traversalThreading(t *testing.T) {
}, seen)
}
func Test_archiveCataloger_zeroLimitsMeanDefault(t *testing.T) {
// a config literal naming only the depth must catalog archives, not forbid both memory and disk
rootDir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "outer.zip"), makeZip(t, map[string][]byte{"a.txt": []byte("a")}), 0o600))
var seen []string
fileCounts := map[string]int{}
tsk := newTestTask(t, cataloging.ArchiveSearchConfig{MaxDepth: 1}, capturingTask(t, &seen, &fileCounts))
s := newTestSBOM()
require.NoError(t, tsk.Execute(context.Background(), dirTestResolver{dir: rootDir}, sbomsync.NewBuilder(s)))
assert.Equal(t, []string{"/outer.zip"}, seen)
assert.Equal(t, 1, fileCounts["/outer.zip"])
assert.Empty(t, s.Artifacts.Unknowns)
}
func Test_archiveCataloger_truncationStillCatalogs(t *testing.T) {
// reaching a limit is a truncation, not a failure: the sub-pipeline still runs over what was stored
big := bytes.Repeat([]byte("x"), 4096)
+3 -2
View File
@@ -27,11 +27,12 @@ type ArchiveSearchConfig struct {
MaxDepth int `yaml:"max-depth" json:"max-depth" mapstructure:"max-depth"`
// MaxMemoryBytes bounds the archive content held in memory at once; content that does not fit is
// written to disk. Zero means DefaultArchiveMaxMemoryBytes, negative is unbounded.
// written to disk. Zero holds nothing in memory, negative is unbounded.
MaxMemoryBytes int64 `yaml:"max-memory-bytes" json:"max-memory-bytes" mapstructure:"max-memory-bytes"`
// MaxDiskBytes bounds the archive content written to disk at once; an archive whose content does
// not fit is skipped or truncated. Zero means DefaultArchiveMaxDiskBytes, negative is unbounded.
// not fit is skipped or truncated. Zero writes nothing to disk, negative is unbounded. MaxMemoryBytes
// and MaxDiskBytes cannot both be zero while MaxDepth is not.
MaxDiskBytes int64 `yaml:"max-disk-bytes" json:"max-disk-bytes" mapstructure:"max-disk-bytes"`
}
+4 -2
View File
@@ -148,8 +148,7 @@ func (c *CreateSBOMConfig) WithFilesConfig(cfg filecataloging.Config) *CreateSBO
}
// WithArchiveConfig sets how archives are cataloged: how deep to recurse into nested archives (MaxDepth
// 0, the default, does not recurse) and how much extracted content the scan may hold at once (a zero
// limit means the default).
// 0, the default, does not recurse) and how much extracted content the scan may hold at once.
func (c *CreateSBOMConfig) WithArchiveConfig(cfg cataloging.ArchiveSearchConfig) *CreateSBOMConfig {
c.Archive = cfg
return c
@@ -515,6 +514,9 @@ func (c *CreateSBOMConfig) validate() error {
if j := c.Packages.JavaArchive.ArchiveSearchConfig; j.MaxDepth != 0 || j.MaxMemoryBytes != 0 || j.MaxDiskBytes != 0 {
return fmt.Errorf("invalid configuration: nested archive depth and limits are not java cataloger settings, set them with CreateSBOMConfig.Archive")
}
if a := c.Archive; a.MaxDepth != 0 && a.MaxMemoryBytes == 0 && a.MaxDiskBytes == 0 {
return fmt.Errorf("invalid configuration: nested archive cataloging is enabled but its memory and disk limits are both 0, leaving nowhere to extract archives")
}
return nil
}
+25
View File
@@ -555,6 +555,31 @@ func TestCreateSBOMConfig_validate(t *testing.T) {
),
wantErr: assert.Error,
},
{
name: "nested archives enabled with no memory or disk",
cfg: DefaultCreateSBOMConfig().WithArchiveConfig(
cataloging.DefaultArchiveSearchConfig().WithMaxDepth(1).WithMaxMemoryBytes(0).WithMaxDiskBytes(0),
),
wantErr: assert.Error,
},
{
name: "nested archives enabled with memory only",
cfg: DefaultCreateSBOMConfig().WithArchiveConfig(
cataloging.DefaultArchiveSearchConfig().WithMaxDepth(1).WithMaxDiskBytes(0),
),
},
{
name: "nested archives enabled with disk only",
cfg: DefaultCreateSBOMConfig().WithArchiveConfig(
cataloging.DefaultArchiveSearchConfig().WithMaxDepth(1).WithMaxMemoryBytes(0),
),
},
{
name: "nested archives disabled with no memory or disk",
cfg: DefaultCreateSBOMConfig().WithArchiveConfig(
cataloging.DefaultArchiveSearchConfig().WithMaxMemoryBytes(0).WithMaxDiskBytes(0),
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {