Populate Files and Relationship fields for spdx-json output (#507)

* update spdx22 Document model to include relationships field

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* update document and relationship to match current JSON spec
https://github.com/spdx/spdx-spec/blob/development/v2.2.1/schemas/spdx-schema.json
https://github.com/spdx/spdx-spec/pull/528
https://github.com/spdx/spdx-spec/pull/528#issuecomment-904180177

Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>

* update File struct based on SPDX schema

Required fields:
[ "SPDXID", "fileName", "copyrightText", "licenseConcluded" ]
Signed-off-by: Christopher Angelo Phillips <christopher.phillips@anchore.com>
This commit is contained in:
Christopher Angelo Phillips 2021-09-17 09:06:12 -04:00 committed by GitHub
parent 9fe1da8ee6
commit 93d00dc340
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 139 additions and 48 deletions

View File

@ -40,4 +40,6 @@ type Document struct {
Files []File `json:"files,omitempty"` Files []File `json:"files,omitempty"`
// Snippets referenced in the SPDX document // Snippets referenced in the SPDX document
Snippets []Snippet `json:"snippets,omitempty"` Snippets []Snippet `json:"snippets,omitempty"`
// Relationships referenced in the SPDX document
Relationships []Relationship `json:"relationships,omitempty"`
} }

View File

@ -20,22 +20,22 @@ type File struct {
Item Item
// (At least one is required.) The checksum property provides a mechanism that can be used to verify that the // (At least one is required.) The checksum property provides a mechanism that can be used to verify that the
// contents of a File or Package have not changed. // contents of a File or Package have not changed.
Checksums []Checksum `json:"checksums"` Checksums []Checksum `json:"checksums,omitempty"`
// This field provides a place for the SPDX file creator to record file contributors. Contributors could include // This field provides a place for the SPDX file creator to record file contributors. Contributors could include
// names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content. // names of copyright holders and/or authors who may not be copyright holders yet contributed to the file content.
FileContributors []string `json:"fileContributors"` FileContributors []string `json:"fileContributors,omitempty"`
// Each element is a SPDX ID for a File. // Each element is a SPDX ID for a File.
FileDependencies []string `json:"fileDependencies"` FileDependencies []string `json:"fileDependencies,omitempty"`
// The name of the file relative to the root of the package. // The name of the file relative to the root of the package.
FileName string `json:"fileName"` FileName string `json:"fileName"`
// The type of the file // The type of the file
FileTypes []string `json:"fileTypes"` FileTypes []string `json:"fileTypes,omitempty"`
// This field provides a place for the SPDX file creator to record potential legal notices found in the file. // This field provides a place for the SPDX file creator to record potential legal notices found in the file.
// This may or may not include copyright statements. // This may or may not include copyright statements.
NoticeText string `json:"noticeText,omitempty"` NoticeText string `json:"noticeText,omitempty"`
// Indicates the project in which the SpdxElement originated. Tools must preserve doap:homepage and doap:name // Indicates the project in which the SpdxElement originated. Tools must preserve doap:homepage and doap:name
// properties and the URI (if one is known) of doap:Project resources that are values of this property. All other // properties and the URI (if one is known) of doap:Project resources that are values of this property. All other
// properties of doap:Projects are not directly supported by SPDX and may be dropped when translating to or // properties of doap:Projects are not directly supported by SPDX and may be dropped when translating to or
// from some SPDX formats. // from some SPDX formats(deprecated).
ArtifactOf []string `json:"artifactOf"` ArtifactOf []string `json:"artifactOf,omitempty"`
} }

View File

@ -1,10 +1,12 @@
package spdx22 package spdx22
type Relationship struct { type Relationship struct {
// SPDX ID for SpdxElement. A related SpdxElement. // Id to which the SPDX element is related
RelatedSpdxElement string `json:"relatedSpdxElement"` SpdxElementID string `json:"spdxElementId"`
// Describes the type of relationship between two SPDX elements. // Describes the type of relationship between two SPDX elements.
RelationshipType RelationshipType `json:"relationshipType"` RelationshipType RelationshipType `json:"relationshipType"`
// SPDX ID for SpdxElement. A related SpdxElement.
RelatedSpdxElement string `json:"relatedSpdxElement"`
Comment string `json:"comment,omitempty"` Comment string `json:"comment,omitempty"`
} }

View File

@ -1,7 +1,9 @@
package packages package packages
import ( import (
"crypto/sha256"
"fmt" "fmt"
"path/filepath"
"strings" "strings"
"github.com/anchore/syft/internal/presenter/packages/model/spdx22" "github.com/anchore/syft/internal/presenter/packages/model/spdx22"
@ -29,6 +31,43 @@ func getSPDXExternalRefs(p *pkg.Package) (externalRefs []spdx22.ExternalRef) {
return externalRefs return externalRefs
} }
func getSPDXFiles(packageSpdxID string, p *pkg.Package) (files []spdx22.File, fileIDs []string, relationships []spdx22.Relationship) {
files = make([]spdx22.File, 0)
fileIDs = make([]string, 0)
relationships = make([]spdx22.Relationship, 0)
pkgFileOwner, ok := p.Metadata.(pkg.FileOwner)
if !ok {
return files, fileIDs, relationships
}
for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
baseFileName := filepath.Base(ownedFilePath)
pathHash := sha256.Sum256([]byte(ownedFilePath))
fileSpdxID := spdx22.ElementID(fmt.Sprintf("File-%s-%x", p.Name, pathHash)).String()
fileIDs = append(fileIDs, fileSpdxID)
files = append(files, spdx22.File{
FileName: ownedFilePath,
Item: spdx22.Item{
Element: spdx22.Element{
SPDXID: fileSpdxID,
Name: baseFileName,
},
},
})
relationships = append(relationships, spdx22.Relationship{
SpdxElementID: packageSpdxID,
RelationshipType: spdx22.ContainsRelationship,
RelatedSpdxElement: fileSpdxID,
})
}
return files, fileIDs, relationships
}
func getSPDXLicense(p *pkg.Package) string { func getSPDXLicense(p *pkg.Package) string {
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license // source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
// The options to populate this field are limited to: // The options to populate this field are limited to:

View File

@ -39,6 +39,7 @@ func (pres *SPDXJsonPresenter) Present(output io.Writer) error {
return enc.Encode(&doc) return enc.Encode(&doc)
} }
// newSPDXJsonDocument creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx22.Document { func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx22.Document {
var name string var name string
switch srcMetadata.Scheme { switch srcMetadata.Scheme {
@ -48,6 +49,8 @@ func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx
name = srcMetadata.Path name = srcMetadata.Path
} }
packages, files, relationships := newSPDXJsonElements(catalog)
return spdx22.Document{ return spdx22.Document{
Element: spdx22.Element{ Element: spdx22.Element{
SPDXID: spdx22.ElementID("DOCUMENT").String(), SPDXID: spdx22.ElementID("DOCUMENT").String(),
@ -65,22 +68,34 @@ func newSPDXJsonDocument(catalog *pkg.Catalog, srcMetadata source.Metadata) spdx
}, },
DataLicense: "CC0-1.0", DataLicense: "CC0-1.0",
DocumentNamespace: fmt.Sprintf("https://anchore.com/syft/image/%s", srcMetadata.ImageMetadata.UserInput), DocumentNamespace: fmt.Sprintf("https://anchore.com/syft/image/%s", srcMetadata.ImageMetadata.UserInput),
Packages: newSPDXJsonPackages(catalog), Packages: packages,
Files: files,
Relationships: relationships,
} }
} }
func newSPDXJsonPackages(catalog *pkg.Catalog) []spdx22.Package { func newSPDXJsonElements(catalog *pkg.Catalog) ([]spdx22.Package, []spdx22.File, []spdx22.Relationship) {
results := make([]spdx22.Package, 0) packages := make([]spdx22.Package, 0)
relationships := make([]spdx22.Relationship, 0)
files := make([]spdx22.File, 0)
for _, p := range catalog.Sorted() { for _, p := range catalog.Sorted() {
license := getSPDXLicense(p) license := getSPDXLicense(p)
packageSpdxID := spdx22.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String()
packageFiles, fileIDs, packageFileRelationships := getSPDXFiles(packageSpdxID, p)
files = append(files, packageFiles...)
relationships = append(relationships, packageFileRelationships...)
// note: the license concluded and declared should be the same since we are collecting license information // note: the license concluded and declared should be the same since we are collecting license information
// from the project data itself (the installed package files). // from the project data itself (the installed package files).
results = append(results, spdx22.Package{ packages = append(packages, spdx22.Package{
Description: getSPDXDescription(p), Description: getSPDXDescription(p),
DownloadLocation: getSPDXDownloadLocation(p), DownloadLocation: getSPDXDownloadLocation(p),
ExternalRefs: getSPDXExternalRefs(p), ExternalRefs: getSPDXExternalRefs(p),
FilesAnalyzed: false, FilesAnalyzed: false,
HasFiles: fileIDs,
Homepage: getSPDXHomepage(p), Homepage: getSPDXHomepage(p),
LicenseDeclared: license, // The Declared License is what the authors of a project believe govern the package LicenseDeclared: license, // The Declared License is what the authors of a project believe govern the package
Originator: getSPDXOriginator(p), Originator: getSPDXOriginator(p),
@ -89,11 +104,12 @@ func newSPDXJsonPackages(catalog *pkg.Catalog) []spdx22.Package {
Item: spdx22.Item{ Item: spdx22.Item{
LicenseConcluded: license, // The Concluded License field is the license the SPDX file creator believes governs the package LicenseConcluded: license, // The Concluded License field is the license the SPDX file creator believes governs the package
Element: spdx22.Element{ Element: spdx22.Element{
SPDXID: spdx22.ElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.Version)).String(), SPDXID: packageSpdxID,
Name: p.Name, Name: p.Name,
}, },
}, },
}) })
} }
return results
return packages, files, relationships
} }

View File

@ -27,6 +27,11 @@
"author": "", "author": "",
"authorEmail": "", "authorEmail": "",
"platform": "", "platform": "",
"files": [
{
"path": "/some/path/pkg1/depedencies/foo"
}
],
"sitePackagesRootPath": "" "sitePackagesRootPath": ""
} }
}, },

View File

@ -9,7 +9,7 @@
"locations": [ "locations": [
{ {
"path": "/somefile-1.txt", "path": "/somefile-1.txt",
"layerID": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59" "layerID": "sha256:ffb5e9eaa453a002110719d12c294960117ca2903953d1faa40f01dc3f77045c"
} }
], ],
"licenses": [ "licenses": [
@ -40,7 +40,7 @@
"locations": [ "locations": [
{ {
"path": "/somefile-2.txt", "path": "/somefile-2.txt",
"layerID": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec" "layerID": "sha256:8463854829fc53d47b9dcdf7ee79fe7eb4ca7933c910f67f8521412f7a2f5c21"
} }
], ],
"licenses": [], "licenses": [],
@ -67,7 +67,7 @@
"type": "image", "type": "image",
"target": { "target": {
"userInput": "user-image-input", "userInput": "user-image-input",
"imageID": "sha256:5900c94a5bc1e083aa24ad1a223bf6eb9910dc8a6b01cb979ec306cb91709ea1", "imageID": "sha256:112851310e48e604f7379e2a3acddab50e91ce926edacb598a532e60ff6b776a",
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368", "manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json", "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"tags": [ "tags": [
@ -77,17 +77,17 @@
"layers": [ "layers": [
{ {
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"digest": "sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59", "digest": "sha256:ffb5e9eaa453a002110719d12c294960117ca2903953d1faa40f01dc3f77045c",
"size": 22 "size": 22
}, },
{ {
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"digest": "sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec", "digest": "sha256:8463854829fc53d47b9dcdf7ee79fe7eb4ca7933c910f67f8521412f7a2f5c21",
"size": 16 "size": 16
} }
], ],
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NjUsImRpZ2VzdCI6InNoYTI1Njo1OTAwYzk0YTViYzFlMDgzYWEyNGFkMWEyMjNiZjZlYjk5MTBkYzhhNmIwMWNiOTc5ZWMzMDZjYjkxNzA5ZWExIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmYjZiZWVjYjc1YjM5ZjRiYjgxM2RiZjE3N2U1MDFlZGQ1ZGRiM2U2OWJiNDVjZWRlYjc4YzY3NmVlMWI3YTU5In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjMxOWI1ODhjZTY0MjUzYTg3YjUzM2M4ZWQwMWNmMDAyNWUwZWFjOThlN2I1MTZlMTI1MzI5NTdlMTI0NGZkZWMifV19", "manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1NjoxMTI4NTEzMTBlNDhlNjA0ZjczNzllMmEzYWNkZGFiNTBlOTFjZTkyNmVkYWNiNTk4YTUzMmU2MGZmNmI3NzZhIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjpmZmI1ZTllYWE0NTNhMDAyMTEwNzE5ZDEyYzI5NDk2MDExN2NhMjkwMzk1M2QxZmFhNDBmMDFkYzNmNzcwNDVjIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2Ojg0NjM4NTQ4MjlmYzUzZDQ3YjlkY2RmN2VlNzlmZTdlYjRjYTc5MzNjOTEwZjY3Zjg1MjE0MTJmN2EyZjVjMjEifV19",
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMDQtMDZUMTk6MTM6NTIuNTI0Mzc4WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIxLTA0LTA2VDE5OjEzOjUyLjQ1ODIwNjFaIiwiY3JlYXRlZF9ieSI6IkFERCBmaWxlLTEudHh0IC9zb21lZmlsZS0xLnR4dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifSx7ImNyZWF0ZWQiOiIyMDIxLTA0LTA2VDE5OjEzOjUyLjUyNDM3OFoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMi50eHQgL3NvbWVmaWxlLTIudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9XSwib3MiOiJsaW51eCIsInJvb3RmcyI6eyJ0eXBlIjoibGF5ZXJzIiwiZGlmZl9pZHMiOlsic2hhMjU2OmZiNmJlZWNiNzViMzlmNGJiODEzZGJmMTc3ZTUwMWVkZDVkZGIzZTY5YmI0NWNlZGViNzhjNjc2ZWUxYjdhNTkiLCJzaGEyNTY6MzE5YjU4OGNlNjQyNTNhODdiNTMzYzhlZDAxY2YwMDI1ZTBlYWM5OGU3YjUxNmUxMjUzMjk1N2UxMjQ0ZmRlYyJdfX0=", "config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjEtMDktMDhUMTc6MjE6NTguODk2NTI5MTkyWiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIxLTA5LTA4VDE3OjIxOjU4Ljg3OTY5MDgyNFoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjEtMDktMDhUMTc6MjE6NTguODk2NTI5MTkyWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6ZmZiNWU5ZWFhNDUzYTAwMjExMDcxOWQxMmMyOTQ5NjAxMTdjYTI5MDM5NTNkMWZhYTQwZjAxZGMzZjc3MDQ1YyIsInNoYTI1Njo4NDYzODU0ODI5ZmM1M2Q0N2I5ZGNkZjdlZTc5ZmU3ZWI0Y2E3OTMzYzkxMGY2N2Y4NTIxNDEyZjdhMmY1YzIxIl19fQ==",
"repoDigests": [], "repoDigests": [],
"scope": "Squashed" "scope": "Squashed"
} }

View File

@ -3,12 +3,12 @@
"name": "/some/path", "name": "/some/path",
"spdxVersion": "SPDX-2.2", "spdxVersion": "SPDX-2.2",
"creationInfo": { "creationInfo": {
"created": "2021-06-23T17:48:32.734847Z", "created": "2021-09-16T20:44:35.198887Z",
"creators": [ "creators": [
"Organization: Anchore, Inc", "Organization: Anchore, Inc",
"Tool: syft-[not provided]" "Tool: syft-[not provided]"
], ],
"licenseListVersion": "3.13" "licenseListVersion": "3.14"
}, },
"dataLicense": "CC0-1.0", "dataLicense": "CC0-1.0",
"documentNamespace": "https://anchore.com/syft/image/", "documentNamespace": "https://anchore.com/syft/image/",
@ -31,6 +31,9 @@
} }
], ],
"filesAnalyzed": false, "filesAnalyzed": false,
"hasFiles": [
"SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9"
],
"licenseDeclared": "MIT", "licenseDeclared": "MIT",
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1", "sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
"versionInfo": "1.0.1" "versionInfo": "1.0.1"
@ -57,5 +60,20 @@
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1", "sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
"versionInfo": "2.0.1" "versionInfo": "2.0.1"
} }
],
"files": [
{
"SPDXID": "SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9",
"name": "foo",
"licenseConcluded": "",
"fileName": "/some/path/pkg1/depedencies/foo"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-python-package-1-1.0.1",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-File-package-1-04cd22424378dcd6c77fce08beb52493b5494a60ea5e1f9bdf9b16dc0cacffe9"
}
] ]
} }

View File

@ -3,12 +3,12 @@
"name": "user-image-input", "name": "user-image-input",
"spdxVersion": "SPDX-2.2", "spdxVersion": "SPDX-2.2",
"creationInfo": { "creationInfo": {
"created": "2021-06-23T17:48:32.7379Z", "created": "2021-09-16T20:44:35.203911Z",
"creators": [ "creators": [
"Organization: Anchore, Inc", "Organization: Anchore, Inc",
"Tool: syft-[not provided]" "Tool: syft-[not provided]"
], ],
"licenseListVersion": "3.13" "licenseListVersion": "3.14"
}, },
"dataLicense": "CC0-1.0", "dataLicense": "CC0-1.0",
"documentNamespace": "https://anchore.com/syft/image/user-image-input", "documentNamespace": "https://anchore.com/syft/image/user-image-input",

View File

@ -1,11 +1,11 @@
[Image] [Image]
Layer: 0 Layer: 0
Digest: sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59 Digest: sha256:ffb5e9eaa453a002110719d12c294960117ca2903953d1faa40f01dc3f77045c
Size: 22 Size: 22
MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip
Layer: 1 Layer: 1
Digest: sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec Digest: sha256:8463854829fc53d47b9dcdf7ee79fe7eb4ca7933c910f67f8521412f7a2f5c21
Size: 16 Size: 16
MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip MediaType: application/vnd.docker.image.rootfs.diff.tar.gzip

View File

@ -158,6 +158,11 @@ func presenterDirectoryInput(t testing.TB) (*pkg.Catalog, source.Metadata, *dist
Metadata: pkg.PythonPackageMetadata{ Metadata: pkg.PythonPackageMetadata{
Name: "package-1", Name: "package-1",
Version: "1.0.1", Version: "1.0.1",
Files: []pkg.PythonFileRecord{
{
Path: "/some/path/pkg1/depedencies/foo",
},
},
}, },
PURL: "a-purl-2", PURL: "a-purl-2",
CPEs: []pkg.CPE{ CPEs: []pkg.CPE{

View File

@ -11,7 +11,7 @@ import (
const ApkDbGlob = "**/lib/apk/db/installed" const ApkDbGlob = "**/lib/apk/db/installed"
var _ fileOwner = (*ApkMetadata)(nil) var _ FileOwner = (*ApkMetadata)(nil)
// ApkMetadata represents all captured data for a Alpine DB package entry. // ApkMetadata represents all captured data for a Alpine DB package entry.
// See the following sources for more information: // See the following sources for more information:
@ -63,7 +63,7 @@ func (m ApkMetadata) PackageURL() string {
return pURL.ToString() return pURL.ToString()
} }
func (m ApkMetadata) ownedFiles() (result []string) { func (m ApkMetadata) OwnedFiles() (result []string) {
s := strset.New() s := strset.New()
for _, f := range m.Files { for _, f := range m.Files {
if f.Path != "" { if f.Path != "" {

View File

@ -73,7 +73,7 @@ func TestApkMetadata_pURL(t *testing.T) {
} }
} }
func TestApkMetadata_fileOwner(t *testing.T) { func TestApkMetadata_FileOwner(t *testing.T) {
tests := []struct { tests := []struct {
metadata ApkMetadata metadata ApkMetadata
expected []string expected []string
@ -107,7 +107,7 @@ func TestApkMetadata_fileOwner(t *testing.T) {
t.Run(strings.Join(test.expected, ","), func(t *testing.T) { t.Run(strings.Join(test.expected, ","), func(t *testing.T) {
var i interface{} var i interface{}
i = test.metadata i = test.metadata
actual := i.(fileOwner).ownedFiles() actual := i.(FileOwner).OwnedFiles()
for _, d := range deep.Equal(test.expected, actual) { for _, d := range deep.Equal(test.expected, actual) {
t.Errorf("diff: %+v", d) t.Errorf("diff: %+v", d)
} }

View File

@ -12,7 +12,7 @@ import (
const DpkgDbGlob = "**/var/lib/dpkg/{status,status.d/**}" const DpkgDbGlob = "**/var/lib/dpkg/{status,status.d/**}"
var _ fileOwner = (*DpkgMetadata)(nil) var _ FileOwner = (*DpkgMetadata)(nil)
// DpkgMetadata represents all captured data for a Debian package DB entry; available fields are described // DpkgMetadata represents all captured data for a Debian package DB entry; available fields are described
// at http://manpages.ubuntu.com/manpages/xenial/man1/dpkg-query.1.html in the --showformat section. // at http://manpages.ubuntu.com/manpages/xenial/man1/dpkg-query.1.html in the --showformat section.
@ -55,7 +55,7 @@ func (m DpkgMetadata) PackageURL(d *distro.Distro) string {
return pURL.ToString() return pURL.ToString()
} }
func (m DpkgMetadata) ownedFiles() (result []string) { func (m DpkgMetadata) OwnedFiles() (result []string) {
s := strset.New() s := strset.New()
for _, f := range m.Files { for _, f := range m.Files {
if f.Path != "" { if f.Path != "" {

View File

@ -54,7 +54,7 @@ func TestDpkgMetadata_pURL(t *testing.T) {
} }
} }
func TestDpkgMetadata_fileOwner(t *testing.T) { func TestDpkgMetadata_FileOwner(t *testing.T) {
tests := []struct { tests := []struct {
metadata DpkgMetadata metadata DpkgMetadata
expected []string expected []string
@ -88,7 +88,7 @@ func TestDpkgMetadata_fileOwner(t *testing.T) {
t.Run(strings.Join(test.expected, ","), func(t *testing.T) { t.Run(strings.Join(test.expected, ","), func(t *testing.T) {
var i interface{} var i interface{}
i = test.metadata i = test.metadata
actual := i.(fileOwner).ownedFiles() actual := i.(FileOwner).OwnedFiles()
for _, d := range deep.Equal(test.expected, actual) { for _, d := range deep.Equal(test.expected, actual) {
t.Errorf("diff: %+v", d) t.Errorf("diff: %+v", d)
} }

View File

@ -1,5 +1,9 @@
package pkg package pkg
type fileOwner interface { // FileOwner is the interface that wraps OwnedFiles method.
ownedFiles() []string //
// OwnedFiles returns a list of files that a piece of
// package Metadata indicates are owned by the package.
type FileOwner interface {
OwnedFiles() []string
} }

View File

@ -55,11 +55,11 @@ func findOwnershipByFilesRelationships(catalog *Catalog) map[ID]map[ID]*strset.S
} }
// check to see if this is a file owner // check to see if this is a file owner
pkgFileOwner, ok := candidateOwnerPkg.Metadata.(fileOwner) pkgFileOwner, ok := candidateOwnerPkg.Metadata.(FileOwner)
if !ok { if !ok {
continue continue
} }
for _, ownedFilePath := range pkgFileOwner.ownedFiles() { for _, ownedFilePath := range pkgFileOwner.OwnedFiles() {
if matchesAny(ownedFilePath, globsForbiddenFromBeingOwned) { if matchesAny(ownedFilePath, globsForbiddenFromBeingOwned) {
// we skip over known exceptions to file ownership, such as the RPM package owning // we skip over known exceptions to file ownership, such as the RPM package owning
// the RPM DB path, otherwise the RPM package would "own" all RPMs, which is not intended // the RPM DB path, otherwise the RPM package would "own" all RPMs, which is not intended

View File

@ -6,7 +6,7 @@ import (
"github.com/scylladb/go-set/strset" "github.com/scylladb/go-set/strset"
) )
var _ fileOwner = (*PythonPackageMetadata)(nil) var _ FileOwner = (*PythonPackageMetadata)(nil)
// PythonFileDigest represents the file metadata for a single file attributed to a python package. // PythonFileDigest represents the file metadata for a single file attributed to a python package.
type PythonFileDigest struct { type PythonFileDigest struct {
@ -34,7 +34,7 @@ type PythonPackageMetadata struct {
TopLevelPackages []string `json:"topLevelPackages,omitempty"` TopLevelPackages []string `json:"topLevelPackages,omitempty"`
} }
func (m PythonPackageMetadata) ownedFiles() (result []string) { func (m PythonPackageMetadata) OwnedFiles() (result []string) {
s := strset.New() s := strset.New()
for _, f := range m.Files { for _, f := range m.Files {
if f.Path != "" { if f.Path != "" {

View File

@ -7,7 +7,7 @@ import (
"github.com/go-test/deep" "github.com/go-test/deep"
) )
func TestPythonMetadata_fileOwner(t *testing.T) { func TestPythonMetadata_FileOwner(t *testing.T) {
tests := []struct { tests := []struct {
metadata PythonPackageMetadata metadata PythonPackageMetadata
expected []string expected []string
@ -41,7 +41,7 @@ func TestPythonMetadata_fileOwner(t *testing.T) {
t.Run(strings.Join(test.expected, ","), func(t *testing.T) { t.Run(strings.Join(test.expected, ","), func(t *testing.T) {
var i interface{} var i interface{}
i = test.metadata i = test.metadata
actual := i.(fileOwner).ownedFiles() actual := i.(FileOwner).OwnedFiles()
for _, d := range deep.Equal(test.expected, actual) { for _, d := range deep.Equal(test.expected, actual) {
t.Errorf("diff: %+v", d) t.Errorf("diff: %+v", d)
} }

View File

@ -15,7 +15,7 @@ import (
const RpmDbGlob = "**/var/lib/rpm/Packages" const RpmDbGlob = "**/var/lib/rpm/Packages"
var _ fileOwner = (*RpmdbMetadata)(nil) var _ FileOwner = (*RpmdbMetadata)(nil)
// RpmdbMetadata represents all captured data for a RPM DB package entry. // RpmdbMetadata represents all captured data for a RPM DB package entry.
type RpmdbMetadata struct { type RpmdbMetadata struct {
@ -79,7 +79,7 @@ func (m RpmdbMetadata) PackageURL(d *distro.Distro) string {
return pURL.ToString() return pURL.ToString()
} }
func (m RpmdbMetadata) ownedFiles() (result []string) { func (m RpmdbMetadata) OwnedFiles() (result []string) {
s := strset.New() s := strset.New()
for _, f := range m.Files { for _, f := range m.Files {
if f.Path != "" { if f.Path != "" {

View File

@ -56,7 +56,7 @@ func TestRpmMetadata_pURL(t *testing.T) {
} }
} }
func TestRpmMetadata_fileOwner(t *testing.T) { func TestRpmMetadata_FileOwner(t *testing.T) {
tests := []struct { tests := []struct {
metadata RpmdbMetadata metadata RpmdbMetadata
expected []string expected []string
@ -90,7 +90,7 @@ func TestRpmMetadata_fileOwner(t *testing.T) {
t.Run(strings.Join(test.expected, ","), func(t *testing.T) { t.Run(strings.Join(test.expected, ","), func(t *testing.T) {
var i interface{} var i interface{}
i = test.metadata i = test.metadata
actual := i.(fileOwner).ownedFiles() actual := i.(FileOwner).OwnedFiles()
for _, d := range deep.Equal(test.expected, actual) { for _, d := range deep.Equal(test.expected, actual) {
t.Errorf("diff: %+v", d) t.Errorf("diff: %+v", d)
} }