github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/provider/lxd/config.go (about)

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