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