github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/environs/config.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package environs 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 12 "github.com/juju/errors" 13 "github.com/juju/loggo" 14 goyaml "gopkg.in/yaml.v1" 15 16 "github.com/juju/juju/environs/config" 17 "github.com/juju/juju/juju/osenv" 18 ) 19 20 var logger = loggo.GetLogger("juju.environs") 21 22 // environ holds information about one environment. 23 type environ struct { 24 config *config.Config 25 err error // an error if the config data could not be parsed. 26 } 27 28 // Environs holds information about each named environment 29 // in an environments.yaml file. 30 type Environs struct { 31 Default string // The name of the default environment. 32 rawEnvirons map[string]map[string]interface{} 33 } 34 35 // Names returns the list of environment names. 36 func (e *Environs) Names() (names []string) { 37 for name := range e.rawEnvirons { 38 names = append(names, name) 39 } 40 return 41 } 42 43 func validateEnvironmentKind(rawEnviron map[string]interface{}) error { 44 kind, _ := rawEnviron["type"].(string) 45 if kind == "" { 46 return fmt.Errorf("environment %q has no type", rawEnviron["name"]) 47 } 48 p, _ := Provider(kind) 49 if p == nil { 50 return fmt.Errorf("environment %q has an unknown provider type %q", rawEnviron["name"], kind) 51 } 52 return nil 53 } 54 55 // disallowedWithNew holds those attributes 56 // that can not be set in an initial environment 57 // config used to bootstrap (they must only be set 58 // on a running environment where appropriate 59 // validation can be performed). 60 var disallowedWithBootstrap = []string{ 61 config.StorageDefaultBlockSourceKey, 62 } 63 64 // Config returns the environment configuration for the environment 65 // with the given name. If the configuration is not 66 // found, an errors.NotFoundError is returned. 67 func (envs *Environs) Config(name string) (*config.Config, error) { 68 if name == "" { 69 name = envs.Default 70 if name == "" { 71 return nil, errors.New("no default environment found") 72 } 73 } 74 attrs, ok := envs.rawEnvirons[name] 75 if !ok { 76 return nil, errors.NotFoundf("environment %q", name) 77 } 78 if err := validateEnvironmentKind(attrs); err != nil { 79 return nil, errors.Trace(err) 80 } 81 82 // Check that we don't have any disallowed fields in new configs used for bootstrap. 83 for _, attr := range disallowedWithBootstrap { 84 if _, ok := attrs[attr]; ok { 85 return nil, fmt.Errorf("attribute %q is not allowed in bootstrap configurations", attr) 86 } 87 } 88 89 // If deprecated config attributes are used, log warnings so the user can know 90 // that they need to be fixed. 91 // We also look up what any new values might be so we can tell the user. 92 newAttrs := config.ProcessDeprecatedAttributes(attrs) 93 envs.logDeprecatedWarnings(attrs, newAttrs, config.ToolsMetadataURLKey, config.AgentMetadataURLKey) 94 95 // null has been renamed to manual (with an alias for existing config). 96 if oldType, _ := attrs["type"].(string); oldType == "null" { 97 logger.Warningf( 98 "Provider type \"null\" has been renamed to \"manual\".\n" + 99 "Please update your environment configuration.", 100 ) 101 } 102 // lxc-use-clone has been renamed to lxc-clone 103 envs.logDeprecatedWarnings(attrs, newAttrs, config.LxcUseClone, config.LxcClone) 104 105 // provisioner-safe-mode has been renamed to provisioner-harvest-mode, so log warnings to the user 106 envs.logDeprecatedWarnings(attrs, newAttrs, config.ProvisionerSafeModeKey, config.ProvisionerHarvestModeKey) 107 108 // tools-stream has been renamed to agent-stream, so log warnings to the user 109 envs.logDeprecatedWarnings(attrs, newAttrs, config.ToolsStreamKey, config.AgentStreamKey) 110 111 // Block attributes only matter if they have been used 112 envs.logBlockDeprecationWarnings(attrs) 113 114 cfg, err := config.New(config.UseDefaults, attrs) 115 if err != nil { 116 return nil, err 117 } 118 return cfg, nil 119 } 120 121 func (envs *Environs) logBlockDeprecationWarnings(attrs map[string]interface{}) { 122 checkBlockVar := func(key string) { 123 if used, ok := attrs[key]; ok { 124 if used.(bool) { 125 envs.logBlockWarning(key, used) 126 } 127 } 128 } 129 checkBlockVar(config.PreventDestroyEnvironmentKey) 130 checkBlockVar(config.PreventRemoveObjectKey) 131 checkBlockVar(config.PreventAllChangesKey) 132 } 133 134 func (envs *Environs) logBlockWarning(key string, value interface{}) { 135 logger.Warningf( 136 "Config attribute %q (%v) is deprecated and will be ignored since \n"+ 137 "upgrade process takes care of it. \n"+ 138 "The attribute %q should be removed from your configuration.", 139 key, value, key, 140 ) 141 } 142 143 // logDeprecatedWarnings constructs log warning messages for deprecated attributes names. 144 // It checks if both old and new attribute names are provided. 145 // When both are provided, the message warns to remove old attribute from configuration. 146 // When only old attribute name is used, the message advises to replace it with the new name. 147 func (envs *Environs) logDeprecatedWarnings(attrs, newAttrs map[string]interface{}, oldKey, newKey string) { 148 if oldValue := attrs[oldKey]; oldValue != nil { 149 // no need to warn if attribute is unused 150 if oldStr, ok := oldValue.(string); ok && oldStr == "" { 151 return 152 } 153 newValue, newValueSpecified := attrs[newKey] 154 var msg string 155 if newValueSpecified { 156 msg = fmt.Sprintf( 157 "Config attribute %q (%v) is deprecated and will be ignored since \n"+ 158 "the new %q (%v) attribute has also been used. \n"+ 159 "The attribute %q should be removed from your configuration.", 160 oldKey, oldValue, newKey, newValue, oldKey) 161 } else { 162 msg = fmt.Sprintf( 163 "Config attribute %q (%v) is deprecated. \n"+ 164 "It is replaced by %q attribute. \n"+ 165 "Your configuration should be updated to set %q as follows \n%v: %v.", 166 oldKey, oldValue, newKey, newKey, newKey, newAttrs[newKey]) 167 } 168 logger.Warningf(msg) 169 } 170 } 171 172 // ProviderRegistry is an interface that provides methods for registering 173 // and obtaining environment providers by provider name. 174 type ProviderRegistry interface { 175 // RegisterProvider registers a new environment provider with the given 176 // name, and zero or more aliases. If a provider already exists with the 177 // given name or alias, an error will be returned. 178 RegisterProvider(p EnvironProvider, providerType string, providerTypeAliases ...string) error 179 180 // RegisteredProviders returns the names of the registered environment 181 // providers. 182 RegisteredProviders() []string 183 184 // Provider returns the environment provider with the specified name. 185 Provider(providerType string) (EnvironProvider, error) 186 } 187 188 // GlobalProviderRegistry returns the global provider registry. 189 func GlobalProviderRegistry() ProviderRegistry { 190 return globalProviders 191 } 192 193 type globalProviderRegistry struct { 194 // providers maps from provider type to EnvironProvider for 195 // each registered provider type. 196 providers map[string]EnvironProvider 197 // providerAliases is a map of provider type aliases. 198 aliases map[string]string 199 } 200 201 var globalProviders = &globalProviderRegistry{ 202 providers: map[string]EnvironProvider{}, 203 aliases: map[string]string{}, 204 } 205 206 func (r *globalProviderRegistry) RegisterProvider(p EnvironProvider, providerType string, providerTypeAliases ...string) error { 207 if r.providers[providerType] != nil || r.aliases[providerType] != "" { 208 return errors.Errorf("duplicate provider name %q", providerType) 209 } 210 r.providers[providerType] = p 211 for _, alias := range providerTypeAliases { 212 if r.providers[alias] != nil || r.aliases[alias] != "" { 213 return errors.Errorf("duplicate provider alias %q", alias) 214 } 215 r.aliases[alias] = providerType 216 } 217 return nil 218 } 219 220 func (r *globalProviderRegistry) RegisteredProviders() []string { 221 var p []string 222 for k := range r.providers { 223 p = append(p, k) 224 } 225 return p 226 } 227 228 func (r *globalProviderRegistry) Provider(providerType string) (EnvironProvider, error) { 229 if alias, ok := r.aliases[providerType]; ok { 230 providerType = alias 231 } 232 p, ok := r.providers[providerType] 233 if !ok { 234 return nil, errors.Errorf("no registered provider for %q", providerType) 235 } 236 return p, nil 237 } 238 239 // RegisterProvider registers a new environment provider. Name gives the name 240 // of the provider, and p the interface to that provider. 241 // 242 // RegisterProvider will panic if the provider name or any of the aliases 243 // are registered more than once. 244 func RegisterProvider(name string, p EnvironProvider, alias ...string) { 245 if err := GlobalProviderRegistry().RegisterProvider(p, name, alias...); err != nil { 246 panic(fmt.Errorf("juju: %v", err)) 247 } 248 } 249 250 // RegisteredProviders enumerate all the environ providers which have been registered. 251 func RegisteredProviders() []string { 252 return GlobalProviderRegistry().RegisteredProviders() 253 } 254 255 // Provider returns the previously registered provider with the given type. 256 func Provider(providerType string) (EnvironProvider, error) { 257 return GlobalProviderRegistry().Provider(providerType) 258 } 259 260 // ReadEnvironsBytes parses the contents of an environments.yaml file 261 // and returns its representation. An environment with an unknown type 262 // will only generate an error when New is called for that environment. 263 // Attributes for environments with known types are checked. 264 func ReadEnvironsBytes(data []byte) (*Environs, error) { 265 var raw struct { 266 Default string 267 Environments map[string]map[string]interface{} 268 } 269 err := goyaml.Unmarshal(data, &raw) 270 if err != nil { 271 return nil, err 272 } 273 274 if raw.Default != "" && raw.Environments[raw.Default] == nil { 275 return nil, errors.Errorf("default environment %q does not exist", raw.Default) 276 } 277 if raw.Default == "" { 278 // If there's a single environment, then we get the default 279 // automatically. 280 if len(raw.Environments) == 1 { 281 for name := range raw.Environments { 282 raw.Default = name 283 break 284 } 285 } 286 } 287 for name, attrs := range raw.Environments { 288 // store the name of the this environment in the config itself 289 // so that providers can see it. 290 attrs["name"] = name 291 } 292 return &Environs{raw.Default, raw.Environments}, nil 293 } 294 295 func environsPath(path string) string { 296 if path == "" { 297 path = osenv.JujuHomePath("environments.yaml") 298 } 299 return path 300 } 301 302 // NoEnvError indicates the default environment config file is missing. 303 type NoEnvError struct { 304 error 305 } 306 307 // IsNoEnv reports whether err is a NoEnvError. 308 func IsNoEnv(err error) bool { 309 _, ok := err.(NoEnvError) 310 return ok 311 } 312 313 // ReadEnvirons reads the juju environments.yaml file 314 // and returns the result of running ParseEnvironments 315 // on the file's contents. 316 // If path is empty, $HOME/.juju/environments.yaml is used. 317 func ReadEnvirons(path string) (*Environs, error) { 318 environsFilepath := environsPath(path) 319 data, err := ioutil.ReadFile(environsFilepath) 320 if err != nil { 321 if os.IsNotExist(err) { 322 return nil, NoEnvError{err} 323 } 324 return nil, err 325 } 326 e, err := ReadEnvironsBytes(data) 327 if err != nil { 328 return nil, fmt.Errorf("cannot parse %q: %v", environsFilepath, err) 329 } 330 return e, nil 331 } 332 333 // WriteEnvirons creates a new juju environments.yaml file with the specified contents. 334 func WriteEnvirons(path string, fileContents string) (string, error) { 335 environsFilepath := environsPath(path) 336 environsDir := filepath.Dir(environsFilepath) 337 var info os.FileInfo 338 var err error 339 if info, err = os.Lstat(environsDir); os.IsNotExist(err) { 340 if err = os.MkdirAll(environsDir, 0700); err != nil { 341 return "", err 342 } 343 } else if err != nil { 344 return "", err 345 } else if info.Mode().Perm() != 0700 { 346 logger.Warningf("permission of %q is %q", environsDir, info.Mode().Perm()) 347 } 348 if err := ioutil.WriteFile(environsFilepath, []byte(fileContents), 0600); err != nil { 349 return "", err 350 } 351 // WriteFile does not change permissions of existing files. 352 if err := os.Chmod(environsFilepath, 0600); err != nil { 353 return "", err 354 } 355 return environsFilepath, nil 356 } 357 358 // BootstrapConfig returns a copy of the supplied configuration with the 359 // admin-secret and ca-private-key attributes removed. If the resulting 360 // config is not suitable for bootstrapping an environment, an error is 361 // returned. 362 func BootstrapConfig(cfg *config.Config) (*config.Config, error) { 363 m := cfg.AllAttrs() 364 // We never want to push admin-secret or the root CA private key to the cloud. 365 delete(m, "admin-secret") 366 delete(m, "ca-private-key") 367 cfg, err := config.New(config.NoDefaults, m) 368 if err != nil { 369 return nil, err 370 } 371 if _, ok := cfg.AgentVersion(); !ok { 372 return nil, fmt.Errorf("environment configuration has no agent-version") 373 } 374 return cfg, nil 375 }