-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
feat(c): add license support for conan lock files #6329
Merged
Merged
Changes from 13 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
441eaa7
feat(conan): detect licenses using cache dir
DmitriyLewen 052f193
test(conan): update prev tests, add new test with cache dir
DmitriyLewen d54d1ce
docs: update conan page
DmitriyLewen 2b06478
fix: check empty app
DmitriyLewen eb3d53d
fix test
DmitriyLewen 58efb64
test: fix `Test_conanLockAnalyzer_Required` test
DmitriyLewen 4a56a0d
fix: sort libs
DmitriyLewen 9cc7e88
Merge branch 'main' of github.com:DmitriyLewen/trivy into feat-conan/…
DmitriyLewen 61af875
refactor after merge main branch
DmitriyLewen 89003d6
refactor: remove extra spaces
DmitriyLewen 9b5d8ce
refactor: use strings.Cut for attributes
DmitriyLewen 7bedafb
refactor: remove bool output for `detectAttribute`
DmitriyLewen 18b1383
test: add tests for detectAttribute
DmitriyLewen 0d67966
refactor: use strings.Trim
DmitriyLewen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,42 +1,165 @@ | ||||||
package conan | ||||||
|
||||||
import ( | ||||||
"bufio" | ||||||
"context" | ||||||
"io" | ||||||
"io/fs" | ||||||
"os" | ||||||
"path" | ||||||
"path/filepath" | ||||||
"sort" | ||||||
"strings" | ||||||
|
||||||
"golang.org/x/xerrors" | ||||||
|
||||||
"github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan" | ||||||
godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" | ||||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer" | ||||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" | ||||||
"github.com/aquasecurity/trivy/pkg/fanal/types" | ||||||
"github.com/aquasecurity/trivy/pkg/log" | ||||||
"github.com/aquasecurity/trivy/pkg/utils/fsutils" | ||||||
) | ||||||
|
||||||
func init() { | ||||||
analyzer.RegisterAnalyzer(&conanLockAnalyzer{}) | ||||||
analyzer.RegisterPostAnalyzer(analyzer.TypeConanLock, newConanLockAnalyzer) | ||||||
} | ||||||
|
||||||
const ( | ||||||
version = 1 | ||||||
version = 2 | ||||||
) | ||||||
|
||||||
// conanLockAnalyzer analyzes conan.lock | ||||||
type conanLockAnalyzer struct{} | ||||||
type conanLockAnalyzer struct { | ||||||
logger *log.Logger | ||||||
parser godeptypes.Parser | ||||||
} | ||||||
|
||||||
func newConanLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { | ||||||
return conanLockAnalyzer{ | ||||||
logger: log.WithPrefix("conan"), | ||||||
parser: conan.NewParser(), | ||||||
}, nil | ||||||
} | ||||||
|
||||||
func (a conanLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { | ||||||
required := func(filePath string, d fs.DirEntry) bool { | ||||||
return a.Required(filePath, nil) | ||||||
} | ||||||
|
||||||
func (a conanLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { | ||||||
p := conan.NewParser() | ||||||
res, err := language.Analyze(types.Conan, input.FilePath, input.Content, p) | ||||||
licenses, err := licensesFromCache() | ||||||
if err != nil { | ||||||
return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err) | ||||||
a.logger.Debug("Unable to parse cache directory to obtain licenses", log.Err(err)) | ||||||
} | ||||||
return res, nil | ||||||
|
||||||
var apps []types.Application | ||||||
if err = fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { | ||||||
app, err := language.Parse(types.Conan, filePath, r, a.parser) | ||||||
if err != nil { | ||||||
return xerrors.Errorf("%s parse error: %w", filePath, err) | ||||||
} | ||||||
|
||||||
if app == nil { | ||||||
return nil | ||||||
} | ||||||
|
||||||
// Fill licenses | ||||||
for i, lib := range app.Libraries { | ||||||
if license, ok := licenses[lib.Name]; ok { | ||||||
app.Libraries[i].Licenses = []string{ | ||||||
license, | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
sort.Sort(app.Libraries) | ||||||
apps = append(apps, *app) | ||||||
return nil | ||||||
}); err != nil { | ||||||
return nil, xerrors.Errorf("unable to parse conan lock file: %w", err) | ||||||
} | ||||||
|
||||||
return &analyzer.AnalysisResult{ | ||||||
Applications: apps, | ||||||
}, nil | ||||||
} | ||||||
|
||||||
func licensesFromCache() (map[string]string, error) { | ||||||
required := func(filePath string, d fs.DirEntry) bool { | ||||||
return filepath.Base(filePath) == "conanfile.py" | ||||||
} | ||||||
|
||||||
// cf. https://docs.conan.io/1/mastering/custom_cache.html | ||||||
cacheDir := os.Getenv("CONAN_USER_HOME") | ||||||
if cacheDir == "" { | ||||||
cacheDir, _ = os.UserHomeDir() | ||||||
} | ||||||
cacheDir = path.Join(cacheDir, ".conan", "data") | ||||||
|
||||||
if !fsutils.DirExists(cacheDir) { | ||||||
return nil, xerrors.Errorf("the Conan cache directory (%s) was not found.", cacheDir) | ||||||
} | ||||||
|
||||||
licenses := make(map[string]string) | ||||||
if err := fsutils.WalkDir(os.DirFS(cacheDir), ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { | ||||||
scanner := bufio.NewScanner(r) | ||||||
var name, license string | ||||||
for scanner.Scan() { | ||||||
line := strings.TrimSpace(scanner.Text()) | ||||||
|
||||||
// cf. https://docs.conan.io/1/reference/conanfile/attributes.html#name | ||||||
if n := detectAttribute("name", line); n != "" { | ||||||
name = n | ||||||
// Check that the license is already found | ||||||
if license != "" { | ||||||
break | ||||||
} | ||||||
} | ||||||
// cf. https://docs.conan.io/1/reference/conanfile/attributes.html#license | ||||||
if l := detectAttribute("license", line); l != "" { | ||||||
license = l | ||||||
// Check that the name is already found | ||||||
if name != "" { | ||||||
break | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
// Skip files without name/license | ||||||
if name == "" || license == "" { | ||||||
return nil | ||||||
} | ||||||
knqyf263 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
licenses[name] = license | ||||||
return nil | ||||||
}); err != nil { | ||||||
return nil, xerrors.Errorf("the Conan cache dir (%s) walk error: %w", cacheDir, err) | ||||||
} | ||||||
return licenses, nil | ||||||
} | ||||||
|
||||||
// detectAttribute detects conan attribute (name, license, etc.) from line | ||||||
// cf. https://docs.conan.io/1/reference/conanfile/attributes.html | ||||||
func detectAttribute(attributeName, line string) string { | ||||||
if !strings.HasPrefix(line, attributeName) { | ||||||
return "" | ||||||
} | ||||||
|
||||||
// e.g. `license = "Apache or MIT"` -> ` "Apache or MIT"` -> `"Apache or MIT"` -> `Apache or MIT` | ||||||
if name, v, ok := strings.Cut(line, "="); ok && strings.TrimSpace(name) == attributeName { | ||||||
attr := strings.TrimSpace(v) | ||||||
return strings.TrimPrefix(strings.TrimSuffix(attr, "\""), "\"") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. strings.Trim seems better.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed in 0d67966 |
||||||
} | ||||||
|
||||||
return "" | ||||||
} | ||||||
|
||||||
func (a conanLockAnalyzer) Required(_ string, fileInfo os.FileInfo) bool { | ||||||
func (a conanLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { | ||||||
// Lock file name can be anything | ||||||
// cf. https://docs.conan.io/en/latest/versioning/lockfiles/introduction.html#locking-dependencies | ||||||
// cf. https://docs.conan.io/1/versioning/lockfiles/introduction.html#locking-dependencies | ||||||
// By default, we only check the default filename - `conan.lock`. | ||||||
return fileInfo.Name() == types.ConanLock | ||||||
return filepath.Base(filePath) == types.ConanLock | ||||||
} | ||||||
|
||||||
func (a conanLockAnalyzer) Type() analyzer.Type { | ||||||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't
conanfile.txt
used?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cache dir contains only
conanfile.py
files:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
conanfile.py
required? I'm wondering if we just don't find a project usingconanfile.txt
as most projects useconanfile.py
. But we can handleconanfile.txt
if we find such a case.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC only
conanfile.py
contains attributes - https://docs.conan.io/2/reference/conanfile_txt.html#conanfile-txtTherefore, we can't detect package name/license from
conanfile.txt
files.Then we don't need to parse the conanfile.txt files.