github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 }