-
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
fix(python): skip dev group's deps for poetry #8106
base: main
Are you sure you want to change the base?
Changes from 6 commits
23d4942
a515a32
e2eb795
7de61c8
43adf6c
a45fc4d
d1e2450
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,9 +3,9 @@ package poetry | |
import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" | ||
|
||
var ( | ||
// docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh | ||
// docker run --name poetry --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh | ||
// apk add curl | ||
// curl -sSL https://install.python-poetry.org | python3 - | ||
// curl -sSL https://install.python-poetry.org | POETRY_VERSION=1.1.7 python3 - | ||
// export PATH=/root/.local/bin:$PATH | ||
// poetry new normal && cd normal | ||
// poetry add [email protected] | ||
|
@@ -14,9 +14,9 @@ var ( | |
{ID: "[email protected]", Name: "pypi", Version: "2.1"}, | ||
} | ||
|
||
// docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh | ||
// docker run --name poetry --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh | ||
// apk add curl | ||
// curl -sSL https://install.python-poetry.org | python3 - | ||
// curl -sSL https://install.python-poetry.org | POETRY_VERSION=1.1.7 python3 - | ||
// export PATH=/root/.local/bin:$PATH | ||
// poetry new many && cd many | ||
// curl -o poetry.lock https://raw.githubusercontent.com/python-poetry/poetry/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock | ||
|
@@ -108,9 +108,9 @@ var ( | |
{ID: "[email protected]", DependsOn: []string{"[email protected]"}}, | ||
} | ||
|
||
// docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh | ||
// docker run --name poetry --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh | ||
// apk add curl | ||
// curl -sSL https://install.python-poetry.org | python3 - | ||
// curl -sSL https://install.python-poetry.org | POETRY_VERSION=1.1.7 python3 - | ||
// export PATH=/root/.local/bin:$PATH | ||
// poetry new web && cd web | ||
// poetry add [email protected] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package python | ||
|
||
import "strings" | ||
|
||
// NormalizePkgName normalizes the package name based on pep-0426 | ||
func NormalizePkgName(name string) string { | ||
// The package names don't use `_`, `.` or upper case, but dependency names can contain them. | ||
// We need to normalize those names. | ||
// cf. https://peps.python.org/pep-0426/#name | ||
name = strings.ToLower(name) // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L819 | ||
name = strings.ReplaceAll(name, "_", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L50 | ||
name = strings.ReplaceAll(name, ".", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L816 | ||
return name | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package python_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/aquasecurity/trivy/pkg/dependency/parser/python" | ||
) | ||
|
||
func Test_NormalizePkgName(t *testing.T) { | ||
tests := []struct { | ||
pkgName string | ||
expected string | ||
}{ | ||
{ | ||
pkgName: "SecretStorage", | ||
expected: "secretstorage", | ||
}, | ||
{ | ||
pkgName: "pywin32-ctypes", | ||
expected: "pywin32-ctypes", | ||
}, | ||
{ | ||
pkgName: "jaraco.classes", | ||
expected: "jaraco-classes", | ||
}, | ||
{ | ||
pkgName: "green_gdk", | ||
expected: "green-gdk", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.pkgName, func(t *testing.T) { | ||
assert.Equal(t, tt.expected, python.NormalizePkgName(tt.pkgName)) | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,7 +94,7 @@ func (a poetryAnalyzer) parsePoetryLock(path string, r io.Reader) (*types.Applic | |
func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Application) error { | ||
// Parse pyproject.toml to identify the direct dependencies | ||
path := filepath.Join(dir, types.PyProject) | ||
p, err := a.parsePyProject(fsys, path) | ||
project, err := a.parsePyProject(fsys, path) | ||
if errors.Is(err, fs.ErrNotExist) { | ||
// Assume all the packages are direct dependencies as it cannot identify them from poetry.lock | ||
a.logger.Debug("pyproject.toml not found", log.FilePath(path)) | ||
|
@@ -105,34 +105,68 @@ func (a poetryAnalyzer) mergePyProject(fsys fs.FS, dir string, app *types.Applic | |
|
||
// Identify the direct/transitive dependencies | ||
for i, pkg := range app.Packages { | ||
if _, ok := p[pkg.Name]; ok { | ||
if _, ok := project.Tool.Poetry.Dependencies[pkg.Name]; ok { | ||
app.Packages[i].Relationship = types.RelationshipDirect | ||
} else { | ||
app.Packages[i].Indirect = true | ||
app.Packages[i].Relationship = types.RelationshipIndirect | ||
} | ||
} | ||
|
||
filterProdPackages(project, app) | ||
return nil | ||
} | ||
|
||
func (a poetryAnalyzer) parsePyProject(fsys fs.FS, path string) (map[string]any, error) { | ||
func filterProdPackages(project pyproject.PyProject, app *types.Application) { | ||
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. We should flag the Dev field instead of filtering for 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. @knqyf263 as i wrote in #8080 (comment) - are you sure that we want to add dev deps under I have yet to meet a python user who needs to scan for dev dependencies 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. Hmm. It sounds reasonable, but we should add a new column showing if --include-dev-deps is supported. 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. But if the implementation cost is not so different, it's better to mark dev dependencies. 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. I'll open another PR for this one |
||
packages := lo.SliceToMap(app.Packages, func(pkg types.Package) (string, types.Package) { | ||
return pkg.ID, pkg | ||
}) | ||
|
||
visited := make(map[string]struct{}) | ||
deps := project.Tool.Poetry.Dependencies | ||
|
||
for group, groupDeps := range project.Tool.Poetry.Groups { | ||
if group == "dev" { | ||
continue | ||
} | ||
deps = lo.Assign(deps, groupDeps.Dependencies) | ||
} | ||
|
||
for _, pkg := range packages { | ||
if _, prodDep := deps[pkg.Name]; !prodDep { | ||
continue | ||
} | ||
walkPackageDeps(pkg.ID, packages, visited) | ||
} | ||
|
||
app.Packages = lo.Filter(app.Packages, func(pkg types.Package, _ int) bool { | ||
_, ok := visited[pkg.ID] | ||
return ok | ||
}) | ||
} | ||
|
||
func walkPackageDeps(pkgID string, packages map[string]types.Package, visited map[string]struct{}) { | ||
if _, ok := visited[pkgID]; ok { | ||
return | ||
} | ||
visited[pkgID] = struct{}{} | ||
for _, dep := range packages[pkgID].DependsOn { | ||
walkPackageDeps(dep, packages, visited) | ||
} | ||
} | ||
|
||
func (a poetryAnalyzer) parsePyProject(fsys fs.FS, path string) (pyproject.PyProject, error) { | ||
// Parse pyproject.toml | ||
f, err := fsys.Open(path) | ||
if err != nil { | ||
return nil, xerrors.Errorf("file open error: %w", err) | ||
return pyproject.PyProject{}, xerrors.Errorf("file open error: %w", err) | ||
} | ||
defer f.Close() | ||
|
||
parsed, err := a.pyprojectParser.Parse(f) | ||
project, err := a.pyprojectParser.Parse(f) | ||
if err != nil { | ||
return nil, err | ||
return pyproject.PyProject{}, err | ||
} | ||
|
||
// Packages from `pyproject.toml` can use uppercase characters, `.` and `_`. | ||
parsed = lo.MapKeys(parsed, func(_ any, pkgName string) string { | ||
return poetry.NormalizePkgName(pkgName) | ||
}) | ||
|
||
return parsed, nil | ||
return project, nil | ||
} |
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.
nit: for consistency and stacktrace
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.
Fixed d1e2450