Remove MetadataType from core package object and normalize JSON metadataType values (#1983)

* [wip]

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* distinct the package metadata functions

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* remove metadata type from package core model

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* incorporate review feedback for names

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add RPM archive metadata and split parser helpers

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* clarify the python package metadata type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rename the KB metadata type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* break hackage and composer types by use case

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* linting fix

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix encoding and decoding for syft-json and cyclonedx

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump json schema to 11

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update cyclonedx-json snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update cyclonedx-xml snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update spdx-json snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update spdx-tv snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update syft-json snapshots

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* correct metadata type in stack yaml parser test

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix bom-ref redactor for cyclonedx-xml

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add tests for legacy package metadata names

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* regenerate json schema v11

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix legacy HackageMetadataType reflect type value check

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix linting

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* packagemetadata discovery should account for type shadowing

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix linting

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix cli tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* bump json schema version to v12

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* update json schema to incorporate changes from main

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add syft-json legacy config option

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add tests around v11-v12 json decoding

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add docs for SYFT_JSON_LEGACY

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* rename structs to be compliant with new naming scheme

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman 2023-10-30 12:12:04 -04:00 committed by GitHub
parent f442586ec9
commit 1aaa644007
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
261 changed files with 7010 additions and 4040 deletions

View File

@ -151,6 +151,39 @@ sequenceDiagram
Note right of catalog: cataloger configuration is done based on src Note right of catalog: cataloger configuration is done based on src
``` ```
### Package object
The `pkg.Package` object is a core data structure that represents a software package. Fields like `name` and `version` probably don't need
a detailed explanation, but some of the other fields are worth a quick overview:
- `FoundBy`: the name of the cataloger that discovered this package (e.g. `python-pip-cataloger`).
- `Locations`: these are the set of paths and layer ids that were parsed to discover this package (e.g. `python-pip-cataloger`).
- `Language`: the language of the package (e.g. `python`).
- `Type`: this is a high-level categorization of the ecosystem the package resides in. For instance, even if the package is a egg, wheel, or requirements.txt reference, it is still logically a "python" package. Not all package types align with a language (e.g. `rpm`) but it is common.
- `Metadata`: specialized data for specific location(s) parsed. We should try and raise up as much raw information that seems useful. As a rule of thumb the object here should be as flat as possible and use the raw names and values from the underlying source material parsed.
When `pkg.Package` is serialized an additional `MetadataType` is shown. This is a label that helps consumers understand the datashape of the `Metadata` field.
By convention the `MetadataType` value should follow these rules of thumb:
- Only use lowercase letters, numbers, and hyphens. Use hyphens to separate words.
- **Try to anchor the name in the ecosystem, language, or packaging tooling it belongs to**. For a package manager for a language ecosystem the language, framework or runtime should be used as a prefix. For instance `pubspec-lock` is an OK name, but `dart-pubspec-lock` is better. For an OS package manager this is not necessary (e.g. `apk-db-entry` is a good name, but `alpine-apk-db-entry` is not since `alpine` and the `a` in `apk` is redundant).
- **Be as specific as possible to what the data represents**. For instance `ruby-gem` is NOT a good `MetadataType` value, but `ruby-gemspec` is. Why? Ruby gem information can come from a gemspec file or a Gemfile.lock, which are very different. The latter name provides more context as to what to expect.
- **Should describe WHAT the data is, NOT HOW it's used**. For instance `r-description-installed-file` is NOT a good `MetadataType` value since it's trying to convey that we use the DESCRIPTION file in the R ecosystem to detect installed packages. Instead simply describe what the DESCRIPTION file is itself without context of how it's used: `r-description`.
- **Use the `lock` suffix** to distinct between manifest files that loosely describe package version requirements vs files that strongly specify one and only one version of a package ("lock" files). These should only be used with respect to package managers that have the guide and lock distinction, but would not be appropriate otherwise (e.g. `rpm` does not have a guide vs lock, so `lock` should NOT be used to describe a db entry).
- **Use the `archive` suffix to indicate a package archive** (e.g. rpm file, apk file, etc) that describes the contents of the package. For example an RPM file that was cataloged would have a `rpm-archive` metadata type (not to be confused with an RPM DB record entry which would be `rpm-db-entry`).
- **Use the `entry` suffix** to indicate information about a package that was found as a single entry within file that has multiple package entries. If the entry was found within a DB or a flat-file store for an OS package manager, you should use `db-entry`.
- **Should NOT contain the phrase `package`**, though exceptions are allowed (say if the canonical name literally has the phrase package in it).
- **Should NOT contain have a `file` suffix** unless the canonical name has the term "file", such as a `pipfile` or `gemfile`. An example of a bad name for this rule is`ruby-gemspec-file`; a better name would be `ruby-gemspec`.
- **Should NOT contain the exact filename+extensions**. For instance `pipfile.lock` shouldn't really be in the name, instead try and describe what the file is: `python-pipfile-lock` (but shouldn't this be `python-pip-lock` you might ask? No, since the `pip` package manger is not related to the `pipfile` project).
- **Should NOT contain the phrase `metadata`**, unless the canonical name has this term.
- **Should represent a single use case**. For example, trying to describe Hackage metadata with a single `HackageMetadata` struct (and thus `MetadataType`) is not allowed since it represents 3 mutually exclusive use cases: representing a `stack.yaml`, `stack.lock`, or `cabal.project` file. Instead, each of these should have their own struct types and `MetadataType` values.
There are other cases that are not covered by these rules... and that's ok! The goal is to provide a consistent naming scheme that is easy to understand and use when it's applicable. If the rules do not exactly apply in your situation then just use your best judgement (or amend these rules as needed whe new common cases come up).
What if the underlying parsed data represents multiple files? There are two approaches to this:
- use the primary file to represent all the data. For instance, though the `dpkg-cataloger` looks at multiple files to get all information about a package, it's the `status` file that gets represented.
- nest each individual file's data under the `Metadata` field. For instance, the `java-archive-cataloger` may find information from on or all of the files: `pom.xml`, `pom.properties`, and `MANIFEST.MF`. However, the metadata is simply `java-metadata' with each possibility as a nested optional field.
### Syft Catalogers ### Syft Catalogers

View File

@ -530,6 +530,31 @@ platform: ""
# - spm-cataloger # - spm-cataloger
catalogers: catalogers:
# all format configuration
format:
# all syft-json format options
json:
# transform any syft-json output to conform to an approximation of the v11.0.1 schema. This includes:
# - using the package metadata type names from before v12 of the JSON schema (changed in https://github.com/anchore/syft/pull/1983)
#
# Note: this will still include package types and fields that were added at or after json schema v12. This means
# that output might not strictly be json schema v11 compliant, however, for consumers that require time to port
# over to the final syft 1.0 json output this option can be used to ease the transition.
#
# Note: long term support for this option is not guaranteed.
legacy: false
# all template format options
template:
# path to the template file to use when rendering the output with the `template` output format.
# Note that all template paths are based on the current syft-json schema.
# same as -t ; SYFT_TEMPLATE_PATH env var
path: ""
# cataloging packages is exposed through the packages and power-user subcommands # cataloging packages is exposed through the packages and power-user subcommands
package: package:

View File

@ -365,7 +365,7 @@ tasks:
generate-json-schema: generate-json-schema:
desc: Generate a new JSON schema desc: Generate a new JSON schema
cmds: cmds:
- "cd syft/internal && go generate . && cd jsonschema && go run ." - "cd syft/internal && go generate . && cd jsonschema && go run . && go fmt ../..."
generate-license-list: generate-license-list:
desc: Generate an updated license processing code off of the latest available SPDX license list desc: Generate an updated license processing code off of the latest available SPDX license list

View File

@ -59,13 +59,14 @@ func Attest(app clio.Application) *cobra.Command {
OutputFile: options.OutputFile{ // nolint:staticcheck OutputFile: options.OutputFile{ // nolint:staticcheck
Enabled: false, // explicitly not allowed Enabled: false, // explicitly not allowed
}, },
OutputTemplate: options.OutputTemplate{ Format: options.DefaultFormat(),
Enabled: false, // explicitly not allowed
},
}, },
Catalog: options.DefaultCatalog(), Catalog: options.DefaultCatalog(),
} }
// template format explicitly not allowed
opts.Format.Template.Enabled = false
return app.SetupCommand(&cobra.Command{ return app.SetupCommand(&cobra.Command{
Use: "attest --output [FORMAT] <IMAGE>", Use: "attest --output [FORMAT] <IMAGE>",
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image", Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",

View File

@ -0,0 +1,45 @@
package options
import (
"github.com/anchore/syft/syft/format/cyclonedxjson"
"github.com/anchore/syft/syft/format/cyclonedxxml"
"github.com/anchore/syft/syft/format/github"
"github.com/anchore/syft/syft/format/spdxjson"
"github.com/anchore/syft/syft/format/spdxtagvalue"
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/format/table"
"github.com/anchore/syft/syft/format/template"
"github.com/anchore/syft/syft/format/text"
"github.com/anchore/syft/syft/sbom"
)
// Format contains all user configuration for output formatting.
type Format struct {
Template FormatTemplate `yaml:"template" json:"template" mapstructure:"template"`
JSON FormatJSON `yaml:"json" json:"json" mapstructure:"json"`
}
func DefaultFormat() Format {
return Format{
Template: DefaultFormatTemplate(),
JSON: DefaultFormatJSON(),
}
}
func (o *Format) Encoders() ([]sbom.FormatEncoder, error) {
// setup all encoders based on the configuration
var list encoderList
// in the future there will be application configuration options that can be used to set the default output format
list.addWithErr(template.ID)(o.Template.formatEncoders())
list.addWithErr(syftjson.ID)(o.JSON.formatEncoders())
list.add(table.ID)(table.NewFormatEncoder())
list.add(text.ID)(text.NewFormatEncoder())
list.add(github.ID)(github.NewFormatEncoder())
list.addWithErr(cyclonedxxml.ID)(cycloneDxXMLEncoders())
list.addWithErr(cyclonedxjson.ID)(cycloneDxJSONEncoders())
list.addWithErr(spdxjson.ID)(spdxJSONEncoders())
list.addWithErr(spdxtagvalue.ID)(spdxTagValueEncoders())
return list.encoders, list.err
}

View File

@ -0,0 +1,25 @@
package options
import (
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/sbom"
)
type FormatJSON struct {
Legacy bool `yaml:"legacy" json:"legacy" mapstructure:"legacy"`
}
func DefaultFormatJSON() FormatJSON {
return FormatJSON{
Legacy: false,
}
}
func (o FormatJSON) formatEncoders() ([]sbom.FormatEncoder, error) {
enc, err := syftjson.NewFormatEncoderWithConfig(
syftjson.EncoderConfig{
Legacy: o.Legacy,
},
)
return []sbom.FormatEncoder{enc}, err
}

View File

@ -6,21 +6,27 @@ import (
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
) )
var _ clio.FlagAdder = (*OutputTemplate)(nil) var _ clio.FlagAdder = (*FormatTemplate)(nil)
type OutputTemplate struct { type FormatTemplate struct {
Enabled bool `yaml:"-" json:"-" mapstructure:"-"` Enabled bool `yaml:"-" json:"-" mapstructure:"-"`
Path string `yaml:"path" json:"path" mapstructure:"path"` // -t template file to use for output Path string `yaml:"path" json:"path" mapstructure:"path"` // -t template file to use for output
} }
func (o *OutputTemplate) AddFlags(flags clio.FlagSet) { func DefaultFormatTemplate() FormatTemplate {
return FormatTemplate{
Enabled: true,
}
}
func (o *FormatTemplate) AddFlags(flags clio.FlagSet) {
if o.Enabled { if o.Enabled {
flags.StringVarP(&o.Path, "template", "t", flags.StringVarP(&o.Path, "template", "t",
"specify the path to a Go template file") "specify the path to a Go template file")
} }
} }
func (o OutputTemplate) formatEncoders() ([]sbom.FormatEncoder, error) { func (o FormatTemplate) formatEncoders() ([]sbom.FormatEncoder, error) {
if !o.Enabled { if !o.Enabled {
return nil, nil return nil, nil
} }

View File

@ -32,7 +32,7 @@ type Output struct {
AllowMultipleOutputs bool `yaml:"-" json:"-" mapstructure:"-"` AllowMultipleOutputs bool `yaml:"-" json:"-" mapstructure:"-"`
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
OutputFile `yaml:",inline" json:"" mapstructure:",squash"` OutputFile `yaml:",inline" json:"" mapstructure:",squash"`
OutputTemplate `yaml:"template" json:"template" mapstructure:"template"` Format `yaml:"format" json:"format" mapstructure:"format"`
} }
func DefaultOutput() Output { func DefaultOutput() Output {
@ -42,9 +42,7 @@ func DefaultOutput() Output {
OutputFile: OutputFile{ OutputFile: OutputFile{
Enabled: true, Enabled: true,
}, },
OutputTemplate: OutputTemplate{ Format: DefaultFormat(),
Enabled: true,
},
} }
} }
@ -68,7 +66,7 @@ func (o Output) SBOMWriter() (sbom.Writer, error) {
usesTemplateOutput := names.Has(string(template.ID)) usesTemplateOutput := names.Has(string(template.ID))
if usesTemplateOutput && o.OutputTemplate.Path == "" { if usesTemplateOutput && o.Format.Template.Path == "" {
return nil, fmt.Errorf(`must specify path to template file when using "template" output format`) return nil, fmt.Errorf(`must specify path to template file when using "template" output format`)
} }
@ -80,24 +78,6 @@ func (o Output) SBOMWriter() (sbom.Writer, error) {
return makeSBOMWriter(o.Outputs, o.File, encoders) return makeSBOMWriter(o.Outputs, o.File, encoders)
} }
func (o *Output) Encoders() ([]sbom.FormatEncoder, error) {
// setup all encoders based on the configuration
var list encoderList
// in the future there will be application configuration options that can be used to set the default output format
list.addWithErr(template.ID)(o.OutputTemplate.formatEncoders())
list.add(syftjson.ID)(syftjson.NewFormatEncoder())
list.add(table.ID)(table.NewFormatEncoder())
list.add(text.ID)(text.NewFormatEncoder())
list.add(github.ID)(github.NewFormatEncoder())
list.addWithErr(cyclonedxxml.ID)(cycloneDxXMLEncoders())
list.addWithErr(cyclonedxjson.ID)(cycloneDxJSONEncoders())
list.addWithErr(spdxjson.ID)(spdxJSONEncoders())
list.addWithErr(spdxtagvalue.ID)(spdxTagValueEncoders())
return list.encoders, list.err
}
func (o Output) OutputNameSet() *strset.Set { func (o Output) OutputNameSet() *strset.Set {
names := strset.New() names := strset.New()
for _, output := range o.Outputs { for _, output := range o.Outputs {

View File

@ -27,7 +27,7 @@ func Test_getEncoders(t *testing.T) {
} }
opts := DefaultOutput() opts := DefaultOutput()
opts.OutputTemplate.Path = "somewhere" opts.Format.Template.Path = "somewhere"
encoders, err := opts.Encoders() encoders, err := opts.Encoders()
require.NoError(t, err) require.NoError(t, err)
@ -158,7 +158,7 @@ func Test_EncoderCollection_ByString_IDOnly_Defaults(t *testing.T) {
} }
opts := DefaultOutput() opts := DefaultOutput()
opts.OutputTemplate.Path = "somewhere" opts.Format.Template.Path = "somewhere"
defaultEncoders, err := opts.Encoders() defaultEncoders, err := opts.Encoders()
require.NoError(t, err) require.NoError(t, err)

View File

@ -3,5 +3,5 @@ package internal
const ( const (
// JSONSchemaVersion is the current schema version output by the JSON encoder // JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "11.0.1" JSONSchemaVersion = "12.0.0"
) )

File diff suppressed because it is too large Load Diff

View File

@ -10,9 +10,9 @@ import (
func encodeAuthor(p pkg.Package) string { func encodeAuthor(p pkg.Package) string {
if hasMetadata(p) { if hasMetadata(p) {
switch metadata := p.Metadata.(type) { switch metadata := p.Metadata.(type) {
case pkg.NpmPackageJSONMetadata: case pkg.NpmPackage:
return metadata.Author return metadata.Author
case pkg.PythonPackageMetadata: case pkg.PythonPackage:
author := metadata.Author author := metadata.Author
if metadata.AuthorEmail != "" { if metadata.AuthorEmail != "" {
if author == "" { if author == "" {
@ -21,7 +21,7 @@ func encodeAuthor(p pkg.Package) string {
author += fmt.Sprintf(" <%s>", metadata.AuthorEmail) author += fmt.Sprintf(" <%s>", metadata.AuthorEmail)
} }
return author return author
case pkg.GemMetadata: case pkg.RubyGemspec:
if len(metadata.Authors) > 0 { if len(metadata.Authors) > 0 {
return strings.Join(metadata.Authors, ",") return strings.Join(metadata.Authors, ",")
} }
@ -33,15 +33,15 @@ func encodeAuthor(p pkg.Package) string {
func decodeAuthor(author string, metadata interface{}) { func decodeAuthor(author string, metadata interface{}) {
switch meta := metadata.(type) { switch meta := metadata.(type) {
case *pkg.NpmPackageJSONMetadata: case *pkg.NpmPackage:
meta.Author = author meta.Author = author
case *pkg.PythonPackageMetadata: case *pkg.PythonPackage:
parts := strings.SplitN(author, " <", 2) parts := strings.SplitN(author, " <", 2)
meta.Author = parts[0] meta.Author = parts[0]
if len(parts) > 1 { if len(parts) > 1 {
meta.AuthorEmail = strings.TrimSuffix(parts[1], ">") meta.AuthorEmail = strings.TrimSuffix(parts[1], ">")
} }
case *pkg.GemMetadata: case *pkg.RubyGemspec:
meta.Authors = strings.Split(author, ",") meta.Authors = strings.Split(author, ",")
} }
} }

View File

@ -23,7 +23,7 @@ func Test_encodeAuthor(t *testing.T) {
{ {
name: "from gem", name: "from gem",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.GemMetadata{ Metadata: pkg.RubyGemspec{
Authors: []string{ Authors: []string{
"auth1", "auth1",
"auth2", "auth2",
@ -35,7 +35,7 @@ func Test_encodeAuthor(t *testing.T) {
{ {
name: "from npm", name: "from npm",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Author: "auth", Author: "auth",
}, },
}, },
@ -44,7 +44,7 @@ func Test_encodeAuthor(t *testing.T) {
{ {
name: "from python - just name", name: "from python - just name",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
Author: "auth", Author: "auth",
}, },
}, },
@ -53,7 +53,7 @@ func Test_encodeAuthor(t *testing.T) {
{ {
name: "from python - just email", name: "from python - just email",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
AuthorEmail: "auth@auth.gov", AuthorEmail: "auth@auth.gov",
}, },
}, },
@ -62,7 +62,7 @@ func Test_encodeAuthor(t *testing.T) {
{ {
name: "from python - both name and email", name: "from python - both name and email",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
Author: "auth", Author: "auth",
AuthorEmail: "auth@auth.gov", AuthorEmail: "auth@auth.gov",
}, },
@ -73,7 +73,7 @@ func Test_encodeAuthor(t *testing.T) {
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION // note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "empty", name: "empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Author: "", Author: "",
}, },
}, },

View File

@ -8,11 +8,21 @@ import (
"github.com/anchore/packageurl-go" "github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/format/common" "github.com/anchore/syft/syft/format/common"
"github.com/anchore/syft/syft/internal/packagemetadata"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
func encodeComponent(p pkg.Package) cyclonedx.Component { func encodeComponent(p pkg.Package) cyclonedx.Component {
props := encodeProperties(p, "syft:package") props := encodeProperties(p, "syft:package")
if p.Metadata != nil {
// encode the metadataType as a property, something that doesn't exist on the core model
props = append(props, cyclonedx.Property{
Name: "syft:package:metadataType",
Value: packagemetadata.JSONName(p.Metadata),
})
}
props = append(props, encodeCPEs(p)...) props = append(props, encodeCPEs(p)...)
locations := p.Locations.ToSlice() locations := p.Locations.ToSlice()
if len(locations) > 0 { if len(locations) > 0 {
@ -85,9 +95,9 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
common.DecodeInto(p, values, "syft:package", CycloneDXFields) common.DecodeInto(p, values, "syft:package", CycloneDXFields)
p.MetadataType = pkg.CleanMetadataType(p.MetadataType) metadataType := values["syft:package:metadataType"]
p.Metadata = decodePackageMetadata(values, c, p.MetadataType) p.Metadata = decodePackageMetadata(values, c, metadataType)
if p.Type == "" { if p.Type == "" {
p.Type = pkg.TypeFromPURL(p.PURL) p.Type = pkg.TypeFromPURL(p.PURL)
@ -109,13 +119,13 @@ func decodeLocations(vals map[string]string) file.LocationSet {
return file.NewLocationSet(out...) return file.NewLocationSet(out...)
} }
func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typ pkg.MetadataType) interface{} { func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typeName string) interface{} {
if typ != "" && c.Properties != nil { if typeName != "" && c.Properties != nil {
metaTyp, ok := pkg.MetadataTypeByName[typ] metadataType := packagemetadata.ReflectTypeFromJSONName(typeName)
if !ok { if metadataType == nil {
return nil return nil
} }
metaPtrTyp := reflect.PtrTo(metaTyp) metaPtrTyp := reflect.PtrTo(metadataType)
metaPtr := common.Decode(metaPtrTyp, vals, "syft:metadata", CycloneDXFields) metaPtr := common.Decode(metaPtrTyp, vals, "syft:metadata", CycloneDXFields)
// Map all explicit metadata properties // Map all explicit metadata properties

View File

@ -17,7 +17,7 @@ func Test_encodeComponentProperties(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input pkg.Package input pkg.Package
expected *[]cyclonedx.Property expected []cyclonedx.Property
}{ }{
{ {
name: "no metadata", name: "no metadata",
@ -31,7 +31,7 @@ func Test_encodeComponentProperties(t *testing.T) {
Locations: file.NewLocationSet( Locations: file.NewLocationSet(
file.NewLocationFromCoordinates(file.Coordinates{RealPath: "test"}), file.NewLocationFromCoordinates(file.Coordinates{RealPath: "test"}),
), ),
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "libc-utils", Package: "libc-utils",
OriginPackage: "libc-dev", OriginPackage: "libc-dev",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>", Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
@ -48,8 +48,9 @@ func Test_encodeComponentProperties(t *testing.T) {
Files: []pkg.ApkFileRecord{}, Files: []pkg.ApkFileRecord{},
}, },
}, },
expected: &[]cyclonedx.Property{ expected: []cyclonedx.Property{
{Name: "syft:package:foundBy", Value: "cataloger"}, {Name: "syft:package:foundBy", Value: "cataloger"},
{Name: "syft:package:metadataType", Value: "apk-db-entry"},
{Name: "syft:location:0:path", Value: "test"}, {Name: "syft:location:0:path", Value: "test"},
{Name: "syft:metadata:gitCommitOfApkPort", Value: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479"}, {Name: "syft:metadata:gitCommitOfApkPort", Value: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479"},
{Name: "syft:metadata:installedSize", Value: "4096"}, {Name: "syft:metadata:installedSize", Value: "4096"},
@ -63,8 +64,7 @@ func Test_encodeComponentProperties(t *testing.T) {
{ {
name: "from dpkg", name: "from dpkg",
input: pkg.Package{ input: pkg.Package{
MetadataType: pkg.DpkgMetadataType, Metadata: pkg.DpkgDBEntry{
Metadata: pkg.DpkgMetadata{
Package: "tzdata", Package: "tzdata",
Version: "2020a-0+deb10u1", Version: "2020a-0+deb10u1",
Source: "tzdata-dev", Source: "tzdata-dev",
@ -75,8 +75,8 @@ func Test_encodeComponentProperties(t *testing.T) {
Files: []pkg.DpkgFileRecord{}, Files: []pkg.DpkgFileRecord{},
}, },
}, },
expected: &[]cyclonedx.Property{ expected: []cyclonedx.Property{
{Name: "syft:package:metadataType", Value: "DpkgMetadata"}, {Name: "syft:package:metadataType", Value: "dpkg-db-entry"},
{Name: "syft:metadata:installedSize", Value: "3036"}, {Name: "syft:metadata:installedSize", Value: "3036"},
{Name: "syft:metadata:source", Value: "tzdata-dev"}, {Name: "syft:metadata:source", Value: "tzdata-dev"},
{Name: "syft:metadata:sourceVersion", Value: "1.0"}, {Name: "syft:metadata:sourceVersion", Value: "1.0"},
@ -89,16 +89,15 @@ func Test_encodeComponentProperties(t *testing.T) {
Version: "v0.0.0-20211006190231-62292e806868", Version: "v0.0.0-20211006190231-62292e806868",
Language: pkg.Go, Language: pkg.Go,
Type: pkg.GoModulePkg, Type: pkg.GoModulePkg,
MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinaryBuildinfoEntry{
Metadata: pkg.GolangBinMetadata{
GoCompiledVersion: "1.17", GoCompiledVersion: "1.17",
Architecture: "amd64", Architecture: "amd64",
H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=", H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=",
}, },
}, },
expected: &[]cyclonedx.Property{ expected: []cyclonedx.Property{
{Name: "syft:package:language", Value: pkg.Go.String()}, {Name: "syft:package:language", Value: pkg.Go.String()},
{Name: "syft:package:metadataType", Value: "GolangBinMetadata"}, {Name: "syft:package:metadataType", Value: "go-module-buildinfo-entry"},
{Name: "syft:package:type", Value: "go-module"}, {Name: "syft:package:type", Value: "go-module"},
{Name: "syft:metadata:architecture", Value: "amd64"}, {Name: "syft:metadata:architecture", Value: "amd64"},
{Name: "syft:metadata:goCompiledVersion", Value: "1.17"}, {Name: "syft:metadata:goCompiledVersion", Value: "1.17"},
@ -112,14 +111,13 @@ func Test_encodeComponentProperties(t *testing.T) {
Version: "v0.0.0-20211006190231-62292e806868", Version: "v0.0.0-20211006190231-62292e806868",
Language: pkg.Go, Language: pkg.Go,
Type: pkg.GoModulePkg, Type: pkg.GoModulePkg,
MetadataType: pkg.GolangModMetadataType, Metadata: pkg.GolangModuleEntry{
Metadata: pkg.GolangModMetadata{
H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=", H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=",
}, },
}, },
expected: &[]cyclonedx.Property{ expected: []cyclonedx.Property{
{Name: "syft:package:language", Value: pkg.Go.String()}, {Name: "syft:package:language", Value: pkg.Go.String()},
{Name: "syft:package:metadataType", Value: "GolangModMetadata"}, {Name: "syft:package:metadataType", Value: "go-module-entry"},
{Name: "syft:package:type", Value: "go-module"}, {Name: "syft:package:type", Value: "go-module"},
{Name: "syft:metadata:h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="}, {Name: "syft:metadata:h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="},
}, },
@ -130,8 +128,7 @@ func Test_encodeComponentProperties(t *testing.T) {
Name: "dive", Name: "dive",
Version: "0.9.2-1", Version: "0.9.2-1",
Type: pkg.RpmPkg, Type: pkg.RpmPkg,
MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmDBEntry{
Metadata: pkg.RpmMetadata{
Name: "dive", Name: "dive",
Epoch: &epoch, Epoch: &epoch,
Arch: "x86_64", Arch: "x86_64",
@ -140,11 +137,11 @@ func Test_encodeComponentProperties(t *testing.T) {
SourceRpm: "dive-0.9.2-1.src.rpm", SourceRpm: "dive-0.9.2-1.src.rpm",
Size: 12406784, Size: 12406784,
Vendor: "", Vendor: "",
Files: []pkg.RpmdbFileRecord{}, Files: []pkg.RpmFileRecord{},
}, },
}, },
expected: &[]cyclonedx.Property{ expected: []cyclonedx.Property{
{Name: "syft:package:metadataType", Value: "RpmMetadata"}, {Name: "syft:package:metadataType", Value: "rpm-db-entry"},
{Name: "syft:package:type", Value: "rpm"}, {Name: "syft:package:type", Value: "rpm"},
{Name: "syft:metadata:epoch", Value: "2"}, {Name: "syft:metadata:epoch", Value: "2"},
{Name: "syft:metadata:release", Value: "1"}, {Name: "syft:metadata:release", Value: "1"},
@ -156,7 +153,13 @@ func Test_encodeComponentProperties(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
c := encodeComponent(test.input) c := encodeComponent(test.input)
assert.Equal(t, test.expected, c.Properties) if test.expected == nil {
if c.Properties != nil {
t.Fatalf("expected no properties, got: %+v", *c.Properties)
}
return
}
assert.ElementsMatch(t, test.expected, *c.Properties)
}) })
} }
} }
@ -271,8 +274,7 @@ func Test_decodeComponent(t *testing.T) {
name string name string
component cyclonedx.Component component cyclonedx.Component
wantLanguage pkg.Language wantLanguage pkg.Language
wantMetadataType pkg.MetadataType wantMetadata any
wantMetadata interface{}
}{ }{
{ {
name: "derive language from pURL if missing", name: "derive language from pURL if missing",
@ -300,8 +302,7 @@ func Test_decodeComponent(t *testing.T) {
}, },
}, },
}, },
wantMetadataType: pkg.RpmMetadataType, wantMetadata: pkg.RpmDBEntry{},
wantMetadata: pkg.RpmMetadata{},
}, },
{ {
name: "handle RpmdbMetadata type with properties", name: "handle RpmdbMetadata type with properties",
@ -314,7 +315,7 @@ func Test_decodeComponent(t *testing.T) {
Properties: &[]cyclonedx.Property{ Properties: &[]cyclonedx.Property{
{ {
Name: "syft:package:metadataType", Name: "syft:package:metadataType",
Value: "RpmMetadata", Value: "RpmDBMetadata",
}, },
{ {
Name: "syft:metadata:release", Name: "syft:metadata:release",
@ -322,8 +323,7 @@ func Test_decodeComponent(t *testing.T) {
}, },
}, },
}, },
wantMetadataType: pkg.RpmMetadataType, wantMetadata: pkg.RpmDBEntry{
wantMetadata: pkg.RpmMetadata{
Release: "some-release", Release: "some-release",
}, },
}, },
@ -335,12 +335,12 @@ func Test_decodeComponent(t *testing.T) {
if tt.wantLanguage != "" { if tt.wantLanguage != "" {
assert.Equal(t, tt.wantLanguage, p.Language) assert.Equal(t, tt.wantLanguage, p.Language)
} }
if tt.wantMetadataType != "" {
assert.Equal(t, tt.wantMetadataType, p.MetadataType)
}
if tt.wantMetadata != nil { if tt.wantMetadata != nil {
assert.Truef(t, reflect.DeepEqual(tt.wantMetadata, p.Metadata), "metadata should match: %+v != %+v", tt.wantMetadata, p.Metadata) assert.Truef(t, reflect.DeepEqual(tt.wantMetadata, p.Metadata), "metadata should match: %+v != %+v", tt.wantMetadata, p.Metadata)
} }
if tt.wantMetadata == nil && tt.wantLanguage == "" {
t.Fatal("this is a useless test, please remove it")
}
}) })
} }
} }

View File

@ -5,9 +5,9 @@ import "github.com/anchore/syft/syft/pkg"
func encodeDescription(p pkg.Package) string { func encodeDescription(p pkg.Package) string {
if hasMetadata(p) { if hasMetadata(p) {
switch metadata := p.Metadata.(type) { switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata: case pkg.ApkDBEntry:
return metadata.Description return metadata.Description
case pkg.NpmPackageJSONMetadata: case pkg.NpmPackage:
return metadata.Description return metadata.Description
} }
} }
@ -16,9 +16,9 @@ func encodeDescription(p pkg.Package) string {
func decodeDescription(description string, metadata interface{}) { func decodeDescription(description string, metadata interface{}) {
switch meta := metadata.(type) { switch meta := metadata.(type) {
case *pkg.ApkMetadata: case *pkg.ApkDBEntry:
meta.Description = description meta.Description = description
case *pkg.NpmPackageJSONMetadata: case *pkg.NpmPackage:
meta.Description = description meta.Description = description
} }
} }

View File

@ -23,7 +23,7 @@ func Test_encodeDescription(t *testing.T) {
{ {
name: "from apk", name: "from apk",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Description: "a description!", Description: "a description!",
}, },
}, },
@ -32,7 +32,7 @@ func Test_encodeDescription(t *testing.T) {
{ {
name: "from npm", name: "from npm",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Description: "a description!", Description: "a description!",
}, },
}, },
@ -42,7 +42,7 @@ func Test_encodeDescription(t *testing.T) {
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION // note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "empty", name: "empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Homepage: "", Homepage: "",
}, },
}, },

View File

@ -19,21 +19,21 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
// Skip adding extracted URL and Homepage metadata // Skip adding extracted URL and Homepage metadata
// as "external_reference" if the metadata isn't IRI-compliant // as "external_reference" if the metadata isn't IRI-compliant
switch metadata := p.Metadata.(type) { switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata: case pkg.ApkDBEntry:
if metadata.URL != "" && isValidExternalRef(metadata.URL) { if metadata.URL != "" && isValidExternalRef(metadata.URL) {
refs = append(refs, cyclonedx.ExternalReference{ refs = append(refs, cyclonedx.ExternalReference{
URL: metadata.URL, URL: metadata.URL,
Type: cyclonedx.ERTypeDistribution, Type: cyclonedx.ERTypeDistribution,
}) })
} }
case pkg.CargoPackageMetadata: case pkg.RustCargoLockEntry:
if metadata.Source != "" { if metadata.Source != "" {
refs = append(refs, cyclonedx.ExternalReference{ refs = append(refs, cyclonedx.ExternalReference{
URL: metadata.Source, URL: metadata.Source,
Type: cyclonedx.ERTypeDistribution, Type: cyclonedx.ERTypeDistribution,
}) })
} }
case pkg.NpmPackageJSONMetadata: case pkg.NpmPackage:
if metadata.URL != "" && isValidExternalRef(metadata.URL) { if metadata.URL != "" && isValidExternalRef(metadata.URL) {
refs = append(refs, cyclonedx.ExternalReference{ refs = append(refs, cyclonedx.ExternalReference{
URL: metadata.URL, URL: metadata.URL,
@ -46,14 +46,14 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
Type: cyclonedx.ERTypeWebsite, Type: cyclonedx.ERTypeWebsite,
}) })
} }
case pkg.GemMetadata: case pkg.RubyGemspec:
if metadata.Homepage != "" && isValidExternalRef(metadata.Homepage) { if metadata.Homepage != "" && isValidExternalRef(metadata.Homepage) {
refs = append(refs, cyclonedx.ExternalReference{ refs = append(refs, cyclonedx.ExternalReference{
URL: metadata.Homepage, URL: metadata.Homepage,
Type: cyclonedx.ERTypeWebsite, Type: cyclonedx.ERTypeWebsite,
}) })
} }
case pkg.JavaMetadata: case pkg.JavaArchive:
if len(metadata.ArchiveDigests) > 0 { if len(metadata.ArchiveDigests) > 0 {
for _, digest := range metadata.ArchiveDigests { for _, digest := range metadata.ArchiveDigests {
refs = append(refs, cyclonedx.ExternalReference{ refs = append(refs, cyclonedx.ExternalReference{
@ -66,7 +66,7 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
}) })
} }
} }
case pkg.PythonPackageMetadata: case pkg.PythonPackage:
if metadata.DirectURLOrigin != nil && metadata.DirectURLOrigin.URL != "" { if metadata.DirectURLOrigin != nil && metadata.DirectURLOrigin.URL != "" {
ref := cyclonedx.ExternalReference{ ref := cyclonedx.ExternalReference{
URL: metadata.DirectURLOrigin.URL, URL: metadata.DirectURLOrigin.URL,
@ -105,16 +105,16 @@ func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) {
return return
} }
switch meta := metadata.(type) { switch meta := metadata.(type) {
case *pkg.ApkMetadata: case *pkg.ApkDBEntry:
meta.URL = refURL(c, cyclonedx.ERTypeDistribution) meta.URL = refURL(c, cyclonedx.ERTypeDistribution)
case *pkg.CargoPackageMetadata: case *pkg.RustCargoLockEntry:
meta.Source = refURL(c, cyclonedx.ERTypeDistribution) meta.Source = refURL(c, cyclonedx.ERTypeDistribution)
case *pkg.NpmPackageJSONMetadata: case *pkg.NpmPackage:
meta.URL = refURL(c, cyclonedx.ERTypeDistribution) meta.URL = refURL(c, cyclonedx.ERTypeDistribution)
meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite) meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite)
case *pkg.GemMetadata: case *pkg.RubyGemspec:
meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite) meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite)
case *pkg.JavaMetadata: case *pkg.JavaArchive:
var digests []syftFile.Digest var digests []syftFile.Digest
if ref := findExternalRef(c, cyclonedx.ERTypeBuildMeta); ref != nil { if ref := findExternalRef(c, cyclonedx.ERTypeBuildMeta); ref != nil {
if ref.Hashes != nil { if ref.Hashes != nil {
@ -128,7 +128,7 @@ func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) {
} }
meta.ArchiveDigests = digests meta.ArchiveDigests = digests
case *pkg.PythonPackageMetadata: case *pkg.PythonPackage:
if meta.DirectURLOrigin == nil { if meta.DirectURLOrigin == nil {
meta.DirectURLOrigin = &pkg.PythonDirectURLOriginInfo{} meta.DirectURLOrigin = &pkg.PythonDirectURLOriginInfo{}
} }

View File

@ -23,7 +23,7 @@ func Test_encodeExternalReferences(t *testing.T) {
{ {
name: "from apk", name: "from apk",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
URL: "http://a-place.gov", URL: "http://a-place.gov",
}, },
}, },
@ -34,7 +34,7 @@ func Test_encodeExternalReferences(t *testing.T) {
{ {
name: "from npm with valid URL", name: "from npm with valid URL",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
URL: "http://a-place.gov", URL: "http://a-place.gov",
}, },
}, },
@ -45,7 +45,7 @@ func Test_encodeExternalReferences(t *testing.T) {
{ {
name: "from npm with invalid URL but valid Homepage", name: "from npm with invalid URL but valid Homepage",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
URL: "b-place", URL: "b-place",
Homepage: "http://b-place.gov", Homepage: "http://b-place.gov",
}, },
@ -61,9 +61,8 @@ func Test_encodeExternalReferences(t *testing.T) {
Version: "0.12.1", Version: "0.12.1",
Language: pkg.Rust, Language: pkg.Rust,
Type: pkg.RustPkg, Type: pkg.RustPkg,
MetadataType: pkg.RustCargoPackageMetadataType,
Licenses: pkg.NewLicenseSet(), Licenses: pkg.NewLicenseSet(),
Metadata: pkg.CargoPackageMetadata{ Metadata: pkg.RustCargoLockEntry{
Name: "ansi_term", Name: "ansi_term",
Version: "0.12.1", Version: "0.12.1",
Source: "registry+https://github.com/rust-lang/crates.io-index", Source: "registry+https://github.com/rust-lang/crates.io-index",
@ -80,7 +79,7 @@ func Test_encodeExternalReferences(t *testing.T) {
{ {
name: "from npm with homepage", name: "from npm with homepage",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
URL: "http://a-place.gov", URL: "http://a-place.gov",
Homepage: "http://homepage", Homepage: "http://homepage",
}, },
@ -93,7 +92,7 @@ func Test_encodeExternalReferences(t *testing.T) {
{ {
name: "from gem", name: "from gem",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.GemMetadata{ Metadata: pkg.RubyGemspec{
Homepage: "http://a-place.gov", Homepage: "http://a-place.gov",
}, },
}, },
@ -104,7 +103,7 @@ func Test_encodeExternalReferences(t *testing.T) {
{ {
name: "from python direct url", name: "from python direct url",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{ DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{
URL: "http://a-place.gov", URL: "http://a-place.gov",
}, },
@ -117,7 +116,7 @@ func Test_encodeExternalReferences(t *testing.T) {
{ {
name: "from python direct url with commit", name: "from python direct url with commit",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{ DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{
URL: "http://a-place.gov", URL: "http://a-place.gov",
CommitID: "test", CommitID: "test",
@ -131,7 +130,7 @@ func Test_encodeExternalReferences(t *testing.T) {
{ {
name: "empty", name: "empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
URL: "", URL: "",
}, },
}, },

View File

@ -4,7 +4,7 @@ import "github.com/anchore/syft/syft/pkg"
func encodeGroup(p pkg.Package) string { func encodeGroup(p pkg.Package) string {
if hasMetadata(p) { if hasMetadata(p) {
if metadata, ok := p.Metadata.(pkg.JavaMetadata); ok && metadata.PomProperties != nil { if metadata, ok := p.Metadata.(pkg.JavaArchive); ok && metadata.PomProperties != nil {
return metadata.PomProperties.GroupID return metadata.PomProperties.GroupID
} }
} }
@ -12,9 +12,9 @@ func encodeGroup(p pkg.Package) string {
} }
func decodeGroup(group string, metadata interface{}) { func decodeGroup(group string, metadata interface{}) {
if meta, ok := metadata.(*pkg.JavaMetadata); ok { if meta, ok := metadata.(*pkg.JavaArchive); ok {
if meta.PomProperties == nil { if meta.PomProperties == nil {
meta.PomProperties = &pkg.PomProperties{} meta.PomProperties = &pkg.JavaPomProperties{}
} }
meta.PomProperties.GroupID = group meta.PomProperties.GroupID = group
} }

View File

@ -22,22 +22,22 @@ func Test_encodeGroup(t *testing.T) {
{ {
name: "metadata is not Java", name: "metadata is not Java",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{}, Metadata: pkg.NpmPackage{},
}, },
expected: "", expected: "",
}, },
{ {
name: "metadata is Java but pom properties is empty", name: "metadata is Java but pom properties is empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.JavaMetadata{}, Metadata: pkg.JavaArchive{},
}, },
expected: "", expected: "",
}, },
{ {
name: "metadata is Java and contains pomProperties", name: "metadata is Java and contains pomProperties",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
GroupID: "test", GroupID: "test",
}, },
}, },

View File

@ -7,11 +7,11 @@ import (
func encodePublisher(p pkg.Package) string { func encodePublisher(p pkg.Package) string {
if hasMetadata(p) { if hasMetadata(p) {
switch metadata := p.Metadata.(type) { switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata: case pkg.ApkDBEntry:
return metadata.Maintainer return metadata.Maintainer
case pkg.RpmMetadata: case pkg.RpmDBEntry:
return metadata.Vendor return metadata.Vendor
case pkg.DpkgMetadata: case pkg.DpkgDBEntry:
return metadata.Maintainer return metadata.Maintainer
} }
} }
@ -20,11 +20,11 @@ func encodePublisher(p pkg.Package) string {
func decodePublisher(publisher string, metadata interface{}) { func decodePublisher(publisher string, metadata interface{}) {
switch meta := metadata.(type) { switch meta := metadata.(type) {
case *pkg.ApkMetadata: case *pkg.ApkDBEntry:
meta.Maintainer = publisher meta.Maintainer = publisher
case *pkg.RpmMetadata: case *pkg.RpmDBEntry:
meta.Vendor = publisher meta.Vendor = publisher
case *pkg.DpkgMetadata: case *pkg.DpkgDBEntry:
meta.Maintainer = publisher meta.Maintainer = publisher
} }
} }

View File

@ -23,7 +23,7 @@ func Test_encodePublisher(t *testing.T) {
{ {
name: "from apk", name: "from apk",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Maintainer: "auth", Maintainer: "auth",
}, },
}, },
@ -32,7 +32,7 @@ func Test_encodePublisher(t *testing.T) {
{ {
name: "from rpm", name: "from rpm",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.RpmMetadata{ Metadata: pkg.RpmDBEntry{
Vendor: "auth", Vendor: "auth",
}, },
}, },
@ -41,7 +41,7 @@ func Test_encodePublisher(t *testing.T) {
{ {
name: "from dpkg", name: "from dpkg",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.DpkgMetadata{ Metadata: pkg.DpkgDBEntry{
Maintainer: "auth", Maintainer: "auth",
}, },
}, },
@ -51,7 +51,7 @@ func Test_encodePublisher(t *testing.T) {
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION // note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "empty", name: "empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Author: "", Author: "",
}, },
}, },

View File

@ -5,9 +5,9 @@ import "github.com/anchore/syft/syft/pkg"
func Description(p pkg.Package) string { func Description(p pkg.Package) string {
if hasMetadata(p) { if hasMetadata(p) {
switch metadata := p.Metadata.(type) { switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata: case pkg.ApkDBEntry:
return metadata.Description return metadata.Description
case pkg.NpmPackageJSONMetadata: case pkg.NpmPackage:
return metadata.Description return metadata.Description
} }
} }

View File

@ -23,7 +23,7 @@ func Test_Description(t *testing.T) {
{ {
name: "from apk", name: "from apk",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Description: "a description!", Description: "a description!",
}, },
}, },
@ -32,7 +32,7 @@ func Test_Description(t *testing.T) {
{ {
name: "from npm", name: "from npm",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Description: "a description!", Description: "a description!",
}, },
}, },
@ -42,7 +42,7 @@ func Test_Description(t *testing.T) {
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION // note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "empty", name: "empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Homepage: "", Homepage: "",
}, },
}, },

View File

@ -16,11 +16,11 @@ func DownloadLocation(p pkg.Package) string {
if hasMetadata(p) { if hasMetadata(p) {
switch metadata := p.Metadata.(type) { switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata: case pkg.ApkDBEntry:
return NoneIfEmpty(metadata.URL) return NoneIfEmpty(metadata.URL)
case pkg.NpmPackageJSONMetadata: case pkg.NpmPackage:
return NoneIfEmpty(metadata.URL) return NoneIfEmpty(metadata.URL)
case pkg.NpmPackageLockJSONMetadata: case pkg.NpmPackageLockEntry:
return NoneIfEmpty(metadata.Resolved) return NoneIfEmpty(metadata.Resolved)
} }
} }

View File

@ -22,7 +22,7 @@ func Test_DownloadLocation(t *testing.T) {
{ {
name: "from apk", name: "from apk",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
URL: "http://a-place.gov", URL: "http://a-place.gov",
}, },
}, },
@ -31,7 +31,7 @@ func Test_DownloadLocation(t *testing.T) {
{ {
name: "from npm", name: "from npm",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
URL: "http://a-place.gov", URL: "http://a-place.gov",
}, },
}, },
@ -40,7 +40,7 @@ func Test_DownloadLocation(t *testing.T) {
{ {
name: "empty", name: "empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
URL: "", URL: "",
}, },
}, },
@ -49,7 +49,7 @@ func Test_DownloadLocation(t *testing.T) {
{ {
name: "from npm package-lock should include resolved", name: "from npm package-lock should include resolved",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageLockJSONMetadata{ Metadata: pkg.NpmPackageLockEntry{
Resolved: "http://package-lock.test", Resolved: "http://package-lock.test",
}, },
}, },
@ -58,7 +58,7 @@ func Test_DownloadLocation(t *testing.T) {
{ {
name: "from npm package-lock empty should be NONE", name: "from npm package-lock empty should be NONE",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageLockJSONMetadata{ Metadata: pkg.NpmPackageLockEntry{
Resolved: "", Resolved: "",
}, },
}, },

View File

@ -5,9 +5,9 @@ import "github.com/anchore/syft/syft/pkg"
func Homepage(p pkg.Package) string { func Homepage(p pkg.Package) string {
if hasMetadata(p) { if hasMetadata(p) {
switch metadata := p.Metadata.(type) { switch metadata := p.Metadata.(type) {
case pkg.GemMetadata: case pkg.RubyGemspec:
return metadata.Homepage return metadata.Homepage
case pkg.NpmPackageJSONMetadata: case pkg.NpmPackage:
return metadata.Homepage return metadata.Homepage
} }
} }

View File

@ -23,7 +23,7 @@ func Test_Homepage(t *testing.T) {
{ {
name: "from gem", name: "from gem",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.GemMetadata{ Metadata: pkg.RubyGemspec{
Homepage: "http://a-place.gov", Homepage: "http://a-place.gov",
}, },
}, },
@ -32,7 +32,7 @@ func Test_Homepage(t *testing.T) {
{ {
name: "from npm", name: "from npm",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Homepage: "http://a-place.gov", Homepage: "http://a-place.gov",
}, },
}, },
@ -42,7 +42,7 @@ func Test_Homepage(t *testing.T) {
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION // note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "empty", name: "empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Homepage: "", Homepage: "",
}, },
}, },

View File

@ -23,7 +23,7 @@ func Test_Originator(t *testing.T) {
{ {
name: "from gem", name: "from gem",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.GemMetadata{ Metadata: pkg.RubyGemspec{
Authors: []string{ Authors: []string{
"auth1", "auth1",
"auth2", "auth2",
@ -35,7 +35,7 @@ func Test_Originator(t *testing.T) {
{ {
name: "from npm", name: "from npm",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Author: "auth", Author: "auth",
}, },
}, },
@ -44,7 +44,7 @@ func Test_Originator(t *testing.T) {
{ {
name: "from apk", name: "from apk",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Maintainer: "auth", Maintainer: "auth",
}, },
}, },
@ -53,7 +53,7 @@ func Test_Originator(t *testing.T) {
{ {
name: "from python - just name", name: "from python - just name",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
Author: "auth", Author: "auth",
}, },
}, },
@ -62,7 +62,7 @@ func Test_Originator(t *testing.T) {
{ {
name: "from python - just email", name: "from python - just email",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
AuthorEmail: "auth@auth.gov", AuthorEmail: "auth@auth.gov",
}, },
}, },
@ -71,7 +71,7 @@ func Test_Originator(t *testing.T) {
{ {
name: "from python - both name and email", name: "from python - both name and email",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
Author: "auth", Author: "auth",
AuthorEmail: "auth@auth.gov", AuthorEmail: "auth@auth.gov",
}, },
@ -81,7 +81,7 @@ func Test_Originator(t *testing.T) {
{ {
name: "from rpm", name: "from rpm",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.RpmMetadata{ Metadata: pkg.RpmDBEntry{
Vendor: "auth", Vendor: "auth",
}, },
}, },
@ -90,7 +90,7 @@ func Test_Originator(t *testing.T) {
{ {
name: "from dpkg", name: "from dpkg",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.DpkgMetadata{ Metadata: pkg.DpkgDBEntry{
Maintainer: "auth", Maintainer: "auth",
}, },
}, },
@ -100,7 +100,7 @@ func Test_Originator(t *testing.T) {
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION // note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "empty", name: "empty",
input: pkg.Package{ input: pkg.Package{
Metadata: pkg.NpmPackageJSONMetadata{ Metadata: pkg.NpmPackage{
Author: "", Author: "",
}, },
}, },

View File

@ -15,25 +15,25 @@ func Originator(p pkg.Package) (string, string) {
author := "" author := ""
if hasMetadata(p) { if hasMetadata(p) {
switch metadata := p.Metadata.(type) { switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata: case pkg.ApkDBEntry:
author = metadata.Maintainer author = metadata.Maintainer
case pkg.NpmPackageJSONMetadata: case pkg.NpmPackage:
author = metadata.Author author = metadata.Author
case pkg.PythonPackageMetadata: case pkg.PythonPackage:
author = metadata.Author author = metadata.Author
if author == "" { if author == "" {
author = metadata.AuthorEmail author = metadata.AuthorEmail
} else if metadata.AuthorEmail != "" { } else if metadata.AuthorEmail != "" {
author = fmt.Sprintf("%s (%s)", author, metadata.AuthorEmail) author = fmt.Sprintf("%s (%s)", author, metadata.AuthorEmail)
} }
case pkg.GemMetadata: case pkg.RubyGemspec:
if len(metadata.Authors) > 0 { if len(metadata.Authors) > 0 {
author = metadata.Authors[0] author = metadata.Authors[0]
} }
case pkg.RpmMetadata: case pkg.RpmDBEntry:
typ = "Organization" typ = "Organization"
author = metadata.Vendor author = metadata.Vendor
case pkg.DpkgMetadata: case pkg.DpkgDBEntry:
author = metadata.Maintainer author = metadata.Maintainer
} }
if typ == "" && author != "" { if typ == "" && author != "" {

View File

@ -477,7 +477,7 @@ func toPackageChecksums(p pkg.Package) ([]spdx.Checksum, bool) {
switch meta := p.Metadata.(type) { switch meta := p.Metadata.(type) {
// we generate digest for some Java packages // we generate digest for some Java packages
// spdx.github.io/spdx-spec/package-information/#710-package-checksum-field // spdx.github.io/spdx-spec/package-information/#710-package-checksum-field
case pkg.JavaMetadata: case pkg.JavaArchive:
// if syft has generated the digest here then filesAnalyzed is true // if syft has generated the digest here then filesAnalyzed is true
if len(meta.ArchiveDigests) > 0 { if len(meta.ArchiveDigests) > 0 {
filesAnalyzed = true filesAnalyzed = true
@ -489,7 +489,7 @@ func toPackageChecksums(p pkg.Package) ([]spdx.Checksum, bool) {
}) })
} }
} }
case pkg.GolangBinMetadata: case pkg.GolangBinaryBuildinfoEntry:
// because the H1 digest is found in the Golang metadata we cannot claim that the files were analyzed // because the H1 digest is found in the Golang metadata we cannot claim that the files were analyzed
algo, hexStr, err := util.HDigestToSHA(meta.H1Digest) algo, hexStr, err := util.HDigestToSHA(meta.H1Digest)
if err != nil { if err != nil {

View File

@ -275,7 +275,7 @@ func Test_toPackageChecksums(t *testing.T) {
Name: "test", Name: "test",
Version: "1.0.0", Version: "1.0.0",
Language: pkg.Java, Language: pkg.Java,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
ArchiveDigests: []file.Digest{ ArchiveDigests: []file.Digest{
{ {
Algorithm: "sha1", // SPDX expects these to be uppercase Algorithm: "sha1", // SPDX expects these to be uppercase
@ -298,7 +298,7 @@ func Test_toPackageChecksums(t *testing.T) {
Name: "test", Name: "test",
Version: "1.0.0", Version: "1.0.0",
Language: pkg.Java, Language: pkg.Java,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
ArchiveDigests: []file.Digest{}, ArchiveDigests: []file.Digest{},
}, },
}, },
@ -321,8 +321,7 @@ func Test_toPackageChecksums(t *testing.T) {
Name: "test", Name: "test",
Version: "1.0.0", Version: "1.0.0",
Language: pkg.Go, Language: pkg.Go,
MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinaryBuildinfoEntry{
Metadata: pkg.GolangBinMetadata{
H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=", H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
}, },
}, },
@ -632,8 +631,7 @@ func Test_H1Digest(t *testing.T) {
pkg: pkg.Package{ pkg: pkg.Package{
Name: "github.com/googleapis/gnostic", Name: "github.com/googleapis/gnostic",
Version: "v0.5.5", Version: "v0.5.5",
MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinaryBuildinfoEntry{
Metadata: pkg.GolangBinMetadata{
H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=", H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
}, },
}, },
@ -644,8 +642,7 @@ func Test_H1Digest(t *testing.T) {
pkg: pkg.Package{ pkg: pkg.Package{
Name: "github.com/googleapis/gnostic", Name: "github.com/googleapis/gnostic",
Version: "v0.5.5", Version: "v0.5.5",
MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinaryBuildinfoEntry{
Metadata: pkg.GolangBinMetadata{
H1Digest: "h1:9fHAtK0uzzz", H1Digest: "h1:9fHAtK0uzzz",
}, },
}, },
@ -656,8 +653,7 @@ func Test_H1Digest(t *testing.T) {
pkg: pkg.Package{ pkg: pkg.Package{
Name: "github.com/googleapis/gnostic", Name: "github.com/googleapis/gnostic",
Version: "v0.5.5", Version: "v0.5.5",
MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinaryBuildinfoEntry{
Metadata: pkg.GolangBinMetadata{
H1Digest: "h12:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=", H1Digest: "h12:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
}, },
}, },

View File

@ -490,7 +490,6 @@ func extractPkgInfo(p *spdx.Package) pkgInfo {
func toSyftPackage(p *spdx.Package) pkg.Package { func toSyftPackage(p *spdx.Package) pkg.Package {
info := extractPkgInfo(p) info := extractPkgInfo(p)
metadataType, metadata := extractMetadata(p, info)
sP := &pkg.Package{ sP := &pkg.Package{
Type: info.typ, Type: info.typ,
Name: p.PackageName, Name: p.PackageName,
@ -499,8 +498,7 @@ func toSyftPackage(p *spdx.Package) pkg.Package {
CPEs: extractCPEs(p), CPEs: extractCPEs(p),
PURL: purlValue(info.purl), PURL: purlValue(info.purl),
Language: info.lang, Language: info.lang,
MetadataType: metadataType, Metadata: extractMetadata(p, info),
Metadata: metadata,
} }
sP.SetID() sP.SetID()
@ -541,7 +539,7 @@ func cleanSPDXID(id string) string {
} }
//nolint:funlen //nolint:funlen
func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface{}) { func extractMetadata(p *spdx.Package, info pkgInfo) any {
arch := info.qualifierValue(pkg.PURLQualifierArch) arch := info.qualifierValue(pkg.PURLQualifierArch)
upstreamValue := info.qualifierValue(pkg.PURLQualifierUpstream) upstreamValue := info.qualifierValue(pkg.PURLQualifierUpstream)
upstream := strings.SplitN(upstreamValue, "@", 2) upstream := strings.SplitN(upstreamValue, "@", 2)
@ -560,7 +558,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
} }
switch info.typ { switch info.typ {
case pkg.ApkPkg: case pkg.ApkPkg:
return pkg.ApkMetadataType, pkg.ApkMetadata{ return pkg.ApkDBEntry{
Package: p.PackageName, Package: p.PackageName,
OriginPackage: upstreamName, OriginPackage: upstreamName,
Maintainer: supplier, Maintainer: supplier,
@ -577,7 +575,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
} else { } else {
epoch = &converted epoch = &converted
} }
return pkg.RpmMetadataType, pkg.RpmMetadata{ return pkg.RpmDBEntry{
Name: p.PackageName, Name: p.PackageName,
Version: p.PackageVersion, Version: p.PackageVersion,
Epoch: epoch, Epoch: epoch,
@ -586,7 +584,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
Vendor: originator, Vendor: originator,
} }
case pkg.DebPkg: case pkg.DebPkg:
return pkg.DpkgMetadataType, pkg.DpkgMetadata{ return pkg.DpkgDBEntry{
Package: p.PackageName, Package: p.PackageName,
Source: upstreamName, Source: upstreamName,
Version: p.PackageVersion, Version: p.PackageVersion,
@ -599,7 +597,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
for _, value := range p.PackageChecksums { for _, value := range p.PackageChecksums {
digests = append(digests, file.Digest{Algorithm: fromChecksumAlgorithm(value.Algorithm), Value: value.Value}) digests = append(digests, file.Digest{Algorithm: fromChecksumAlgorithm(value.Algorithm), Value: value.Value})
} }
return pkg.JavaMetadataType, pkg.JavaMetadata{ return pkg.JavaArchive{
ArchiveDigests: digests, ArchiveDigests: digests,
} }
case pkg.GoModulePkg: case pkg.GoModulePkg:
@ -613,11 +611,11 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
h1Digest = digest h1Digest = digest
break break
} }
return pkg.GolangBinMetadataType, pkg.GolangBinMetadata{ return pkg.GolangBinaryBuildinfoEntry{
H1Digest: h1Digest, H1Digest: h1Digest,
} }
} }
return pkg.UnknownMetadataType, nil return nil
} }
func findPURLValue(p *spdx.Package) string { func findPURLValue(p *spdx.Package) string {

View File

@ -103,15 +103,13 @@ func TestToSyftModel(t *testing.T) {
p1 := pkgs[0] p1 := pkgs[0]
assert.Equal(t, p1.Name, "pkg-1") assert.Equal(t, p1.Name, "pkg-1")
assert.Equal(t, p1.MetadataType, pkg.ApkMetadataType) p1meta := p1.Metadata.(pkg.ApkDBEntry)
p1meta := p1.Metadata.(pkg.ApkMetadata)
assert.Equal(t, p1meta.OriginPackage, "p1-origin") assert.Equal(t, p1meta.OriginPackage, "p1-origin")
assert.Len(t, p1.CPEs, 2) assert.Len(t, p1.CPEs, 2)
p2 := pkgs[1] p2 := pkgs[1]
assert.Equal(t, p2.Name, "pkg-2") assert.Equal(t, p2.Name, "pkg-2")
assert.Equal(t, p2.MetadataType, pkg.DpkgMetadataType) p2meta := p2.Metadata.(pkg.DpkgDBEntry)
p2meta := p2.Metadata.(pkg.DpkgMetadata)
assert.Equal(t, p2meta.Source, "p2-origin") assert.Equal(t, p2meta.Source, "p2-origin")
assert.Equal(t, p2meta.SourceVersion, "9.1.3") assert.Equal(t, p2meta.SourceVersion, "9.1.3")
assert.Len(t, p2.CPEs, 3) assert.Len(t, p2.CPEs, 3)
@ -121,7 +119,6 @@ func Test_extractMetadata(t *testing.T) {
oneTwoThreeFour := 1234 oneTwoThreeFour := 1234
tests := []struct { tests := []struct {
pkg spdx.Package pkg spdx.Package
metaType pkg.MetadataType
meta interface{} meta interface{}
}{ }{
{ {
@ -136,8 +133,7 @@ func Test_extractMetadata(t *testing.T) {
}, },
}, },
}, },
metaType: pkg.DpkgMetadataType, meta: pkg.DpkgDBEntry{
meta: pkg.DpkgMetadata{
Package: "SomeDebPkg", Package: "SomeDebPkg",
Source: "somedebpkg-origin", Source: "somedebpkg-origin",
Version: "43.1.235", Version: "43.1.235",
@ -157,8 +153,7 @@ func Test_extractMetadata(t *testing.T) {
}, },
}, },
}, },
metaType: pkg.ApkMetadataType, meta: pkg.ApkDBEntry{
meta: pkg.ApkMetadata{
Package: "SomeApkPkg", Package: "SomeApkPkg",
OriginPackage: "apk-origin", OriginPackage: "apk-origin",
Version: "3.2.9", Version: "3.2.9",
@ -177,8 +172,7 @@ func Test_extractMetadata(t *testing.T) {
}, },
}, },
}, },
metaType: pkg.RpmMetadataType, meta: pkg.RpmDBEntry{
meta: pkg.RpmMetadata{
Name: "SomeRpmPkg", Name: "SomeRpmPkg",
Version: "13.2.79", Version: "13.2.79",
Epoch: &oneTwoThreeFour, Epoch: &oneTwoThreeFour,
@ -192,8 +186,7 @@ func Test_extractMetadata(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.pkg.PackageName, func(t *testing.T) { t.Run(test.pkg.PackageName, func(t *testing.T) {
info := extractPkgInfo(&test.pkg) info := extractPkgInfo(&test.pkg)
metaType, meta := extractMetadata(&test.pkg, info) meta := extractMetadata(&test.pkg, info)
assert.Equal(t, test.metaType, metaType)
assert.EqualValues(t, test.meta, meta) assert.EqualValues(t, test.meta, meta)
}) })
} }
@ -319,8 +312,7 @@ func TestH1Digest(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
p := toSyftPackage(&test.pkg) p := toSyftPackage(&test.pkg)
require.Equal(t, pkg.GolangBinMetadataType, p.MetadataType) meta := p.Metadata.(pkg.GolangBinaryBuildinfoEntry)
meta := p.Metadata.(pkg.GolangBinMetadata)
require.Equal(t, test.expectedDigest, meta.H1Digest) require.Equal(t, test.expectedDigest, meta.H1Digest)
}) })
} }
@ -440,11 +432,9 @@ func Test_convertToAndFromFormat(t *testing.T) {
packages := []pkg.Package{ packages := []pkg.Package{
{ {
Name: "pkg1", Name: "pkg1",
MetadataType: pkg.UnknownMetadataType,
}, },
{ {
Name: "pkg2", Name: "pkg2",
MetadataType: pkg.UnknownMetadataType,
}, },
} }
@ -545,7 +535,6 @@ func Test_convertToAndFromFormat(t *testing.T) {
cmpopts.IgnoreUnexported(pkg.Collection{}), cmpopts.IgnoreUnexported(pkg.Collection{}),
cmpopts.IgnoreUnexported(pkg.Package{}), cmpopts.IgnoreUnexported(pkg.Package{}),
cmpopts.IgnoreUnexported(pkg.LicenseSet{}), cmpopts.IgnoreUnexported(pkg.LicenseSet{}),
cmpopts.IgnoreFields(pkg.Package{}, "MetadataType"),
cmpopts.IgnoreFields(sbom.Artifacts{}, "FileMetadata", "FileDigests"), cmpopts.IgnoreFields(sbom.Artifacts{}, "FileMetadata", "FileDigests"),
); diff != "" { ); diff != "" {
t.Fatalf("packages do not match:\n%s", diff) t.Fatalf("packages do not match:\n%s", diff)
@ -639,7 +628,6 @@ func Test_directPackageFiles(t *testing.T) {
p := pkg.Package{ p := pkg.Package{
Name: "some-package", Name: "some-package",
Version: "1.0.5", Version: "1.0.5",
MetadataType: pkg.UnknownMetadataType,
} }
p.SetID() p.SetID()
f := file.Location{ f := file.Location{

View File

@ -43,14 +43,14 @@
"name": "syft:package:language", "name": "syft:package:language",
"value": "python" "value": "python"
}, },
{
"name": "syft:package:metadataType",
"value": "PythonPackageMetadata"
},
{ {
"name": "syft:package:type", "name": "syft:package:type",
"value": "python" "value": "python"
}, },
{
"name": "syft:package:metadataType",
"value": "python-package"
},
{ {
"name": "syft:location:0:path", "name": "syft:location:0:path",
"value": "/some/path/pkg1" "value": "/some/path/pkg1"
@ -69,14 +69,14 @@
"name": "syft:package:foundBy", "name": "syft:package:foundBy",
"value": "the-cataloger-2" "value": "the-cataloger-2"
}, },
{
"name": "syft:package:metadataType",
"value": "DpkgMetadata"
},
{ {
"name": "syft:package:type", "name": "syft:package:type",
"value": "deb" "value": "deb"
}, },
{
"name": "syft:package:metadataType",
"value": "dpkg-db-entry"
},
{ {
"name": "syft:location:0:path", "name": "syft:location:0:path",
"value": "/some/path/pkg1" "value": "/some/path/pkg1"

View File

@ -44,14 +44,14 @@
"name": "syft:package:language", "name": "syft:package:language",
"value": "python" "value": "python"
}, },
{
"name": "syft:package:metadataType",
"value": "PythonPackageMetadata"
},
{ {
"name": "syft:package:type", "name": "syft:package:type",
"value": "python" "value": "python"
}, },
{
"name": "syft:package:metadataType",
"value": "python-package"
},
{ {
"name": "syft:location:0:layerID", "name": "syft:location:0:layerID",
"value": "sha256:redacted" "value": "sha256:redacted"
@ -74,14 +74,14 @@
"name": "syft:package:foundBy", "name": "syft:package:foundBy",
"value": "the-cataloger-2" "value": "the-cataloger-2"
}, },
{
"name": "syft:package:metadataType",
"value": "DpkgMetadata"
},
{ {
"name": "syft:package:type", "name": "syft:package:type",
"value": "deb" "value": "deb"
}, },
{
"name": "syft:package:metadataType",
"value": "dpkg-db-entry"
},
{ {
"name": "syft:location:0:layerID", "name": "syft:location:0:layerID",
"value": "sha256:redacted" "value": "sha256:redacted"

View File

@ -27,20 +27,20 @@
<properties> <properties>
<property name="syft:package:foundBy">the-cataloger-1</property> <property name="syft:package:foundBy">the-cataloger-1</property>
<property name="syft:package:language">python</property> <property name="syft:package:language">python</property>
<property name="syft:package:metadataType">PythonPackageMetadata</property>
<property name="syft:package:type">python</property> <property name="syft:package:type">python</property>
<property name="syft:package:metadataType">python-package</property>
<property name="syft:location:0:path">/some/path/pkg1</property> <property name="syft:location:0:path">/some/path/pkg1</property>
</properties> </properties>
</component> </component>
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=ad5013466727018f" type="library"> <component bom-ref="redacted" type="library">
<name>package-2</name> <name>package-2</name>
<version>2.0.1</version> <version>2.0.1</version>
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe> <cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
<purl>pkg:deb/debian/package-2@2.0.1</purl> <purl>pkg:deb/debian/package-2@2.0.1</purl>
<properties> <properties>
<property name="syft:package:foundBy">the-cataloger-2</property> <property name="syft:package:foundBy">the-cataloger-2</property>
<property name="syft:package:metadataType">DpkgMetadata</property>
<property name="syft:package:type">deb</property> <property name="syft:package:type">deb</property>
<property name="syft:package:metadataType">dpkg-db-entry</property>
<property name="syft:location:0:path">/some/path/pkg1</property> <property name="syft:location:0:path">/some/path/pkg1</property>
<property name="syft:metadata:installedSize">0</property> <property name="syft:metadata:installedSize">0</property>
</properties> </properties>

View File

@ -28,21 +28,21 @@
<properties> <properties>
<property name="syft:package:foundBy">the-cataloger-1</property> <property name="syft:package:foundBy">the-cataloger-1</property>
<property name="syft:package:language">python</property> <property name="syft:package:language">python</property>
<property name="syft:package:metadataType">PythonPackageMetadata</property>
<property name="syft:package:type">python</property> <property name="syft:package:type">python</property>
<property name="syft:package:metadataType">python-package</property>
<property name="syft:location:0:layerID">sha256:redacted</property> <property name="syft:location:0:layerID">sha256:redacted</property>
<property name="syft:location:0:path">/somefile-1.txt</property> <property name="syft:location:0:path">/somefile-1.txt</property>
</properties> </properties>
</component> </component>
<component bom-ref="pkg:deb/debian/package-2@2.0.1?package-id=f27313b22a5ba330" type="library"> <component bom-ref="redacted" type="library">
<name>package-2</name> <name>package-2</name>
<version>2.0.1</version> <version>2.0.1</version>
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe> <cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
<purl>pkg:deb/debian/package-2@2.0.1</purl> <purl>pkg:deb/debian/package-2@2.0.1</purl>
<properties> <properties>
<property name="syft:package:foundBy">the-cataloger-2</property> <property name="syft:package:foundBy">the-cataloger-2</property>
<property name="syft:package:metadataType">DpkgMetadata</property>
<property name="syft:package:type">deb</property> <property name="syft:package:type">deb</property>
<property name="syft:package:metadataType">dpkg-db-entry</property>
<property name="syft:location:0:layerID">sha256:redacted</property> <property name="syft:location:0:layerID">sha256:redacted</property>
<property name="syft:location:0:path">/somefile-2.txt</property> <property name="syft:location:0:path">/somefile-2.txt</property>
<property name="syft:metadata:installedSize">0</property> <property name="syft:metadata:installedSize">0</property>

View File

@ -109,11 +109,10 @@ func newDirectoryCatalog() *pkg.Collection {
file.NewLocation("/some/path/pkg1"), file.NewLocation("/some/path/pkg1"),
), ),
Language: pkg.Python, Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: pkg.NewLicenseSet( Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"), pkg.NewLicense("MIT"),
), ),
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
Name: "package-1", Name: "package-1",
Version: "1.0.1", Version: "1.0.1",
Files: []pkg.PythonFileRecord{ Files: []pkg.PythonFileRecord{
@ -135,8 +134,7 @@ func newDirectoryCatalog() *pkg.Collection {
Locations: file.NewLocationSet( Locations: file.NewLocationSet(
file.NewLocation("/some/path/pkg1"), file.NewLocation("/some/path/pkg1"),
), ),
MetadataType: pkg.DpkgMetadataType, Metadata: pkg.DpkgDBEntry{
Metadata: pkg.DpkgMetadata{
Package: "package-2", Package: "package-2",
Version: "2.0.1", Version: "2.0.1",
}, },
@ -162,11 +160,10 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
file.NewLocation("/some/path/pkg1"), file.NewLocation("/some/path/pkg1"),
), ),
Language: pkg.Python, Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: pkg.NewLicenseSet( Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"), pkg.NewLicense("MIT"),
), ),
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
Name: "package-1", Name: "package-1",
Version: "1.0.1", Version: "1.0.1",
Author: "test-author", Author: "test-author",
@ -189,8 +186,7 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
Locations: file.NewLocationSet( Locations: file.NewLocationSet(
file.NewLocation("/some/path/pkg1"), file.NewLocation("/some/path/pkg1"),
), ),
MetadataType: pkg.DpkgMetadataType, Metadata: pkg.DpkgDBEntry{
Metadata: pkg.DpkgMetadata{
Package: "package-2", Package: "package-2",
Version: "2.0.1", Version: "2.0.1",
}, },

View File

@ -95,10 +95,12 @@ func changeToDirectoryWithGoldenFixture(t testing.TB, testImage string) func() {
} }
func populateImageCatalog(catalog *pkg.Collection, img *image.Image) { func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
// TODO: this helper function is coupled to the image-simple fixture, which seems like a bad idea
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks) _, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks) _, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
// populate catalog with test data // populate catalog with test data
if ref1 != nil {
catalog.Add(pkg.Package{ catalog.Add(pkg.Package{
Name: "package-1", Name: "package-1",
Version: "1.0.1", Version: "1.0.1",
@ -108,11 +110,10 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
Type: pkg.PythonPkg, Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1", FoundBy: "the-cataloger-1",
Language: pkg.Python, Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: pkg.NewLicenseSet( Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"), pkg.NewLicense("MIT"),
), ),
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
Name: "package-1", Name: "package-1",
Version: "1.0.1", Version: "1.0.1",
}, },
@ -121,6 +122,9 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
}, },
}) })
}
if ref2 != nil {
catalog.Add(pkg.Package{ catalog.Add(pkg.Package{
Name: "package-2", Name: "package-2",
Version: "2.0.1", Version: "2.0.1",
@ -129,8 +133,7 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
), ),
Type: pkg.DebPkg, Type: pkg.DebPkg,
FoundBy: "the-cataloger-2", FoundBy: "the-cataloger-2",
MetadataType: pkg.DpkgMetadataType, Metadata: pkg.DpkgDBEntry{
Metadata: pkg.DpkgMetadata{
Package: "package-2", Package: "package-2",
Version: "2.0.1", Version: "2.0.1",
}, },
@ -140,3 +143,4 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
}, },
}) })
} }
}

View File

@ -15,7 +15,7 @@
"packages": [ "packages": [
{ {
"name": "package-1", "name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-9265397e5e15168a", "SPDXID": "SPDXRef-Package-python-package-1-fb6bef15e281ea43",
"versionInfo": "1.0.1", "versionInfo": "1.0.1",
"supplier": "NOASSERTION", "supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -39,7 +39,7 @@
}, },
{ {
"name": "package-2", "name": "package-2",
"SPDXID": "SPDXRef-Package-deb-package-2-ad5013466727018f", "SPDXID": "SPDXRef-Package-deb-package-2-39392bb5e270f669",
"versionInfo": "2.0.1", "versionInfo": "2.0.1",
"supplier": "NOASSERTION", "supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -73,12 +73,12 @@
"relationships": [ "relationships": [
{ {
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path", "spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-9265397e5e15168a", "relatedSpdxElement": "SPDXRef-Package-python-package-1-fb6bef15e281ea43",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path", "spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-ad5013466727018f", "relatedSpdxElement": "SPDXRef-Package-deb-package-2-39392bb5e270f669",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {

View File

@ -15,7 +15,7 @@
"packages": [ "packages": [
{ {
"name": "package-1", "name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "SPDXID": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"versionInfo": "1.0.1", "versionInfo": "1.0.1",
"supplier": "NOASSERTION", "supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -39,7 +39,7 @@
}, },
{ {
"name": "package-2", "name": "package-2",
"SPDXID": "SPDXRef-Package-deb-package-2-f27313b22a5ba330", "SPDXID": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
"versionInfo": "2.0.1", "versionInfo": "2.0.1",
"supplier": "NOASSERTION", "supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -87,12 +87,12 @@
"relationships": [ "relationships": [
{ {
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input", "spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "relatedSpdxElement": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input", "spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-f27313b22a5ba330", "relatedSpdxElement": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {

View File

@ -15,7 +15,7 @@
"packages": [ "packages": [
{ {
"name": "package-1", "name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "SPDXID": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"versionInfo": "1.0.1", "versionInfo": "1.0.1",
"supplier": "NOASSERTION", "supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -39,7 +39,7 @@
}, },
{ {
"name": "package-2", "name": "package-2",
"SPDXID": "SPDXRef-Package-deb-package-2-f27313b22a5ba330", "SPDXID": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
"versionInfo": "2.0.1", "versionInfo": "2.0.1",
"supplier": "NOASSERTION", "supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION", "downloadLocation": "NOASSERTION",
@ -178,43 +178,43 @@
], ],
"relationships": [ "relationships": [
{ {
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c", "relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174", "relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6", "relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f", "relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f", "relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd", "relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input", "spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-125840abc1c66dd7", "relatedSpdxElement": "SPDXRef-Package-python-package-1-80210ebcba92e632",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input", "spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-f27313b22a5ba330", "relatedSpdxElement": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
"relationshipType": "CONTAINS" "relationshipType": "CONTAINS"
}, },
{ {

View File

@ -20,7 +20,7 @@ FilesAnalyzed: false
##### Package: @at-sign ##### Package: @at-sign
PackageName: @at-sign PackageName: @at-sign
SPDXID: SPDXRef-Package--at-sign-3732f7a5679bdec4 SPDXID: SPDXRef-Package--at-sign-1c8c811ea5b1cd46
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false
@ -32,7 +32,7 @@ PackageCopyrightText: NOASSERTION
##### Package: some/slashes ##### Package: some/slashes
PackageName: some/slashes PackageName: some/slashes
SPDXID: SPDXRef-Package-some-slashes-1345166d4801153b SPDXID: SPDXRef-Package-some-slashes-8a8e95924316c66b
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false
@ -44,7 +44,7 @@ PackageCopyrightText: NOASSERTION
##### Package: under_scores ##### Package: under_scores
PackageName: under_scores PackageName: under_scores
SPDXID: SPDXRef-Package-under-scores-290d5c77210978c1 SPDXID: SPDXRef-Package-under-scores-883703d950ec00f3
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
FilesAnalyzed: false FilesAnalyzed: false
@ -55,8 +55,8 @@ PackageCopyrightText: NOASSERTION
##### Relationships ##### Relationships
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package--at-sign-3732f7a5679bdec4 Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package--at-sign-1c8c811ea5b1cd46
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-some-slashes-1345166d4801153b Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-some-slashes-8a8e95924316c66b
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-under-scores-290d5c77210978c1 Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-under-scores-883703d950ec00f3
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-foobar-baz Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-foobar-baz

View File

@ -61,7 +61,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
##### Package: package-2 ##### Package: package-2
PackageName: package-2 PackageName: package-2
SPDXID: SPDXRef-Package-deb-package-2-f27313b22a5ba330 SPDXID: SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
PackageVersion: 2.0.1 PackageVersion: 2.0.1
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
@ -76,7 +76,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1 ##### Package: package-1
PackageName: package-1 PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7 SPDXID: SPDXRef-Package-python-package-1-80210ebcba92e632
PackageVersion: 1.0.1 PackageVersion: 1.0.1
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
@ -90,13 +90,13 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
##### Relationships ##### Relationships
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174 Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6 Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-125840abc1c66dd7 Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-80210ebcba92e632
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-f27313b22a5ba330 Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input

View File

@ -20,7 +20,7 @@ FilesAnalyzed: false
##### Package: package-2 ##### Package: package-2
PackageName: package-2 PackageName: package-2
SPDXID: SPDXRef-Package-deb-package-2-ad5013466727018f SPDXID: SPDXRef-Package-deb-package-2-39392bb5e270f669
PackageVersion: 2.0.1 PackageVersion: 2.0.1
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
@ -35,7 +35,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1 ##### Package: package-1
PackageName: package-1 PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-9265397e5e15168a SPDXID: SPDXRef-Package-python-package-1-fb6bef15e281ea43
PackageVersion: 1.0.1 PackageVersion: 1.0.1
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
@ -49,7 +49,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-2
##### Relationships ##### Relationships
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-9265397e5e15168a Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-fb6bef15e281ea43
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-ad5013466727018f Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-39392bb5e270f669
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path

View File

@ -23,7 +23,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
##### Package: package-2 ##### Package: package-2
PackageName: package-2 PackageName: package-2
SPDXID: SPDXRef-Package-deb-package-2-f27313b22a5ba330 SPDXID: SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
PackageVersion: 2.0.1 PackageVersion: 2.0.1
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
@ -38,7 +38,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1 ##### Package: package-1
PackageName: package-1 PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7 SPDXID: SPDXRef-Package-python-package-1-80210ebcba92e632
PackageVersion: 1.0.1 PackageVersion: 1.0.1
PackageSupplier: NOASSERTION PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION PackageDownloadLocation: NOASSERTION
@ -52,7 +52,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
##### Relationships ##### Relationships
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-125840abc1c66dd7 Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-80210ebcba92e632
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-f27313b22a5ba330 Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input

View File

@ -18,18 +18,7 @@ var _ sbom.FormatDecoder = (*decoder)(nil)
type decoder struct{} type decoder struct{}
type DecoderConfig struct {
}
func DefaultDecoderConfig() DecoderConfig {
return DecoderConfig{}
}
func NewFormatDecoder() sbom.FormatDecoder { func NewFormatDecoder() sbom.FormatDecoder {
return NewFormatDecoderWithConfig(DefaultDecoderConfig())
}
func NewFormatDecoderWithConfig(DecoderConfig) sbom.FormatDecoder {
return decoder{} return decoder{}
} }

View File

@ -23,10 +23,37 @@ import (
) )
func Test_EncodeDecodeCycle(t *testing.T) { func Test_EncodeDecodeCycle(t *testing.T) {
testImage := "image-simple"
originalSBOM := testutil.ImageInput(t, testImage)
enc := NewFormatEncoder() table := []struct {
name string
fixtureImage string
cfg EncoderConfig
}{
{
name: "go case",
fixtureImage: "image-simple",
cfg: DefaultEncoderConfig(),
},
{
name: "default alpine",
fixtureImage: "image-alpine",
cfg: DefaultEncoderConfig(),
},
{
name: "legacy alpine",
fixtureImage: "image-alpine",
cfg: EncoderConfig{
Legacy: true,
},
},
}
for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
originalSBOM := testutil.ImageInput(t, tt.fixtureImage)
enc, err := NewFormatEncoderWithConfig(tt.cfg)
require.NoError(t, err)
dec := NewFormatDecoder() dec := NewFormatDecoder()
var buf bytes.Buffer var buf bytes.Buffer
@ -61,9 +88,11 @@ func Test_EncodeDecodeCycle(t *testing.T) {
// semantically the same // semantically the same
continue continue
} }
t.Errorf("package difference (%s): %+v", p.Name, d) t.Errorf("%q package difference (%s): %+v", tt.fixtureImage, p.Name, d)
} }
} }
})
}
} }
func TestOutOfDateParser(t *testing.T) { func TestOutOfDateParser(t *testing.T) {
@ -138,7 +167,6 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
}, },
}, },
PURL: "pkg:generic/pkg@version", PURL: "pkg:generic/pkg@version",
MetadataType: "",
Metadata: nil, Metadata: nil,
} }
p.SetID() p.SetID()

View File

@ -12,11 +12,32 @@ var _ sbom.FormatEncoder = (*encoder)(nil)
const ID sbom.FormatID = "syft-json" const ID sbom.FormatID = "syft-json"
type EncoderConfig struct {
Legacy bool // transform the output to the legacy syft-json format (pre v1.0 changes, enumerated in the README.md)
}
type encoder struct { type encoder struct {
cfg EncoderConfig
} }
func NewFormatEncoder() sbom.FormatEncoder { func NewFormatEncoder() sbom.FormatEncoder {
return encoder{} enc, err := NewFormatEncoderWithConfig(DefaultEncoderConfig())
if err != nil {
panic(err)
}
return enc
}
func NewFormatEncoderWithConfig(cfg EncoderConfig) (sbom.FormatEncoder, error) {
return encoder{
cfg: cfg,
}, nil
}
func DefaultEncoderConfig() EncoderConfig {
return EncoderConfig{
Legacy: false,
}
} }
func (e encoder) ID() sbom.FormatID { func (e encoder) ID() sbom.FormatID {
@ -35,7 +56,7 @@ func (e encoder) Version() string {
} }
func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error { func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
doc := ToFormatModel(s) doc := ToFormatModel(s, e.cfg)
enc := json.NewEncoder(writer) enc := json.NewEncoder(writer)
// prevent > and < from being escaped in the payload // prevent > and < from being escaped in the payload

View File

@ -77,9 +77,8 @@ func TestEncodeFullJSONDocument(t *testing.T) {
Type: pkg.PythonPkg, Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1", FoundBy: "the-cataloger-1",
Language: pkg.Python, Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackage{
Name: "package-1", Name: "package-1",
Version: "1.0.1", Version: "1.0.1",
Files: []pkg.PythonFileRecord{}, Files: []pkg.PythonFileRecord{},
@ -100,8 +99,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
), ),
Type: pkg.DebPkg, Type: pkg.DebPkg,
FoundBy: "the-cataloger-2", FoundBy: "the-cataloger-2",
MetadataType: pkg.DpkgMetadataType, Metadata: pkg.DpkgDBEntry{
Metadata: pkg.DpkgMetadata{
Package: "package-2", Package: "package-2",
Version: "2.0.1", Version: "2.0.1",
Files: []pkg.DpkgFileRecord{}, Files: []pkg.DpkgFileRecord{},

View File

@ -5,9 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
"strings"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/packagemetadata"
"github.com/anchore/syft/syft/license" "github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
@ -60,27 +62,27 @@ func newModelLicensesFromValues(licenses []string) (ml []License) {
} }
func (f *licenses) UnmarshalJSON(b []byte) error { func (f *licenses) UnmarshalJSON(b []byte) error {
var licenses []License var lics []License
if err := json.Unmarshal(b, &licenses); err != nil { if err := json.Unmarshal(b, &lics); err != nil {
var simpleLicense []string var simpleLicense []string
if err := json.Unmarshal(b, &simpleLicense); err != nil { if err := json.Unmarshal(b, &simpleLicense); err != nil {
return fmt.Errorf("unable to unmarshal license: %w", err) return fmt.Errorf("unable to unmarshal license: %w", err)
} }
licenses = newModelLicensesFromValues(simpleLicense) lics = newModelLicensesFromValues(simpleLicense)
} }
*f = licenses *f = lics
return nil return nil
} }
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package. // PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
type PackageCustomData struct { type PackageCustomData struct {
MetadataType pkg.MetadataType `json:"metadataType,omitempty"` MetadataType string `json:"metadataType,omitempty"`
Metadata interface{} `json:"metadata,omitempty"` Metadata any `json:"metadata,omitempty"`
} }
// packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling. // packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling.
type packageMetadataUnpacker struct { type packageMetadataUnpacker struct {
MetadataType pkg.MetadataType `json:"metadataType"` MetadataType string `json:"metadataType"`
Metadata json.RawMessage `json:"metadata"` Metadata json.RawMessage `json:"metadata"`
} }
@ -112,20 +114,46 @@ func (p *Package) UnmarshalJSON(b []byte) error {
} }
func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error { func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error {
p.MetadataType = pkg.CleanMetadataType(unpacker.MetadataType) if unpacker.MetadataType == "" {
typ, ok := pkg.MetadataTypeByName[p.MetadataType]
if ok {
val := reflect.New(typ).Interface()
if len(unpacker.Metadata) > 0 {
if err := json.Unmarshal(unpacker.Metadata, val); err != nil {
return err
}
}
p.Metadata = reflect.ValueOf(val).Elem().Interface()
return nil return nil
} }
// check for legacy correction cases from schema v11 -> v12
ty := unpacker.MetadataType
switch unpacker.MetadataType {
case "HackageMetadataType":
for _, l := range p.Locations {
if strings.HasSuffix(l.RealPath, ".yaml.lock") {
ty = "haskell-hackage-stack-lock-entry"
break
} else if strings.HasSuffix(l.RealPath, ".yaml") {
ty = "haskell-hackage-stack-entry"
break
}
}
case "RpmMetadata":
for _, l := range p.Locations {
if strings.HasSuffix(l.RealPath, ".rpm") {
ty = "rpm-archive"
break
}
}
case "RustCargoPackageMetadata":
var found bool
for _, l := range p.Locations {
if strings.HasSuffix(strings.ToLower(l.RealPath), "cargo.lock") {
ty = "rust-cargo-lock-entry"
found = true
break
}
}
if !found {
ty = "rust-cargo-audit-entry"
}
}
typ := packagemetadata.ReflectTypeFromJSONName(ty)
if typ == nil {
// capture unknown metadata as a generic struct // capture unknown metadata as a generic struct
if len(unpacker.Metadata) > 0 { if len(unpacker.Metadata) > 0 {
var val interface{} var val interface{}
@ -135,9 +163,15 @@ func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error {
p.Metadata = val p.Metadata = val
} }
if p.MetadataType != "" {
return errUnknownMetadataType return errUnknownMetadataType
} }
val := reflect.New(typ).Interface()
if len(unpacker.Metadata) > 0 {
if err := json.Unmarshal(unpacker.Metadata, val); err != nil {
return err
}
}
p.Metadata = reflect.ValueOf(val).Elem().Interface()
return nil return nil
} }

View File

@ -12,7 +12,7 @@ import (
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
func TestUnmarshalPackageGolang(t *testing.T) { func Test_UnmarshalJSON(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
packageData []byte packageData []byte
@ -50,9 +50,9 @@ func TestUnmarshalPackageGolang(t *testing.T) {
} }
}`), }`),
assert: func(p *Package) { assert: func(p *Package) {
assert.NotNil(t, p.Metadata) require.NotNil(t, p.Metadata)
golangMetadata := p.Metadata.(pkg.GolangBinMetadata) golangMetadata := p.Metadata.(pkg.GolangBinaryBuildinfoEntry)
assert.NotEmpty(t, golangMetadata) require.NotEmpty(t, golangMetadata)
assert.Equal(t, "go1.18", golangMetadata.GoCompiledVersion) assert.Equal(t, "go1.18", golangMetadata.GoCompiledVersion)
}, },
}, },
@ -169,6 +169,223 @@ func TestUnmarshalPackageGolang(t *testing.T) {
}, p.Licenses) }, p.Licenses)
}, },
}, },
{
name: "breaking v11-v12 schema change: rpm db vs archive (select db)",
packageData: []byte(`{
"id": "739158935bfffc4d",
"name": "dbus",
"version": "1:1.12.8-12.el8",
"type": "rpm",
"foundBy": "rpm-db-cataloger",
"locations": [
{
"path": "/var/lib/rpm/Packages",
"layerID": "sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a",
"annotations": {
"evidence": "primary"
}
}
],
"licenses": [],
"language": "",
"cpes": [],
"purl": "pkg:rpm/centos/dbus@1.12.8-12.el8?arch=aarch64&epoch=1&upstream=dbus-1.12.8-12.el8.src.rpm&distro=centos-8",
"metadataType": "RpmMetadata",
"metadata": {
"name": "dbus",
"version": "1.12.8",
"epoch": 1,
"architecture": "aarch64",
"release": "12.el8",
"sourceRpm": "dbus-1.12.8-12.el8.src.rpm",
"size": 0,
"vendor": "CentOS",
"modularityLabel": "",
"files": []
}
}
`),
assert: func(p *Package) {
assert.Equal(t, pkg.RpmPkg, p.Type)
assert.Equal(t, reflect.TypeOf(pkg.RpmDBEntry{}).Name(), reflect.TypeOf(p.Metadata).Name())
},
},
{
name: "breaking v11-v12 schema change: rpm db vs archive (select archive)",
packageData: []byte(`{
"id": "739158935bfffc4d",
"name": "dbus",
"version": "1:1.12.8-12.el8",
"type": "rpm",
"foundBy": "rpm-db-cataloger",
"locations": [
{
"path": "/var/cache/dbus-1.12.8-12.el8.rpm",
"layerID": "sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a",
"annotations": {
"evidence": "primary"
}
}
],
"licenses": [],
"language": "",
"cpes": [],
"purl": "pkg:rpm/centos/dbus@1.12.8-12.el8?arch=aarch64&epoch=1&upstream=dbus-1.12.8-12.el8.src.rpm&distro=centos-8",
"metadataType": "RpmMetadata",
"metadata": {
"name": "dbus",
"version": "1.12.8",
"epoch": 1,
"architecture": "aarch64",
"release": "12.el8",
"sourceRpm": "dbus-1.12.8-12.el8.src.rpm",
"size": 0,
"vendor": "CentOS",
"modularityLabel": "",
"files": []
}
}
`),
assert: func(p *Package) {
assert.Equal(t, pkg.RpmPkg, p.Type)
assert.Equal(t, reflect.TypeOf(pkg.RpmArchive{}).Name(), reflect.TypeOf(p.Metadata).Name())
},
},
{
name: "breaking v11-v12 schema change: stack.yaml vs stack.yaml.lock (select stack.yaml)",
packageData: []byte(`{
"id": "46ff1a71f7715f38",
"name": "hspec-discover",
"version": "2.9.4",
"type": "hackage",
"foundBy": "haskell-cataloger",
"locations": [
{
"path": "/stack.yaml",
"annotations": {
"evidence": "primary"
}
}
],
"licenses": [],
"language": "haskell",
"cpes": [
"cpe:2.3:a:hspec-discover:hspec-discover:2.9.4:*:*:*:*:*:*:*",
"cpe:2.3:a:hspec-discover:hspec_discover:2.9.4:*:*:*:*:*:*:*",
"cpe:2.3:a:hspec_discover:hspec-discover:2.9.4:*:*:*:*:*:*:*",
"cpe:2.3:a:hspec_discover:hspec_discover:2.9.4:*:*:*:*:*:*:*",
"cpe:2.3:a:hspec:hspec-discover:2.9.4:*:*:*:*:*:*:*",
"cpe:2.3:a:hspec:hspec_discover:2.9.4:*:*:*:*:*:*:*"
],
"purl": "pkg:hackage/hspec-discover@2.9.4",
"metadataType": "HackageMetadataType",
"metadata": {
"name": "",
"version": "",
"pkgHash": "fbcf49ecfc3d4da53e797fd0275264cba776ffa324ee223e2a3f4ec2d2c9c4a6"
}
}`),
assert: func(p *Package) {
assert.Equal(t, pkg.HackagePkg, p.Type)
assert.Equal(t, reflect.TypeOf(pkg.HackageStackYamlEntry{}).Name(), reflect.TypeOf(p.Metadata).Name())
},
},
{
name: "breaking v11-v12 schema change: stack.yaml vs stack.yaml.lock (select stack.yaml.lock)",
packageData: []byte(`{
"id": "87939e95124ceb92",
"name": "optparse-applicative",
"version": "0.16.1.0",
"type": "hackage",
"foundBy": "haskell-cataloger",
"locations": [
{
"path": "/stack.yaml.lock",
"annotations": {
"evidence": "primary"
}
}
],
"licenses": [],
"language": "haskell",
"cpes": [
"cpe:2.3:a:optparse-applicative:optparse-applicative:0.16.1.0:*:*:*:*:*:*:*",
"cpe:2.3:a:optparse-applicative:optparse_applicative:0.16.1.0:*:*:*:*:*:*:*",
"cpe:2.3:a:optparse_applicative:optparse-applicative:0.16.1.0:*:*:*:*:*:*:*",
"cpe:2.3:a:optparse_applicative:optparse_applicative:0.16.1.0:*:*:*:*:*:*:*",
"cpe:2.3:a:optparse:optparse-applicative:0.16.1.0:*:*:*:*:*:*:*",
"cpe:2.3:a:optparse:optparse_applicative:0.16.1.0:*:*:*:*:*:*:*"
],
"purl": "pkg:hackage/optparse-applicative@0.16.1.0",
"metadataType": "HackageMetadataType",
"metadata": {
"name": "",
"version": "",
"pkgHash": "418c22ed6a19124d457d96bc66bd22c93ac22fad0c7100fe4972bbb4ac989731",
"snapshotURL": "https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/14.yaml"
}
}`),
assert: func(p *Package) {
assert.Equal(t, pkg.HackagePkg, p.Type)
assert.Equal(t, reflect.TypeOf(pkg.HackageStackYamlLockEntry{}).Name(), reflect.TypeOf(p.Metadata).Name())
},
},
{
name: "breaking v11-v12 schema change: rust cargo.lock vs audit (select cargo.lock)",
packageData: []byte(`{
"id": "95124ceb9287939e",
"name": "optpkg",
"version": "1.16.1",
"type": "hackage",
"foundBy": "rust-cataloger",
"locations": [
{
"path": "/cargo.lock",
"annotations": {
"evidence": "primary"
}
}
],
"licenses": [],
"language": "rust",
"cpes": [],
"purl": "pkg:cargo/optpkg@1.16.1",
"metadataType": "RustCargoPackageMetadata",
"metadata": {}
}`),
assert: func(p *Package) {
assert.Equal(t, pkg.HackagePkg, p.Type)
assert.Equal(t, reflect.TypeOf(pkg.RustCargoLockEntry{}).Name(), reflect.TypeOf(p.Metadata).Name())
},
},
{
name: "breaking v11-v12 schema change: rust cargo.lock vs audit (select audit binary)",
packageData: []byte(`{
"id": "95124ceb9287939e",
"name": "optpkg",
"version": "1.16.1",
"type": "hackage",
"foundBy": "rust-cataloger",
"locations": [
{
"path": "/my-binary",
"annotations": {
"evidence": "primary"
}
}
],
"licenses": [],
"language": "rust",
"cpes": [],
"purl": "pkg:cargo/optpkg@1.16.1",
"metadataType": "RustCargoPackageMetadata",
"metadata": {}
}`),
assert: func(p *Package) {
assert.Equal(t, pkg.HackagePkg, p.Type)
assert.Equal(t, reflect.TypeOf(pkg.RustBinaryAuditEntry{}).Name(), reflect.TypeOf(p.Metadata).Name())
},
},
} }
for _, test := range tests { for _, test := range tests {
@ -185,13 +402,12 @@ func Test_unpackMetadata(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
packageData []byte packageData []byte
metadataType pkg.MetadataType wantMetadata any
wantMetadata interface{}
wantErr require.ErrorAssertionFunc wantErr require.ErrorAssertionFunc
}{ }{
{ {
name: "unmarshal package metadata", name: "unmarshal package metadata",
metadataType: pkg.GolangBinMetadataType, wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
packageData: []byte(`{ packageData: []byte(`{
"id": "8b594519bc23da50", "id": "8b594519bc23da50",
"name": "gopkg.in/square/go-jose.v2", "name": "gopkg.in/square/go-jose.v2",
@ -217,7 +433,7 @@ func Test_unpackMetadata(t *testing.T) {
}, },
{ {
name: "can handle package without metadata", name: "can handle package without metadata",
metadataType: "", wantMetadata: nil,
packageData: []byte(`{ packageData: []byte(`{
"id": "8b594519bc23da50", "id": "8b594519bc23da50",
"name": "gopkg.in/square/go-jose.v2", "name": "gopkg.in/square/go-jose.v2",
@ -237,7 +453,7 @@ func Test_unpackMetadata(t *testing.T) {
}, },
{ {
name: "can handle RpmdbMetadata", name: "can handle RpmdbMetadata",
metadataType: pkg.RpmMetadataType, wantMetadata: pkg.RpmDBEntry{},
packageData: []byte(`{ packageData: []byte(`{
"id": "4ac699c3b8fe1835", "id": "4ac699c3b8fe1835",
"name": "acl", "name": "acl",
@ -273,7 +489,6 @@ func Test_unpackMetadata(t *testing.T) {
}, },
{ {
name: "bad metadata type is an error", name: "bad metadata type is an error",
metadataType: "BOGOSITY",
wantErr: require.Error, wantErr: require.Error,
packageData: []byte(`{ packageData: []byte(`{
"id": "8b594519bc23da50", "id": "8b594519bc23da50",
@ -302,7 +517,6 @@ func Test_unpackMetadata(t *testing.T) {
} }
}`), }`),
wantErr: require.Error, wantErr: require.Error,
metadataType: "NewMetadataType",
wantMetadata: map[string]interface{}{ wantMetadata: map[string]interface{}{
"thing": "thing-1", "thing": "thing-1",
}, },
@ -312,28 +526,24 @@ func Test_unpackMetadata(t *testing.T) {
packageData: []byte(`{ packageData: []byte(`{
"metadataType": "GolangBinMetadata" "metadataType": "GolangBinMetadata"
}`), }`),
metadataType: pkg.GolangBinMetadataType, wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
wantMetadata: pkg.GolangBinMetadata{},
}, },
{ {
name: "can handle package with golang bin metadata type", name: "can handle package with golang bin metadata type",
packageData: []byte(`{ packageData: []byte(`{
"metadataType": "GolangBinMetadata" "metadataType": "GolangBinMetadata"
}`), }`),
metadataType: pkg.GolangBinMetadataType, wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
wantMetadata: pkg.GolangBinMetadata{},
}, },
{ {
name: "can handle package with unknonwn metadata type and missing metadata", name: "can handle package with unknown metadata type and missing metadata",
packageData: []byte(`{ packageData: []byte(`{
"metadataType": "BadMetadata" "metadataType": "BadMetadata"
}`), }`),
wantErr: require.Error, wantErr: require.Error,
metadataType: "BadMetadata",
wantMetadata: nil,
}, },
{ {
name: "can handle package with unknonwn metadata type and metadata", name: "can handle package with unknown metadata type and metadata",
packageData: []byte(`{ packageData: []byte(`{
"metadataType": "BadMetadata", "metadataType": "BadMetadata",
"metadata": { "metadata": {
@ -341,10 +551,6 @@ func Test_unpackMetadata(t *testing.T) {
} }
}`), }`),
wantErr: require.Error, wantErr: require.Error,
metadataType: "BadMetadata",
wantMetadata: map[string]interface{}{
"random": "thing",
},
}, },
} }
@ -355,19 +561,18 @@ func Test_unpackMetadata(t *testing.T) {
} }
p := &Package{} p := &Package{}
var basic PackageBasicData
require.NoError(t, json.Unmarshal(test.packageData, &basic))
p.PackageBasicData = basic
var unpacker packageMetadataUnpacker var unpacker packageMetadataUnpacker
require.NoError(t, json.Unmarshal(test.packageData, &unpacker)) require.NoError(t, json.Unmarshal(test.packageData, &unpacker))
err := unpackPkgMetadata(p, unpacker) err := unpackPkgMetadata(p, unpacker)
assert.Equal(t, test.metadataType, p.MetadataType)
test.wantErr(t, err) test.wantErr(t, err)
if test.wantMetadata != nil { if test.wantMetadata != nil {
assert.True(t, reflect.DeepEqual(test.wantMetadata, p.Metadata)) if p.Metadata == nil {
t.Fatalf("expected metadata to be populated")
return
}
assert.Equal(t, reflect.TypeOf(test.wantMetadata).Name(), reflect.TypeOf(p.Metadata).Name())
} }
}) })
} }

View File

@ -0,0 +1 @@
FROM alpine:3.18.4@sha256:48d9183eb12a05c99bcc0bf44a003607b8e941e1d4f41f9ad12bdcc4b5672f86

View File

@ -1,7 +1,7 @@
{ {
"artifacts": [ "artifacts": [
{ {
"id": "9265397e5e15168a", "id": "fb6bef15e281ea43",
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
"type": "python", "type": "python",
@ -25,7 +25,7 @@
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*" "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
], ],
"purl": "a-purl-2", "purl": "a-purl-2",
"metadataType": "PythonPackageMetadata", "metadataType": "python-package",
"metadata": { "metadata": {
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
@ -41,7 +41,7 @@
} }
}, },
{ {
"id": "ad5013466727018f", "id": "39392bb5e270f669",
"name": "package-2", "name": "package-2",
"version": "2.0.1", "version": "2.0.1",
"type": "deb", "type": "deb",
@ -57,7 +57,7 @@
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*" "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
], ],
"purl": "pkg:deb/debian/package-2@2.0.1", "purl": "pkg:deb/debian/package-2@2.0.1",
"metadataType": "DpkgMetadata", "metadataType": "dpkg-db-entry",
"metadata": { "metadata": {
"package": "package-2", "package": "package-2",
"source": "", "source": "",

View File

@ -1,7 +1,7 @@
{ {
"artifacts": [ "artifacts": [
{ {
"id": "271e49ba46e0b601", "id": "d748d4614750058d",
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
"type": "python", "type": "python",
@ -25,7 +25,7 @@
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*" "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
], ],
"purl": "a-purl-1", "purl": "a-purl-1",
"metadataType": "PythonPackageMetadata", "metadataType": "python-package",
"metadata": { "metadata": {
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
@ -36,7 +36,7 @@
} }
}, },
{ {
"id": "aa0ca2c331576dfd", "id": "fa4ec37eccd65756",
"name": "package-2", "name": "package-2",
"version": "2.0.1", "version": "2.0.1",
"type": "deb", "type": "deb",
@ -52,7 +52,7 @@
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*" "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
], ],
"purl": "a-purl-2", "purl": "a-purl-2",
"metadataType": "DpkgMetadata", "metadataType": "dpkg-db-entry",
"metadata": { "metadata": {
"package": "package-2", "package": "package-2",
"source": "", "source": "",

View File

@ -1,7 +1,7 @@
{ {
"artifacts": [ "artifacts": [
{ {
"id": "125840abc1c66dd7", "id": "80210ebcba92e632",
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
"type": "python", "type": "python",
@ -26,7 +26,7 @@
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*" "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
], ],
"purl": "a-purl-1", "purl": "a-purl-1",
"metadataType": "PythonPackageMetadata", "metadataType": "python-package",
"metadata": { "metadata": {
"name": "package-1", "name": "package-1",
"version": "1.0.1", "version": "1.0.1",
@ -37,7 +37,7 @@
} }
}, },
{ {
"id": "f27313b22a5ba330", "id": "4b756c6f6fb127a3",
"name": "package-2", "name": "package-2",
"version": "2.0.1", "version": "2.0.1",
"type": "deb", "type": "deb",
@ -54,7 +54,7 @@
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*" "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
], ],
"purl": "pkg:deb/debian/package-2@2.0.1", "purl": "pkg:deb/debian/package-2@2.0.1",
"metadataType": "DpkgMetadata", "metadataType": "dpkg-db-entry",
"metadata": { "metadata": {
"package": "package-2", "package": "package-2",
"source": "", "source": "",

View File

@ -12,6 +12,7 @@ import (
"github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/format/syftjson/model" "github.com/anchore/syft/syft/format/syftjson/model"
"github.com/anchore/syft/syft/internal/packagemetadata"
"github.com/anchore/syft/syft/internal/sourcemetadata" "github.com/anchore/syft/syft/internal/sourcemetadata"
"github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@ -20,9 +21,9 @@ import (
) )
// ToFormatModel transforms the sbom import a format-specific model. // ToFormatModel transforms the sbom import a format-specific model.
func ToFormatModel(s sbom.SBOM) model.Document { func ToFormatModel(s sbom.SBOM, cfg EncoderConfig) model.Document {
return model.Document{ return model.Document{
Artifacts: toPackageModels(s.Artifacts.Packages), Artifacts: toPackageModels(s.Artifacts.Packages, cfg),
ArtifactRelationships: toRelationshipModel(s.Relationships), ArtifactRelationships: toRelationshipModel(s.Relationships),
Files: toFile(s), Files: toFile(s),
Secrets: toSecrets(s.Artifacts.Secrets), Secrets: toSecrets(s.Artifacts.Secrets),
@ -196,13 +197,13 @@ func toFileType(ty stereoscopeFile.Type) string {
} }
} }
func toPackageModels(catalog *pkg.Collection) []model.Package { func toPackageModels(catalog *pkg.Collection, cfg EncoderConfig) []model.Package {
artifacts := make([]model.Package, 0) artifacts := make([]model.Package, 0)
if catalog == nil { if catalog == nil {
return artifacts return artifacts
} }
for _, p := range catalog.Sorted() { for _, p := range catalog.Sorted() {
artifacts = append(artifacts, toPackageModel(p)) artifacts = append(artifacts, toPackageModel(p, cfg))
} }
return artifacts return artifacts
} }
@ -233,7 +234,7 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
} }
// toPackageModel crates a new Package from the given pkg.Package. // toPackageModel crates a new Package from the given pkg.Package.
func toPackageModel(p pkg.Package) model.Package { func toPackageModel(p pkg.Package, cfg EncoderConfig) model.Package {
var cpes = make([]string, len(p.CPEs)) var cpes = make([]string, len(p.CPEs))
for i, c := range p.CPEs { for i, c := range p.CPEs {
cpes[i] = cpe.String(c) cpes[i] = cpe.String(c)
@ -246,6 +247,13 @@ func toPackageModel(p pkg.Package) model.Package {
licenses = toLicenseModel(p.Licenses.ToSlice()) licenses = toLicenseModel(p.Licenses.ToSlice())
} }
var ty string
if cfg.Legacy {
ty = packagemetadata.JSONLegacyName(p.Metadata)
} else {
ty = packagemetadata.JSONName(p.Metadata)
}
return model.Package{ return model.Package{
PackageBasicData: model.PackageBasicData{ PackageBasicData: model.PackageBasicData{
ID: string(p.ID()), ID: string(p.ID()),
@ -260,7 +268,7 @@ func toPackageModel(p pkg.Package) model.Package {
PURL: p.PURL, PURL: p.PURL,
}, },
PackageCustomData: model.PackageCustomData{ PackageCustomData: model.PackageCustomData{
MetadataType: p.MetadataType, MetadataType: ty,
Metadata: p.Metadata, Metadata: p.Metadata,
}, },
} }

View File

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -11,6 +13,7 @@ import (
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/format/syftjson/model" "github.com/anchore/syft/syft/format/syftjson/model"
"github.com/anchore/syft/syft/internal/sourcemetadata" "github.com/anchore/syft/syft/internal/sourcemetadata"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
) )
@ -303,3 +306,48 @@ func Test_toFileMetadataEntry(t *testing.T) {
}) })
} }
} }
func Test_toPackageModel_metadataType(t *testing.T) {
tests := []struct {
name string
p pkg.Package
cfg EncoderConfig
want model.Package
}{
{
name: "empty config",
p: pkg.Package{
Metadata: pkg.RpmDBEntry{},
},
cfg: EncoderConfig{},
want: model.Package{
PackageCustomData: model.PackageCustomData{
MetadataType: "rpm-db-entry",
Metadata: pkg.RpmDBEntry{},
},
},
},
{
name: "legacy config",
p: pkg.Package{
Metadata: pkg.RpmDBEntry{},
},
cfg: EncoderConfig{
Legacy: true,
},
want: model.Package{
PackageCustomData: model.PackageCustomData{
MetadataType: "RpmMetadata",
Metadata: pkg.RpmDBEntry{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if d := cmp.Diff(tt.want, toPackageModel(tt.p, tt.cfg), cmpopts.EquateEmpty()); d != "" {
t.Errorf("unexpected package (-want +got):\n%s", d)
}
})
}
}

View File

@ -320,7 +320,6 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
Type: p.Type, Type: p.Type,
CPEs: cpes, CPEs: cpes,
PURL: p.PURL, PURL: p.PURL,
MetadataType: p.MetadataType,
Metadata: p.Metadata, Metadata: p.Metadata,
} }

View File

@ -19,6 +19,7 @@ const ID sbom.FormatID = "template"
type EncoderConfig struct { type EncoderConfig struct {
TemplatePath string TemplatePath string
syftjson.EncoderConfig
} }
type encoder struct { type encoder struct {
@ -52,7 +53,9 @@ func NewFormatEncoder(cfg EncoderConfig) (sbom.FormatEncoder, error) {
} }
func DefaultEncoderConfig() EncoderConfig { func DefaultEncoderConfig() EncoderConfig {
return EncoderConfig{} return EncoderConfig{
EncoderConfig: syftjson.DefaultEncoderConfig(),
}
} }
func (e encoder) ID() sbom.FormatID { func (e encoder) ID() sbom.FormatID {
@ -87,6 +90,6 @@ func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
return fmt.Errorf("unable to parse template: %w", err) return fmt.Errorf("unable to parse template: %w", err)
} }
doc := syftjson.ToFormatModel(s) doc := syftjson.ToFormatModel(s, e.cfg.EncoderConfig)
return tmpl.Execute(writer, doc) return tmpl.Execute(writer, doc)
} }

View File

@ -11,6 +11,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/iancoleman/strcase"
"github.com/invopop/jsonschema" "github.com/invopop/jsonschema"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
@ -35,12 +36,22 @@ func schemaID() jsonschema.ID {
return jsonschema.ID(fmt.Sprintf("anchore.io/schema/syft/json/%s", internal.JSONSchemaVersion)) return jsonschema.ID(fmt.Sprintf("anchore.io/schema/syft/json/%s", internal.JSONSchemaVersion))
} }
func assembleTypeContainer(items []any) any { func assembleTypeContainer(items []any) (any, map[string]string) {
structFields := make([]reflect.StructField, len(items)) structFields := make([]reflect.StructField, len(items))
mapping := make(map[string]string, len(items))
typesMissingNames := make([]reflect.Type, 0)
for i, item := range items { for i, item := range items {
itemType := reflect.TypeOf(item) itemType := reflect.TypeOf(item)
fieldName := itemType.Name()
jsonName := packagemetadata.JSONName(item)
fieldName := strcase.ToCamel(jsonName)
if jsonName == "" {
typesMissingNames = append(typesMissingNames, itemType)
continue
}
mapping[itemType.Name()] = fieldName
structFields[i] = reflect.StructField{ structFields[i] = reflect.StructField{
Name: fieldName, Name: fieldName,
@ -48,8 +59,16 @@ func assembleTypeContainer(items []any) any {
} }
} }
if len(typesMissingNames) > 0 {
fmt.Println("the following types are missing JSON names (manually curated in ./syft/internal/packagemetadata/names.go):")
for _, t := range typesMissingNames {
fmt.Println(" - ", t.Name())
}
os.Exit(1)
}
structType := reflect.StructOf(structFields) structType := reflect.StructOf(structFields)
return reflect.New(structType).Elem().Interface() return reflect.New(structType).Elem().Interface(), mapping
} }
func build() *jsonschema.Schema { func build() *jsonschema.Schema {
@ -61,7 +80,7 @@ func build() *jsonschema.Schema {
}, },
} }
pkgMetadataContainer := assembleTypeContainer(packagemetadata.AllTypes()) pkgMetadataContainer, pkgMetadataMapping := assembleTypeContainer(packagemetadata.AllTypes())
pkgMetadataContainerType := reflect.TypeOf(pkgMetadataContainer) pkgMetadataContainerType := reflect.TypeOf(pkgMetadataContainer)
// srcMetadataContainer := assembleTypeContainer(sourcemetadata.AllTypes()) // srcMetadataContainer := assembleTypeContainer(sourcemetadata.AllTypes())
@ -73,17 +92,23 @@ func build() *jsonschema.Schema {
// TODO: add source metadata types // TODO: add source metadata types
// inject the definitions of all packages metadatas into the schema definitions // inject the definitions of all packages metadata into the schema definitions
var metadataNames []string var metadataNames []string
for name, definition := range pkgMetadataSchema.Definitions { for typeName, definition := range pkgMetadataSchema.Definitions {
if name == pkgMetadataContainerType.Name() { if typeName == pkgMetadataContainerType.Name() {
// ignore the definition for the fake container // ignore the definition for the fake container
continue continue
} }
documentSchema.Definitions[name] = definition
if strings.HasSuffix(name, "Metadata") { displayName, ok := pkgMetadataMapping[typeName]
metadataNames = append(metadataNames, name) if ok {
// this is a package metadata type...
documentSchema.Definitions[displayName] = definition
metadataNames = append(metadataNames, displayName)
} else {
// this is a type that the metadata type uses (e.g. DpkgFileRecord)
documentSchema.Definitions[typeName] = definition
} }
} }

View File

@ -0,0 +1,69 @@
package packagemetadata
import (
"reflect"
"testing"
)
type CompletionTester struct {
saw []any
valid []any
ignore []any
}
func NewCompletionTester(t testing.TB, ignore ...any) *CompletionTester {
tester := &CompletionTester{
valid: AllTypes(),
ignore: ignore,
}
t.Cleanup(func() {
t.Helper()
tester.validate(t)
})
return tester
}
func (tr *CompletionTester) Tested(t testing.TB, m any) {
t.Helper()
if m == nil {
return
}
if len(tr.valid) == 0 {
t.Fatal("no valid metadata types to test against")
}
ty := reflect.TypeOf(m)
for _, v := range tr.valid {
if reflect.TypeOf(v) == ty {
tr.saw = append(tr.saw, m)
return
}
}
t.Fatalf("tested metadata type is not valid: %s", ty.Name())
}
func (tr *CompletionTester) validate(t testing.TB) {
t.Helper()
count := make(map[reflect.Type]int)
for _, m := range tr.saw {
count[reflect.TypeOf(m)]++
}
validations:
for _, v := range tr.valid {
ty := reflect.TypeOf(v)
for _, ignore := range tr.ignore {
if ty == reflect.TypeOf(ignore) {
// skip ignored types
continue validations
}
}
if c, exists := count[ty]; c == 0 || !exists {
t.Errorf("metadata type %s is not covered by a test", ty.Name())
}
}
}

View File

@ -14,8 +14,12 @@ import (
"github.com/scylladb/go-set/strset" "github.com/scylladb/go-set/strset"
) )
var metadataExceptions = strset.New( // these are names of struct types in the pkg package that are not metadata types (thus should not be in the JSON schema)
"FileMetadata", var knownNonMetadataTypeNames = strset.New(
"Package",
"Collection",
"License",
"LicenseSet",
) )
func DiscoverTypeNames() ([]string, error) { func DiscoverTypeNames() ([]string, error) {
@ -67,9 +71,9 @@ func findMetadataDefinitionNames(paths ...string) ([]string, error) {
strNames := names.List() strNames := names.List()
sort.Strings(strNames) sort.Strings(strNames)
// note: 30 is a point-in-time gut check. This number could be updated if new metadata definitions are added, but is not required. // note: 35 is a point-in-time gut check. This number could be updated if new metadata definitions are added, but is not required.
// it is really intended to catch any major issues with the generation process that would generate, say, 0 definitions. // it is really intended to catch any major issues with the generation process that would generate, say, 0 definitions.
if len(strNames) < 30 { if len(strNames) < 35 {
return nil, fmt.Errorf("not enough metadata definitions found (discovered: " + fmt.Sprintf("%d", len(strNames)) + ")") return nil, fmt.Errorf("not enough metadata definitions found (discovered: " + fmt.Sprintf("%d", len(strNames)) + ")")
} }
@ -101,25 +105,53 @@ func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error)
continue continue
} }
structType, ok := spec.Type.(*ast.StructType) name := spec.Name.String()
if !ok {
// only look for exported types
if !isMetadataTypeCandidate(name) {
continue continue
} }
// check if the struct type ends with "Metadata" structType := extractStructType(spec.Type)
name := spec.Name.String() if structType == nil {
continue
}
// only look for exported types that end with "Metadata"
if isMetadataTypeCandidate(name) {
// print the full declaration of the struct type
metadataDefinitions = append(metadataDefinitions, name) metadataDefinitions = append(metadataDefinitions, name)
usedTypeNames = append(usedTypeNames, typeNamesUsedInStruct(structType)...) usedTypeNames = append(usedTypeNames, typeNamesUsedInStruct(structType)...)
} }
} }
}
return metadataDefinitions, usedTypeNames, nil return metadataDefinitions, usedTypeNames, nil
} }
func extractStructType(exp ast.Expr) *ast.StructType {
var structType *ast.StructType
switch ty := exp.(type) {
case *ast.StructType:
// this is a standard definition:
// type FooMetadata struct { ... }
structType = ty
case *ast.Ident:
if ty.Obj == nil {
return nil
}
// this might be a type created from another type:
// type FooMetadata BarMetadata
// ... but we need to check that the other type definition is a struct type
typeSpec, ok := ty.Obj.Decl.(*ast.TypeSpec)
if !ok {
return nil
}
nestedStructType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
return nil
}
structType = nestedStructType
}
return structType
}
func typeNamesUsedInStruct(structType *ast.StructType) []string { func typeNamesUsedInStruct(structType *ast.StructType) []string {
// recursively find all type names used in the struct type // recursively find all type names used in the struct type
var names []string var names []string
@ -144,7 +176,6 @@ func typeNamesUsedInStruct(structType *ast.StructType) []string {
func isMetadataTypeCandidate(name string) bool { func isMetadataTypeCandidate(name string) bool {
return len(name) > 0 && return len(name) > 0 &&
strings.HasSuffix(name, "Metadata") &&
unicode.IsUpper(rune(name[0])) && // must be exported unicode.IsUpper(rune(name[0])) && // must be exported
!metadataExceptions.Has(name) !knownNonMetadataTypeNames.Has(name)
} }

View File

@ -0,0 +1,41 @@
package packagemetadata
import (
"testing"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDiscoverTypeNames_byExample(t *testing.T) {
tests := []struct {
name string
want string
}{
{
name: "go case",
want: "ApkDBEntry",
},
{
name: "type shadowed with another type",
want: "RpmDBEntry",
},
{
name: "type shadows another type",
want: "RpmArchive",
},
}
got, err := DiscoverTypeNames()
require.NotEmpty(t, got)
gotSet := strset.New(got...)
require.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.True(t, gotSet.Has(tt.want))
require.NoError(t, err)
})
}
}

View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"github.com/dave/jennifer/jen" "github.com/dave/jennifer/jen"
@ -22,6 +23,10 @@ func main() {
panic(fmt.Errorf("unable to get all metadata type names: %w", err)) panic(fmt.Errorf("unable to get all metadata type names: %w", err))
} }
// for _, typeName := range typeNames {
// fmt.Printf(" - %s\n", typeName)
//}
fmt.Printf("updating package metadata type list with %+v types\n", len(typeNames)) fmt.Printf("updating package metadata type list with %+v types\n", len(typeNames))
f := jen.NewFile("packagemetadata") f := jen.NewFile("packagemetadata")
@ -45,6 +50,12 @@ func main() {
if err != nil { if err != nil {
panic(fmt.Errorf("unable to open file: %w", err)) panic(fmt.Errorf("unable to open file: %w", err))
} }
// fix a little whitespacing
rendered = strings.ReplaceAll(rendered, ",", ",\n")
rendered = strings.ReplaceAll(rendered, "[]any{", "[]any{\n")
rendered = strings.ReplaceAll(rendered, "}}\n}", "},\n}\n}")
_, err = fh.WriteString(rendered) _, err = fh.WriteString(rendered)
if err != nil { if err != nil {
panic(fmt.Errorf("unable to write file: %w", err)) panic(fmt.Errorf("unable to write file: %w", err))

View File

@ -6,5 +6,43 @@ import "github.com/anchore/syft/syft/pkg"
// AllTypes returns a list of all pkg metadata types that syft supports (that are represented in the pkg.Package.Metadata field). // AllTypes returns a list of all pkg metadata types that syft supports (that are represented in the pkg.Package.Metadata field).
func AllTypes() []any { func AllTypes() []any {
return []any{pkg.AlpmMetadata{}, pkg.ApkMetadata{}, pkg.BinaryMetadata{}, pkg.CargoPackageMetadata{}, pkg.CocoapodsMetadata{}, pkg.ConanLockMetadata{}, pkg.ConanMetadata{}, pkg.DartPubMetadata{}, pkg.DotnetDepsMetadata{}, pkg.DotnetPortableExecutableMetadata{}, pkg.DpkgMetadata{}, pkg.GemMetadata{}, pkg.GolangBinMetadata{}, pkg.GolangModMetadata{}, pkg.HackageMetadata{}, pkg.JavaMetadata{}, pkg.KbPackageMetadata{}, pkg.LinuxKernelMetadata{}, pkg.LinuxKernelModuleMetadata{}, pkg.MixLockMetadata{}, pkg.NixStoreMetadata{}, pkg.NpmPackageJSONMetadata{}, pkg.NpmPackageLockJSONMetadata{}, pkg.PhpComposerJSONMetadata{}, pkg.PortageMetadata{}, pkg.PythonPackageMetadata{}, pkg.PythonPipfileLockMetadata{}, pkg.PythonRequirementsMetadata{}, pkg.RDescriptionFileMetadata{}, pkg.RebarLockMetadata{}, pkg.RpmMetadata{}, pkg.SwiftPackageManagerMetadata{}} return []any{
pkg.AlpmDBEntry{},
pkg.ApkDBEntry{},
pkg.BinarySignature{},
pkg.CocoaPodfileLockEntry{},
pkg.ConanLockEntry{},
pkg.ConanfileEntry{},
pkg.ConaninfoEntry{},
pkg.DartPubspecLockEntry{},
pkg.DotnetDepsEntry{},
pkg.DotnetPortableExecutableEntry{},
pkg.DpkgDBEntry{},
pkg.ElixirMixLockEntry{},
pkg.ErlangRebarLockEntry{},
pkg.GolangBinaryBuildinfoEntry{},
pkg.GolangModuleEntry{},
pkg.HackageStackYamlEntry{},
pkg.HackageStackYamlLockEntry{},
pkg.JavaArchive{},
pkg.LinuxKernel{},
pkg.LinuxKernelModule{},
pkg.MicrosoftKbPatch{},
pkg.NixStoreEntry{},
pkg.NpmPackage{},
pkg.NpmPackageLockEntry{},
pkg.PhpComposerInstalledEntry{},
pkg.PhpComposerLockEntry{},
pkg.PortageEntry{},
pkg.PythonPackage{},
pkg.PythonPipfileLockEntry{},
pkg.PythonRequirementsEntry{},
pkg.RDescription{},
pkg.RpmArchive{},
pkg.RpmDBEntry{},
pkg.RubyGemspec{},
pkg.RustBinaryAuditEntry{},
pkg.RustCargoLockEntry{},
pkg.SwiftPackageManagerResolvedEntry{},
}
} }

View File

@ -2,12 +2,141 @@ package packagemetadata
import ( import (
"reflect" "reflect"
"strings"
"github.com/anchore/syft/syft/pkg"
) )
func AllNames() []string { type jsonType struct {
ty any
name string
legacyNames []string
noLookupLegacyName string // legacy name that conflict with other types, thus should not affect the lookup
}
func jsonNames(ty any, name string, legacyNames ...string) jsonType {
return jsonType{
ty: ty,
name: name,
legacyNames: expandLegacyNameVariants(legacyNames...),
}
}
func jsonNamesWithoutLookup(ty any, name string, noLookupLegacyName string) jsonType {
return jsonType{
ty: ty,
name: name,
noLookupLegacyName: noLookupLegacyName,
}
}
type jsonTypeMapping struct {
typeToName map[reflect.Type]string
typeToLegacyName map[reflect.Type]string
nameToType map[string]reflect.Type
}
func makeJSONTypes(types ...jsonType) jsonTypeMapping {
out := jsonTypeMapping{
typeToName: make(map[reflect.Type]string),
typeToLegacyName: make(map[reflect.Type]string),
nameToType: make(map[string]reflect.Type),
}
for _, t := range types {
typ := reflect.TypeOf(t.ty)
out.typeToName[typ] = t.name
if len(t.noLookupLegacyName) > 0 {
out.typeToLegacyName[typ] = t.noLookupLegacyName
} else if len(t.legacyNames) > 0 {
out.typeToLegacyName[typ] = t.legacyNames[0]
}
out.nameToType[strings.ToLower(t.name)] = typ
for _, name := range t.legacyNames {
out.nameToType[strings.ToLower(name)] = typ
}
}
return out
}
// jsonNameFromType is lookup of all known package metadata types to their current JSON name and all previously known aliases.
// It is important that if a name needs to change that the old name is kept in this map (as an alias) for backwards
// compatibility to support decoding older JSON documents.
var jsonTypes = makeJSONTypes(
jsonNames(pkg.AlpmDBEntry{}, "alpm-db-entry", "AlpmMetadata"),
jsonNames(pkg.ApkDBEntry{}, "apk-db-entry", "ApkMetadata"),
jsonNames(pkg.BinarySignature{}, "binary-signature", "BinaryMetadata"),
jsonNames(pkg.CocoaPodfileLockEntry{}, "cocoa-podfile-lock-entry", "CocoapodsMetadataType"),
jsonNames(pkg.ConanLockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"),
jsonNames(pkg.ConanfileEntry{}, "c-conan-file-entry", "ConanMetadataType"),
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
jsonNames(pkg.DotnetDepsEntry{}, "dotnet-deps-entry", "DotnetDepsMetadata"),
jsonNames(pkg.DotnetPortableExecutableEntry{}, "dotnet-portable-executable-entry"),
jsonNames(pkg.DpkgDBEntry{}, "dpkg-db-entry", "DpkgMetadata"),
jsonNames(pkg.RubyGemspec{}, "ruby-gemspec", "GemMetadata"),
jsonNames(pkg.GolangBinaryBuildinfoEntry{}, "go-module-buildinfo-entry", "GolangBinMetadata", "GolangMetadata"),
jsonNames(pkg.GolangModuleEntry{}, "go-module-entry", "GolangModMetadata"),
jsonNames(pkg.HackageStackYamlLockEntry{}, "haskell-hackage-stack-lock-entry", "HackageMetadataType"),
jsonNamesWithoutLookup(pkg.HackageStackYamlEntry{}, "haskell-hackage-stack-entry", "HackageMetadataType"), // the legacy value is split into two types, where the other is preferred
jsonNames(pkg.JavaArchive{}, "java-archive", "JavaMetadata"),
jsonNames(pkg.MicrosoftKbPatch{}, "microsoft-kb-patch", "KbPatchMetadata"),
jsonNames(pkg.LinuxKernel{}, "linux-kernel-archive", "LinuxKernel"),
jsonNames(pkg.LinuxKernelModule{}, "linux-kernel-module", "LinuxKernelModule"),
jsonNames(pkg.ElixirMixLockEntry{}, "elixir-mix-lock-entry", "MixLockMetadataType"),
jsonNames(pkg.NixStoreEntry{}, "nix-store-entry", "NixStoreMetadata"),
jsonNames(pkg.NpmPackage{}, "javascript-npm-package", "NpmPackageJsonMetadata"),
jsonNames(pkg.NpmPackageLockEntry{}, "javascript-npm-package-lock-entry", "NpmPackageLockJsonMetadata"),
jsonNames(pkg.PhpComposerLockEntry{}, "php-composer-lock-entry", "PhpComposerJsonMetadata"),
jsonNamesWithoutLookup(pkg.PhpComposerInstalledEntry{}, "php-composer-installed-entry", "PhpComposerJsonMetadata"), // the legacy value is split into two types, where the other is preferred
jsonNames(pkg.PortageEntry{}, "portage-db-entry", "PortageMetadata"),
jsonNames(pkg.PythonPackage{}, "python-package", "PythonPackageMetadata"),
jsonNames(pkg.PythonPipfileLockEntry{}, "python-pipfile-lock-entry", "PythonPipfileLockMetadata"),
jsonNames(pkg.PythonRequirementsEntry{}, "python-pip-requirements-entry", "PythonRequirementsMetadata"),
jsonNames(pkg.ErlangRebarLockEntry{}, "erlang-rebar-lock-entry", "RebarLockMetadataType"),
jsonNames(pkg.RDescription{}, "r-description", "RDescriptionFileMetadataType"),
jsonNames(pkg.RpmDBEntry{}, "rpm-db-entry", "RpmMetadata", "RpmdbMetadata"),
jsonNamesWithoutLookup(pkg.RpmArchive{}, "rpm-archive", "RpmMetadata"), // the legacy value is split into two types, where the other is preferred
jsonNames(pkg.SwiftPackageManagerResolvedEntry{}, "swift-package-manager-lock-entry", "SwiftPackageManagerMetadata"),
jsonNames(pkg.RustCargoLockEntry{}, "rust-cargo-lock-entry", "RustCargoPackageMetadata"),
jsonNamesWithoutLookup(pkg.RustBinaryAuditEntry{}, "rust-cargo-audit-entry", "RustCargoPackageMetadata"), // the legacy value is split into two types, where the other is preferred
)
func expandLegacyNameVariants(names ...string) []string {
var candidates []string
for _, name := range names {
candidates = append(candidates, name)
if strings.HasSuffix(name, "MetadataType") {
candidates = append(candidates, strings.TrimSuffix(name, "Type"))
} else if strings.HasSuffix(name, "Metadata") {
candidates = append(candidates, name+"Type")
}
}
return candidates
}
func AllTypeNames() []string {
names := make([]string, 0) names := make([]string, 0)
for _, t := range AllTypes() { for _, t := range AllTypes() {
names = append(names, reflect.TypeOf(t).Name()) names = append(names, reflect.TypeOf(t).Name())
} }
return names return names
} }
func JSONName(metadata any) string {
if name, exists := jsonTypes.typeToName[reflect.TypeOf(metadata)]; exists {
return name
}
return ""
}
func JSONLegacyName(metadata any) string {
if name, exists := jsonTypes.typeToLegacyName[reflect.TypeOf(metadata)]; exists {
return name
}
return JSONName(metadata)
}
func ReflectTypeFromJSONName(name string) reflect.Type {
name = strings.ToLower(name)
return jsonTypes.nameToType[name]
}

View File

@ -1,11 +1,14 @@
package packagemetadata package packagemetadata
import ( import (
"reflect"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/pkg"
) )
func TestAllNames(t *testing.T) { func TestAllNames(t *testing.T) {
@ -14,7 +17,7 @@ func TestAllNames(t *testing.T) {
expected, err := DiscoverTypeNames() expected, err := DiscoverTypeNames()
require.NoError(t, err) require.NoError(t, err)
actual := AllNames() actual := AllTypeNames()
// ensure that the codebase (from ast analysis) reflects the latest code generated state // ensure that the codebase (from ast analysis) reflects the latest code generated state
if !assert.ElementsMatch(t, expected, actual) { if !assert.ElementsMatch(t, expected, actual) {
@ -22,4 +25,469 @@ func TestAllNames(t *testing.T) {
t.Log("did you add a new pkg.*Metadata type without updating the JSON schema?") t.Log("did you add a new pkg.*Metadata type without updating the JSON schema?")
t.Log("if so, you need to update the schema version and regenerate the JSON schema (make generate-json-schema)") t.Log("if so, you need to update the schema version and regenerate the JSON schema (make generate-json-schema)")
} }
for _, ty := range AllTypes() {
assert.NotEmpty(t, JSONName(ty), "metadata type %q does not have a JSON name", reflect.TypeOf(ty).Name())
}
}
func TestReflectTypeFromJSONName(t *testing.T) {
tests := []struct {
name string
lookup string
wantRecord reflect.Type
}{
{
name: "exact match on ID",
lookup: "rust-cargo-lock-entry",
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
{
name: "exact match on former name",
lookup: "RustCargoPackageMetadata",
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
{
name: "case insensitive on ID",
lookup: "RUST-CARGO-lock-entrY",
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
{
name: "case insensitive on alias",
lookup: "rusTcArgopacKagEmEtadATa",
wantRecord: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
{
name: "consistent override",
// there are two correct answers for this -- we should always get the same answer.
lookup: "HackageMetadataType",
wantRecord: reflect.TypeOf(pkg.HackageStackYamlLockEntry{}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ReflectTypeFromJSONName(tt.lookup)
require.NotNil(t, got)
assert.Equal(t, tt.wantRecord.Name(), got.Name())
})
}
}
func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) {
testCases := []struct {
name string
input string
expected reflect.Type
}{
// these cases are always 1:1
{
name: "map pkg.AlpmDBEntry struct type",
input: "AlpmMetadata",
expected: reflect.TypeOf(pkg.AlpmDBEntry{}),
},
{
name: "map pkg.ApkDBEntry struct type",
input: "ApkMetadata",
expected: reflect.TypeOf(pkg.ApkDBEntry{}),
},
{
name: "map pkg.BinarySignature struct type",
input: "BinaryMetadata",
expected: reflect.TypeOf(pkg.BinarySignature{}),
},
{
name: "map pkg.CocoaPodfileLockEntry struct type",
input: "CocoapodsMetadataType",
expected: reflect.TypeOf(pkg.CocoaPodfileLockEntry{}),
},
{
name: "map pkg.ConanLockEntry struct type",
input: "ConanLockMetadataType",
expected: reflect.TypeOf(pkg.ConanLockEntry{}),
},
{
name: "map pkg.ConanfileEntry struct type",
input: "ConanMetadataType",
expected: reflect.TypeOf(pkg.ConanfileEntry{}),
},
{
name: "map pkg.DartPubspecLockEntry struct type",
input: "DartPubMetadata",
expected: reflect.TypeOf(pkg.DartPubspecLockEntry{}),
},
{
name: "map pkg.DotnetDepsEntry struct type",
input: "DotnetDepsMetadata",
expected: reflect.TypeOf(pkg.DotnetDepsEntry{}),
},
{
name: "map pkg.DpkgDBEntry struct type",
input: "DpkgMetadata",
expected: reflect.TypeOf(pkg.DpkgDBEntry{}),
},
{
name: "map pkg.RubyGemspec struct type",
input: "GemMetadata",
expected: reflect.TypeOf(pkg.RubyGemspec{}),
},
{
name: "map pkg.GolangBinaryBuildinfoEntry struct type",
input: "GolangBinMetadata",
expected: reflect.TypeOf(pkg.GolangBinaryBuildinfoEntry{}),
},
{
name: "map pkg.GolangModuleEntry struct type",
input: "GolangModMetadata",
expected: reflect.TypeOf(pkg.GolangModuleEntry{}),
},
{
name: "map pkg.JavaArchive struct type",
input: "JavaMetadata",
expected: reflect.TypeOf(pkg.JavaArchive{}),
},
{
name: "map pkg.MicrosoftKbPatch struct type",
input: "KbPatchMetadata",
expected: reflect.TypeOf(pkg.MicrosoftKbPatch{}),
},
{
name: "map pkg.LinuxKernel struct type",
input: "LinuxKernel",
expected: reflect.TypeOf(pkg.LinuxKernel{}),
},
{
name: "map pkg.LinuxKernelModule struct type",
input: "LinuxKernelModule",
expected: reflect.TypeOf(pkg.LinuxKernelModule{}),
},
{
name: "map pkg.ElixirMixLockEntry struct type",
input: "MixLockMetadataType",
expected: reflect.TypeOf(pkg.ElixirMixLockEntry{}),
},
{
name: "map pkg.NixStoreEntry struct type",
input: "NixStoreMetadata",
expected: reflect.TypeOf(pkg.NixStoreEntry{}),
},
{
name: "map pkg.NpmPackage struct type",
input: "NpmPackageJsonMetadata",
expected: reflect.TypeOf(pkg.NpmPackage{}),
},
{
name: "map pkg.NpmPackageLockEntry struct type",
input: "NpmPackageLockJsonMetadata",
expected: reflect.TypeOf(pkg.NpmPackageLockEntry{}),
},
{
name: "map pkg.PortageEntry struct type",
input: "PortageMetadata",
expected: reflect.TypeOf(pkg.PortageEntry{}),
},
{
name: "map pkg.PythonPackage struct type",
input: "PythonPackageMetadata",
expected: reflect.TypeOf(pkg.PythonPackage{}),
},
{
name: "map pkg.PythonPipfileLockEntry struct type",
input: "PythonPipfileLockMetadata",
expected: reflect.TypeOf(pkg.PythonPipfileLockEntry{}),
},
{
name: "map pkg.PythonRequirementsEntry struct type",
input: "PythonRequirementsMetadata",
expected: reflect.TypeOf(pkg.PythonRequirementsEntry{}),
},
{
name: "map pkg.ErlangRebarLockEntry struct type",
input: "RebarLockMetadataType",
expected: reflect.TypeOf(pkg.ErlangRebarLockEntry{}),
},
{
name: "map pkg.RDescription struct type",
input: "RDescriptionFileMetadataType",
expected: reflect.TypeOf(pkg.RDescription{}),
},
{
name: "map pkg.RpmDBEntry struct type",
input: "RpmdbMetadata",
expected: reflect.TypeOf(pkg.RpmDBEntry{}),
},
// these cases are 1:many
{
name: "map pkg.RpmDBEntry struct type - overlap with RpmArchiveMetadata",
input: "RpmMetadata",
// this used to be shared as a use case for both RpmArchive and RpmDBEntry
// from a data-shape perspective either would be equally correct
// however, the RPMDBMetadata has been around longer and may have been more widely used
// so we'll map to that type for backwards compatibility.
expected: reflect.TypeOf(pkg.RpmDBEntry{}),
},
{
name: "map pkg.HackageStackYamlLockEntry struct type - overlap with HackageStack*Metadata",
input: "HackageMetadataType",
// this used to be shared as a use case for both HackageStackYamlLockEntry and HackageStackYamlEntry
// but the HackageStackYamlLockEntry maps most closely to the original data shape.
expected: reflect.TypeOf(pkg.HackageStackYamlLockEntry{}),
},
{
name: "map pkg.PhpComposerLockEntry struct type",
input: "PhpComposerJsonMetadata",
// this used to be shared as a use case for both PhpComposerLockEntry and PhpComposerInstalledEntry
// neither of these is more correct over the other. These parsers were also introduced at the same time.
expected: reflect.TypeOf(pkg.PhpComposerLockEntry{}),
},
{
name: "map pkg.RustCargoLockEntry struct type",
input: "RustCargoPackageMetadata",
// this used to be shared as a use case for both RustCargoLockEntry and RustBinaryAuditEntry
// neither of these is more correct over the other.
expected: reflect.TypeOf(pkg.RustCargoLockEntry{}),
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := ReflectTypeFromJSONName(testCase.input)
assert.Equal(t, testCase.expected.Name(), result.Name())
})
}
}
func Test_JSONName_JSONLegacyName(t *testing.T) {
// note: these are all the types and names covered by the v11.x and v12.x JSON schemas
tests := []struct {
name string
metadata any
expectedJSONName string
expectedLegacyName string
}{
{
name: "AlpmMetadata",
metadata: pkg.AlpmDBEntry{},
expectedJSONName: "alpm-db-entry",
expectedLegacyName: "AlpmMetadata",
},
{
name: "ApkMetadata",
metadata: pkg.ApkDBEntry{},
expectedJSONName: "apk-db-entry",
expectedLegacyName: "ApkMetadata",
},
{
name: "BinaryMetadata",
metadata: pkg.BinarySignature{},
expectedJSONName: "binary-signature",
expectedLegacyName: "BinaryMetadata",
},
{
name: "CocoapodsMetadata",
metadata: pkg.CocoaPodfileLockEntry{},
expectedJSONName: "cocoa-podfile-lock-entry",
expectedLegacyName: "CocoapodsMetadataType",
},
{
name: "ConanLockMetadata",
metadata: pkg.ConanLockEntry{},
expectedJSONName: "c-conan-lock-entry",
expectedLegacyName: "ConanLockMetadataType",
},
{
name: "ConanMetadata",
metadata: pkg.ConanfileEntry{},
expectedJSONName: "c-conan-file-entry",
expectedLegacyName: "ConanMetadataType",
},
{
name: "DartPubMetadata",
metadata: pkg.DartPubspecLockEntry{},
expectedJSONName: "dart-pubspec-lock-entry",
expectedLegacyName: "DartPubMetadata",
},
{
name: "DotnetDepsMetadata",
metadata: pkg.DotnetDepsEntry{},
expectedJSONName: "dotnet-deps-entry",
expectedLegacyName: "DotnetDepsMetadata",
},
{
name: "DotnetPortableExecutableMetadata",
metadata: pkg.DotnetPortableExecutableEntry{},
expectedJSONName: "dotnet-portable-executable-entry",
expectedLegacyName: "dotnet-portable-executable-entry", // note: the legacy name should never be blank if it didn't exist pre v11.x
},
{
name: "DpkgMetadata",
metadata: pkg.DpkgDBEntry{},
expectedJSONName: "dpkg-db-entry",
expectedLegacyName: "DpkgMetadata",
},
{
name: "GemMetadata",
metadata: pkg.RubyGemspec{},
expectedJSONName: "ruby-gemspec",
expectedLegacyName: "GemMetadata",
},
{
name: "GolangBinMetadata",
metadata: pkg.GolangBinaryBuildinfoEntry{},
expectedJSONName: "go-module-buildinfo-entry",
expectedLegacyName: "GolangBinMetadata",
},
{
name: "GolangModMetadata",
metadata: pkg.GolangModuleEntry{},
expectedJSONName: "go-module-entry",
expectedLegacyName: "GolangModMetadata",
},
{
name: "HackageStackYamlLockMetadata",
metadata: pkg.HackageStackYamlLockEntry{},
expectedJSONName: "haskell-hackage-stack-lock-entry",
expectedLegacyName: "HackageMetadataType", // this is closest to the original data shape in <=v11.x schema
},
{
name: "HackageStackYamlMetadata",
metadata: pkg.HackageStackYamlEntry{},
expectedJSONName: "haskell-hackage-stack-entry",
expectedLegacyName: "HackageMetadataType", // note: this conflicts with <=v11.x schema for "haskell-hackage-stack-lock" metadata type
},
{
name: "JavaMetadata",
metadata: pkg.JavaArchive{},
expectedJSONName: "java-archive",
expectedLegacyName: "JavaMetadata",
},
{
name: "KbPatchMetadata",
metadata: pkg.MicrosoftKbPatch{},
expectedJSONName: "microsoft-kb-patch",
expectedLegacyName: "KbPatchMetadata",
},
{
name: "LinuxKernel",
metadata: pkg.LinuxKernel{},
expectedJSONName: "linux-kernel-archive",
expectedLegacyName: "LinuxKernel",
},
{
name: "LinuxKernelModule",
metadata: pkg.LinuxKernelModule{},
expectedJSONName: "linux-kernel-module",
expectedLegacyName: "LinuxKernelModule",
},
{
name: "MixLockMetadata",
metadata: pkg.ElixirMixLockEntry{},
expectedJSONName: "elixir-mix-lock-entry",
expectedLegacyName: "MixLockMetadataType",
},
{
name: "NixStoreMetadata",
metadata: pkg.NixStoreEntry{},
expectedJSONName: "nix-store-entry",
expectedLegacyName: "NixStoreMetadata",
},
{
name: "NpmPackageJSONMetadata",
metadata: pkg.NpmPackage{},
expectedJSONName: "javascript-npm-package",
expectedLegacyName: "NpmPackageJsonMetadata",
},
{
name: "NpmPackageLockJSONMetadata",
metadata: pkg.NpmPackageLockEntry{},
expectedJSONName: "javascript-npm-package-lock-entry",
expectedLegacyName: "NpmPackageLockJsonMetadata",
},
{
name: "PhpComposerLockMetadata",
metadata: pkg.PhpComposerLockEntry{},
expectedJSONName: "php-composer-lock-entry",
expectedLegacyName: "PhpComposerJsonMetadata", // note: maps to multiple entries (v11-12 breaking change)
},
{
name: "PhpComposerInstalledMetadata",
metadata: pkg.PhpComposerInstalledEntry{},
expectedJSONName: "php-composer-installed-entry",
expectedLegacyName: "PhpComposerJsonMetadata", // note: maps to multiple entries (v11-12 breaking change)
},
{
name: "PortageMetadata",
metadata: pkg.PortageEntry{},
expectedJSONName: "portage-db-entry",
expectedLegacyName: "PortageMetadata",
},
{
name: "PythonPackageMetadata",
metadata: pkg.PythonPackage{},
expectedJSONName: "python-package",
expectedLegacyName: "PythonPackageMetadata",
},
{
name: "PythonPipfileLockMetadata",
metadata: pkg.PythonPipfileLockEntry{},
expectedJSONName: "python-pipfile-lock-entry",
expectedLegacyName: "PythonPipfileLockMetadata",
},
{
name: "PythonRequirementsMetadata",
metadata: pkg.PythonRequirementsEntry{},
expectedJSONName: "python-pip-requirements-entry",
expectedLegacyName: "PythonRequirementsMetadata",
},
{
name: "RebarLockMetadata",
metadata: pkg.ErlangRebarLockEntry{},
expectedJSONName: "erlang-rebar-lock-entry",
expectedLegacyName: "RebarLockMetadataType",
},
{
name: "RDescriptionFileMetadata",
metadata: pkg.RDescription{},
expectedJSONName: "r-description",
expectedLegacyName: "RDescriptionFileMetadataType",
},
{
name: "RpmDBMetadata",
metadata: pkg.RpmDBEntry{},
expectedJSONName: "rpm-db-entry",
expectedLegacyName: "RpmMetadata", // not accurate, but how it was pre v12 of the schema
},
{
name: "RpmArchiveMetadata",
metadata: pkg.RpmArchive{},
expectedJSONName: "rpm-archive",
expectedLegacyName: "RpmMetadata", // note: conflicts with <=v11.x schema for "rpm-db-entry" metadata type
},
{
name: "SwiftPackageManagerMetadata",
metadata: pkg.SwiftPackageManagerResolvedEntry{},
expectedJSONName: "swift-package-manager-lock-entry",
expectedLegacyName: "SwiftPackageManagerMetadata",
},
{
name: "CargoPackageMetadata",
metadata: pkg.RustCargoLockEntry{},
expectedJSONName: "rust-cargo-lock-entry",
expectedLegacyName: "RustCargoPackageMetadata", // note: maps to multiple entries (v11-12 breaking change)
},
{
name: "CargoPackageMetadata (audit binary)",
metadata: pkg.RustBinaryAuditEntry{},
expectedJSONName: "rust-cargo-audit-entry",
expectedLegacyName: "RustCargoPackageMetadata", // note: maps to multiple entries (v11-12 breaking change)
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actualJSONName := JSONName(test.metadata)
actualLegacyName := JSONLegacyName(test.metadata)
assert.Equal(t, test.expectedJSONName, actualJSONName, "unexpected name")
assert.Equal(t, test.expectedLegacyName, actualLegacyName, "unexpected legacy name")
})
}
} }

View File

@ -13,7 +13,7 @@ var jsonNameFromType = map[reflect.Type][]string{
reflect.TypeOf(source.StereoscopeImageSourceMetadata{}): {"image"}, reflect.TypeOf(source.StereoscopeImageSourceMetadata{}): {"image"},
} }
func AllNames() []string { func AllTypeNames() []string {
names := make([]string, 0) names := make([]string, 0)
for _, t := range AllTypes() { for _, t := range AllTypes() {
names = append(names, reflect.TypeOf(t).Name()) names = append(names, reflect.TypeOf(t).Name())
@ -32,7 +32,7 @@ func ReflectTypeFromJSONName(name string) reflect.Type {
name = strings.ToLower(name) name = strings.ToLower(name)
for t, vs := range jsonNameFromType { for t, vs := range jsonNameFromType {
for _, v := range vs { for _, v := range vs {
if v == name { if strings.ToLower(v) == name {
return t return t
} }
} }

View File

@ -1,6 +1,7 @@
package sourcemetadata package sourcemetadata
import ( import (
"reflect"
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
@ -14,7 +15,7 @@ func TestAllNames(t *testing.T) {
expected, err := DiscoverTypeNames() expected, err := DiscoverTypeNames()
require.NoError(t, err) require.NoError(t, err)
actual := AllNames() actual := AllTypeNames()
// ensure that the codebase (from ast analysis) reflects the latest code generated state // ensure that the codebase (from ast analysis) reflects the latest code generated state
if !assert.ElementsMatch(t, expected, actual) { if !assert.ElementsMatch(t, expected, actual) {
@ -24,6 +25,6 @@ func TestAllNames(t *testing.T) {
} }
for _, ty := range AllTypes() { for _, ty := range AllTypes() {
assert.NotEmpty(t, JSONName(ty), "metadata type %q does not have a JSON name", ty) assert.NotEmpty(t, JSONName(ty), "metadata type %q does not have a JSON name", reflect.TypeOf(ty).Name())
} }
} }

View File

@ -9,11 +9,12 @@ import (
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
) )
var _ FileOwner = (*AlpmMetadata)(nil) var _ FileOwner = (*AlpmDBEntry)(nil)
const AlpmDBGlob = "**/var/lib/pacman/local/**/desc" const AlpmDBGlob = "**/var/lib/pacman/local/**/desc"
type AlpmMetadata struct { // AlpmDBEntry is a struct that represents the package data stored in the pacman fla-filet stores for arch linux.
type AlpmDBEntry struct {
BasePackage string `mapstructure:"base" json:"basepackage" cyclonedx:"basepackage"` BasePackage string `mapstructure:"base" json:"basepackage" cyclonedx:"basepackage"`
Package string `mapstructure:"name" json:"package" cyclonedx:"package"` Package string `mapstructure:"name" json:"package" cyclonedx:"package"`
Version string `mapstructure:"version" json:"version" cyclonedx:"version"` Version string `mapstructure:"version" json:"version" cyclonedx:"version"`
@ -39,7 +40,7 @@ type AlpmFileRecord struct {
Digests []file.Digest `mapstructure:"digests" json:"digest,omitempty"` Digests []file.Digest `mapstructure:"digests" json:"digest,omitempty"`
} }
func (m AlpmMetadata) OwnedFiles() (result []string) { func (m AlpmDBEntry) OwnedFiles() (result []string) {
s := strset.New() s := strset.New()
for _, f := range m.Files { for _, f := range m.Files {
if f.Path != "" { if f.Path != "" {

View File

@ -15,14 +15,14 @@ import (
const ApkDBGlob = "**/lib/apk/db/installed" const ApkDBGlob = "**/lib/apk/db/installed"
var _ FileOwner = (*ApkMetadata)(nil) var _ FileOwner = (*ApkDBEntry)(nil)
// ApkMetadata represents all captured data for a Alpine DB package entry. // ApkDBEntry represents all captured data for the alpine linux package manager flat-file store.
// See the following sources for more information: // See the following sources for more information:
// - https://wiki.alpinelinux.org/wiki/Apk_spec // - https://wiki.alpinelinux.org/wiki/Apk_spec
// - https://git.alpinelinux.org/apk-tools/tree/src/package.c // - https://git.alpinelinux.org/apk-tools/tree/src/package.c
// - https://git.alpinelinux.org/apk-tools/tree/src/database.c // - https://git.alpinelinux.org/apk-tools/tree/src/database.c
type ApkMetadata struct { type ApkDBEntry struct {
Package string `mapstructure:"P" json:"package"` Package string `mapstructure:"P" json:"package"`
OriginPackage string `mapstructure:"o" json:"originPackage" cyclonedx:"originPackage"` OriginPackage string `mapstructure:"o" json:"originPackage" cyclonedx:"originPackage"`
Maintainer string `mapstructure:"m" json:"maintainer"` Maintainer string `mapstructure:"m" json:"maintainer"`
@ -41,9 +41,9 @@ type ApkMetadata struct {
type spaceDelimitedStringSlice []string type spaceDelimitedStringSlice []string
func (m *ApkMetadata) UnmarshalJSON(data []byte) error { func (m *ApkDBEntry) UnmarshalJSON(data []byte) error {
var fields []reflect.StructField var fields []reflect.StructField
t := reflect.TypeOf(ApkMetadata{}) t := reflect.TypeOf(ApkDBEntry{})
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
f := t.Field(i) f := t.Field(i)
if f.Name == "Dependencies" { if f.Name == "Dependencies" {
@ -102,7 +102,7 @@ type ApkFileRecord struct {
Digest *file.Digest `json:"digest,omitempty"` Digest *file.Digest `json:"digest,omitempty"`
} }
func (m ApkMetadata) OwnedFiles() (result []string) { func (m ApkDBEntry) OwnedFiles() (result []string) {
s := strset.New() s := strset.New()
for _, f := range m.Files { for _, f := range m.Files {
if f.Path != "" { if f.Path != "" {

View File

@ -12,13 +12,13 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
input string input string
want ApkMetadata want ApkDBEntry
wantErr require.ErrorAssertionFunc wantErr require.ErrorAssertionFunc
}{ }{
{ {
name: "empty", name: "empty",
input: "{}", input: "{}",
want: ApkMetadata{}, want: ApkDBEntry{},
}, },
{ {
name: "string array dependencies", name: "string array dependencies",
@ -42,7 +42,7 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
], ],
"pullDependencies": ["foo", "bar"] "pullDependencies": ["foo", "bar"]
}`, }`,
want: ApkMetadata{ want: ApkDBEntry{
Package: "scanelf", Package: "scanelf",
OriginPackage: "pax-utils", OriginPackage: "pax-utils",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>", Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
@ -80,7 +80,7 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
], ],
"pullDependencies": "foo bar" "pullDependencies": "foo bar"
}`, }`,
want: ApkMetadata{ want: ApkDBEntry{
Package: "scanelf", Package: "scanelf",
OriginPackage: "pax-utils", OriginPackage: "pax-utils",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>", Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
@ -101,7 +101,7 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
input: `{ input: `{
"pullDependencies": null "pullDependencies": null
}`, }`,
want: ApkMetadata{ want: ApkDBEntry{
Dependencies: nil, Dependencies: nil,
}, },
}, },
@ -111,7 +111,7 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
if tt.wantErr == nil { if tt.wantErr == nil {
tt.wantErr = require.NoError tt.wantErr = require.NoError
} }
var got ApkMetadata var got ApkDBEntry
err := json.Unmarshal([]byte(tt.input), &got) err := json.Unmarshal([]byte(tt.input), &got)
tt.wantErr(t, err) tt.wantErr(t, err)
if err != nil { if err != nil {

View File

@ -2,10 +2,12 @@ package pkg
import "github.com/anchore/syft/syft/file" import "github.com/anchore/syft/syft/file"
type BinaryMetadata struct { // BinarySignature represents a set of matched values within a binary file.
type BinarySignature struct {
Matches []ClassifierMatch `mapstructure:"Matches" json:"matches"` Matches []ClassifierMatch `mapstructure:"Matches" json:"matches"`
} }
// ClassifierMatch represents a single matched value within a binary file and the "class" name the search pattern represents.
type ClassifierMatch struct { type ClassifierMatch struct {
Classifier string `mapstructure:"Classifier" json:"classifier"` Classifier string `mapstructure:"Classifier" json:"classifier"`
Location file.Location `mapstructure:"Location" json:"location"` Location file.Location `mapstructure:"Location" json:"location"`

View File

@ -7,6 +7,7 @@ import (
const catalogerName = "alpmdb-cataloger" const catalogerName = "alpmdb-cataloger"
// NewAlpmdbCataloger returns a new cataloger object initialized for arch linux pacman database flat-file stores.
func NewAlpmdbCataloger() *generic.Cataloger { func NewAlpmdbCataloger() *generic.Cataloger {
return generic.NewCataloger(catalogerName). return generic.NewCataloger(catalogerName).
WithParserByGlobs(parseAlpmDB, pkg.AlpmDBGlob) WithParserByGlobs(parseAlpmDB, pkg.AlpmDBGlob)

View File

@ -26,8 +26,7 @@ func TestAlpmCataloger(t *testing.T) {
Locations: file.NewLocationSet(dbLocation), Locations: file.NewLocationSet(dbLocation),
CPEs: nil, CPEs: nil,
PURL: "", PURL: "",
MetadataType: "AlpmMetadata", Metadata: pkg.AlpmDBEntry{
Metadata: pkg.AlpmMetadata{
BasePackage: "gmp", BasePackage: "gmp",
Package: "gmp", Package: "gmp",
Version: "6.2.1-2", Version: "6.2.1-2",

View File

@ -19,8 +19,7 @@ func newPackage(m *parsedData, release *linux.Release, dbLocation file.Location)
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
Type: pkg.AlpmPkg, Type: pkg.AlpmPkg,
PURL: packageURL(m, release), PURL: packageURL(m, release),
MetadataType: pkg.AlpmMetadataType, Metadata: m.AlpmDBEntry,
Metadata: m.AlpmMetadata,
} }
p.SetID() p.SetID()

View File

@ -21,7 +21,7 @@ func Test_PackageURL(t *testing.T) {
name: "bad distro id", name: "bad distro id",
metadata: &parsedData{ metadata: &parsedData{
Licenses: "", Licenses: "",
AlpmMetadata: pkg.AlpmMetadata{ AlpmDBEntry: pkg.AlpmDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
Architecture: "a", Architecture: "a",
@ -37,7 +37,7 @@ func Test_PackageURL(t *testing.T) {
name: "gocase", name: "gocase",
metadata: &parsedData{ metadata: &parsedData{
Licenses: "", Licenses: "",
AlpmMetadata: pkg.AlpmMetadata{ AlpmDBEntry: pkg.AlpmDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
Architecture: "a", Architecture: "a",
@ -53,7 +53,7 @@ func Test_PackageURL(t *testing.T) {
name: "missing architecture", name: "missing architecture",
metadata: &parsedData{ metadata: &parsedData{
Licenses: "", Licenses: "",
AlpmMetadata: pkg.AlpmMetadata{ AlpmDBEntry: pkg.AlpmDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
}, },
@ -66,7 +66,7 @@ func Test_PackageURL(t *testing.T) {
{ {
metadata: &parsedData{ metadata: &parsedData{
Licenses: "", Licenses: "",
AlpmMetadata: pkg.AlpmMetadata{ AlpmDBEntry: pkg.AlpmDBEntry{
Package: "python", Package: "python",
Version: "3.10.0", Version: "3.10.0",
Architecture: "any", Architecture: "any",
@ -81,7 +81,7 @@ func Test_PackageURL(t *testing.T) {
{ {
metadata: &parsedData{ metadata: &parsedData{
Licenses: "", Licenses: "",
AlpmMetadata: pkg.AlpmMetadata{ AlpmDBEntry: pkg.AlpmDBEntry{
Package: "g plus plus", Package: "g plus plus",
Version: "v84", Version: "v84",
Architecture: "x86_64", Architecture: "x86_64",
@ -97,7 +97,7 @@ func Test_PackageURL(t *testing.T) {
name: "add source information as qualifier", name: "add source information as qualifier",
metadata: &parsedData{ metadata: &parsedData{
Licenses: "", Licenses: "",
AlpmMetadata: pkg.AlpmMetadata{ AlpmDBEntry: pkg.AlpmDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
Architecture: "a", Architecture: "a",

View File

@ -32,9 +32,10 @@ var (
type parsedData struct { type parsedData struct {
Licenses string `mapstructure:"license"` Licenses string `mapstructure:"license"`
pkg.AlpmMetadata `mapstructure:",squash"` pkg.AlpmDBEntry `mapstructure:",squash"`
} }
// parseAlpmDB parses the arch linux pacman database flat-files and returns the packages and relationships found within.
func parseAlpmDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { func parseAlpmDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
data, err := parseAlpmDBEntry(reader) data, err := parseAlpmDBEntry(reader)
if err != nil { if err != nil {

View File

@ -17,12 +17,12 @@ func TestDatabaseParser(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
fixture string fixture string
expected pkg.AlpmMetadata expected pkg.AlpmDBEntry
}{ }{
{ {
name: "test alpm database parsing", name: "test alpm database parsing",
fixture: "test-fixtures/files", fixture: "test-fixtures/files",
expected: pkg.AlpmMetadata{ expected: pkg.AlpmDBEntry{
Backup: []pkg.AlpmFileRecord{ Backup: []pkg.AlpmFileRecord{
{ {
Path: "/etc/pacman.conf", Path: "/etc/pacman.conf",

View File

@ -10,7 +10,7 @@ import (
const catalogerName = "apkdb-cataloger" const catalogerName = "apkdb-cataloger"
// NewApkdbCataloger returns a new Alpine DB cataloger object. // NewApkdbCataloger returns a new cataloger object initialized for Alpine package DB flat-file stores.
func NewApkdbCataloger() *generic.Cataloger { func NewApkdbCataloger() *generic.Cataloger {
return generic.NewCataloger(catalogerName). return generic.NewCataloger(catalogerName).
WithParserByGlobs(parseApkDB, pkg.ApkDBGlob) WithParserByGlobs(parseApkDB, pkg.ApkDBGlob)

View File

@ -24,10 +24,9 @@ func newPackage(d parsedData, release *linux.Release, dbLocation file.Location)
Version: d.Version, Version: d.Version,
Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...),
PURL: packageURL(d.ApkMetadata, release), PURL: packageURL(d.ApkDBEntry, release),
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType, Metadata: d.ApkDBEntry,
Metadata: d.ApkMetadata,
} }
p.SetID() p.SetID()
@ -36,7 +35,7 @@ func newPackage(d parsedData, release *linux.Release, dbLocation file.Location)
} }
// packageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec) // packageURL returns the PURL for the specific Alpine package (see https://github.com/package-url/purl-spec)
func packageURL(m pkg.ApkMetadata, distro *linux.Release) string { func packageURL(m pkg.ApkDBEntry, distro *linux.Release) string {
if distro == nil { if distro == nil {
return "" return ""
} }

View File

@ -23,7 +23,7 @@ func Test_PackageURL(t *testing.T) {
name: "non-alpine distro", name: "non-alpine distro",
metadata: parsedData{ metadata: parsedData{
License: "", License: "",
ApkMetadata: pkg.ApkMetadata{ ApkDBEntry: pkg.ApkDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
Architecture: "a", Architecture: "a",
@ -39,7 +39,7 @@ func Test_PackageURL(t *testing.T) {
name: "gocase", name: "gocase",
metadata: parsedData{ metadata: parsedData{
License: "", License: "",
ApkMetadata: pkg.ApkMetadata{ ApkDBEntry: pkg.ApkDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
Architecture: "a", Architecture: "a",
@ -55,7 +55,7 @@ func Test_PackageURL(t *testing.T) {
name: "missing architecture", name: "missing architecture",
metadata: parsedData{ metadata: parsedData{
License: "", License: "",
ApkMetadata: pkg.ApkMetadata{ ApkDBEntry: pkg.ApkDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
}, },
@ -70,7 +70,7 @@ func Test_PackageURL(t *testing.T) {
{ {
metadata: parsedData{ metadata: parsedData{
License: "", License: "",
ApkMetadata: pkg.ApkMetadata{ ApkDBEntry: pkg.ApkDBEntry{
Package: "g++", Package: "g++",
Version: "v84", Version: "v84",
Architecture: "am86", Architecture: "am86",
@ -85,7 +85,7 @@ func Test_PackageURL(t *testing.T) {
{ {
metadata: parsedData{ metadata: parsedData{
License: "", License: "",
ApkMetadata: pkg.ApkMetadata{ ApkDBEntry: pkg.ApkDBEntry{
Package: "g plus plus", Package: "g plus plus",
Version: "v84", Version: "v84",
Architecture: "am86", Architecture: "am86",
@ -101,7 +101,7 @@ func Test_PackageURL(t *testing.T) {
name: "add source information as qualifier", name: "add source information as qualifier",
metadata: parsedData{ metadata: parsedData{
License: "", License: "",
ApkMetadata: pkg.ApkMetadata{ ApkDBEntry: pkg.ApkDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
Architecture: "a", Architecture: "a",
@ -118,7 +118,7 @@ func Test_PackageURL(t *testing.T) {
name: "wolfi distro", name: "wolfi distro",
metadata: parsedData{ metadata: parsedData{
License: "", License: "",
ApkMetadata: pkg.ApkMetadata{ ApkDBEntry: pkg.ApkDBEntry{
Package: "p", Package: "p",
Version: "v", Version: "v",
Architecture: "a", Architecture: "a",
@ -134,7 +134,7 @@ func Test_PackageURL(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
actual := packageURL(test.metadata.ApkMetadata, &test.distro) actual := packageURL(test.metadata.ApkDBEntry, &test.distro)
if actual != test.expected { if actual != test.expected {
dmp := diffmatchpatch.New() dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(test.expected, actual, true) diffs := dmp.DiffMain(test.expected, actual, true)
@ -171,11 +171,11 @@ func Test_PackageURL(t *testing.T) {
func TestApkMetadata_FileOwner(t *testing.T) { func TestApkMetadata_FileOwner(t *testing.T) {
tests := []struct { tests := []struct {
metadata pkg.ApkMetadata metadata pkg.ApkDBEntry
expected []string expected []string
}{ }{
{ {
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Files: []pkg.ApkFileRecord{ Files: []pkg.ApkFileRecord{
{Path: "/somewhere"}, {Path: "/somewhere"},
{Path: "/else"}, {Path: "/else"},
@ -187,7 +187,7 @@ func TestApkMetadata_FileOwner(t *testing.T) {
}, },
}, },
{ {
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Files: []pkg.ApkFileRecord{ Files: []pkg.ApkFileRecord{
{Path: "/somewhere"}, {Path: "/somewhere"},
{Path: ""}, {Path: ""},

View File

@ -27,10 +27,10 @@ var (
type parsedData struct { type parsedData struct {
License string `mapstructure:"L" json:"license"` License string `mapstructure:"L" json:"license"`
pkg.ApkMetadata pkg.ApkDBEntry
} }
// parseApkDB parses packages from a given APK installed DB file. For more // parseApkDB parses packages from a given APK "installed" flat-file DB. For more
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec. // information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
// //
//nolint:funlen,gocognit //nolint:funlen,gocognit
@ -390,7 +390,7 @@ func discoverPackageDependencies(pkgs []pkg.Package) (relationships []artifact.R
lookup := make(map[string][]pkg.Package) lookup := make(map[string][]pkg.Package)
// read "Provides" (p) and add as keys for lookup keys as well as package names // read "Provides" (p) and add as keys for lookup keys as well as package names
for _, p := range pkgs { for _, p := range pkgs {
apkg, ok := p.Metadata.(pkg.ApkMetadata) apkg, ok := p.Metadata.(pkg.ApkDBEntry)
if !ok { if !ok {
log.Warnf("cataloger failed to extract apk 'provides' metadata for package %+v", p.Name) log.Warnf("cataloger failed to extract apk 'provides' metadata for package %+v", p.Name)
continue continue
@ -404,7 +404,7 @@ func discoverPackageDependencies(pkgs []pkg.Package) (relationships []artifact.R
// read "Pull Dependencies" (D) and match with keys // read "Pull Dependencies" (D) and match with keys
for _, p := range pkgs { for _, p := range pkgs {
apkg, ok := p.Metadata.(pkg.ApkMetadata) apkg, ok := p.Metadata.(pkg.ApkDBEntry)
if !ok { if !ok {
log.Warnf("cataloger failed to extract apk dependency metadata for package %+v", p.Name) log.Warnf("cataloger failed to extract apk dependency metadata for package %+v", p.Name)
continue continue

View File

@ -23,11 +23,11 @@ import (
func TestExtraFileAttributes(t *testing.T) { func TestExtraFileAttributes(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
expected pkg.ApkMetadata expected pkg.ApkDBEntry
}{ }{
{ {
name: "test extra file attributes (checksum) are ignored", name: "test extra file attributes (checksum) are ignored",
expected: pkg.ApkMetadata{ expected: pkg.ApkDBEntry{
Files: []pkg.ApkFileRecord{ Files: []pkg.ApkFileRecord{
{ {
Path: "/usr", Path: "/usr",
@ -67,7 +67,7 @@ func TestExtraFileAttributes(t *testing.T) {
pkgs, _, err := parseApkDB(nil, new(generic.Environment), lrc) pkgs, _, err := parseApkDB(nil, new(generic.Environment), lrc)
assert.NoError(t, err) assert.NoError(t, err)
require.Len(t, pkgs, 1) require.Len(t, pkgs, 1)
metadata := pkgs[0].Metadata.(pkg.ApkMetadata) metadata := pkgs[0].Metadata.(pkg.ApkDBEntry)
if diff := cmp.Diff(test.expected.Files, metadata.Files); diff != "" { if diff := cmp.Diff(test.expected.Files, metadata.Files); diff != "" {
t.Errorf("Files mismatch (-want +got):\n%s", diff) t.Errorf("Files mismatch (-want +got):\n%s", diff)
@ -92,8 +92,7 @@ func TestSinglePackageDetails(t *testing.T) {
pkg.NewLicense("GPL2+"), pkg.NewLicense("GPL2+"),
), ),
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "musl-utils", Package: "musl-utils",
OriginPackage: "musl", OriginPackage: "musl",
Version: "1.1.24-r2", Version: "1.1.24-r2",
@ -180,8 +179,7 @@ func TestSinglePackageDetails(t *testing.T) {
pkg.NewLicense("GPL-2.0-only"), pkg.NewLicense("GPL-2.0-only"),
), ),
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "alpine-baselayout-data", Package: "alpine-baselayout-data",
OriginPackage: "alpine-baselayout", OriginPackage: "alpine-baselayout",
Version: "3.4.0-r0", Version: "3.4.0-r0",
@ -226,8 +224,7 @@ func TestSinglePackageDetails(t *testing.T) {
), ),
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
PURL: "", PURL: "",
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "alpine-baselayout", Package: "alpine-baselayout",
OriginPackage: "alpine-baselayout", OriginPackage: "alpine-baselayout",
Version: "3.2.0-r6", Version: "3.2.0-r6",
@ -705,8 +702,7 @@ func TestMultiplePackages(t *testing.T) {
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
PURL: "pkg:apk/alpine/libc-utils@0.7.2-r0?arch=x86_64&upstream=libc-dev&distro=alpine-3.12", PURL: "pkg:apk/alpine/libc-utils@0.7.2-r0?arch=x86_64&upstream=libc-dev&distro=alpine-3.12",
Locations: fixtureLocationSet, Locations: fixtureLocationSet,
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "libc-utils", Package: "libc-utils",
OriginPackage: "libc-dev", OriginPackage: "libc-dev",
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>", Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
@ -734,8 +730,7 @@ func TestMultiplePackages(t *testing.T) {
pkg.NewLicenseFromLocations("BSD", location), pkg.NewLicenseFromLocations("BSD", location),
pkg.NewLicenseFromLocations("GPL2+", location), pkg.NewLicenseFromLocations("GPL2+", location),
), ),
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "musl-utils", Package: "musl-utils",
OriginPackage: "musl", OriginPackage: "musl",
Version: "1.1.24-r2", Version: "1.1.24-r2",
@ -872,14 +867,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
genFn: func() ([]pkg.Package, []artifact.Relationship) { genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{ a := pkg.Package{
Name: "package-a", Name: "package-a",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Provides: []string{"a-thing"}, Provides: []string{"a-thing"},
}, },
} }
a.SetID() a.SetID()
b := pkg.Package{ b := pkg.Package{
Name: "package-b", Name: "package-b",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Provides: []string{"b-thing"}, Provides: []string{"b-thing"},
}, },
} }
@ -893,14 +888,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
genFn: func() ([]pkg.Package, []artifact.Relationship) { genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{ a := pkg.Package{
Name: "package-a", Name: "package-a",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Dependencies: []string{"b-thing"}, Dependencies: []string{"b-thing"},
}, },
} }
a.SetID() a.SetID()
b := pkg.Package{ b := pkg.Package{
Name: "package-b", Name: "package-b",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Provides: []string{"b-thing"}, Provides: []string{"b-thing"},
}, },
} }
@ -920,14 +915,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
genFn: func() ([]pkg.Package, []artifact.Relationship) { genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{ a := pkg.Package{
Name: "package-a", Name: "package-a",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Dependencies: []string{"so:libc.musl-x86_64.so.1"}, Dependencies: []string{"so:libc.musl-x86_64.so.1"},
}, },
} }
a.SetID() a.SetID()
b := pkg.Package{ b := pkg.Package{
Name: "package-b", Name: "package-b",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Provides: []string{"so:libc.musl-x86_64.so.1=1"}, Provides: []string{"so:libc.musl-x86_64.so.1=1"},
}, },
} }
@ -947,14 +942,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
genFn: func() ([]pkg.Package, []artifact.Relationship) { genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{ a := pkg.Package{
Name: "package-a", Name: "package-a",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Dependencies: []string{"so:libc.musl-x86_64.so.1"}, Dependencies: []string{"so:libc.musl-x86_64.so.1"},
}, },
} }
a.SetID() a.SetID()
b := pkg.Package{ b := pkg.Package{
Name: "package-b", Name: "package-b",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Provides: []string{""}, Provides: []string{""},
}, },
} }
@ -968,14 +963,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
genFn: func() ([]pkg.Package, []artifact.Relationship) { genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{ a := pkg.Package{
Name: "package-a", Name: "package-a",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Dependencies: []string{"musl>=1.2"}, Dependencies: []string{"musl>=1.2"},
}, },
} }
a.SetID() a.SetID()
b := pkg.Package{ b := pkg.Package{
Name: "musl", Name: "musl",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Provides: []string{"so:libc.musl-x86_64.so.1=1"}, Provides: []string{"so:libc.musl-x86_64.so.1=1"},
}, },
} }
@ -995,14 +990,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
genFn: func() ([]pkg.Package, []artifact.Relationship) { genFn: func() ([]pkg.Package, []artifact.Relationship) {
a := pkg.Package{ a := pkg.Package{
Name: "alpine-baselayout", Name: "alpine-baselayout",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Dependencies: []string{"/bin/sh"}, Dependencies: []string{"/bin/sh"},
}, },
} }
a.SetID() a.SetID()
b := pkg.Package{ b := pkg.Package{
Name: "busybox", Name: "busybox",
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Provides: []string{"/bin/sh"}, Provides: []string{"/bin/sh"},
}, },
} }

View File

@ -61,8 +61,8 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
// add the locations // add the locations
target.Locations.Add(extra.Locations.ToSlice()...) target.Locations.Add(extra.Locations.ToSlice()...)
// update the metadata to indicate which classifiers were used // update the metadata to indicate which classifiers were used
meta, _ := target.Metadata.(pkg.BinaryMetadata) meta, _ := target.Metadata.(pkg.BinarySignature)
if m, ok := extra.Metadata.(pkg.BinaryMetadata); ok { if m, ok := extra.Metadata.(pkg.BinarySignature); ok {
meta.Matches = append(meta.Matches, m.Matches...) meta.Matches = append(meta.Matches, m.Matches...)
} }
target.Metadata = meta target.Metadata = meta

View File

@ -331,7 +331,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Version: "3.11.2", Version: "3.11.2",
PURL: "pkg:generic/python@3.11.2", PURL: "pkg:generic/python@3.11.2",
Locations: locations("python3", "libpython3.11.so.1.0"), Locations: locations("python3", "libpython3.11.so.1.0"),
Metadata: pkg.BinaryMetadata{ Metadata: pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match("python-binary", "python3"), match("python-binary", "python3"),
match("python-binary", "libpython3.11.so.1.0"), match("python-binary", "libpython3.11.so.1.0"),
@ -348,7 +348,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Version: "3.9.13", Version: "3.9.13",
PURL: "pkg:generic/python@3.9.13", PURL: "pkg:generic/python@3.9.13",
Locations: locations("python3.9", "libpython3.9.so.1.0"), Locations: locations("python3.9", "libpython3.9.so.1.0"),
Metadata: pkg.BinaryMetadata{ Metadata: pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match("python-binary", "python3.9"), match("python-binary", "python3.9"),
match("python-binary", "libpython3.9.so.1.0"), match("python-binary", "libpython3.9.so.1.0"),
@ -365,7 +365,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Version: "3.9.2", Version: "3.9.2",
PURL: "pkg:generic/python@3.9.2", PURL: "pkg:generic/python@3.9.2",
Locations: locations("python3.9"), Locations: locations("python3.9"),
Metadata: pkg.BinaryMetadata{ Metadata: pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match("python-binary", "python3.9"), match("python-binary", "python3.9"),
}, },
@ -380,7 +380,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Version: "3.4.10", Version: "3.4.10",
PURL: "pkg:generic/python@3.4.10", PURL: "pkg:generic/python@3.4.10",
Locations: locations("python3.4", "libpython3.4m.so.1.0"), Locations: locations("python3.4", "libpython3.4m.so.1.0"),
Metadata: pkg.BinaryMetadata{ Metadata: pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match("python-binary", "python3.4"), match("python-binary", "python3.4"),
match("python-binary", "libpython3.4m.so.1.0"), match("python-binary", "libpython3.4m.so.1.0"),
@ -420,7 +420,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Type: "binary", Type: "binary",
PURL: "pkg:generic/python@3.8.16", PURL: "pkg:generic/python@3.8.16",
Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"), Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"),
Metadata: pkg.BinaryMetadata{ Metadata: pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match("python-binary", "dir/python3.8"), match("python-binary", "dir/python3.8"),
match("python-binary", "python3.8"), match("python-binary", "python3.8"),
@ -577,7 +577,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Type: "binary", Type: "binary",
PURL: "pkg:generic/ruby@3.2.1", PURL: "pkg:generic/ruby@3.2.1",
Locations: locations("ruby", "libruby.so.3.2.1"), Locations: locations("ruby", "libruby.so.3.2.1"),
Metadata: pkg.BinaryMetadata{ Metadata: pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match("ruby-binary", "ruby"), match("ruby-binary", "ruby"),
match("ruby-binary", "libruby.so.3.2.1"), match("ruby-binary", "libruby.so.3.2.1"),
@ -594,7 +594,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Type: "binary", Type: "binary",
PURL: "pkg:generic/ruby@2.7.7p221", PURL: "pkg:generic/ruby@2.7.7p221",
Locations: locations("ruby", "libruby.so.2.7.7"), Locations: locations("ruby", "libruby.so.2.7.7"),
Metadata: pkg.BinaryMetadata{ Metadata: pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match("ruby-binary", "ruby"), match("ruby-binary", "ruby"),
match("ruby-binary", "libruby.so.2.7.7"), match("ruby-binary", "libruby.so.2.7.7"),
@ -611,7 +611,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
Type: "binary", Type: "binary",
PURL: "pkg:generic/ruby@2.6.10p210", PURL: "pkg:generic/ruby@2.6.10p210",
Locations: locations("ruby", "libruby.so.2.6.10"), Locations: locations("ruby", "libruby.so.2.6.10"),
Metadata: pkg.BinaryMetadata{ Metadata: pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match("ruby-binary", "ruby"), match("ruby-binary", "ruby"),
match("ruby-binary", "libruby.so.2.6.10"), match("ruby-binary", "libruby.so.2.6.10"),
@ -774,8 +774,8 @@ func locations(locations ...string) file.LocationSet {
} }
// metadata paths are: realPath, virtualPath // metadata paths are: realPath, virtualPath
func metadata(classifier string, paths ...string) pkg.BinaryMetadata { func metadata(classifier string, paths ...string) pkg.BinarySignature {
return pkg.BinaryMetadata{ return pkg.BinarySignature{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
match(classifier, paths...), match(classifier, paths...),
}, },
@ -819,8 +819,8 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
} }
} }
m1 := expected.Metadata.(pkg.BinaryMetadata).Matches m1 := expected.Metadata.(pkg.BinarySignature).Matches
m2 := p.Metadata.(pkg.BinaryMetadata).Matches m2 := p.Metadata.(pkg.BinarySignature).Matches
matches := true matches := true
if len(m1) == len(m2) { if len(m1) == len(m2) {
for i, m1 := range m1 { for i, m1 := range m1 {

View File

@ -162,8 +162,8 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evide
locationSet := file.NewLocationSet(location) locationSet := file.NewLocationSet(location)
locationSet.Add(p.Locations.ToSlice()...) locationSet.Add(p.Locations.ToSlice()...)
p.Locations = locationSet p.Locations = locationSet
meta, _ := p.Metadata.(pkg.BinaryMetadata) meta, _ := p.Metadata.(pkg.BinarySignature)
p.Metadata = pkg.BinaryMetadata{ p.Metadata = pkg.BinarySignature{
Matches: append([]pkg.ClassifierMatch{ Matches: append([]pkg.ClassifierMatch{
{ {
Classifier: classifier.Class, Classifier: classifier.Class,

View File

@ -32,8 +32,7 @@ func newPackage(classifier classifier, location file.Location, matchMetadata map
Type: pkg.BinaryPkg, Type: pkg.BinaryPkg,
CPEs: cpes, CPEs: cpes,
FoundBy: catalogerName, FoundBy: catalogerName,
MetadataType: pkg.BinaryMetadataType, Metadata: pkg.BinarySignature{
Metadata: pkg.BinaryMetadata{
Matches: []pkg.ClassifierMatch{ Matches: []pkg.ClassifierMatch{
{ {
Classifier: classifier.Class, Classifier: classifier.Class,

View File

@ -22,7 +22,7 @@ type upstreamCandidate struct {
Type pkg.Type Type pkg.Type
} }
func upstreamCandidates(m pkg.ApkMetadata) (candidates []upstreamCandidate) { func upstreamCandidates(m pkg.ApkDBEntry) (candidates []upstreamCandidate) {
// Do not consider OriginPackage variations when generating CPE candidates for the child package // Do not consider OriginPackage variations when generating CPE candidates for the child package
// because doing so will result in false positives when matching to vulnerabilities in Grype since // because doing so will result in false positives when matching to vulnerabilities in Grype since
// it won't know to lookup apk fix entries using the OriginPackage name. // it won't know to lookup apk fix entries using the OriginPackage name.
@ -60,7 +60,7 @@ func upstreamCandidates(m pkg.ApkMetadata) (candidates []upstreamCandidate) {
} }
func candidateVendorsForAPK(p pkg.Package) fieldCandidateSet { func candidateVendorsForAPK(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.ApkMetadata) metadata, ok := p.Metadata.(pkg.ApkDBEntry)
if !ok { if !ok {
return nil return nil
} }
@ -101,7 +101,7 @@ func candidateVendorsForAPK(p pkg.Package) fieldCandidateSet {
} }
func candidateProductsForAPK(p pkg.Package) fieldCandidateSet { func candidateProductsForAPK(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.ApkMetadata) metadata, ok := p.Metadata.(pkg.ApkDBEntry)
if !ok { if !ok {
return nil return nil
} }

View File

@ -17,7 +17,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
{ {
name: "py3-cryptography Package", name: "py3-cryptography Package",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "py3-cryptography", Package: "py3-cryptography",
}, },
}, },
@ -26,7 +26,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
{ {
name: "py2-pypdf with explicit different origin", name: "py2-pypdf with explicit different origin",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "py2-pypdf", Package: "py2-pypdf",
OriginPackage: "abcdefg", OriginPackage: "abcdefg",
}, },
@ -36,7 +36,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
{ {
name: "ruby-armadillo Package", name: "ruby-armadillo Package",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "ruby-armadillo", Package: "ruby-armadillo",
}, },
}, },
@ -45,7 +45,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
{ {
name: "python-3.6", name: "python-3.6",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "python-3.6", Package: "python-3.6",
}, },
}, },
@ -54,7 +54,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
{ {
name: "ruby-3.6", name: "ruby-3.6",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "ruby-3.6", Package: "ruby-3.6",
URL: "https://www.ruby-lang.org/", URL: "https://www.ruby-lang.org/",
}, },
@ -64,7 +64,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
{ {
name: "make", name: "make",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "make", Package: "make",
URL: "https://www.gnu.org/software/make", URL: "https://www.gnu.org/software/make",
}, },
@ -76,8 +76,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
pkg: pkg.Package{ pkg: pkg.Package{
Name: "ruby-rake", Name: "ruby-rake",
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "ruby-rake", Package: "ruby-rake",
URL: "https://github.com/ruby/rake", URL: "https://github.com/ruby/rake",
OriginPackage: "ruby-rake", OriginPackage: "ruby-rake",
@ -90,8 +89,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
pkg: pkg.Package{ pkg: pkg.Package{
Name: "ruby-rake", Name: "ruby-rake",
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "ruby-rake", Package: "ruby-rake",
URL: "https://www.ruby-lang.org/", URL: "https://www.ruby-lang.org/",
OriginPackage: "ruby", OriginPackage: "ruby",
@ -116,7 +114,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
{ {
name: "py3-cryptography Package", name: "py3-cryptography Package",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "py3-cryptography", Package: "py3-cryptography",
}, },
}, },
@ -125,7 +123,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
{ {
name: "py2-pypdf with explicit different origin", name: "py2-pypdf with explicit different origin",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "py2-pypdf", Package: "py2-pypdf",
OriginPackage: "abcdefg", OriginPackage: "abcdefg",
}, },
@ -135,7 +133,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
{ {
name: "ruby-armadillo Package", name: "ruby-armadillo Package",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "ruby-armadillo", Package: "ruby-armadillo",
}, },
}, },
@ -144,7 +142,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
{ {
name: "python-3.6", name: "python-3.6",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "python-3.6", Package: "python-3.6",
}, },
}, },
@ -153,7 +151,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
{ {
name: "ruby-3.6", name: "ruby-3.6",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "ruby-3.6", Package: "ruby-3.6",
URL: "https://www.ruby-lang.org/", URL: "https://www.ruby-lang.org/",
}, },
@ -163,7 +161,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
{ {
name: "make", name: "make",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "make", Package: "make",
URL: "https://www.gnu.org/software/make", URL: "https://www.gnu.org/software/make",
}, },
@ -173,7 +171,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
{ {
name: "ruby-rake with matching origin", name: "ruby-rake with matching origin",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkDBEntry{
Package: "ruby-rake", Package: "ruby-rake",
URL: "https://github.com/ruby/rake", URL: "https://github.com/ruby/rake",
OriginPackage: "ruby-rake", OriginPackage: "ruby-rake",
@ -186,8 +184,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
pkg: pkg.Package{ pkg: pkg.Package{
Name: "ruby-rake", Name: "ruby-rake",
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "ruby-rake", Package: "ruby-rake",
URL: "https://www.ruby-lang.org/", URL: "https://www.ruby-lang.org/",
OriginPackage: "ruby", OriginPackage: "ruby",
@ -206,12 +203,12 @@ func Test_candidateProductsForAPK(t *testing.T) {
func Test_upstreamCandidates(t *testing.T) { func Test_upstreamCandidates(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
metadata pkg.ApkMetadata metadata pkg.ApkDBEntry
expected []upstreamCandidate expected []upstreamCandidate
}{ }{
{ {
name: "gocase", name: "gocase",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "p", Package: "p",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -220,7 +217,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "same package and origin simple case", name: "same package and origin simple case",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "p", Package: "p",
OriginPackage: "p", OriginPackage: "p",
}, },
@ -230,7 +227,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "different package and origin", name: "different package and origin",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "p", Package: "p",
OriginPackage: "origin", OriginPackage: "origin",
}, },
@ -240,7 +237,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "upstream python package information as qualifier py- prefix", name: "upstream python package information as qualifier py- prefix",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "py-potatoes", Package: "py-potatoes",
OriginPackage: "py-potatoes", OriginPackage: "py-potatoes",
}, },
@ -250,7 +247,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "upstream python package information as qualifier py3- prefix", name: "upstream python package information as qualifier py3- prefix",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "py3-potatoes", Package: "py3-potatoes",
OriginPackage: "py3-potatoes", OriginPackage: "py3-potatoes",
}, },
@ -260,7 +257,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "python package with distinct origin package", name: "python package with distinct origin package",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "py3-non-existant", Package: "py3-non-existant",
OriginPackage: "abcdefg", OriginPackage: "abcdefg",
}, },
@ -270,7 +267,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "upstream ruby package information as qualifier", name: "upstream ruby package information as qualifier",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "ruby-something", Package: "ruby-something",
OriginPackage: "ruby-something", OriginPackage: "ruby-something",
}, },
@ -280,7 +277,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "ruby package with distinct origin package", name: "ruby package with distinct origin package",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "ruby-something", Package: "ruby-something",
OriginPackage: "1234567", OriginPackage: "1234567",
}, },
@ -290,7 +287,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "postgesql-15 upstream postgresql", name: "postgesql-15 upstream postgresql",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "postgresql-15", Package: "postgresql-15",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -299,7 +296,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "postgesql15 upstream postgresql", name: "postgesql15 upstream postgresql",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "postgresql15", Package: "postgresql15",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -308,7 +305,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "go-1.19 upstream go", name: "go-1.19 upstream go",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "go-1.19", Package: "go-1.19",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -317,7 +314,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "go1.143 upstream go", name: "go1.143 upstream go",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "go1.143", Package: "go1.143",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -326,7 +323,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "abc-101.191.23456 upstream abc", name: "abc-101.191.23456 upstream abc",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "abc-101.191.23456", Package: "abc-101.191.23456",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -335,7 +332,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "abc101.191.23456 upstream abc", name: "abc101.191.23456 upstream abc",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "abc101.191.23456", Package: "abc101.191.23456",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -344,7 +341,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "abc101-12345-1045 upstream abc101-12345", name: "abc101-12345-1045 upstream abc101-12345",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "abc101-12345-1045", Package: "abc101-12345-1045",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -353,7 +350,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "abc101-a12345-1045 upstream abc101-a12345", name: "abc101-a12345-1045 upstream abc101-a12345",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "abc101-a12345-1045", Package: "abc101-a12345-1045",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -362,7 +359,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "package starting with single digit", name: "package starting with single digit",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "3proxy", Package: "3proxy",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -371,7 +368,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "package starting with multiple digits", name: "package starting with multiple digits",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "356proxy", Package: "356proxy",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -380,7 +377,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "package composed of only digits", name: "package composed of only digits",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "123456", Package: "123456",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -389,7 +386,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "ruby-3.6 upstream ruby", name: "ruby-3.6 upstream ruby",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "ruby-3.6", Package: "ruby-3.6",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -398,7 +395,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "ruby3.6 upstream ruby", name: "ruby3.6 upstream ruby",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "ruby3.6", Package: "ruby3.6",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -407,7 +404,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "ruby3.6-tacos upstream tacos", name: "ruby3.6-tacos upstream tacos",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "ruby3.6-tacos", Package: "ruby3.6-tacos",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -416,7 +413,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "ruby-3.6-tacos upstream tacos", name: "ruby-3.6-tacos upstream tacos",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "ruby-3.6-tacos", Package: "ruby-3.6-tacos",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{
@ -425,7 +422,7 @@ func Test_upstreamCandidates(t *testing.T) {
}, },
{ {
name: "abc1234jksajflksa", name: "abc1234jksajflksa",
metadata: pkg.ApkMetadata{ metadata: pkg.ApkDBEntry{
Package: "abc1234jksajflksa", Package: "abc1234jksajflksa",
}, },
expected: []upstreamCandidate{ expected: []upstreamCandidate{

View File

@ -168,18 +168,18 @@ func candidateVendors(p pkg.Package) []string {
} }
} }
switch p.MetadataType { switch p.Metadata.(type) {
case pkg.RpmMetadataType: case pkg.RpmDBEntry:
vendors.union(candidateVendorsForRPM(p)) vendors.union(candidateVendorsForRPM(p))
case pkg.GemMetadataType: case pkg.RubyGemspec:
vendors.union(candidateVendorsForRuby(p)) vendors.union(candidateVendorsForRuby(p))
case pkg.PythonPackageMetadataType: case pkg.PythonPackage:
vendors.union(candidateVendorsForPython(p)) vendors.union(candidateVendorsForPython(p))
case pkg.JavaMetadataType: case pkg.JavaArchive:
vendors.union(candidateVendorsForJava(p)) vendors.union(candidateVendorsForJava(p))
case pkg.ApkMetadataType: case pkg.ApkDBEntry:
vendors.union(candidateVendorsForAPK(p)) vendors.union(candidateVendorsForAPK(p))
case pkg.NpmPackageJSONMetadataType: case pkg.NpmPackage:
vendors.union(candidateVendorsForJavascript(p)) vendors.union(candidateVendorsForJavascript(p))
} }
@ -217,12 +217,14 @@ func candidateVendors(p pkg.Package) []string {
func candidateProducts(p pkg.Package) []string { func candidateProducts(p pkg.Package) []string {
products := newFieldCandidateSet(p.Name) products := newFieldCandidateSet(p.Name)
_, hasJavaMetadata := p.Metadata.(pkg.JavaArchive)
switch { switch {
case p.Language == pkg.Python: case p.Language == pkg.Python:
if !strings.HasPrefix(p.Name, "python") { if !strings.HasPrefix(p.Name, "python") {
products.addValue("python-" + p.Name) products.addValue("python-" + p.Name)
} }
case p.Language == pkg.Java || p.MetadataType == pkg.JavaMetadataType: case p.Language == pkg.Java || hasJavaMetadata:
products.addValue(candidateProductsForJava(p)...) products.addValue(candidateProductsForJava(p)...)
case p.Language == pkg.Go: case p.Language == pkg.Go:
// replace all candidates with only the golang-specific helper // replace all candidates with only the golang-specific helper
@ -234,7 +236,7 @@ func candidateProducts(p pkg.Package) []string {
} }
} }
if p.MetadataType == pkg.ApkMetadataType { if _, hasAPKMetadata := p.Metadata.(pkg.ApkDBEntry); hasAPKMetadata {
products.union(candidateProductsForAPK(p)) products.union(candidateProductsForAPK(p))
} }

View File

@ -72,8 +72,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Python, Language: pkg.Python,
Type: pkg.DebPkg, Type: pkg.DebPkg,
MetadataType: pkg.PythonPackageMetadataType, Metadata: pkg.PythonPackage{
Metadata: pkg.PythonPackageMetadata{
Author: "alex goodman", Author: "alex goodman",
AuthorEmail: "william.goodman@anchore.com", AuthorEmail: "william.goodman@anchore.com",
}, },
@ -121,8 +120,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
Version: "3.2", Version: "3.2",
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.JavaScript, Language: pkg.JavaScript,
MetadataType: pkg.NpmPackageJSONMetadataType, Metadata: pkg.NpmPackage{
Metadata: pkg.NpmPackageJSONMetadata{
Author: "jon", Author: "jon",
URL: "https://github.com/bob/npm-name", URL: "https://github.com/bob/npm-name",
}, },
@ -140,8 +138,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Ruby, Language: pkg.Ruby,
Type: pkg.DebPkg, Type: pkg.DebPkg,
MetadataType: pkg.GemMetadataType, Metadata: pkg.RubyGemspec{
Metadata: pkg.GemMetadata{
Authors: []string{ Authors: []string{
"someones name", "someones name",
"someones.elses.name@gmail.com", "someones.elses.name@gmail.com",
@ -182,9 +179,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{ PomProperties: &pkg.JavaPomProperties{
PomProperties: &pkg.PomProperties{
GroupID: "org.sonatype.nexus", GroupID: "org.sonatype.nexus",
}, },
}, },
@ -204,8 +200,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
Name: "wstx-asl", Name: "wstx-asl",
Version: "3.2.7", Version: "3.2.7",
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
Main: map[string]string{ Main: map[string]string{
"Ant-Version": "Apache Ant 1.6.5", "Ant-Version": "Apache Ant 1.6.5",
@ -256,8 +251,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "java-cataloger", FoundBy: "java-cataloger",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{
VirtualPath: "/opt/jboss/keycloak/modules/system/layers/base/org/apache/cxf/impl/main/cxf-rt-bindings-xml-3.3.10.jar", VirtualPath: "/opt/jboss/keycloak/modules/system/layers/base/org/apache/cxf/impl/main/cxf-rt-bindings-xml-3.3.10.jar",
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
Main: map[string]string{ Main: map[string]string{
@ -287,7 +281,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
"Tool": "Bnd-4.2.0.201903051501", "Tool": "Bnd-4.2.0.201903051501",
}, },
}, },
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
Path: "META-INF/maven/org.apache.cxf/cxf-rt-bindings-xml/pom.properties", Path: "META-INF/maven/org.apache.cxf/cxf-rt-bindings-xml/pom.properties",
GroupID: "org.apache.cxf", GroupID: "org.apache.cxf",
ArtifactID: "cxf-rt-bindings-xml", ArtifactID: "cxf-rt-bindings-xml",
@ -308,8 +302,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
Version: "3.2", Version: "3.2",
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Type: pkg.RpmPkg, Type: pkg.RpmPkg,
MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmDBEntry{
Metadata: pkg.RpmMetadata{
Vendor: "some-vendor", Vendor: "some-vendor",
}, },
}, },
@ -326,8 +319,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
Version: "1:3.2", Version: "1:3.2",
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Type: pkg.RpmPkg, Type: pkg.RpmPkg,
MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmDBEntry{
Metadata: pkg.RpmMetadata{
Vendor: "some-vendor", Vendor: "some-vendor",
}, },
}, },
@ -344,8 +336,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
Version: "1:3.2", Version: "1:3.2",
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Type: pkg.DebPkg, Type: pkg.DebPkg,
MetadataType: pkg.DpkgMetadataType, Metadata: pkg.DpkgDBEntry{},
Metadata: pkg.DpkgMetadata{},
}, },
expected: []string{ expected: []string{
"cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*", "cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*",
@ -359,9 +350,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{ PomProperties: &pkg.JavaPomProperties{
PomProperties: &pkg.PomProperties{
GroupID: "com.cloudbees.jenkins.plugins", GroupID: "com.cloudbees.jenkins.plugins",
}, },
}, },
@ -380,9 +370,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{ PomProperties: &pkg.JavaPomProperties{
PomProperties: &pkg.PomProperties{
GroupID: "io.jenkins.plugins.name.something", GroupID: "io.jenkins.plugins.name.something",
}, },
}, },
@ -404,9 +393,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{ PomProperties: &pkg.JavaPomProperties{
PomProperties: &pkg.PomProperties{
GroupID: "io.jenkins.plugins", GroupID: "io.jenkins.plugins",
}, },
}, },
@ -424,9 +412,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{ PomProperties: &pkg.JavaPomProperties{
PomProperties: &pkg.PomProperties{
GroupID: "io.jenkins-ci.plugins", GroupID: "io.jenkins-ci.plugins",
}, },
}, },
@ -446,9 +433,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{ PomProperties: &pkg.JavaPomProperties{
PomProperties: &pkg.PomProperties{
GroupID: "org.jenkins-ci.plugins", GroupID: "org.jenkins-ci.plugins",
}, },
}, },
@ -468,9 +454,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{ PomProperties: &pkg.JavaPomProperties{
PomProperties: &pkg.PomProperties{
GroupID: "org.atlassian.jira", GroupID: "org.atlassian.jira",
ArtifactID: "jira_client_core", ArtifactID: "jira_client_core",
}, },
@ -503,9 +488,8 @@ func TestGeneratePackageCPEs(t *testing.T) {
FoundBy: "some-analyzer", FoundBy: "some-analyzer",
Language: pkg.Java, Language: pkg.Java,
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{ PomProperties: &pkg.JavaPomProperties{
PomProperties: &pkg.PomProperties{
GroupID: "com.cloudbees.jenkins.modules", GroupID: "com.cloudbees.jenkins.modules",
ArtifactID: "cloudbees-installation-manager", ArtifactID: "cloudbees-installation-manager",
}, },
@ -573,8 +557,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
Language: pkg.Java, Language: pkg.Java,
FoundBy: "java-cataloger", FoundBy: "java-cataloger",
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
Main: map[string]string{ Main: map[string]string{
"Extension-Name": "handlebars", "Extension-Name": "handlebars",
@ -586,7 +569,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
"Short-Name": "handlebars", "Short-Name": "handlebars",
}, },
}, },
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
GroupID: "org.jenkins-ci.ui", GroupID: "org.jenkins-ci.ui",
ArtifactID: "handlebars", ArtifactID: "handlebars",
Version: "3.0.8", Version: "3.0.8",
@ -610,15 +593,14 @@ func TestGeneratePackageCPEs(t *testing.T) {
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
FoundBy: "java-cataloger", FoundBy: "java-cataloger",
Language: pkg.Java, Language: pkg.Java,
MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaArchive{
Metadata: pkg.JavaMetadata{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
Main: map[string]string{ Main: map[string]string{
"Extension-Name": "active-directory", "Extension-Name": "active-directory",
"Group-Id": "org.jenkins-ci.plugins", "Group-Id": "org.jenkins-ci.plugins",
}, },
}, },
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
GroupID: "org.jenkins-ci.plugins", GroupID: "org.jenkins-ci.plugins",
ArtifactID: "org.jenkins-ci.plugins", ArtifactID: "org.jenkins-ci.plugins",
Version: "2.25.1", Version: "2.25.1",
@ -648,8 +630,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
Type: pkg.GemPkg, Type: pkg.GemPkg,
FoundBy: "gem-cataloger", FoundBy: "gem-cataloger",
Language: pkg.Ruby, Language: pkg.Ruby,
MetadataType: pkg.GemMetadataType, Metadata: pkg.RubyGemspec{
Metadata: pkg.GemMetadata{
Name: "bundler", Name: "bundler",
Version: "2.1.4", Version: "2.1.4",
Authors: []string{ Authors: []string{
@ -700,8 +681,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
Type: pkg.ApkPkg, Type: pkg.ApkPkg,
FoundBy: "apk-db-analyzer", FoundBy: "apk-db-analyzer",
Language: pkg.UnknownLanguage, Language: pkg.UnknownLanguage,
MetadataType: pkg.ApkMetadataType, Metadata: pkg.ApkDBEntry{
Metadata: pkg.ApkMetadata{
Package: "ruby-rake", Package: "ruby-rake",
URL: "https://www.ruby-lang.org/", URL: "https://www.ruby-lang.org/",
OriginPackage: "ruby", OriginPackage: "ruby",
@ -744,18 +724,18 @@ func TestGeneratePackageCPEs(t *testing.T) {
sort.Strings(extra) sort.Strings(extra)
if len(extra) > 0 { if len(extra) > 0 {
t.Errorf("found extra CPEs:") t.Errorf("found extra CPEs:")
}
for _, d := range extra { for _, d := range extra {
fmt.Printf(" %q,\n", d) t.Logf(" %q,\n", d)
}
} }
missing := strset.Difference(expectedCpeSet, actualCpeSet).List() missing := strset.Difference(expectedCpeSet, actualCpeSet).List()
sort.Strings(missing) sort.Strings(missing)
if len(missing) > 0 { if len(missing) > 0 {
t.Errorf("missing CPEs:") t.Errorf("missing CPEs:")
}
for _, d := range missing { for _, d := range missing {
fmt.Printf(" %q,\n", d) t.Logf(" %q,\n", d)
}
} }
}) })
} }
@ -797,8 +777,8 @@ func TestCandidateProducts(t *testing.T) {
Name: "some-java-package-with-group-id", Name: "some-java-package-with-group-id",
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
Language: pkg.Java, Language: pkg.Java,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
GroupID: "com.apple.itunes", GroupID: "com.apple.itunes",
}, },
}, },
@ -811,8 +791,8 @@ func TestCandidateProducts(t *testing.T) {
Name: "some-java-package-with-group-id", Name: "some-java-package-with-group-id",
Type: pkg.JavaPkg, Type: pkg.JavaPkg,
Language: pkg.Java, Language: pkg.Java,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
GroupID: "com.apple.itunes.*", GroupID: "com.apple.itunes.*",
}, },
}, },
@ -825,8 +805,8 @@ func TestCandidateProducts(t *testing.T) {
Name: "some-jenkins-plugin", Name: "some-jenkins-plugin",
Type: pkg.JenkinsPluginPkg, Type: pkg.JenkinsPluginPkg,
Language: pkg.Java, Language: pkg.Java,
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
GroupID: "com.cloudbees.jenkins.plugins", GroupID: "com.cloudbees.jenkins.plugins",
}, },
}, },

View File

@ -55,7 +55,7 @@ func candidateVendorsForJava(p pkg.Package) fieldCandidateSet {
func vendorsFromJavaManifestNames(p pkg.Package) fieldCandidateSet { func vendorsFromJavaManifestNames(p pkg.Package) fieldCandidateSet {
vendors := newFieldCandidateSet() vendors := newFieldCandidateSet()
metadata, ok := p.Metadata.(pkg.JavaMetadata) metadata, ok := p.Metadata.(pkg.JavaArchive)
if !ok { if !ok {
return vendors return vendors
} }
@ -159,7 +159,7 @@ func productsFromArtifactAndGroupIDs(artifactID string, groupIDs []string) []str
} }
func artifactIDFromJavaPackage(p pkg.Package) string { func artifactIDFromJavaPackage(p pkg.Package) string {
metadata, ok := p.Metadata.(pkg.JavaMetadata) metadata, ok := p.Metadata.(pkg.JavaArchive)
if !ok { if !ok {
return "" return ""
} }
@ -177,7 +177,7 @@ func artifactIDFromJavaPackage(p pkg.Package) string {
} }
func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) { func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
metadata, ok := p.Metadata.(pkg.JavaMetadata) metadata, ok := p.Metadata.(pkg.JavaArchive)
if !ok { if !ok {
return nil return nil
} }
@ -188,7 +188,7 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
// GroupIDsFromJavaMetadata returns the possible group IDs for a Java package // GroupIDsFromJavaMetadata returns the possible group IDs for a Java package
// This function is similar to GroupIDFromJavaPackage, but returns all possible group IDs and is less strict // This function is similar to GroupIDFromJavaPackage, but returns all possible group IDs and is less strict
// It is used as a way to generate possible candidates for CPE matching. // It is used as a way to generate possible candidates for CPE matching.
func GroupIDsFromJavaMetadata(pkgName string, metadata pkg.JavaMetadata) (groupIDs []string) { func GroupIDsFromJavaMetadata(pkgName string, metadata pkg.JavaArchive) (groupIDs []string) {
groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...) groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...)
groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...) groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...)
groupIDs = append(groupIDs, groupIDsFromJavaManifest(pkgName, metadata.Manifest)...) groupIDs = append(groupIDs, groupIDsFromJavaManifest(pkgName, metadata.Manifest)...)
@ -196,7 +196,7 @@ func GroupIDsFromJavaMetadata(pkgName string, metadata pkg.JavaMetadata) (groupI
return groupIDs return groupIDs
} }
func groupIDsFromPomProperties(properties *pkg.PomProperties) (groupIDs []string) { func groupIDsFromPomProperties(properties *pkg.JavaPomProperties) (groupIDs []string) {
if properties == nil { if properties == nil {
return nil return nil
} }
@ -214,7 +214,7 @@ func groupIDsFromPomProperties(properties *pkg.PomProperties) (groupIDs []string
return groupIDs return groupIDs
} }
func groupIDsFromPomProject(project *pkg.PomProject) (groupIDs []string) { func groupIDsFromPomProject(project *pkg.JavaPomProject) (groupIDs []string) {
if project == nil { if project == nil {
return nil return nil
} }

View File

@ -78,8 +78,8 @@ func Test_candidateProductsForJava(t *testing.T) {
{ {
name: "duplicate groupID in artifactID field", name: "duplicate groupID in artifactID field",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
GroupID: "org.sonatype.nexus", GroupID: "org.sonatype.nexus",
ArtifactID: "org.sonatype.nexus", ArtifactID: "org.sonatype.nexus",
}, },
@ -90,8 +90,8 @@ func Test_candidateProductsForJava(t *testing.T) {
{ {
name: "detect groupID-like value in artifactID field", name: "detect groupID-like value in artifactID field",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
ArtifactID: "org.sonatype.nexus", ArtifactID: "org.sonatype.nexus",
}, },
}, },
@ -153,8 +153,8 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "go case", name: "go case",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
GroupID: "io.jenkins-ci.plugin.thing;version='[2,3)'", GroupID: "io.jenkins-ci.plugin.thing;version='[2,3)'",
}, },
}, },
@ -164,8 +164,8 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "from artifactID", name: "from artifactID",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
ArtifactID: "io.jenkins-ci.plugin.thing; version='[2,3)' ; org.something.else", ArtifactID: "io.jenkins-ci.plugin.thing; version='[2,3)' ; org.something.else",
}, },
}, },
@ -175,7 +175,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "from main Extension-Name field", name: "from main Extension-Name field",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
Main: map[string]string{ Main: map[string]string{
"Extension-Name": "io.jenkins-ci.plugin.thing", "Extension-Name": "io.jenkins-ci.plugin.thing",
@ -188,7 +188,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "from named section Extension-Name field", name: "from named section Extension-Name field",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
NamedSections: map[string]map[string]string{ NamedSections: map[string]map[string]string{
"section": { "section": {
@ -203,7 +203,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "from main field - tier 1", name: "from main field - tier 1",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
Main: map[string]string{ Main: map[string]string{
// positive cases // positive cases
@ -236,7 +236,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "from main field - tier 2", name: "from main field - tier 2",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
Main: map[string]string{ Main: map[string]string{
// positive cases // positive cases
@ -256,7 +256,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "from main field - negative cases", name: "from main field - negative cases",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
Main: map[string]string{ Main: map[string]string{
// negative cases // negative cases
@ -271,7 +271,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "from named section field - tier 1", name: "from named section field - tier 1",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
NamedSections: map[string]map[string]string{ NamedSections: map[string]map[string]string{
"section": { "section": {
@ -306,7 +306,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "from named section field - negative cases", name: "from named section field - negative cases",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
NamedSections: map[string]map[string]string{ NamedSections: map[string]map[string]string{
"section": { "section": {
@ -323,7 +323,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
{ {
name: "no manifest or pom info", name: "no manifest or pom info",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{}, Metadata: pkg.JavaArchive{},
}, },
expects: nil, expects: nil,
}, },
@ -349,8 +349,8 @@ func Test_artifactIDFromJavaPackage(t *testing.T) {
{ {
name: "go case", name: "go case",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
ArtifactID: "cloudbees-installation-manager", ArtifactID: "cloudbees-installation-manager",
}, },
}, },
@ -360,8 +360,8 @@ func Test_artifactIDFromJavaPackage(t *testing.T) {
{ {
name: "ignore groupID-like things", name: "ignore groupID-like things",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
PomProperties: &pkg.PomProperties{ PomProperties: &pkg.JavaPomProperties{
ArtifactID: "io.jenkins-ci.plugin.thing", ArtifactID: "io.jenkins-ci.plugin.thing",
}, },
}, },
@ -390,7 +390,7 @@ func Test_vendorsFromJavaManifestNames(t *testing.T) {
{ {
name: "from manifest named section fields", name: "from manifest named section fields",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
NamedSections: map[string]map[string]string{ NamedSections: map[string]map[string]string{
"section": { "section": {
@ -407,7 +407,7 @@ func Test_vendorsFromJavaManifestNames(t *testing.T) {
{ {
name: "from manifest named section fields - negative cases", name: "from manifest named section fields - negative cases",
pkg: pkg.Package{ pkg: pkg.Package{
Metadata: pkg.JavaMetadata{ Metadata: pkg.JavaArchive{
Manifest: &pkg.JavaManifest{ Manifest: &pkg.JavaManifest{
NamedSections: map[string]map[string]string{ NamedSections: map[string]map[string]string{
"section": { "section": {

View File

@ -3,12 +3,12 @@ package cpe
import "github.com/anchore/syft/syft/pkg" import "github.com/anchore/syft/syft/pkg"
func candidateVendorsForJavascript(p pkg.Package) fieldCandidateSet { func candidateVendorsForJavascript(p pkg.Package) fieldCandidateSet {
if p.MetadataType != pkg.NpmPackageJSONMetadataType { if _, ok := p.Metadata.(pkg.NpmPackage); !ok {
return nil return nil
} }
vendors := newFieldCandidateSet() vendors := newFieldCandidateSet()
metadata, ok := p.Metadata.(pkg.NpmPackageJSONMetadata) metadata, ok := p.Metadata.(pkg.NpmPackage)
if !ok { if !ok {
return nil return nil
} }

View File

@ -16,7 +16,7 @@ func additionalVendorsForPython(v string) (vendors []string) {
} }
func candidateVendorsForPython(p pkg.Package) fieldCandidateSet { func candidateVendorsForPython(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.PythonPackageMetadata) metadata, ok := p.Metadata.(pkg.PythonPackage)
if !ok { if !ok {
return nil return nil
} }

View File

@ -3,7 +3,7 @@ package cpe
import "github.com/anchore/syft/syft/pkg" import "github.com/anchore/syft/syft/pkg"
func candidateVendorsForRPM(p pkg.Package) fieldCandidateSet { func candidateVendorsForRPM(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.RpmMetadata) metadata, ok := p.Metadata.(pkg.RpmDBEntry)
if !ok { if !ok {
return nil return nil
} }

Some files were not shown because too many files have changed in this diff Show More