From 759909f611619b7895abcaa9160e31fac917322d Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Tue, 18 Nov 2025 09:19:41 -0500 Subject: [PATCH] fix: emit lua rockspec dependencies in metadata (#4376) The types / schema allowed for this field to begin with but it wasn't set. Signed-off-by: Will Murphy --- syft/pkg/cataloger/lua/parse_rockspec.go | 38 +++++- syft/pkg/cataloger/lua/parse_rockspec_test.go | 111 ++++++++++++++++++ 2 files changed, 147 insertions(+), 2 deletions(-) diff --git a/syft/pkg/cataloger/lua/parse_rockspec.go b/syft/pkg/cataloger/lua/parse_rockspec.go index 2e39cdb01..f49107688 100644 --- a/syft/pkg/cataloger/lua/parse_rockspec.go +++ b/syft/pkg/cataloger/lua/parse_rockspec.go @@ -35,6 +35,7 @@ func parseRockspec(ctx context.Context, resolver file.Resolver, _ *generic.Envir } var name, version, license, homepage, description, url string + var dependencies map[string]string for _, node := range doc.value { switch node.key { @@ -60,6 +61,16 @@ func parseRockspec(ctx context.Context, resolver file.Resolver, _ *generic.Envir license = strings.ReplaceAll(child.String(), " ", "-") } } + case "dependencies": + if dependencies == nil { + dependencies = make(map[string]string) + } + for _, child := range node.Slice() { + depName, depVersion := parseDependency(child.String()) + if depName != "" { + dependencies[depName] = depVersion + } + } } } @@ -73,11 +84,34 @@ func parseRockspec(ctx context.Context, resolver file.Resolver, _ *generic.Envir Repository: repository{ URL: url, }, - Homepage: homepage, - Description: description, + Homepage: homepage, + Description: description, + Dependencies: dependencies, }, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ) return []pkg.Package{p}, nil, nil } + +// parseDependency extracts the package name and version constraint from a dependency string. +// Examples: +// - "lua >= 5.1" -> ("lua", ">= 5.1") +// - "lpeg" -> ("lpeg", "") +// - "lualogging >= 1.4.0, < 2.0.0" -> ("lualogging", ">= 1.4.0, < 2.0.0") +func parseDependency(dep string) (name string, version string) { + dep = strings.TrimSpace(dep) + if dep == "" { + return "", "" + } + + // Find the first space which separates package name from version constraint + parts := strings.SplitN(dep, " ", 2) + name = strings.TrimSpace(parts[0]) + + if len(parts) == 2 { + version = strings.TrimSpace(parts[1]) + } + + return name, version +} diff --git a/syft/pkg/cataloger/lua/parse_rockspec_test.go b/syft/pkg/cataloger/lua/parse_rockspec_test.go index e2ae3b48e..85a90be14 100644 --- a/syft/pkg/cataloger/lua/parse_rockspec_test.go +++ b/syft/pkg/cataloger/lua/parse_rockspec_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/stretchr/testify/assert" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" @@ -33,6 +35,38 @@ func TestParseRockspec(t *testing.T) { Homepage: "https://konghq.com", Description: "Kong is a scalable and customizable API Management Layer built on top of Nginx.", URL: "git+https://github.com/Kong/kong.git", + Dependencies: map[string]string{ + "inspect": "== 3.1.3", + "luasec": "== 1.3.2", + "luasocket": "== 3.0-rc1", + "penlight": "== 1.13.1", + "lua-resty-http": "== 0.17.1", + "lua-resty-jit-uuid": "== 0.0.7", + "lua-ffi-zlib": "== 0.6", + "multipart": "== 0.5.9", + "version": "== 1.0.1", + "kong-lapis": "== 1.16.0.1", + "kong-pgmoon": "== 1.16.2", + "luatz": "== 0.4", + "lua_system_constants": "== 0.1.4", + "lyaml": "== 6.2.8", + "luasyslog": "== 2.0.1", + "lua_pack": "== 2.0.0", + "binaryheap": ">= 0.4", + "luaxxhash": ">= 1.0", + "lua-protobuf": "== 0.5.0", + "lua-resty-healthcheck": "== 3.0.1", + "lua-messagepack": "== 0.5.4", + "lua-resty-aws": "== 1.3.6", + "lua-resty-openssl": "== 1.2.0", + "lua-resty-counter": "== 0.2.1", + "lua-resty-ipmatcher": "== 0.6.1", + "lua-resty-acme": "== 0.12.0", + "lua-resty-session": "== 4.0.5", + "lua-resty-timer-ng": "== 0.2.6", + "lpeg": "== 1.1.0", + "lua-resty-ljsonschema": "== 1.1.6-2", + }, }, }, }, @@ -54,6 +88,9 @@ func TestParseRockspec(t *testing.T) { Homepage: "http://www.inf.puc-rio.br/~roberto/lpeg.html", Description: "Parsing Expression Grammars For Lua", URL: "http://www.inf.puc-rio.br/~roberto/lpeg/lpeg-1.0.2.tar.gz", + Dependencies: map[string]string{ + "lua": ">= 5.1", + }, }, }, }, @@ -75,6 +112,10 @@ func TestParseRockspec(t *testing.T) { Homepage: "https://github.com/Kong/pgmoon", Description: "Postgres driver for OpenResty and Lua", URL: "git+https://github.com/kong/pgmoon.git", + Dependencies: map[string]string{ + "lua": ">= 5.1", + "lpeg": "", + }, }, }, }, @@ -96,6 +137,10 @@ func TestParseRockspec(t *testing.T) { Homepage: "https://github.com/lunarmodules/luasyslog", Description: "Syslog logging for Lua", URL: "git://github.com/lunarmodules/luasyslog.git", + Dependencies: map[string]string{ + "lua": ">= 5.1", + "lualogging": ">= 1.4.0, < 2.0.0", + }, }, }, }, @@ -115,3 +160,69 @@ func Test_corruptRockspec(t *testing.T) { WithError(). TestParser(t, parseRockspec) } + +func Test_parseDependency(t *testing.T) { + tests := []struct { + name string + input string + expectedName string + expectedVersion string + }{ + { + name: "dependency with >= constraint", + input: "lua >= 5.1", + expectedName: "lua", + expectedVersion: ">= 5.1", + }, + { + name: "dependency with == constraint", + input: "inspect == 3.1.3", + expectedName: "inspect", + expectedVersion: "== 3.1.3", + }, + { + name: "dependency without constraint", + input: "lpeg", + expectedName: "lpeg", + expectedVersion: "", + }, + { + name: "dependency with complex constraint", + input: "lualogging >= 1.4.0, < 2.0.0", + expectedName: "lualogging", + expectedVersion: ">= 1.4.0, < 2.0.0", + }, + { + name: "dependency with version including dash", + input: "luasocket == 3.0-rc1", + expectedName: "luasocket", + expectedVersion: "== 3.0-rc1", + }, + { + name: "dependency with extra whitespace", + input: " kong-pgmoon == 1.16.2 ", + expectedName: "kong-pgmoon", + expectedVersion: "== 1.16.2", + }, + { + name: "empty string", + input: "", + expectedName: "", + expectedVersion: "", + }, + { + name: "whitespace only", + input: " ", + expectedName: "", + expectedVersion: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actualName, actualVersion := parseDependency(test.input) + assert.Equal(t, test.expectedName, actualName) + assert.Equal(t, test.expectedVersion, actualVersion) + }) + } +}