github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 // Config returns the environment configuration for the environment 56 // with the given name. If the configuration is not 57 // found, an errors.NotFoundError is returned. 58 func (envs *Environs) Config(name string) (*config.Config, error) { 59 if name == "" { 60 name = envs.Default 61 if name == "" { 62 return nil, errors.New("no default environment found") 63 } 64 } 65 attrs, ok := envs.rawEnvirons[name] 66 if !ok { 67 return nil, errors.NotFoundf("environment %q", name) 68 } 69 if err := validateEnvironmentKind(attrs); err != nil { 70 return nil, errors.Trace(err) 71 } 72 73 // If deprecated config attributes are used, log warnings so the user can know 74 // that they need to be fixed. 75 // We also look up what any new values might be so we can tell the user. 76 newAttrs := config.ProcessDeprecatedAttributes(attrs) 77 envs.logDeprecatedWarnings(attrs, newAttrs, config.ToolsMetadataURLKey, config.AgentMetadataURLKey) 78 79 // null has been renamed to manual (with an alias for existing config). 80 if oldType, _ := attrs["type"].(string); oldType == "null" { 81 logger.Warningf( 82 "Provider type \"null\" has been renamed to \"manual\".\n" + 83 "Please update your environment configuration.", 84 ) 85 } 86 // lxc-use-clone has been renamed to lxc-clone 87 envs.logDeprecatedWarnings(attrs, newAttrs, config.LxcUseClone, config.LxcClone) 88 89 // provisioner-safe-mode has been renamed to provisioner-harvest-mode, so log warnings to the user 90 envs.logDeprecatedWarnings(attrs, newAttrs, config.ProvisionerSafeModeKey, config.ProvisionerHarvestModeKey) 91 92 // tools-stream has been renamed to agent-stream, so log warnings to the user 93 envs.logDeprecatedWarnings(attrs, newAttrs, config.ToolsStreamKey, config.AgentStreamKey) 94 95 cfg, err := config.New(config.UseDefaults, attrs) 96 if err != nil { 97 return nil, err 98 } 99 return cfg, nil 100 } 101 102 // logDeprecatedWarnings constructs log warning messages for deprecated attributes names. 103 // It checks if both old and new attribute names are provided. 104 // When both are provided, the message warns to remove old attribute from configuration. 105 // When only old attribute name is used, the message advises to replace it with the new name. 106 func (envs *Environs) logDeprecatedWarnings(attrs, newAttrs map[string]interface{}, oldKey, newKey string) { 107 if oldValue := attrs[oldKey]; oldValue != nil { 108 // no need to warn if attribute is unused 109 if oldStr, ok := oldValue.(string); ok && oldStr == "" { 110 return 111 } 112 newValue, newValueSpecified := attrs[newKey] 113 var msg string 114 if newValueSpecified { 115 msg = fmt.Sprintf( 116 "Config attribute %q (%v) is deprecated and will be ignored since \n"+ 117 "the new %q (%v) attribute has also been used. \n"+ 118 "The attribute %q should be removed from your configuration.", 119 oldKey, oldValue, newKey, newValue, oldKey) 120 } else { 121 msg = fmt.Sprintf( 122 "Config attribute %q (%v) is deprecated. \n"+ 123 "It is replaced by %q attribute. \n"+ 124 "Your configuration should be updated to set %q as follows \n%v: %v.", 125 oldKey, oldValue, newKey, newKey, newKey, newAttrs[newKey]) 126 } 127 logger.Warningf(msg) 128 } 129 } 130 131 // providers maps from provider type to EnvironProvider for 132 // each registered provider type. 133 // 134 // providers should not typically be used directly; the 135 // Provider function will handle provider type aliases, 136 // and should be used instead. 137 var providers = make(map[string]EnvironProvider) 138 139 // providerAliases is a map of provider type aliases. 140 var providerAliases = make(map[string]string) 141 142 // RegisterProvider registers a new environment provider. Name gives the name 143 // of the provider, and p the interface to that provider. 144 // 145 // RegisterProvider will panic if the provider name or any of the aliases 146 // are registered more than once. 147 func RegisterProvider(name string, p EnvironProvider, alias ...string) { 148 if providers[name] != nil || providerAliases[name] != "" { 149 panic(errors.Errorf("juju: duplicate provider name %q", name)) 150 } 151 providers[name] = p 152 for _, alias := range alias { 153 if providers[alias] != nil || providerAliases[alias] != "" { 154 panic(errors.Errorf("juju: duplicate provider alias %q", alias)) 155 } 156 providerAliases[alias] = name 157 } 158 } 159 160 // RegisteredProviders enumerate all the environ providers which have been registered. 161 func RegisteredProviders() []string { 162 var p []string 163 for k := range providers { 164 p = append(p, k) 165 } 166 return p 167 } 168 169 // Provider returns the previously registered provider with the given type. 170 func Provider(providerType string) (EnvironProvider, error) { 171 if alias, ok := providerAliases[providerType]; ok { 172 providerType = alias 173 } 174 p, ok := providers[providerType] 175 if !ok { 176 return nil, errors.Errorf("no registered provider for %q", providerType) 177 } 178 return p, nil 179 } 180 181 // ReadEnvironsBytes parses the contents of an environments.yaml file 182 // and returns its representation. An environment with an unknown type 183 // will only generate an error when New is called for that environment. 184 // Attributes for environments with known types are checked. 185 func ReadEnvironsBytes(data []byte) (*Environs, error) { 186 var raw struct { 187 Default string 188 Environments map[string]map[string]interface{} 189 } 190 err := goyaml.Unmarshal(data, &raw) 191 if err != nil { 192 return nil, err 193 } 194 195 if raw.Default != "" && raw.Environments[raw.Default] == nil { 196 return nil, errors.Errorf("default environment %q does not exist", raw.Default) 197 } 198 if raw.Default == "" { 199 // If there's a single environment, then we get the default 200 // automatically. 201 if len(raw.Environments) == 1 { 202 for name := range raw.Environments { 203 raw.Default = name 204 break 205 } 206 } 207 } 208 for name, attrs := range raw.Environments { 209 // store the name of the this environment in the config itself 210 // so that providers can see it. 211 attrs["name"] = name 212 } 213 return &Environs{raw.Default, raw.Environments}, nil 214 } 215 216 func environsPath(path string) string { 217 if path == "" { 218 path = osenv.JujuHomePath("environments.yaml") 219 } 220 return path 221 } 222 223 // NoEnvError indicates the default environment config file is missing. 224 type NoEnvError struct { 225 error 226 } 227 228 // IsNoEnv reports whether err is a NoEnvError. 229 func IsNoEnv(err error) bool { 230 _, ok := err.(NoEnvError) 231 return ok 232 } 233 234 // ReadEnvirons reads the juju environments.yaml file 235 // and returns the result of running ParseEnvironments 236 // on the file's contents. 237 // If path is empty, $HOME/.juju/environments.yaml is used. 238 func ReadEnvirons(path string) (*Environs, error) { 239 environsFilepath := environsPath(path) 240 data, err := ioutil.ReadFile(environsFilepath) 241 if err != nil { 242 if os.IsNotExist(err) { 243 return nil, NoEnvError{err} 244 } 245 return nil, err 246 } 247 e, err := ReadEnvironsBytes(data) 248 if err != nil { 249 return nil, fmt.Errorf("cannot parse %q: %v", environsFilepath, err) 250 } 251 return e, nil 252 } 253 254 // WriteEnvirons creates a new juju environments.yaml file with the specified contents. 255 func WriteEnvirons(path string, fileContents string) (string, error) { 256 environsFilepath := environsPath(path) 257 environsDir := filepath.Dir(environsFilepath) 258 var info os.FileInfo 259 var err error 260 if info, err = os.Lstat(environsDir); os.IsNotExist(err) { 261 if err = os.MkdirAll(environsDir, 0700); err != nil { 262 return "", err 263 } 264 } else if err != nil { 265 return "", err 266 } else if info.Mode().Perm() != 0700 { 267 logger.Warningf("permission of %q is %q", environsDir, info.Mode().Perm()) 268 } 269 if err := ioutil.WriteFile(environsFilepath, []byte(fileContents), 0600); err != nil { 270 return "", err 271 } 272 // WriteFile does not change permissions of existing files. 273 if err := os.Chmod(environsFilepath, 0600); err != nil { 274 return "", err 275 } 276 return environsFilepath, nil 277 } 278 279 // BootstrapConfig returns a copy of the supplied configuration with the 280 // admin-secret and ca-private-key attributes removed. If the resulting 281 // config is not suitable for bootstrapping an environment, an error is 282 // returned. 283 func BootstrapConfig(cfg *config.Config) (*config.Config, error) { 284 m := cfg.AllAttrs() 285 // We never want to push admin-secret or the root CA private key to the cloud. 286 delete(m, "admin-secret") 287 delete(m, "ca-private-key") 288 cfg, err := config.New(config.NoDefaults, m) 289 if err != nil { 290 return nil, err 291 } 292 if _, ok := cfg.AgentVersion(); !ok { 293 return nil, fmt.Errorf("environment configuration has no agent-version") 294 } 295 return cfg, nil 296 }