@@ -42,6 +42,7 @@ func ParseProxyTargetsAndUpstreamsFromRawContent(content string) *ParseResult {
4242 upstreams := make (map [string ][]ProxyTarget )
4343
4444 // First, collect all upstream names and their contexts
45+ // Also collect literal variable assignments from `set $var value;`
4546 upstreamNames := make (map [string ]bool )
4647 upstreamContexts := make (map [string ]* TheUpstreamContext )
4748 upstreamRegex := regexp .MustCompile (`(?s)upstream\s+([^\s]+)\s*\{([^}]+)\}` )
@@ -92,13 +93,24 @@ func ParseProxyTargetsAndUpstreamsFromRawContent(content string) *ParseResult {
9293 }
9394 }
9495
96+ // Collect simple literal variables defined via `set $var value;`
97+ // Only variables with literal values (no nginx variables inside) are recorded.
98+ variableValues := extractLiteralSetVariables (content )
99+
95100 // Parse proxy_pass directives, but skip upstream references
96101 proxyPassRegex := regexp .MustCompile (`(?m)^\s*proxy_pass\s+([^;]+);` )
97102 proxyMatches := proxyPassRegex .FindAllStringSubmatch (content , - 1 )
98103
99104 for _ , match := range proxyMatches {
100105 if len (match ) >= 2 {
101- proxyPassURL := strings .TrimSpace (match [1 ])
106+ rawValue := strings .TrimSpace (match [1 ])
107+
108+ // If the value is a single variable like `$target`, try to resolve it from `set $target ...;`
109+ if resolved , ok := resolveSingleVariable (rawValue , variableValues ); ok {
110+ rawValue = resolved
111+ }
112+
113+ proxyPassURL := rawValue
102114 // Skip if this proxy_pass references an upstream
103115 if ! isUpstreamReference (proxyPassURL , upstreamNames ) {
104116 target := parseProxyPassURL (proxyPassURL , "proxy_pass" )
@@ -115,7 +127,14 @@ func ParseProxyTargetsAndUpstreamsFromRawContent(content string) *ParseResult {
115127
116128 for _ , match := range grpcMatches {
117129 if len (match ) >= 2 {
118- grpcPassURL := strings .TrimSpace (match [1 ])
130+ rawValue := strings .TrimSpace (match [1 ])
131+
132+ // If the value is a single variable like `$target`, try to resolve it from `set $target ...;`
133+ if resolved , ok := resolveSingleVariable (rawValue , variableValues ); ok {
134+ rawValue = resolved
135+ }
136+
137+ grpcPassURL := rawValue
119138 // Skip if this grpc_pass references an upstream
120139 if ! isUpstreamReference (grpcPassURL , upstreamNames ) {
121140 target := parseProxyPassURL (grpcPassURL , "grpc_pass" )
@@ -391,3 +410,63 @@ func isUpstreamReference(passURL string, upstreamNames map[string]bool) bool {
391410
392411 return false
393412}
413+
414+ // extractLiteralSetVariables parses `set $var value;` directives from the entire content and
415+ // returns a map of variable name to its literal value. Values containing nginx variables are ignored.
416+ func extractLiteralSetVariables (content string ) map [string ]string {
417+ result := make (map [string ]string )
418+
419+ // Capture variable name and raw value (without trailing semicolon)
420+ setRegex := regexp .MustCompile (`(?m)^\s*set\s+\$([A-Za-z0-9_]+)\s+([^;]+);` )
421+ matches := setRegex .FindAllStringSubmatch (content , - 1 )
422+ for _ , m := range matches {
423+ if len (m ) < 3 {
424+ continue
425+ }
426+ name := m [1 ]
427+ value := strings .TrimSpace (m [2 ])
428+
429+ // Remove surrounding quotes if any
430+ if len (value ) >= 2 {
431+ if (strings .HasPrefix (value , `"` ) && strings .HasSuffix (value , `"` )) ||
432+ (strings .HasPrefix (value , `'` ) && strings .HasSuffix (value , `'` )) {
433+ value = strings .Trim (value , `"'` )
434+ }
435+ }
436+
437+ // Ignore values containing nginx variables unless it is a single variable reference
438+ if strings .Contains (value , "$" ) {
439+ // Support simple indirection: set $a $b;
440+ if resolved , ok := resolveSingleVariable (value , result ); ok {
441+ result [name ] = resolved
442+ }
443+ continue
444+ }
445+
446+ // Record literal value
447+ result [name ] = value
448+ }
449+ return result
450+ }
451+
452+ // resolveSingleVariable resolves an expression that is exactly a single variable like `$target`
453+ // using the provided map. Returns (resolvedValue, true) if resolvable; otherwise ("", false).
454+ func resolveSingleVariable (expr string , variables map [string ]string ) (string , bool ) {
455+ expr = strings .TrimSpace (expr )
456+ // Match exactly `$varName` with optional surrounding spaces
457+ varOnlyRegex := regexp .MustCompile (`^\$([A-Za-z0-9_]+)$` )
458+ sub := varOnlyRegex .FindStringSubmatch (expr )
459+ if len (sub ) < 2 {
460+ return "" , false
461+ }
462+ name := sub [1 ]
463+ val , ok := variables [name ]
464+ if ! ok {
465+ return "" , false
466+ }
467+ // Guard against cyclic or unresolved values that still contain variables
468+ if strings .Contains (val , "$" ) {
469+ return "" , false
470+ }
471+ return val , true
472+ }
0 commit comments