From 83056a4b6fda29c6c1e6caab5dc60c52c4474e6a Mon Sep 17 00:00:00 2001 From: Alfredo Deza Date: Tue, 21 Jul 2020 14:25:51 -0400 Subject: [PATCH] cataloger: add a requirements.txt parser Signed-off-by: Alfredo Deza --- imgbom/cataloger/python/parse_requirements.go | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 imgbom/cataloger/python/parse_requirements.go diff --git a/imgbom/cataloger/python/parse_requirements.go b/imgbom/cataloger/python/parse_requirements.go new file mode 100644 index 000000000..decf25d88 --- /dev/null +++ b/imgbom/cataloger/python/parse_requirements.go @@ -0,0 +1,69 @@ +package python + +import ( + "bufio" + "fmt" + "io" + "strings" + + "github.com/anchore/imgbom/imgbom/pkg" +) + +func parseRequirementsTxt(_ string, reader io.Reader) ([]pkg.Package, error) { + packages := make([]pkg.Package, 0) + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + + line = strings.TrimRight(line, "\n") + + switch { + case strings.HasPrefix(line, "#"): + // commented out line, skip + continue + case strings.HasPrefix(line, "-e"): + // editable packages aren't parsed (yet) + continue + case len(strings.Split(line, "==")) < 2: + // a package without a version, or a range (unpinned) which + // does not tell us exactly what will be installed + // XXX only needed if we want to log this, otherwise the next case catches it + continue + case len(strings.Split(line, "==")) == 2: + // remove comments if present + uncommented := removeTrailingComment(line) + // parse a new requirement + parts := strings.Split(uncommented, "==") + name := strings.TrimSpace(parts[0]) + version := strings.TrimSpace(parts[1]) + packages = append(packages, pkg.Package{ + Name: name, + Version: version, + Language: pkg.Python, + Type: pkg.PythonRequirementsPkg, + }) + default: + continue + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to parse python requirements file: %w", err) + } + + return packages, nil +} + +func removeTrailingComment(line string) string { + parts := strings.Split(line, "#") + switch len(parts) { + case 1: + // there aren't any comments + return line + default: + // any number of "#" means we only want the first part, assuming this + // isn't prefixed with "#" (up to the caller) + return parts[0] + } +}