1
// Copyright 2011, 2012, 2013 Canonical Ltd.
2
// Licensed under the LGPLv3, see LICENCE file for details.
12
"github.com/juju/schema"
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{}
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{}) {
32
*err = fmt.Errorf("option %q expected %s, got %#v", name, option.Type, value)
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
38
// are always considered valid.
39
func (option Option) validate(name string, value interface{}) (_ interface{}, err error) {
43
defer option.error(&err, name, value)
44
if checker := optionTypeCheckers[option.Type]; checker != nil {
45
if value, err = checker.Coerce(value, nil); err != nil {
50
panic(fmt.Errorf("option %q has unknown type %q", name, option.Type))
53
var optionTypeCheckers = map[string]schema.Checker{
54
"string": schema.String(),
56
"float": schema.Float(),
57
"boolean": schema.Bool(),
60
// parse returns an appropriately-typed value for the supplied string, or
61
// returns an error if it cannot be parsed to the correct type.
62
func (option Option) parse(name, str string) (_ interface{}, err error) {
63
defer option.error(&err, name, str)
68
return strconv.ParseInt(str, 10, 64)
70
return strconv.ParseFloat(str, 64)
72
return strconv.ParseBool(str)
74
panic(fmt.Errorf("option %q has unknown type %q", name, option.Type))
77
// Config represents the supported configuration options for a charm,
78
// as declared in its config.yaml file.
80
Options map[string]Option
83
// NewConfig returns a new Config without any options.
84
func NewConfig() *Config {
85
return &Config{map[string]Option{}}
88
// ReadConfig reads a Config in YAML format.
89
func ReadConfig(r io.Reader) (*Config, error) {
90
data, err := ioutil.ReadAll(r)
95
if err := yaml.Unmarshal(data, &config); err != nil {
99
return nil, fmt.Errorf("invalid config: empty configuration")
101
if config.Options == nil {
102
// We are allowed an empty configuration if the options
103
// field is explicitly specified, but there is no easy way
104
// to tell if it was specified or not without unmarshaling
105
// into interface{} and explicitly checking the field.
106
var configInterface interface{}
107
if err := yaml.Unmarshal(data, &configInterface); err != nil {
110
m, _ := configInterface.(map[interface{}]interface{})
111
if _, ok := m["options"]; !ok {
112
return nil, fmt.Errorf("invalid config: empty configuration")
115
for name, option := range config.Options {
117
case "string", "int", "float", "boolean":
119
// Missing type is valid in python.
120
option.Type = "string"
122
return nil, fmt.Errorf("invalid config: option %q has unknown type %q", name, option.Type)
124
def := option.Default
125
if def == "" && option.Type == "string" {
126
// Skip normal validation for compatibility with pyjuju.
127
} else if option.Default, err = option.validate(name, def); err != nil {
128
option.error(&err, name, def)
129
return nil, fmt.Errorf("invalid config default: %v", err)
131
config.Options[name] = option
136
// option returns the named option from the config, or an error if none
138
func (c *Config) option(name string) (Option, error) {
139
if option, ok := c.Options[name]; ok {
142
return Option{}, fmt.Errorf("unknown option %q", name)
145
// DefaultSettings returns settings containing the default value of every
146
// option in the config. Default values may be nil.
147
func (c *Config) DefaultSettings() Settings {
148
out := make(Settings)
149
for name, option := range c.Options {
150
out[name] = option.Default
155
// ValidateSettings returns a copy of the supplied settings with a consistent type
156
// for each value. It returns an error if the settings contain unknown keys
157
// or invalid values.
158
func (c *Config) ValidateSettings(settings Settings) (Settings, error) {
159
out := make(Settings)
160
for name, value := range settings {
161
if option, err := c.option(name); err != nil {
163
} else if value, err = option.validate(name, value); err != nil {
171
// FilterSettings returns the subset of the supplied settings that are valid.
172
func (c *Config) FilterSettings(settings Settings) Settings {
173
out := make(Settings)
174
for name, value := range settings {
175
if option, err := c.option(name); err == nil {
176
if value, err := option.validate(name, value); err == nil {
184
// ParseSettingsStrings returns settings derived from the supplied map. Every
185
// value in the map must be parseable to the correct type for the option
186
// identified by its key. Empty values are interpreted as nil.
187
func (c *Config) ParseSettingsStrings(values map[string]string) (Settings, error) {
188
out := make(Settings)
189
for name, str := range values {
190
option, err := c.option(name)
194
value, err := option.parse(name, str)
203
// ParseSettingsYAML returns settings derived from the supplied YAML data. The
204
// YAML must unmarshal to a map of strings to settings data; the supplied key
205
// must be present in the map, and must point to a map in which every value
206
// must have, or be a string parseable to, the correct type for the associated
207
// config option. Empty strings and nil values are both interpreted as nil.
208
func (c *Config) ParseSettingsYAML(yamlData []byte, key string) (Settings, error) {
209
var allSettings map[string]Settings
210
if err := yaml.Unmarshal(yamlData, &allSettings); err != nil {
211
return nil, fmt.Errorf("cannot parse settings data: %v", err)
213
settings, ok := allSettings[key]
215
return nil, fmt.Errorf("no settings found for %q", key)
217
out := make(Settings)
218
for name, value := range settings {
219
option, err := c.option(name)
223
// Accept string values for compatibility with python.
224
if str, ok := value.(string); ok {
225
if value, err = option.parse(name, str); err != nil {
228
} else if value, err = option.validate(name, value); err != nil {