diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md
index 1299b757752..15080b1ebab 100644
--- a/docs/sources/administration/provisioning.md
+++ b/docs/sources/administration/provisioning.md
@@ -45,6 +45,8 @@ datasources:
password: $PASSWORD
```
+If you have a literal `$` in your value and want to avoid interpolation, `$$` can be used.
+
## Configuration Management Tools
diff --git a/pkg/services/provisioning/values/values.go b/pkg/services/provisioning/values/values.go
index 63da76a9338..de86085671e 100644
--- a/pkg/services/provisioning/values/values.go
+++ b/pkg/services/provisioning/values/values.go
@@ -14,6 +14,7 @@ import (
"os"
"reflect"
"strconv"
+ "strings"
"github.com/grafana/grafana/pkg/util/errutil"
)
@@ -185,8 +186,14 @@ func transformMap(i map[interface{}]interface{}) interface{} {
// interpolateValue returns final value after interpolation. At the moment only env var interpolation is done
// here but in the future something like interpolation from file could be also done here.
+// For a literal '$', '$$' can be used to avoid interpolation.
func interpolateValue(val string) string {
- return os.ExpandEnv(val)
+ parts := strings.Split(val, "$$")
+ interpolated := make([]string, len(parts))
+ for i, v := range parts {
+ interpolated[i] = os.ExpandEnv(v)
+ }
+ return strings.Join(interpolated, "$")
}
type interpolated struct {
diff --git a/pkg/services/provisioning/values/values_test.go b/pkg/services/provisioning/values/values_test.go
index 284069be395..064dee86d6a 100644
--- a/pkg/services/provisioning/values/values_test.go
+++ b/pkg/services/provisioning/values/values_test.go
@@ -1,16 +1,18 @@
package values
import (
- . "github.com/smartystreets/goconvey/convey"
- "gopkg.in/yaml.v2"
"os"
"testing"
+
+ . "github.com/smartystreets/goconvey/convey"
+ "gopkg.in/yaml.v2"
)
func TestValues(t *testing.T) {
Convey("Values", t, func() {
os.Setenv("INT", "1")
os.Setenv("STRING", "test")
+ os.Setenv("EMPTYSTRING", "")
os.Setenv("BOOL", "true")
Convey("IntValue", func() {
@@ -61,6 +63,24 @@ func TestValues(t *testing.T) {
So(d.Val.Value(), ShouldEqual, "")
So(d.Val.Raw, ShouldEqual, "")
})
+
+ Convey("empty var should have empty value", func() {
+ unmarshalingTest(`val: $EMPTYSTRING`, d)
+ So(d.Val.Value(), ShouldEqual, "")
+ So(d.Val.Raw, ShouldEqual, "$EMPTYSTRING")
+ })
+
+ Convey("$$ should be a literal $", func() {
+ unmarshalingTest(`val: $$`, d)
+ So(d.Val.Value(), ShouldEqual, "$")
+ So(d.Val.Raw, ShouldEqual, "$$")
+ })
+
+ Convey("$$ should be a literal $ and not expanded within a string", func() {
+ unmarshalingTest(`val: mY,Passwo$$rd`, d)
+ So(d.Val.Value(), ShouldEqual, "mY,Passwo$rd")
+ So(d.Val.Raw, ShouldEqual, "mY,Passwo$$rd")
+ })
})
Convey("BoolValue", func() {
@@ -199,6 +219,7 @@ func TestValues(t *testing.T) {
Reset(func() {
os.Unsetenv("INT")
os.Unsetenv("STRING")
+ os.Unsetenv("EMPTYSTRING")
os.Unsetenv("BOOL")
})
})