fix: properly parse conan ref and include user and channel (#2034)

* fix: properly parse conan ref and include user and channel

Signed-off-by: Stefan Profanter <stefan.profanter@agile-robots.com>

* unexport the conanRef type

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Stefan Profanter <stefan.profanter@agile-robots.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Stefan Profanter 2023-08-23 19:51:07 +02:00 committed by GitHub
parent a2b389523d
commit 07ac640ac5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 131 additions and 48 deletions

View File

@ -8,23 +8,67 @@ import (
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package { type conanRef struct {
fields := strings.Split(strings.TrimSpace(m.Ref), "/") Name string
if len(fields) < 2 { Version string
return nil User string
Channel string
Revision string
Timestamp string
} }
pkgName, pkgVersion := fields[0], fields[1] func splitConanRef(ref string) *conanRef {
// Conan ref format is:
// pkg/0.1@user/channel#rrev%timestamp
// This method is based on conan's ref.loads method:
// https://github.com/conan-io/conan/blob/release/2.0/conans/model/recipe_ref.py#L93C21-L93C21
if pkgName == "" || pkgVersion == "" { var cref conanRef
// timestamp
tokens := strings.Split(ref, "%")
text := tokens[0]
if len(tokens) == 2 {
cref.Timestamp = tokens[1]
}
// revision
tokens = strings.Split(text, "#")
ref = tokens[0]
if len(tokens) == 2 {
cref.Revision = tokens[1]
}
// name and version are always given
tokens = strings.Split(ref, "@")
nameAndVersion := strings.Split(tokens[0], "/")
if len(nameAndVersion) < 2 || nameAndVersion[0] == "" || nameAndVersion[1] == "" {
return nil
}
cref.Name = nameAndVersion[0]
cref.Version = nameAndVersion[1]
// user and channel
if len(tokens) == 2 && tokens[1] != "" {
tokens = strings.Split(tokens[1], "/")
if len(tokens) == 2 {
cref.User = tokens[0]
cref.Channel = tokens[1]
}
}
return &cref
}
func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package {
ref := splitConanRef(m.Ref)
if ref == nil {
return nil return nil
} }
p := pkg.Package{ p := pkg.Package{
Name: pkgName, Name: ref.Name,
Version: pkgVersion, Version: ref.Version,
Locations: file.NewLocationSet(locations...), Locations: file.NewLocationSet(locations...),
PURL: packageURL(pkgName, pkgVersion), PURL: packageURL(ref),
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType, MetadataType: pkg.ConanMetadataType,
@ -37,22 +81,16 @@ func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.P
} }
func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *pkg.Package { func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *pkg.Package {
fields := strings.Split(strings.Split(m.Ref, "@")[0], "/") ref := splitConanRef(m.Ref)
if len(fields) < 2 { if ref == nil {
return nil
}
pkgName, pkgVersion := fields[0], fields[1]
if pkgName == "" || pkgVersion == "" {
return nil return nil
} }
p := pkg.Package{ p := pkg.Package{
Name: pkgName, Name: ref.Name,
Version: pkgVersion, Version: ref.Version,
Locations: file.NewLocationSet(locations...), Locations: file.NewLocationSet(locations...),
PURL: packageURL(pkgName, pkgVersion), PURL: packageURL(ref),
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanLockMetadataType, MetadataType: pkg.ConanLockMetadataType,
@ -64,13 +102,20 @@ func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *p
return &p return &p
} }
func packageURL(name, version string) string { func packageURL(ref *conanRef) string {
qualifiers := packageurl.Qualifiers{}
if ref.Channel != "" {
qualifiers = append(qualifiers, packageurl.Qualifier{
Key: "channel",
Value: ref.Channel,
})
}
return packageurl.NewPackageURL( return packageurl.NewPackageURL(
packageurl.TypeConan, packageurl.TypeConan,
"", ref.User,
name, ref.Name,
version, ref.Version,
nil, // TODO: no qualifiers (...yet) qualifiers,
"", "",
).ToString() ).ToString()
} }

View File

@ -36,7 +36,7 @@ func parseConanfile(_ file.Resolver, _ *generic.Environment, reader file.Locatio
switch { switch {
case strings.Contains(line, "[requires]"): case strings.Contains(line, "[requires]"):
inRequirements = true inRequirements = true
case strings.ContainsAny(line, "[]#"): case strings.ContainsAny(line, "[]") || strings.HasPrefix(strings.TrimSpace(line), "#"):
inRequirements = false inRequirements = false
} }

View File

@ -52,13 +52,13 @@ func TestParseConanfile(t *testing.T) {
{ {
Name: "spdlog", Name: "spdlog",
Version: "1.9.2", Version: "1.9.2",
PURL: "pkg:conan/spdlog@1.9.2", PURL: "pkg:conan/my_user/spdlog@1.9.2?channel=my_channel",
Locations: fixtureLocationSet, Locations: fixtureLocationSet,
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType, MetadataType: pkg.ConanMetadataType,
Metadata: pkg.ConanMetadata{ Metadata: pkg.ConanMetadata{
Ref: "spdlog/1.9.2", Ref: "spdlog/1.9.2@my_user/my_channel#1234567%%987654",
}, },
}, },
{ {
@ -70,19 +70,19 @@ func TestParseConanfile(t *testing.T) {
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType, MetadataType: pkg.ConanMetadataType,
Metadata: pkg.ConanMetadata{ Metadata: pkg.ConanMetadata{
Ref: "sdl/2.0.20", Ref: "sdl/2.0.20#1234567%%987654",
}, },
}, },
{ {
Name: "fltk", Name: "fltk",
Version: "1.3.8", Version: "1.3.8",
PURL: "pkg:conan/fltk@1.3.8", PURL: "pkg:conan/my_user/fltk@1.3.8?channel=my_channel",
Locations: fixtureLocationSet, Locations: fixtureLocationSet,
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanMetadataType, MetadataType: pkg.ConanMetadataType,
Metadata: pkg.ConanMetadata{ Metadata: pkg.ConanMetadata{
Ref: "fltk/1.3.8", Ref: "fltk/1.3.8@my_user/my_channel",
}, },
}, },
} }

View File

@ -13,20 +13,49 @@ func TestParseConanlock(t *testing.T) {
fixture := "test-fixtures/conan.lock" fixture := "test-fixtures/conan.lock"
expected := []pkg.Package{ expected := []pkg.Package{
{ {
Name: "zlib", Name: "spdlog",
Version: "1.2.12", Version: "1.11.0",
PURL: "pkg:conan/zlib@1.2.12", PURL: "pkg:conan/spdlog@1.11.0",
Locations: file.NewLocationSet(file.NewLocation(fixture)), Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP, Language: pkg.CPP,
Type: pkg.ConanPkg, Type: pkg.ConanPkg,
MetadataType: pkg.ConanLockMetadataType, MetadataType: pkg.ConanLockMetadataType,
Metadata: pkg.ConanLockMetadata{ Metadata: pkg.ConanLockMetadata{
Ref: "zlib/1.2.12", Ref: "spdlog/1.11.0",
Options: map[string]string{ Options: map[string]string{
"fPIC": "True", "fPIC": "True",
"header_only": "False",
"no_exceptions": "False",
"shared": "False", "shared": "False",
"wchar_filenames": "False",
"wchar_support": "False",
"fmt:fPIC": "True",
"fmt:header_only": "False",
"fmt:shared": "False",
"fmt:with_fmt_alias": "False",
"fmt:with_os_api": "True",
},
Path: "conanfile.py",
Context: "host",
},
},
{
Name: "fmt",
Version: "9.1.0",
PURL: "pkg:conan/my_user/fmt@9.1.0?channel=my_channel",
Locations: file.NewLocationSet(file.NewLocation(fixture)),
Language: pkg.CPP,
Type: pkg.ConanPkg,
MetadataType: pkg.ConanLockMetadataType,
Metadata: pkg.ConanLockMetadata{
Ref: "fmt/9.1.0@my_user/my_channel#6708c9d84f98d56a6d9f2e6c2d5639ba",
Options: map[string]string{
"fPIC": "True",
"header_only": "False",
"shared": "False",
"with_fmt_alias": "False",
"with_os_api": "True",
}, },
Path: "all/conanfile.py",
Context: "host", Context: "host",
}, },
}, },

View File

@ -2,15 +2,24 @@
"graph_lock": { "graph_lock": {
"nodes": { "nodes": {
"0": { "0": {
"ref": "zlib/1.2.12", "ref": "spdlog/1.11.0",
"options": "fPIC=True\nshared=False", "options": "fPIC=True\nheader_only=False\nno_exceptions=False\nshared=False\nwchar_filenames=False\nwchar_support=False\nfmt:fPIC=True\nfmt:header_only=False\nfmt:shared=False\nfmt:with_fmt_alias=False\nfmt:with_os_api=True",
"requires": [], "requires": [
"path": "all/conanfile.py", "1"
],
"path": "conanfile.py",
"context": "host"
},
"1": {
"ref": "fmt/9.1.0@my_user/my_channel#6708c9d84f98d56a6d9f2e6c2d5639ba",
"options": "fPIC=True\nheader_only=False\nshared=False\nwith_fmt_alias=False\nwith_os_api=True",
"package_id": "2c09c8f84c016041549fcee94e4caae5d89424b6",
"prev": "9f5ab13fc7c73e4a9f87e4e213f2cfa4",
"context": "host" "context": "host"
} }
}, },
"revisions_enabled": false "revisions_enabled": true
}, },
"version": "0.4", "version": "0.4",
"profile_host": "[settings]\narch=x86_64\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++\ncompiler.version=9\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n" "profile_host": "[settings]\narch=x86_64\narch.march=None\narch_build=x86_64\nbuild_type=Release\ncompiler=gcc\ncompiler.libcxx=libstdc++11\ncompiler.version=11\nos=Linux\nos_build=Linux\n[options]\n[build_requires]\n[env]\n"
} }

View File

@ -4,9 +4,9 @@
catch2/2.13.8 catch2/2.13.8
docopt.cpp/0.6.3 docopt.cpp/0.6.3
fmt/8.1.1 fmt/8.1.1
spdlog/1.9.2 spdlog/1.9.2@my_user/my_channel#1234567%%987654
sdl/2.0.20 sdl/2.0.20#1234567%%987654
fltk/1.3.8 fltk/1.3.8@my_user/my_channel
[generators] [generators]
cmake_find_package_multi cmake_find_package_multi