Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate SPDX-JSON relationships to SBOM model #634

Merged
merged 14 commits into from Nov 23, 2021

Conversation

wagoodman
Copy link
Contributor

@wagoodman wagoodman commented Nov 17, 2021

This is a follow up to #607 and #623
Closes #556

Changes made:

  • Removes the creation of relationships in the spdx22json format encoder and moves file-ownership relationship creation back to the main Catalog function.
  • Adds a new sbom.AllCoordinates that returns a slice of all coordinates referenced throughout all SBOM artifacts and relationships. This is needed to guarantee inclusion of all referenced files as nodes in the SBOM.
  • The power-user JSON document data structure has been replaced with the syftjson format model and the poweruser.Presenter has been deleted (as well as the remainder of internal/presenters 馃帀 馃尞 )
  • Combines file classifications, metadata, and contents fields for a location into a single entry. For this reason the fileMetadata, fileClassifications, and fileContents power-user JSON fields have been removed and replaced with a single files section. This requires bumping the JSON schema version from 1 to 2 .
  • Adds the application configuration to the output of the packages output (for syftjson format). This now matches the same behavior as power-user.

Notes:

Questions:

  • a future PR could implement syft1json and syft2json formats for backwards compatibility, though this is not planned at this time. Should we do this before bumping to schema version 2?
  • What should we do in cases where a package manager claims ownership of a file that does not exist (relative to the source.FileResolver)? Should we log and continue (what is implemented now)? Or make note of this in the SBOM?

@github-actions
Copy link

github-actions bot commented Nov 17, 2021

Benchmark Test Results

Benchmark results from the latest changes vs base branch
name                                                   old time/op    new time/op    delta
ImagePackageCatalogers/ruby-gemspec-cataloger-2          1.96ms 卤 8%    1.49ms 卤 1%  -23.76%  (p=0.016 n=5+4)
ImagePackageCatalogers/python-package-cataloger-2        4.77ms 卤 3%    3.76ms 卤 2%  -21.16%  (p=0.008 n=5+5)
ImagePackageCatalogers/javascript-package-cataloger-2    1.09ms 卤 1%    0.91ms 卤 4%  -17.10%  (p=0.008 n=5+5)
ImagePackageCatalogers/dpkgdb-cataloger-2                1.38ms 卤 1%    1.08ms 卤 1%  -21.94%  (p=0.008 n=5+5)
ImagePackageCatalogers/rpmdb-cataloger-2                 1.18ms 卤 3%    0.95ms 卤 1%  -19.72%  (p=0.008 n=5+5)
ImagePackageCatalogers/java-cataloger-2                  16.2ms 卤 2%    12.3ms 卤 7%  -23.94%  (p=0.008 n=5+5)
ImagePackageCatalogers/apkdb-cataloger-2                 1.91ms 卤 2%    1.58ms 卤 2%  -17.29%  (p=0.008 n=5+5)
ImagePackageCatalogers/go-module-binary-cataloger-2      2.07碌s 卤 2%    1.89碌s 卤 2%   -8.54%  (p=0.008 n=5+5)

name                                                   old alloc/op   new alloc/op   delta
ImagePackageCatalogers/ruby-gemspec-cataloger-2           286kB 卤 0%     286kB 卤 0%     ~     (p=0.310 n=5+5)
ImagePackageCatalogers/python-package-cataloger-2        1.29MB 卤 0%    1.29MB 卤 0%   +0.11%  (p=0.008 n=5+5)
ImagePackageCatalogers/javascript-package-cataloger-2     211kB 卤 0%     211kB 卤 0%     ~     (p=0.421 n=5+5)
ImagePackageCatalogers/dpkgdb-cataloger-2                 269kB 卤 0%     271kB 卤 0%   +0.59%  (p=0.008 n=5+5)
ImagePackageCatalogers/rpmdb-cataloger-2                  234kB 卤 0%     234kB 卤 0%   +0.01%  (p=0.008 n=5+5)
ImagePackageCatalogers/java-cataloger-2                  3.36MB 卤 0%    3.35MB 卤 0%     ~     (p=0.151 n=5+5)
ImagePackageCatalogers/apkdb-cataloger-2                 1.35MB 卤 0%    1.35MB 卤 0%   +0.19%  (p=0.008 n=5+5)
ImagePackageCatalogers/go-module-binary-cataloger-2        480B 卤 0%      560B 卤 0%  +16.67%  (p=0.008 n=5+5)

name                                                   old allocs/op  new allocs/op  delta
ImagePackageCatalogers/ruby-gemspec-cataloger-2           8.93k 卤 0%     8.93k 卤 0%     ~     (p=1.000 n=5+5)
ImagePackageCatalogers/python-package-cataloger-2         38.5k 卤 0%     38.6k 卤 0%   +0.21%  (p=0.008 n=5+5)
ImagePackageCatalogers/javascript-package-cataloger-2     5.82k 卤 0%     5.82k 卤 0%     ~     (all equal)
ImagePackageCatalogers/dpkgdb-cataloger-2                 8.55k 卤 0%     8.61k 卤 0%   +0.65%  (p=0.008 n=5+5)
ImagePackageCatalogers/rpmdb-cataloger-2                  7.10k 卤 0%     7.11k 卤 0%   +0.01%  (p=0.008 n=5+5)
ImagePackageCatalogers/java-cataloger-2                   66.9k 卤 0%     66.9k 卤 0%     ~     (p=0.444 n=5+5)
ImagePackageCatalogers/apkdb-cataloger-2                  11.1k 卤 0%     11.2k 卤 0%   +0.97%  (p=0.029 n=4+4)
ImagePackageCatalogers/go-module-binary-cataloger-2        11.0 卤 0%      12.0 卤 0%   +9.09%  (p=0.008 n=5+5)

@wagoodman wagoodman added WIP work in progress / do not merge and removed WIP work in progress / do not merge labels Nov 17, 2021
@wagoodman wagoodman requested a review from a team November 17, 2021 20:25
@wagoodman wagoodman linked an issue Nov 17, 2021 that may be closed by this pull request
Base automatically changed from split-location to main November 18, 2021 18:13
@wagoodman
Copy link
Contributor Author

Merging #623 has caused a lot of merge conflicts and rebasing that is needed (note the base branch changed, so the diff is way off now). I'll adjust shortly.

@wagoodman wagoodman force-pushed the migrate-spdx-json-to-new-relationships branch from e117cc9 to cde16e1 Compare November 19, 2021 11:54
@wagoodman wagoodman marked this pull request as ready for review November 19, 2021 12:17
@wagoodman
Copy link
Contributor Author

ok, rebased and cleaned up! 馃

Copy link
Contributor

@spiffcs spiffcs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass - Stopped at mimetype_helper and will dig in more after lunch -

To answer questions on the PR:

  1. I think we should implement backwards compatibility before bumping the schema version. It's easy to lose that thread and let changes get ahead of the opportunity for us to do it now.

  2. If there is metadata from a package manager that suggests and file exists, but we have read the entire tree and concluded it doesn't exist because some future build action had removed it then including it in the document might cause a kind of SBOM false positive. On the other hand, if we support annotations I think we can include that message saying - package manager says x should exist, but we concluded it has been removed

I think the incorrect path is including it with no context. Either do what is implemented now, or explore annotation land.

@@ -76,19 +90,12 @@ func documentNamespace(name string, srcMetadata source.Metadata) string {
return path.Join(anchoreNamespace, identifier)
}

func extractFromCatalog(catalog *pkg.Catalog) ([]model.Package, []model.File, []model.Relationship) {
func toPackages(catalog *pkg.Catalog, relationships []artifact.Relationship) []model.Package {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the new pattern for building packages.

As we build more relationships is this going to get more complex?

Right now relationships are homogenous in that they are all file relationships so fileIDSForPackage is pretty clean. As we pass more and more types are we going to have to segment them out or will relationships always be this large slice we pass around and ask different parsing functions to figure out what they care about?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing around a slice of relationships isn't a large problem --even if it's a lot of them, it's s pointer to an underlying array, so I feel pretty OK with this. When it comes to code complexity, I also feel like we're OK here... specifically fileIDSForPackage is filtering out the set of relationships to only those that this package takes a part of and with the appropriate relationship type.

I do feel like there may be room for improvement as to where the "relationship helper function" go. This one is ad-hoc in a format-related package. That can be dealt with in the future after this PR.

return nil
}

mimeTypePrefix := strings.Split(metadata.MIMEType, "/")[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooo is this always consistent? Adding a note to come back and check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed --it will either match up with a prefix, or not. Either way there will be at least one value in the slice and we're taking the first value.

func lookupRelationship(ty artifact.RelationshipType) (bool, model.RelationshipType, string) {
switch ty {
case artifact.ContainsRelationship:
return true, model.ContainsRelationship, ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add the comment here from the current documentation?

ContainsRelationship is to be used when SPDXRef-A contains SPDXRef-B.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other was highly suggested by the SPDX spec to have a comment, however, it doesn't seem like adding comments that copy-paste from the SPDX docs will help here (programmatically it does, but from a user/consumer sense it doesn't). Maybe in the future we could provide more context in the comments field, but for now should probably leave it out.

@@ -7,7 +7,7 @@ import (
"github.com/anchore/syft/syft/sbom"
)

func encoder(output io.Writer, s sbom.SBOM) error {
func encoder(output io.Writer, s sbom.SBOM, _ interface{}) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a smell that we updated the encoder signature to include this 3rd argument that only a couple of the types use?

Would it be better if this was not a function type and instead an interface that a struct could implement so things specific to that struct can be injected into the method without polluting the larger signature?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great suggestion and points out a problem that we have already started to get rid of in the code base... I'll take a closer look at options here 馃憤

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR: I ended up adding an sbom.Descriptor that is added to sbom.SBOM that contains the application configuration, the tool name, and tool version.

I needed to refresh myself on the input going into the design for format encoding and decoding. The first approach taken (but not used) was to introduce Encoder, Decoder, and Validator interfaces (I re-mocked it up here 5fb0235). This approach looked great, however, the drawback was that you couldn't leverage the pro's of this approach. Specifically, you could push format-specific fields (such as application config) down below the interface and let the concrete implementation deal with these difference for each instance. However, that requires that this data is passed at object construction or between construction and the call to Encode(), neither of which is ideal since there would be a need to have a list of all formatters to reference... needing to have the data at this time would be difficult.

Function types that are assigned to the Format struct are the next best thing, since it enforces that state must be passed in at encode-time, and not earlier.

I then started to look the other way: since injection of this state via behavior seemed wrong (due to the above notes) I started looking for ways to encapsulate the app config. I had initially thrown out encoding it in sbom.SBOM and instead started looking into making a new envelope (something like format.Context that would contain sbom.SBOM and the application config), however, the more I poked at this the more sane it seemed to put the application config into the SBOM itself.

In syft json, CycloneDX, and SPDX, there are sections that describe the tool that created the SBOM. Now that there are encode and decode workflows we must persist this information in the model or else we will loose it in decoding operations.

Consider an encode-decode-encode workflow with an old version of syft to a new version of syft... we encode the original SBOM with the old version of syft followed by a decode and encode with the new version of syft. This would be how you convert from an older schema version to a new schema version. Without persisting the original tool name, version, and configuration, we would have no record on how the information was created to begin with. It should only be the schema section that changes (to indicate the new format) but not the tool information.

For these reasons I went the route of adding a new sbom.Descriptor section that persists name, version, and optional configuration for the tool that created the SBOM.

Distro Distro `json:"distro"` // Distro represents the Linux distribution that was detected from the source
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
Files []File `json:"files,omitempty"` // note: must have omitempty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's happening 馃

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see if we can sync and nail down how/why Files should be a sibling field to Artifacts - I think I understand, but it would be good to codify it somewhere so we can be very explicit on why they are different.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all File Artifact, but not all Artifact File?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the "artifacts" section is just packages, but in the future it may contain other fields that are sibling fields of "artifacts" today (see #555). Until we tackle #555 it would be consistent to leave Files as a sibling of Artifacts.

@@ -26,6 +30,8 @@ func ToFormatModel(s sbom.SBOM, applicationConfig interface{}) model.Document {
return model.Document{
Artifacts: toPackageModels(s.Artifacts.PackageCatalog),
ArtifactRelationships: toRelationshipModel(s.Relationships),
Files: toFile(s),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More to my point about nailing the distinction. A little weird that Files takes the entire sbom, but Secrets below it has a subsection from Artifacts that we lift to the top level of this struct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little weird that Files takes the entire sbom...

you're right, though it's still necessary. The short answer from our offline conversation is that the Artifacts struct is not guaranteed to have all of the nodes in the graph that can be created from sbom.SBOM, some of the edges in Relationships describe nodes that a cataloger may not have directly observed (e.g. a package manager claiming that a package owns a file that does not exist on the filesystem).

At a minimum to describe all files one needs sbom.Artifacts and sbom.Relationships to get a full picture of all files that will be in the SBOM. Since this is a helper function of the sbom package it seemed right from a consumer point of view to only need to pass an SBOM to get the answer to the question "what files are in my SBOM" (as opposed to needing to pass elements of an SBOM).

internal/formats/table/encoder.go Outdated Show resolved Hide resolved
Copy link
Contributor

@spiffcs spiffcs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More comments

To Review:

  • to_format_model.go (each implementation)
  • new mimetype_helper
  • sbom.go coordinate additions
  • CoordinateSet and its use

cmd/power_user.go Outdated Show resolved Hide resolved
internal/formats/spdx22json/model/file.go Show resolved Hide resolved
@@ -15,7 +15,7 @@ func TestEncodeDecodeCycle(t *testing.T) {
originalSBOM := testutils.ImageInput(t, testImage)

var buf bytes.Buffer
assert.NoError(t, encoder(&buf, originalSBOM))
assert.NoError(t, encoder(&buf, originalSBOM, map[string]string{"config": "value"}))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a comment here? I think this ties into the config portion existing now within the encoder signature

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I'll play with this while working on #634 (comment) ... at the very least I'll provide a comment

syft/artifact/relationship.go Show resolved Hide resolved
syft/format/encoder.go Outdated Show resolved Hide resolved
@@ -40,6 +46,85 @@ func ToFormatModel(s sbom.SBOM, applicationConfig interface{}) model.Document {
}
}

func toSecrets(data map[source.Coordinates][]file.SearchResult) []model.Secrets {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map[source.Coordinates][]file.SearchResult

Is there any value in codifying these as concreate types beyond the assembly of their basic types?

Found as part of Sbom here:

syft/syft/sbom/sbom.go

Lines 17 to 25 in 4f00995

type Artifacts struct {
PackageCatalog *pkg.Catalog
FileMetadata map[source.Coordinates]source.FileMetadata
FileDigests map[source.Coordinates][]file.Digest
FileClassifications map[source.Coordinates][]file.Classification
FileContents map[source.Coordinates]string
Secrets map[source.Coordinates][]file.SearchResult
Distro *distro.Distro
}

map[source.Coordinates][]file.SearchResult => someIndex?

Only asking to see if additional methods could be added which makes working with these types easier

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From our offline conversation: I'll try out making types for each of these "map" fields but probably won't add any helper functions for those types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the new types didn't make any behavioral changes --the new types at the very least abstract what is in the type so if it changes later (say to a struct) then we don't need to update a bunch of function signatures.

Copy link
Contributor Author

@wagoodman wagoodman Nov 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, well. When I updated the file package to use the new sbom.File* types, I got this:

package command-line-arguments
        imports github.com/anchore/syft/cmd
        imports github.com/anchore/syft/internal/anchore
        imports github.com/anchore/syft/internal/formats/syftjson
        imports github.com/anchore/syft/internal/formats/syftjson/model
        imports github.com/anchore/syft/syft/file
        imports github.com/anchore/syft/syft/sbom
        imports github.com/anchore/syft/syft/file: import cycle not allowed

I don't have a good answer for this (not why, that is known, but on the path forward with these new types). For now I'm going to leave the primitives instead of introducing the new types. This can be a problem we solve later.

@@ -67,6 +69,14 @@ func Catalog(resolver source.FileResolver, theDistro *distro.Distro, catalogers
// generate PURL
p.PURL = generatePackageURL(p, theDistro)

// create file-to-package relationships for files owned by the package
owningRelationships, err := packageFileOwnershipRelationships(p, resolver)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know catalog is being given the responsibility of building these relationships so are we keeping it around going forward? Seems like an ok spot to land for relationship generation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cataloging has the ability to raise up relationships when it makes sense (shifting that responsibility "left" towards the catalogers when it has the "most knowledge" about the relationship in question, say, tracking a graph of transitive dependency for a package... only the cataloger has enough information to capture these relationships). Though there will be other times when crafting relationships are better served on the "right" after all cataloging is completed (say when elements from different catalogers may be related).

This will probably shake out over time and we can adjust where this lives as we see better places for it. (for now I'll leave it where it is --though shout out if you see a better spot for it)

syft/pkg/cataloger/golang/binary_cataloger.go Show resolved Hide resolved
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
@wagoodman wagoodman force-pushed the migrate-spdx-json-to-new-relationships branch from eadb555 to aade54d Compare November 23, 2021 14:58
@wagoodman
Copy link
Contributor Author

@spiffcs I've addressed all of your comments --ready for re-review! 馃帀 馃尞

@wagoodman wagoodman merged commit bd9007f into main Nov 23, 2021
@wagoodman wagoodman deleted the migrate-spdx-json-to-new-relationships branch November 23, 2021 19:54
fengshunli pushed a commit to fengshunli/syft that referenced this pull request Jan 24, 2022
* remove power-user document shape

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add power-user specific fields to syft-json format

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* port remaining spdx-json relationships to sbom model

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add coordinate set

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add SBOM file path helper

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* use internal mimetype helper in go binary cataloger

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add new package-of relationship

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update json schema to v2

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* replace power-user presenter with syft-json format

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix tests and linting

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* remove "package-of" relationship (in favor of "contains")

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add tests for spdx22json format encoding enhancements

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update TODO and log entries

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* introduce sbom.Descriptor

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: fsl <1171313930@qq.com>
GijsCalis pushed a commit to GijsCalis/syft that referenced this pull request Feb 19, 2024
* remove power-user document shape

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add power-user specific fields to syft-json format

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* port remaining spdx-json relationships to sbom model

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add coordinate set

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add SBOM file path helper

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* use internal mimetype helper in go binary cataloger

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add new package-of relationship

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update json schema to v2

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* replace power-user presenter with syft-json format

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix tests and linting

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* remove "package-of" relationship (in favor of "contains")

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add tests for spdx22json format encoding enhancements

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update TODO and log entries

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* introduce sbom.Descriptor

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Prefer artifact relationships over package relationships
2 participants