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  }