mirror of
https://github.com/anchore/syft.git
synced 2025-11-18 08:53:15 +01:00
Merge pull request #374 from anchore/add-binary-classifier
Add file classification cataloger
This commit is contained in:
commit
9ad786d608
11
README.md
11
README.md
@ -108,6 +108,17 @@ package:
|
|||||||
# same as -s ; SYFT_PACKAGE_CATALOGER_SCOPE env var
|
# same as -s ; SYFT_PACKAGE_CATALOGER_SCOPE env var
|
||||||
scope: "squashed"
|
scope: "squashed"
|
||||||
|
|
||||||
|
# cataloging file classifications is exposed through the power-user subcommand
|
||||||
|
file-classification:
|
||||||
|
cataloger:
|
||||||
|
# enable/disable cataloging of file classifications
|
||||||
|
# SYFT_FILE_CLASSIFICATION_CATALOGER_ENABLED env var
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# the search space to look for file classifications (options: all-layers, squashed)
|
||||||
|
# SYFT_FILE_CLASSIFICATION_CATALOGER_SCOPE env var
|
||||||
|
scope: "squashed"
|
||||||
|
|
||||||
# cataloging file metadata is exposed through the power-user subcommand
|
# cataloging file metadata is exposed through the power-user subcommand
|
||||||
file-metadata:
|
file-metadata:
|
||||||
cataloger:
|
cataloger:
|
||||||
|
|||||||
@ -20,6 +20,7 @@ func powerUserTasks() ([]powerUserTask, error) {
|
|||||||
catalogFileMetadataTask,
|
catalogFileMetadataTask,
|
||||||
catalogFileDigestsTask,
|
catalogFileDigestsTask,
|
||||||
catalogSecretsTask,
|
catalogSecretsTask,
|
||||||
|
catalogFileClassificationsTask,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, generator := range generators {
|
for _, generator := range generators {
|
||||||
@ -156,3 +157,31 @@ func catalogSecretsTask() (powerUserTask, error) {
|
|||||||
|
|
||||||
return task, nil
|
return task, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func catalogFileClassificationsTask() (powerUserTask, error) {
|
||||||
|
if !appConfig.FileClassification.Cataloger.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: in the future we could expose out the classifiers via configuration
|
||||||
|
classifierCataloger, err := file.NewClassificationCataloger(file.DefaultClassifiers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
task := func(results *poweruser.JSONDocumentConfig, src source.Source) error {
|
||||||
|
resolver, err := src.FileResolver(appConfig.FileClassification.Cataloger.ScopeOpt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := classifierCataloger.Catalog(resolver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
results.FileClassifications = result
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return task, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -30,14 +30,15 @@ type Application struct {
|
|||||||
ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading)
|
ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading)
|
||||||
Output string `yaml:"output" json:"output" mapstructure:"output"` // -o, the Presenter hint string to use for report formatting
|
Output string `yaml:"output" json:"output" mapstructure:"output"` // -o, the Presenter hint string to use for report formatting
|
||||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
||||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
|
||||||
CliOptions CliOnlyOptions `yaml:"-" json:"-"` // all options only available through the CLI (not via env vars or config)
|
|
||||||
Dev Development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
|
||||||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||||
Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
|
Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
|
||||||
Package Packages `yaml:"package" json:"package" mapstructure:"package"`
|
CliOptions CliOnlyOptions `yaml:"-" json:"-"` // all options only available through the CLI (not via env vars or config)
|
||||||
|
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
||||||
|
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||||
|
Package packages `yaml:"package" json:"package" mapstructure:"package"`
|
||||||
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
|
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
|
||||||
Secrets Secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
|
||||||
|
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newApplicationConfig(v *viper.Viper, cliOpts CliOnlyOptions) *Application {
|
func newApplicationConfig(v *viper.Viper, cliOpts CliOnlyOptions) *Application {
|
||||||
|
|||||||
@ -2,12 +2,12 @@ package config
|
|||||||
|
|
||||||
import "github.com/spf13/viper"
|
import "github.com/spf13/viper"
|
||||||
|
|
||||||
type Development struct {
|
type development struct {
|
||||||
ProfileCPU bool `yaml:"profile-cpu" json:"profile-cpu" mapstructure:"profile-cpu"`
|
ProfileCPU bool `yaml:"profile-cpu" json:"profile-cpu" mapstructure:"profile-cpu"`
|
||||||
ProfileMem bool `yaml:"profile-mem" json:"profile-mem" mapstructure:"profile-mem"`
|
ProfileMem bool `yaml:"profile-mem" json:"profile-mem" mapstructure:"profile-mem"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg Development) loadDefaultValues(v *viper.Viper) {
|
func (cfg development) loadDefaultValues(v *viper.Viper) {
|
||||||
v.SetDefault("dev.profile-cpu", false)
|
v.SetDefault("dev.profile-cpu", false)
|
||||||
v.SetDefault("dev.profile-mem", false)
|
v.SetDefault("dev.profile-mem", false)
|
||||||
}
|
}
|
||||||
|
|||||||
19
internal/config/file_classification.go
Normal file
19
internal/config/file_classification.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileClassification struct {
|
||||||
|
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg fileClassification) loadDefaultValues(v *viper.Viper) {
|
||||||
|
v.SetDefault("file-classification.cataloger.enabled", true)
|
||||||
|
v.SetDefault("file-classification.cataloger.scope", source.SquashedScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *fileClassification) parseConfigValues() error {
|
||||||
|
return cfg.Cataloger.parseConfigValues()
|
||||||
|
}
|
||||||
@ -2,14 +2,14 @@ package config
|
|||||||
|
|
||||||
import "github.com/spf13/viper"
|
import "github.com/spf13/viper"
|
||||||
|
|
||||||
type Packages struct {
|
type packages struct {
|
||||||
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
|
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg Packages) loadDefaultValues(v *viper.Viper) {
|
func (cfg packages) loadDefaultValues(v *viper.Viper) {
|
||||||
v.SetDefault("package.cataloger.enabled", true)
|
v.SetDefault("package.cataloger.enabled", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Packages) parseConfigValues() error {
|
func (cfg *packages) parseConfigValues() error {
|
||||||
return cfg.Cataloger.parseConfigValues()
|
return cfg.Cataloger.parseConfigValues()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Secrets struct {
|
type secrets struct {
|
||||||
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
|
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
|
||||||
AdditionalPatterns map[string]string `yaml:"additional-patterns" json:"additional-patterns" mapstructure:"additional-patterns"`
|
AdditionalPatterns map[string]string `yaml:"additional-patterns" json:"additional-patterns" mapstructure:"additional-patterns"`
|
||||||
ExcludePatternNames []string `yaml:"exclude-pattern-names" json:"exclude-pattern-names" mapstructure:"exclude-pattern-names"`
|
ExcludePatternNames []string `yaml:"exclude-pattern-names" json:"exclude-pattern-names" mapstructure:"exclude-pattern-names"`
|
||||||
@ -14,7 +14,7 @@ type Secrets struct {
|
|||||||
SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"`
|
SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg Secrets) loadDefaultValues(v *viper.Viper) {
|
func (cfg secrets) loadDefaultValues(v *viper.Viper) {
|
||||||
v.SetDefault("secrets.cataloger.enabled", true)
|
v.SetDefault("secrets.cataloger.enabled", true)
|
||||||
v.SetDefault("secrets.cataloger.scope", source.AllLayersScope)
|
v.SetDefault("secrets.cataloger.scope", source.AllLayersScope)
|
||||||
v.SetDefault("secrets.reveal-values", false)
|
v.SetDefault("secrets.reveal-values", false)
|
||||||
@ -23,6 +23,6 @@ func (cfg Secrets) loadDefaultValues(v *viper.Viper) {
|
|||||||
v.SetDefault("secrets.exclude-pattern-names", []string{})
|
v.SetDefault("secrets.exclude-pattern-names", []string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Secrets) parseConfigValues() error {
|
func (cfg *secrets) parseConfigValues() error {
|
||||||
return cfg.Cataloger.parseConfigValues()
|
return cfg.Cataloger.parseConfigValues()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import "regexp"
|
|
||||||
|
|
||||||
// MatchCaptureGroups takes a regular expression and string and returns all of the named capture group results in a map.
|
|
||||||
func MatchCaptureGroups(regEx *regexp.Regexp, str string) map[string]string {
|
|
||||||
match := regEx.FindStringSubmatch(str)
|
|
||||||
results := make(map[string]string)
|
|
||||||
for i, name := range regEx.SubexpNames() {
|
|
||||||
if i > 0 && i <= len(match) {
|
|
||||||
results[name] = match[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
@ -9,6 +9,7 @@ type JSONDocument struct {
|
|||||||
// here should be optional by supplying "omitempty" on these fields hint to the jsonschema generator to not
|
// here should be optional by supplying "omitempty" on these fields hint to the jsonschema generator to not
|
||||||
// require these fields. As an accepted rule in this repo all collections should still be initialized in the
|
// require these fields. As an accepted rule in this repo all collections should still be initialized in the
|
||||||
// context of being used in a JSON document.
|
// context of being used in a JSON document.
|
||||||
|
FileClassifications []JSONFileClassifications `json:"fileClassifications,omitempty"` // note: must have omitempty
|
||||||
FileMetadata []JSONFileMetadata `json:"fileMetadata,omitempty"` // note: must have omitempty
|
FileMetadata []JSONFileMetadata `json:"fileMetadata,omitempty"` // note: must have omitempty
|
||||||
Secrets []JSONSecrets `json:"secrets,omitempty"` // note: must have omitempty
|
Secrets []JSONSecrets `json:"secrets,omitempty"` // note: must have omitempty
|
||||||
packages.JSONDocument
|
packages.JSONDocument
|
||||||
@ -27,6 +28,7 @@ func NewJSONDocument(config JSONDocumentConfig) (JSONDocument, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return JSONDocument{
|
return JSONDocument{
|
||||||
|
FileClassifications: NewJSONFileClassifications(config.FileClassifications),
|
||||||
FileMetadata: fileMetadata,
|
FileMetadata: fileMetadata,
|
||||||
Secrets: NewJSONSecrets(config.Secrets),
|
Secrets: NewJSONSecrets(config.Secrets),
|
||||||
JSONDocument: pkgsDoc,
|
JSONDocument: pkgsDoc,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ type JSONDocumentConfig struct {
|
|||||||
PackageCatalog *pkg.Catalog
|
PackageCatalog *pkg.Catalog
|
||||||
FileMetadata map[source.Location]source.FileMetadata
|
FileMetadata map[source.Location]source.FileMetadata
|
||||||
FileDigests map[source.Location][]file.Digest
|
FileDigests map[source.Location][]file.Digest
|
||||||
|
FileClassifications map[source.Location][]file.Classification
|
||||||
Secrets map[source.Location][]file.SearchResult
|
Secrets map[source.Location][]file.SearchResult
|
||||||
Distro *distro.Distro
|
Distro *distro.Distro
|
||||||
SourceMetadata source.Metadata
|
SourceMetadata source.Metadata
|
||||||
|
|||||||
34
internal/presenter/poweruser/json_file_classifications.go
Normal file
34
internal/presenter/poweruser/json_file_classifications.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package poweruser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONFileClassifications struct {
|
||||||
|
Location source.Location `json:"location"`
|
||||||
|
Classification file.Classification `json:"classification"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJSONFileClassifications(data map[source.Location][]file.Classification) []JSONFileClassifications {
|
||||||
|
results := make([]JSONFileClassifications, 0)
|
||||||
|
for location, classifications := range data {
|
||||||
|
for _, classification := range classifications {
|
||||||
|
results = append(results, JSONFileClassifications{
|
||||||
|
Location: location,
|
||||||
|
Classification: classification,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by real path then virtual path to ensure the result is stable across multiple runs
|
||||||
|
sort.SliceStable(results, func(i, j int) bool {
|
||||||
|
if results[i].Location.RealPath == results[j].Location.RealPath {
|
||||||
|
return results[i].Location.VirtualPath < results[j].Location.VirtualPath
|
||||||
|
}
|
||||||
|
return results[i].Location.RealPath < results[j].Location.RealPath
|
||||||
|
})
|
||||||
|
return results
|
||||||
|
}
|
||||||
@ -161,15 +161,6 @@
|
|||||||
"configPath": "",
|
"configPath": "",
|
||||||
"output": "",
|
"output": "",
|
||||||
"quiet": false,
|
"quiet": false,
|
||||||
"log": {
|
|
||||||
"structured": false,
|
|
||||||
"level": "",
|
|
||||||
"file-location": ""
|
|
||||||
},
|
|
||||||
"dev": {
|
|
||||||
"profile-cpu": false,
|
|
||||||
"profile-mem": false
|
|
||||||
},
|
|
||||||
"check-for-app-update": false,
|
"check-for-app-update": false,
|
||||||
"anchore": {
|
"anchore": {
|
||||||
"host": "",
|
"host": "",
|
||||||
@ -177,6 +168,15 @@
|
|||||||
"dockerfile": "",
|
"dockerfile": "",
|
||||||
"overwrite-existing-image": false
|
"overwrite-existing-image": false
|
||||||
},
|
},
|
||||||
|
"dev": {
|
||||||
|
"profile-cpu": false,
|
||||||
|
"profile-mem": false
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"structured": false,
|
||||||
|
"level": "",
|
||||||
|
"file-location": ""
|
||||||
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"cataloger": {
|
"cataloger": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@ -192,6 +192,12 @@
|
|||||||
"sha256"
|
"sha256"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"file-classification": {
|
||||||
|
"cataloger": {
|
||||||
|
"enabled": false,
|
||||||
|
"scope": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"secrets": {
|
"secrets": {
|
||||||
"cataloger": {
|
"cataloger": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
|||||||
45
internal/regex_helpers.go
Normal file
45
internal/regex_helpers.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "regexp"
|
||||||
|
|
||||||
|
// MatchNamedCaptureGroups takes a regular expression and string and returns all of the named capture group results in a map.
|
||||||
|
// This is only for the first match in the regex. Callers shouldn't be providing regexes with multiple capture groups with the same name.
|
||||||
|
func MatchNamedCaptureGroups(regEx *regexp.Regexp, content string) map[string]string {
|
||||||
|
// note: we are looking across all matches and stopping on the first non-empty match. Why? Take the following example:
|
||||||
|
// input: "cool something to match against" pattern: `((?P<name>match) (?P<version>against))?`. Since the pattern is
|
||||||
|
// encapsulated in an optional capture group, there will be results for each character, but the results will match
|
||||||
|
// on nothing. The only "true" match will be at the end ("match against").
|
||||||
|
allMatches := regEx.FindAllStringSubmatch(content, -1)
|
||||||
|
var results map[string]string
|
||||||
|
for _, match := range allMatches {
|
||||||
|
// fill a candidate results map with named capture group results, accepting empty values, but not groups with
|
||||||
|
// no names
|
||||||
|
for nameIdx, name := range regEx.SubexpNames() {
|
||||||
|
if nameIdx > len(match) || len(name) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if results == nil {
|
||||||
|
results = make(map[string]string)
|
||||||
|
}
|
||||||
|
results[name] = match[nameIdx]
|
||||||
|
}
|
||||||
|
// note: since we are looking for the first best potential match we should stop when we find the first one
|
||||||
|
// with non-empty results.
|
||||||
|
if !isEmptyMap(results) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyMap(m map[string]string) bool {
|
||||||
|
if len(m) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, value := range m {
|
||||||
|
if value != "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
70
internal/regex_helpers_test.go
Normal file
70
internal/regex_helpers_test.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMatchCaptureGroups(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
pattern string
|
||||||
|
expected map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "go-case",
|
||||||
|
input: "match this thing",
|
||||||
|
pattern: `(?P<name>match).*(?P<version>thing)`,
|
||||||
|
expected: map[string]string{
|
||||||
|
"name": "match",
|
||||||
|
"version": "thing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "only matches the first instance",
|
||||||
|
input: "match this thing batch another think",
|
||||||
|
pattern: `(?P<name>[mb]atch).*?(?P<version>thin[gk])`,
|
||||||
|
expected: map[string]string{
|
||||||
|
"name": "match",
|
||||||
|
"version": "thing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested capture groups",
|
||||||
|
input: "cool something to match against",
|
||||||
|
pattern: `((?P<name>match) (?P<version>against))`,
|
||||||
|
expected: map[string]string{
|
||||||
|
"name": "match",
|
||||||
|
"version": "against",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested optional capture groups",
|
||||||
|
input: "cool something to match against",
|
||||||
|
pattern: `((?P<name>match) (?P<version>against))?`,
|
||||||
|
expected: map[string]string{
|
||||||
|
"name": "match",
|
||||||
|
"version": "against",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested optional capture groups with larger match",
|
||||||
|
input: "cool something to match against match never",
|
||||||
|
pattern: `.*?((?P<name>match) (?P<version>(against|never)))?`,
|
||||||
|
expected: map[string]string{
|
||||||
|
"name": "match",
|
||||||
|
"version": "against",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
actual := MatchNamedCaptureGroups(regexp.MustCompile(test.pattern), test.input)
|
||||||
|
assert.Equal(t, test.expected, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
935
schema/json/schema-1.0.6.json
Normal file
935
schema/json/schema-1.0.6.json
Normal file
@ -0,0 +1,935 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Document",
|
||||||
|
"definitions": {
|
||||||
|
"ApkFileRecord": {
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ownerUid": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ownerGid": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"checksum": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"ApkMetadata": {
|
||||||
|
"required": [
|
||||||
|
"package",
|
||||||
|
"originPackage",
|
||||||
|
"maintainer",
|
||||||
|
"version",
|
||||||
|
"license",
|
||||||
|
"architecture",
|
||||||
|
"url",
|
||||||
|
"description",
|
||||||
|
"size",
|
||||||
|
"installedSize",
|
||||||
|
"pullDependencies",
|
||||||
|
"pullChecksum",
|
||||||
|
"gitCommitOfApkPort",
|
||||||
|
"files"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"package": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"originPackage": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maintainer": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"architecture": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"installedSize": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"pullDependencies": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pullChecksum": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"gitCommitOfApkPort": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/ApkFileRecord"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"CargoPackageMetadata": {
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"version",
|
||||||
|
"source",
|
||||||
|
"checksum",
|
||||||
|
"dependencies"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"checksum": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Classification": {
|
||||||
|
"required": [
|
||||||
|
"class",
|
||||||
|
"metadata"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"class": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"patternProperties": {
|
||||||
|
".*": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Descriptor": {
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"version"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Digest": {
|
||||||
|
"required": [
|
||||||
|
"algorithm",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"algorithm": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Distribution": {
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"version",
|
||||||
|
"idLike"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"idLike": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Document": {
|
||||||
|
"required": [
|
||||||
|
"artifacts",
|
||||||
|
"artifactRelationships",
|
||||||
|
"source",
|
||||||
|
"distro",
|
||||||
|
"descriptor",
|
||||||
|
"schema"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"fileClassifications": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/FileClassifications"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"fileMetadata": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/FileMetadata"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"secrets": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Secrets"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"artifacts": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Package"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"artifactRelationships": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Relationship"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Source"
|
||||||
|
},
|
||||||
|
"distro": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Distribution"
|
||||||
|
},
|
||||||
|
"descriptor": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Descriptor"
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Schema"
|
||||||
|
},
|
||||||
|
"artifacts.metadata": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/ApkMetadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/CargoPackageMetadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/DpkgMetadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/GemMetadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/JavaMetadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/NpmPackageJSONMetadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/PythonPackageMetadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/RpmdbMetadata"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DpkgFileRecord": {
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"md5"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"md5": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"DpkgMetadata": {
|
||||||
|
"required": [
|
||||||
|
"package",
|
||||||
|
"source",
|
||||||
|
"version",
|
||||||
|
"sourceVersion",
|
||||||
|
"architecture",
|
||||||
|
"maintainer",
|
||||||
|
"installedSize",
|
||||||
|
"files"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"package": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sourceVersion": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"architecture": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"maintainer": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"installedSize": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/DpkgFileRecord"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"FileClassifications": {
|
||||||
|
"required": [
|
||||||
|
"location",
|
||||||
|
"classification"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"location": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Location"
|
||||||
|
},
|
||||||
|
"classification": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Classification"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"FileMetadata": {
|
||||||
|
"required": [
|
||||||
|
"location",
|
||||||
|
"metadata"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"location": {
|
||||||
|
"$ref": "#/definitions/Location"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/FileMetadataEntry"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"FileMetadataEntry": {
|
||||||
|
"required": [
|
||||||
|
"mode",
|
||||||
|
"type",
|
||||||
|
"userID",
|
||||||
|
"groupID"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"mode": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"linkDestination": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"userID": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"groupID": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"digests": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/Digest"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"GemMetadata": {
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"version"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"authors": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"licenses": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"homepage": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"JavaManifest": {
|
||||||
|
"properties": {
|
||||||
|
"main": {
|
||||||
|
"patternProperties": {
|
||||||
|
".*": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"namedSections": {
|
||||||
|
"patternProperties": {
|
||||||
|
".*": {
|
||||||
|
"patternProperties": {
|
||||||
|
".*": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"JavaMetadata": {
|
||||||
|
"required": [
|
||||||
|
"virtualPath"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"virtualPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/JavaManifest"
|
||||||
|
},
|
||||||
|
"pomProperties": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/PomProperties"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Location": {
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"layerID": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"NpmPackageJSONMetadata": {
|
||||||
|
"required": [
|
||||||
|
"author",
|
||||||
|
"licenses",
|
||||||
|
"homepage",
|
||||||
|
"description",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"files": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"licenses": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"homepage": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Package": {
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"version",
|
||||||
|
"type",
|
||||||
|
"foundBy",
|
||||||
|
"locations",
|
||||||
|
"licenses",
|
||||||
|
"language",
|
||||||
|
"cpes",
|
||||||
|
"purl",
|
||||||
|
"metadataType",
|
||||||
|
"metadata"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"foundBy": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"locations": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Location"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"licenses": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"cpes": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"purl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metadataType": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"PomProperties": {
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"name",
|
||||||
|
"groupId",
|
||||||
|
"artifactId",
|
||||||
|
"version",
|
||||||
|
"extraFields"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"groupId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"artifactId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"extraFields": {
|
||||||
|
"patternProperties": {
|
||||||
|
".*": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"PythonFileDigest": {
|
||||||
|
"required": [
|
||||||
|
"algorithm",
|
||||||
|
"value"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"algorithm": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"PythonFileRecord": {
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"digest": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/PythonFileDigest"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"PythonPackageMetadata": {
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"version",
|
||||||
|
"license",
|
||||||
|
"author",
|
||||||
|
"authorEmail",
|
||||||
|
"platform",
|
||||||
|
"sitePackagesRootPath"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"authorEmail": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/PythonFileRecord"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"sitePackagesRootPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"topLevelPackages": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Relationship": {
|
||||||
|
"required": [
|
||||||
|
"parent",
|
||||||
|
"child",
|
||||||
|
"type",
|
||||||
|
"metadata"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"parent": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"child": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"RpmdbFileRecord": {
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"mode",
|
||||||
|
"size",
|
||||||
|
"sha256"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"sha256": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"RpmdbMetadata": {
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"version",
|
||||||
|
"epoch",
|
||||||
|
"architecture",
|
||||||
|
"release",
|
||||||
|
"sourceRpm",
|
||||||
|
"size",
|
||||||
|
"license",
|
||||||
|
"vendor",
|
||||||
|
"files"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"epoch": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"architecture": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sourceRpm": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"vendor": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/RpmdbFileRecord"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Schema": {
|
||||||
|
"required": [
|
||||||
|
"version",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"SearchResult": {
|
||||||
|
"required": [
|
||||||
|
"classification",
|
||||||
|
"lineNumber",
|
||||||
|
"lineOffset",
|
||||||
|
"seekPosition",
|
||||||
|
"length"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"classification": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"lineNumber": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"lineOffset": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"seekPosition": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"length": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Secrets": {
|
||||||
|
"required": [
|
||||||
|
"location",
|
||||||
|
"secrets"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"location": {
|
||||||
|
"$ref": "#/definitions/Location"
|
||||||
|
},
|
||||||
|
"secrets": {
|
||||||
|
"items": {
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"$ref": "#/definitions/SearchResult"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"Source": {
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
syft/file/classification_cataloger.go
Normal file
37
syft/file/classification_cataloger.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClassificationCataloger struct {
|
||||||
|
classifiers []Classifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClassificationCataloger(classifiers []Classifier) (*ClassificationCataloger, error) {
|
||||||
|
return &ClassificationCataloger{
|
||||||
|
classifiers: classifiers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ClassificationCataloger) Catalog(resolver source.FileResolver) (map[source.Location][]Classification, error) {
|
||||||
|
results := make(map[source.Location][]Classification)
|
||||||
|
|
||||||
|
numResults := 0
|
||||||
|
for location := range resolver.AllLocations() {
|
||||||
|
for _, classifier := range i.classifiers {
|
||||||
|
result, err := classifier.Classify(resolver, location)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
results[location] = append(results[location], *result)
|
||||||
|
numResults++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Debugf("classification cataloger discovered %d results", numResults)
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
145
syft/file/classification_cataloger_test.go
Normal file
145
syft/file/classification_cataloger_test.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fixtureDir string
|
||||||
|
location string
|
||||||
|
expected []Classification
|
||||||
|
expectedErr func(assert.TestingT, error, ...interface{}) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "positive-libpython3.7.so",
|
||||||
|
fixtureDir: "test-fixtures/classifiers/positive",
|
||||||
|
location: "test-fixtures/classifiers/positive/libpython3.7.so",
|
||||||
|
expected: []Classification{
|
||||||
|
{
|
||||||
|
Class: "python-binary",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"version": "3.7.4a-vZ9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive-python3.6",
|
||||||
|
fixtureDir: "test-fixtures/classifiers/positive",
|
||||||
|
location: "test-fixtures/classifiers/positive/python3.6",
|
||||||
|
expected: []Classification{
|
||||||
|
{
|
||||||
|
Class: "python-binary",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"version": "3.6.3a-vZ9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive-patchlevel.h",
|
||||||
|
fixtureDir: "test-fixtures/classifiers/positive",
|
||||||
|
location: "test-fixtures/classifiers/positive/patchlevel.h",
|
||||||
|
expected: []Classification{
|
||||||
|
{
|
||||||
|
Class: "cpython-source",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"version": "3.9-aZ5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive-go",
|
||||||
|
fixtureDir: "test-fixtures/classifiers/positive",
|
||||||
|
location: "test-fixtures/classifiers/positive/go",
|
||||||
|
expected: []Classification{
|
||||||
|
{
|
||||||
|
Class: "go-binary",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"version": "1.14",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive-go-hint",
|
||||||
|
fixtureDir: "test-fixtures/classifiers/positive",
|
||||||
|
location: "test-fixtures/classifiers/positive/VERSION",
|
||||||
|
expected: []Classification{
|
||||||
|
{
|
||||||
|
Class: "go-binary-hint",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"version": "1.15",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: assert.NoError,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive-busybox",
|
||||||
|
fixtureDir: "test-fixtures/classifiers/positive",
|
||||||
|
location: "test-fixtures/classifiers/positive/busybox",
|
||||||
|
expected: []Classification{
|
||||||
|
{
|
||||||
|
Class: "busybox-binary",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"version": "3.33.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: assert.NoError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
c, err := NewClassificationCataloger(DefaultClassifiers)
|
||||||
|
test.expectedErr(t, err)
|
||||||
|
|
||||||
|
src, err := source.NewFromDirectory(test.fixtureDir)
|
||||||
|
test.expectedErr(t, err)
|
||||||
|
|
||||||
|
resolver, err := src.FileResolver(source.SquashedScope)
|
||||||
|
test.expectedErr(t, err)
|
||||||
|
|
||||||
|
actualResults, err := c.Catalog(resolver)
|
||||||
|
test.expectedErr(t, err)
|
||||||
|
|
||||||
|
loc := source.NewLocation(test.location)
|
||||||
|
|
||||||
|
if _, ok := actualResults[loc]; !ok {
|
||||||
|
t.Fatalf("could not find test location=%q", test.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, actualResults[loc])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) {
|
||||||
|
|
||||||
|
c, err := NewClassificationCataloger(DefaultClassifiers)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
src, err := source.NewFromDirectory("test-fixtures/classifiers/negative")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
resolver, err := src.FileResolver(source.SquashedScope)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
actualResults, err := c.Catalog(resolver)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, len(actualResults))
|
||||||
|
|
||||||
|
}
|
||||||
141
syft/file/classifier.go
Normal file
141
syft/file/classifier.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"regexp"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultClassifiers = []Classifier{
|
||||||
|
{
|
||||||
|
Class: "python-binary",
|
||||||
|
FilepathPatterns: []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`(.*/|^)python(?P<version>[0-9]+\.[0-9]+)$`),
|
||||||
|
regexp.MustCompile(`(.*/|^)libpython(?P<version>[0-9]+\.[0-9]+).so.*$`),
|
||||||
|
},
|
||||||
|
EvidencePatternTemplates: []string{
|
||||||
|
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Class: "cpython-source",
|
||||||
|
FilepathPatterns: []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`(.*/|^)patchlevel.h$`),
|
||||||
|
},
|
||||||
|
EvidencePatternTemplates: []string{
|
||||||
|
`(?m)#define\s+PY_VERSION\s+"?(?P<version>[0-9\.\-_a-zA-Z]+)"?`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Class: "go-binary",
|
||||||
|
FilepathPatterns: []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`(.*/|^)go$`),
|
||||||
|
},
|
||||||
|
EvidencePatternTemplates: []string{
|
||||||
|
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Class: "go-binary-hint",
|
||||||
|
FilepathPatterns: []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`(.*/|^)VERSION$`),
|
||||||
|
},
|
||||||
|
EvidencePatternTemplates: []string{
|
||||||
|
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Class: "busybox-binary",
|
||||||
|
FilepathPatterns: []*regexp.Regexp{
|
||||||
|
regexp.MustCompile(`(.*/|^)busybox$`),
|
||||||
|
},
|
||||||
|
EvidencePatternTemplates: []string{
|
||||||
|
`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Classifier struct {
|
||||||
|
Class string
|
||||||
|
FilepathPatterns []*regexp.Regexp
|
||||||
|
EvidencePatternTemplates []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Classification struct {
|
||||||
|
Class string `json:"class"`
|
||||||
|
Metadata map[string]string `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Classifier) Classify(resolver source.FileResolver, location source.Location) (*Classification, error) {
|
||||||
|
doesFilepathMatch, filepathNamedGroupValues := filepathMatches(c.FilepathPatterns, location)
|
||||||
|
if !doesFilepathMatch {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contentReader, err := resolver.FileContentsByLocation(location)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer contentReader.Close()
|
||||||
|
|
||||||
|
// TODO: there is room for improvement here, as this may use an excessive amount of memory. Alternate approach is to leverage a RuneReader.
|
||||||
|
contents, err := ioutil.ReadAll(contentReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result *Classification
|
||||||
|
for _, patternTemplate := range c.EvidencePatternTemplates {
|
||||||
|
tmpl, err := template.New("").Parse(patternTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse classifier template=%q : %w", patternTemplate, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patternBuf := &bytes.Buffer{}
|
||||||
|
err = tmpl.Execute(patternBuf, filepathNamedGroupValues)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to render template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern, err := regexp.Compile(patternBuf.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to compile rendered regex=%q: %w", patternBuf.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pattern.Match(contents) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matchMetadata := internal.MatchNamedCaptureGroups(pattern, string(contents))
|
||||||
|
if result == nil {
|
||||||
|
result = &Classification{
|
||||||
|
Class: c.Class,
|
||||||
|
Metadata: matchMetadata,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for key, value := range matchMetadata {
|
||||||
|
result.Metadata[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filepathMatches(patterns []*regexp.Regexp, location source.Location) (bool, map[string]string) {
|
||||||
|
for _, path := range []string{location.RealPath, location.VirtualPath} {
|
||||||
|
if path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
if pattern.MatchString(path) {
|
||||||
|
return true, internal.MatchNamedCaptureGroups(pattern, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
90
syft/file/classifier_test.go
Normal file
90
syft/file/classifier_test.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilepathMatches(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
location source.Location
|
||||||
|
patterns []string
|
||||||
|
expectedMatches bool
|
||||||
|
expectedNamedGroups map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple-filename-match",
|
||||||
|
location: source.Location{
|
||||||
|
RealPath: "python2.7",
|
||||||
|
},
|
||||||
|
patterns: []string{
|
||||||
|
`python([0-9]+\.[0-9]+)$`,
|
||||||
|
},
|
||||||
|
expectedMatches: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filepath-match",
|
||||||
|
location: source.Location{
|
||||||
|
RealPath: "/usr/bin/python2.7",
|
||||||
|
},
|
||||||
|
patterns: []string{
|
||||||
|
`python([0-9]+\.[0-9]+)$`,
|
||||||
|
},
|
||||||
|
expectedMatches: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "virtual-filepath-match",
|
||||||
|
location: source.Location{
|
||||||
|
VirtualPath: "/usr/bin/python2.7",
|
||||||
|
},
|
||||||
|
patterns: []string{
|
||||||
|
`python([0-9]+\.[0-9]+)$`,
|
||||||
|
},
|
||||||
|
expectedMatches: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "full-filepath-match",
|
||||||
|
location: source.Location{
|
||||||
|
VirtualPath: "/usr/bin/python2.7",
|
||||||
|
},
|
||||||
|
patterns: []string{
|
||||||
|
`.*/bin/python([0-9]+\.[0-9]+)$`,
|
||||||
|
},
|
||||||
|
expectedMatches: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "anchored-filename-match-FAILS",
|
||||||
|
location: source.Location{
|
||||||
|
RealPath: "/usr/bin/python2.7",
|
||||||
|
},
|
||||||
|
patterns: []string{
|
||||||
|
`^python([0-9]+\.[0-9]+)$`,
|
||||||
|
},
|
||||||
|
expectedMatches: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty-filename-match-FAILS",
|
||||||
|
location: source.Location{},
|
||||||
|
patterns: []string{
|
||||||
|
`^python([0-9]+\.[0-9]+)$`,
|
||||||
|
},
|
||||||
|
expectedMatches: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
var patterns []*regexp.Regexp
|
||||||
|
for _, p := range test.patterns {
|
||||||
|
patterns = append(patterns, regexp.MustCompile(p))
|
||||||
|
}
|
||||||
|
actualMatches, actualNamedGroups := filepathMatches(patterns, test.location)
|
||||||
|
assert.Equal(t, test.expectedMatches, actualMatches)
|
||||||
|
assert.Equal(t, test.expectedNamedGroups, actualNamedGroups)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
1
syft/file/test-fixtures/classifiers/negative/.gitignore
vendored
Normal file
1
syft/file/test-fixtures/classifiers/negative/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!libpython2.7.so
|
||||||
1
syft/file/test-fixtures/classifiers/negative/busybox
Normal file
1
syft/file/test-fixtures/classifiers/negative/busybox
Normal file
@ -0,0 +1 @@
|
|||||||
|
another bad binary
|
||||||
1
syft/file/test-fixtures/classifiers/negative/go
Normal file
1
syft/file/test-fixtures/classifiers/negative/go
Normal file
@ -0,0 +1 @@
|
|||||||
|
a bad go binary
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
# note: this should NOT match
|
||||||
|
|
||||||
|
DO NOT DETECT
|
||||||
3
syft/file/test-fixtures/classifiers/negative/python2.6
Normal file
3
syft/file/test-fixtures/classifiers/negative/python2.6
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# note: this should NOT match
|
||||||
|
|
||||||
|
just some noise
|
||||||
1
syft/file/test-fixtures/classifiers/positive/.gitignore
vendored
Normal file
1
syft/file/test-fixtures/classifiers/positive/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
!libpython3.7.so
|
||||||
1
syft/file/test-fixtures/classifiers/positive/VERSION
Normal file
1
syft/file/test-fixtures/classifiers/positive/VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
go1.15-beta2
|
||||||
3
syft/file/test-fixtures/classifiers/positive/busybox
Normal file
3
syft/file/test-fixtures/classifiers/positive/busybox
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# note: this SHOULD match as busybox 3.33.3
|
||||||
|
|
||||||
|
noise!BusyBox v3.33.3!noise
|
||||||
1
syft/file/test-fixtures/classifiers/positive/go
Normal file
1
syft/file/test-fixtures/classifiers/positive/go
Normal file
@ -0,0 +1 @@
|
|||||||
|
go1.14
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
# note: this SHOULD match as python 3.7
|
||||||
|
noise3.7.4a-vZ9!morenoise
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
# note: this SHOULD match as python 3.9
|
||||||
|
|
||||||
|
some source code...
|
||||||
|
|
||||||
|
#define PY_VERSION 3.9-aZ5
|
||||||
|
|
||||||
|
more source!
|
||||||
3
syft/file/test-fixtures/classifiers/positive/python3.6
Normal file
3
syft/file/test-fixtures/classifiers/positive/python3.6
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# note: this SHOULD match as python 3.6
|
||||||
|
|
||||||
|
noise3.6.3a-vZ9!morenoise
|
||||||
@ -21,7 +21,7 @@ func parseLicensesFromCopyright(reader io.Reader) []string {
|
|||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
|
|
||||||
matchesByGroup := internal.MatchCaptureGroups(licensePattern, line)
|
matchesByGroup := internal.MatchNamedCaptureGroups(licensePattern, line)
|
||||||
if len(matchesByGroup) > 0 {
|
if len(matchesByGroup) > 0 {
|
||||||
candidate, ok := matchesByGroup["license"]
|
candidate, ok := matchesByGroup["license"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@ -145,7 +145,7 @@ func extractAllFields(reader *bufio.Reader) (map[string]interface{}, error) {
|
|||||||
// of the "<name>" form, then return name and nil
|
// of the "<name>" form, then return name and nil
|
||||||
func extractSourceVersion(source string) (string, string) {
|
func extractSourceVersion(source string) (string, string) {
|
||||||
// special handling for the Source field since it has formatted data
|
// special handling for the Source field since it has formatted data
|
||||||
match := internal.MatchCaptureGroups(sourceRegexp, source)
|
match := internal.MatchNamedCaptureGroups(sourceRegexp, source)
|
||||||
return match["name"], match["version"]
|
return match["name"], match["version"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -63,7 +63,7 @@ func (a *Author) UnmarshalJSON(b []byte) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// parse out "name <email> (url)" into an Author struct
|
// parse out "name <email> (url)" into an Author struct
|
||||||
fields = internal.MatchCaptureGroups(authorPattern, authorStr)
|
fields = internal.MatchNamedCaptureGroups(authorPattern, authorStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// translate the map into a structure
|
// translate the map into a structure
|
||||||
|
|||||||
@ -77,7 +77,7 @@ func parseGemSpecEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for field, pattern := range patterns {
|
for field, pattern := range patterns {
|
||||||
matchMap := internal.MatchCaptureGroups(pattern, sanitizedLine)
|
matchMap := internal.MatchNamedCaptureGroups(pattern, sanitizedLine)
|
||||||
if value := matchMap[field]; value != "" {
|
if value := matchMap[field]; value != "" {
|
||||||
if postProcessor := postProcessors[field]; postProcessor != nil {
|
if postProcessor := postProcessors[field]; postProcessor != nil {
|
||||||
fields[field] = postProcessor(value)
|
fields[field] = postProcessor(value)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user