mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
feat: add RelationshipsBySourceOwnership to syft json output (#1248)
This commit is contained in:
parent
fa0b3c0438
commit
89575199b8
16 changed files with 1677 additions and 36 deletions
|
@ -6,5 +6,5 @@ const (
|
||||||
|
|
||||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||||
JSONSchemaVersion = "4.0.0"
|
JSONSchemaVersion = "4.1.0"
|
||||||
)
|
)
|
||||||
|
|
1578
schema/json/schema-4.1.0.json
Normal file
1578
schema/json/schema-4.1.0.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -156,6 +156,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Source: source.Metadata{
|
Source: source.Metadata{
|
||||||
|
ID: "c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
|
||||||
Scheme: source.ImageScheme,
|
Scheme: source.ImageScheme,
|
||||||
ImageMetadata: source.ImageMetadata{
|
ImageMetadata: source.ImageMetadata{
|
||||||
UserInput: "user-image-input",
|
UserInput: "user-image-input",
|
||||||
|
|
|
@ -10,12 +10,14 @@ import (
|
||||||
|
|
||||||
// Source object represents the thing that was cataloged
|
// Source object represents the thing that was cataloged
|
||||||
type Source struct {
|
type Source struct {
|
||||||
|
ID string `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Target interface{} `json:"target"`
|
Target interface{} `json:"target"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// sourceUnpacker is used to unmarshal Source objects
|
// sourceUnpacker is used to unmarshal Source objects
|
||||||
type sourceUnpacker struct {
|
type sourceUnpacker struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Target json.RawMessage `json:"target"`
|
Target json.RawMessage `json:"target"`
|
||||||
}
|
}
|
||||||
|
@ -28,6 +30,7 @@ func (s *Source) UnmarshalJSON(b []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Type = unpacker.Type
|
s.Type = unpacker.Type
|
||||||
|
s.ID = unpacker.ID
|
||||||
|
|
||||||
switch s.Type {
|
switch s.Type {
|
||||||
case "directory", "file":
|
case "directory", "file":
|
||||||
|
|
|
@ -20,10 +20,12 @@ func TestSource_UnmarshalJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "directory",
|
name: "directory",
|
||||||
input: []byte(`{
|
input: []byte(`{
|
||||||
|
"id": "foobar",
|
||||||
"type": "directory",
|
"type": "directory",
|
||||||
"target":"/var/lib/foo"
|
"target":"/var/lib/foo"
|
||||||
}`),
|
}`),
|
||||||
expectedSource: &Source{
|
expectedSource: &Source{
|
||||||
|
ID: "foobar",
|
||||||
Type: "directory",
|
Type: "directory",
|
||||||
Target: "/var/lib/foo",
|
Target: "/var/lib/foo",
|
||||||
},
|
},
|
||||||
|
@ -32,6 +34,7 @@ func TestSource_UnmarshalJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "image",
|
name: "image",
|
||||||
input: []byte(`{
|
input: []byte(`{
|
||||||
|
"id": "foobar",
|
||||||
"type": "image",
|
"type": "image",
|
||||||
"target": {
|
"target": {
|
||||||
"userInput": "alpine:3.10",
|
"userInput": "alpine:3.10",
|
||||||
|
@ -55,6 +58,7 @@ func TestSource_UnmarshalJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
}`),
|
}`),
|
||||||
expectedSource: &Source{
|
expectedSource: &Source{
|
||||||
|
ID: "foobar",
|
||||||
Type: "image",
|
Type: "image",
|
||||||
Target: source.ImageMetadata{
|
Target: source.ImageMetadata{
|
||||||
UserInput: "alpine:3.10",
|
UserInput: "alpine:3.10",
|
||||||
|
@ -98,10 +102,12 @@ func TestSource_UnmarshalJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "file",
|
name: "file",
|
||||||
input: []byte(`{
|
input: []byte(`{
|
||||||
|
"id": "foobar",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"target":"/var/lib/foo/go.mod"
|
"target":"/var/lib/foo/go.mod"
|
||||||
}`),
|
}`),
|
||||||
expectedSource: &Source{
|
expectedSource: &Source{
|
||||||
|
ID: "foobar",
|
||||||
Type: "file",
|
Type: "file",
|
||||||
Target: "/var/lib/foo/go.mod",
|
Target: "/var/lib/foo/go.mod",
|
||||||
},
|
},
|
||||||
|
@ -110,10 +116,12 @@ func TestSource_UnmarshalJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "unknown source type",
|
name: "unknown source type",
|
||||||
input: []byte(`{
|
input: []byte(`{
|
||||||
|
"id": "foobar",
|
||||||
"type": "unknown-thing",
|
"type": "unknown-thing",
|
||||||
"target":"/var/lib/foo"
|
"target":"/var/lib/foo"
|
||||||
}`),
|
}`),
|
||||||
expectedSource: &Source{
|
expectedSource: &Source{
|
||||||
|
ID: "foobar",
|
||||||
Type: "unknown-thing",
|
Type: "unknown-thing",
|
||||||
},
|
},
|
||||||
errAssertion: assert.Error,
|
errAssertion: assert.Error,
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
],
|
],
|
||||||
"artifactRelationships": [],
|
"artifactRelationships": [],
|
||||||
"source": {
|
"source": {
|
||||||
|
"id": "eda6cf0b63f1a1d2eaf7792a2a98c832c21a18e6992bcebffe6381781cc85cbc",
|
||||||
"type": "directory",
|
"type": "directory",
|
||||||
"target": "/some/path"
|
"target": "/some/path"
|
||||||
},
|
},
|
||||||
|
@ -88,7 +89,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-4.0.0.json"
|
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-4.1.0.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,6 +139,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": {
|
"source": {
|
||||||
|
"id": "c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
|
||||||
"type": "image",
|
"type": "image",
|
||||||
"target": {
|
"target": {
|
||||||
"userInput": "user-image-input",
|
"userInput": "user-image-input",
|
||||||
|
@ -184,7 +185,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-4.0.0.json"
|
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-4.1.0.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"locations": [
|
"locations": [
|
||||||
{
|
{
|
||||||
"path": "/somefile-1.txt",
|
"path": "/somefile-1.txt",
|
||||||
"layerID": "sha256:7ef28e9c2d56471ee090b578a678bdf28c3b5a311ca7b2e28c2a4185e5bb34c0"
|
"layerID": "sha256:4965affaf42a7174561882c5fd87e2db6f0b07df532459ba86f98a8bd2af11de"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [
|
"licenses": [
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
"locations": [
|
"locations": [
|
||||||
{
|
{
|
||||||
"path": "/somefile-2.txt",
|
"path": "/somefile-2.txt",
|
||||||
"layerID": "sha256:86da8aee621161bea2efaf27a2709ddab5e7d44e30ecdfda728b02c03a28fd98"
|
"layerID": "sha256:460c3e27be163efe75df048c4d4cf3a22e7e363f02521fa2e82a3bd257a682d4"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"licenses": [],
|
"licenses": [],
|
||||||
|
@ -64,10 +64,11 @@
|
||||||
],
|
],
|
||||||
"artifactRelationships": [],
|
"artifactRelationships": [],
|
||||||
"source": {
|
"source": {
|
||||||
|
"id": "00afea0209d754683fdfcdd47cfea94ec9f2e81286be444e297a8c776c4accbf",
|
||||||
"type": "image",
|
"type": "image",
|
||||||
"target": {
|
"target": {
|
||||||
"userInput": "user-image-input",
|
"userInput": "user-image-input",
|
||||||
"imageID": "sha256:5dd5f5f4247e4e946f555f0de7681a631a5240b614e52717d0aed04808e8c65f",
|
"imageID": "sha256:6b1b476e6dc187bb688566606cf7a59d7804d81169967d8c6bb121627b0a387f",
|
||||||
"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 +78,17 @@
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
"digest": "sha256:7ef28e9c2d56471ee090b578a678bdf28c3b5a311ca7b2e28c2a4185e5bb34c0",
|
"digest": "sha256:4965affaf42a7174561882c5fd87e2db6f0b07df532459ba86f98a8bd2af11de",
|
||||||
"size": 22
|
"size": 22
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
"digest": "sha256:86da8aee621161bea2efaf27a2709ddab5e7d44e30ecdfda728b02c03a28fd98",
|
"digest": "sha256:460c3e27be163efe75df048c4d4cf3a22e7e363f02521fa2e82a3bd257a682d4",
|
||||||
"size": 16
|
"size": 16
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1Njo1ZGQ1ZjVmNDI0N2U0ZTk0NmY1NTVmMGRlNzY4MWE2MzFhNTI0MGI2MTRlNTI3MTdkMGFlZDA0ODA4ZThjNjVmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1Njo3ZWYyOGU5YzJkNTY0NzFlZTA5MGI1NzhhNjc4YmRmMjhjM2I1YTMxMWNhN2IyZTI4YzJhNDE4NWU1YmIzNGMwIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2Ojg2ZGE4YWVlNjIxMTYxYmVhMmVmYWYyN2EyNzA5ZGRhYjVlN2Q0NGUzMGVjZGZkYTcyOGIwMmMwM2EyOGZkOTgifV19",
|
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1Njo2YjFiNDc2ZTZkYzE4N2JiNjg4NTY2NjA2Y2Y3YTU5ZDc4MDRkODExNjk5NjdkOGM2YmIxMjE2MjdiMGEzODdmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1Njo0OTY1YWZmYWY0MmE3MTc0NTYxODgyYzVmZDg3ZTJkYjZmMGIwN2RmNTMyNDU5YmE4NmY5OGE4YmQyYWYxMWRlIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OjQ2MGMzZTI3YmUxNjNlZmU3NWRmMDQ4YzRkNGNmM2EyMmU3ZTM2M2YwMjUyMWZhMmU4MmEzYmQyNTdhNjgyZDQifV19",
|
||||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjItMDYtMDJUMTQ6MzQ6MzQuNzE5MTM1MTc0WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIyLTA2LTAyVDE0OjM0OjM0LjY4NjkzMzI2M1oiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjItMDYtMDJUMTQ6MzQ6MzQuNzE5MTM1MTc0WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6N2VmMjhlOWMyZDU2NDcxZWUwOTBiNTc4YTY3OGJkZjI4YzNiNWEzMTFjYTdiMmUyOGMyYTQxODVlNWJiMzRjMCIsInNoYTI1Njo4NmRhOGFlZTYyMTE2MWJlYTJlZmFmMjdhMjcwOWRkYWI1ZTdkNDRlMzBlY2RmZGE3MjhiMDJjMDNhMjhmZDk4Il19fQ==",
|
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjItMTAtMDVUMTQ6MjQ6NTguNzc0NTY2MjM2WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIyLTEwLTA1VDE0OjI0OjU4Ljc0NDY3NTEyOVoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjItMTAtMDVUMTQ6MjQ6NTguNzc0NTY2MjM2WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6NDk2NWFmZmFmNDJhNzE3NDU2MTg4MmM1ZmQ4N2UyZGI2ZjBiMDdkZjUzMjQ1OWJhODZmOThhOGJkMmFmMTFkZSIsInNoYTI1Njo0NjBjM2UyN2JlMTYzZWZlNzVkZjA0OGM0ZDRjZjNhMjJlN2UzNjNmMDI1MjFmYTJlODJhM2JkMjU3YTY4MmQ0Il19fQ==",
|
||||||
"repoDigests": [],
|
"repoDigests": [],
|
||||||
"architecture": "",
|
"architecture": "",
|
||||||
"os": ""
|
"os": ""
|
||||||
|
@ -111,7 +112,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schema": {
|
"schema": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-4.0.0.json"
|
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-4.1.0.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -17,8 +17,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToFormatModel transforms the sbom import a format-specific model.
|
// ToFormatModel transforms the sbom import a format-specific model.
|
||||||
// note: this is needed for anchore import functionality
|
|
||||||
// TODO: unexport this when/if anchore import functionality is removed
|
|
||||||
func ToFormatModel(s sbom.SBOM) model.Document {
|
func ToFormatModel(s sbom.SBOM) model.Document {
|
||||||
src, err := toSourceModel(s.Source)
|
src, err := toSourceModel(s.Source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -236,16 +234,19 @@ func toSourceModel(src source.Metadata) (model.Source, error) {
|
||||||
metadata.Tags = []string{}
|
metadata.Tags = []string{}
|
||||||
}
|
}
|
||||||
return model.Source{
|
return model.Source{
|
||||||
|
ID: src.ID,
|
||||||
Type: "image",
|
Type: "image",
|
||||||
Target: metadata,
|
Target: metadata,
|
||||||
}, nil
|
}, nil
|
||||||
case source.DirectoryScheme:
|
case source.DirectoryScheme:
|
||||||
return model.Source{
|
return model.Source{
|
||||||
|
ID: src.ID,
|
||||||
Type: "directory",
|
Type: "directory",
|
||||||
Target: src.Path,
|
Target: src.Path,
|
||||||
}, nil
|
}, nil
|
||||||
case source.FileScheme:
|
case source.FileScheme:
|
||||||
return model.Source{
|
return model.Source{
|
||||||
|
ID: src.ID,
|
||||||
Type: "file",
|
Type: "file",
|
||||||
Target: src.Path,
|
Target: src.Path,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -26,10 +26,12 @@ func Test_toSourceModel(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "directory",
|
name: "directory",
|
||||||
src: source.Metadata{
|
src: source.Metadata{
|
||||||
|
ID: "test-id",
|
||||||
Scheme: source.DirectoryScheme,
|
Scheme: source.DirectoryScheme,
|
||||||
Path: "some/path",
|
Path: "some/path",
|
||||||
},
|
},
|
||||||
expected: model.Source{
|
expected: model.Source{
|
||||||
|
ID: "test-id",
|
||||||
Type: "directory",
|
Type: "directory",
|
||||||
Target: "some/path",
|
Target: "some/path",
|
||||||
},
|
},
|
||||||
|
@ -37,10 +39,12 @@ func Test_toSourceModel(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "file",
|
name: "file",
|
||||||
src: source.Metadata{
|
src: source.Metadata{
|
||||||
|
ID: "test-id",
|
||||||
Scheme: source.FileScheme,
|
Scheme: source.FileScheme,
|
||||||
Path: "some/path",
|
Path: "some/path",
|
||||||
},
|
},
|
||||||
expected: model.Source{
|
expected: model.Source{
|
||||||
|
ID: "test-id",
|
||||||
Type: "file",
|
Type: "file",
|
||||||
Target: "some/path",
|
Target: "some/path",
|
||||||
},
|
},
|
||||||
|
@ -48,6 +52,7 @@ func Test_toSourceModel(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "image",
|
name: "image",
|
||||||
src: source.Metadata{
|
src: source.Metadata{
|
||||||
|
ID: "test-id",
|
||||||
Scheme: source.ImageScheme,
|
Scheme: source.ImageScheme,
|
||||||
ImageMetadata: source.ImageMetadata{
|
ImageMetadata: source.ImageMetadata{
|
||||||
UserInput: "user-input",
|
UserInput: "user-input",
|
||||||
|
@ -57,6 +62,7 @@ func Test_toSourceModel(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: model.Source{
|
expected: model.Source{
|
||||||
|
ID: "test-id",
|
||||||
Type: "image",
|
Type: "image",
|
||||||
Target: source.ImageMetadata{
|
Target: source.ImageMetadata{
|
||||||
UserInput: "user-input",
|
UserInput: "user-input",
|
||||||
|
|
|
@ -64,6 +64,9 @@ func toSyftRelationships(doc *model.Document, catalog *pkg.Catalog, relationship
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set source metadata in identifier map
|
||||||
|
idMap[doc.Source.ID] = toSyftSource(doc.Source)
|
||||||
|
|
||||||
for _, f := range doc.Files {
|
for _, f := range doc.Files {
|
||||||
idMap[f.ID] = f.Location
|
idMap[f.ID] = f.Location
|
||||||
}
|
}
|
||||||
|
@ -78,6 +81,14 @@ func toSyftRelationships(doc *model.Document, catalog *pkg.Catalog, relationship
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toSyftSource(s model.Source) *source.Source {
|
||||||
|
newSrc := &source.Source{
|
||||||
|
Metadata: *toSyftSourceData(s),
|
||||||
|
}
|
||||||
|
newSrc.SetID()
|
||||||
|
return newSrc
|
||||||
|
}
|
||||||
|
|
||||||
func toSyftRelationship(idMap map[string]interface{}, relationship model.Relationship, idAliases map[string]string) *artifact.Relationship {
|
func toSyftRelationship(idMap map[string]interface{}, relationship model.Relationship, idAliases map[string]string) *artifact.Relationship {
|
||||||
id := func(id string) string {
|
id := func(id string) string {
|
||||||
aliased, ok := idAliases[id]
|
aliased, ok := idAliases[id]
|
||||||
|
@ -86,16 +97,19 @@ func toSyftRelationship(idMap map[string]interface{}, relationship model.Relatio
|
||||||
}
|
}
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
from, ok := idMap[id(relationship.Parent)].(artifact.Identifiable)
|
from, ok := idMap[id(relationship.Parent)].(artifact.Identifiable)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warnf("relationship mapping from key %s is not a valid artifact.Identifiable type: %+v", relationship.Parent, idMap[relationship.Parent])
|
log.Warnf("relationship mapping from key %s is not a valid artifact.Identifiable type: %+v", relationship.Parent, idMap[relationship.Parent])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
to, ok := idMap[id(relationship.Child)].(artifact.Identifiable)
|
to, ok := idMap[id(relationship.Child)].(artifact.Identifiable)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warnf("relationship mapping to key %s is not a valid artifact.Identifiable type: %+v", relationship.Child, idMap[relationship.Child])
|
log.Warnf("relationship mapping to key %s is not a valid artifact.Identifiable type: %+v", relationship.Child, idMap[relationship.Child])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
typ := artifact.RelationshipType(relationship.Type)
|
typ := artifact.RelationshipType(relationship.Type)
|
||||||
|
|
||||||
switch typ {
|
switch typ {
|
||||||
|
@ -126,16 +140,19 @@ func toSyftSourceData(s model.Source) *source.Metadata {
|
||||||
switch s.Type {
|
switch s.Type {
|
||||||
case "directory":
|
case "directory":
|
||||||
return &source.Metadata{
|
return &source.Metadata{
|
||||||
|
ID: s.ID,
|
||||||
Scheme: source.DirectoryScheme,
|
Scheme: source.DirectoryScheme,
|
||||||
Path: s.Target.(string),
|
Path: s.Target.(string),
|
||||||
}
|
}
|
||||||
case "file":
|
case "file":
|
||||||
return &source.Metadata{
|
return &source.Metadata{
|
||||||
|
ID: s.ID,
|
||||||
Scheme: source.FileScheme,
|
Scheme: source.FileScheme,
|
||||||
Path: s.Target.(string),
|
Path: s.Target.(string),
|
||||||
}
|
}
|
||||||
case "image":
|
case "image":
|
||||||
return &source.Metadata{
|
return &source.Metadata{
|
||||||
|
ID: s.ID,
|
||||||
Scheme: source.ImageScheme,
|
Scheme: source.ImageScheme,
|
||||||
ImageMetadata: s.Target.(source.ImageMetadata),
|
ImageMetadata: s.Target.(source.ImageMetadata),
|
||||||
}
|
}
|
||||||
|
|
15
syft/lib.go
15
syft/lib.go
|
@ -74,9 +74,24 @@ func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Catalog, []
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
relationships = append(relationships, newSourceRelationshipsFromCatalog(src, catalog)...)
|
||||||
|
|
||||||
return catalog, relationships, release, nil
|
return catalog, relationships, release, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newSourceRelationshipsFromCatalog(src *source.Source, c *pkg.Catalog) []artifact.Relationship {
|
||||||
|
relationships := make([]artifact.Relationship, 0) // Should we pre-allocate this by giving catalog a Len() method?
|
||||||
|
for p := range c.Enumerate() {
|
||||||
|
relationships = append(relationships, artifact.Relationship{
|
||||||
|
From: src,
|
||||||
|
To: p,
|
||||||
|
Type: artifact.ContainsRelationship,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return relationships
|
||||||
|
}
|
||||||
|
|
||||||
// SetLogger sets the logger object used for all syft logging calls.
|
// SetLogger sets the logger object used for all syft logging calls.
|
||||||
func SetLogger(logger logger.Logger) {
|
func SetLogger(logger logger.Logger) {
|
||||||
log.Log = logger
|
log.Log = logger
|
||||||
|
|
|
@ -2,6 +2,7 @@ package source
|
||||||
|
|
||||||
// Metadata represents any static source data that helps describe "what" was cataloged.
|
// Metadata represents any static source data that helps describe "what" was cataloged.
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
|
ID string `hash:"ignore"` // the id generated from the parent source struct
|
||||||
Scheme Scheme // the source data scheme type (directory or image)
|
Scheme Scheme // the source data scheme type (directory or image)
|
||||||
ImageMetadata ImageMetadata // all image info (image only)
|
ImageMetadata ImageMetadata // all image info (image only)
|
||||||
Path string // the root path to be cataloged (directory only)
|
Path string // the root path to be cataloged (directory only)
|
||||||
|
|
|
@ -27,13 +27,13 @@ import (
|
||||||
// Source is an object that captures the data source to be cataloged, configuration, and a specific resolver used
|
// Source is an object that captures the data source to be cataloged, configuration, and a specific resolver used
|
||||||
// in cataloging (based on the data source and configuration)
|
// in cataloging (based on the data source and configuration)
|
||||||
type Source struct {
|
type Source struct {
|
||||||
id artifact.ID
|
id artifact.ID `hash:"ignore"`
|
||||||
Image *image.Image // the image object to be cataloged (image only)
|
Image *image.Image `hash:"ignore"` // the image object to be cataloged (image only)
|
||||||
Metadata Metadata
|
Metadata Metadata
|
||||||
directoryResolver *directoryResolver
|
directoryResolver *directoryResolver `hash:"ignore"`
|
||||||
path string
|
path string
|
||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
Exclusions []string
|
Exclusions []string `hash:"ignore"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input is an object that captures the detected user input regarding source location, scheme, and provider type.
|
// Input is an object that captures the detected user input regarding source location, scheme, and provider type.
|
||||||
|
@ -241,28 +241,33 @@ func generateFileSource(fs afero.Fs, location string) (*Source, func(), error) {
|
||||||
|
|
||||||
// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively.
|
// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively.
|
||||||
func NewFromDirectory(path string) (Source, error) {
|
func NewFromDirectory(path string) (Source, error) {
|
||||||
return Source{
|
s := Source{
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
Metadata: Metadata{
|
Metadata: Metadata{
|
||||||
Scheme: DirectoryScheme,
|
Scheme: DirectoryScheme,
|
||||||
Path: path,
|
Path: path,
|
||||||
},
|
},
|
||||||
path: path,
|
path: path,
|
||||||
}, nil
|
}
|
||||||
|
s.SetID()
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromFile creates a new source object tailored to catalog a file.
|
// NewFromFile creates a new source object tailored to catalog a file.
|
||||||
func NewFromFile(path string) (Source, func()) {
|
func NewFromFile(path string) (Source, func()) {
|
||||||
analysisPath, cleanupFn := fileAnalysisPath(path)
|
analysisPath, cleanupFn := fileAnalysisPath(path)
|
||||||
|
|
||||||
return Source{
|
s := Source{
|
||||||
mutex: &sync.Mutex{},
|
mutex: &sync.Mutex{},
|
||||||
Metadata: Metadata{
|
Metadata: Metadata{
|
||||||
Scheme: FileScheme,
|
Scheme: FileScheme,
|
||||||
Path: path,
|
Path: path,
|
||||||
},
|
},
|
||||||
path: analysisPath,
|
path: analysisPath,
|
||||||
}, cleanupFn
|
}
|
||||||
|
|
||||||
|
s.SetID()
|
||||||
|
return s, cleanupFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileAnalysisPath returns the path given, or in the case the path is an archive, the location where the archive
|
// fileAnalysisPath returns the path given, or in the case the path is an archive, the location where the archive
|
||||||
|
@ -298,16 +303,18 @@ func NewFromImage(img *image.Image, userImageStr string) (Source, error) {
|
||||||
return Source{}, fmt.Errorf("no image given")
|
return Source{}, fmt.Errorf("no image given")
|
||||||
}
|
}
|
||||||
|
|
||||||
return Source{
|
s := Source{
|
||||||
Image: img,
|
Image: img,
|
||||||
Metadata: Metadata{
|
Metadata: Metadata{
|
||||||
Scheme: ImageScheme,
|
Scheme: ImageScheme,
|
||||||
ImageMetadata: NewImageMetadata(img, userImageStr),
|
ImageMetadata: NewImageMetadata(img, userImageStr),
|
||||||
},
|
},
|
||||||
}, nil
|
}
|
||||||
|
s.SetID()
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Source) ID() artifact.ID {
|
func (s *Source) ID() artifact.ID {
|
||||||
if s.id == "" {
|
if s.id == "" {
|
||||||
s.SetID()
|
s.SetID()
|
||||||
}
|
}
|
||||||
|
@ -333,7 +340,7 @@ func (s *Source) SetID() {
|
||||||
}
|
}
|
||||||
d = di.String()
|
d = di.String()
|
||||||
case ImageScheme:
|
case ImageScheme:
|
||||||
manifestDigest := digest.FromBytes(s.Image.Metadata.RawManifest).String()
|
manifestDigest := digest.FromBytes(s.Metadata.ImageMetadata.RawManifest).String()
|
||||||
if manifestDigest != "" {
|
if manifestDigest != "" {
|
||||||
d = manifestDigest
|
d = manifestDigest
|
||||||
break
|
break
|
||||||
|
@ -341,7 +348,7 @@ func (s *Source) SetID() {
|
||||||
|
|
||||||
// calcuate chain ID for image sources where manifestDigest is not available
|
// calcuate chain ID for image sources where manifestDigest is not available
|
||||||
// https://github.com/opencontainers/image-spec/blob/main/config.md#layer-chainid
|
// https://github.com/opencontainers/image-spec/blob/main/config.md#layer-chainid
|
||||||
d = calculateChainID(s.Image)
|
d = calculateChainID(s.Metadata.ImageMetadata.Layers)
|
||||||
if d == "" {
|
if d == "" {
|
||||||
// TODO what happens here if image has no layers?
|
// TODO what happens here if image has no layers?
|
||||||
// Is this case possible
|
// Is this case possible
|
||||||
|
@ -353,27 +360,28 @@ func (s *Source) SetID() {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.id = artifact.ID(strings.TrimPrefix(d, "sha256:"))
|
s.id = artifact.ID(strings.TrimPrefix(d, "sha256:"))
|
||||||
|
s.Metadata.ID = strings.TrimPrefix(d, "sha256:")
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateChainID(img *image.Image) string {
|
func calculateChainID(lm []LayerMetadata) string {
|
||||||
if len(img.Layers) < 1 {
|
if len(lm) < 1 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiffID(L0) = digest of layer 0
|
// DiffID(L0) = digest of layer 0
|
||||||
// https://github.com/anchore/stereoscope/blob/1b1b744a919964f38d14e1416fb3f25221b761ce/pkg/image/layer_metadata.go#L19-L32
|
// https://github.com/anchore/stereoscope/blob/1b1b744a919964f38d14e1416fb3f25221b761ce/pkg/image/layer_metadata.go#L19-L32
|
||||||
chainID := img.Layers[0].Metadata.Digest
|
chainID := lm[0].Digest
|
||||||
id := chain(chainID, img.Layers[1:])
|
id := chain(chainID, lm[1:])
|
||||||
|
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
func chain(chainID string, layers []*image.Layer) string {
|
func chain(chainID string, layers []LayerMetadata) string {
|
||||||
if len(layers) < 1 {
|
if len(layers) < 1 {
|
||||||
return chainID
|
return chainID
|
||||||
}
|
}
|
||||||
|
|
||||||
chainID = digest.FromString(layers[0].Metadata.Digest + " " + chainID).String()
|
chainID = digest.FromString(layers[0].Digest + " " + chainID).String()
|
||||||
return chain(chainID, layers[1:])
|
return chain(chainID, layers[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ func TestSetID(t *testing.T) {
|
||||||
Path: "test-fixtures/image-simple",
|
Path: "test-fixtures/image-simple",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: artifact.ID("febd2d6148dc327d"),
|
expected: artifact.ID("26e8f4daad203793"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue