mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Provisioning: Apply settings expanders (#25692)
* Provisioning: Apply settings expanders Adds support for the variable expanders introduced to the configuration file in #25075 to provisioning. * Update pkg/services/provisioning/values/values_test.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/provisioning/values/values.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Provisioning: Handle errors when expanding JSON maps
This commit is contained in:
parent
9a6bf88020
commit
112c28033d
@ -11,11 +11,14 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
@ -117,7 +120,10 @@ func (val *JSONValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
interpolated := make(map[string]interface{})
|
||||
raw := make(map[string]interface{})
|
||||
for key, val := range unmarshaled {
|
||||
interpolated[key], raw[key] = transformInterface(val)
|
||||
interpolated[key], raw[key], err = transformInterface(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
val.Raw = raw
|
||||
@ -143,7 +149,10 @@ func (val *StringMapValue) UnmarshalYAML(unmarshal func(interface{}) error) erro
|
||||
interpolated := make(map[string]string)
|
||||
raw := make(map[string]string)
|
||||
for key, val := range unmarshaled {
|
||||
interpolated[key], raw[key] = interpolateValue(val)
|
||||
interpolated[key], raw[key], err = interpolateValue(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
val.Raw = raw
|
||||
val.value = interpolated
|
||||
@ -158,11 +167,11 @@ func (val *StringMapValue) Value() map[string]string {
|
||||
// slices and the actual interpolation is done on all simple string values in the structure. It returns a copy of any
|
||||
// map or slice value instead of modifying them in place and also return value without interpolation but with converted
|
||||
// type as a second value.
|
||||
func transformInterface(i interface{}) (interface{}, interface{}) {
|
||||
func transformInterface(i interface{}) (interface{}, interface{}, error) {
|
||||
typeOf := reflect.TypeOf(i)
|
||||
|
||||
if typeOf == nil {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
switch typeOf.Kind() {
|
||||
@ -174,43 +183,55 @@ func transformInterface(i interface{}) (interface{}, interface{}) {
|
||||
return interpolateValue(i.(string))
|
||||
default:
|
||||
// Was int, float or some other value that we do not need to do any transform on.
|
||||
return i, i
|
||||
return i, i, nil
|
||||
}
|
||||
}
|
||||
|
||||
func transformSlice(i []interface{}) (interface{}, interface{}) {
|
||||
func transformSlice(i []interface{}) (interface{}, interface{}, error) {
|
||||
var transformedSlice []interface{}
|
||||
var rawSlice []interface{}
|
||||
for _, val := range i {
|
||||
transformed, raw := transformInterface(val)
|
||||
transformed, raw, err := transformInterface(val)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
transformedSlice = append(transformedSlice, transformed)
|
||||
rawSlice = append(rawSlice, raw)
|
||||
}
|
||||
return transformedSlice, rawSlice
|
||||
return transformedSlice, rawSlice, nil
|
||||
}
|
||||
|
||||
func transformMap(i map[interface{}]interface{}) (interface{}, interface{}) {
|
||||
func transformMap(i map[interface{}]interface{}) (interface{}, interface{}, error) {
|
||||
transformed := make(map[string]interface{})
|
||||
raw := make(map[string]interface{})
|
||||
for key, val := range i {
|
||||
stringKey, ok := key.(string)
|
||||
if ok {
|
||||
transformed[stringKey], raw[stringKey] = transformInterface(val)
|
||||
var err error
|
||||
transformed[stringKey], raw[stringKey], err = transformInterface(val)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return transformed, raw
|
||||
return transformed, raw, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
// interpolateValue returns the final value after interpolation. In addition to environment variable interpolation,
|
||||
// expanders available for the settings file are expanded here.
|
||||
// For a literal '$', '$$' can be used to avoid interpolation.
|
||||
func interpolateValue(val string) (string, string) {
|
||||
func interpolateValue(val string) (string, string, error) {
|
||||
parts := strings.Split(val, "$$")
|
||||
interpolated := make([]string, len(parts))
|
||||
for i, v := range parts {
|
||||
expanded, err := setting.ExpandVar(v)
|
||||
if err != nil {
|
||||
return val, val, fmt.Errorf("failed to interpolate value '%s': %w", val, err)
|
||||
}
|
||||
v = expanded
|
||||
interpolated[i] = os.ExpandEnv(v)
|
||||
}
|
||||
return strings.Join(interpolated, "$"), val
|
||||
return strings.Join(interpolated, "$"), val, nil
|
||||
}
|
||||
|
||||
type interpolated struct {
|
||||
@ -228,6 +249,9 @@ func getInterpolated(unmarshal func(interface{}) error) (*interpolated, error) {
|
||||
}
|
||||
// We get new raw value here which can have a bit different type, as yaml types nested maps as
|
||||
// map[interface{}]interface and we want it to be map[string]interface{}
|
||||
value, raw := interpolateValue(veryRaw)
|
||||
value, raw, err := interpolateValue(veryRaw)
|
||||
if err != nil {
|
||||
return &interpolated{}, err
|
||||
}
|
||||
return &interpolated{raw: raw, value: value}, nil
|
||||
}
|
||||
|
@ -1,9 +1,18 @@
|
||||
package values
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
@ -245,3 +254,53 @@ func unmarshalingTest(document string, out interface{}) {
|
||||
err := yaml.Unmarshal([]byte(document), out)
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
func TestValues_readFile(t *testing.T) {
|
||||
type Data struct {
|
||||
Val StringValue `yaml:"val"`
|
||||
}
|
||||
|
||||
f, err := ioutil.TempFile(os.TempDir(), "file expansion *")
|
||||
require.NoError(t, err)
|
||||
file := f.Name()
|
||||
|
||||
defer func() {
|
||||
require.NoError(t, os.Remove(file))
|
||||
}()
|
||||
|
||||
const expected = "hello, world"
|
||||
_, err = f.WriteString(expected)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Close())
|
||||
|
||||
data := &Data{}
|
||||
err = yaml.Unmarshal([]byte(fmt.Sprintf("val: $__file{%s}", file)), data)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, data.Val.Value())
|
||||
}
|
||||
|
||||
func TestValues_expanderError(t *testing.T) {
|
||||
type Data struct {
|
||||
Top JSONValue `yaml:"top"`
|
||||
}
|
||||
|
||||
setting.AddExpander("fail", 0, failExpander{})
|
||||
|
||||
data := &Data{}
|
||||
err := yaml.Unmarshal([]byte("top:\n val: $__fail{val}"), data)
|
||||
require.Error(t, err)
|
||||
require.Truef(t, errors.Is(err, errExpand), "expected error to wrap: %v\ngot: %v", errExpand, err)
|
||||
assert.Empty(t, data)
|
||||
}
|
||||
|
||||
var errExpand = errors.New("test error: bad expander")
|
||||
|
||||
type failExpander struct{}
|
||||
|
||||
func (f failExpander) SetupExpander(file *ini.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f failExpander) Expand(s string) (string, error) {
|
||||
return "", errExpand
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user