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