Permalink
Oct 19, 2017
Jun 28, 2013
Newer
100644
236 lines (216 sloc)
7.02 KB
16
// Settings is a group of charm config option names and values. A Settings
17
// S is considered valid by the Config C if every key in S is an option in
18
// C, and every value either has the correct type or is nil.
19
type Settings map[string]interface{}
20
21
// Option represents a single charm config option.
23
Type string `yaml:"type"`
24
Description string `yaml:"description,omitempty"`
25
Default interface{} `yaml:"default,omitempty"`
28
// error replaces any supplied non-nil error with a new error describing a
29
// validation failure for the supplied value.
30
func (option Option) error(err *error, name string, value interface{}) {
31
if *err != nil {
32
*err = fmt.Errorf("option %q expected %s, got %#v", name, option.Type, value)
33
}
34
}
35
36
// validate returns an appropriately-typed value for the supplied value, or
37
// returns an error if it cannot be converted to the correct type. Nil values
39
func (option Option) validate(name string, value interface{}) (_ interface{}, err error) {
40
if value == nil {
41
return nil, nil
42
}
45
if value, err = checker.Coerce(value, nil); err != nil {
46
return nil, err
47
}
48
return value, nil
49
}
53
var optionTypeCheckers = map[string]schema.Checker{
54
"string": schema.String(),
55
"int": schema.Int(),
56
"float": schema.Float(),
57
"boolean": schema.Bool(),
58
}
59
69
val, err = strconv.ParseBool(str)
70
default:
71
return nil, fmt.Errorf("option %q has unknown type %q", name, option.Type)
78
// Config represents the supported configuration options for a charm,
79
// as declared in its config.yaml file.
80
type Config struct {
81
Options map[string]Option
82
}
83
89
// ReadConfig reads a Config in YAML format.
90
func ReadConfig(r io.Reader) (*Config, error) {
99
if config == nil {
100
return nil, fmt.Errorf("invalid config: empty configuration")
101
}
102
if config.Options == nil {
103
// We are allowed an empty configuration if the options
104
// field is explicitly specified, but there is no easy way
105
// to tell if it was specified or not without unmarshaling
106
// into interface{} and explicitly checking the field.
107
var configInterface interface{}
108
if err := yaml.Unmarshal(data, &configInterface); err != nil {
109
return nil, err
110
}
111
m, _ := configInterface.(map[interface{}]interface{})
112
if _, ok := m["options"]; !ok {
113
return nil, fmt.Errorf("invalid config: empty configuration")
114
}
115
}
116
for name, option := range config.Options {
117
switch option.Type {
118
case "string", "int", "float", "boolean":
119
case "":
120
// Missing type is valid in python.
121
option.Type = "string"
122
default:
123
return nil, fmt.Errorf("invalid config: option %q has unknown type %q", name, option.Type)
125
def := option.Default
126
if def == "" && option.Type == "string" {
127
// Skip normal validation for compatibility with pyjuju.
128
} else if option.Default, err = option.validate(name, def); err != nil {
129
option.error(&err, name, def)
130
return nil, fmt.Errorf("invalid config default: %v", err)
132
config.Options[name] = option
133
}
134
return config, nil
135
}
136
137
// option returns the named option from the config, or an error if none
138
// such exists.
139
func (c *Config) option(name string) (Option, error) {
140
if option, ok := c.Options[name]; ok {
141
return option, nil
146
// DefaultSettings returns settings containing the default value of every
147
// option in the config. Default values may be nil.
148
func (c *Config) DefaultSettings() Settings {
149
out := make(Settings)
150
for name, option := range c.Options {
151
out[name] = option.Default
152
}
153
return out
154
}
155
156
// ValidateSettings returns a copy of the supplied settings with a consistent type
157
// for each value. It returns an error if the settings contain unknown keys
158
// or invalid values.
159
func (c *Config) ValidateSettings(settings Settings) (Settings, error) {
160
out := make(Settings)
161
for name, value := range settings {
162
if option, err := c.option(name); err != nil {
163
return nil, err
164
} else if value, err = option.validate(name, value); err != nil {
165
return nil, err
167
out[name] = value
168
}
169
return out, nil
170
}
171
172
// FilterSettings returns the subset of the supplied settings that are valid.
173
func (c *Config) FilterSettings(settings Settings) Settings {
174
out := make(Settings)
175
for name, value := range settings {
176
if option, err := c.option(name); err == nil {
177
if value, err := option.validate(name, value); err == nil {
178
out[name] = value
185
// ParseSettingsStrings returns settings derived from the supplied map. Every
186
// value in the map must be parseable to the correct type for the option
187
// identified by its key. Empty values are interpreted as nil.
188
func (c *Config) ParseSettingsStrings(values map[string]string) (Settings, error) {
189
out := make(Settings)
190
for name, str := range values {
191
option, err := c.option(name)
192
if err != nil {
193
return nil, err
195
value, err := option.parse(name, str)
196
if err != nil {
197
return nil, err
198
}
199
out[name] = value
204
// ParseSettingsYAML returns settings derived from the supplied YAML data. The
205
// YAML must unmarshal to a map of strings to settings data; the supplied key
206
// must be present in the map, and must point to a map in which every value
207
// must have, or be a string parseable to, the correct type for the associated
208
// config option. Empty strings and nil values are both interpreted as nil.
209
func (c *Config) ParseSettingsYAML(yamlData []byte, key string) (Settings, error) {
210
var allSettings map[string]Settings
212
return nil, fmt.Errorf("cannot parse settings data: %v", err)
213
}
214
settings, ok := allSettings[key]
215
if !ok {
216
return nil, fmt.Errorf("no settings found for %q", key)
217
}
218
out := make(Settings)
219
for name, value := range settings {
220
option, err := c.option(name)
221
if err != nil {
222
return nil, err
224
// Accept string values for compatibility with python.
225
if str, ok := value.(string); ok {
226
if value, err = option.parse(name, str); err != nil {
227
return nil, err
228
}
229
} else if value, err = option.validate(name, value); err != nil {
230
return nil, err