launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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/loggo/loggo" 13 "launchpad.net/goyaml" 14 15 "launchpad.net/juju-core/environs/config" 16 "launchpad.net/juju-core/errors" 17 "launchpad.net/juju-core/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, fmt.Errorf("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, 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 if oldToolsURL := attrs["tools-url"]; oldToolsURL != nil && oldToolsURL.(string) != "" { 76 _, newToolsSpecified := attrs["tools-metadata-url"] 77 var msg string 78 if newToolsSpecified { 79 msg = fmt.Sprintf( 80 "Config attribute %q (%v) is deprecated and will be ignored since\n"+ 81 "the new tools URL attribute %q has also been used.\n"+ 82 "The attribute %q should be removed from your configuration.", 83 "tools-url", oldToolsURL, "tools-metadata-url", "tools-url") 84 } else { 85 msg = fmt.Sprintf( 86 "Config attribute %q (%v) is deprecated.\n"+ 87 "The location to find tools is now specified using the %q attribute.\n"+ 88 "Your configuration should be updated to set %q as follows\n%v: %v.", 89 "tools-url", oldToolsURL, "tools-metadata-url", "tools-metadata-url", "tools-metadata-url", oldToolsURL) 90 } 91 logger.Warningf(msg) 92 } 93 // null has been renamed to manual (with an alias for existing config). 94 if oldType, _ := attrs["type"].(string); oldType == "null" { 95 logger.Warningf( 96 "Provider type \"null\" has been renamed to \"manual\".\n" + 97 "Please update your environment configuration.", 98 ) 99 } 100 101 cfg, err := config.New(config.UseDefaults, attrs) 102 if err != nil { 103 return nil, err 104 } 105 return cfg, nil 106 } 107 108 // providers maps from provider type to EnvironProvider for 109 // each registered provider type. 110 // 111 // providers should not typically be used directly; the 112 // Provider function will handle provider type aliases, 113 // and should be used instead. 114 var providers = make(map[string]EnvironProvider) 115 116 // providerAliases is a map of provider type aliases. 117 var providerAliases = make(map[string]string) 118 119 // RegisterProvider registers a new environment provider. Name gives the name 120 // of the provider, and p the interface to that provider. 121 // 122 // RegisterProvider will panic if the provider name or any of the aliases 123 // are registered more than once. 124 func RegisterProvider(name string, p EnvironProvider, alias ...string) { 125 if providers[name] != nil || providerAliases[name] != "" { 126 panic(fmt.Errorf("juju: duplicate provider name %q", name)) 127 } 128 providers[name] = p 129 for _, alias := range alias { 130 if providers[alias] != nil || providerAliases[alias] != "" { 131 panic(fmt.Errorf("juju: duplicate provider alias %q", alias)) 132 } 133 providerAliases[alias] = name 134 } 135 } 136 137 // Provider returns the previously registered provider with the given type. 138 func Provider(typ string) (EnvironProvider, error) { 139 if alias, ok := providerAliases[typ]; ok { 140 typ = alias 141 } 142 p, ok := providers[typ] 143 if !ok { 144 return nil, fmt.Errorf("no registered provider for %q", typ) 145 } 146 return p, nil 147 } 148 149 // ReadEnvironsBytes parses the contents of an environments.yaml file 150 // and returns its representation. An environment with an unknown type 151 // will only generate an error when New is called for that environment. 152 // Attributes for environments with known types are checked. 153 func ReadEnvironsBytes(data []byte) (*Environs, error) { 154 var raw struct { 155 Default string 156 Environments map[string]map[string]interface{} 157 } 158 err := goyaml.Unmarshal(data, &raw) 159 if err != nil { 160 return nil, err 161 } 162 163 if raw.Default != "" && raw.Environments[raw.Default] == nil { 164 return nil, fmt.Errorf("default environment %q does not exist", raw.Default) 165 } 166 if raw.Default == "" { 167 // If there's a single environment, then we get the default 168 // automatically. 169 if len(raw.Environments) == 1 { 170 for name := range raw.Environments { 171 raw.Default = name 172 break 173 } 174 } 175 } 176 for name, attrs := range raw.Environments { 177 // store the name of the this environment in the config itself 178 // so that providers can see it. 179 attrs["name"] = name 180 } 181 return &Environs{raw.Default, raw.Environments}, nil 182 } 183 184 func environsPath(path string) string { 185 if path == "" { 186 path = osenv.JujuHomePath("environments.yaml") 187 } 188 return path 189 } 190 191 // NoEnvError indicates the default environment config file is missing. 192 type NoEnvError struct { 193 error 194 } 195 196 // IsNoEnv reports whether err is a NoEnvError. 197 func IsNoEnv(err error) bool { 198 _, ok := err.(NoEnvError) 199 return ok 200 } 201 202 // ReadEnvirons reads the juju environments.yaml file 203 // and returns the result of running ParseEnvironments 204 // on the file's contents. 205 // If path is empty, $HOME/.juju/environments.yaml is used. 206 func ReadEnvirons(path string) (*Environs, error) { 207 environsFilepath := environsPath(path) 208 data, err := ioutil.ReadFile(environsFilepath) 209 if err != nil { 210 if os.IsNotExist(err) { 211 return nil, NoEnvError{err} 212 } 213 return nil, err 214 } 215 e, err := ReadEnvironsBytes(data) 216 if err != nil { 217 return nil, fmt.Errorf("cannot parse %q: %v", environsFilepath, err) 218 } 219 return e, nil 220 } 221 222 // WriteEnvirons creates a new juju environments.yaml file with the specified contents. 223 func WriteEnvirons(path string, fileContents string) (string, error) { 224 environsFilepath := environsPath(path) 225 environsDir := filepath.Dir(environsFilepath) 226 var info os.FileInfo 227 var err error 228 if info, err = os.Lstat(environsDir); os.IsNotExist(err) { 229 if err = os.MkdirAll(environsDir, 0700); err != nil { 230 return "", err 231 } 232 } else if err != nil { 233 return "", err 234 } else if info.Mode().Perm() != 0700 { 235 logger.Warningf("permission of %q is %q", environsDir, info.Mode().Perm()) 236 } 237 if err := ioutil.WriteFile(environsFilepath, []byte(fileContents), 0600); err != nil { 238 return "", err 239 } 240 // WriteFile does not change permissions of existing files. 241 if err := os.Chmod(environsFilepath, 0600); err != nil { 242 return "", err 243 } 244 return environsFilepath, nil 245 } 246 247 // BootstrapConfig returns a copy of the supplied configuration with the 248 // admin-secret and ca-private-key attributes removed. If the resulting 249 // config is not suitable for bootstrapping an environment, an error is 250 // returned. 251 func BootstrapConfig(cfg *config.Config) (*config.Config, error) { 252 m := cfg.AllAttrs() 253 // We never want to push admin-secret or the root CA private key to the cloud. 254 delete(m, "admin-secret") 255 delete(m, "ca-private-key") 256 cfg, err := config.New(config.NoDefaults, m) 257 if err != nil { 258 return nil, err 259 } 260 if _, ok := cfg.AgentVersion(); !ok { 261 return nil, fmt.Errorf("environment configuration has no agent-version") 262 } 263 return cfg, nil 264 }