grafana/pkg/services/provisioning/values/values.go

350 lines
9.5 KiB
Go

// Package values is a set of value types to use in provisioning. They add custom unmarshaling logic that puts the string values
// through os.ExpandEnv.
// Usage:
//
// type Data struct {
// Field StringValue `yaml:"field"` // Instead of string
// }
//
// d := &Data{}
// // unmarshal into d
// d.Field.Value() // returns the final interpolated value from the yaml file
package values
import (
"fmt"
"os"
"reflect"
"strconv"
"strings"
"github.com/grafana/grafana/pkg/setting"
)
// IntValue represents a string value in a YAML
// config that can be overridden by environment variables
type IntValue struct {
value int
Raw string
}
// UnmarshalYAML converts YAML into an *IntValue
func (val *IntValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
interpolated, err := getInterpolated(unmarshal)
if err != nil {
return err
}
if len(interpolated.value) == 0 {
// To keep the same behaviour as the yaml lib which just does not set the value if it is empty.
return nil
}
val.Raw = interpolated.raw
val.value, err = strconv.Atoi(interpolated.value)
if err != nil {
return fmt.Errorf("%v: %w", "cannot convert value int", err)
}
return err
}
// Value returns the wrapped int value
func (val *IntValue) Value() int {
return val.value
}
// Int64Value represents a string value in a YAML
// config that can be overridden by environment variables
type Int64Value struct {
value int64
Raw string
}
// UnmarshalYAML converts YAML into an *Int64Value
func (val *Int64Value) UnmarshalYAML(unmarshal func(interface{}) error) error {
interpolated, err := getInterpolated(unmarshal)
if err != nil {
return err
}
if len(interpolated.value) == 0 {
// To keep the same behaviour as the yaml lib which just does not set the value if it is empty.
return nil
}
val.Raw = interpolated.raw
val.value, err = strconv.ParseInt(interpolated.value, 10, 64)
return err
}
// Value returns the wrapped int64 value
func (val *Int64Value) Value() int64 {
return val.value
}
// StringValue represents a string value in a YAML
// config that can be overridden by environment variables
type StringValue struct {
value string
Raw string
}
// UnmarshalYAML converts YAML into an *StringValue
func (val *StringValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
interpolated, err := getInterpolated(unmarshal)
if err != nil {
return err
}
val.Raw = interpolated.raw
val.value = interpolated.value
return err
}
// Value returns the wrapped string value
func (val *StringValue) Value() string {
return val.value
}
// BoolValue represents a string value in a YAML
// config that can be overridden by environment variables
type BoolValue struct {
value bool
Raw string
}
// UnmarshalYAML converts YAML into an *BoolValue
func (val *BoolValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
interpolated, err := getInterpolated(unmarshal)
if err != nil {
return err
}
val.Raw = interpolated.raw
val.value, err = strconv.ParseBool(interpolated.value)
return err
}
// Value returns the wrapped bool value
func (val *BoolValue) Value() bool {
return val.value
}
// JSONValue represents a string value in a YAML
// config that can be overridden by environment variables
type JSONValue struct {
value map[string]interface{}
Raw map[string]interface{}
}
// UnmarshalYAML converts YAML into an *JSONValue
func (val *JSONValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
unmarshaled := make(map[string]interface{})
err := unmarshal(unmarshaled)
if err != nil {
return err
}
interpolated := make(map[string]interface{})
raw := make(map[string]interface{})
for key, val := range unmarshaled {
interpolated[key], raw[key], err = transformInterface(val)
if err != nil {
return err
}
}
val.Raw = raw
val.value = interpolated
return err
}
// Value returns the wrapped JSON value as map[string]interface{}
func (val *JSONValue) Value() map[string]interface{} {
return val.value
}
// StringMapValue represents a string value in a YAML
// config that can be overridden by environment variables
type StringMapValue struct {
value map[string]string
Raw map[string]string
}
// UnmarshalYAML converts YAML into an *StringMapValue
func (val *StringMapValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
unmarshaled := make(map[string]string)
err := unmarshal(unmarshaled)
if err != nil {
return err
}
interpolated := make(map[string]string)
raw := make(map[string]string)
for key, val := range unmarshaled {
interpolated[key], raw[key], err = interpolateValue(val)
if err != nil {
return err
}
}
val.Raw = raw
val.value = interpolated
return err
}
// Value returns the wrapped map[string]string value
func (val *StringMapValue) Value() map[string]string {
return val.value
}
// JSONSliceValue represents a slice value in a YAML
// config that can be overridden by environment variables
type JSONSliceValue struct {
value []map[string]interface{}
Raw []map[string]interface{}
}
// UnmarshalYAML converts YAML into an *JSONSliceValue
func (val *JSONSliceValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
unmarshaled := make([]interface{}, 0)
err := unmarshal(&unmarshaled)
if err != nil {
return err
}
interpolated := make([]map[string]interface{}, 0)
raw := make([]map[string]interface{}, 0)
for _, v := range unmarshaled {
i := make(map[string]interface{})
r := make(map[string]interface{})
for key, val := range v.(map[string]interface{}) {
i[key], r[key], err = transformInterface(val)
if err != nil {
return err
}
}
interpolated = append(interpolated, i)
raw = append(raw, r)
}
val.Raw = raw
val.value = interpolated
return err
}
// Value returns the wrapped []interface{} value
func (val *JSONSliceValue) Value() []map[string]interface{} {
return val.value
}
// transformInterface tries to transform any interface type into proper value with env expansion. It traverses maps and
// 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{}, error) {
typeOf := reflect.TypeOf(i)
if typeOf == nil {
return nil, nil, nil
}
switch typeOf.Kind() {
case reflect.Slice:
return transformSlice(i.([]interface{}))
case reflect.Map:
return transformMap(i.(map[string]interface{}))
case reflect.String:
return interpolateIfaceValue(i.(string))
default:
// Was int, float or some other value that we do not need to do any transform on.
return i, i, nil
}
}
func transformSlice(i []interface{}) (interface{}, interface{}, error) {
transformedSlice := make([]interface{}, 0, len(i))
rawSlice := make([]interface{}, 0, len(i))
for _, val := range i {
transformed, raw, err := transformInterface(val)
if err != nil {
return nil, nil, err
}
transformedSlice = append(transformedSlice, transformed)
rawSlice = append(rawSlice, raw)
}
return transformedSlice, rawSlice, nil
}
func transformMap(i map[string]interface{}) (interface{}, interface{}, error) {
transformed := make(map[string]interface{})
raw := make(map[string]interface{})
for key, val := range i {
var err error
transformed[key], raw[key], err = transformInterface(val)
if err != nil {
return nil, nil, err
}
}
return transformed, raw, nil
}
func interpolateIfaceValue(val string) (interface{}, string, error) {
parts := strings.Split(val, "$$")
if len(parts) > 1 {
return interpolateValue(val)
}
expanded, err := setting.ExpandVar(val)
if err != nil {
return val, val, fmt.Errorf("failed to interpolate value '%s': %w", val, err)
}
expandedEnv := os.ExpandEnv(expanded)
if expandedEnv != val {
// If the value is an environment variable, consider it may not be a string
intV, err := strconv.ParseInt(expandedEnv, 10, 64)
if err == nil {
return intV, val, nil
}
floatV, err := strconv.ParseFloat(expandedEnv, 64)
if err == nil {
return floatV, val, nil
}
boolV, err := strconv.ParseBool(expandedEnv)
if err == nil {
return boolV, val, nil
}
}
return expandedEnv, val, nil
}
// 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, 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, nil
}
type interpolated struct {
value string
raw string
}
// getInterpolated unmarshals the value as string and runs interpolation on it. It is the responsibility of each
// value type to convert this string value to appropriate type.
func getInterpolated(unmarshal func(interface{}) error) (*interpolated, error) {
var veryRaw string
err := unmarshal(&veryRaw)
if err != nil {
return &interpolated{}, err
}
// 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, err := interpolateValue(veryRaw)
if err != nil {
return &interpolated{}, err
}
return &interpolated{raw: raw, value: value}, nil
}