fix tests and linting

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2025-10-29 11:55:02 -04:00
parent d6512456b3
commit 16fb680b15
33 changed files with 513 additions and 525 deletions

View File

@ -10,128 +10,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestLinkCatalogersToConfigs(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
repoRoot, err := RepoRoot()
require.NoError(t, err)
linkages, err := LinkCatalogersToConfigs(repoRoot)
require.NoError(t, err)
// verify we discovered multiple catalogers
require.NotEmpty(t, linkages, "should discover at least one cataloger linkage")
// test cases for known catalogers with configs
// NOTE: Some catalogers may not be detected if their Name() method is in a different file
// than the constructor function. This is a known limitation.
tests := []struct {
catalogerName string
wantConfig string
optional bool // set to true if detection may not work due to cross-file Name() methods
}{
{
catalogerName: "go-module-binary-cataloger",
wantConfig: "golang.CatalogerConfig",
},
{
catalogerName: "go-module-file-cataloger",
wantConfig: "golang.CatalogerConfig",
},
{
catalogerName: "python-package-cataloger",
wantConfig: "python.CatalogerConfig",
},
{
catalogerName: "java-archive-cataloger",
wantConfig: "java.ArchiveCatalogerConfig",
},
{
catalogerName: "java-pom-cataloger",
wantConfig: "java.ArchiveCatalogerConfig",
optional: true, // Name() method in different file
},
{
catalogerName: "dotnet-deps-binary-cataloger",
wantConfig: "dotnet.CatalogerConfig",
optional: true, // Name() method in different file
},
{
catalogerName: "javascript-lock-cataloger",
wantConfig: "javascript.CatalogerConfig",
},
{
catalogerName: "linux-kernel-cataloger",
wantConfig: "kernel.LinuxKernelCatalogerConfig",
},
{
catalogerName: "nix-cataloger",
wantConfig: "nix.Config",
optional: true, // Name() method in different file
},
}
for _, tt := range tests {
t.Run(tt.catalogerName, func(t *testing.T) {
config, ok := linkages[tt.catalogerName]
if tt.optional && !ok {
t.Skipf("cataloger %s not detected (expected due to cross-file Name() method)", tt.catalogerName)
return
}
require.True(t, ok, "should find linkage for cataloger: %s", tt.catalogerName)
require.Equal(t, tt.wantConfig, config, "config type should match for cataloger: %s", tt.catalogerName)
})
}
// test catalogers without configs (should have empty string)
catalogersWithoutConfig := []string{
"python-installed-package-cataloger",
"java-gradle-lockfile-cataloger",
"java-jvm-cataloger",
"dotnet-packages-lock-cataloger",
"javascript-package-cataloger",
}
for _, catalogerName := range catalogersWithoutConfig {
t.Run(catalogerName+"_no_config", func(t *testing.T) {
config, ok := linkages[catalogerName]
if ok {
require.Empty(t, config, "cataloger %s should have empty config", catalogerName)
}
})
}
// print summary for manual inspection
t.Logf("Discovered %d cataloger-to-config linkages:", len(linkages))
// separate into catalogers with and without configs
withConfig := make(map[string]string)
withoutConfig := make([]string, 0)
for name, config := range linkages {
if config != "" {
withConfig[name] = config
} else {
withoutConfig = append(withoutConfig, name)
}
}
t.Logf("Catalogers with configs (%d):", len(withConfig))
for name, config := range withConfig {
t.Logf(" %s -> %s", name, config)
}
t.Logf("Catalogers without configs (%d):", len(withoutConfig))
for _, name := range withoutConfig {
t.Logf(" %s", name)
}
// ensure we found at least some catalogers with configs
require.GreaterOrEqual(t, len(withConfig), 6, "should find at least 6 catalogers with configs")
}
func TestLinkCatalogersToConfigsFromPath(t *testing.T) { func TestLinkCatalogersToConfigsFromPath(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -161,8 +39,8 @@ func TestLinkCatalogersToConfigsFromPath(t *testing.T) {
}, },
}, },
{ {
name: "custom cataloger with Name() in different file - not detected", name: "custom cataloger with Name() in different file - not detected",
fixturePath: "custom-cataloger-different-file", fixturePath: "custom-cataloger-different-file",
expectedLinkages: map[string]string{ expectedLinkages: map[string]string{
// empty - current limitation, cannot detect cross-file Names // empty - current limitation, cannot detect cross-file Names
}, },
@ -204,7 +82,7 @@ func TestLinkCatalogersToConfigsFromPath(t *testing.T) {
name: "selector expression config", name: "selector expression config",
fixturePath: "selector-expression-config", fixturePath: "selector-expression-config",
expectedLinkages: map[string]string{ expectedLinkages: map[string]string{
"rust-cataloger": "cargo.CatalogerConfig", "rust-cataloger": "rust.CatalogerConfig",
}, },
}, },
} }
@ -215,8 +93,9 @@ func TestLinkCatalogersToConfigsFromPath(t *testing.T) {
tt.wantErr = require.NoError tt.wantErr = require.NoError
} }
fixtureDir := filepath.Join("test-fixtures", "config-linking", tt.fixturePath) fixtureDir := filepath.Join("testdata", "cataloger", tt.fixturePath)
linkages, err := LinkCatalogersToConfigsFromPath(fixtureDir, fixtureDir) catalogerRoot := filepath.Join(fixtureDir, "cataloger")
linkages, err := LinkCatalogersToConfigsFromPath(catalogerRoot, fixtureDir)
tt.wantErr(t, err) tt.wantErr(t, err)
if err != nil { if err != nil {
@ -229,51 +108,64 @@ func TestLinkCatalogersToConfigsFromPath(t *testing.T) {
} }
func TestExtractConfigTypeName(t *testing.T) { func TestExtractConfigTypeName(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
tests := []struct { tests := []struct {
name string name string
fixturePath string
catalogerName string catalogerName string
expectedConfig string expectedConfig string
expectedNoConfig bool expectedNoConfig bool
}{ }{
{ {
name: "golang config", name: "golang config",
catalogerName: "go-module-binary-cataloger", fixturePath: "simple-generic-cataloger",
catalogerName: "go-module-cataloger",
expectedConfig: "golang.CatalogerConfig", expectedConfig: "golang.CatalogerConfig",
}, },
{ {
name: "python config", name: "python config with constant",
fixturePath: "cataloger-with-constant",
catalogerName: "python-package-cataloger", catalogerName: "python-package-cataloger",
expectedConfig: "python.CatalogerConfig", expectedConfig: "python.CatalogerConfig",
}, },
{ {
name: "java archive config", name: "java archive config same file",
catalogerName: "java-archive-cataloger", fixturePath: "custom-cataloger-same-file",
catalogerName: "java-pom-cataloger",
expectedConfig: "java.ArchiveCatalogerConfig", expectedConfig: "java.ArchiveCatalogerConfig",
}, },
{ {
name: "kernel config", name: "kernel config imported type",
fixturePath: "imported-config-type",
catalogerName: "linux-kernel-cataloger", catalogerName: "linux-kernel-cataloger",
expectedConfig: "kernel.LinuxKernelCatalogerConfig", expectedConfig: "kernel.LinuxKernelCatalogerConfig",
}, },
{ {
name: "python installed - no config", name: "javascript - no config",
catalogerName: "python-installed-package-cataloger", fixturePath: "no-config-cataloger",
catalogerName: "javascript-cataloger",
expectedNoConfig: true, expectedNoConfig: true,
}, },
{
name: "ruby with mixed naming",
fixturePath: "mixed-naming-patterns",
catalogerName: "ruby-cataloger",
expectedConfig: "ruby.Config",
},
{
name: "rust with selector expression",
fixturePath: "selector-expression-config",
catalogerName: "rust-cataloger",
expectedConfig: "rust.CatalogerConfig",
},
} }
repoRoot, err := RepoRoot()
require.NoError(t, err)
linkages, err := LinkCatalogersToConfigs(repoRoot)
require.NoError(t, err)
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
fixtureDir := filepath.Join("testdata", "cataloger", tt.fixturePath)
catalogerRoot := filepath.Join(fixtureDir, "cataloger")
linkages, err := LinkCatalogersToConfigsFromPath(catalogerRoot, fixtureDir)
require.NoError(t, err)
config, ok := linkages[tt.catalogerName] config, ok := linkages[tt.catalogerName]
if tt.expectedNoConfig { if tt.expectedNoConfig {

View File

@ -58,6 +58,8 @@ var observationExceptions = map[string]*strset.Set{
"linux-kernel-cataloger": strset.New("relationships"), "linux-kernel-cataloger": strset.New("relationships"),
} }
// TestCatalogersInSync ensures that all catalogers from the syft binary are documented in packages.yaml
// and vice versa, and that all capability fields are properly filled without TODOs or null values.
func TestCatalogersInSync(t *testing.T) { func TestCatalogersInSync(t *testing.T) {
// get canonical list from syft binary // get canonical list from syft binary
catalogersInBinary := getCatalogerNamesFromBinary(t) catalogersInBinary := getCatalogerNamesFromBinary(t)
@ -141,6 +143,8 @@ func validateCapabilitiesFilled(t *testing.T, catalogers []capabilities.Cataloge
} }
} }
// TestPackageTypeCoverage ensures that every package type defined in pkg.AllPkgs is represented in at least
// one cataloger's capabilities, preventing orphaned package types that are defined but never documented.
func TestPackageTypeCoverage(t *testing.T) { func TestPackageTypeCoverage(t *testing.T) {
// load catalogers from embedded YAML // load catalogers from embedded YAML
catalogerEntries, err := capabilities.Packages() catalogerEntries, err := capabilities.Packages()
@ -184,6 +188,8 @@ func TestPackageTypeCoverage(t *testing.T) {
missingTypesWithoutExceptions) missingTypesWithoutExceptions)
} }
// TestMetadataTypeCoverage ensures that every metadata type defined in packagemetadata.AllTypes() is represented
// in at least one cataloger's capabilities, preventing orphaned metadata types that are defined but never produced.
func TestMetadataTypeCoverage(t *testing.T) { func TestMetadataTypeCoverage(t *testing.T) {
// load catalogers from embedded YAML // load catalogers from embedded YAML
catalogerEntries, err := capabilities.Packages() catalogerEntries, err := capabilities.Packages()
@ -231,6 +237,9 @@ func TestMetadataTypeCoverage(t *testing.T) {
missingTypesWithoutExceptions) missingTypesWithoutExceptions)
} }
// TestCatalogerStructure validates that catalogers follow structural conventions: generic catalogers must have
// parsers and parser-level capabilities, custom catalogers must have detectors and cataloger-level capabilities,
// and all catalogers must have an ecosystem set.
func TestCatalogerStructure(t *testing.T) { func TestCatalogerStructure(t *testing.T) {
// load catalogers from embedded YAML // load catalogers from embedded YAML
catalogerEntries, err := capabilities.Packages() catalogerEntries, err := capabilities.Packages()
@ -269,6 +278,8 @@ func TestCatalogerStructure(t *testing.T) {
} }
} }
// TestCatalogerDataQuality checks for data integrity issues in packages.yaml, including duplicate cataloger
// names, duplicate parser functions within catalogers, and validates that detector definitions are well-formed.
func TestCatalogerDataQuality(t *testing.T) { func TestCatalogerDataQuality(t *testing.T) {
// load catalogers from embedded YAML // load catalogers from embedded YAML
catalogerEntries, err := capabilities.Packages() catalogerEntries, err := capabilities.Packages()
@ -345,7 +356,8 @@ func TestCatalogerDataQuality(t *testing.T) {
}) })
} }
// TestCapabilitiesAreUpToDate verifies that regeneration runs successfully // TestCapabilitiesAreUpToDate verifies that packages.yaml is up to date by running regeneration and checking
// for uncommitted changes. This test only runs in CI to catch cases where code changed but capabilities weren't regenerated.
func TestCapabilitiesAreUpToDate(t *testing.T) { func TestCapabilitiesAreUpToDate(t *testing.T) {
if os.Getenv("CI") == "" { if os.Getenv("CI") == "" {
t.Skip("skipping regeneration test in local environment") t.Skip("skipping regeneration test in local environment")
@ -367,8 +379,9 @@ func TestCapabilitiesAreUpToDate(t *testing.T) {
require.NoError(t, err, "packages.yaml has uncommitted changes after regeneration. Run 'go generate ./internal/capabilities' locally and commit the changes.") require.NoError(t, err, "packages.yaml has uncommitted changes after regeneration. Run 'go generate ./internal/capabilities' locally and commit the changes.")
} }
// TestCatalogersHaveTestObservations verifies that all catalogers have test observations, // TestCatalogersHaveTestObservations ensures that all custom catalogers (and optionally parsers) have
// ensuring they are using the pkgtest helpers // test observations recorded in test-fixtures/test-observations.json, which proves they are using the
// pkgtest.CatalogTester helpers and have test coverage.
func TestCatalogersHaveTestObservations(t *testing.T) { func TestCatalogersHaveTestObservations(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -486,6 +499,9 @@ func extractPackageName(catalogerName string) string {
return catalogerName return catalogerName
} }
// TestConfigCompleteness validates the integrity of config references in packages.yaml, ensuring that all
// configs in the configs section are referenced by at least one cataloger, all cataloger config references exist,
// and all app-key references in config fields exist in the application section.
func TestConfigCompleteness(t *testing.T) { func TestConfigCompleteness(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -543,6 +559,8 @@ func TestConfigCompleteness(t *testing.T) {
} }
} }
// TestAppConfigFieldsHaveDescriptions ensures that all application config fields discovered from the
// options package have descriptions, which are required for user-facing documentation.
func TestAppConfigFieldsHaveDescriptions(t *testing.T) { func TestAppConfigFieldsHaveDescriptions(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -561,6 +579,8 @@ func TestAppConfigFieldsHaveDescriptions(t *testing.T) {
require.Empty(t, missingDescriptions, "the following configs are missing descriptions: %v", missingDescriptions) require.Empty(t, missingDescriptions, "the following configs are missing descriptions: %v", missingDescriptions)
} }
// TestAppConfigKeyFormat validates that all application config keys follow the expected naming convention
// of "ecosystem.field-name" using kebab-case (lowercase with hyphens, no underscores or spaces).
func TestAppConfigKeyFormat(t *testing.T) { func TestAppConfigKeyFormat(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -579,8 +599,9 @@ func TestAppConfigKeyFormat(t *testing.T) {
} }
} }
// TestCapabilityConfigFieldReferences validates that config field names referenced in CapabilitiesV2 // TestCapabilityConfigFieldReferences validates that config field names referenced in capability conditions
// conditions actually exist in the cataloger's config struct // actually exist in the cataloger's config struct, preventing typos and ensuring capability conditions can
// be properly evaluated at runtime.
func TestCapabilityConfigFieldReferences(t *testing.T) { func TestCapabilityConfigFieldReferences(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -697,7 +718,9 @@ func TestCapabilityConfigFieldReferences(t *testing.T) {
} }
} }
// TestCapabilityFieldNaming validates that capability field names follow known patterns // TestCapabilityFieldNaming validates that all capability field names follow known patterns
// (e.g., "license", "dependency.depth", "package_manager.files.listing"), catching typos and ensuring
// consistency across catalogers.
func TestCapabilityFieldNaming(t *testing.T) { func TestCapabilityFieldNaming(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -754,7 +777,9 @@ func TestCapabilityFieldNaming(t *testing.T) {
} }
} }
// TestCapabilityValueTypes validates that capability field values match expected types // TestCapabilityValueTypes validates that capability field values match their expected types based on the
// field name (e.g., boolean fields like "license" must have bool values, array fields like "dependency.depth"
// must have []string values), preventing type mismatches that would cause runtime errors.
func TestCapabilityValueTypes(t *testing.T) { func TestCapabilityValueTypes(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -870,8 +895,9 @@ func validateCapabilityValueType(fieldPath string, value interface{}) error {
return nil return nil
} }
// TestMetadataTypesHaveJSONSchemaTypes validates that metadata_types and json_schema_types are synchronized // TestMetadataTypesHaveJSONSchemaTypes validates that metadata_types and json_schema_types arrays are synchronized
// in packages.yaml - every metadata type should have a corresponding json_schema_type with correct conversion // in packages.yaml, ensuring every metadata type (e.g., "pkg.AlpmDBEntry") has a corresponding json_schema_type
// (e.g., "AlpmDbEntry") with correct conversion, which is required for JSON schema generation.
func TestMetadataTypesHaveJSONSchemaTypes(t *testing.T) { func TestMetadataTypesHaveJSONSchemaTypes(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -1230,7 +1256,8 @@ func validateFieldPath(repoRoot, structName string, fieldPath []string) error {
} }
// TestCapabilityEvidenceFieldReferences validates that evidence field references in capabilities // TestCapabilityEvidenceFieldReferences validates that evidence field references in capabilities
// actually exist on their corresponding metadata structs // (e.g., "AlpmDBEntry.Files[].Digests") actually exist on their corresponding metadata structs by using
// AST parsing to verify the field paths, preventing broken references when structs are refactored.
func TestCapabilityEvidenceFieldReferences(t *testing.T) { func TestCapabilityEvidenceFieldReferences(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)
@ -1305,8 +1332,9 @@ func TestCapabilityEvidenceFieldReferences(t *testing.T) {
} }
} }
// TestDetectorConfigFieldReferences validates that config field names referenced in detector // TestDetectorConfigFieldReferences validates that config field names referenced in detector conditions
// conditions actually exist in the cataloger's config struct // actually exist in the cataloger's config struct, ensuring that conditional detectors can properly
// evaluate their activation conditions based on configuration.
func TestDetectorConfigFieldReferences(t *testing.T) { func TestDetectorConfigFieldReferences(t *testing.T) {
repoRoot, err := RepoRoot() repoRoot, err := RepoRoot()
require.NoError(t, err) require.NoError(t, err)

View File

@ -156,25 +156,25 @@ func extractConfigStructTypes(filePath string) ([]string, error) {
// 1. Finding files with cataloger imports in options directory // 1. Finding files with cataloger imports in options directory
// 2. Extracting ecosystem config fields from Catalog struct // 2. Extracting ecosystem config fields from Catalog struct
// 3. Matching file structs against Catalog fields // 3. Matching file structs against Catalog fields
// Returns a map of file path to top-level YAML key // Returns a map of file path to top-level YAML key and a reverse lookup map of YAML key to struct name
func discoverCatalogerConfigs(repoRoot string) (map[string]string, error) { func discoverCatalogerConfigs(repoRoot string) (map[string]string, map[string]string, error) {
optionsDir := filepath.Join(repoRoot, "cmd", "syft", "internal", "options") optionsDir := filepath.Join(repoRoot, "cmd", "syft", "internal", "options")
catalogFilePath := filepath.Join(optionsDir, "catalog.go") catalogFilePath := filepath.Join(optionsDir, "catalog.go")
// get ecosystem config fields from Catalog struct // get ecosystem config fields from Catalog struct
ecosystemConfigs, err := extractEcosystemConfigFieldsFromCatalog(catalogFilePath) ecosystemConfigs, err := extractEcosystemConfigFieldsFromCatalog(catalogFilePath)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if len(ecosystemConfigs) == 0 { if len(ecosystemConfigs) == 0 {
return nil, fmt.Errorf("no ecosystem config fields found in Catalog struct") return nil, nil, fmt.Errorf("no ecosystem config fields found in Catalog struct")
} }
// find files with cataloger imports // find files with cataloger imports
candidateFiles, err := findFilesWithCatalogerImports(optionsDir) candidateFiles, err := findFilesWithCatalogerImports(optionsDir)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// match candidate files against Catalog ecosystem fields // match candidate files against Catalog ecosystem fields
@ -184,7 +184,7 @@ func discoverCatalogerConfigs(repoRoot string) (map[string]string, error) {
for _, filePath := range candidateFiles { for _, filePath := range candidateFiles {
structTypes, err := extractConfigStructTypes(filePath) structTypes, err := extractConfigStructTypes(filePath)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// check if any struct type matches an ecosystem config // check if any struct type matches an ecosystem config
@ -207,17 +207,23 @@ func discoverCatalogerConfigs(repoRoot string) (map[string]string, error) {
if len(missingConfigs) > 0 { if len(missingConfigs) > 0 {
sort.Strings(missingConfigs) sort.Strings(missingConfigs)
return nil, fmt.Errorf("could not find files for ecosystem configs: %s", strings.Join(missingConfigs, ", ")) return nil, nil, fmt.Errorf("could not find files for ecosystem configs: %s", strings.Join(missingConfigs, ", "))
} }
return fileToKey, nil // build reverse lookup map (yamlKey -> structName)
keyToStruct := make(map[string]string)
for structName, yamlKey := range ecosystemConfigs {
keyToStruct[yamlKey] = structName
}
return fileToKey, keyToStruct, nil
} }
// DiscoverAppConfigs discovers all application-level cataloger configuration fields // DiscoverAppConfigs discovers all application-level cataloger configuration fields
// from the options package // from the options package
func DiscoverAppConfigs(repoRoot string) ([]AppConfigField, error) { func DiscoverAppConfigs(repoRoot string) ([]AppConfigField, error) {
// discover cataloger config files dynamically // discover cataloger config files dynamically
configFiles, err := discoverCatalogerConfigs(repoRoot) configFiles, keyToStruct, err := discoverCatalogerConfigs(repoRoot)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to discover cataloger configs: %w", err) return nil, fmt.Errorf("failed to discover cataloger configs: %w", err)
} }
@ -225,7 +231,7 @@ func DiscoverAppConfigs(repoRoot string) ([]AppConfigField, error) {
// extract configuration fields from each discovered file // extract configuration fields from each discovered file
var configs []AppConfigField var configs []AppConfigField
for filePath, topLevelKey := range configFiles { for filePath, topLevelKey := range configFiles {
fields, err := extractAppConfigFields(filePath, topLevelKey) fields, err := extractAppConfigFields(filePath, topLevelKey, keyToStruct)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to extract config from %s: %w", filePath, err) return nil, fmt.Errorf("failed to extract config from %s: %w", filePath, err)
} }
@ -241,7 +247,7 @@ func DiscoverAppConfigs(repoRoot string) ([]AppConfigField, error) {
} }
// extractAppConfigFields extracts config fields from an options file // extractAppConfigFields extracts config fields from an options file
func extractAppConfigFields(filePath, topLevelKey string) ([]AppConfigField, error) { func extractAppConfigFields(filePath, topLevelKey string, keyToStruct map[string]string) ([]AppConfigField, error) {
fset := token.NewFileSet() fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments) f, err := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
if err != nil { if err != nil {
@ -251,7 +257,7 @@ func extractAppConfigFields(filePath, topLevelKey string) ([]AppConfigField, err
var configs []AppConfigField var configs []AppConfigField
// find the main config struct (not nested ones) // find the main config struct (not nested ones)
configStruct, descriptions := findAppConfigStructAndDescriptions(f, topLevelKey) configStruct, descriptions := findAppConfigStructAndDescriptions(f, topLevelKey, keyToStruct)
if configStruct == nil { if configStruct == nil {
return nil, fmt.Errorf("no config struct found in %s", filePath) return nil, fmt.Errorf("no config struct found in %s", filePath)
} }
@ -302,26 +308,13 @@ func extractAppConfigFields(filePath, topLevelKey string) ([]AppConfigField, err
// findAppConfigStructAndDescriptions finds the main config struct and extracts field descriptions // findAppConfigStructAndDescriptions finds the main config struct and extracts field descriptions
// from the DescribeFields method // from the DescribeFields method
func findAppConfigStructAndDescriptions(f *ast.File, topLevelKey string) (*ast.StructType, map[string]string) { func findAppConfigStructAndDescriptions(f *ast.File, topLevelKey string, keyToStruct map[string]string) (*ast.StructType, map[string]string) {
expectedName := determineExpectedConfigName(topLevelKey) structName := keyToStruct[topLevelKey]
configStruct := findConfigStruct(f, expectedName) configStruct := findConfigStruct(f, structName)
descriptions := extractDescriptionsFromDescribeFields(f) descriptions := extractDescriptionsFromDescribeFields(f)
return configStruct, descriptions return configStruct, descriptions
} }
// determineExpectedConfigName maps the top-level key to the expected config struct name
func determineExpectedConfigName(topLevelKey string) string {
// handle special cases first
switch topLevelKey {
case "linux-kernel":
return "linuxKernelConfig"
case "javascript":
return "javaScriptConfig"
default:
return topLevelKey + "Config"
}
}
// findConfigStruct searches for the config struct with the expected name in the AST // findConfigStruct searches for the config struct with the expected name in the AST
func findConfigStruct(f *ast.File, expectedName string) *ast.StructType { func findConfigStruct(f *ast.File, expectedName string) *ast.StructType {
for _, decl := range f.Decls { for _, decl := range f.Decls {

View File

@ -9,52 +9,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestDetermineExpectedConfigName(t *testing.T) {
tests := []struct {
name string
topLevelKey string
wantName string
}{
{
name: "linux-kernel special case",
topLevelKey: "linux-kernel",
wantName: "linuxKernelConfig",
},
{
name: "javascript special case",
topLevelKey: "javascript",
wantName: "javaScriptConfig",
},
{
name: "standard config golang",
topLevelKey: "golang",
wantName: "golangConfig",
},
{
name: "standard config python",
topLevelKey: "python",
wantName: "pythonConfig",
},
{
name: "standard config java",
topLevelKey: "java",
wantName: "javaConfig",
},
{
name: "standard config dotnet",
topLevelKey: "dotnet",
wantName: "dotnetConfig",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := determineExpectedConfigName(tt.topLevelKey)
require.Equal(t, tt.wantName, got)
})
}
}
func TestCleanDescription(t *testing.T) { func TestCleanDescription(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

View File

@ -33,8 +33,13 @@ var appConfigAnnotationPattern = regexp.MustCompile(`^//\s*app-config:\s*(.+)$`)
// Returns map where key is "packageName.StructName" (e.g., "golang.CatalogerConfig") // Returns map where key is "packageName.StructName" (e.g., "golang.CatalogerConfig")
func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) { func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) {
catalogerRoot := filepath.Join(repoRoot, "syft", "pkg", "cataloger") catalogerRoot := filepath.Join(repoRoot, "syft", "pkg", "cataloger")
return DiscoverConfigsFromPath(catalogerRoot)
}
// find all .go files under syft/pkg/cataloger/ recursively // DiscoverConfigsFromPath walks the given directory and discovers all configuration structs
// Returns map where key is "packageName.StructName" (e.g., "golang.CatalogerConfig")
func DiscoverConfigsFromPath(catalogerRoot string) (map[string]ConfigInfo, error) {
// find all .go files under the directory recursively
var files []string var files []string
err := filepath.Walk(catalogerRoot, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(catalogerRoot, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
@ -52,7 +57,7 @@ func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) {
discovered := make(map[string]ConfigInfo) discovered := make(map[string]ConfigInfo)
for _, file := range files { for _, file := range files {
configs, err := discoverConfigsInFile(file, repoRoot) configs, err := discoverConfigsInFile(file)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse %s: %w", file, err) return nil, fmt.Errorf("failed to parse %s: %w", file, err)
} }
@ -68,19 +73,15 @@ func DiscoverConfigs(repoRoot string) (map[string]ConfigInfo, error) {
return discovered, nil return discovered, nil
} }
func discoverConfigsInFile(path, repoRoot string) (map[string]ConfigInfo, error) { func discoverConfigsInFile(path string) (map[string]ConfigInfo, error) {
fset := token.NewFileSet() fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments) f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// extract package name from file path // extract package name from file path (use absolute path, not relative)
relPath, err := filepath.Rel(repoRoot, path) packageName := extractPackageNameFromPath(path)
if err != nil {
relPath = path
}
packageName := extractPackageNameFromPath(relPath)
if packageName == "" { if packageName == "" {
return nil, nil return nil, nil
} }

View File

@ -2,129 +2,131 @@ package main
import ( import (
"go/ast" "go/ast"
"path/filepath"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// expected config structs that should be discovered with app-config annotations
var expectedCatalogConfigs = []string{
"golang.CatalogerConfig",
"golang.MainModuleVersionConfig",
"java.ArchiveCatalogerConfig",
"python.CatalogerConfig",
"dotnet.CatalogerConfig",
"kernel.LinuxKernelCatalogerConfig",
"javascript.CatalogerConfig",
"nix.Config",
}
func TestDiscoverConfigs(t *testing.T) { func TestDiscoverConfigs(t *testing.T) {
repoRoot, err := RepoRoot() tests := []struct {
require.NoError(t, err) name string
fixturePath string
expectedConfigs []string
verifyConfig func(t *testing.T, configs map[string]ConfigInfo)
}{
{
name: "simple config with annotations",
fixturePath: "simple-config",
expectedConfigs: []string{
"golang.CatalogerConfig",
},
verifyConfig: func(t *testing.T, configs map[string]ConfigInfo) {
golangConfig := configs["golang.CatalogerConfig"]
require.Equal(t, "golang", golangConfig.PackageName)
require.Equal(t, "CatalogerConfig", golangConfig.StructName)
require.Len(t, golangConfig.Fields, 3, "should have 3 annotated fields")
configs, err := DiscoverConfigs(repoRoot) // verify specific field
require.NoError(t, err) var foundSearchLocalModCache bool
for _, field := range golangConfig.Fields {
if field.Name == "SearchLocalModCacheLicenses" {
foundSearchLocalModCache = true
require.Equal(t, "bool", field.Type)
require.Equal(t, "golang.search-local-mod-cache-licenses", field.AppKey)
require.Contains(t, field.Description, "searching for go package licenses")
}
}
require.True(t, foundSearchLocalModCache, "should find SearchLocalModCacheLicenses field")
},
},
{
name: "nested config struct",
fixturePath: "nested-config",
expectedConfigs: []string{
"golang.CatalogerConfig",
"golang.MainModuleVersionConfig",
},
verifyConfig: func(t *testing.T, configs map[string]ConfigInfo) {
// verify main config
golangConfig := configs["golang.CatalogerConfig"]
require.Equal(t, "golang", golangConfig.PackageName)
require.Equal(t, "CatalogerConfig", golangConfig.StructName)
// verify we discovered multiple config structs // verify nested config
require.NotEmpty(t, configs, "should discover at least one config struct") mainModuleConfig := configs["golang.MainModuleVersionConfig"]
require.Equal(t, "golang", mainModuleConfig.PackageName)
require.Equal(t, "MainModuleVersionConfig", mainModuleConfig.StructName)
require.Len(t, mainModuleConfig.Fields, 2, "should have 2 annotated fields")
// check for known config structs that have app-config annotations // check for specific nested field
for _, expected := range expectedCatalogConfigs { var foundFromLDFlags bool
config, ok := configs[expected] for _, field := range mainModuleConfig.Fields {
require.True(t, ok, "should discover config: %s", expected) if field.Name == "FromLDFlags" {
require.NotEmpty(t, config.Fields, "config %s should have fields", expected) foundFromLDFlags = true
require.Equal(t, expected, config.PackageName+"."+config.StructName) require.Equal(t, "bool", field.Type)
require.Equal(t, "golang.main-module-version.from-ld-flags", field.AppKey)
require.Contains(t, field.Description, "extract version from LD flags")
}
}
require.True(t, foundFromLDFlags, "should find FromLDFlags field")
},
},
{
name: "multiple configs in different packages",
fixturePath: "multiple-configs",
expectedConfigs: []string{
"python.CatalogerConfig",
"java.ArchiveCatalogerConfig",
},
verifyConfig: func(t *testing.T, configs map[string]ConfigInfo) {
// verify python config
pythonConfig := configs["python.CatalogerConfig"]
require.Equal(t, "python", pythonConfig.PackageName)
require.Len(t, pythonConfig.Fields, 1)
// verify java config
javaConfig := configs["java.ArchiveCatalogerConfig"]
require.Equal(t, "java", javaConfig.PackageName)
require.Len(t, javaConfig.Fields, 1)
},
},
{
name: "config without annotations",
fixturePath: "no-annotations",
expectedConfigs: []string{},
verifyConfig: func(t *testing.T, configs map[string]ConfigInfo) {
// should not discover any configs without annotations
require.Empty(t, configs, "should not discover configs without app-config annotations")
},
},
} }
// verify golang.CatalogerConfig structure for _, tt := range tests {
golangConfig := configs["golang.CatalogerConfig"] t.Run(tt.name, func(t *testing.T) {
wantGolangConfig := ConfigInfo{ fixtureDir := filepath.Join("testdata", "config-discovery", tt.fixturePath, "cataloger")
PackageName: "golang", configs, err := DiscoverConfigsFromPath(fixtureDir)
StructName: "CatalogerConfig", require.NoError(t, err)
}
if diff := cmp.Diff(wantGolangConfig.PackageName, golangConfig.PackageName); diff != "" {
t.Errorf("golang.CatalogerConfig.PackageName mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(wantGolangConfig.StructName, golangConfig.StructName); diff != "" {
t.Errorf("golang.CatalogerConfig.StructName mismatch (-want +got):\n%s", diff)
}
require.NotEmpty(t, golangConfig.Fields)
// check for specific field // Debug: log what was discovered
var foundSearchLocalModCache bool t.Logf("Discovered %d configs:", len(configs))
for _, field := range golangConfig.Fields { for key, config := range configs {
if field.Name == "SearchLocalModCacheLicenses" { t.Logf(" %s: %d fields", key, len(config.Fields))
foundSearchLocalModCache = true
wantField := ConfigField{
Name: "SearchLocalModCacheLicenses",
Type: "bool",
AppKey: "golang.search-local-mod-cache-licenses",
} }
if diff := cmp.Diff(wantField.Name, field.Name); diff != "" {
t.Errorf("SearchLocalModCacheLicenses field Name mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(wantField.Type, field.Type); diff != "" {
t.Errorf("SearchLocalModCacheLicenses field Type mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(wantField.AppKey, field.AppKey); diff != "" {
t.Errorf("SearchLocalModCacheLicenses field AppKey mismatch (-want +got):\n%s", diff)
}
require.NotEmpty(t, field.Description)
require.Contains(t, field.Description, "searching for go package licenses")
}
}
require.True(t, foundSearchLocalModCache, "should find SearchLocalModCacheLicenses field")
// verify nested config struct // verify expected configs were discovered
golangMainModuleConfig := configs["golang.MainModuleVersionConfig"] for _, expected := range tt.expectedConfigs {
wantMainModuleConfig := ConfigInfo{ config, ok := configs[expected]
PackageName: "golang", require.True(t, ok, "should discover config: %s", expected)
StructName: "MainModuleVersionConfig", require.NotEmpty(t, config.Fields, "config %s should have fields", expected)
} require.Equal(t, expected, config.PackageName+"."+config.StructName)
if diff := cmp.Diff(wantMainModuleConfig.PackageName, golangMainModuleConfig.PackageName); diff != "" { }
t.Errorf("golang.MainModuleVersionConfig.PackageName mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(wantMainModuleConfig.StructName, golangMainModuleConfig.StructName); diff != "" {
t.Errorf("golang.MainModuleVersionConfig.StructName mismatch (-want +got):\n%s", diff)
}
require.NotEmpty(t, golangMainModuleConfig.Fields)
// check for specific nested field // run custom verification
var foundFromLDFlags bool if tt.verifyConfig != nil {
for _, field := range golangMainModuleConfig.Fields { tt.verifyConfig(t, configs)
if field.Name == "FromLDFlags" {
foundFromLDFlags = true
wantField := ConfigField{
Name: "FromLDFlags",
Type: "bool",
AppKey: "golang.main-module-version.from-ld-flags",
} }
if diff := cmp.Diff(wantField.Name, field.Name); diff != "" { })
t.Errorf("FromLDFlags field Name mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(wantField.Type, field.Type); diff != "" {
t.Errorf("FromLDFlags field Type mismatch (-want +got):\n%s", diff)
}
if diff := cmp.Diff(wantField.AppKey, field.AppKey); diff != "" {
t.Errorf("FromLDFlags field AppKey mismatch (-want +got):\n%s", diff)
}
require.NotEmpty(t, field.Description)
}
}
require.True(t, foundFromLDFlags, "should find FromLDFlags field in MainModuleVersionConfig")
// print summary for manual inspection
t.Logf("Discovered %d config structs:", len(configs))
for key, config := range configs {
t.Logf(" %s: %d fields", key, len(config.Fields))
for _, field := range config.Fields {
t.Logf(" - %s (%s): %s", field.Name, field.Type, field.AppKey)
if diff := cmp.Diff("", field.Description); diff == "" {
t.Logf(" WARNING: field %s has no description", field.Name)
}
}
} }
} }

View File

@ -215,14 +215,20 @@ func parseGenericCatalogerFunction(funcDecl *ast.FuncDecl, filePath, repoRoot st
func extractPackageNameFromPath(filePath string) string { func extractPackageNameFromPath(filePath string) string {
parts := strings.Split(filePath, string(filepath.Separator)) parts := strings.Split(filePath, string(filepath.Separator))
// find the index of "cataloger" in the path // find the LAST occurrence of "cataloger" in the path
// (to handle test fixtures with multiple "cataloger" segments)
lastCatalogerIndex := -1
for i, part := range parts { for i, part := range parts {
if part == "cataloger" && i+1 < len(parts) { if part == "cataloger" {
// return the next segment after "cataloger" lastCatalogerIndex = i
return parts[i+1]
} }
} }
if lastCatalogerIndex != -1 && lastCatalogerIndex+1 < len(parts) {
// return the next segment after the last "cataloger"
return parts[lastCatalogerIndex+1]
}
return "" return ""
} }

View File

@ -9,52 +9,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// test helper functions
// parseFuncDecl parses a function declaration from a code string
func parseFuncDecl(t *testing.T, code string) *ast.FuncDecl {
t.Helper()
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", "package test\n"+code, 0)
require.NoError(t, err)
require.Len(t, file.Decls, 1, "expected exactly one declaration")
funcDecl, ok := file.Decls[0].(*ast.FuncDecl)
require.True(t, ok, "expected declaration to be a function")
return funcDecl
}
// parseCallExpr parses a call expression from a code string
func parseCallExpr(t *testing.T, code string) *ast.CallExpr {
t.Helper()
expr, err := parser.ParseExpr(code)
require.NoError(t, err)
callExpr, ok := expr.(*ast.CallExpr)
require.True(t, ok, "expected expression to be a call expression")
return callExpr
}
// parseCompositeLit parses a composite literal from a code string
func parseCompositeLit(t *testing.T, code string) *ast.CompositeLit {
t.Helper()
expr, err := parser.ParseExpr(code)
require.NoError(t, err)
lit, ok := expr.(*ast.CompositeLit)
require.True(t, ok, "expected expression to be a composite literal")
return lit
}
// parseConstDecl parses a const declaration from a code string and returns the GenDecl
func parseConstDecl(t *testing.T, code string) *ast.GenDecl {
t.Helper()
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", "package test\n"+code, 0)
require.NoError(t, err)
require.Len(t, file.Decls, 1, "expected exactly one declaration")
genDecl, ok := file.Decls[0].(*ast.GenDecl)
require.True(t, ok, "expected declaration to be a general declaration")
return genDecl
}
func TestReturnsPackageCataloger(t *testing.T) { func TestReturnsPackageCataloger(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -387,3 +341,49 @@ func TestResolveImportPath(t *testing.T) {
}) })
} }
} }
// test helper functions
// parseFuncDecl parses a function declaration from a code string
func parseFuncDecl(t *testing.T, code string) *ast.FuncDecl {
t.Helper()
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", "package test\n"+code, 0)
require.NoError(t, err)
require.Len(t, file.Decls, 1, "expected exactly one declaration")
funcDecl, ok := file.Decls[0].(*ast.FuncDecl)
require.True(t, ok, "expected declaration to be a function")
return funcDecl
}
// parseCallExpr parses a call expression from a code string
func parseCallExpr(t *testing.T, code string) *ast.CallExpr {
t.Helper()
expr, err := parser.ParseExpr(code)
require.NoError(t, err)
callExpr, ok := expr.(*ast.CallExpr)
require.True(t, ok, "expected expression to be a call expression")
return callExpr
}
// parseCompositeLit parses a composite literal from a code string
func parseCompositeLit(t *testing.T, code string) *ast.CompositeLit {
t.Helper()
expr, err := parser.ParseExpr(code)
require.NoError(t, err)
lit, ok := expr.(*ast.CompositeLit)
require.True(t, ok, "expected expression to be a composite literal")
return lit
}
// parseConstDecl parses a const declaration from a code string and returns the GenDecl
func parseConstDecl(t *testing.T, code string) *ast.GenDecl {
t.Helper()
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "", "package test\n"+code, 0)
require.NoError(t, err)
require.Len(t, file.Decls, 1, "expected exactly one declaration")
genDecl, ok := file.Decls[0].(*ast.GenDecl)
require.True(t, ok, "expected declaration to be a general declaration")
return genDecl
}

View File

@ -1,19 +0,0 @@
package duplicate
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type Config1 struct {
Option1 bool
}
func NewDuplicateCataloger1(cfg Config1) pkg.Cataloger {
return generic.NewCataloger("duplicate-cataloger").
WithParserByGlobs(parse1, "**/*.txt")
}
func parse1(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,19 +0,0 @@
package duplicate
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type Config2 struct {
Option2 string
}
func NewDuplicateCataloger2(cfg Config2) pkg.Cataloger {
return generic.NewCataloger("duplicate-cataloger").
WithParserByGlobs(parse2, "**/*.json")
}
func parse2(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,15 +0,0 @@
package kernel
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
func NewLinuxKernelCataloger(cfg LinuxKernelCatalogerConfig) pkg.Cataloger {
return generic.NewCataloger("linux-kernel-cataloger").
WithParserByGlobs(parse, "**/vmlinuz")
}
func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,19 +0,0 @@
package ruby
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type Config struct {
Setting bool
}
func NewRubyCataloger(opts Config) pkg.Cataloger {
return generic.NewCataloger("ruby-cataloger").
WithParserByGlobs(parse, "**/Gemfile")
}
func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,17 +0,0 @@
package binary
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type Parser struct{}
func NewBinaryCataloger(parser Parser) pkg.Cataloger {
return generic.NewCataloger("binary-cataloger").
WithParserByGlobs(parse, "**/*")
}
func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,16 +0,0 @@
package rust
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/test/cargo"
)
func NewRustCataloger(cfg cargo.CatalogerConfig) pkg.Cataloger {
return generic.NewCataloger("rust-cataloger").
WithParserByGlobs(parse, "**/Cargo.toml")
}
func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,19 +0,0 @@
package golang
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
type CatalogerConfig struct {
SomeOption bool
}
func NewGoModuleCataloger(cfg CatalogerConfig) pkg.Cataloger {
return generic.NewCataloger("go-module-cataloger").
WithParserByGlobs(parseGoMod, "**/go.mod")
}
func parseGoMod(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,6 +1,10 @@
package python package python
import ( import (
"context"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/generic"
) )
@ -11,11 +15,11 @@ type CatalogerConfig struct {
Setting string Setting string
} }
func NewPythonCataloger(cfg CatalogerConfig) pkg.Cataloger { func NewPythonCataloger(_ CatalogerConfig) pkg.Cataloger {
return generic.NewCataloger(catalogerName). return generic.NewCataloger(catalogerName).
WithParserByGlobs(parse, "**/*.py") WithParserByGlobs(parse, "**/*.py")
} }
func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil return nil, nil, nil
} }

View File

@ -0,0 +1,23 @@
package duplicate1
import (
"context"
"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"
)
type Config struct {
Option1 bool
}
func NewDuplicateCataloger(_ Config) pkg.Cataloger {
return generic.NewCataloger("duplicate-cataloger").
WithParserByGlobs(parse, "**/*.txt")
}
func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil
}

View File

@ -0,0 +1,23 @@
package duplicate2
import (
"context"
"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"
)
type Config struct {
Option2 string
}
func NewDuplicateCataloger(_ Config) pkg.Cataloger {
return generic.NewCataloger("duplicate-cataloger").
WithParserByGlobs(parse, "**/*.json")
}
func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,6 +1,10 @@
package dotnet package dotnet
import ( import (
"context"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
@ -18,6 +22,6 @@ func (d dotnetCataloger) Name() string {
return catalogerName return catalogerName
} }
func (d dotnetCataloger) Catalog(resolver any) ([]pkg.Package, []pkg.Relationship, error) { func (d dotnetCataloger) Catalog(_ context.Context, _ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil return nil, nil, nil
} }

View File

@ -1,6 +1,10 @@
package java package java
import ( import (
"context"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
@ -18,7 +22,7 @@ func (p pomXMLCataloger) Name() string {
return pomCatalogerName return pomCatalogerName
} }
func (p pomXMLCataloger) Catalog(resolver any) ([]pkg.Package, []pkg.Relationship, error) { func (p pomXMLCataloger) Catalog(_ context.Context, _ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil return nil, nil, nil
} }

View File

@ -0,0 +1,19 @@
package kernel
import (
"context"
"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"
)
func NewLinuxKernelCataloger(_ LinuxKernelCatalogerConfig) pkg.Cataloger {
return generic.NewCataloger("linux-kernel-cataloger").
WithParserByGlobs(parse, "**/vmlinuz")
}
func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil
}

View File

@ -0,0 +1,23 @@
package ruby
import (
"context"
"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"
)
type Config struct {
Setting bool
}
func NewRubyCataloger(_ Config) pkg.Cataloger {
return generic.NewCataloger("ruby-cataloger").
WithParserByGlobs(parse, "**/Gemfile")
}
func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil
}

View File

@ -1,6 +1,10 @@
package javascript package javascript
import ( import (
"context"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/generic"
) )
@ -10,6 +14,6 @@ func NewJavaScriptCataloger() pkg.Cataloger {
WithParserByGlobs(parse, "**/*.js") WithParserByGlobs(parse, "**/*.js")
} }
func parse(path string, reader any) ([]pkg.Package, []pkg.Relationship, error) { func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil return nil, nil, nil
} }

View File

@ -0,0 +1,21 @@
package binary
import (
"context"
"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"
)
type Parser struct{}
func NewBinaryCataloger(_ Parser) pkg.Cataloger {
return generic.NewCataloger("binary-cataloger").
WithParserByGlobs(parse, "**/*")
}
func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil
}

View File

@ -0,0 +1,24 @@
package rust
import (
"context"
"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"
)
// CatalogerConfig is imported from a selector expression in the real code
type CatalogerConfig struct {
SomeOption bool
}
func NewRustCataloger(_ CatalogerConfig) pkg.Cataloger {
return generic.NewCataloger("rust-cataloger").
WithParserByGlobs(parse, "**/Cargo.toml")
}
func parse(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil
}

View File

@ -0,0 +1,23 @@
package golang
import (
"context"
"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"
)
type CatalogerConfig struct {
SomeOption bool
}
func NewGoModuleCataloger(_ CatalogerConfig) pkg.Cataloger {
return generic.NewCataloger("go-module-cataloger").
WithParserByGlobs(parseGoMod, "**/go.mod")
}
func parseGoMod(_ context.Context, _ file.Resolver, _ *generic.Environment, _ file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
return nil, nil, nil
}

View File

@ -0,0 +1,8 @@
package java
// ArchiveCatalogerConfig contains configuration for the java archive cataloger
type ArchiveCatalogerConfig struct {
// include archive contents in catalog
// app-config: java.use-network
UseNetwork bool
}

View File

@ -0,0 +1,8 @@
package python
// CatalogerConfig contains configuration for the python cataloger
type CatalogerConfig struct {
// guess unpinned python package requirements
// app-config: python.guess-unpinned-requirements
GuessUnpinnedRequirements bool
}

View File

@ -0,0 +1,22 @@
package golang
// MainModuleVersionConfig contains nested configuration for main module version detection
type MainModuleVersionConfig struct {
// extract version from LD flags
// app-config: golang.main-module-version.from-ld-flags
FromLDFlags bool
// extract version from build info
// app-config: golang.main-module-version.from-build-info
FromBuildInfo bool
}
// CatalogerConfig contains configuration for the golang cataloger
type CatalogerConfig struct {
// enable searching for go package licenses in the local mod cache
// app-config: golang.search-local-mod-cache-licenses
SearchLocalModCacheLicenses bool
// main module version configuration
MainModuleVersion MainModuleVersionConfig
}

View File

@ -0,0 +1,10 @@
package javascript
// CatalogerConfig contains configuration for the javascript cataloger (no annotations)
type CatalogerConfig struct {
// this field has no app-config annotation
SearchRemoteLicenses bool
// this field also has no annotation
IncludeDevDependencies bool
}

View File

@ -0,0 +1,16 @@
package golang
// CatalogerConfig contains configuration for the golang cataloger
type CatalogerConfig struct {
// enable searching for go package licenses in the local mod cache
// app-config: golang.search-local-mod-cache-licenses
SearchLocalModCacheLicenses bool
// base URL for npm registry
// app-config: golang.npm-base-url
NpmBaseURL string
// list of globs to search for go.mod files
// app-config: golang.search-patterns
SearchPatterns []string
}