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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for conan lock v2 #2461

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion syft/internal/packagemetadata/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func AllTypes() []any {
pkg.ApkDBEntry{},
pkg.BinarySignature{},
pkg.CocoaPodfileLockEntry{},
pkg.ConanLockEntry{},
pkg.ConanV1LockEntry{},
pkg.ConanfileEntry{},
pkg.ConaninfoEntry{},
pkg.DartPubspecLockEntry{},
Expand Down
2 changes: 1 addition & 1 deletion syft/internal/packagemetadata/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.ApkDBEntry{}, "apk-db-entry", "ApkMetadata"),
jsonNames(pkg.BinarySignature{}, "binary-signature", "BinaryMetadata"),
jsonNames(pkg.CocoaPodfileLockEntry{}, "cocoa-podfile-lock-entry", "CocoapodsMetadataType"),
jsonNames(pkg.ConanLockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"),
jsonNames(pkg.ConanV1LockEntry{}, "c-conan-lock-entry", "ConanLockMetadataType"),
jsonNames(pkg.ConanfileEntry{}, "c-conan-file-entry", "ConanMetadataType"),
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
Expand Down
4 changes: 2 additions & 2 deletions syft/internal/packagemetadata/names_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func TestReflectTypeFromJSONName_LegacyValues(t *testing.T) {
{
name: "map pkg.ConanLockEntry struct type",
input: "ConanLockMetadataType",
expected: reflect.TypeOf(pkg.ConanLockEntry{}),
expected: reflect.TypeOf(pkg.ConanV1LockEntry{}),
},
{
name: "map pkg.ConanfileEntry struct type",
Expand Down Expand Up @@ -290,7 +290,7 @@ func Test_JSONName_JSONLegacyName(t *testing.T) {
},
{
name: "ConanLockMetadata",
metadata: pkg.ConanLockEntry{},
metadata: pkg.ConanV1LockEntry{},
expectedJSONName: "c-conan-lock-entry",
expectedLegacyName: "ConanLockMetadataType",
},
Expand Down
6 changes: 5 additions & 1 deletion syft/pkg/cataloger/cpp/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ func newConanfilePackage(m pkg.ConanfileEntry, locations ...file.Location) *pkg.
return newConanPackage(m.Ref, m, locations...)
}

func newConanlockPackage(m pkg.ConanLockEntry, locations ...file.Location) *pkg.Package {
func newConanlockPackage(m pkg.ConanV1LockEntry, locations ...file.Location) *pkg.Package {
return newConanPackage(m.Ref, m, locations...)
}

func newConanRefrencePackage(m pkg.ConanV2LockEntry, locations ...file.Location) *pkg.Package {
return newConanPackage(m.Ref, m, locations...)
}

Expand Down
82 changes: 79 additions & 3 deletions syft/pkg/cataloger/cpp/parse_conanlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ type conanLock struct {
Path string `json:"path"`
} `json:"nodes"`
} `json:"graph_lock"`
Version string `json:"version"`
ProfileHost string `json:"profile_host"`
Version string `json:"version"`
ProfileHost string `json:"profile_host"`
ProfileBuild string `json:"profile_build,omitempty"`
// conan v0.5+ lockfiles use "requires", "build_requires" and "python_requires"
Requires []string `json:"requires,omitempty"`
BuildRequires []string `json:"build_requires,omitempty"`
PythonRequires []string `json:"python_requires,omitempty"`
}

// parseConanlock is a parser function for conan.lock contents, returning all packages discovered.
Expand All @@ -41,13 +46,33 @@ func parseConanlock(_ file.Resolver, _ *generic.Environment, reader file.Locatio
// in a second iteration
var indexToPkgMap = map[string]pkg.Package{}

// Support for conan lock 2.x requires field
for _, ref := range cl.Requires {
reference := parseConanV2Reference(ref)
if reference.Name == "" {
continue
}

p := newConanRefrencePackage(
reference,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
)

if p != nil {
pk := *p
pkgs = append(pkgs, pk)
indexToPkgMap[reference.Name] = pk
}

}

// we do not want to store the index list requires in the conan metadata, because it is not useful to have it in
// the SBOM. Instead, we will store it in a map and then use it to build the relationships
// maps pkg.ID to a list of indices
var parsedPkgRequires = map[artifact.ID][]string{}

for idx, node := range cl.GraphLock.Nodes {
metadata := pkg.ConanLockEntry{
metadata := pkg.ConanV1LockEntry{
Ref: node.Ref,
Options: parseOptions(node.Options),
Path: node.Path,
Expand Down Expand Up @@ -102,3 +127,54 @@ func parseOptions(options string) map[string]string {

return o
}

func parseConanV2Reference(ref string) pkg.ConanV2LockEntry {
// very flexible format name/version[@username[/channel]][#rrev][:pkgid[#prev]][%timestamp]
reference := pkg.ConanV2LockEntry{Ref: ref}

parts := strings.SplitN(ref, "%", 2)
if len(parts) == 2 {
ref = parts[0]
reference.TimeStamp = parts[1]
}

parts = strings.SplitN(ref, ":", 2)
if len(parts) == 2 {
ref = parts[0]
parts = strings.SplitN(parts[1], "#", 2)
reference.PackageID = parts[0]
if len(parts) == 2 {
reference.PackageRevision = parts[1]
}
}

parts = strings.SplitN(ref, "#", 2)
if len(parts) == 2 {
ref = parts[0]
reference.RecipeRevision = parts[1]
}

parts = strings.SplitN(ref, "@", 2)
if len(parts) == 2 {
ref = parts[0]
UsernameChannel := parts[1]

parts = strings.SplitN(UsernameChannel, "/", 2)
reference.Username = parts[0]
if len(parts) == 2 {
reference.Channel = parts[1]
}
}

parts = strings.SplitN(ref, "/", 2)
if len(parts) == 2 {
reference.Name = parts[0]
reference.Version = parts[1]
} else {
// consumer conanfile.txt or conanfile.py might not have a name
reference.Name = ""
reference.Version = ref
}

return reference
}
12 changes: 6 additions & 6 deletions syft/pkg/cataloger/cpp/parse_conanlock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestParseConanlock(t *testing.T) {
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
Metadata: pkg.ConanLockEntry{
Metadata: pkg.ConanV1LockEntry{
Ref: "mfast/1.2.2@my_user/my_channel#c6f6387c9b99780f0ee05e25f99d0f39",
Options: map[string]string{
"fPIC": "True",
Expand Down Expand Up @@ -110,7 +110,7 @@ func TestParseConanlock(t *testing.T) {
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
Metadata: pkg.ConanLockEntry{
Metadata: pkg.ConanV1LockEntry{
Ref: "boost/1.75.0#a9c318f067216f900900e044e7af4ab1",
Options: map[string]string{
"addr2line_location": "/usr/bin/addr2line",
Expand Down Expand Up @@ -196,7 +196,7 @@ func TestParseConanlock(t *testing.T) {
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
Metadata: pkg.ConanLockEntry{
Metadata: pkg.ConanV1LockEntry{
Ref: "zlib/1.2.12#c67ce17f2e96b972d42393ce50a76a1a",
Options: map[string]string{
"fPIC": "True",
Expand All @@ -214,7 +214,7 @@ func TestParseConanlock(t *testing.T) {
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
Metadata: pkg.ConanLockEntry{
Metadata: pkg.ConanV1LockEntry{
Ref: "bzip2/1.0.8#62a8031289639043797cf53fa876d0ef",
Options: map[string]string{
"build_executable": "True",
Expand All @@ -233,7 +233,7 @@ func TestParseConanlock(t *testing.T) {
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
Metadata: pkg.ConanLockEntry{
Metadata: pkg.ConanV1LockEntry{
Ref: "libbacktrace/cci.20210118#76e40b760e0bcd602d46db56b22820ab",
Options: map[string]string{
"fPIC": "True",
Expand All @@ -251,7 +251,7 @@ func TestParseConanlock(t *testing.T) {
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
Metadata: pkg.ConanLockEntry{
Metadata: pkg.ConanV1LockEntry{
Ref: "tinyxml2/9.0.0#9f13a36ebfc222cd55fe531a0a8d94d1",
Options: map[string]string{
"fPIC": "True",
Expand Down
16 changes: 14 additions & 2 deletions syft/pkg/conan.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package pkg

// ConanLockEntry represents a single "node" entry from a conan.lock file.
type ConanLockEntry struct {
type ConanV2LockEntry struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Username string `json:"username,omitempty"`
Channel string `json:"channel,omitempty"`
RecipeRevision string `json:"recipe_revision,omitempty"`
PackageID string `json:"package_id,omitempty"`
PackageRevision string `json:"package_revision,omitempty"`
TimeStamp string `json:"timestamp,omitempty"`
Ref string `json:"ref"`
}

// ConanV1LockEntry represents a single "node" entry from a conan.lock file.
type ConanV1LockEntry struct {
Ref string `json:"ref"`
PackageID string `json:"package_id,omitempty"`
Prev string `json:"prev,omitempty"`
Expand Down
Loading