mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
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:
parent
f442586ec9
commit
1aaa644007
@ -151,6 +151,39 @@ sequenceDiagram
|
||||
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
|
||||
|
||||
|
||||
25
README.md
25
README.md
@ -530,6 +530,31 @@ platform: ""
|
||||
# - spm-cataloger
|
||||
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
|
||||
package:
|
||||
|
||||
|
||||
@ -365,7 +365,7 @@ tasks:
|
||||
generate-json-schema:
|
||||
desc: Generate a new JSON schema
|
||||
cmds:
|
||||
- "cd syft/internal && go generate . && cd jsonschema && go run ."
|
||||
- "cd syft/internal && go generate . && cd jsonschema && go run . && go fmt ../..."
|
||||
|
||||
generate-license-list:
|
||||
desc: Generate an updated license processing code off of the latest available SPDX license list
|
||||
|
||||
@ -59,13 +59,14 @@ func Attest(app clio.Application) *cobra.Command {
|
||||
OutputFile: options.OutputFile{ // nolint:staticcheck
|
||||
Enabled: false, // explicitly not allowed
|
||||
},
|
||||
OutputTemplate: options.OutputTemplate{
|
||||
Enabled: false, // explicitly not allowed
|
||||
},
|
||||
Format: options.DefaultFormat(),
|
||||
},
|
||||
Catalog: options.DefaultCatalog(),
|
||||
}
|
||||
|
||||
// template format explicitly not allowed
|
||||
opts.Format.Template.Enabled = false
|
||||
|
||||
return app.SetupCommand(&cobra.Command{
|
||||
Use: "attest --output [FORMAT] <IMAGE>",
|
||||
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
|
||||
|
||||
45
cmd/syft/cli/options/format.go
Normal file
45
cmd/syft/cli/options/format.go
Normal 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
|
||||
}
|
||||
25
cmd/syft/cli/options/format_json.go
Normal file
25
cmd/syft/cli/options/format_json.go
Normal 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
|
||||
}
|
||||
@ -6,21 +6,27 @@ import (
|
||||
"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:"-"`
|
||||
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 {
|
||||
flags.StringVarP(&o.Path, "template", "t",
|
||||
"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 {
|
||||
return nil, nil
|
||||
}
|
||||
@ -32,7 +32,7 @@ type Output struct {
|
||||
AllowMultipleOutputs bool `yaml:"-" json:"-" mapstructure:"-"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputFile `yaml:",inline" json:"" mapstructure:",squash"`
|
||||
OutputTemplate `yaml:"template" json:"template" mapstructure:"template"`
|
||||
Format `yaml:"format" json:"format" mapstructure:"format"`
|
||||
}
|
||||
|
||||
func DefaultOutput() Output {
|
||||
@ -42,9 +42,7 @@ func DefaultOutput() Output {
|
||||
OutputFile: OutputFile{
|
||||
Enabled: true,
|
||||
},
|
||||
OutputTemplate: OutputTemplate{
|
||||
Enabled: true,
|
||||
},
|
||||
Format: DefaultFormat(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +66,7 @@ func (o Output) SBOMWriter() (sbom.Writer, error) {
|
||||
|
||||
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`)
|
||||
}
|
||||
|
||||
@ -80,24 +78,6 @@ func (o Output) SBOMWriter() (sbom.Writer, error) {
|
||||
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 {
|
||||
names := strset.New()
|
||||
for _, output := range o.Outputs {
|
||||
|
||||
@ -27,7 +27,7 @@ func Test_getEncoders(t *testing.T) {
|
||||
}
|
||||
|
||||
opts := DefaultOutput()
|
||||
opts.OutputTemplate.Path = "somewhere"
|
||||
opts.Format.Template.Path = "somewhere"
|
||||
|
||||
encoders, err := opts.Encoders()
|
||||
require.NoError(t, err)
|
||||
@ -158,7 +158,7 @@ func Test_EncoderCollection_ByString_IDOnly_Defaults(t *testing.T) {
|
||||
}
|
||||
|
||||
opts := DefaultOutput()
|
||||
opts.OutputTemplate.Path = "somewhere"
|
||||
opts.Format.Template.Path = "somewhere"
|
||||
|
||||
defaultEncoders, err := opts.Encoders()
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// 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.
|
||||
JSONSchemaVersion = "11.0.1"
|
||||
JSONSchemaVersion = "12.0.0"
|
||||
)
|
||||
|
||||
2200
schema/json/schema-12.0.0.json
Normal file
2200
schema/json/schema-12.0.0.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,9 +10,9 @@ import (
|
||||
func encodeAuthor(p pkg.Package) string {
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
case pkg.NpmPackage:
|
||||
return metadata.Author
|
||||
case pkg.PythonPackageMetadata:
|
||||
case pkg.PythonPackage:
|
||||
author := metadata.Author
|
||||
if metadata.AuthorEmail != "" {
|
||||
if author == "" {
|
||||
@ -21,7 +21,7 @@ func encodeAuthor(p pkg.Package) string {
|
||||
author += fmt.Sprintf(" <%s>", metadata.AuthorEmail)
|
||||
}
|
||||
return author
|
||||
case pkg.GemMetadata:
|
||||
case pkg.RubyGemspec:
|
||||
if len(metadata.Authors) > 0 {
|
||||
return strings.Join(metadata.Authors, ",")
|
||||
}
|
||||
@ -33,15 +33,15 @@ func encodeAuthor(p pkg.Package) string {
|
||||
|
||||
func decodeAuthor(author string, metadata interface{}) {
|
||||
switch meta := metadata.(type) {
|
||||
case *pkg.NpmPackageJSONMetadata:
|
||||
case *pkg.NpmPackage:
|
||||
meta.Author = author
|
||||
case *pkg.PythonPackageMetadata:
|
||||
case *pkg.PythonPackage:
|
||||
parts := strings.SplitN(author, " <", 2)
|
||||
meta.Author = parts[0]
|
||||
if len(parts) > 1 {
|
||||
meta.AuthorEmail = strings.TrimSuffix(parts[1], ">")
|
||||
}
|
||||
case *pkg.GemMetadata:
|
||||
case *pkg.RubyGemspec:
|
||||
meta.Authors = strings.Split(author, ",")
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func Test_encodeAuthor(t *testing.T) {
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Metadata: pkg.RubyGemspec{
|
||||
Authors: []string{
|
||||
"auth1",
|
||||
"auth2",
|
||||
@ -35,7 +35,7 @@ func Test_encodeAuthor(t *testing.T) {
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
@ -44,7 +44,7 @@ func Test_encodeAuthor(t *testing.T) {
|
||||
{
|
||||
name: "from python - just name",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
@ -53,7 +53,7 @@ func Test_encodeAuthor(t *testing.T) {
|
||||
{
|
||||
name: "from python - just email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
@ -62,7 +62,7 @@ func Test_encodeAuthor(t *testing.T) {
|
||||
{
|
||||
name: "from python - both name and email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "auth",
|
||||
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
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "",
|
||||
},
|
||||
},
|
||||
|
||||
@ -8,11 +8,21 @@ import (
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format/common"
|
||||
"github.com/anchore/syft/syft/internal/packagemetadata"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func encodeComponent(p pkg.Package) cyclonedx.Component {
|
||||
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)...)
|
||||
locations := p.Locations.ToSlice()
|
||||
if len(locations) > 0 {
|
||||
@ -85,9 +95,9 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
||||
|
||||
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 == "" {
|
||||
p.Type = pkg.TypeFromPURL(p.PURL)
|
||||
@ -109,13 +119,13 @@ func decodeLocations(vals map[string]string) file.LocationSet {
|
||||
return file.NewLocationSet(out...)
|
||||
}
|
||||
|
||||
func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typ pkg.MetadataType) interface{} {
|
||||
if typ != "" && c.Properties != nil {
|
||||
metaTyp, ok := pkg.MetadataTypeByName[typ]
|
||||
if !ok {
|
||||
func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typeName string) interface{} {
|
||||
if typeName != "" && c.Properties != nil {
|
||||
metadataType := packagemetadata.ReflectTypeFromJSONName(typeName)
|
||||
if metadataType == nil {
|
||||
return nil
|
||||
}
|
||||
metaPtrTyp := reflect.PtrTo(metaTyp)
|
||||
metaPtrTyp := reflect.PtrTo(metadataType)
|
||||
metaPtr := common.Decode(metaPtrTyp, vals, "syft:metadata", CycloneDXFields)
|
||||
|
||||
// Map all explicit metadata properties
|
||||
|
||||
@ -17,7 +17,7 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected *[]cyclonedx.Property
|
||||
expected []cyclonedx.Property
|
||||
}{
|
||||
{
|
||||
name: "no metadata",
|
||||
@ -31,7 +31,7 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocationFromCoordinates(file.Coordinates{RealPath: "test"}),
|
||||
),
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "libc-utils",
|
||||
OriginPackage: "libc-dev",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
@ -48,8 +48,9 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
Files: []pkg.ApkFileRecord{},
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
expected: []cyclonedx.Property{
|
||||
{Name: "syft:package:foundBy", Value: "cataloger"},
|
||||
{Name: "syft:package:metadataType", Value: "apk-db-entry"},
|
||||
{Name: "syft:location:0:path", Value: "test"},
|
||||
{Name: "syft:metadata:gitCommitOfApkPort", Value: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479"},
|
||||
{Name: "syft:metadata:installedSize", Value: "4096"},
|
||||
@ -63,8 +64,7 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
{
|
||||
name: "from dpkg",
|
||||
input: pkg.Package{
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Package: "tzdata",
|
||||
Version: "2020a-0+deb10u1",
|
||||
Source: "tzdata-dev",
|
||||
@ -75,8 +75,8 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
{Name: "syft:package:metadataType", Value: "DpkgMetadata"},
|
||||
expected: []cyclonedx.Property{
|
||||
{Name: "syft:package:metadataType", Value: "dpkg-db-entry"},
|
||||
{Name: "syft:metadata:installedSize", Value: "3036"},
|
||||
{Name: "syft:metadata:source", Value: "tzdata-dev"},
|
||||
{Name: "syft:metadata:sourceVersion", Value: "1.0"},
|
||||
@ -85,20 +85,19 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
{
|
||||
name: "from go bin",
|
||||
input: pkg.Package{
|
||||
Name: "golang.org/x/net",
|
||||
Version: "v0.0.0-20211006190231-62292e806868",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
Name: "golang.org/x/net",
|
||||
Version: "v0.0.0-20211006190231-62292e806868",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||
GoCompiledVersion: "1.17",
|
||||
Architecture: "amd64",
|
||||
H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=",
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
expected: []cyclonedx.Property{
|
||||
{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:metadata:architecture", Value: "amd64"},
|
||||
{Name: "syft:metadata:goCompiledVersion", Value: "1.17"},
|
||||
@ -108,18 +107,17 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
{
|
||||
name: "from go mod",
|
||||
input: pkg.Package{
|
||||
Name: "golang.org/x/net",
|
||||
Version: "v0.0.0-20211006190231-62292e806868",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
MetadataType: pkg.GolangModMetadataType,
|
||||
Metadata: pkg.GolangModMetadata{
|
||||
Name: "golang.org/x/net",
|
||||
Version: "v0.0.0-20211006190231-62292e806868",
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Metadata: pkg.GolangModuleEntry{
|
||||
H1Digest: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k=",
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
expected: []cyclonedx.Property{
|
||||
{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:metadata:h1Digest", Value: "h1:KlOXYy8wQWTUJYFgkUI40Lzr06ofg5IRXUK5C7qZt1k="},
|
||||
},
|
||||
@ -127,11 +125,10 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: pkg.RpmMetadata{
|
||||
Name: "dive",
|
||||
Version: "0.9.2-1",
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Name: "dive",
|
||||
Epoch: &epoch,
|
||||
Arch: "x86_64",
|
||||
@ -140,11 +137,11 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
Vendor: "",
|
||||
Files: []pkg.RpmdbFileRecord{},
|
||||
Files: []pkg.RpmFileRecord{},
|
||||
},
|
||||
},
|
||||
expected: &[]cyclonedx.Property{
|
||||
{Name: "syft:package:metadataType", Value: "RpmMetadata"},
|
||||
expected: []cyclonedx.Property{
|
||||
{Name: "syft:package:metadataType", Value: "rpm-db-entry"},
|
||||
{Name: "syft:package:type", Value: "rpm"},
|
||||
{Name: "syft:metadata:epoch", Value: "2"},
|
||||
{Name: "syft:metadata:release", Value: "1"},
|
||||
@ -156,7 +153,13 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -268,11 +271,10 @@ func Test_deriveBomRef(t *testing.T) {
|
||||
|
||||
func Test_decodeComponent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
component cyclonedx.Component
|
||||
wantLanguage pkg.Language
|
||||
wantMetadataType pkg.MetadataType
|
||||
wantMetadata interface{}
|
||||
name string
|
||||
component cyclonedx.Component
|
||||
wantLanguage pkg.Language
|
||||
wantMetadata any
|
||||
}{
|
||||
{
|
||||
name: "derive language from pURL if missing",
|
||||
@ -300,8 +302,7 @@ func Test_decodeComponent(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMetadataType: pkg.RpmMetadataType,
|
||||
wantMetadata: pkg.RpmMetadata{},
|
||||
wantMetadata: pkg.RpmDBEntry{},
|
||||
},
|
||||
{
|
||||
name: "handle RpmdbMetadata type with properties",
|
||||
@ -314,7 +315,7 @@ func Test_decodeComponent(t *testing.T) {
|
||||
Properties: &[]cyclonedx.Property{
|
||||
{
|
||||
Name: "syft:package:metadataType",
|
||||
Value: "RpmMetadata",
|
||||
Value: "RpmDBMetadata",
|
||||
},
|
||||
{
|
||||
Name: "syft:metadata:release",
|
||||
@ -322,8 +323,7 @@ func Test_decodeComponent(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
wantMetadataType: pkg.RpmMetadataType,
|
||||
wantMetadata: pkg.RpmMetadata{
|
||||
wantMetadata: pkg.RpmDBEntry{
|
||||
Release: "some-release",
|
||||
},
|
||||
},
|
||||
@ -335,12 +335,12 @@ func Test_decodeComponent(t *testing.T) {
|
||||
if tt.wantLanguage != "" {
|
||||
assert.Equal(t, tt.wantLanguage, p.Language)
|
||||
}
|
||||
if tt.wantMetadataType != "" {
|
||||
assert.Equal(t, tt.wantMetadataType, p.MetadataType)
|
||||
}
|
||||
if tt.wantMetadata != nil {
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,9 +5,9 @@ import "github.com/anchore/syft/syft/pkg"
|
||||
func encodeDescription(p pkg.Package) string {
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
case pkg.ApkDBEntry:
|
||||
return metadata.Description
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
case pkg.NpmPackage:
|
||||
return metadata.Description
|
||||
}
|
||||
}
|
||||
@ -16,9 +16,9 @@ func encodeDescription(p pkg.Package) string {
|
||||
|
||||
func decodeDescription(description string, metadata interface{}) {
|
||||
switch meta := metadata.(type) {
|
||||
case *pkg.ApkMetadata:
|
||||
case *pkg.ApkDBEntry:
|
||||
meta.Description = description
|
||||
case *pkg.NpmPackageJSONMetadata:
|
||||
case *pkg.NpmPackage:
|
||||
meta.Description = description
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func Test_encodeDescription(t *testing.T) {
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Description: "a description!",
|
||||
},
|
||||
},
|
||||
@ -32,7 +32,7 @@ func Test_encodeDescription(t *testing.T) {
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
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
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
|
||||
@ -19,21 +19,21 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
||||
// Skip adding extracted URL and Homepage metadata
|
||||
// as "external_reference" if the metadata isn't IRI-compliant
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
case pkg.ApkDBEntry:
|
||||
if metadata.URL != "" && isValidExternalRef(metadata.URL) {
|
||||
refs = append(refs, cyclonedx.ExternalReference{
|
||||
URL: metadata.URL,
|
||||
Type: cyclonedx.ERTypeDistribution,
|
||||
})
|
||||
}
|
||||
case pkg.CargoPackageMetadata:
|
||||
case pkg.RustCargoLockEntry:
|
||||
if metadata.Source != "" {
|
||||
refs = append(refs, cyclonedx.ExternalReference{
|
||||
URL: metadata.Source,
|
||||
Type: cyclonedx.ERTypeDistribution,
|
||||
})
|
||||
}
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
case pkg.NpmPackage:
|
||||
if metadata.URL != "" && isValidExternalRef(metadata.URL) {
|
||||
refs = append(refs, cyclonedx.ExternalReference{
|
||||
URL: metadata.URL,
|
||||
@ -46,14 +46,14 @@ func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference {
|
||||
Type: cyclonedx.ERTypeWebsite,
|
||||
})
|
||||
}
|
||||
case pkg.GemMetadata:
|
||||
case pkg.RubyGemspec:
|
||||
if metadata.Homepage != "" && isValidExternalRef(metadata.Homepage) {
|
||||
refs = append(refs, cyclonedx.ExternalReference{
|
||||
URL: metadata.Homepage,
|
||||
Type: cyclonedx.ERTypeWebsite,
|
||||
})
|
||||
}
|
||||
case pkg.JavaMetadata:
|
||||
case pkg.JavaArchive:
|
||||
if len(metadata.ArchiveDigests) > 0 {
|
||||
for _, digest := range metadata.ArchiveDigests {
|
||||
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 != "" {
|
||||
ref := cyclonedx.ExternalReference{
|
||||
URL: metadata.DirectURLOrigin.URL,
|
||||
@ -105,16 +105,16 @@ func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) {
|
||||
return
|
||||
}
|
||||
switch meta := metadata.(type) {
|
||||
case *pkg.ApkMetadata:
|
||||
case *pkg.ApkDBEntry:
|
||||
meta.URL = refURL(c, cyclonedx.ERTypeDistribution)
|
||||
case *pkg.CargoPackageMetadata:
|
||||
case *pkg.RustCargoLockEntry:
|
||||
meta.Source = refURL(c, cyclonedx.ERTypeDistribution)
|
||||
case *pkg.NpmPackageJSONMetadata:
|
||||
case *pkg.NpmPackage:
|
||||
meta.URL = refURL(c, cyclonedx.ERTypeDistribution)
|
||||
meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite)
|
||||
case *pkg.GemMetadata:
|
||||
case *pkg.RubyGemspec:
|
||||
meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite)
|
||||
case *pkg.JavaMetadata:
|
||||
case *pkg.JavaArchive:
|
||||
var digests []syftFile.Digest
|
||||
if ref := findExternalRef(c, cyclonedx.ERTypeBuildMeta); ref != nil {
|
||||
if ref.Hashes != nil {
|
||||
@ -128,7 +128,7 @@ func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) {
|
||||
}
|
||||
|
||||
meta.ArchiveDigests = digests
|
||||
case *pkg.PythonPackageMetadata:
|
||||
case *pkg.PythonPackage:
|
||||
if meta.DirectURLOrigin == nil {
|
||||
meta.DirectURLOrigin = &pkg.PythonDirectURLOriginInfo{}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
@ -34,7 +34,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
{
|
||||
name: "from npm with valid URL",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
@ -45,7 +45,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
{
|
||||
name: "from npm with invalid URL but valid Homepage",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
URL: "b-place",
|
||||
Homepage: "http://b-place.gov",
|
||||
},
|
||||
@ -57,13 +57,12 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
{
|
||||
name: "from cargo lock",
|
||||
input: pkg.Package{
|
||||
Name: "ansi_term",
|
||||
Version: "0.12.1",
|
||||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
MetadataType: pkg.RustCargoPackageMetadataType,
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Metadata: pkg.CargoPackageMetadata{
|
||||
Name: "ansi_term",
|
||||
Version: "0.12.1",
|
||||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Metadata: pkg.RustCargoLockEntry{
|
||||
Name: "ansi_term",
|
||||
Version: "0.12.1",
|
||||
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",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
URL: "http://a-place.gov",
|
||||
Homepage: "http://homepage",
|
||||
},
|
||||
@ -93,7 +92,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Metadata: pkg.RubyGemspec{
|
||||
Homepage: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
@ -104,7 +103,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
{
|
||||
name: "from python direct url",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
@ -117,7 +116,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
{
|
||||
name: "from python direct url with commit",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
DirectURLOrigin: &pkg.PythonDirectURLOriginInfo{
|
||||
URL: "http://a-place.gov",
|
||||
CommitID: "test",
|
||||
@ -131,7 +130,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
||||
{
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
|
||||
@ -4,7 +4,7 @@ import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func encodeGroup(p pkg.Package) string {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -12,9 +12,9 @@ func encodeGroup(p pkg.Package) string {
|
||||
}
|
||||
|
||||
func decodeGroup(group string, metadata interface{}) {
|
||||
if meta, ok := metadata.(*pkg.JavaMetadata); ok {
|
||||
if meta, ok := metadata.(*pkg.JavaArchive); ok {
|
||||
if meta.PomProperties == nil {
|
||||
meta.PomProperties = &pkg.PomProperties{}
|
||||
meta.PomProperties = &pkg.JavaPomProperties{}
|
||||
}
|
||||
meta.PomProperties.GroupID = group
|
||||
}
|
||||
|
||||
@ -22,22 +22,22 @@ func Test_encodeGroup(t *testing.T) {
|
||||
{
|
||||
name: "metadata is not Java",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{},
|
||||
Metadata: pkg.NpmPackage{},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "metadata is Java but pom properties is empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{},
|
||||
Metadata: pkg.JavaArchive{},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "metadata is Java and contains pomProperties",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "test",
|
||||
},
|
||||
},
|
||||
|
||||
@ -7,11 +7,11 @@ import (
|
||||
func encodePublisher(p pkg.Package) string {
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
case pkg.ApkDBEntry:
|
||||
return metadata.Maintainer
|
||||
case pkg.RpmMetadata:
|
||||
case pkg.RpmDBEntry:
|
||||
return metadata.Vendor
|
||||
case pkg.DpkgMetadata:
|
||||
case pkg.DpkgDBEntry:
|
||||
return metadata.Maintainer
|
||||
}
|
||||
}
|
||||
@ -20,11 +20,11 @@ func encodePublisher(p pkg.Package) string {
|
||||
|
||||
func decodePublisher(publisher string, metadata interface{}) {
|
||||
switch meta := metadata.(type) {
|
||||
case *pkg.ApkMetadata:
|
||||
case *pkg.ApkDBEntry:
|
||||
meta.Maintainer = publisher
|
||||
case *pkg.RpmMetadata:
|
||||
case *pkg.RpmDBEntry:
|
||||
meta.Vendor = publisher
|
||||
case *pkg.DpkgMetadata:
|
||||
case *pkg.DpkgDBEntry:
|
||||
meta.Maintainer = publisher
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func Test_encodePublisher(t *testing.T) {
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
@ -32,7 +32,7 @@ func Test_encodePublisher(t *testing.T) {
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RpmMetadata{
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Vendor: "auth",
|
||||
},
|
||||
},
|
||||
@ -41,7 +41,7 @@ func Test_encodePublisher(t *testing.T) {
|
||||
{
|
||||
name: "from dpkg",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
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
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "",
|
||||
},
|
||||
},
|
||||
|
||||
@ -5,9 +5,9 @@ import "github.com/anchore/syft/syft/pkg"
|
||||
func Description(p pkg.Package) string {
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
case pkg.ApkDBEntry:
|
||||
return metadata.Description
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
case pkg.NpmPackage:
|
||||
return metadata.Description
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func Test_Description(t *testing.T) {
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Description: "a description!",
|
||||
},
|
||||
},
|
||||
@ -32,7 +32,7 @@ func Test_Description(t *testing.T) {
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
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
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
|
||||
@ -16,11 +16,11 @@ func DownloadLocation(p pkg.Package) string {
|
||||
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
case pkg.ApkDBEntry:
|
||||
return NoneIfEmpty(metadata.URL)
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
case pkg.NpmPackage:
|
||||
return NoneIfEmpty(metadata.URL)
|
||||
case pkg.NpmPackageLockJSONMetadata:
|
||||
case pkg.NpmPackageLockEntry:
|
||||
return NoneIfEmpty(metadata.Resolved)
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
@ -31,7 +31,7 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
URL: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
@ -40,7 +40,7 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
{
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
URL: "",
|
||||
},
|
||||
},
|
||||
@ -49,7 +49,7 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
{
|
||||
name: "from npm package-lock should include resolved",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{
|
||||
Metadata: pkg.NpmPackageLockEntry{
|
||||
Resolved: "http://package-lock.test",
|
||||
},
|
||||
},
|
||||
@ -58,7 +58,7 @@ func Test_DownloadLocation(t *testing.T) {
|
||||
{
|
||||
name: "from npm package-lock empty should be NONE",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{
|
||||
Metadata: pkg.NpmPackageLockEntry{
|
||||
Resolved: "",
|
||||
},
|
||||
},
|
||||
|
||||
@ -5,9 +5,9 @@ import "github.com/anchore/syft/syft/pkg"
|
||||
func Homepage(p pkg.Package) string {
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.GemMetadata:
|
||||
case pkg.RubyGemspec:
|
||||
return metadata.Homepage
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
case pkg.NpmPackage:
|
||||
return metadata.Homepage
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func Test_Homepage(t *testing.T) {
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Metadata: pkg.RubyGemspec{
|
||||
Homepage: "http://a-place.gov",
|
||||
},
|
||||
},
|
||||
@ -32,7 +32,7 @@ func Test_Homepage(t *testing.T) {
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
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
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Homepage: "",
|
||||
},
|
||||
},
|
||||
|
||||
@ -23,7 +23,7 @@ func Test_Originator(t *testing.T) {
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.GemMetadata{
|
||||
Metadata: pkg.RubyGemspec{
|
||||
Authors: []string{
|
||||
"auth1",
|
||||
"auth2",
|
||||
@ -35,7 +35,7 @@ func Test_Originator(t *testing.T) {
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
@ -44,7 +44,7 @@ func Test_Originator(t *testing.T) {
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
@ -53,7 +53,7 @@ func Test_Originator(t *testing.T) {
|
||||
{
|
||||
name: "from python - just name",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
@ -62,7 +62,7 @@ func Test_Originator(t *testing.T) {
|
||||
{
|
||||
name: "from python - just email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
@ -71,7 +71,7 @@ func Test_Originator(t *testing.T) {
|
||||
{
|
||||
name: "from python - both name and email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "auth",
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
@ -81,7 +81,7 @@ func Test_Originator(t *testing.T) {
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RpmMetadata{
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Vendor: "auth",
|
||||
},
|
||||
},
|
||||
@ -90,7 +90,7 @@ func Test_Originator(t *testing.T) {
|
||||
{
|
||||
name: "from dpkg",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
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
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "",
|
||||
},
|
||||
},
|
||||
|
||||
@ -15,25 +15,25 @@ func Originator(p pkg.Package) (string, string) {
|
||||
author := ""
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkMetadata:
|
||||
case pkg.ApkDBEntry:
|
||||
author = metadata.Maintainer
|
||||
case pkg.NpmPackageJSONMetadata:
|
||||
case pkg.NpmPackage:
|
||||
author = metadata.Author
|
||||
case pkg.PythonPackageMetadata:
|
||||
case pkg.PythonPackage:
|
||||
author = metadata.Author
|
||||
if author == "" {
|
||||
author = metadata.AuthorEmail
|
||||
} else if metadata.AuthorEmail != "" {
|
||||
author = fmt.Sprintf("%s (%s)", author, metadata.AuthorEmail)
|
||||
}
|
||||
case pkg.GemMetadata:
|
||||
case pkg.RubyGemspec:
|
||||
if len(metadata.Authors) > 0 {
|
||||
author = metadata.Authors[0]
|
||||
}
|
||||
case pkg.RpmMetadata:
|
||||
case pkg.RpmDBEntry:
|
||||
typ = "Organization"
|
||||
author = metadata.Vendor
|
||||
case pkg.DpkgMetadata:
|
||||
case pkg.DpkgDBEntry:
|
||||
author = metadata.Maintainer
|
||||
}
|
||||
if typ == "" && author != "" {
|
||||
|
||||
@ -477,7 +477,7 @@ func toPackageChecksums(p pkg.Package) ([]spdx.Checksum, bool) {
|
||||
switch meta := p.Metadata.(type) {
|
||||
// we generate digest for some Java packages
|
||||
// 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 len(meta.ArchiveDigests) > 0 {
|
||||
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
|
||||
algo, hexStr, err := util.HDigestToSHA(meta.H1Digest)
|
||||
if err != nil {
|
||||
|
||||
@ -275,7 +275,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
Name: "test",
|
||||
Version: "1.0.0",
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
ArchiveDigests: []file.Digest{
|
||||
{
|
||||
Algorithm: "sha1", // SPDX expects these to be uppercase
|
||||
@ -298,7 +298,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
Name: "test",
|
||||
Version: "1.0.0",
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
ArchiveDigests: []file.Digest{},
|
||||
},
|
||||
},
|
||||
@ -318,11 +318,10 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
{
|
||||
name: "Go Binary Package",
|
||||
pkg: pkg.Package{
|
||||
Name: "test",
|
||||
Version: "1.0.0",
|
||||
Language: pkg.Go,
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
Name: "test",
|
||||
Version: "1.0.0",
|
||||
Language: pkg.Go,
|
||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||
H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
|
||||
},
|
||||
},
|
||||
@ -630,10 +629,9 @@ func Test_H1Digest(t *testing.T) {
|
||||
{
|
||||
name: "valid h1digest",
|
||||
pkg: pkg.Package{
|
||||
Name: "github.com/googleapis/gnostic",
|
||||
Version: "v0.5.5",
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
Name: "github.com/googleapis/gnostic",
|
||||
Version: "v0.5.5",
|
||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||
H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
|
||||
},
|
||||
},
|
||||
@ -642,10 +640,9 @@ func Test_H1Digest(t *testing.T) {
|
||||
{
|
||||
name: "invalid h1digest",
|
||||
pkg: pkg.Package{
|
||||
Name: "github.com/googleapis/gnostic",
|
||||
Version: "v0.5.5",
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
Name: "github.com/googleapis/gnostic",
|
||||
Version: "v0.5.5",
|
||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||
H1Digest: "h1:9fHAtK0uzzz",
|
||||
},
|
||||
},
|
||||
@ -654,10 +651,9 @@ func Test_H1Digest(t *testing.T) {
|
||||
{
|
||||
name: "unsupported h-digest",
|
||||
pkg: pkg.Package{
|
||||
Name: "github.com/googleapis/gnostic",
|
||||
Version: "v0.5.5",
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
Name: "github.com/googleapis/gnostic",
|
||||
Version: "v0.5.5",
|
||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||
H1Digest: "h12:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
|
||||
},
|
||||
},
|
||||
|
||||
@ -490,17 +490,15 @@ func extractPkgInfo(p *spdx.Package) pkgInfo {
|
||||
|
||||
func toSyftPackage(p *spdx.Package) pkg.Package {
|
||||
info := extractPkgInfo(p)
|
||||
metadataType, metadata := extractMetadata(p, info)
|
||||
sP := &pkg.Package{
|
||||
Type: info.typ,
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Licenses: pkg.NewLicenseSet(parseSPDXLicenses(p)...),
|
||||
CPEs: extractCPEs(p),
|
||||
PURL: purlValue(info.purl),
|
||||
Language: info.lang,
|
||||
MetadataType: metadataType,
|
||||
Metadata: metadata,
|
||||
Type: info.typ,
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Licenses: pkg.NewLicenseSet(parseSPDXLicenses(p)...),
|
||||
CPEs: extractCPEs(p),
|
||||
PURL: purlValue(info.purl),
|
||||
Language: info.lang,
|
||||
Metadata: extractMetadata(p, info),
|
||||
}
|
||||
|
||||
sP.SetID()
|
||||
@ -541,7 +539,7 @@ func cleanSPDXID(id string) string {
|
||||
}
|
||||
|
||||
//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)
|
||||
upstreamValue := info.qualifierValue(pkg.PURLQualifierUpstream)
|
||||
upstream := strings.SplitN(upstreamValue, "@", 2)
|
||||
@ -560,7 +558,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
||||
}
|
||||
switch info.typ {
|
||||
case pkg.ApkPkg:
|
||||
return pkg.ApkMetadataType, pkg.ApkMetadata{
|
||||
return pkg.ApkDBEntry{
|
||||
Package: p.PackageName,
|
||||
OriginPackage: upstreamName,
|
||||
Maintainer: supplier,
|
||||
@ -577,7 +575,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
||||
} else {
|
||||
epoch = &converted
|
||||
}
|
||||
return pkg.RpmMetadataType, pkg.RpmMetadata{
|
||||
return pkg.RpmDBEntry{
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Epoch: epoch,
|
||||
@ -586,7 +584,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
||||
Vendor: originator,
|
||||
}
|
||||
case pkg.DebPkg:
|
||||
return pkg.DpkgMetadataType, pkg.DpkgMetadata{
|
||||
return pkg.DpkgDBEntry{
|
||||
Package: p.PackageName,
|
||||
Source: upstreamName,
|
||||
Version: p.PackageVersion,
|
||||
@ -599,7 +597,7 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
||||
for _, value := range p.PackageChecksums {
|
||||
digests = append(digests, file.Digest{Algorithm: fromChecksumAlgorithm(value.Algorithm), Value: value.Value})
|
||||
}
|
||||
return pkg.JavaMetadataType, pkg.JavaMetadata{
|
||||
return pkg.JavaArchive{
|
||||
ArchiveDigests: digests,
|
||||
}
|
||||
case pkg.GoModulePkg:
|
||||
@ -613,11 +611,11 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
||||
h1Digest = digest
|
||||
break
|
||||
}
|
||||
return pkg.GolangBinMetadataType, pkg.GolangBinMetadata{
|
||||
return pkg.GolangBinaryBuildinfoEntry{
|
||||
H1Digest: h1Digest,
|
||||
}
|
||||
}
|
||||
return pkg.UnknownMetadataType, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPURLValue(p *spdx.Package) string {
|
||||
|
||||
@ -103,15 +103,13 @@ func TestToSyftModel(t *testing.T) {
|
||||
|
||||
p1 := pkgs[0]
|
||||
assert.Equal(t, p1.Name, "pkg-1")
|
||||
assert.Equal(t, p1.MetadataType, pkg.ApkMetadataType)
|
||||
p1meta := p1.Metadata.(pkg.ApkMetadata)
|
||||
p1meta := p1.Metadata.(pkg.ApkDBEntry)
|
||||
assert.Equal(t, p1meta.OriginPackage, "p1-origin")
|
||||
assert.Len(t, p1.CPEs, 2)
|
||||
|
||||
p2 := pkgs[1]
|
||||
assert.Equal(t, p2.Name, "pkg-2")
|
||||
assert.Equal(t, p2.MetadataType, pkg.DpkgMetadataType)
|
||||
p2meta := p2.Metadata.(pkg.DpkgMetadata)
|
||||
p2meta := p2.Metadata.(pkg.DpkgDBEntry)
|
||||
assert.Equal(t, p2meta.Source, "p2-origin")
|
||||
assert.Equal(t, p2meta.SourceVersion, "9.1.3")
|
||||
assert.Len(t, p2.CPEs, 3)
|
||||
@ -120,9 +118,8 @@ func TestToSyftModel(t *testing.T) {
|
||||
func Test_extractMetadata(t *testing.T) {
|
||||
oneTwoThreeFour := 1234
|
||||
tests := []struct {
|
||||
pkg spdx.Package
|
||||
metaType pkg.MetadataType
|
||||
meta interface{}
|
||||
pkg spdx.Package
|
||||
meta interface{}
|
||||
}{
|
||||
{
|
||||
pkg: spdx.Package{
|
||||
@ -136,8 +133,7 @@ func Test_extractMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
metaType: pkg.DpkgMetadataType,
|
||||
meta: pkg.DpkgMetadata{
|
||||
meta: pkg.DpkgDBEntry{
|
||||
Package: "SomeDebPkg",
|
||||
Source: "somedebpkg-origin",
|
||||
Version: "43.1.235",
|
||||
@ -157,8 +153,7 @@ func Test_extractMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
metaType: pkg.ApkMetadataType,
|
||||
meta: pkg.ApkMetadata{
|
||||
meta: pkg.ApkDBEntry{
|
||||
Package: "SomeApkPkg",
|
||||
OriginPackage: "apk-origin",
|
||||
Version: "3.2.9",
|
||||
@ -177,8 +172,7 @@ func Test_extractMetadata(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
metaType: pkg.RpmMetadataType,
|
||||
meta: pkg.RpmMetadata{
|
||||
meta: pkg.RpmDBEntry{
|
||||
Name: "SomeRpmPkg",
|
||||
Version: "13.2.79",
|
||||
Epoch: &oneTwoThreeFour,
|
||||
@ -192,8 +186,7 @@ func Test_extractMetadata(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.pkg.PackageName, func(t *testing.T) {
|
||||
info := extractPkgInfo(&test.pkg)
|
||||
metaType, meta := extractMetadata(&test.pkg, info)
|
||||
assert.Equal(t, test.metaType, metaType)
|
||||
meta := extractMetadata(&test.pkg, info)
|
||||
assert.EqualValues(t, test.meta, meta)
|
||||
})
|
||||
}
|
||||
@ -319,8 +312,7 @@ func TestH1Digest(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
p := toSyftPackage(&test.pkg)
|
||||
require.Equal(t, pkg.GolangBinMetadataType, p.MetadataType)
|
||||
meta := p.Metadata.(pkg.GolangBinMetadata)
|
||||
meta := p.Metadata.(pkg.GolangBinaryBuildinfoEntry)
|
||||
require.Equal(t, test.expectedDigest, meta.H1Digest)
|
||||
})
|
||||
}
|
||||
@ -439,12 +431,10 @@ func Test_toSyftRelationships(t *testing.T) {
|
||||
func Test_convertToAndFromFormat(t *testing.T) {
|
||||
packages := []pkg.Package{
|
||||
{
|
||||
Name: "pkg1",
|
||||
MetadataType: pkg.UnknownMetadataType,
|
||||
Name: "pkg1",
|
||||
},
|
||||
{
|
||||
Name: "pkg2",
|
||||
MetadataType: pkg.UnknownMetadataType,
|
||||
Name: "pkg2",
|
||||
},
|
||||
}
|
||||
|
||||
@ -545,7 +535,6 @@ func Test_convertToAndFromFormat(t *testing.T) {
|
||||
cmpopts.IgnoreUnexported(pkg.Collection{}),
|
||||
cmpopts.IgnoreUnexported(pkg.Package{}),
|
||||
cmpopts.IgnoreUnexported(pkg.LicenseSet{}),
|
||||
cmpopts.IgnoreFields(pkg.Package{}, "MetadataType"),
|
||||
cmpopts.IgnoreFields(sbom.Artifacts{}, "FileMetadata", "FileDigests"),
|
||||
); diff != "" {
|
||||
t.Fatalf("packages do not match:\n%s", diff)
|
||||
@ -637,9 +626,8 @@ func Test_directPackageFiles(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
p := pkg.Package{
|
||||
Name: "some-package",
|
||||
Version: "1.0.5",
|
||||
MetadataType: pkg.UnknownMetadataType,
|
||||
Name: "some-package",
|
||||
Version: "1.0.5",
|
||||
}
|
||||
p.SetID()
|
||||
f := file.Location{
|
||||
|
||||
@ -43,14 +43,14 @@
|
||||
"name": "syft:package:language",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "PythonPackageMetadata"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:type",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "python-package"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
"value": "/some/path/pkg1"
|
||||
@ -69,14 +69,14 @@
|
||||
"name": "syft:package:foundBy",
|
||||
"value": "the-cataloger-2"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "DpkgMetadata"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:type",
|
||||
"value": "deb"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "dpkg-db-entry"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
"value": "/some/path/pkg1"
|
||||
|
||||
@ -44,14 +44,14 @@
|
||||
"name": "syft:package:language",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "PythonPackageMetadata"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:type",
|
||||
"value": "python"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "python-package"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:redacted"
|
||||
@ -74,14 +74,14 @@
|
||||
"name": "syft:package:foundBy",
|
||||
"value": "the-cataloger-2"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "DpkgMetadata"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:type",
|
||||
"value": "deb"
|
||||
},
|
||||
{
|
||||
"name": "syft:package:metadataType",
|
||||
"value": "dpkg-db-entry"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:redacted"
|
||||
|
||||
@ -27,20 +27,20 @@
|
||||
<properties>
|
||||
<property name="syft:package:foundBy">the-cataloger-1</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:metadataType">python-package</property>
|
||||
<property name="syft:location:0:path">/some/path/pkg1</property>
|
||||
</properties>
|
||||
</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>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
<purl>pkg:deb/debian/package-2@2.0.1</purl>
|
||||
<properties>
|
||||
<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:metadataType">dpkg-db-entry</property>
|
||||
<property name="syft:location:0:path">/some/path/pkg1</property>
|
||||
<property name="syft:metadata:installedSize">0</property>
|
||||
</properties>
|
||||
|
||||
@ -28,21 +28,21 @@
|
||||
<properties>
|
||||
<property name="syft:package:foundBy">the-cataloger-1</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:metadataType">python-package</property>
|
||||
<property name="syft:location:0:layerID">sha256:redacted</property>
|
||||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</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>
|
||||
<version>2.0.1</version>
|
||||
<cpe>cpe:2.3:*:some:package:2:*:*:*:*:*:*:*</cpe>
|
||||
<purl>pkg:deb/debian/package-2@2.0.1</purl>
|
||||
<properties>
|
||||
<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:metadataType">dpkg-db-entry</property>
|
||||
<property name="syft:location:0:layerID">sha256:redacted</property>
|
||||
<property name="syft:location:0:path">/somefile-2.txt</property>
|
||||
<property name="syft:metadata:installedSize">0</property>
|
||||
|
||||
@ -108,12 +108,11 @@ func newDirectoryCatalog() *pkg.Collection {
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/some/path/pkg1"),
|
||||
),
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Files: []pkg.PythonFileRecord{
|
||||
@ -135,8 +134,7 @@ func newDirectoryCatalog() *pkg.Collection {
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/some/path/pkg1"),
|
||||
),
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
@ -161,12 +159,11 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/some/path/pkg1"),
|
||||
),
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Author: "test-author",
|
||||
@ -189,8 +186,7 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocation("/some/path/pkg1"),
|
||||
),
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
|
||||
@ -95,48 +95,52 @@ func changeToDirectoryWithGoldenFixture(t testing.TB, testImage string) func() {
|
||||
}
|
||||
|
||||
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)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocationFromImage(string(ref1.RealPath), *ref1.Reference, img),
|
||||
),
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
if ref1 != nil {
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-1", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocationFromImage(string(ref2.RealPath), *ref2.Reference, img),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "package-2",
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocationFromImage(string(ref1.RealPath), *ref1.Reference, img),
|
||||
),
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackage{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-1", // intentionally a bad pURL for test fixtures
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if ref2 != nil {
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewLocationFromImage(string(ref2.RealPath), *ref2.Reference, img),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "pkg:deb/debian/package-2@2.0.1",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-9265397e5e15168a",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-fb6bef15e281ea43",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-ad5013466727018f",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-39392bb5e270f669",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -73,12 +73,12 @@
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-9265397e5e15168a",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-fb6bef15e281ea43",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-ad5013466727018f",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-39392bb5e270f669",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -87,12 +87,12 @@
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
@ -178,43 +178,43 @@
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-Package-python-package-1-80210ebcba92e632",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-f27313b22a5ba330",
|
||||
"relatedSpdxElement": "SPDXRef-Package-deb-package-2-4b756c6f6fb127a3",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
|
||||
@ -20,7 +20,7 @@ FilesAnalyzed: false
|
||||
##### Package: @at-sign
|
||||
|
||||
PackageName: @at-sign
|
||||
SPDXID: SPDXRef-Package--at-sign-3732f7a5679bdec4
|
||||
SPDXID: SPDXRef-Package--at-sign-1c8c811ea5b1cd46
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
@ -32,7 +32,7 @@ PackageCopyrightText: NOASSERTION
|
||||
##### Package: some/slashes
|
||||
|
||||
PackageName: some/slashes
|
||||
SPDXID: SPDXRef-Package-some-slashes-1345166d4801153b
|
||||
SPDXID: SPDXRef-Package-some-slashes-8a8e95924316c66b
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
@ -44,7 +44,7 @@ PackageCopyrightText: NOASSERTION
|
||||
##### Package: under_scores
|
||||
|
||||
PackageName: under_scores
|
||||
SPDXID: SPDXRef-Package-under-scores-290d5c77210978c1
|
||||
SPDXID: SPDXRef-Package-under-scores-883703d950ec00f3
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
@ -55,8 +55,8 @@ PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package--at-sign-3732f7a5679bdec4
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-some-slashes-1345166d4801153b
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-under-scores-290d5c77210978c1
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package--at-sign-1c8c811ea5b1cd46
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-some-slashes-8a8e95924316c66b
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-foobar-baz CONTAINS SPDXRef-Package-under-scores-883703d950ec00f3
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-foobar-baz
|
||||
|
||||
|
||||
@ -61,7 +61,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
SPDXID: SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@ -76,7 +76,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
SPDXID: SPDXRef-Package-python-package-1-80210ebcba92e632
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@ -90,13 +90,13 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
||||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 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-125840abc1c66dd7 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-125840abc1c66dd7 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-deb-package-2-f27313b22a5ba330
|
||||
Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
|
||||
Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
|
||||
Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
|
||||
Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
||||
Relationship: SPDXRef-Package-python-package-1-80210ebcba92e632 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
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-80210ebcba92e632
|
||||
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ FilesAnalyzed: false
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-ad5013466727018f
|
||||
SPDXID: SPDXRef-Package-deb-package-2-39392bb5e270f669
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@ -35,7 +35,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-9265397e5e15168a
|
||||
SPDXID: SPDXRef-Package-python-package-1-fb6bef15e281ea43
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@ -49,7 +49,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-2
|
||||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-9265397e5e15168a
|
||||
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-ad5013466727018f
|
||||
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-39392bb5e270f669
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
||||
##### Package: package-2
|
||||
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-f27313b22a5ba330
|
||||
SPDXID: SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@ -38,7 +38,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
SPDXID: SPDXRef-Package-python-package-1-80210ebcba92e632
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
@ -52,7 +52,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
||||
|
||||
##### 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-deb-package-2-f27313b22a5ba330
|
||||
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-4b756c6f6fb127a3
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input
|
||||
|
||||
|
||||
@ -18,18 +18,7 @@ var _ sbom.FormatDecoder = (*decoder)(nil)
|
||||
|
||||
type decoder struct{}
|
||||
|
||||
type DecoderConfig struct {
|
||||
}
|
||||
|
||||
func DefaultDecoderConfig() DecoderConfig {
|
||||
return DecoderConfig{}
|
||||
}
|
||||
|
||||
func NewFormatDecoder() sbom.FormatDecoder {
|
||||
return NewFormatDecoderWithConfig(DefaultDecoderConfig())
|
||||
}
|
||||
|
||||
func NewFormatDecoderWithConfig(DecoderConfig) sbom.FormatDecoder {
|
||||
return decoder{}
|
||||
}
|
||||
|
||||
|
||||
@ -23,46 +23,75 @@ import (
|
||||
)
|
||||
|
||||
func Test_EncodeDecodeCycle(t *testing.T) {
|
||||
testImage := "image-simple"
|
||||
originalSBOM := testutil.ImageInput(t, testImage)
|
||||
|
||||
enc := NewFormatEncoder()
|
||||
dec := NewFormatDecoder()
|
||||
|
||||
var buf bytes.Buffer
|
||||
assert.NoError(t, enc.Encode(&buf, originalSBOM))
|
||||
|
||||
actualSBOM, decodedID, decodedVersion, err := dec.Decode(bytes.NewReader(buf.Bytes()))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ID, decodedID)
|
||||
assert.Equal(t, internal.JSONSchemaVersion, decodedVersion)
|
||||
|
||||
for _, d := range deep.Equal(originalSBOM.Source, actualSBOM.Source) {
|
||||
if strings.HasSuffix(d, "<nil slice> != []") {
|
||||
// semantically the same
|
||||
continue
|
||||
}
|
||||
t.Errorf("metadata difference: %+v", d)
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualPackages := actualSBOM.Artifacts.Packages.Sorted()
|
||||
for idx, p := range originalSBOM.Artifacts.Packages.Sorted() {
|
||||
if !assert.Equal(t, p.Name, actualPackages[idx].Name) {
|
||||
t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name)
|
||||
continue
|
||||
}
|
||||
for _, tt := range table {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
originalSBOM := testutil.ImageInput(t, tt.fixtureImage)
|
||||
|
||||
for _, d := range deep.Equal(p, actualPackages[idx]) {
|
||||
if strings.Contains(d, ".VirtualPath: ") {
|
||||
// location.Virtual path is not exposed in the json output
|
||||
continue
|
||||
enc, err := NewFormatEncoderWithConfig(tt.cfg)
|
||||
require.NoError(t, err)
|
||||
dec := NewFormatDecoder()
|
||||
|
||||
var buf bytes.Buffer
|
||||
assert.NoError(t, enc.Encode(&buf, originalSBOM))
|
||||
|
||||
actualSBOM, decodedID, decodedVersion, err := dec.Decode(bytes.NewReader(buf.Bytes()))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ID, decodedID)
|
||||
assert.Equal(t, internal.JSONSchemaVersion, decodedVersion)
|
||||
|
||||
for _, d := range deep.Equal(originalSBOM.Source, actualSBOM.Source) {
|
||||
if strings.HasSuffix(d, "<nil slice> != []") {
|
||||
// semantically the same
|
||||
continue
|
||||
}
|
||||
t.Errorf("metadata difference: %+v", d)
|
||||
}
|
||||
if strings.HasSuffix(d, "<nil slice> != []") {
|
||||
// semantically the same
|
||||
continue
|
||||
|
||||
actualPackages := actualSBOM.Artifacts.Packages.Sorted()
|
||||
for idx, p := range originalSBOM.Artifacts.Packages.Sorted() {
|
||||
if !assert.Equal(t, p.Name, actualPackages[idx].Name) {
|
||||
t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, d := range deep.Equal(p, actualPackages[idx]) {
|
||||
if strings.Contains(d, ".VirtualPath: ") {
|
||||
// location.Virtual path is not exposed in the json output
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(d, "<nil slice> != []") {
|
||||
// semantically the same
|
||||
continue
|
||||
}
|
||||
t.Errorf("%q package difference (%s): %+v", tt.fixtureImage, p.Name, d)
|
||||
}
|
||||
}
|
||||
t.Errorf("package difference (%s): %+v", p.Name, d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,9 +166,8 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
|
||||
Update: "update",
|
||||
},
|
||||
},
|
||||
PURL: "pkg:generic/pkg@version",
|
||||
MetadataType: "",
|
||||
Metadata: nil,
|
||||
PURL: "pkg:generic/pkg@version",
|
||||
Metadata: nil,
|
||||
}
|
||||
p.SetID()
|
||||
|
||||
|
||||
@ -12,11 +12,32 @@ var _ sbom.FormatEncoder = (*encoder)(nil)
|
||||
|
||||
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 {
|
||||
cfg EncoderConfig
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -35,7 +56,7 @@ func (e encoder) Version() string {
|
||||
}
|
||||
|
||||
func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
|
||||
doc := ToFormatModel(s)
|
||||
doc := ToFormatModel(s, e.cfg)
|
||||
|
||||
enc := json.NewEncoder(writer)
|
||||
// prevent > and < from being escaped in the payload
|
||||
|
||||
@ -74,12 +74,11 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
RealPath: "/a/place/a",
|
||||
}),
|
||||
),
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
|
||||
Metadata: pkg.PythonPackage{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Files: []pkg.PythonFileRecord{},
|
||||
@ -98,10 +97,9 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
RealPath: "/b/place/b",
|
||||
}),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Package: "package-2",
|
||||
Version: "2.0.1",
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
|
||||
@ -5,9 +5,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/packagemetadata"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
@ -60,28 +62,28 @@ func newModelLicensesFromValues(licenses []string) (ml []License) {
|
||||
}
|
||||
|
||||
func (f *licenses) UnmarshalJSON(b []byte) error {
|
||||
var licenses []License
|
||||
if err := json.Unmarshal(b, &licenses); err != nil {
|
||||
var lics []License
|
||||
if err := json.Unmarshal(b, &lics); err != nil {
|
||||
var simpleLicense []string
|
||||
if err := json.Unmarshal(b, &simpleLicense); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal license: %w", err)
|
||||
}
|
||||
licenses = newModelLicensesFromValues(simpleLicense)
|
||||
lics = newModelLicensesFromValues(simpleLicense)
|
||||
}
|
||||
*f = licenses
|
||||
*f = lics
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageCustomData struct {
|
||||
MetadataType pkg.MetadataType `json:"metadataType,omitempty"`
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
MetadataType string `json:"metadataType,omitempty"`
|
||||
Metadata any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling.
|
||||
type packageMetadataUnpacker struct {
|
||||
MetadataType pkg.MetadataType `json:"metadataType"`
|
||||
Metadata json.RawMessage `json:"metadata"`
|
||||
MetadataType string `json:"metadataType"`
|
||||
Metadata json.RawMessage `json:"metadata"`
|
||||
}
|
||||
|
||||
func (p *packageMetadataUnpacker) String() string {
|
||||
@ -112,32 +114,64 @@ func (p *Package) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error {
|
||||
p.MetadataType = pkg.CleanMetadataType(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()
|
||||
if unpacker.MetadataType == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// capture unknown metadata as a generic struct
|
||||
if len(unpacker.Metadata) > 0 {
|
||||
var val interface{}
|
||||
if err := json.Unmarshal(unpacker.Metadata, &val); err != nil {
|
||||
return err
|
||||
// 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"
|
||||
}
|
||||
p.Metadata = val
|
||||
}
|
||||
|
||||
if p.MetadataType != "" {
|
||||
typ := packagemetadata.ReflectTypeFromJSONName(ty)
|
||||
if typ == nil {
|
||||
// capture unknown metadata as a generic struct
|
||||
if len(unpacker.Metadata) > 0 {
|
||||
var val interface{}
|
||||
if err := json.Unmarshal(unpacker.Metadata, &val); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Metadata = val
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestUnmarshalPackageGolang(t *testing.T) {
|
||||
func Test_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
packageData []byte
|
||||
@ -50,9 +50,9 @@ func TestUnmarshalPackageGolang(t *testing.T) {
|
||||
}
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
assert.NotNil(t, p.Metadata)
|
||||
golangMetadata := p.Metadata.(pkg.GolangBinMetadata)
|
||||
assert.NotEmpty(t, golangMetadata)
|
||||
require.NotNil(t, p.Metadata)
|
||||
golangMetadata := p.Metadata.(pkg.GolangBinaryBuildinfoEntry)
|
||||
require.NotEmpty(t, golangMetadata)
|
||||
assert.Equal(t, "go1.18", golangMetadata.GoCompiledVersion)
|
||||
},
|
||||
},
|
||||
@ -169,6 +169,223 @@ func TestUnmarshalPackageGolang(t *testing.T) {
|
||||
}, 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 {
|
||||
@ -185,13 +402,12 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
packageData []byte
|
||||
metadataType pkg.MetadataType
|
||||
wantMetadata interface{}
|
||||
wantMetadata any
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "unmarshal package metadata",
|
||||
metadataType: pkg.GolangBinMetadataType,
|
||||
wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
|
||||
packageData: []byte(`{
|
||||
"id": "8b594519bc23da50",
|
||||
"name": "gopkg.in/square/go-jose.v2",
|
||||
@ -217,7 +433,7 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "can handle package without metadata",
|
||||
metadataType: "",
|
||||
wantMetadata: nil,
|
||||
packageData: []byte(`{
|
||||
"id": "8b594519bc23da50",
|
||||
"name": "gopkg.in/square/go-jose.v2",
|
||||
@ -237,7 +453,7 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "can handle RpmdbMetadata",
|
||||
metadataType: pkg.RpmMetadataType,
|
||||
wantMetadata: pkg.RpmDBEntry{},
|
||||
packageData: []byte(`{
|
||||
"id": "4ac699c3b8fe1835",
|
||||
"name": "acl",
|
||||
@ -272,9 +488,8 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
}`),
|
||||
},
|
||||
{
|
||||
name: "bad metadata type is an error",
|
||||
metadataType: "BOGOSITY",
|
||||
wantErr: require.Error,
|
||||
name: "bad metadata type is an error",
|
||||
wantErr: require.Error,
|
||||
packageData: []byte(`{
|
||||
"id": "8b594519bc23da50",
|
||||
"name": "gopkg.in/square/go-jose.v2",
|
||||
@ -301,8 +516,7 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
"thing": "thing-1"
|
||||
}
|
||||
}`),
|
||||
wantErr: require.Error,
|
||||
metadataType: "NewMetadataType",
|
||||
wantErr: require.Error,
|
||||
wantMetadata: map[string]interface{}{
|
||||
"thing": "thing-1",
|
||||
},
|
||||
@ -312,39 +526,31 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
packageData: []byte(`{
|
||||
"metadataType": "GolangBinMetadata"
|
||||
}`),
|
||||
metadataType: pkg.GolangBinMetadataType,
|
||||
wantMetadata: pkg.GolangBinMetadata{},
|
||||
wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
|
||||
},
|
||||
{
|
||||
name: "can handle package with golang bin metadata type",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "GolangBinMetadata"
|
||||
}`),
|
||||
metadataType: pkg.GolangBinMetadataType,
|
||||
wantMetadata: pkg.GolangBinMetadata{},
|
||||
wantMetadata: pkg.GolangBinaryBuildinfoEntry{},
|
||||
},
|
||||
{
|
||||
name: "can handle package with unknonwn metadata type and missing metadata",
|
||||
name: "can handle package with unknown metadata type and missing metadata",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "BadMetadata"
|
||||
}`),
|
||||
wantErr: require.Error,
|
||||
metadataType: "BadMetadata",
|
||||
wantMetadata: nil,
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "can handle package with unknonwn metadata type and metadata",
|
||||
name: "can handle package with unknown metadata type and metadata",
|
||||
packageData: []byte(`{
|
||||
"metadataType": "BadMetadata",
|
||||
"metadata": {
|
||||
"random": "thing"
|
||||
}
|
||||
}`),
|
||||
wantErr: require.Error,
|
||||
metadataType: "BadMetadata",
|
||||
wantMetadata: map[string]interface{}{
|
||||
"random": "thing",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
@ -355,19 +561,18 @@ func Test_unpackMetadata(t *testing.T) {
|
||||
}
|
||||
p := &Package{}
|
||||
|
||||
var basic PackageBasicData
|
||||
require.NoError(t, json.Unmarshal(test.packageData, &basic))
|
||||
p.PackageBasicData = basic
|
||||
|
||||
var unpacker packageMetadataUnpacker
|
||||
require.NoError(t, json.Unmarshal(test.packageData, &unpacker))
|
||||
|
||||
err := unpackPkgMetadata(p, unpacker)
|
||||
assert.Equal(t, test.metadataType, p.MetadataType)
|
||||
test.wantErr(t, err)
|
||||
|
||||
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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
FROM alpine:3.18.4@sha256:48d9183eb12a05c99bcc0bf44a003607b8e941e1d4f41f9ad12bdcc4b5672f86
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "9265397e5e15168a",
|
||||
"id": "fb6bef15e281ea43",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@ -25,7 +25,7 @@
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"metadataType": "PythonPackageMetadata",
|
||||
"metadataType": "python-package",
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@ -41,7 +41,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ad5013466727018f",
|
||||
"id": "39392bb5e270f669",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
@ -57,7 +57,7 @@
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:deb/debian/package-2@2.0.1",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadataType": "dpkg-db-entry",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
"source": "",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "271e49ba46e0b601",
|
||||
"id": "d748d4614750058d",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@ -25,7 +25,7 @@
|
||||
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-1",
|
||||
"metadataType": "PythonPackageMetadata",
|
||||
"metadataType": "python-package",
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@ -36,7 +36,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "aa0ca2c331576dfd",
|
||||
"id": "fa4ec37eccd65756",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
@ -52,7 +52,7 @@
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-2",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadataType": "dpkg-db-entry",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
"source": "",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "125840abc1c66dd7",
|
||||
"id": "80210ebcba92e632",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
@ -26,7 +26,7 @@
|
||||
"cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "a-purl-1",
|
||||
"metadataType": "PythonPackageMetadata",
|
||||
"metadataType": "python-package",
|
||||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
@ -37,7 +37,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "f27313b22a5ba330",
|
||||
"id": "4b756c6f6fb127a3",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
"type": "deb",
|
||||
@ -54,7 +54,7 @@
|
||||
"cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:deb/debian/package-2@2.0.1",
|
||||
"metadataType": "DpkgMetadata",
|
||||
"metadataType": "dpkg-db-entry",
|
||||
"metadata": {
|
||||
"package": "package-2",
|
||||
"source": "",
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"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/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@ -20,9 +21,9 @@ import (
|
||||
)
|
||||
|
||||
// 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{
|
||||
Artifacts: toPackageModels(s.Artifacts.Packages),
|
||||
Artifacts: toPackageModels(s.Artifacts.Packages, cfg),
|
||||
ArtifactRelationships: toRelationshipModel(s.Relationships),
|
||||
Files: toFile(s),
|
||||
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)
|
||||
if catalog == nil {
|
||||
return artifacts
|
||||
}
|
||||
for _, p := range catalog.Sorted() {
|
||||
artifacts = append(artifacts, toPackageModel(p))
|
||||
artifacts = append(artifacts, toPackageModel(p, cfg))
|
||||
}
|
||||
return artifacts
|
||||
}
|
||||
@ -233,7 +234,7 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
|
||||
}
|
||||
|
||||
// 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))
|
||||
for i, c := range p.CPEs {
|
||||
cpes[i] = cpe.String(c)
|
||||
@ -246,6 +247,13 @@ func toPackageModel(p pkg.Package) model.Package {
|
||||
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{
|
||||
PackageBasicData: model.PackageBasicData{
|
||||
ID: string(p.ID()),
|
||||
@ -260,7 +268,7 @@ func toPackageModel(p pkg.Package) model.Package {
|
||||
PURL: p.PURL,
|
||||
},
|
||||
PackageCustomData: model.PackageCustomData{
|
||||
MetadataType: p.MetadataType,
|
||||
MetadataType: ty,
|
||||
Metadata: p.Metadata,
|
||||
},
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -11,6 +13,7 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format/syftjson/model"
|
||||
"github.com/anchore/syft/syft/internal/sourcemetadata"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -311,17 +311,16 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
|
||||
}
|
||||
|
||||
out := pkg.Package{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: file.NewLocationSet(p.Locations...),
|
||||
Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
|
||||
Language: p.Language,
|
||||
Type: p.Type,
|
||||
CPEs: cpes,
|
||||
PURL: p.PURL,
|
||||
MetadataType: p.MetadataType,
|
||||
Metadata: p.Metadata,
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: file.NewLocationSet(p.Locations...),
|
||||
Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
|
||||
Language: p.Language,
|
||||
Type: p.Type,
|
||||
CPEs: cpes,
|
||||
PURL: p.PURL,
|
||||
Metadata: p.Metadata,
|
||||
}
|
||||
|
||||
// we don't know if this package ID is truly unique, however, we need to trust the user input in case there are
|
||||
|
||||
@ -19,6 +19,7 @@ const ID sbom.FormatID = "template"
|
||||
|
||||
type EncoderConfig struct {
|
||||
TemplatePath string
|
||||
syftjson.EncoderConfig
|
||||
}
|
||||
|
||||
type encoder struct {
|
||||
@ -52,7 +53,9 @@ func NewFormatEncoder(cfg EncoderConfig) (sbom.FormatEncoder, error) {
|
||||
}
|
||||
|
||||
func DefaultEncoderConfig() EncoderConfig {
|
||||
return EncoderConfig{}
|
||||
return EncoderConfig{
|
||||
EncoderConfig: syftjson.DefaultEncoderConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
doc := syftjson.ToFormatModel(s)
|
||||
doc := syftjson.ToFormatModel(s, e.cfg.EncoderConfig)
|
||||
return tmpl.Execute(writer, doc)
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
"github.com/invopop/jsonschema"
|
||||
|
||||
"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))
|
||||
}
|
||||
|
||||
func assembleTypeContainer(items []any) any {
|
||||
func assembleTypeContainer(items []any) (any, map[string]string) {
|
||||
structFields := make([]reflect.StructField, len(items))
|
||||
|
||||
mapping := make(map[string]string, len(items))
|
||||
typesMissingNames := make([]reflect.Type, 0)
|
||||
for i, item := range items {
|
||||
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{
|
||||
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)
|
||||
return reflect.New(structType).Elem().Interface()
|
||||
return reflect.New(structType).Elem().Interface(), mapping
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// srcMetadataContainer := assembleTypeContainer(sourcemetadata.AllTypes())
|
||||
@ -73,17 +92,23 @@ func build() *jsonschema.Schema {
|
||||
|
||||
// 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
|
||||
for name, definition := range pkgMetadataSchema.Definitions {
|
||||
if name == pkgMetadataContainerType.Name() {
|
||||
for typeName, definition := range pkgMetadataSchema.Definitions {
|
||||
if typeName == pkgMetadataContainerType.Name() {
|
||||
// ignore the definition for the fake container
|
||||
continue
|
||||
}
|
||||
documentSchema.Definitions[name] = definition
|
||||
if strings.HasSuffix(name, "Metadata") {
|
||||
metadataNames = append(metadataNames, name)
|
||||
|
||||
displayName, ok := pkgMetadataMapping[typeName]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
69
syft/internal/packagemetadata/completion_tester.go
Normal file
69
syft/internal/packagemetadata/completion_tester.go
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,8 +14,12 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
)
|
||||
|
||||
var metadataExceptions = strset.New(
|
||||
"FileMetadata",
|
||||
// these are names of struct types in the pkg package that are not metadata types (thus should not be in the JSON schema)
|
||||
var knownNonMetadataTypeNames = strset.New(
|
||||
"Package",
|
||||
"Collection",
|
||||
"License",
|
||||
"LicenseSet",
|
||||
)
|
||||
|
||||
func DiscoverTypeNames() ([]string, error) {
|
||||
@ -67,9 +71,9 @@ func findMetadataDefinitionNames(paths ...string) ([]string, error) {
|
||||
strNames := names.List()
|
||||
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.
|
||||
if len(strNames) < 30 {
|
||||
if len(strNames) < 35 {
|
||||
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
|
||||
}
|
||||
|
||||
structType, ok := spec.Type.(*ast.StructType)
|
||||
if !ok {
|
||||
name := spec.Name.String()
|
||||
|
||||
// only look for exported types
|
||||
if !isMetadataTypeCandidate(name) {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if the struct type ends with "Metadata"
|
||||
name := spec.Name.String()
|
||||
|
||||
// only look for exported types that end with "Metadata"
|
||||
if isMetadataTypeCandidate(name) {
|
||||
// print the full declaration of the struct type
|
||||
metadataDefinitions = append(metadataDefinitions, name)
|
||||
usedTypeNames = append(usedTypeNames, typeNamesUsedInStruct(structType)...)
|
||||
structType := extractStructType(spec.Type)
|
||||
if structType == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
metadataDefinitions = append(metadataDefinitions, name)
|
||||
usedTypeNames = append(usedTypeNames, typeNamesUsedInStruct(structType)...)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
// recursively find all type names used in the struct type
|
||||
var names []string
|
||||
@ -144,7 +176,6 @@ func typeNamesUsedInStruct(structType *ast.StructType) []string {
|
||||
|
||||
func isMetadataTypeCandidate(name string) bool {
|
||||
return len(name) > 0 &&
|
||||
strings.HasSuffix(name, "Metadata") &&
|
||||
unicode.IsUpper(rune(name[0])) && // must be exported
|
||||
!metadataExceptions.Has(name)
|
||||
!knownNonMetadataTypeNames.Has(name)
|
||||
}
|
||||
|
||||
41
syft/internal/packagemetadata/discover_type_names_test.go
Normal file
41
syft/internal/packagemetadata/discover_type_names_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dave/jennifer/jen"
|
||||
|
||||
@ -22,6 +23,10 @@ func main() {
|
||||
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))
|
||||
|
||||
f := jen.NewFile("packagemetadata")
|
||||
@ -45,6 +50,12 @@ func main() {
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to write file: %w", err))
|
||||
|
||||
@ -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).
|
||||
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{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,12 +2,141 @@ package packagemetadata
|
||||
|
||||
import (
|
||||
"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)
|
||||
for _, t := range AllTypes() {
|
||||
names = append(names, reflect.TypeOf(t).Name())
|
||||
}
|
||||
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]
|
||||
}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
package packagemetadata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestAllNames(t *testing.T) {
|
||||
@ -14,7 +17,7 @@ func TestAllNames(t *testing.T) {
|
||||
expected, err := DiscoverTypeNames()
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := AllNames()
|
||||
actual := AllTypeNames()
|
||||
|
||||
// ensure that the codebase (from ast analysis) reflects the latest code generated state
|
||||
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("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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ var jsonNameFromType = map[reflect.Type][]string{
|
||||
reflect.TypeOf(source.StereoscopeImageSourceMetadata{}): {"image"},
|
||||
}
|
||||
|
||||
func AllNames() []string {
|
||||
func AllTypeNames() []string {
|
||||
names := make([]string, 0)
|
||||
for _, t := range AllTypes() {
|
||||
names = append(names, reflect.TypeOf(t).Name())
|
||||
@ -32,7 +32,7 @@ func ReflectTypeFromJSONName(name string) reflect.Type {
|
||||
name = strings.ToLower(name)
|
||||
for t, vs := range jsonNameFromType {
|
||||
for _, v := range vs {
|
||||
if v == name {
|
||||
if strings.ToLower(v) == name {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package sourcemetadata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@ -14,7 +15,7 @@ func TestAllNames(t *testing.T) {
|
||||
expected, err := DiscoverTypeNames()
|
||||
require.NoError(t, err)
|
||||
|
||||
actual := AllNames()
|
||||
actual := AllTypeNames()
|
||||
|
||||
// ensure that the codebase (from ast analysis) reflects the latest code generated state
|
||||
if !assert.ElementsMatch(t, expected, actual) {
|
||||
@ -24,6 +25,6 @@ func TestAllNames(t *testing.T) {
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,11 +9,12 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
var _ FileOwner = (*AlpmMetadata)(nil)
|
||||
var _ FileOwner = (*AlpmDBEntry)(nil)
|
||||
|
||||
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"`
|
||||
Package string `mapstructure:"name" json:"package" cyclonedx:"package"`
|
||||
Version string `mapstructure:"version" json:"version" cyclonedx:"version"`
|
||||
@ -39,7 +40,7 @@ type AlpmFileRecord struct {
|
||||
Digests []file.Digest `mapstructure:"digests" json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
func (m AlpmMetadata) OwnedFiles() (result []string) {
|
||||
func (m AlpmDBEntry) OwnedFiles() (result []string) {
|
||||
s := strset.New()
|
||||
for _, f := range m.Files {
|
||||
if f.Path != "" {
|
||||
@ -15,14 +15,14 @@ import (
|
||||
|
||||
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:
|
||||
// - 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/database.c
|
||||
type ApkMetadata struct {
|
||||
type ApkDBEntry struct {
|
||||
Package string `mapstructure:"P" json:"package"`
|
||||
OriginPackage string `mapstructure:"o" json:"originPackage" cyclonedx:"originPackage"`
|
||||
Maintainer string `mapstructure:"m" json:"maintainer"`
|
||||
@ -41,9 +41,9 @@ type ApkMetadata struct {
|
||||
|
||||
type spaceDelimitedStringSlice []string
|
||||
|
||||
func (m *ApkMetadata) UnmarshalJSON(data []byte) error {
|
||||
func (m *ApkDBEntry) UnmarshalJSON(data []byte) error {
|
||||
var fields []reflect.StructField
|
||||
t := reflect.TypeOf(ApkMetadata{})
|
||||
t := reflect.TypeOf(ApkDBEntry{})
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Name == "Dependencies" {
|
||||
@ -102,7 +102,7 @@ type ApkFileRecord struct {
|
||||
Digest *file.Digest `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
func (m ApkMetadata) OwnedFiles() (result []string) {
|
||||
func (m ApkDBEntry) OwnedFiles() (result []string) {
|
||||
s := strset.New()
|
||||
for _, f := range m.Files {
|
||||
if f.Path != "" {
|
||||
@ -12,13 +12,13 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want ApkMetadata
|
||||
want ApkDBEntry
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: "{}",
|
||||
want: ApkMetadata{},
|
||||
want: ApkDBEntry{},
|
||||
},
|
||||
{
|
||||
name: "string array dependencies",
|
||||
@ -42,7 +42,7 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
||||
],
|
||||
"pullDependencies": ["foo", "bar"]
|
||||
}`,
|
||||
want: ApkMetadata{
|
||||
want: ApkDBEntry{
|
||||
Package: "scanelf",
|
||||
OriginPackage: "pax-utils",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
@ -80,7 +80,7 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
||||
],
|
||||
"pullDependencies": "foo bar"
|
||||
}`,
|
||||
want: ApkMetadata{
|
||||
want: ApkDBEntry{
|
||||
Package: "scanelf",
|
||||
OriginPackage: "pax-utils",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
@ -101,7 +101,7 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
||||
input: `{
|
||||
"pullDependencies": null
|
||||
}`,
|
||||
want: ApkMetadata{
|
||||
want: ApkDBEntry{
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
@ -111,7 +111,7 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
var got ApkMetadata
|
||||
var got ApkDBEntry
|
||||
err := json.Unmarshal([]byte(tt.input), &got)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
@ -2,10 +2,12 @@ package pkg
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// ClassifierMatch represents a single matched value within a binary file and the "class" name the search pattern represents.
|
||||
type ClassifierMatch struct {
|
||||
Classifier string `mapstructure:"Classifier" json:"classifier"`
|
||||
Location file.Location `mapstructure:"Location" json:"location"`
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
const catalogerName = "alpmdb-cataloger"
|
||||
|
||||
// NewAlpmdbCataloger returns a new cataloger object initialized for arch linux pacman database flat-file stores.
|
||||
func NewAlpmdbCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger(catalogerName).
|
||||
WithParserByGlobs(parseAlpmDB, pkg.AlpmDBGlob)
|
||||
|
||||
@ -23,11 +23,10 @@ func TestAlpmCataloger(t *testing.T) {
|
||||
pkg.NewLicenseFromLocations("LGPL3", dbLocation),
|
||||
pkg.NewLicenseFromLocations("GPL", dbLocation),
|
||||
),
|
||||
Locations: file.NewLocationSet(dbLocation),
|
||||
CPEs: nil,
|
||||
PURL: "",
|
||||
MetadataType: "AlpmMetadata",
|
||||
Metadata: pkg.AlpmMetadata{
|
||||
Locations: file.NewLocationSet(dbLocation),
|
||||
CPEs: nil,
|
||||
PURL: "",
|
||||
Metadata: pkg.AlpmDBEntry{
|
||||
BasePackage: "gmp",
|
||||
Package: "gmp",
|
||||
Version: "6.2.1-2",
|
||||
|
||||
@ -13,14 +13,13 @@ func newPackage(m *parsedData, release *linux.Release, dbLocation file.Location)
|
||||
licenseCandidates := strings.Split(m.Licenses, "\n")
|
||||
|
||||
p := pkg.Package{
|
||||
Name: m.Package,
|
||||
Version: m.Version,
|
||||
Locations: file.NewLocationSet(dbLocation),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
|
||||
Type: pkg.AlpmPkg,
|
||||
PURL: packageURL(m, release),
|
||||
MetadataType: pkg.AlpmMetadataType,
|
||||
Metadata: m.AlpmMetadata,
|
||||
Name: m.Package,
|
||||
Version: m.Version,
|
||||
Locations: file.NewLocationSet(dbLocation),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
|
||||
Type: pkg.AlpmPkg,
|
||||
PURL: packageURL(m, release),
|
||||
Metadata: m.AlpmDBEntry,
|
||||
}
|
||||
p.SetID()
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "bad distro id",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
@ -37,7 +37,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "gocase",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
@ -53,7 +53,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "missing architecture",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
},
|
||||
@ -66,7 +66,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
{
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||
Package: "python",
|
||||
Version: "3.10.0",
|
||||
Architecture: "any",
|
||||
@ -81,7 +81,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
{
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "x86_64",
|
||||
@ -97,7 +97,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "add source information as qualifier",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
AlpmDBEntry: pkg.AlpmDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
|
||||
@ -31,10 +31,11 @@ var (
|
||||
)
|
||||
|
||||
type parsedData struct {
|
||||
Licenses string `mapstructure:"license"`
|
||||
pkg.AlpmMetadata `mapstructure:",squash"`
|
||||
Licenses string `mapstructure:"license"`
|
||||
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) {
|
||||
data, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
|
||||
@ -17,12 +17,12 @@ func TestDatabaseParser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
expected pkg.AlpmMetadata
|
||||
expected pkg.AlpmDBEntry
|
||||
}{
|
||||
{
|
||||
name: "test alpm database parsing",
|
||||
fixture: "test-fixtures/files",
|
||||
expected: pkg.AlpmMetadata{
|
||||
expected: pkg.AlpmDBEntry{
|
||||
Backup: []pkg.AlpmFileRecord{
|
||||
{
|
||||
Path: "/etc/pacman.conf",
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
|
||||
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 {
|
||||
return generic.NewCataloger(catalogerName).
|
||||
WithParserByGlobs(parseApkDB, pkg.ApkDBGlob)
|
||||
|
||||
@ -20,14 +20,13 @@ func newPackage(d parsedData, release *linux.Release, dbLocation file.Location)
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...),
|
||||
PURL: packageURL(d.ApkMetadata, release),
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: d.ApkMetadata,
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...),
|
||||
PURL: packageURL(d.ApkDBEntry, release),
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: d.ApkDBEntry,
|
||||
}
|
||||
|
||||
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)
|
||||
func packageURL(m pkg.ApkMetadata, distro *linux.Release) string {
|
||||
func packageURL(m pkg.ApkDBEntry, distro *linux.Release) string {
|
||||
if distro == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "non-alpine distro",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
ApkDBEntry: pkg.ApkDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
@ -39,7 +39,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "gocase",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
ApkDBEntry: pkg.ApkDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
@ -55,7 +55,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "missing architecture",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
ApkDBEntry: pkg.ApkDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
},
|
||||
@ -70,7 +70,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
{
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
ApkDBEntry: pkg.ApkDBEntry{
|
||||
Package: "g++",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
@ -85,7 +85,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
{
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
ApkDBEntry: pkg.ApkDBEntry{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
@ -101,7 +101,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "add source information as qualifier",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
ApkDBEntry: pkg.ApkDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
@ -118,7 +118,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
name: "wolfi distro",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
ApkDBEntry: pkg.ApkDBEntry{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
@ -134,7 +134,7 @@ func Test_PackageURL(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
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 {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.expected, actual, true)
|
||||
@ -171,11 +171,11 @@ func Test_PackageURL(t *testing.T) {
|
||||
|
||||
func TestApkMetadata_FileOwner(t *testing.T) {
|
||||
tests := []struct {
|
||||
metadata pkg.ApkMetadata
|
||||
metadata pkg.ApkDBEntry
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{Path: "/somewhere"},
|
||||
{Path: "/else"},
|
||||
@ -187,7 +187,7 @@ func TestApkMetadata_FileOwner(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{Path: "/somewhere"},
|
||||
{Path: ""},
|
||||
|
||||
@ -27,10 +27,10 @@ var (
|
||||
|
||||
type parsedData struct {
|
||||
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.
|
||||
//
|
||||
//nolint:funlen,gocognit
|
||||
@ -390,7 +390,7 @@ func discoverPackageDependencies(pkgs []pkg.Package) (relationships []artifact.R
|
||||
lookup := make(map[string][]pkg.Package)
|
||||
// read "Provides" (p) and add as keys for lookup keys as well as package names
|
||||
for _, p := range pkgs {
|
||||
apkg, ok := p.Metadata.(pkg.ApkMetadata)
|
||||
apkg, ok := p.Metadata.(pkg.ApkDBEntry)
|
||||
if !ok {
|
||||
log.Warnf("cataloger failed to extract apk 'provides' metadata for package %+v", p.Name)
|
||||
continue
|
||||
@ -404,7 +404,7 @@ func discoverPackageDependencies(pkgs []pkg.Package) (relationships []artifact.R
|
||||
|
||||
// read "Pull Dependencies" (D) and match with keys
|
||||
for _, p := range pkgs {
|
||||
apkg, ok := p.Metadata.(pkg.ApkMetadata)
|
||||
apkg, ok := p.Metadata.(pkg.ApkDBEntry)
|
||||
if !ok {
|
||||
log.Warnf("cataloger failed to extract apk dependency metadata for package %+v", p.Name)
|
||||
continue
|
||||
|
||||
@ -23,11 +23,11 @@ import (
|
||||
func TestExtraFileAttributes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expected pkg.ApkMetadata
|
||||
expected pkg.ApkDBEntry
|
||||
}{
|
||||
{
|
||||
name: "test extra file attributes (checksum) are ignored",
|
||||
expected: pkg.ApkMetadata{
|
||||
expected: pkg.ApkDBEntry{
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{
|
||||
Path: "/usr",
|
||||
@ -67,7 +67,7 @@ func TestExtraFileAttributes(t *testing.T) {
|
||||
pkgs, _, err := parseApkDB(nil, new(generic.Environment), lrc)
|
||||
assert.NoError(t, err)
|
||||
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 != "" {
|
||||
t.Errorf("Files mismatch (-want +got):\n%s", diff)
|
||||
@ -91,9 +91,8 @@ func TestSinglePackageDetails(t *testing.T) {
|
||||
pkg.NewLicense("BSD"),
|
||||
pkg.NewLicense("GPL2+"),
|
||||
),
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "musl-utils",
|
||||
OriginPackage: "musl",
|
||||
Version: "1.1.24-r2",
|
||||
@ -179,9 +178,8 @@ func TestSinglePackageDetails(t *testing.T) {
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("GPL-2.0-only"),
|
||||
),
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "alpine-baselayout-data",
|
||||
OriginPackage: "alpine-baselayout",
|
||||
Version: "3.4.0-r0",
|
||||
@ -224,10 +222,9 @@ func TestSinglePackageDetails(t *testing.T) {
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("GPL-2.0-only"),
|
||||
),
|
||||
Type: pkg.ApkPkg,
|
||||
PURL: "",
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Type: pkg.ApkPkg,
|
||||
PURL: "",
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "alpine-baselayout",
|
||||
OriginPackage: "alpine-baselayout",
|
||||
Version: "3.2.0-r6",
|
||||
@ -702,11 +699,10 @@ func TestMultiplePackages(t *testing.T) {
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MPL-2.0 AND MIT", location),
|
||||
),
|
||||
Type: pkg.ApkPkg,
|
||||
PURL: "pkg:apk/alpine/libc-utils@0.7.2-r0?arch=x86_64&upstream=libc-dev&distro=alpine-3.12",
|
||||
Locations: fixtureLocationSet,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Type: pkg.ApkPkg,
|
||||
PURL: "pkg:apk/alpine/libc-utils@0.7.2-r0?arch=x86_64&upstream=libc-dev&distro=alpine-3.12",
|
||||
Locations: fixtureLocationSet,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "libc-utils",
|
||||
OriginPackage: "libc-dev",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
@ -734,8 +730,7 @@ func TestMultiplePackages(t *testing.T) {
|
||||
pkg.NewLicenseFromLocations("BSD", location),
|
||||
pkg.NewLicenseFromLocations("GPL2+", location),
|
||||
),
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "musl-utils",
|
||||
OriginPackage: "musl",
|
||||
Version: "1.1.24-r2",
|
||||
@ -872,14 +867,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
|
||||
genFn: func() ([]pkg.Package, []artifact.Relationship) {
|
||||
a := pkg.Package{
|
||||
Name: "package-a",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Provides: []string{"a-thing"},
|
||||
},
|
||||
}
|
||||
a.SetID()
|
||||
b := pkg.Package{
|
||||
Name: "package-b",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Provides: []string{"b-thing"},
|
||||
},
|
||||
}
|
||||
@ -893,14 +888,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
|
||||
genFn: func() ([]pkg.Package, []artifact.Relationship) {
|
||||
a := pkg.Package{
|
||||
Name: "package-a",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Dependencies: []string{"b-thing"},
|
||||
},
|
||||
}
|
||||
a.SetID()
|
||||
b := pkg.Package{
|
||||
Name: "package-b",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Provides: []string{"b-thing"},
|
||||
},
|
||||
}
|
||||
@ -920,14 +915,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
|
||||
genFn: func() ([]pkg.Package, []artifact.Relationship) {
|
||||
a := pkg.Package{
|
||||
Name: "package-a",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Dependencies: []string{"so:libc.musl-x86_64.so.1"},
|
||||
},
|
||||
}
|
||||
a.SetID()
|
||||
b := pkg.Package{
|
||||
Name: "package-b",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
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) {
|
||||
a := pkg.Package{
|
||||
Name: "package-a",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Dependencies: []string{"so:libc.musl-x86_64.so.1"},
|
||||
},
|
||||
}
|
||||
a.SetID()
|
||||
b := pkg.Package{
|
||||
Name: "package-b",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Provides: []string{""},
|
||||
},
|
||||
}
|
||||
@ -968,14 +963,14 @@ func Test_discoverPackageDependencies(t *testing.T) {
|
||||
genFn: func() ([]pkg.Package, []artifact.Relationship) {
|
||||
a := pkg.Package{
|
||||
Name: "package-a",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Dependencies: []string{"musl>=1.2"},
|
||||
},
|
||||
}
|
||||
a.SetID()
|
||||
b := pkg.Package{
|
||||
Name: "musl",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
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) {
|
||||
a := pkg.Package{
|
||||
Name: "alpine-baselayout",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Dependencies: []string{"/bin/sh"},
|
||||
},
|
||||
}
|
||||
a.SetID()
|
||||
b := pkg.Package{
|
||||
Name: "busybox",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Provides: []string{"/bin/sh"},
|
||||
},
|
||||
}
|
||||
|
||||
@ -61,8 +61,8 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
|
||||
// add the locations
|
||||
target.Locations.Add(extra.Locations.ToSlice()...)
|
||||
// update the metadata to indicate which classifiers were used
|
||||
meta, _ := target.Metadata.(pkg.BinaryMetadata)
|
||||
if m, ok := extra.Metadata.(pkg.BinaryMetadata); ok {
|
||||
meta, _ := target.Metadata.(pkg.BinarySignature)
|
||||
if m, ok := extra.Metadata.(pkg.BinarySignature); ok {
|
||||
meta.Matches = append(meta.Matches, m.Matches...)
|
||||
}
|
||||
target.Metadata = meta
|
||||
|
||||
@ -331,7 +331,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Version: "3.11.2",
|
||||
PURL: "pkg:generic/python@3.11.2",
|
||||
Locations: locations("python3", "libpython3.11.so.1.0"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("python-binary", "python3"),
|
||||
match("python-binary", "libpython3.11.so.1.0"),
|
||||
@ -348,7 +348,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Version: "3.9.13",
|
||||
PURL: "pkg:generic/python@3.9.13",
|
||||
Locations: locations("python3.9", "libpython3.9.so.1.0"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("python-binary", "python3.9"),
|
||||
match("python-binary", "libpython3.9.so.1.0"),
|
||||
@ -365,7 +365,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Version: "3.9.2",
|
||||
PURL: "pkg:generic/python@3.9.2",
|
||||
Locations: locations("python3.9"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("python-binary", "python3.9"),
|
||||
},
|
||||
@ -380,7 +380,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Version: "3.4.10",
|
||||
PURL: "pkg:generic/python@3.4.10",
|
||||
Locations: locations("python3.4", "libpython3.4m.so.1.0"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("python-binary", "python3.4"),
|
||||
match("python-binary", "libpython3.4m.so.1.0"),
|
||||
@ -420,7 +420,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/python@3.8.16",
|
||||
Locations: locations("dir/python3.8", "python3.8", "libpython3.8.so"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("python-binary", "dir/python3.8"),
|
||||
match("python-binary", "python3.8"),
|
||||
@ -577,7 +577,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@3.2.1",
|
||||
Locations: locations("ruby", "libruby.so.3.2.1"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.3.2.1"),
|
||||
@ -594,7 +594,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@2.7.7p221",
|
||||
Locations: locations("ruby", "libruby.so.2.7.7"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.2.7.7"),
|
||||
@ -611,7 +611,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/ruby@2.6.10p210",
|
||||
Locations: locations("ruby", "libruby.so.2.6.10"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match("ruby-binary", "ruby"),
|
||||
match("ruby-binary", "libruby.so.2.6.10"),
|
||||
@ -774,8 +774,8 @@ func locations(locations ...string) file.LocationSet {
|
||||
}
|
||||
|
||||
// metadata paths are: realPath, virtualPath
|
||||
func metadata(classifier string, paths ...string) pkg.BinaryMetadata {
|
||||
return pkg.BinaryMetadata{
|
||||
func metadata(classifier string, paths ...string) pkg.BinarySignature {
|
||||
return pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
match(classifier, paths...),
|
||||
},
|
||||
@ -819,8 +819,8 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
|
||||
}
|
||||
}
|
||||
|
||||
m1 := expected.Metadata.(pkg.BinaryMetadata).Matches
|
||||
m2 := p.Metadata.(pkg.BinaryMetadata).Matches
|
||||
m1 := expected.Metadata.(pkg.BinarySignature).Matches
|
||||
m2 := p.Metadata.(pkg.BinarySignature).Matches
|
||||
matches := true
|
||||
if len(m1) == len(m2) {
|
||||
for i, m1 := range m1 {
|
||||
|
||||
@ -162,8 +162,8 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evide
|
||||
locationSet := file.NewLocationSet(location)
|
||||
locationSet.Add(p.Locations.ToSlice()...)
|
||||
p.Locations = locationSet
|
||||
meta, _ := p.Metadata.(pkg.BinaryMetadata)
|
||||
p.Metadata = pkg.BinaryMetadata{
|
||||
meta, _ := p.Metadata.(pkg.BinarySignature)
|
||||
p.Metadata = pkg.BinarySignature{
|
||||
Matches: append([]pkg.ClassifierMatch{
|
||||
{
|
||||
Classifier: classifier.Class,
|
||||
|
||||
@ -29,11 +29,10 @@ func newPackage(classifier classifier, location file.Location, matchMetadata map
|
||||
Locations: file.NewLocationSet(
|
||||
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Type: pkg.BinaryPkg,
|
||||
CPEs: cpes,
|
||||
FoundBy: catalogerName,
|
||||
MetadataType: pkg.BinaryMetadataType,
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Type: pkg.BinaryPkg,
|
||||
CPEs: cpes,
|
||||
FoundBy: catalogerName,
|
||||
Metadata: pkg.BinarySignature{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
{
|
||||
Classifier: classifier.Class,
|
||||
|
||||
@ -22,7 +22,7 @@ type upstreamCandidate struct {
|
||||
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
|
||||
// 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.
|
||||
@ -60,7 +60,7 @@ func upstreamCandidates(m pkg.ApkMetadata) (candidates []upstreamCandidate) {
|
||||
}
|
||||
|
||||
func candidateVendorsForAPK(p pkg.Package) fieldCandidateSet {
|
||||
metadata, ok := p.Metadata.(pkg.ApkMetadata)
|
||||
metadata, ok := p.Metadata.(pkg.ApkDBEntry)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@ -101,7 +101,7 @@ func candidateVendorsForAPK(p pkg.Package) fieldCandidateSet {
|
||||
}
|
||||
|
||||
func candidateProductsForAPK(p pkg.Package) fieldCandidateSet {
|
||||
metadata, ok := p.Metadata.(pkg.ApkMetadata)
|
||||
metadata, ok := p.Metadata.(pkg.ApkDBEntry)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "py3-cryptography Package",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "py3-cryptography",
|
||||
},
|
||||
},
|
||||
@ -26,7 +26,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "py2-pypdf with explicit different origin",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "py2-pypdf",
|
||||
OriginPackage: "abcdefg",
|
||||
},
|
||||
@ -36,7 +36,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "ruby-armadillo Package",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-armadillo",
|
||||
},
|
||||
},
|
||||
@ -45,7 +45,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "python-3.6",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "python-3.6",
|
||||
},
|
||||
},
|
||||
@ -54,7 +54,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "ruby-3.6",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-3.6",
|
||||
URL: "https://www.ruby-lang.org/",
|
||||
},
|
||||
@ -64,7 +64,7 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "make",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "make",
|
||||
URL: "https://www.gnu.org/software/make",
|
||||
},
|
||||
@ -74,10 +74,9 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "ruby-rake with matching origin",
|
||||
pkg: pkg.Package{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://github.com/ruby/rake",
|
||||
OriginPackage: "ruby-rake",
|
||||
@ -88,10 +87,9 @@ func Test_candidateVendorsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "ruby-rake with non-matching origin",
|
||||
pkg: pkg.Package{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://www.ruby-lang.org/",
|
||||
OriginPackage: "ruby",
|
||||
@ -116,7 +114,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "py3-cryptography Package",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "py3-cryptography",
|
||||
},
|
||||
},
|
||||
@ -125,7 +123,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "py2-pypdf with explicit different origin",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "py2-pypdf",
|
||||
OriginPackage: "abcdefg",
|
||||
},
|
||||
@ -135,7 +133,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "ruby-armadillo Package",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-armadillo",
|
||||
},
|
||||
},
|
||||
@ -144,7 +142,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "python-3.6",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "python-3.6",
|
||||
},
|
||||
},
|
||||
@ -153,7 +151,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "ruby-3.6",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-3.6",
|
||||
URL: "https://www.ruby-lang.org/",
|
||||
},
|
||||
@ -163,7 +161,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "make",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "make",
|
||||
URL: "https://www.gnu.org/software/make",
|
||||
},
|
||||
@ -173,7 +171,7 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "ruby-rake with matching origin",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://github.com/ruby/rake",
|
||||
OriginPackage: "ruby-rake",
|
||||
@ -184,10 +182,9 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
{
|
||||
name: "ruby-rake with non-matching origin",
|
||||
pkg: pkg.Package{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Name: "ruby-rake",
|
||||
Type: pkg.ApkPkg,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://www.ruby-lang.org/",
|
||||
OriginPackage: "ruby",
|
||||
@ -206,12 +203,12 @@ func Test_candidateProductsForAPK(t *testing.T) {
|
||||
func Test_upstreamCandidates(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata pkg.ApkMetadata
|
||||
metadata pkg.ApkDBEntry
|
||||
expected []upstreamCandidate
|
||||
}{
|
||||
{
|
||||
name: "gocase",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "p",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -220,7 +217,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "same package and origin simple case",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "p",
|
||||
OriginPackage: "p",
|
||||
},
|
||||
@ -230,7 +227,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "different package and origin",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "p",
|
||||
OriginPackage: "origin",
|
||||
},
|
||||
@ -240,7 +237,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "upstream python package information as qualifier py- prefix",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "py-potatoes",
|
||||
OriginPackage: "py-potatoes",
|
||||
},
|
||||
@ -250,7 +247,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "upstream python package information as qualifier py3- prefix",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "py3-potatoes",
|
||||
OriginPackage: "py3-potatoes",
|
||||
},
|
||||
@ -260,7 +257,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "python package with distinct origin package",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "py3-non-existant",
|
||||
OriginPackage: "abcdefg",
|
||||
},
|
||||
@ -270,7 +267,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "upstream ruby package information as qualifier",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-something",
|
||||
OriginPackage: "ruby-something",
|
||||
},
|
||||
@ -280,7 +277,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ruby package with distinct origin package",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-something",
|
||||
OriginPackage: "1234567",
|
||||
},
|
||||
@ -290,7 +287,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "postgesql-15 upstream postgresql",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "postgresql-15",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -299,7 +296,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "postgesql15 upstream postgresql",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "postgresql15",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -308,7 +305,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "go-1.19 upstream go",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "go-1.19",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -317,7 +314,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "go1.143 upstream go",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "go1.143",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -326,7 +323,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "abc-101.191.23456 upstream abc",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "abc-101.191.23456",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -335,7 +332,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "abc101.191.23456 upstream abc",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "abc101.191.23456",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -344,7 +341,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "abc101-12345-1045 upstream abc101-12345",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "abc101-12345-1045",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -353,7 +350,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "abc101-a12345-1045 upstream abc101-a12345",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "abc101-a12345-1045",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -362,7 +359,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "package starting with single digit",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "3proxy",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -371,7 +368,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "package starting with multiple digits",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "356proxy",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -380,7 +377,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "package composed of only digits",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "123456",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -389,7 +386,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ruby-3.6 upstream ruby",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-3.6",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -398,7 +395,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ruby3.6 upstream ruby",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby3.6",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -407,7 +404,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ruby3.6-tacos upstream tacos",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby3.6-tacos",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -416,7 +413,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "ruby-3.6-tacos upstream tacos",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-3.6-tacos",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
@ -425,7 +422,7 @@ func Test_upstreamCandidates(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "abc1234jksajflksa",
|
||||
metadata: pkg.ApkMetadata{
|
||||
metadata: pkg.ApkDBEntry{
|
||||
Package: "abc1234jksajflksa",
|
||||
},
|
||||
expected: []upstreamCandidate{
|
||||
|
||||
@ -168,18 +168,18 @@ func candidateVendors(p pkg.Package) []string {
|
||||
}
|
||||
}
|
||||
|
||||
switch p.MetadataType {
|
||||
case pkg.RpmMetadataType:
|
||||
switch p.Metadata.(type) {
|
||||
case pkg.RpmDBEntry:
|
||||
vendors.union(candidateVendorsForRPM(p))
|
||||
case pkg.GemMetadataType:
|
||||
case pkg.RubyGemspec:
|
||||
vendors.union(candidateVendorsForRuby(p))
|
||||
case pkg.PythonPackageMetadataType:
|
||||
case pkg.PythonPackage:
|
||||
vendors.union(candidateVendorsForPython(p))
|
||||
case pkg.JavaMetadataType:
|
||||
case pkg.JavaArchive:
|
||||
vendors.union(candidateVendorsForJava(p))
|
||||
case pkg.ApkMetadataType:
|
||||
case pkg.ApkDBEntry:
|
||||
vendors.union(candidateVendorsForAPK(p))
|
||||
case pkg.NpmPackageJSONMetadataType:
|
||||
case pkg.NpmPackage:
|
||||
vendors.union(candidateVendorsForJavascript(p))
|
||||
}
|
||||
|
||||
@ -217,12 +217,14 @@ func candidateVendors(p pkg.Package) []string {
|
||||
func candidateProducts(p pkg.Package) []string {
|
||||
products := newFieldCandidateSet(p.Name)
|
||||
|
||||
_, hasJavaMetadata := p.Metadata.(pkg.JavaArchive)
|
||||
|
||||
switch {
|
||||
case p.Language == pkg.Python:
|
||||
if !strings.HasPrefix(p.Name, "python") {
|
||||
products.addValue("python-" + p.Name)
|
||||
}
|
||||
case p.Language == pkg.Java || p.MetadataType == pkg.JavaMetadataType:
|
||||
case p.Language == pkg.Java || hasJavaMetadata:
|
||||
products.addValue(candidateProductsForJava(p)...)
|
||||
case p.Language == pkg.Go:
|
||||
// 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))
|
||||
}
|
||||
|
||||
|
||||
@ -67,13 +67,12 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "python language",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Python,
|
||||
Type: pkg.DebPkg,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Python,
|
||||
Type: pkg.DebPkg,
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "alex goodman",
|
||||
AuthorEmail: "william.goodman@anchore.com",
|
||||
},
|
||||
@ -117,12 +116,11 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "javascript language",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.JavaScript,
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "jon",
|
||||
URL: "https://github.com/bob/npm-name",
|
||||
},
|
||||
@ -135,13 +133,12 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "ruby language",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Ruby,
|
||||
Type: pkg.DebPkg,
|
||||
MetadataType: pkg.GemMetadataType,
|
||||
Metadata: pkg.GemMetadata{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Ruby,
|
||||
Type: pkg.DebPkg,
|
||||
Metadata: pkg.RubyGemspec{
|
||||
Authors: []string{
|
||||
"someones name",
|
||||
"someones.elses.name@gmail.com",
|
||||
@ -177,14 +174,13 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "java language with groupID",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "org.sonatype.nexus",
|
||||
},
|
||||
},
|
||||
@ -201,11 +197,10 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "java with URL in metadata", // regression: https://github.com/anchore/grype/issues/417
|
||||
p: pkg.Package{
|
||||
Name: "wstx-asl",
|
||||
Version: "3.2.7",
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Name: "wstx-asl",
|
||||
Version: "3.2.7",
|
||||
Type: pkg.JavaPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Ant-Version": "Apache Ant 1.6.5",
|
||||
@ -251,13 +246,12 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "java language - multi tier manifest fields",
|
||||
p: pkg.Package{
|
||||
Name: "cxf-rt-bindings-xml",
|
||||
Version: "3.3.10",
|
||||
FoundBy: "java-cataloger",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Name: "cxf-rt-bindings-xml",
|
||||
Version: "3.3.10",
|
||||
FoundBy: "java-cataloger",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
VirtualPath: "/opt/jboss/keycloak/modules/system/layers/base/org/apache/cxf/impl/main/cxf-rt-bindings-xml-3.3.10.jar",
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
@ -287,7 +281,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
"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",
|
||||
GroupID: "org.apache.cxf",
|
||||
ArtifactID: "cxf-rt-bindings-xml",
|
||||
@ -304,12 +298,11 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "rpm vendor selection",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: pkg.RpmMetadata{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Vendor: "some-vendor",
|
||||
},
|
||||
},
|
||||
@ -322,12 +315,11 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "rpm with epoch",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "1:3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: pkg.RpmMetadata{
|
||||
Name: "name",
|
||||
Version: "1:3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Type: pkg.RpmPkg,
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Vendor: "some-vendor",
|
||||
},
|
||||
},
|
||||
@ -340,12 +332,11 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "deb with epoch",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "1:3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Type: pkg.DebPkg,
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{},
|
||||
Name: "name",
|
||||
Version: "1:3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Type: pkg.DebPkg,
|
||||
Metadata: pkg.DpkgDBEntry{},
|
||||
},
|
||||
expected: []string{
|
||||
"cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*",
|
||||
@ -354,14 +345,13 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "cloudbees jenkins package identified via groupId",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "com.cloudbees.jenkins.plugins",
|
||||
},
|
||||
},
|
||||
@ -375,14 +365,13 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "jenkins.io package identified via groupId prefix",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "io.jenkins.plugins.name.something",
|
||||
},
|
||||
},
|
||||
@ -399,14 +388,13 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "jenkins.io package identified via groupId",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "io.jenkins.plugins",
|
||||
},
|
||||
},
|
||||
@ -419,14 +407,13 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "jenkins-ci.io package identified via groupId",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "io.jenkins-ci.plugins",
|
||||
},
|
||||
},
|
||||
@ -441,14 +428,13 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "jenkins-ci.org package identified via groupId",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "org.jenkins-ci.plugins",
|
||||
},
|
||||
},
|
||||
@ -463,14 +449,13 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "jira-atlassian filtering",
|
||||
p: pkg.Package{
|
||||
Name: "jira_client_core",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Name: "jira_client_core",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "org.atlassian.jira",
|
||||
ArtifactID: "jira_client_core",
|
||||
},
|
||||
@ -498,14 +483,13 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "jenkins filtering",
|
||||
p: pkg.Package{
|
||||
Name: "cloudbees-installation-manager",
|
||||
Version: "2.89.0.33",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Name: "cloudbees-installation-manager",
|
||||
Version: "2.89.0.33",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "com.cloudbees.jenkins.modules",
|
||||
ArtifactID: "cloudbees-installation-manager",
|
||||
},
|
||||
@ -568,13 +552,12 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "regression: handlebars within java archive",
|
||||
p: pkg.Package{
|
||||
Name: "handlebars",
|
||||
Version: "3.0.8",
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
FoundBy: "java-cataloger",
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Name: "handlebars",
|
||||
Version: "3.0.8",
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
FoundBy: "java-cataloger",
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Extension-Name": "handlebars",
|
||||
@ -586,7 +569,7 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
"Short-Name": "handlebars",
|
||||
},
|
||||
},
|
||||
PomProperties: &pkg.PomProperties{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "org.jenkins-ci.ui",
|
||||
ArtifactID: "handlebars",
|
||||
Version: "3.0.8",
|
||||
@ -605,20 +588,19 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "regression: jenkins plugin active-directory",
|
||||
p: pkg.Package{
|
||||
Name: "active-directory",
|
||||
Version: "2.25.1",
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
FoundBy: "java-cataloger",
|
||||
Language: pkg.Java,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Name: "active-directory",
|
||||
Version: "2.25.1",
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
FoundBy: "java-cataloger",
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Extension-Name": "active-directory",
|
||||
"Group-Id": "org.jenkins-ci.plugins",
|
||||
},
|
||||
},
|
||||
PomProperties: &pkg.PomProperties{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "org.jenkins-ci.plugins",
|
||||
ArtifactID: "org.jenkins-ci.plugins",
|
||||
Version: "2.25.1",
|
||||
@ -643,13 +625,12 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "regression: special characters in CPE should result in no generation",
|
||||
p: pkg.Package{
|
||||
Name: "bundler",
|
||||
Version: "2.1.4",
|
||||
Type: pkg.GemPkg,
|
||||
FoundBy: "gem-cataloger",
|
||||
Language: pkg.Ruby,
|
||||
MetadataType: pkg.GemMetadataType,
|
||||
Metadata: pkg.GemMetadata{
|
||||
Name: "bundler",
|
||||
Version: "2.1.4",
|
||||
Type: pkg.GemPkg,
|
||||
FoundBy: "gem-cataloger",
|
||||
Language: pkg.Ruby,
|
||||
Metadata: pkg.RubyGemspec{
|
||||
Name: "bundler",
|
||||
Version: "2.1.4",
|
||||
Authors: []string{
|
||||
@ -695,13 +676,12 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "regression: ruby-rake apk missing expected ruby-lang:rake CPE",
|
||||
p: pkg.Package{
|
||||
Name: "ruby-rake",
|
||||
Version: "2.7.6-r0",
|
||||
Type: pkg.ApkPkg,
|
||||
FoundBy: "apk-db-analyzer",
|
||||
Language: pkg.UnknownLanguage,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Name: "ruby-rake",
|
||||
Version: "2.7.6-r0",
|
||||
Type: pkg.ApkPkg,
|
||||
FoundBy: "apk-db-analyzer",
|
||||
Language: pkg.UnknownLanguage,
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Package: "ruby-rake",
|
||||
URL: "https://www.ruby-lang.org/",
|
||||
OriginPackage: "ruby",
|
||||
@ -744,18 +724,18 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
sort.Strings(extra)
|
||||
if len(extra) > 0 {
|
||||
t.Errorf("found extra CPEs:")
|
||||
}
|
||||
for _, d := range extra {
|
||||
fmt.Printf(" %q,\n", d)
|
||||
for _, d := range extra {
|
||||
t.Logf(" %q,\n", d)
|
||||
}
|
||||
}
|
||||
|
||||
missing := strset.Difference(expectedCpeSet, actualCpeSet).List()
|
||||
sort.Strings(missing)
|
||||
if len(missing) > 0 {
|
||||
t.Errorf("missing CPEs:")
|
||||
}
|
||||
for _, d := range missing {
|
||||
fmt.Printf(" %q,\n", d)
|
||||
for _, d := range missing {
|
||||
t.Logf(" %q,\n", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -797,8 +777,8 @@ func TestCandidateProducts(t *testing.T) {
|
||||
Name: "some-java-package-with-group-id",
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "com.apple.itunes",
|
||||
},
|
||||
},
|
||||
@ -811,8 +791,8 @@ func TestCandidateProducts(t *testing.T) {
|
||||
Name: "some-java-package-with-group-id",
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "com.apple.itunes.*",
|
||||
},
|
||||
},
|
||||
@ -825,8 +805,8 @@ func TestCandidateProducts(t *testing.T) {
|
||||
Name: "some-jenkins-plugin",
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "com.cloudbees.jenkins.plugins",
|
||||
},
|
||||
},
|
||||
|
||||
@ -55,7 +55,7 @@ func candidateVendorsForJava(p pkg.Package) fieldCandidateSet {
|
||||
func vendorsFromJavaManifestNames(p pkg.Package) fieldCandidateSet {
|
||||
vendors := newFieldCandidateSet()
|
||||
|
||||
metadata, ok := p.Metadata.(pkg.JavaMetadata)
|
||||
metadata, ok := p.Metadata.(pkg.JavaArchive)
|
||||
if !ok {
|
||||
return vendors
|
||||
}
|
||||
@ -159,7 +159,7 @@ func productsFromArtifactAndGroupIDs(artifactID string, groupIDs []string) []str
|
||||
}
|
||||
|
||||
func artifactIDFromJavaPackage(p pkg.Package) string {
|
||||
metadata, ok := p.Metadata.(pkg.JavaMetadata)
|
||||
metadata, ok := p.Metadata.(pkg.JavaArchive)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
@ -177,7 +177,7 @@ func artifactIDFromJavaPackage(p pkg.Package) string {
|
||||
}
|
||||
|
||||
func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
|
||||
metadata, ok := p.Metadata.(pkg.JavaMetadata)
|
||||
metadata, ok := p.Metadata.(pkg.JavaArchive)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@ -188,7 +188,7 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
|
||||
// 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
|
||||
// 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, groupIDsFromPomProject(metadata.PomProject)...)
|
||||
groupIDs = append(groupIDs, groupIDsFromJavaManifest(pkgName, metadata.Manifest)...)
|
||||
@ -196,7 +196,7 @@ func GroupIDsFromJavaMetadata(pkgName string, metadata pkg.JavaMetadata) (groupI
|
||||
return groupIDs
|
||||
}
|
||||
|
||||
func groupIDsFromPomProperties(properties *pkg.PomProperties) (groupIDs []string) {
|
||||
func groupIDsFromPomProperties(properties *pkg.JavaPomProperties) (groupIDs []string) {
|
||||
if properties == nil {
|
||||
return nil
|
||||
}
|
||||
@ -214,7 +214,7 @@ func groupIDsFromPomProperties(properties *pkg.PomProperties) (groupIDs []string
|
||||
return groupIDs
|
||||
}
|
||||
|
||||
func groupIDsFromPomProject(project *pkg.PomProject) (groupIDs []string) {
|
||||
func groupIDsFromPomProject(project *pkg.JavaPomProject) (groupIDs []string) {
|
||||
if project == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -78,8 +78,8 @@ func Test_candidateProductsForJava(t *testing.T) {
|
||||
{
|
||||
name: "duplicate groupID in artifactID field",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "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",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
ArtifactID: "org.sonatype.nexus",
|
||||
},
|
||||
},
|
||||
@ -153,8 +153,8 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "go case",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "io.jenkins-ci.plugin.thing;version='[2,3)'",
|
||||
},
|
||||
},
|
||||
@ -164,8 +164,8 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "from artifactID",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
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",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Extension-Name": "io.jenkins-ci.plugin.thing",
|
||||
@ -188,7 +188,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "from named section Extension-Name field",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
NamedSections: map[string]map[string]string{
|
||||
"section": {
|
||||
@ -203,7 +203,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "from main field - tier 1",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
// positive cases
|
||||
@ -236,7 +236,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "from main field - tier 2",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
// positive cases
|
||||
@ -256,7 +256,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "from main field - negative cases",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
// negative cases
|
||||
@ -271,7 +271,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "from named section field - tier 1",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
NamedSections: map[string]map[string]string{
|
||||
"section": {
|
||||
@ -306,7 +306,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "from named section field - negative cases",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
NamedSections: map[string]map[string]string{
|
||||
"section": {
|
||||
@ -323,7 +323,7 @@ func Test_groupIDsFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "no manifest or pom info",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{},
|
||||
Metadata: pkg.JavaArchive{},
|
||||
},
|
||||
expects: nil,
|
||||
},
|
||||
@ -349,8 +349,8 @@ func Test_artifactIDFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "go case",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
ArtifactID: "cloudbees-installation-manager",
|
||||
},
|
||||
},
|
||||
@ -360,8 +360,8 @@ func Test_artifactIDFromJavaPackage(t *testing.T) {
|
||||
{
|
||||
name: "ignore groupID-like things",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
ArtifactID: "io.jenkins-ci.plugin.thing",
|
||||
},
|
||||
},
|
||||
@ -390,7 +390,7 @@ func Test_vendorsFromJavaManifestNames(t *testing.T) {
|
||||
{
|
||||
name: "from manifest named section fields",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
NamedSections: map[string]map[string]string{
|
||||
"section": {
|
||||
@ -407,7 +407,7 @@ func Test_vendorsFromJavaManifestNames(t *testing.T) {
|
||||
{
|
||||
name: "from manifest named section fields - negative cases",
|
||||
pkg: pkg.Package{
|
||||
Metadata: pkg.JavaMetadata{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
NamedSections: map[string]map[string]string{
|
||||
"section": {
|
||||
|
||||
@ -3,12 +3,12 @@ package cpe
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func candidateVendorsForJavascript(p pkg.Package) fieldCandidateSet {
|
||||
if p.MetadataType != pkg.NpmPackageJSONMetadataType {
|
||||
if _, ok := p.Metadata.(pkg.NpmPackage); !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
vendors := newFieldCandidateSet()
|
||||
metadata, ok := p.Metadata.(pkg.NpmPackageJSONMetadata)
|
||||
metadata, ok := p.Metadata.(pkg.NpmPackage)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ func additionalVendorsForPython(v string) (vendors []string) {
|
||||
}
|
||||
|
||||
func candidateVendorsForPython(p pkg.Package) fieldCandidateSet {
|
||||
metadata, ok := p.Metadata.(pkg.PythonPackageMetadata)
|
||||
metadata, ok := p.Metadata.(pkg.PythonPackage)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ package cpe
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
|
||||
func candidateVendorsForRPM(p pkg.Package) fieldCandidateSet {
|
||||
metadata, ok := p.Metadata.(pkg.RpmMetadata)
|
||||
metadata, ok := p.Metadata.(pkg.RpmDBEntry)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user