Skip to content

Commit 1ded1cc

Browse files
committed
Allow variables to be used as part of a value in configuration.
Signed-off-by: Joseph Riddle <[email protected]>
1 parent 192d2bf commit 1ded1cc

File tree

2 files changed

+105
-0
lines changed

2 files changed

+105
-0
lines changed

conf/parse.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"fmt"
3232
"os"
3333
"path/filepath"
34+
"regexp"
3435
"strconv"
3536
"strings"
3637
"time"
@@ -287,6 +288,7 @@ func (p *parser) processItem(it item, fp string) error {
287288
setValue(it, p.popContext())
288289
case itemString:
289290
// FIXME(dlc) sanitize string?
291+
p.checkForEmbeddedVariables(&it)
290292
setValue(it, it.val)
291293
case itemInteger:
292294
lastDigit := 0
@@ -430,6 +432,33 @@ const pkey = "pk"
430432
// We special case raw strings here that are bcrypt'd. This allows us not to force quoting the strings
431433
const bcryptPrefix = "2a$"
432434

435+
// To match embedded variables.
436+
var varPat = regexp.MustCompile(`\$\$[^@\s]+`)
437+
438+
// checkForEmbeddedVariable will check for embedded variables in an itemString.
439+
// If they are found and we can look them up we will replace them in item, otherwise will error.
440+
func (p *parser) checkForEmbeddedVariables(it *item) error {
441+
if !strings.ContainsAny(it.val, "$$") {
442+
return nil
443+
}
444+
// We have some embedded variables.
445+
for _, m := range varPat.FindAllString(it.val, -1) {
446+
value, found, err := p.lookupVariable(m[2:]) // Strip leading $$ for lookup.
447+
if err != nil {
448+
return fmt.Errorf("variable reference for '%s' on line %d could not be parsed: %s",
449+
m, it.line, err)
450+
}
451+
if !found {
452+
return fmt.Errorf("variable reference for '%s' on line %d can not be found",
453+
m, it.line)
454+
}
455+
if v, ok := value.(string); ok {
456+
it.val = strings.Replace(it.val, m, v, 1)
457+
}
458+
}
459+
return nil
460+
}
461+
433462
// lookupVariable will lookup a variable reference. It will use block scoping on keys
434463
// it has seen before, with the top level scoping being the environment variables. We
435464
// ignore array contexts and only process the map contexts..

conf/parse_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,82 @@ func TestEnvVariableStringStartingWithNumberUsingQuotes(t *testing.T) {
166166
test(t, fmt.Sprintf("foo = $%s", evar), ex)
167167
}
168168

169+
func TestEnvVariableEmbedded(t *testing.T) {
170+
cluster := `
171+
cluster {
172+
# set the variable token
173+
TOKEN: abc
174+
authorization {
175+
user: user
176+
# normal variable syntax
177+
password: $TOKEN
178+
}
179+
# embedded variable syntax
180+
routes = [ nats://user:[email protected]:6222 ]
181+
}`
182+
ex := map[string]any{
183+
"cluster": map[string]any{
184+
"TOKEN": "abc",
185+
"authorization": map[string]any{
186+
"user": "user",
187+
"password": "abc",
188+
},
189+
"routes": []any{
190+
"nats://user:[email protected]:6222",
191+
},
192+
},
193+
}
194+
195+
// don't use test() here because we want to test the Parse function without checking pedantic mode.
196+
m, err := Parse(cluster)
197+
if err != nil {
198+
t.Fatalf("Received err: %v\n", err)
199+
}
200+
if m == nil {
201+
t.Fatal("Received nil map")
202+
}
203+
204+
if !reflect.DeepEqual(m, ex) {
205+
t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, ex)
206+
}
207+
}
208+
209+
func TestEnvVariableEmbeddedMissing(t *testing.T) {
210+
cluster := `
211+
cluster {
212+
authorization {
213+
user: user
214+
# normal variable syntax
215+
password: $TOKEN
216+
}
217+
# embedded variable syntax
218+
routes = [ nats://user:[email protected]:6222 ]
219+
}`
220+
221+
_, err := Parse(cluster)
222+
if err == nil {
223+
t.Fatalf("Expected err not being able to process embedded variable, got none")
224+
}
225+
}
226+
227+
func TestEnvVariableEmbeddedOutsideOfQuotes(t *testing.T) {
228+
cluster := `
229+
cluster {
230+
authorization {
231+
user: user
232+
# invalid embedded variable syntax
233+
password: $$TOKEN
234+
}
235+
# embedded variable syntax
236+
routes = [ nats://user:[email protected]:6222 ]
237+
}`
238+
239+
_, err := Parse(cluster)
240+
if err == nil {
241+
t.Fatalf("Expected err not being able to process embedded variable, got none")
242+
}
243+
}
244+
169245
func TestBcryptVariable(t *testing.T) {
170246
ex := map[string]any{
171247
"password": "$2a$11$ooo",

0 commit comments

Comments
 (0)