Skip to content

Commit 450bd41

Browse files
committed
fix: module loading regex #1108
1 parent b31365f commit 450bd41

File tree

2 files changed

+138
-3
lines changed

2 files changed

+138
-3
lines changed

internal/nginx/modules.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,8 @@ func updateDynamicModulesStatus() {
9797
return
9898
}
9999

100-
// Regular expression to find loaded dynamic modules in nginx -T output
101-
// Look for lines like "load_module modules/ngx_http_image_filter_module.so;"
102-
loadModuleRe := regexp.MustCompile(`load_module\s+(?:modules/|/.*/)([a-zA-Z0-9_-]+)\.so;`)
100+
// Use the shared regex function to find loaded dynamic modules
101+
loadModuleRe := GetLoadModuleRegex()
103102
matches := loadModuleRe.FindAllStringSubmatch(out, -1)
104103

105104
for _, match := range matches {
@@ -117,6 +116,24 @@ func updateDynamicModulesStatus() {
117116
}
118117
}
119118

119+
// GetLoadModuleRegex returns a compiled regular expression to match nginx load_module statements.
120+
// It matches both quoted and unquoted module paths:
121+
// - load_module "/usr/local/nginx/modules/ngx_stream_module.so";
122+
// - load_module modules/ngx_http_upstream_fair_module.so;
123+
//
124+
// The regex captures the module name (without path and extension).
125+
func GetLoadModuleRegex() *regexp.Regexp {
126+
// Pattern explanation:
127+
// load_module\s+ - matches "load_module" followed by whitespace
128+
// "? - optional opening quote
129+
// (?:[^"\s]+/)? - non-capturing group for optional path (any non-quote, non-space chars ending with /)
130+
// ([a-zA-Z0-9_-]+) - capturing group for module name
131+
// \.so - matches ".so" extension
132+
// "? - optional closing quote
133+
// \s*; - optional whitespace followed by semicolon
134+
return regexp.MustCompile(`load_module\s+"?(?:[^"\s]+/)?([a-zA-Z0-9_-]+)\.so"?\s*;`)
135+
}
136+
120137
func GetModules() *orderedmap.OrderedMap[string, *Module] {
121138
modulesCacheLock.RLock()
122139
cachedModules := modulesCache

internal/nginx/modules_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package nginx
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestGetLoadModuleRegex(t *testing.T) {
8+
testCases := []struct {
9+
name string
10+
input string
11+
expected []string // expected module names
12+
}{
13+
{
14+
name: "quoted absolute path",
15+
input: `load_module "/usr/local/nginx/modules/ngx_stream_module.so";`,
16+
expected: []string{"ngx_stream_module"},
17+
},
18+
{
19+
name: "unquoted relative path",
20+
input: `load_module modules/ngx_http_upstream_fair_module.so;`,
21+
expected: []string{"ngx_http_upstream_fair_module"},
22+
},
23+
{
24+
name: "quoted relative path",
25+
input: `load_module "modules/ngx_http_geoip_module.so";`,
26+
expected: []string{"ngx_http_geoip_module"},
27+
},
28+
{
29+
name: "unquoted absolute path",
30+
input: `load_module /etc/nginx/modules/ngx_http_cache_purge_module.so;`,
31+
expected: []string{"ngx_http_cache_purge_module"},
32+
},
33+
{
34+
name: "multiple modules",
35+
input: `load_module "/path/ngx_module1.so";\nload_module modules/ngx_module2.so;`,
36+
expected: []string{"ngx_module1", "ngx_module2"},
37+
},
38+
{
39+
name: "with extra whitespace",
40+
input: `load_module "modules/ngx_test_module.so" ;`,
41+
expected: []string{"ngx_test_module"},
42+
},
43+
{
44+
name: "no matches",
45+
input: `some other nginx config`,
46+
expected: []string{},
47+
},
48+
}
49+
50+
regex := GetLoadModuleRegex()
51+
52+
for _, tc := range testCases {
53+
t.Run(tc.name, func(t *testing.T) {
54+
matches := regex.FindAllStringSubmatch(tc.input, -1)
55+
56+
if len(matches) != len(tc.expected) {
57+
t.Errorf("Expected %d matches, got %d", len(tc.expected), len(matches))
58+
return
59+
}
60+
61+
for i, match := range matches {
62+
if len(match) < 2 {
63+
t.Errorf("Match %d should have at least 2 groups, got %d", i, len(match))
64+
continue
65+
}
66+
67+
moduleName := match[1]
68+
expectedModule := tc.expected[i]
69+
70+
if moduleName != expectedModule {
71+
t.Errorf("Expected module name %s, got %s", expectedModule, moduleName)
72+
}
73+
}
74+
})
75+
}
76+
}
77+
78+
func TestModulesLoaded(t *testing.T) {
79+
text := `
80+
load_module "/usr/local/nginx/modules/ngx_stream_module.so";
81+
load_module modules/ngx_http_upstream_fair_module.so;
82+
load_module "modules/ngx_http_geoip_module.so";
83+
load_module /etc/nginx/modules/ngx_http_cache_purge_module.so;
84+
`
85+
86+
loadModuleRe := GetLoadModuleRegex()
87+
matches := loadModuleRe.FindAllStringSubmatch(text, -1)
88+
89+
t.Log("matches", matches)
90+
91+
// Expected module names
92+
expectedModules := []string{
93+
"ngx_stream_module",
94+
"ngx_http_upstream_fair_module",
95+
"ngx_http_geoip_module",
96+
"ngx_http_cache_purge_module",
97+
}
98+
99+
if len(matches) != len(expectedModules) {
100+
t.Errorf("Expected %d matches, got %d", len(expectedModules), len(matches))
101+
}
102+
103+
for i, match := range matches {
104+
if len(match) < 2 {
105+
t.Errorf("Match %d should have at least 2 groups, got %d", i, len(match))
106+
continue
107+
}
108+
109+
moduleName := match[1]
110+
expectedModule := expectedModules[i]
111+
112+
t.Logf("Match %d: %s", i, moduleName)
113+
114+
if moduleName != expectedModule {
115+
t.Errorf("Expected module name %s, got %s", expectedModule, moduleName)
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)