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

* [wip]

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

* distinct the package metadata functions

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

* remove metadata type from package core model

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

* incorporate review feedback for names

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

* add RPM archive metadata and split parser helpers

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

* clarify the python package metadata type

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

* rename the KB metadata type

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

* break hackage and composer types by use case

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

* linting fix

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

* fix encoding and decoding for syft-json and cyclonedx

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

* bump json schema to 11

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

* update cyclonedx-json snapshots

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

* update cyclonedx-xml snapshots

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

* update spdx-json snapshots

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

* update spdx-tv snapshots

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

* update syft-json snapshots

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

* correct metadata type in stack yaml parser test

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

* fix bom-ref redactor for cyclonedx-xml

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

* add tests for legacy package metadata names

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

* regenerate json schema v11

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

* fix legacy HackageMetadataType reflect type value check

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

* fix linting

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

* packagemetadata discovery should account for type shadowing

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

* fix linting

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

* fix cli tests

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

* bump json schema version to v12

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

* update json schema to incorporate changes from main

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

* add syft-json legacy config option

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

* add tests around v11-v12 json decoding

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

* add docs for SYFT_JSON_LEGACY

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

* rename structs to be compliant with new naming scheme

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

---------

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

View File

@ -151,6 +151,39 @@ sequenceDiagram
Note right of catalog: cataloger configuration is done based on src
```
### 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

View File

@ -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:

View File

@ -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

View File

@ -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",

View File

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

View File

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

View File

@ -6,21 +6,27 @@ import (
"github.com/anchore/syft/syft/sbom"
)
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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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"
)

File diff suppressed because it is too large Load Diff

View File

@ -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, ",")
}
}

View File

@ -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: "",
},
},

View File

@ -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

View File

@ -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")
}
})
}
}

View File

@ -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
}
}

View File

@ -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: "",
},
},

View File

@ -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{}
}

View File

@ -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: "",
},
},

View File

@ -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
}

View File

@ -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",
},
},

View File

@ -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
}
}

View File

@ -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: "",
},
},

View File

@ -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
}
}

View File

@ -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: "",
},
},

View File

@ -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)
}
}

View File

@ -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: "",
},
},

View File

@ -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
}
}

View File

@ -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: "",
},
},

View File

@ -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: "",
},
},

View File

@ -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 != "" {

View File

@ -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 {

View File

@ -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=",
},
},

View File

@ -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 {

View File

@ -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{

View File

@ -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"

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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",
},

View File

@ -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:*:*:*:*:*:*:*"),
},
})
}
}

View File

@ -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"
},
{

View File

@ -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"
},
{

View File

@ -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"
},
{

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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{}
}

View File

@ -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()

View File

@ -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

View File

@ -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{},

View File

@ -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
}

View File

@ -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())
}
})
}

View File

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

View File

@ -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": "",

View File

@ -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": "",

View File

@ -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": "",

View File

@ -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,
},
}

View File

@ -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)
}
})
}
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}
}

View File

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

View File

@ -14,8 +14,12 @@ import (
"github.com/scylladb/go-set/strset"
)
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)
}

View File

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

View File

@ -3,6 +3,7 @@ package main
import (
"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))

View File

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

View File

@ -2,12 +2,141 @@ package packagemetadata
import (
"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]
}

View File

@ -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")
})
}
}

View File

@ -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
}
}

View File

@ -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())
}
}

View File

@ -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 != "" {

View File

@ -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 != "" {

View File

@ -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 {

View File

@ -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"`

View File

@ -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)

View File

@ -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",

View File

@ -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()

View File

@ -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",

View File

@ -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 {

View File

@ -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",

View File

@ -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)

View File

@ -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 ""
}

View File

@ -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: ""},

View File

@ -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

View File

@ -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"},
},
}

View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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
}

View File

@ -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{

View File

@ -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))
}

View File

@ -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",
},
},

View File

@ -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
}

View File

@ -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": {

View File

@ -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
}

View File

@ -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
}

View File

@ -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