~ubuntu-branches/ubuntu/vivid/juju-core/vivid-updates

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charm.v5/config.go

  • Committer: Package Import Robot
  • Author(s): Curtis C. Hovey
  • Date: 2015-09-29 19:43:29 UTC
  • mfrom: (47.1.4 wily-proposed)
  • Revision ID: package-import@ubuntu.com-20150929194329-9y496tbic30hc7vp
Tags: 1.24.6-0ubuntu1~15.04.1
Backport of 1.24.6 from wily. (LP: #1500916, #1497087)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2011, 2012, 2013 Canonical Ltd.
 
2
// Licensed under the LGPLv3, see LICENCE file for details.
 
3
 
 
4
package charm
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "io"
 
9
        "io/ioutil"
 
10
        "strconv"
 
11
 
 
12
        "github.com/juju/schema"
 
13
        "gopkg.in/yaml.v1"
 
14
)
 
15
 
 
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.
 
22
type Option struct {
 
23
        Type        string      `yaml:"type"`
 
24
        Description string      `yaml:"description,omitempty"`
 
25
        Default     interface{} `yaml:"default,omitempty"`
 
26
}
 
27
 
 
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
 
38
// are always considered valid.
 
39
func (option Option) validate(name string, value interface{}) (_ interface{}, err error) {
 
40
        if value == nil {
 
41
                return nil, nil
 
42
        }
 
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 {
 
46
                        return nil, err
 
47
                }
 
48
                return value, nil
 
49
        }
 
50
        panic(fmt.Errorf("option %q has unknown type %q", name, option.Type))
 
51
}
 
52
 
 
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
 
 
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)
 
64
        switch option.Type {
 
65
        case "string":
 
66
                return str, nil
 
67
        case "int":
 
68
                return strconv.ParseInt(str, 10, 64)
 
69
        case "float":
 
70
                return strconv.ParseFloat(str, 64)
 
71
        case "boolean":
 
72
                return strconv.ParseBool(str)
 
73
        }
 
74
        panic(fmt.Errorf("option %q has unknown type %q", name, option.Type))
 
75
}
 
76
 
 
77
// Config represents the supported configuration options for a charm,
 
78
// as declared in its config.yaml file.
 
79
type Config struct {
 
80
        Options map[string]Option
 
81
}
 
82
 
 
83
// NewConfig returns a new Config without any options.
 
84
func NewConfig() *Config {
 
85
        return &Config{map[string]Option{}}
 
86
}
 
87
 
 
88
// ReadConfig reads a Config in YAML format.
 
89
func ReadConfig(r io.Reader) (*Config, error) {
 
90
        data, err := ioutil.ReadAll(r)
 
91
        if err != nil {
 
92
                return nil, err
 
93
        }
 
94
        var config *Config
 
95
        if err := yaml.Unmarshal(data, &config); err != nil {
 
96
                return nil, err
 
97
        }
 
98
        if config == nil {
 
99
                return nil, fmt.Errorf("invalid config: empty configuration")
 
100
        }
 
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 {
 
108
                        return nil, err
 
109
                }
 
110
                m, _ := configInterface.(map[interface{}]interface{})
 
111
                if _, ok := m["options"]; !ok {
 
112
                        return nil, fmt.Errorf("invalid config: empty configuration")
 
113
                }
 
114
        }
 
115
        for name, option := range config.Options {
 
116
                switch option.Type {
 
117
                case "string", "int", "float", "boolean":
 
118
                case "":
 
119
                        // Missing type is valid in python.
 
120
                        option.Type = "string"
 
121
                default:
 
122
                        return nil, fmt.Errorf("invalid config: option %q has unknown type %q", name, option.Type)
 
123
                }
 
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)
 
130
                }
 
131
                config.Options[name] = option
 
132
        }
 
133
        return config, nil
 
134
}
 
135
 
 
136
// option returns the named option from the config, or an error if none
 
137
// such exists.
 
138
func (c *Config) option(name string) (Option, error) {
 
139
        if option, ok := c.Options[name]; ok {
 
140
                return option, nil
 
141
        }
 
142
        return Option{}, fmt.Errorf("unknown option %q", name)
 
143
}
 
144
 
 
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
 
151
        }
 
152
        return out
 
153
}
 
154
 
 
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 {
 
162
                        return nil, err
 
163
                } else if value, err = option.validate(name, value); err != nil {
 
164
                        return nil, err
 
165
                }
 
166
                out[name] = value
 
167
        }
 
168
        return out, nil
 
169
}
 
170
 
 
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 {
 
177
                                out[name] = value
 
178
                        }
 
179
                }
 
180
        }
 
181
        return out
 
182
}
 
183
 
 
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)
 
191
                if err != nil {
 
192
                        return nil, err
 
193
                }
 
194
                value, err := option.parse(name, str)
 
195
                if err != nil {
 
196
                        return nil, err
 
197
                }
 
198
                out[name] = value
 
199
        }
 
200
        return out, nil
 
201
}
 
202
 
 
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)
 
212
        }
 
213
        settings, ok := allSettings[key]
 
214
        if !ok {
 
215
                return nil, fmt.Errorf("no settings found for %q", key)
 
216
        }
 
217
        out := make(Settings)
 
218
        for name, value := range settings {
 
219
                option, err := c.option(name)
 
220
                if err != nil {
 
221
                        return nil, err
 
222
                }
 
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 {
 
226
                                return nil, err
 
227
                        }
 
228
                } else if value, err = option.validate(name, value); err != nil {
 
229
                        return nil, err
 
230
                }
 
231
                out[name] = value
 
232
        }
 
233
        return out, nil
 
234
}