-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
poetry.go
131 lines (109 loc) · 3.64 KB
/
poetry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package poetry
import (
"context"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/dependency/parser/python/poetry"
"github.com/aquasecurity/trivy/pkg/dependency/parser/python/pyproject"
"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.RegisterPostAnalyzer(analyzer.TypePoetry, newPoetryAnalyzer)
}
const version = 1
type poetryAnalyzer struct {
logger *log.Logger
pyprojectParser *pyproject.Parser
lockParser language.Parser
}
func newPoetryAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) {
return &poetryAnalyzer{
logger: log.WithPrefix("poetry"),
pyprojectParser: pyproject.NewParser(),
lockParser: poetry.NewParser(),
}, nil
}
func (a poetryAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) {
var apps []types.Application
required := func(path string, d fs.DirEntry) bool {
return filepath.Base(path) == types.PoetryLock
}
err := fsutils.WalkDir(input.FS, ".", required, func(path string, d fs.DirEntry, r io.Reader) error {
// Parse poetry.lock
app, err := a.parsePoetryLock(path, r)
if err != nil {
return xerrors.Errorf("parse error: %w", err)
} else if app == nil {
return nil
}
// Parse pyproject.toml alongside poetry.lock to identify the direct dependencies
if err = a.mergePyProject(input.FS, filepath.Dir(path), app); err != nil {
a.logger.Warn("Unable to parse pyproject.toml to identify direct dependencies",
log.String("path", filepath.Join(filepath.Dir(path), types.PyProject)), log.Err(err))
}
apps = append(apps, *app)
return nil
})
if err != nil {
return nil, xerrors.Errorf("poetry walk error: %w", err)
}
return &analyzer.AnalysisResult{
Applications: apps,
}, nil
}
func (a poetryAnalyzer) Required(filePath string, _ os.FileInfo) bool {
fileName := filepath.Base(filePath)
return fileName == types.PoetryLock || fileName == types.PyProject
}
func (a poetryAnalyzer) Type() analyzer.Type {
return analyzer.TypePoetry
}
func (a poetryAnalyzer) Version() int {
return version
}
func (a poetryAnalyzer) parsePoetryLock(path string, r io.Reader) (*types.Application, error) {
return language.Parse(types.Poetry, path, r, a.lockParser)
}
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)
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.String("path", path))
return nil
} else if err != nil {
return xerrors.Errorf("unable to parse %s: %w", path, err)
}
for i, pkg := range app.Packages {
// Identify the direct/transitive dependencies
if _, ok := p[pkg.Name]; ok {
app.Packages[i].Relationship = types.RelationshipDirect
} else {
app.Packages[i].Indirect = true
app.Packages[i].Relationship = types.RelationshipIndirect
}
}
return nil
}
func (a poetryAnalyzer) parsePyProject(fsys fs.FS, path string) (map[string]any, error) {
// Parse pyproject.toml
f, err := fsys.Open(path)
if err != nil {
return nil, xerrors.Errorf("file open error: %w", err)
}
defer f.Close()
parsed, err := a.pyprojectParser.Parse(f)
if err != nil {
return nil, err
}
return parsed, nil
}