github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/provider/local/environprovider.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package local 5 6 import ( 7 "fmt" 8 "net" 9 "os" 10 "os/user" 11 "syscall" 12 13 "github.com/juju/loggo" 14 "github.com/juju/utils" 15 "github.com/juju/utils/apt" 16 "github.com/juju/utils/proxy" 17 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/juju/osenv" 22 "github.com/juju/juju/provider" 23 "github.com/juju/juju/version" 24 ) 25 26 var logger = loggo.GetLogger("juju.provider.local") 27 28 var _ environs.EnvironProvider = (*environProvider)(nil) 29 30 type environProvider struct{} 31 32 var providerInstance = &environProvider{} 33 34 func init() { 35 environs.RegisterProvider(provider.Local, providerInstance) 36 } 37 38 var userCurrent = user.Current 39 40 // Open implements environs.EnvironProvider.Open. 41 func (environProvider) Open(cfg *config.Config) (environs.Environ, error) { 42 logger.Infof("opening environment %q", cfg.Name()) 43 if _, ok := cfg.AgentVersion(); !ok { 44 newCfg, err := cfg.Apply(map[string]interface{}{ 45 "agent-version": version.Current.Number.String(), 46 }) 47 if err != nil { 48 return nil, err 49 } 50 cfg = newCfg 51 } 52 // Set the "namespace" attribute. We do this here, and not in Prepare, 53 // for backwards compatibility: older versions did not store the namespace 54 // in config. 55 if namespace, _ := cfg.UnknownAttrs()["namespace"].(string); namespace == "" { 56 username := os.Getenv("USER") 57 if username == "" { 58 u, err := userCurrent() 59 if err != nil { 60 return nil, fmt.Errorf("failed to determine username for namespace: %v", err) 61 } 62 username = u.Username 63 } 64 var err error 65 namespace = fmt.Sprintf("%s-%s", username, cfg.Name()) 66 cfg, err = cfg.Apply(map[string]interface{}{"namespace": namespace}) 67 if err != nil { 68 return nil, fmt.Errorf("failed to create namespace: %v", err) 69 } 70 } 71 // Do the initial validation on the config. 72 localConfig, err := providerInstance.newConfig(cfg) 73 if err != nil { 74 return nil, err 75 } 76 if err := VerifyPrerequisites(localConfig.container()); err != nil { 77 return nil, fmt.Errorf("failed verification of local provider prerequisites: %v", err) 78 } 79 environ := &localEnviron{name: cfg.Name()} 80 if err := environ.SetConfig(cfg); err != nil { 81 return nil, fmt.Errorf("failure setting config: %v", err) 82 } 83 return environ, nil 84 } 85 86 var detectAptProxies = apt.DetectProxies 87 88 // Prepare implements environs.EnvironProvider.Prepare. 89 func (p environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { 90 // The user must not set bootstrap-ip; this is determined by the provider, 91 // and its presence used to determine whether the environment has yet been 92 // bootstrapped. 93 if _, ok := cfg.UnknownAttrs()["bootstrap-ip"]; ok { 94 return nil, fmt.Errorf("bootstrap-ip must not be specified") 95 } 96 err := checkLocalPort(cfg.StatePort(), "state port") 97 if err != nil { 98 return nil, err 99 } 100 err = checkLocalPort(cfg.APIPort(), "API port") 101 if err != nil { 102 return nil, err 103 } 104 // If the user has specified no values for any of the three normal 105 // proxies, then look in the environment and set them. 106 attrs := map[string]interface{}{ 107 // We must not proxy SSH through the API server in a 108 // local provider environment. Besides not being useful, 109 // it may not work; there is no requirement for sshd to 110 // be available on machine-0. 111 "proxy-ssh": false, 112 } 113 setIfNotBlank := func(key, value string) { 114 if value != "" { 115 attrs[key] = value 116 } 117 } 118 logger.Tracef("Look for proxies?") 119 if cfg.HttpProxy() == "" && 120 cfg.HttpsProxy() == "" && 121 cfg.FtpProxy() == "" && 122 cfg.NoProxy() == "" { 123 proxySettings := proxy.DetectProxies() 124 logger.Tracef("Proxies detected %#v", proxySettings) 125 setIfNotBlank("http-proxy", proxySettings.Http) 126 setIfNotBlank("https-proxy", proxySettings.Https) 127 setIfNotBlank("ftp-proxy", proxySettings.Ftp) 128 setIfNotBlank("no-proxy", proxySettings.NoProxy) 129 } 130 if cfg.AptHttpProxy() == "" && 131 cfg.AptHttpsProxy() == "" && 132 cfg.AptFtpProxy() == "" { 133 proxySettings, err := detectAptProxies() 134 if err != nil { 135 return nil, err 136 } 137 setIfNotBlank("apt-http-proxy", proxySettings.Http) 138 setIfNotBlank("apt-https-proxy", proxySettings.Https) 139 setIfNotBlank("apt-ftp-proxy", proxySettings.Ftp) 140 } 141 if len(attrs) > 0 { 142 cfg, err = cfg.Apply(attrs) 143 if err != nil { 144 return nil, err 145 } 146 } 147 148 return p.Open(cfg) 149 } 150 151 // checkLocalPort checks that the passed port is not used so far. 152 var checkLocalPort = func(port int, description string) error { 153 logger.Infof("checking %s", description) 154 // Try to connect the port on localhost. 155 address := fmt.Sprintf("localhost:%d", port) 156 // TODO(mue) Add a timeout? 157 conn, err := net.Dial("tcp", address) 158 if err != nil { 159 if nerr, ok := err.(*net.OpError); ok { 160 if nerr.Err == syscall.ECONNREFUSED { 161 // No connection, so everything is fine. 162 return nil 163 } 164 } 165 return err 166 } 167 // Connected, so port is in use. 168 err = conn.Close() 169 if err != nil { 170 return err 171 } 172 return fmt.Errorf("cannot use %d as %s, already in use", port, description) 173 } 174 175 // Validate implements environs.EnvironProvider.Validate. 176 func (provider environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { 177 // Check for valid changes for the base config values. 178 if err := config.Validate(cfg, old); err != nil { 179 return nil, err 180 } 181 validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) 182 if err != nil { 183 return nil, fmt.Errorf("failed to validate unknown attrs: %v", err) 184 } 185 localConfig := newEnvironConfig(cfg, validated) 186 // Before potentially creating directories, make sure that the 187 // root directory has not changed. 188 containerType := localConfig.container() 189 if old != nil { 190 oldLocalConfig, err := provider.newConfig(old) 191 if err != nil { 192 return nil, fmt.Errorf("old config is not a valid local config: %v", old) 193 } 194 if containerType != oldLocalConfig.container() { 195 return nil, fmt.Errorf("cannot change container from %q to %q", 196 oldLocalConfig.container(), containerType) 197 } 198 if localConfig.rootDir() != oldLocalConfig.rootDir() { 199 return nil, fmt.Errorf("cannot change root-dir from %q to %q", 200 oldLocalConfig.rootDir(), 201 localConfig.rootDir()) 202 } 203 if localConfig.networkBridge() != oldLocalConfig.networkBridge() { 204 return nil, fmt.Errorf("cannot change network-bridge from %q to %q", 205 oldLocalConfig.rootDir(), 206 localConfig.rootDir()) 207 } 208 if localConfig.storagePort() != oldLocalConfig.storagePort() { 209 return nil, fmt.Errorf("cannot change storage-port from %v to %v", 210 oldLocalConfig.storagePort(), 211 localConfig.storagePort()) 212 } 213 } 214 // Currently only supported containers are "lxc" and "kvm". 215 if containerType != instance.LXC && containerType != instance.KVM { 216 return nil, fmt.Errorf("unsupported container type: %q", containerType) 217 } 218 dir, err := utils.NormalizePath(localConfig.rootDir()) 219 if err != nil { 220 return nil, err 221 } 222 if dir == "." { 223 dir = osenv.JujuHomePath(cfg.Name()) 224 } 225 // Always assign the normalized path. 226 localConfig.attrs["root-dir"] = dir 227 228 // Apply the coerced unknown values back into the config. 229 return cfg.Apply(localConfig.attrs) 230 } 231 232 // BoilerplateConfig implements environs.EnvironProvider.BoilerplateConfig. 233 func (environProvider) BoilerplateConfig() string { 234 return ` 235 # https://juju.ubuntu.com/docs/config-local.html 236 local: 237 type: local 238 239 # root-dir holds the directory that is used for the storage files and 240 # database. The default location is $JUJU_HOME/<env-name>. 241 # $JUJU_HOME defaults to ~/.juju. Override if needed. 242 # 243 # root-dir: ~/.juju/local 244 245 # storage-port holds the port where the local provider starts the 246 # HTTP file server. Override the value if you have multiple local 247 # providers, or if the default port is used by another program. 248 # 249 # storage-port: 8040 250 251 # network-bridge holds the name of the LXC network bridge to use. 252 # Override if the default LXC network bridge is different. 253 # 254 # 255 # network-bridge: lxcbr0 256 257 # The default series to deploy the state-server and charms on. 258 # Make sure to uncomment the following option and set the value to 259 # precise or trusty as desired. 260 # 261 # default-series: precise 262 263 `[1:] 264 } 265 266 // SecretAttrs implements environs.EnvironProvider.SecretAttrs. 267 func (environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) { 268 // don't have any secret attrs 269 return nil, nil 270 } 271 272 func (p environProvider) newConfig(cfg *config.Config) (*environConfig, error) { 273 valid, err := p.Validate(cfg, nil) 274 if err != nil { 275 return nil, err 276 } 277 return newEnvironConfig(valid, valid.UnknownAttrs()), nil 278 }