fix: snap cataloger incorrectly identifies snap container as deb package (#4500)

Signed-off-by: Alan Pope <alan@popey.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
Alan Pope 2026-01-30 15:19:26 +00:00 committed by GitHub
parent 8d836fb8b0
commit 0bca34f986
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 3 additions and 136 deletions

View File

@ -118,29 +118,3 @@ catalogers:
default: false default: false
- name: package_manager.package_integrity_hash - name: package_manager.package_integrity_hash
default: false default: false
- function: parseSnapYaml
detector: # AUTO-GENERATED
method: glob # AUTO-GENERATED
criteria: # AUTO-GENERATED
- '**/meta/snap.yaml'
metadata_types: # AUTO-GENERATED
- pkg.SnapEntry
package_types: # AUTO-GENERATED
- deb
json_schema_types: # AUTO-GENERATED
- SnapEntry
capabilities: # MANUAL - preserved across regeneration
- name: license
default: false
- name: dependency.depth
default: []
- name: dependency.edges
default: ""
- name: dependency.kinds
default: []
- name: package_manager.files.listing
default: false
- name: package_manager.files.digests
default: false
- name: package_manager.package_integrity_hash
default: false

View File

@ -15,8 +15,6 @@ const catalogerName = "snap-cataloger"
// NewCataloger returns a new Snap cataloger object that can parse snap package metadata. // NewCataloger returns a new Snap cataloger object that can parse snap package metadata.
func NewCataloger() pkg.Cataloger { func NewCataloger() pkg.Cataloger {
return generic.NewCataloger(catalogerName). return generic.NewCataloger(catalogerName).
// Look for snap.yaml to identify snap type and base snap info
WithParserByGlobs(parseSnapYaml, "**/meta/snap.yaml").
// Base snaps: dpkg.yaml files containing package manifests // Base snaps: dpkg.yaml files containing package manifests
WithParserByGlobs(parseBaseDpkgYaml, "**/usr/share/snappy/dpkg.yaml"). WithParserByGlobs(parseBaseDpkgYaml, "**/usr/share/snappy/dpkg.yaml").
// Kernel snaps: changelog files for kernel version info // Kernel snaps: changelog files for kernel version info

View File

@ -19,17 +19,13 @@ func TestCataloger_Globs(t *testing.T) {
name: "system snap with manifest.yaml", name: "system snap with manifest.yaml",
fixture: "test-fixtures/glob-paths/system", fixture: "test-fixtures/glob-paths/system",
}, },
{
name: "snap with meta/snap.yaml",
fixture: "test-fixtures/glob-paths/meta",
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
pkgtest.NewCatalogTester(). pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture). FromDirectory(t, test.fixture).
IgnoreUnfulfilledPathResponses("**/meta/snap.yaml", "**/usr/share/snappy/dpkg.yaml", "**/doc/linux-modules-*/changelog.Debian.gz", "**/snap/manifest.yaml", "**/snap/snapcraft.yaml"). IgnoreUnfulfilledPathResponses("**/usr/share/snappy/dpkg.yaml", "**/doc/linux-modules-*/changelog.Debian.gz", "**/snap/manifest.yaml", "**/snap/snapcraft.yaml").
TestCataloger(t, NewCataloger()) TestCataloger(t, NewCataloger())
}) })
} }

View File

@ -8,30 +8,6 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
) )
func TestParseSnapYaml(t *testing.T) {
fixture := "test-fixtures/snap.yaml"
locations := file.NewLocationSet(file.NewLocation(fixture))
expected := []pkg.Package{
{
Name: "test-snap",
Version: "1.0.0",
Type: pkg.DebPkg,
PURL: "pkg:generic/snap/test-snap@1.0.0?arch=amd64&base=core20&type=app",
Locations: locations,
Metadata: pkg.SnapEntry{
SnapType: pkg.SnapTypeApp,
Base: "core20",
SnapName: "test-snap",
SnapVersion: "1.0.0",
Architecture: "amd64",
},
},
}
pkgtest.TestFileParser(t, fixture, parseSnapYaml, expected, nil)
}
func TestParseSystemManifest(t *testing.T) { func TestParseSystemManifest(t *testing.T) {
fixture := "test-fixtures/manifest.yaml" fixture := "test-fixtures/manifest.yaml"
locations := file.NewLocationSet(file.NewLocation(fixture)) locations := file.NewLocationSet(file.NewLocation(fixture))

View File

@ -4,6 +4,7 @@ import (
"compress/gzip" "compress/gzip"
"context" "context"
"fmt" "fmt"
"io"
"regexp" "regexp"
"strings" "strings"
@ -58,7 +59,7 @@ func readChangelogLines(reader file.LocationReadCloser) ([]string, error) {
} }
defer gzReader.Close() defer gzReader.Close()
content, err := readAll(gzReader) content, err := io.ReadAll(gzReader)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read changelog content: %w", err) return nil, fmt.Errorf("failed to read changelog content: %w", err)
} }

View File

@ -1,68 +0,0 @@
package snap
import (
"context"
"fmt"
"io"
"gopkg.in/yaml.v3"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
// snapYaml represents the structure of meta/snap.yaml files
type snapYaml struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
Base string `yaml:"base"`
Type string `yaml:"type"`
Architecture string `yaml:"architecture"`
Summary string `yaml:"summary"`
Description string `yaml:"description"`
}
// parseSnapYaml parses meta/snap.yaml files to identify snap type and basic metadata
func parseSnapYaml(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var snap snapYaml
decoder := yaml.NewDecoder(reader)
if err := decoder.Decode(&snap); err != nil {
return nil, nil, fmt.Errorf("failed to parse snap.yaml: %w", err)
}
if snap.Name == "" {
return nil, nil, fmt.Errorf("snap.yaml missing required 'name' field")
}
// Determine snap type - default to "app" if not specified
snapType := snap.Type
if snapType == "" {
snapType = pkg.SnapTypeApp
}
metadata := pkg.SnapEntry{
SnapType: snapType,
Base: snap.Base,
SnapName: snap.Name,
SnapVersion: snap.Version,
Architecture: snap.Architecture,
}
// Create a package representing the snap itself
snapPkg := newPackage(
snap.Name,
snap.Version,
metadata,
reader.Location,
)
return []pkg.Package{snapPkg}, nil, nil
}
// readAll reads all content from a reader and returns it as bytes
func readAll(r io.Reader) ([]byte, error) {
return io.ReadAll(r)
}

View File

@ -1,3 +0,0 @@
name: test-snap
version: 1.0
type: app

View File

@ -1,7 +0,0 @@
name: test-snap
version: 1.0.0
summary: A test snap
description: This is a test snap for testing purposes
base: core20
type: app
architecture: amd64