feat: add package supplier flag (#4131)

---------

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
Christopher Angelo Phillips 2025-08-12 14:49:41 -04:00 committed by GitHub
parent 89470ecdd3
commit 6b48bd4b5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 3453 additions and 73 deletions

View File

@ -218,8 +218,9 @@ func getSource(ctx context.Context, opts *options.Catalog, userInput string, sou
cfg := syft.DefaultGetSourceConfig().
WithRegistryOptions(opts.Registry.ToOptions()).
WithAlias(source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
Name: opts.Source.Name,
Version: opts.Source.Version,
Supplier: opts.Source.Supplier,
}).
WithExcludeConfig(source.ExcludeConfig{
Paths: opts.Exclusions,

View File

@ -259,6 +259,9 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&cfg.Source.BasePath, "base-path", "",
"base directory for scanning, no links will be followed above this directory, and all paths will be reported relative to this directory")
flags.StringVarP(&cfg.Source.Supplier, "source-supplier", "",
"the organization that supplied the component, which often may be the manufacturer, distributor, or repackager")
}
func (cfg *Catalog) DescribeFields(descriptions fangs.FieldDescriptionSet) {

View File

@ -43,9 +43,9 @@ over to the final syft 1.0 json output this option can be used to ease the trans
Note: long term support for this option is not guaranteed (it may change or break at any time)`)
descriptions.Add(&o.Template.Path, `path to the template file to use when rendering the output with the template output format.
descriptions.Add(&o.Template.Path, `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`)
descriptions.Add(&o.Template.Legacy, `if true, uses the go structs for the syft-json format for templating.
descriptions.Add(&o.Template.Legacy, `if true, uses the go structs for the syft-json format for templating.
if false, uses the syft-json output for templating (which follows the syft JSON schema exactly).
Note: long term support for this option is not guaranteed (it may change or break at any time)`)

View File

@ -16,6 +16,8 @@ import (
type sourceConfig struct {
Name string `json:"name" yaml:"name" mapstructure:"name"`
Version string `json:"version" yaml:"version" mapstructure:"version"`
Supplier string `json:"supplier" yaml:"supplier" mapstructure:"supplier"`
Source string `json:"source" yaml:"source" mapstructure:"source"`
BasePath string `yaml:"base-path" json:"base-path" mapstructure:"base-path"` // specify base path for all file paths
File fileSource `json:"file" yaml:"file" mapstructure:"file"`
Image imageSource `json:"image" yaml:"image" mapstructure:"image"`

View File

@ -78,7 +78,10 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
for _, image := range images {
originalSBOM, _ := catalogFixtureImage(t, image, source.SquashedScope)
// we need a way to inject supplier into this test
// supplier is not available as part of the SBOM Config API since the flag
// is used in conjunction with the SourceConfig which is injected into generateSBOM during scan
originalSBOM.Source.Supplier = "anchore"
f := encoders.GetByString(test.name)
require.NotNil(t, f)

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 = "16.0.36"
JSONSchemaVersion = "16.0.37"
)

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.36/document",
"$id": "anchore.io/schema/syft/json/16.0.37/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@ -3047,6 +3047,9 @@
"version": {
"type": "string"
},
"supplier": {
"type": "string"
},
"type": {
"type": "string"
},

View File

@ -48,7 +48,7 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
packages := s.Artifacts.Packages.Sorted()
components := make([]cyclonedx.Component, len(packages))
for i, p := range packages {
components[i] = helpers.EncodeComponent(p, locationSorter)
components[i] = helpers.EncodeComponent(p, s.Source.Supplier, locationSorter)
}
components = append(components, toOSComponent(s.Artifacts.LinuxDistribution)...)
@ -220,11 +220,22 @@ func toBomDescriptor(name, version string, srcMetadata source.Description) *cycl
},
},
},
Supplier: toBomSupplier(srcMetadata),
Properties: toBomProperties(srcMetadata),
Component: toBomDescriptorComponent(srcMetadata),
}
}
func toBomSupplier(srcMetadata source.Description) *cyclonedx.OrganizationalEntity {
if srcMetadata.Supplier != "" {
return &cyclonedx.OrganizationalEntity{
Name: srcMetadata.Supplier,
}
}
return nil
}
// used to indicate that a relationship listed under the syft artifact package can be represented as a cyclonedx dependency.
// NOTE: CycloneDX provides the ability to describe components and their dependency on other components.
// The dependency graph is capable of representing both direct and transitive relationships.
@ -321,10 +332,11 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
log.Debugf("unable to get fingerprint of source image metadata=%s: %+v", metadata.ID, err)
}
return &cyclonedx.Component{
BOMRef: string(bomRef),
Type: cyclonedx.ComponentTypeContainer,
Name: name,
Version: version,
BOMRef: string(bomRef),
Type: cyclonedx.ComponentTypeContainer,
Name: name,
Version: version,
Supplier: toBomSupplier(srcMetadata),
}
case source.DirectoryMetadata:
if name == "" {
@ -337,9 +349,10 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
return &cyclonedx.Component{
BOMRef: string(bomRef),
// TODO: this is lossy... we can't know if this is a file or a directory
Type: cyclonedx.ComponentTypeFile,
Name: name,
Version: version,
Type: cyclonedx.ComponentTypeFile,
Name: name,
Version: version,
Supplier: toBomSupplier(srcMetadata),
}
case source.FileMetadata:
if name == "" {
@ -352,9 +365,10 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
return &cyclonedx.Component{
BOMRef: string(bomRef),
// TODO: this is lossy... we can't know if this is a file or a directory
Type: cyclonedx.ComponentTypeFile,
Name: name,
Version: version,
Type: cyclonedx.ComponentTypeFile,
Name: name,
Version: version,
Supplier: toBomSupplier(srcMetadata),
}
}

View File

@ -389,7 +389,69 @@ func Test_toBomDescriptor(t *testing.T) {
Name: "syft:image:labels:key1",
Value: "value1",
},
}},
},
},
},
{
name: "with optional supplier is on the root component and bom metadata",
args: args{
name: "test-image",
version: "1.0.0",
srcMetadata: source.Description{
Name: "test-image",
Version: "1.0.0",
Supplier: "optional-supplier",
Metadata: source.ImageMetadata{},
},
},
want: &cyclonedx.Metadata{
Timestamp: "",
Lifecycles: nil,
Tools: &cyclonedx.ToolsChoice{
Components: &[]cyclonedx.Component{
{
Type: cyclonedx.ComponentTypeApplication,
Author: "anchore",
Name: "test-image",
Version: "1.0.0",
},
},
},
Authors: nil,
Component: &cyclonedx.Component{
BOMRef: "",
MIMEType: "",
Type: "container",
Supplier: &cyclonedx.OrganizationalEntity{
Name: "optional-supplier",
},
Author: "",
Publisher: "",
Group: "",
Name: "test-image",
Version: "1.0.0",
Description: "",
Scope: "",
Hashes: nil,
Licenses: nil,
Copyright: "",
CPE: "",
PackageURL: "",
SWID: nil,
Modified: nil,
Pedigree: nil,
ExternalReferences: nil,
Properties: nil,
Components: nil,
Evidence: nil,
ReleaseNotes: nil,
},
Manufacture: nil,
Supplier: &cyclonedx.OrganizationalEntity{
Name: "optional-supplier",
},
Licenses: nil,
},
},
}
for _, tt := range tests {

View File

@ -257,13 +257,11 @@ func toRootPackage(s source.Description) *spdx.Package {
PackageChecksums: checksums,
PackageExternalReferences: nil,
PrimaryPackagePurpose: purpose,
PackageSupplier: &spdx.Supplier{
Supplier: helpers.NOASSERTION,
},
PackageCopyrightText: helpers.NOASSERTION,
PackageDownloadLocation: helpers.NOASSERTION,
PackageLicenseConcluded: helpers.NOASSERTION,
PackageLicenseDeclared: helpers.NOASSERTION,
PackageSupplier: toSPDXSupplier(s),
PackageCopyrightText: helpers.NOASSERTION,
PackageDownloadLocation: helpers.NOASSERTION,
PackageLicenseConcluded: helpers.NOASSERTION,
PackageLicenseDeclared: helpers.NOASSERTION,
}
if purl != nil {
@ -327,6 +325,23 @@ func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
return spdx.ElementID(helpers.SanitizeElementID(id))
}
func toSPDXSupplier(s source.Description) *spdx.Supplier {
supplier := helpers.NOASSERTION
if s.Supplier != "" {
supplier = s.Supplier
}
supplierType := ""
if supplier != helpers.NOASSERTION {
supplierType = helpers.SUPPLIERORG
}
return &spdx.Supplier{
Supplier: supplier,
SupplierType: supplierType,
}
}
// packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
//
//nolint:funlen
@ -391,7 +406,7 @@ func toPackages(rels *relationship.Index, catalog *pkg.Collection, sbom sbom.SBO
// 7.6: Package Originator: may have single result for either Person or Organization,
// or NOASSERTION
// Cardinality: optional, one
PackageSupplier: toPackageSupplier(p),
PackageSupplier: toPackageSupplier(p, sbom.Source.Supplier),
PackageOriginator: toPackageOriginator(p),
@ -556,11 +571,18 @@ func toPackageOriginator(p pkg.Package) *spdx.Originator {
}
}
func toPackageSupplier(p pkg.Package) *spdx.Supplier {
func toPackageSupplier(p pkg.Package, sbomSupplier string) *spdx.Supplier {
kind, supplier := helpers.Supplier(p)
if kind == "" || supplier == "" {
supplier := helpers.NOASSERTION
supplierType := ""
if sbomSupplier != "" {
supplier = sbomSupplier
supplierType = helpers.SUPPLIERORG
}
return &spdx.Supplier{
Supplier: helpers.NOASSERTION,
Supplier: supplier,
SupplierType: supplierType,
}
}
return &spdx.Supplier{

View File

@ -36,8 +36,9 @@ func Test_toFormatModel(t *testing.T) {
name: "container",
in: sbom.SBOM{
Source: source.Description{
Name: "alpine",
Version: "sha256:d34db33f",
Name: "alpine",
Version: "sha256:d34db33f",
Supplier: "Alpine Linux",
Metadata: source.ImageMetadata{
UserInput: "alpine:latest",
ManifestDigest: "sha256:d34db33f",
@ -61,7 +62,8 @@ func Test_toFormatModel(t *testing.T) {
PackageName: "pkg-1",
PackageVersion: "version-1",
PackageSupplier: &spdx.Supplier{
Supplier: "NOASSERTION",
Supplier: "Alpine Linux",
SupplierType: "Organization",
},
},
{
@ -78,7 +80,8 @@ func Test_toFormatModel(t *testing.T) {
},
},
PackageSupplier: &spdx.Supplier{
Supplier: "NOASSERTION",
Supplier: "Alpine Linux",
SupplierType: "Organization",
},
},
},

View File

@ -145,10 +145,21 @@ func containerSource(p *spdx.Package) source.Description {
c := p.PackageChecksums[0]
digest = fmt.Sprintf("%s:%s", fromChecksumAlgorithm(c.Algorithm), c.Value)
}
supplier := ""
if p.PackageSupplier != nil {
// we also don't want NOASSERTION transferred to the syft format
// NOASSERTION == ""
if p.PackageSupplier.Supplier != helpers.NOASSERTION && p.PackageSupplier.SupplierType == helpers.SUPPLIERORG {
supplier = p.PackageSupplier.Supplier
}
}
return source.Description{
ID: id,
Name: p.PackageName,
Version: p.PackageVersion,
ID: id,
Name: p.PackageName,
Version: p.PackageVersion,
Supplier: supplier,
Metadata: source.ImageMetadata{
UserInput: container,
ID: id,
@ -179,10 +190,16 @@ func fileSource(p *spdx.Package) source.Description {
metadata, version = fileSourceMetadata(p)
}
supplier := ""
if p.PackageSupplier.Supplier != helpers.NOASSERTION {
supplier = p.PackageSupplier.Supplier
}
return source.Description{
ID: string(p.PackageSPDXIdentifier),
Name: p.PackageName,
Version: version,
Supplier: supplier,
Metadata: metadata,
}
}

View File

@ -525,17 +525,19 @@ func Test_convertToAndFromFormat(t *testing.T) {
UserInput: "some-image:some-tag",
ManifestDigest: "sha256:ab8b83234bc28f28d8e",
},
Name: "some-image",
Version: "some-tag",
Name: "some-image",
Version: "some-tag",
Supplier: "some-supplier",
},
packages: packages,
relationships: relationships,
},
{
name: ". directory source",
name: ". directory source with supplier",
source: source.Description{
ID: "DocumentRoot-Directory-.",
Name: ".",
ID: "DocumentRoot-Directory-.",
Name: ".",
Supplier: "some-supplier",
Metadata: source.DirectoryMetadata{
Path: ".",
},
@ -544,7 +546,7 @@ func Test_convertToAndFromFormat(t *testing.T) {
relationships: relationships,
},
{
name: "directory source",
name: "directory source without supplier",
source: source.Description{
ID: "DocumentRoot-Directory-my-app",
Name: "my-app",

View File

@ -14,7 +14,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func EncodeComponent(p pkg.Package, locationSorter func(a, b file.Location) int) cyclonedx.Component {
func EncodeComponent(p pkg.Package, supplier string, locationSorter func(a, b file.Location) int) cyclonedx.Component {
props := EncodeProperties(p, "syft:package")
if p.Metadata != nil {
@ -49,6 +49,7 @@ func EncodeComponent(p pkg.Package, locationSorter func(a, b file.Location) int)
Name: p.Name,
Group: encodeGroup(p),
Version: p.Version,
Supplier: encodeSupplier(p, supplier),
PackageURL: p.PURL,
Licenses: encodeLicenses(p),
CPE: encodeSingleCPE(p),
@ -61,6 +62,16 @@ func EncodeComponent(p pkg.Package, locationSorter func(a, b file.Location) int)
}
}
// TODO: we eventually want to update this so that we can read "supplier" from different syft metadata
func encodeSupplier(_ pkg.Package, sbomSupplier string) *cyclonedx.OrganizationalEntity {
if sbomSupplier != "" {
return &cyclonedx.OrganizationalEntity{
Name: sbomSupplier,
}
}
return nil
}
func DeriveBomRef(p pkg.Package) string {
// try and parse the PURL if possible and append syft id to it, to make
// the purl unique in the BOM.

View File

@ -152,7 +152,8 @@ func Test_encodeComponentProperties(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := EncodeComponent(test.input, file.LocationSorter(nil))
sbomSupplier := ""
c := EncodeComponent(test.input, sbomSupplier, file.LocationSorter(nil))
if test.expected == nil {
if c.Properties != nil {
t.Fatalf("expected no properties, got: %+v", *c.Properties)
@ -212,7 +213,8 @@ func Test_encodeCompomentType(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.pkg.ID()
p := EncodeComponent(tt.pkg, file.LocationSorter(nil))
sbomSupplier := ""
p := EncodeComponent(tt.pkg, sbomSupplier, file.LocationSorter(nil))
assert.Equal(t, tt.want, p)
})
}

View File

@ -211,6 +211,16 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
}
c := meta.Component
supplier := ""
// First check component-level supplier
if c.Supplier != nil && c.Supplier.Name != "" {
supplier = c.Supplier.Name
}
// Fall back to metadata-level supplier if component supplier is not set
if supplier == "" && meta.Supplier != nil && meta.Supplier.Name != "" {
supplier = meta.Supplier.Name
}
switch c.Type {
case cyclonedx.ComponentTypeContainer:
var labels map[string]string
@ -220,7 +230,8 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
}
return source.Description{
ID: "",
ID: "",
Supplier: supplier,
// TODO: can we decode alias name-version somehow? (it isn't be encoded in the first place yet)
Metadata: source.ImageMetadata{
@ -236,6 +247,7 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
// TODO: this is lossy... we can't know if this is a file or a directory
return source.Description{
ID: "",
Supplier: supplier,
Metadata: source.FileMetadata{Path: c.Name},
}
}

View File

@ -10,6 +10,7 @@ import (
const NONE = "NONE"
const NOASSERTION = "NOASSERTION"
const SUPPLIERORG = "Organization"
func DownloadLocation(p pkg.Package) string {
// 3.7: Package Download Location

View File

@ -12,10 +12,16 @@ import (
)
// Source object represents the thing that was cataloged
// Note: syft currently makes no claims or runs any logic to determine the Supplier field below
// Instead, the Supplier can be determined by the user of syft and passed as a config or flag to help fulfill
// the NTIA minimum elements. For mor information see the NTIA framing document below
// https://www.ntia.gov/files/ntia/publications/framingsbom_20191112.pdf
type Source struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Supplier string `json:"supplier,omitempty"`
Type string `json:"type"`
Metadata interface{} `json:"metadata"`
}
@ -25,6 +31,7 @@ type sourceUnpacker struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Version string `json:"version"`
Supplier string `json:"supplier,omitempty"`
Type string `json:"type"`
Metadata json.RawMessage `json:"metadata"`
Target json.RawMessage `json:"target"` // pre-v9 schema support
@ -40,6 +47,7 @@ func (s *Source) UnmarshalJSON(b []byte) error {
s.Name = unpacker.Name
s.Version = unpacker.Version
s.Supplier = unpacker.Supplier
s.Type = unpacker.Type
s.ID = unpacker.ID

View File

@ -320,6 +320,7 @@ func toSourceModel(src source.Description) model.Source {
ID: src.ID,
Name: src.Name,
Version: src.Version,
Supplier: src.Supplier,
Type: sourcemetadata.JSONName(src.Metadata),
Metadata: src.Metadata,
}

View File

@ -56,19 +56,21 @@ func Test_toSourceModel(t *testing.T) {
{
name: "directory",
src: source.Description{
ID: "test-id",
Name: "some-name",
Version: "some-version",
ID: "test-id",
Name: "some-name",
Version: "some-version",
Supplier: "optional-supplier",
Metadata: source.DirectoryMetadata{
Path: "some/path",
Base: "some/base",
},
},
expected: model.Source{
ID: "test-id",
Name: "some-name",
Version: "some-version",
Type: "directory",
ID: "test-id",
Name: "some-name",
Version: "some-version",
Supplier: "optional-supplier",
Type: "directory",
Metadata: source.DirectoryMetadata{
Path: "some/path",
Base: "some/base",
@ -78,9 +80,10 @@ func Test_toSourceModel(t *testing.T) {
{
name: "file",
src: source.Description{
ID: "test-id",
Name: "some-name",
Version: "some-version",
ID: "test-id",
Name: "some-name",
Version: "some-version",
Supplier: "optional-supplier",
Metadata: source.FileMetadata{
Path: "some/path",
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
@ -88,10 +91,11 @@ func Test_toSourceModel(t *testing.T) {
},
},
expected: model.Source{
ID: "test-id",
Name: "some-name",
Version: "some-version",
Type: "file",
ID: "test-id",
Name: "some-name",
Version: "some-version",
Supplier: "optional-supplier",
Type: "file",
Metadata: source.FileMetadata{
Path: "some/path",
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},

View File

@ -315,6 +315,7 @@ func toSyftSourceData(s model.Source) *source.Description {
ID: s.ID,
Name: s.Name,
Version: s.Version,
Supplier: s.Supplier,
Metadata: s.Metadata,
}
}

View File

@ -1,8 +1,9 @@
package source
type Alias struct {
Name string `json:"name" yaml:"name" mapstructure:"name"`
Version string `json:"version" yaml:"version" mapstructure:"version"`
Name string `json:"name" yaml:"name" mapstructure:"name"`
Version string `json:"version" yaml:"version" mapstructure:"version"`
Supplier string `json:"supplier" yaml:"supplier" mapstructure:"supplier"`
}
func (a *Alias) IsEmpty() bool {

View File

@ -5,5 +5,6 @@ type Description struct {
ID string `hash:"ignore"` // the id generated from the parent source struct
Name string `hash:"ignore"`
Version string `hash:"ignore"`
Supplier string `hash:"ignore"`
Metadata interface{}
}

View File

@ -112,6 +112,7 @@ func (s directorySource) ID() artifact.ID {
func (s directorySource) Describe() source.Description {
name := cleanDirPath(s.config.Path, s.config.Base)
version := ""
supplier := ""
if !s.config.Alias.IsEmpty() {
a := s.config.Alias
if a.Name != "" {
@ -120,11 +121,15 @@ func (s directorySource) Describe() source.Description {
if a.Version != "" {
version = a.Version
}
if a.Supplier != "" {
supplier = a.Supplier
}
}
return source.Description{
ID: string(s.id),
Name: name,
Version: version,
ID: string(s.id),
Name: name,
Version: version,
Supplier: supplier,
Metadata: source.DirectoryMetadata{
Path: s.config.Path,
Base: s.config.Base,

View File

@ -475,12 +475,13 @@ func Test_DirectorySource_ID(t *testing.T) {
wantErr: require.Error,
},
{
name: "to dir with name and version",
name: "to dir with name and version and supplier",
cfg: Config{
Path: "./test-fixtures",
Alias: source.Alias{
Name: "name-me-that!",
Version: "version-me-this!",
Name: "name-me-that!",
Version: "version-me-this!",
Supplier: "some-supplier",
},
},
want: artifact.ID("51a5f2a1536cf4b5220d4247814b07eec5862ab0547050f90e9ae216548ded7e"),
@ -490,8 +491,9 @@ func Test_DirectorySource_ID(t *testing.T) {
cfg: Config{
Path: "./test-fixtures/image-simple",
Alias: source.Alias{
Name: "name-me-that!",
Version: "version-me-this!",
Name: "name-me-that!",
Version: "version-me-this!",
Supplier: "some-supplier",
},
},
// note: this must match the previous value because the alias should trump the path info

View File

@ -123,6 +123,7 @@ func (s fileSource) ID() artifact.ID {
func (s fileSource) Describe() source.Description {
name := path.Base(s.config.Path)
version := s.digestForVersion
supplier := ""
if !s.config.Alias.IsEmpty() {
a := s.config.Alias
if a.Name != "" {
@ -132,11 +133,16 @@ func (s fileSource) Describe() source.Description {
if a.Version != "" {
version = a.Version
}
if a.Supplier != "" {
supplier = a.Supplier
}
}
return source.Description{
ID: string(s.id),
Name: name,
Version: version,
ID: string(s.id),
Name: name,
Version: version,
Supplier: supplier,
Metadata: source.FileMetadata{
Path: s.config.Path,
Digests: s.digests,

View File

@ -51,6 +51,7 @@ func (s stereoscopeImageSource) Describe() source.Description {
a := s.config.Alias
name := a.Name
supplier := a.Supplier
nameIfUnset := func(n string) {
if name != "" {
return
@ -90,6 +91,7 @@ func (s stereoscopeImageSource) Describe() source.Description {
ID: string(s.id),
Name: name,
Version: version,
Supplier: supplier,
Metadata: s.metadata,
}
}

View File

@ -71,6 +71,21 @@ func TestPackagesCmdFlags(t *testing.T) {
assertSuccessfulReturnCode,
},
},
{
name: "source flags override bom metadata",
args: []string{
"scan",
"--source-name", "custom-name",
"--source-version", "custom-version",
"--source-supplier", "custom-supplier",
"-o", "json", coverageImage},
assertions: []traitAssertion{
assertInOutput("custom-name"),
assertInOutput("custom-version"),
assertInOutput("custom-supplier"),
assertSuccessfulReturnCode,
},
},
// I haven't been able to reproduce locally yet, but in CI this has proven to be unstable:
// For the same commit:
// pass: https://github.com/anchore/syft/runs/4611344142?check_suite_focus=true