1
// Copyright 2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/errors"
12
"github.com/juju/schema"
13
"gopkg.in/juju/environschema.v1"
15
"github.com/juju/juju/environs/config"
16
"github.com/juju/juju/tools/lxdclient"
19
// TODO(ericsnow) Support providing cert/key file.
21
// The LXD-specific config keys.
23
cfgRemoteURL = "remote-url"
24
cfgClientCert = "client-cert"
25
cfgClientKey = "client-key"
26
cfgServerPEMCert = "server-cert"
29
// configSchema defines the schema for the configuration attributes
30
// defined by the LXD provider.
31
var configSchema = environschema.Fields{
33
Description: `Identifies the LXD API server to use for managing containers, if any.`,
34
Type: environschema.Tstring,
38
Description: `The client key used for connecting to a LXD host machine.`,
39
Type: environschema.Tstring,
43
Description: `The client cert used for connecting to a LXD host machine.`,
44
Type: environschema.Tstring,
48
Description: `The certificate of the LXD server on the host machine.`,
49
Type: environschema.Tstring,
55
// TODO(ericsnow) Extract the defaults from configSchema as soon as
56
// (or if) environschema.Attr supports defaults.
58
configBaseDefaults = schema.Defaults{
65
configFields, configDefaults = func() (schema.Fields, schema.Defaults) {
66
fields, defaults, err := configSchema.ValidationSchema()
70
defaults = updateDefaults(defaults, configBaseDefaults)
71
return fields, defaults
74
configSecretFields = []string{
75
cfgClientKey, // only privileged agents should get to talk to LXD
79
func updateDefaults(defaults schema.Defaults, updates schema.Defaults) schema.Defaults {
80
updated := schema.Defaults{}
81
for k, v := range defaults {
84
for k, v := range updates {
85
// TODO(ericsnow) Delete the item if v is nil?
91
func adjustDefaults(cfg *config.Config, defaults map[string]interface{}) (map[string]interface{}, []string) {
93
updated := make(map[string]interface{})
94
for k, v := range defaults {
101
// TODO(ericsnow) environschema.Fields should have this...
102
func ensureImmutableFields(oldAttrs, newAttrs map[string]interface{}) error {
103
for name, attr := range configSchema {
107
if newAttrs[name] != oldAttrs[name] {
108
return errors.Errorf("%s: cannot change from %v to %v", name, oldAttrs[name], newAttrs[name])
114
type environConfig struct {
116
attrs map[string]interface{}
119
// newConfig builds a new environConfig from the provided Config and
121
func newConfig(cfg *config.Config) *environConfig {
122
return &environConfig{
124
attrs: cfg.UnknownAttrs(),
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...
134
// Ensure that the provided config is valid.
135
if err := config.Validate(cfg, nil); err != nil {
136
return nil, errors.Trace(err)
139
// Apply the defaults and coerce/validate the custom config attrs.
140
fixedDefaults, unset := adjustDefaults(cfg, defaults)
141
cfg, err := cfg.Remove(unset)
143
return nil, errors.Trace(err)
145
validated, err := cfg.ValidateUnknownAttrs(configFields, fixedDefaults)
147
return nil, errors.Trace(err)
149
validCfg, err := cfg.Apply(validated)
151
return nil, errors.Trace(err)
155
ecfg := newConfig(validCfg)
157
// Update to defaults set via client config.
158
clientCfg, err := ecfg.clientConfig()
160
return nil, errors.Trace(err)
162
ecfg, err = ecfg.updateForClientConfig(clientCfg)
164
return nil, errors.Trace(err)
167
// Do final (more complex, provider-specific) validation.
168
if err := ecfg.validate(); err != nil {
169
return nil, errors.Trace(err)
175
func (c *environConfig) dirname() string {
176
// TODO(ericsnow) Put it under one of the juju/paths.*() directories.
180
func (c *environConfig) remoteURL() string {
181
raw := c.attrs[cfgRemoteURL]
185
func (c *environConfig) clientCert() string {
186
raw := c.attrs[cfgClientCert]
190
func (c *environConfig) clientKey() string {
191
raw := c.attrs[cfgClientKey]
195
func (c *environConfig) serverPEMCert() string {
196
raw := c.attrs[cfgServerPEMCert]
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{
205
ServerPEMCert: c.serverPEMCert(),
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())
215
cfg := lxdclient.Config{
218
cfg, err := cfg.WithDefaults()
220
return cfg, errors.Trace(err)
225
// TODO(ericsnow) Switch to a DI testing approach and eliminiate this var.
226
var asNonLocal = lxdclient.Config.UsingTCPRemote
228
func (c *environConfig) updateForClientConfig(clientCfg lxdclient.Config) (*environConfig, error) {
229
nonlocal, err := asNonLocal(clientCfg)
231
return nil, errors.Trace(err)
235
c.attrs[cfgRemoteURL] = clientCfg.Remote.Host
236
c.attrs[cfgServerPEMCert] = clientCfg.Remote.ServerPEMCert
238
var cert lxdclient.Cert
239
if clientCfg.Remote.Cert != nil {
240
cert = *clientCfg.Remote.Cert
242
c.attrs[cfgClientCert] = string(cert.CertPEM)
243
c.attrs[cfgClientKey] = string(cert.KeyPEM)
245
// Apply the updates.
246
cfg, err := c.Config.Apply(c.attrs)
248
return nil, errors.Trace(err)
250
return newConfig(cfg), nil
253
// secret gathers the "secret" config values and returns them.
254
func (c *environConfig) secret() map[string]string {
255
if len(configSecretFields) == 0 {
259
secretAttrs := make(map[string]string, len(configSecretFields))
260
for _, key := range configSecretFields {
261
secretAttrs[key] = c.attrs[key].(string)
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 == "" {
274
if c.attrs[field].(string) == "" {
275
return errors.Errorf("%s: must not be empty", field)
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())
283
if c.clientCert() != "" && c.clientKey() == "" {
284
return errors.Errorf("missing %s (got %s value %q)", cfgClientKey, cfgClientCert, c.clientCert())
287
// Check sanity of complex provider-specific fields.
288
cfg, err := c.clientConfig()
290
return errors.Trace(err)
292
if err := cfg.Validate(); err != nil {
293
return errors.Trace(err)
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)
308
updates, err := newValidConfig(cfg, configDefaults)
310
return errors.Trace(err)
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)
319
// Apply the updates.
320
// TODO(ericsnow) Should updates.Config be set in instead of cfg?
322
c.attrs = cfg.UnknownAttrs()