~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/lxd/config.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
// +build go1.3
 
5
 
 
6
package lxd
 
7
 
 
8
import (
 
9
        "fmt"
 
10
 
 
11
        "github.com/juju/errors"
 
12
        "github.com/juju/schema"
 
13
        "gopkg.in/juju/environschema.v1"
 
14
 
 
15
        "github.com/juju/juju/environs/config"
 
16
        "github.com/juju/juju/tools/lxdclient"
 
17
)
 
18
 
 
19
// TODO(ericsnow) Support providing cert/key file.
 
20
 
 
21
// The LXD-specific config keys.
 
22
const (
 
23
        cfgRemoteURL     = "remote-url"
 
24
        cfgClientCert    = "client-cert"
 
25
        cfgClientKey     = "client-key"
 
26
        cfgServerPEMCert = "server-cert"
 
27
)
 
28
 
 
29
// configSchema defines the schema for the configuration attributes
 
30
// defined by the LXD provider.
 
31
var configSchema = environschema.Fields{
 
32
        cfgRemoteURL: {
 
33
                Description: `Identifies the LXD API server to use for managing containers, if any.`,
 
34
                Type:        environschema.Tstring,
 
35
                Immutable:   true,
 
36
        },
 
37
        cfgClientKey: {
 
38
                Description: `The client key used for connecting to a LXD host machine.`,
 
39
                Type:        environschema.Tstring,
 
40
                Immutable:   true,
 
41
        },
 
42
        cfgClientCert: {
 
43
                Description: `The client cert used for connecting to a LXD host machine.`,
 
44
                Type:        environschema.Tstring,
 
45
                Immutable:   true,
 
46
        },
 
47
        cfgServerPEMCert: {
 
48
                Description: `The certificate of the LXD server on the host machine.`,
 
49
                Type:        environschema.Tstring,
 
50
                Immutable:   true,
 
51
        },
 
52
}
 
53
 
 
54
var (
 
55
        // TODO(ericsnow) Extract the defaults from configSchema as soon as
 
56
        // (or if) environschema.Attr supports defaults.
 
57
 
 
58
        configBaseDefaults = schema.Defaults{
 
59
                cfgRemoteURL:     "",
 
60
                cfgClientCert:    "",
 
61
                cfgClientKey:     "",
 
62
                cfgServerPEMCert: "",
 
63
        }
 
64
 
 
65
        configFields, configDefaults = func() (schema.Fields, schema.Defaults) {
 
66
                fields, defaults, err := configSchema.ValidationSchema()
 
67
                if err != nil {
 
68
                        panic(err)
 
69
                }
 
70
                defaults = updateDefaults(defaults, configBaseDefaults)
 
71
                return fields, defaults
 
72
        }()
 
73
 
 
74
        configSecretFields = []string{
 
75
                cfgClientKey, // only privileged agents should get to talk to LXD
 
76
        }
 
77
)
 
78
 
 
79
func updateDefaults(defaults schema.Defaults, updates schema.Defaults) schema.Defaults {
 
80
        updated := schema.Defaults{}
 
81
        for k, v := range defaults {
 
82
                updated[k] = v
 
83
        }
 
84
        for k, v := range updates {
 
85
                // TODO(ericsnow) Delete the item if v is nil?
 
86
                updated[k] = v
 
87
        }
 
88
        return updated
 
89
}
 
90
 
 
91
func adjustDefaults(cfg *config.Config, defaults map[string]interface{}) (map[string]interface{}, []string) {
 
92
        var unset []string
 
93
        updated := make(map[string]interface{})
 
94
        for k, v := range defaults {
 
95
                updated[k] = v
 
96
        }
 
97
 
 
98
        return updated, unset
 
99
}
 
100
 
 
101
// TODO(ericsnow) environschema.Fields should have this...
 
102
func ensureImmutableFields(oldAttrs, newAttrs map[string]interface{}) error {
 
103
        for name, attr := range configSchema {
 
104
                if !attr.Immutable {
 
105
                        continue
 
106
                }
 
107
                if newAttrs[name] != oldAttrs[name] {
 
108
                        return errors.Errorf("%s: cannot change from %v to %v", name, oldAttrs[name], newAttrs[name])
 
109
                }
 
110
        }
 
111
        return nil
 
112
}
 
113
 
 
114
type environConfig struct {
 
115
        *config.Config
 
116
        attrs map[string]interface{}
 
117
}
 
118
 
 
119
// newConfig builds a new environConfig from the provided Config and
 
120
// returns it.
 
121
func newConfig(cfg *config.Config) *environConfig {
 
122
        return &environConfig{
 
123
                Config: cfg,
 
124
                attrs:  cfg.UnknownAttrs(),
 
125
        }
 
126
}
 
127
 
 
128
// newValidConfig builds a new environConfig from the provided Config
 
129
// and returns it. This includes applying the provided defaults
 
130
// values, if any. The resulting config values are validated.
 
131
func newValidConfig(cfg *config.Config, defaults map[string]interface{}) (*environConfig, error) {
 
132
        // Any auth credentials handling should happen first...
 
133
 
 
134
        // Ensure that the provided config is valid.
 
135
        if err := config.Validate(cfg, nil); err != nil {
 
136
                return nil, errors.Trace(err)
 
137
        }
 
138
 
 
139
        // Apply the defaults and coerce/validate the custom config attrs.
 
140
        fixedDefaults, unset := adjustDefaults(cfg, defaults)
 
141
        cfg, err := cfg.Remove(unset)
 
142
        if err != nil {
 
143
                return nil, errors.Trace(err)
 
144
        }
 
145
        validated, err := cfg.ValidateUnknownAttrs(configFields, fixedDefaults)
 
146
        if err != nil {
 
147
                return nil, errors.Trace(err)
 
148
        }
 
149
        validCfg, err := cfg.Apply(validated)
 
150
        if err != nil {
 
151
                return nil, errors.Trace(err)
 
152
        }
 
153
 
 
154
        // Build the config.
 
155
        ecfg := newConfig(validCfg)
 
156
 
 
157
        // Update to defaults set via client config.
 
158
        clientCfg, err := ecfg.clientConfig()
 
159
        if err != nil {
 
160
                return nil, errors.Trace(err)
 
161
        }
 
162
        ecfg, err = ecfg.updateForClientConfig(clientCfg)
 
163
        if err != nil {
 
164
                return nil, errors.Trace(err)
 
165
        }
 
166
 
 
167
        // Do final (more complex, provider-specific) validation.
 
168
        if err := ecfg.validate(); err != nil {
 
169
                return nil, errors.Trace(err)
 
170
        }
 
171
 
 
172
        return ecfg, nil
 
173
}
 
174
 
 
175
func (c *environConfig) dirname() string {
 
176
        // TODO(ericsnow) Put it under one of the juju/paths.*() directories.
 
177
        return ""
 
178
}
 
179
 
 
180
func (c *environConfig) remoteURL() string {
 
181
        raw := c.attrs[cfgRemoteURL]
 
182
        return raw.(string)
 
183
}
 
184
 
 
185
func (c *environConfig) clientCert() string {
 
186
        raw := c.attrs[cfgClientCert]
 
187
        return raw.(string)
 
188
}
 
189
 
 
190
func (c *environConfig) clientKey() string {
 
191
        raw := c.attrs[cfgClientKey]
 
192
        return raw.(string)
 
193
}
 
194
 
 
195
func (c *environConfig) serverPEMCert() string {
 
196
        raw := c.attrs[cfgServerPEMCert]
 
197
        return raw.(string)
 
198
}
 
199
 
 
200
// clientConfig builds a LXD Config based on the env config and returns it.
 
201
func (c *environConfig) clientConfig() (lxdclient.Config, error) {
 
202
        remote := lxdclient.Remote{
 
203
                Name:          "juju-remote",
 
204
                Host:          c.remoteURL(),
 
205
                ServerPEMCert: c.serverPEMCert(),
 
206
        }
 
207
        if c.clientCert() != "" {
 
208
                certPEM := []byte(c.clientCert())
 
209
                keyPEM := []byte(c.clientKey())
 
210
                cert := lxdclient.NewCert(certPEM, keyPEM)
 
211
                cert.Name = fmt.Sprintf("juju cert for env %q", c.Name())
 
212
                remote.Cert = &cert
 
213
        }
 
214
 
 
215
        cfg := lxdclient.Config{
 
216
                Remote: remote,
 
217
        }
 
218
        cfg, err := cfg.WithDefaults()
 
219
        if err != nil {
 
220
                return cfg, errors.Trace(err)
 
221
        }
 
222
        return cfg, nil
 
223
}
 
224
 
 
225
// TODO(ericsnow) Switch to a DI testing approach and eliminiate this var.
 
226
var asNonLocal = lxdclient.Config.UsingTCPRemote
 
227
 
 
228
func (c *environConfig) updateForClientConfig(clientCfg lxdclient.Config) (*environConfig, error) {
 
229
        nonlocal, err := asNonLocal(clientCfg)
 
230
        if err != nil {
 
231
                return nil, errors.Trace(err)
 
232
        }
 
233
        clientCfg = nonlocal
 
234
 
 
235
        c.attrs[cfgRemoteURL] = clientCfg.Remote.Host
 
236
        c.attrs[cfgServerPEMCert] = clientCfg.Remote.ServerPEMCert
 
237
 
 
238
        var cert lxdclient.Cert
 
239
        if clientCfg.Remote.Cert != nil {
 
240
                cert = *clientCfg.Remote.Cert
 
241
        }
 
242
        c.attrs[cfgClientCert] = string(cert.CertPEM)
 
243
        c.attrs[cfgClientKey] = string(cert.KeyPEM)
 
244
 
 
245
        // Apply the updates.
 
246
        cfg, err := c.Config.Apply(c.attrs)
 
247
        if err != nil {
 
248
                return nil, errors.Trace(err)
 
249
        }
 
250
        return newConfig(cfg), nil
 
251
}
 
252
 
 
253
// secret gathers the "secret" config values and returns them.
 
254
func (c *environConfig) secret() map[string]string {
 
255
        if len(configSecretFields) == 0 {
 
256
                return nil
 
257
        }
 
258
 
 
259
        secretAttrs := make(map[string]string, len(configSecretFields))
 
260
        for _, key := range configSecretFields {
 
261
                secretAttrs[key] = c.attrs[key].(string)
 
262
        }
 
263
        return secretAttrs
 
264
}
 
265
 
 
266
// validate checks more complex LCD-specific config values.
 
267
func (c *environConfig) validate() error {
 
268
        // All fields must be populated, even with just the default.
 
269
        // TODO(ericsnow) Shouldn't configSchema support this?
 
270
        for field := range configFields {
 
271
                if dflt, ok := configDefaults[field]; ok && dflt == "" {
 
272
                        continue
 
273
                }
 
274
                if c.attrs[field].(string) == "" {
 
275
                        return errors.Errorf("%s: must not be empty", field)
 
276
                }
 
277
        }
 
278
 
 
279
        // If cert is provided then key must be (and vice versa).
 
280
        if c.clientCert() == "" && c.clientKey() != "" {
 
281
                return errors.Errorf("missing %s (got %s value %q)", cfgClientCert, cfgClientKey, c.clientKey())
 
282
        }
 
283
        if c.clientCert() != "" && c.clientKey() == "" {
 
284
                return errors.Errorf("missing %s (got %s value %q)", cfgClientKey, cfgClientCert, c.clientCert())
 
285
        }
 
286
 
 
287
        // Check sanity of complex provider-specific fields.
 
288
        cfg, err := c.clientConfig()
 
289
        if err != nil {
 
290
                return errors.Trace(err)
 
291
        }
 
292
        if err := cfg.Validate(); err != nil {
 
293
                return errors.Trace(err)
 
294
        }
 
295
 
 
296
        return nil
 
297
}
 
298
 
 
299
// update applies changes from the provided config to the env config.
 
300
// Changes to any immutable attributes result in an error.
 
301
func (c *environConfig) update(cfg *config.Config) error {
 
302
        // Validate the updates. newValidConfig does not modify the "known"
 
303
        // config attributes so it is safe to call Validate here first.
 
304
        if err := config.Validate(cfg, c.Config); err != nil {
 
305
                return errors.Trace(err)
 
306
        }
 
307
 
 
308
        updates, err := newValidConfig(cfg, configDefaults)
 
309
        if err != nil {
 
310
                return errors.Trace(err)
 
311
        }
 
312
 
 
313
        // Check that no immutable fields have changed.
 
314
        attrs := updates.UnknownAttrs()
 
315
        if err := ensureImmutableFields(c.attrs, attrs); err != nil {
 
316
                return errors.Trace(err)
 
317
        }
 
318
 
 
319
        // Apply the updates.
 
320
        // TODO(ericsnow) Should updates.Config be set in instead of cfg?
 
321
        c.Config = cfg
 
322
        c.attrs = cfg.UnknownAttrs()
 
323
        return nil
 
324
}