github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/agent/agent.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agent
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strconv"
    16  
    17  	"github.com/juju/errors"
    18  	"github.com/juju/loggo"
    19  	"github.com/juju/names"
    20  	"github.com/juju/utils"
    21  	"github.com/juju/utils/series"
    22  	"github.com/juju/utils/shell"
    23  	"github.com/juju/version"
    24  
    25  	"github.com/juju/juju/api"
    26  	"github.com/juju/juju/apiserver/params"
    27  	"github.com/juju/juju/juju/paths"
    28  	"github.com/juju/juju/mongo"
    29  	"github.com/juju/juju/network"
    30  	"github.com/juju/juju/state/multiwatcher"
    31  )
    32  
    33  var logger = loggo.GetLogger("juju.agent")
    34  
    35  const (
    36  	// BootstrapNonce is used as a nonce for the initial controller machine.
    37  	BootstrapNonce = "user-admin:bootstrap"
    38  
    39  	// BootstrapMachineId is the ID of the initial controller machine.
    40  	BootstrapMachineId = "0"
    41  )
    42  
    43  // These are base values used for the corresponding defaults.
    44  var (
    45  	logDir          = paths.MustSucceed(paths.LogDir(series.HostSeries()))
    46  	dataDir         = paths.MustSucceed(paths.DataDir(series.HostSeries()))
    47  	confDir         = paths.MustSucceed(paths.ConfDir(series.HostSeries()))
    48  	metricsSpoolDir = paths.MustSucceed(paths.MetricsSpoolDir(series.HostSeries()))
    49  )
    50  
    51  // Agent exposes the agent's configuration to other components. This
    52  // interface should probably be segregated (agent.ConfigGetter and
    53  // agent.ConfigChanger?) but YAGNI *currently* advises against same.
    54  type Agent interface {
    55  
    56  	// CurrentConfig returns a copy of the agent's configuration. No
    57  	// guarantees regarding ongoing correctness are made.
    58  	CurrentConfig() Config
    59  
    60  	// ChangeConfig allows clients to change the agent's configuration
    61  	// by supplying a callback that applies the changes.
    62  	ChangeConfig(ConfigMutator) error
    63  }
    64  
    65  // APIHostPortsSetter trivially wraps an Agent to implement
    66  // worker/apiaddressupdater/APIAddressSetter.
    67  type APIHostPortsSetter struct {
    68  	Agent
    69  }
    70  
    71  // SetAPIHostPorts is the APIAddressSetter interface.
    72  func (s APIHostPortsSetter) SetAPIHostPorts(servers [][]network.HostPort) error {
    73  	return s.ChangeConfig(func(c ConfigSetter) error {
    74  		c.SetAPIHostPorts(servers)
    75  		return nil
    76  	})
    77  }
    78  
    79  // StateServingInfoSetter trivially wraps an Agent to implement
    80  // worker/certupdater/SetStateServingInfo.
    81  type StateServingInfoSetter struct {
    82  	Agent
    83  }
    84  
    85  // SetStateServingInfo is the SetStateServingInfo interface.
    86  func (s StateServingInfoSetter) SetStateServingInfo(info params.StateServingInfo) error {
    87  	return s.ChangeConfig(func(c ConfigSetter) error {
    88  		c.SetStateServingInfo(info)
    89  		return nil
    90  	})
    91  }
    92  
    93  // Paths holds the directory paths used by the agent.
    94  type Paths struct {
    95  	// DataDir is the data directory where each agent has a subdirectory
    96  	// containing the configuration files.
    97  	DataDir string
    98  	// LogDir is the log directory where all logs from all agents on
    99  	// the machine are written.
   100  	LogDir string
   101  	// MetricsSpoolDir is the spool directory where workloads store
   102  	// collected metrics.
   103  	MetricsSpoolDir string
   104  	// ConfDir is the directory where all  config file for
   105  	// Juju agents are stored.
   106  	ConfDir string
   107  }
   108  
   109  // Migrate assigns the directory locations specified from the new path configuration.
   110  func (p *Paths) Migrate(newPaths Paths) {
   111  	if newPaths.DataDir != "" {
   112  		p.DataDir = newPaths.DataDir
   113  	}
   114  	if newPaths.LogDir != "" {
   115  		p.LogDir = newPaths.LogDir
   116  	}
   117  	if newPaths.MetricsSpoolDir != "" {
   118  		p.MetricsSpoolDir = newPaths.MetricsSpoolDir
   119  	}
   120  	if newPaths.ConfDir != "" {
   121  		p.ConfDir = newPaths.ConfDir
   122  	}
   123  }
   124  
   125  // NewPathsWithDefaults returns a Paths struct initialized with default locations if not otherwise specified.
   126  func NewPathsWithDefaults(p Paths) Paths {
   127  	paths := DefaultPaths
   128  	if p.DataDir != "" {
   129  		paths.DataDir = p.DataDir
   130  	}
   131  	if p.LogDir != "" {
   132  		paths.LogDir = p.LogDir
   133  	}
   134  	if p.MetricsSpoolDir != "" {
   135  		paths.MetricsSpoolDir = p.MetricsSpoolDir
   136  	}
   137  	if p.ConfDir != "" {
   138  		paths.ConfDir = p.ConfDir
   139  	}
   140  	return paths
   141  }
   142  
   143  var (
   144  	// DefaultPaths defines the default paths for an agent.
   145  	DefaultPaths = Paths{
   146  		DataDir:         dataDir,
   147  		LogDir:          path.Join(logDir, "juju"),
   148  		MetricsSpoolDir: metricsSpoolDir,
   149  		ConfDir:         confDir,
   150  	}
   151  )
   152  
   153  // SystemIdentity is the name of the file where the environment SSH key is kept.
   154  const SystemIdentity = "system-identity"
   155  
   156  const (
   157  	LxcBridge              = "LXC_BRIDGE"
   158  	LxdBridge              = "LXD_BRIDGE"
   159  	ProviderType           = "PROVIDER_TYPE"
   160  	ContainerType          = "CONTAINER_TYPE"
   161  	Namespace              = "NAMESPACE"
   162  	AgentServiceName       = "AGENT_SERVICE_NAME"
   163  	MongoOplogSize         = "MONGO_OPLOG_SIZE"
   164  	NumaCtlPreference      = "NUMA_CTL_PREFERENCE"
   165  	AllowsSecureConnection = "SECURE_CONTROLLER_CONNECTION"
   166  )
   167  
   168  // The Config interface is the sole way that the agent gets access to the
   169  // configuration information for the machine and unit agents.  There should
   170  // only be one instance of a config object for any given agent, and this
   171  // interface is passed between multiple go routines.  The mutable methods are
   172  // protected by a mutex, and it is expected that the caller doesn't modify any
   173  // slice that may be returned.
   174  //
   175  // NOTE: should new mutating methods be added to this interface, consideration
   176  // is needed around the synchronisation as a single instance is used in
   177  // multiple go routines.
   178  type Config interface {
   179  	// DataDir returns the data directory. Each agent has a subdirectory
   180  	// containing the configuration files.
   181  	DataDir() string
   182  
   183  	// LogDir returns the log directory. All logs from all agents on
   184  	// the machine are written to this directory.
   185  	LogDir() string
   186  
   187  	// SystemIdentityPath returns the path of the file where the environment
   188  	// SSH key is kept.
   189  	SystemIdentityPath() string
   190  
   191  	// Jobs returns a list of MachineJobs that need to run.
   192  	Jobs() []multiwatcher.MachineJob
   193  
   194  	// Tag returns the tag of the entity on whose behalf the state connection
   195  	// will be made.
   196  	Tag() names.Tag
   197  
   198  	// Dir returns the agent's directory.
   199  	Dir() string
   200  
   201  	// Nonce returns the nonce saved when the machine was provisioned
   202  	// TODO: make this one of the key/value pairs.
   203  	Nonce() string
   204  
   205  	// CACert returns the CA certificate that is used to validate the state or
   206  	// API server's certificate.
   207  	CACert() string
   208  
   209  	// APIAddresses returns the addresses needed to connect to the api server
   210  	APIAddresses() ([]string, error)
   211  
   212  	// WriteCommands returns shell commands to write the agent configuration.
   213  	// It returns an error if the configuration does not have all the right
   214  	// elements.
   215  	WriteCommands(renderer shell.Renderer) ([]string, error)
   216  
   217  	// StateServingInfo returns the details needed to run
   218  	// a controller and reports whether those details
   219  	// are available
   220  	StateServingInfo() (params.StateServingInfo, bool)
   221  
   222  	// APIInfo returns details for connecting to the API server and
   223  	// reports whether the details are available.
   224  	APIInfo() (*api.Info, bool)
   225  
   226  	// MongoInfo returns details for connecting to the controller's mongo
   227  	// database and reports whether those details are available
   228  	MongoInfo() (*mongo.MongoInfo, bool)
   229  
   230  	// OldPassword returns the fallback password when connecting to the
   231  	// API server.
   232  	OldPassword() string
   233  
   234  	// UpgradedToVersion returns the version for which all upgrade steps have been
   235  	// successfully run, which is also the same as the initially deployed version.
   236  	UpgradedToVersion() version.Number
   237  
   238  	// Value returns the value associated with the key, or an empty string if
   239  	// the key is not found.
   240  	Value(key string) string
   241  
   242  	// PreferIPv6 returns whether to prefer using IPv6 addresses (if
   243  	// available) when connecting to the state or API server.
   244  	PreferIPv6() bool
   245  
   246  	// Model returns the tag for the model that the agent belongs to.
   247  	Model() names.ModelTag
   248  
   249  	// MetricsSpoolDir returns the spool directory where workloads store
   250  	// collected metrics.
   251  	MetricsSpoolDir() string
   252  
   253  	// MongoVersion returns the version of mongo that the state server
   254  	// is using.
   255  	MongoVersion() mongo.Version
   256  }
   257  
   258  type configSetterOnly interface {
   259  	// Clone returns a copy of the configuration that
   260  	// is unaffected by subsequent calls to the Set*
   261  	// methods
   262  	Clone() Config
   263  
   264  	// SetOldPassword sets the password that is currently
   265  	// valid but needs to be changed. This is used as
   266  	// a fallback.
   267  	SetOldPassword(oldPassword string)
   268  
   269  	// SetPassword sets the password to be used when
   270  	// connecting to the state.
   271  	SetPassword(newPassword string)
   272  
   273  	// SetValue updates the value for the specified key.
   274  	SetValue(key, value string)
   275  
   276  	// SetUpgradedToVersion sets the version that
   277  	// the agent has successfully upgraded to.
   278  	SetUpgradedToVersion(newVersion version.Number)
   279  
   280  	// SetAPIHostPorts sets the API host/port addresses to connect to.
   281  	SetAPIHostPorts(servers [][]network.HostPort)
   282  
   283  	// SetCACert sets the CA cert used for validating API connections.
   284  	SetCACert(string)
   285  
   286  	// Migrate takes an existing agent config and applies the given
   287  	// parameters to change it.
   288  	//
   289  	// Only non-empty fields in newParams are used
   290  	// to change existing config settings. All changes are written
   291  	// atomically. UpgradedToVersion cannot be changed here, because
   292  	// Migrate is most likely called during an upgrade, so it will be
   293  	// changed at the end of the upgrade anyway, if successful.
   294  	//
   295  	// Migrate does not actually write the new configuration.
   296  	//
   297  	// Note that if the configuration file moves location,
   298  	// (if DataDir is set), the the caller is responsible for removing
   299  	// the old configuration.
   300  	Migrate(MigrateParams) error
   301  
   302  	// SetStateServingInfo sets the information needed
   303  	// to run a controller
   304  	SetStateServingInfo(info params.StateServingInfo)
   305  
   306  	// SetMongoVersion sets the passed version as currently in use.
   307  	SetMongoVersion(mongo.Version)
   308  }
   309  
   310  // LogFileName returns the filename for the Agent's log file.
   311  func LogFilename(c Config) string {
   312  	return filepath.Join(c.LogDir(), c.Tag().String()+".log")
   313  }
   314  
   315  type ConfigMutator func(ConfigSetter) error
   316  
   317  type ConfigWriter interface {
   318  	// Write writes the agent configuration.
   319  	Write() error
   320  }
   321  
   322  type ConfigSetter interface {
   323  	Config
   324  	configSetterOnly
   325  }
   326  
   327  type ConfigSetterWriter interface {
   328  	Config
   329  	configSetterOnly
   330  	ConfigWriter
   331  }
   332  
   333  // MigrateParams holds agent config values to change in a
   334  // Migrate call. Empty fields will be ignored. DeleteValues
   335  // specifies a list of keys to delete.
   336  type MigrateParams struct {
   337  	Paths        Paths
   338  	Jobs         []multiwatcher.MachineJob
   339  	DeleteValues []string
   340  	Values       map[string]string
   341  	Model        names.ModelTag
   342  }
   343  
   344  // Ensure that the configInternal struct implements the Config interface.
   345  var _ Config = (*configInternal)(nil)
   346  
   347  type connectionDetails struct {
   348  	addresses []string
   349  	password  string
   350  }
   351  
   352  func (d *connectionDetails) clone() *connectionDetails {
   353  	if d == nil {
   354  		return nil
   355  	}
   356  	newd := *d
   357  	newd.addresses = append([]string{}, d.addresses...)
   358  	return &newd
   359  }
   360  
   361  type configInternal struct {
   362  	configFilePath    string
   363  	paths             Paths
   364  	tag               names.Tag
   365  	nonce             string
   366  	model             names.ModelTag
   367  	jobs              []multiwatcher.MachineJob
   368  	upgradedToVersion version.Number
   369  	caCert            string
   370  	stateDetails      *connectionDetails
   371  	apiDetails        *connectionDetails
   372  	oldPassword       string
   373  	servingInfo       *params.StateServingInfo
   374  	values            map[string]string
   375  	preferIPv6        bool
   376  	mongoVersion      string
   377  }
   378  
   379  // AgentConfigParams holds the parameters required to create
   380  // a new AgentConfig.
   381  type AgentConfigParams struct {
   382  	Paths             Paths
   383  	Jobs              []multiwatcher.MachineJob
   384  	UpgradedToVersion version.Number
   385  	Tag               names.Tag
   386  	Password          string
   387  	Nonce             string
   388  	Model             names.ModelTag
   389  	StateAddresses    []string
   390  	APIAddresses      []string
   391  	CACert            string
   392  	Values            map[string]string
   393  	PreferIPv6        bool
   394  	MongoVersion      mongo.Version
   395  }
   396  
   397  // NewAgentConfig returns a new config object suitable for use for a
   398  // machine or unit agent.
   399  func NewAgentConfig(configParams AgentConfigParams) (ConfigSetterWriter, error) {
   400  	if configParams.Paths.DataDir == "" {
   401  		return nil, errors.Trace(requiredError("data directory"))
   402  	}
   403  	if configParams.Tag == nil {
   404  		return nil, errors.Trace(requiredError("entity tag"))
   405  	}
   406  	switch configParams.Tag.(type) {
   407  	case names.MachineTag, names.UnitTag:
   408  		// these are the only two type of tags that can represent an agent
   409  	default:
   410  		return nil, errors.Errorf("entity tag must be MachineTag or UnitTag, got %T", configParams.Tag)
   411  	}
   412  	if configParams.UpgradedToVersion == version.Zero {
   413  		return nil, errors.Trace(requiredError("upgradedToVersion"))
   414  	}
   415  	if configParams.Password == "" {
   416  		return nil, errors.Trace(requiredError("password"))
   417  	}
   418  	if uuid := configParams.Model.Id(); uuid == "" {
   419  		return nil, errors.Trace(requiredError("model"))
   420  	} else if !names.IsValidModel(uuid) {
   421  		return nil, errors.Errorf("%q is not a valid model uuid", uuid)
   422  	}
   423  	if len(configParams.CACert) == 0 {
   424  		return nil, errors.Trace(requiredError("CA certificate"))
   425  	}
   426  	// Note that the password parts of the state and api information are
   427  	// blank.  This is by design.
   428  	config := &configInternal{
   429  		paths:             NewPathsWithDefaults(configParams.Paths),
   430  		jobs:              configParams.Jobs,
   431  		upgradedToVersion: configParams.UpgradedToVersion,
   432  		tag:               configParams.Tag,
   433  		nonce:             configParams.Nonce,
   434  		model:             configParams.Model,
   435  		caCert:            configParams.CACert,
   436  		oldPassword:       configParams.Password,
   437  		values:            configParams.Values,
   438  		preferIPv6:        configParams.PreferIPv6,
   439  		mongoVersion:      configParams.MongoVersion.String(),
   440  	}
   441  
   442  	if len(configParams.StateAddresses) > 0 {
   443  		config.stateDetails = &connectionDetails{
   444  			addresses: configParams.StateAddresses,
   445  		}
   446  	}
   447  	if len(configParams.APIAddresses) > 0 {
   448  		config.apiDetails = &connectionDetails{
   449  			addresses: configParams.APIAddresses,
   450  		}
   451  	}
   452  	if err := config.check(); err != nil {
   453  		return nil, err
   454  	}
   455  	if config.values == nil {
   456  		config.values = make(map[string]string)
   457  	}
   458  	config.configFilePath = ConfigPath(config.paths.DataDir, config.tag)
   459  	return config, nil
   460  }
   461  
   462  // NewStateMachineConfig returns a configuration suitable for
   463  // a machine running the controller.
   464  func NewStateMachineConfig(configParams AgentConfigParams, serverInfo params.StateServingInfo) (ConfigSetterWriter, error) {
   465  	if serverInfo.Cert == "" {
   466  		return nil, errors.Trace(requiredError("controller cert"))
   467  	}
   468  	if serverInfo.PrivateKey == "" {
   469  		return nil, errors.Trace(requiredError("controller key"))
   470  	}
   471  	if serverInfo.CAPrivateKey == "" {
   472  		return nil, errors.Trace(requiredError("ca cert key"))
   473  	}
   474  	if serverInfo.StatePort == 0 {
   475  		return nil, errors.Trace(requiredError("state port"))
   476  	}
   477  	if serverInfo.APIPort == 0 {
   478  		return nil, errors.Trace(requiredError("api port"))
   479  	}
   480  	config, err := NewAgentConfig(configParams)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  	config.SetStateServingInfo(serverInfo)
   485  	return config, nil
   486  }
   487  
   488  // BaseDir returns the directory containing the data directories for
   489  // all the agents.
   490  func BaseDir(dataDir string) string {
   491  	// Note: must use path, not filepath, as this function is
   492  	// (indirectly) used by the client on Windows.
   493  	return path.Join(dataDir, "agents")
   494  }
   495  
   496  // Dir returns the agent-specific data directory.
   497  func Dir(dataDir string, tag names.Tag) string {
   498  	// Note: must use path, not filepath, as this
   499  	// function is used by the client on Windows.
   500  	return path.Join(BaseDir(dataDir), tag.String())
   501  }
   502  
   503  // ConfigPath returns the full path to the agent config file.
   504  // NOTE: Delete this once all agents accept --config instead
   505  // of --data-dir - it won't be needed anymore.
   506  func ConfigPath(dataDir string, tag names.Tag) string {
   507  	return filepath.Join(Dir(dataDir, tag), agentConfigFilename)
   508  }
   509  
   510  // ReadConfig reads configuration data from the given location.
   511  func ReadConfig(configFilePath string) (ConfigSetterWriter, error) {
   512  	var (
   513  		format formatter
   514  		config *configInternal
   515  	)
   516  	configData, err := ioutil.ReadFile(configFilePath)
   517  	if err != nil {
   518  		return nil, fmt.Errorf("cannot read agent config %q: %v", configFilePath, err)
   519  	}
   520  	format, config, err = parseConfigData(configData)
   521  	if err != nil {
   522  		return nil, err
   523  	}
   524  	logger.Debugf("read agent config, format %q", format.version())
   525  	config.configFilePath = configFilePath
   526  	return config, nil
   527  }
   528  
   529  func (c0 *configInternal) Clone() Config {
   530  	c1 := *c0
   531  	// Deep copy only fields which may be affected
   532  	// by ConfigSetter methods.
   533  	c1.stateDetails = c0.stateDetails.clone()
   534  	c1.apiDetails = c0.apiDetails.clone()
   535  	c1.jobs = append([]multiwatcher.MachineJob{}, c0.jobs...)
   536  	c1.values = make(map[string]string, len(c0.values))
   537  	for key, val := range c0.values {
   538  		c1.values[key] = val
   539  	}
   540  	return &c1
   541  }
   542  
   543  func (config *configInternal) Migrate(newParams MigrateParams) error {
   544  	config.paths.Migrate(newParams.Paths)
   545  	config.configFilePath = ConfigPath(config.paths.DataDir, config.tag)
   546  	if len(newParams.Jobs) > 0 {
   547  		config.jobs = make([]multiwatcher.MachineJob, len(newParams.Jobs))
   548  		copy(config.jobs, newParams.Jobs)
   549  	}
   550  	for _, key := range newParams.DeleteValues {
   551  		delete(config.values, key)
   552  	}
   553  	for key, value := range newParams.Values {
   554  		if config.values == nil {
   555  			config.values = make(map[string]string)
   556  		}
   557  		config.values[key] = value
   558  	}
   559  	if newParams.Model.Id() != "" {
   560  		config.model = newParams.Model
   561  	}
   562  	if err := config.check(); err != nil {
   563  		return fmt.Errorf("migrated agent config is invalid: %v", err)
   564  	}
   565  	return nil
   566  }
   567  
   568  func (c *configInternal) SetUpgradedToVersion(newVersion version.Number) {
   569  	c.upgradedToVersion = newVersion
   570  }
   571  
   572  func (c *configInternal) SetAPIHostPorts(servers [][]network.HostPort) {
   573  	if c.apiDetails == nil {
   574  		return
   575  	}
   576  	var addrs []string
   577  	for _, serverHostPorts := range servers {
   578  		hps := network.PrioritizeInternalHostPorts(serverHostPorts, false)
   579  		addrs = append(addrs, hps...)
   580  	}
   581  	c.apiDetails.addresses = addrs
   582  	logger.Infof("API server address details %q written to agent config as %q", servers, addrs)
   583  }
   584  
   585  func (c *configInternal) SetCACert(cert string) {
   586  	c.caCert = cert
   587  }
   588  
   589  func (c *configInternal) SetValue(key, value string) {
   590  	if value == "" {
   591  		delete(c.values, key)
   592  	} else {
   593  		c.values[key] = value
   594  	}
   595  }
   596  
   597  func (c *configInternal) SetOldPassword(oldPassword string) {
   598  	c.oldPassword = oldPassword
   599  }
   600  
   601  func (c *configInternal) SetPassword(newPassword string) {
   602  	if c.stateDetails != nil {
   603  		c.stateDetails.password = newPassword
   604  	}
   605  	if c.apiDetails != nil {
   606  		c.apiDetails.password = newPassword
   607  	}
   608  }
   609  
   610  func (c *configInternal) Write() error {
   611  	data, err := c.fileContents()
   612  	if err != nil {
   613  		return err
   614  	}
   615  	// Make sure the config dir gets created.
   616  	configDir := filepath.Dir(c.configFilePath)
   617  	if err := os.MkdirAll(configDir, 0755); err != nil {
   618  		return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err)
   619  	}
   620  	return utils.AtomicWriteFile(c.configFilePath, data, 0600)
   621  }
   622  
   623  func requiredError(what string) error {
   624  	return fmt.Errorf("%s not found in configuration", what)
   625  }
   626  
   627  func (c *configInternal) File(name string) string {
   628  	return path.Join(c.Dir(), name)
   629  }
   630  
   631  func (c *configInternal) DataDir() string {
   632  	return c.paths.DataDir
   633  }
   634  
   635  func (c *configInternal) MetricsSpoolDir() string {
   636  	return c.paths.MetricsSpoolDir
   637  }
   638  
   639  func (c *configInternal) LogDir() string {
   640  	return c.paths.LogDir
   641  }
   642  
   643  func (c *configInternal) SystemIdentityPath() string {
   644  	return filepath.Join(c.paths.DataDir, SystemIdentity)
   645  }
   646  
   647  func (c *configInternal) Jobs() []multiwatcher.MachineJob {
   648  	return c.jobs
   649  }
   650  
   651  func (c *configInternal) Nonce() string {
   652  	return c.nonce
   653  }
   654  
   655  func (c *configInternal) UpgradedToVersion() version.Number {
   656  	return c.upgradedToVersion
   657  }
   658  
   659  func (c *configInternal) CACert() string {
   660  	return c.caCert
   661  }
   662  
   663  func (c *configInternal) Value(key string) string {
   664  	return c.values[key]
   665  }
   666  
   667  func (c *configInternal) PreferIPv6() bool {
   668  	return c.preferIPv6
   669  }
   670  
   671  func (c *configInternal) StateServingInfo() (params.StateServingInfo, bool) {
   672  	if c.servingInfo == nil {
   673  		return params.StateServingInfo{}, false
   674  	}
   675  	return *c.servingInfo, true
   676  }
   677  
   678  func (c *configInternal) SetStateServingInfo(info params.StateServingInfo) {
   679  	c.servingInfo = &info
   680  }
   681  
   682  func (c *configInternal) APIAddresses() ([]string, error) {
   683  	if c.apiDetails == nil {
   684  		return []string{}, errors.New("No apidetails in config")
   685  	}
   686  	return append([]string{}, c.apiDetails.addresses...), nil
   687  }
   688  
   689  func (c *configInternal) OldPassword() string {
   690  	return c.oldPassword
   691  }
   692  
   693  func (c *configInternal) Tag() names.Tag {
   694  	return c.tag
   695  }
   696  
   697  func (c *configInternal) Model() names.ModelTag {
   698  	return c.model
   699  }
   700  
   701  func (c *configInternal) Dir() string {
   702  	return Dir(c.paths.DataDir, c.tag)
   703  }
   704  
   705  func (c *configInternal) check() error {
   706  	if c.stateDetails == nil && c.apiDetails == nil {
   707  		return errors.Trace(requiredError("state or API addresses"))
   708  	}
   709  	if c.stateDetails != nil {
   710  		if err := checkAddrs(c.stateDetails.addresses, "controller address"); err != nil {
   711  			return err
   712  		}
   713  	}
   714  	if c.apiDetails != nil {
   715  		if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil {
   716  			return err
   717  		}
   718  	}
   719  	return nil
   720  }
   721  
   722  // MongoVersion implements Config.
   723  func (c *configInternal) MongoVersion() mongo.Version {
   724  	v, err := mongo.NewVersion(c.mongoVersion)
   725  	if err != nil {
   726  		return mongo.Mongo24
   727  	}
   728  	return v
   729  }
   730  
   731  // SetMongoVersion implements configSetterOnly.
   732  func (c *configInternal) SetMongoVersion(v mongo.Version) {
   733  	c.mongoVersion = v.String()
   734  }
   735  
   736  var validAddr = regexp.MustCompile("^.+:[0-9]+$")
   737  
   738  func checkAddrs(addrs []string, what string) error {
   739  	if len(addrs) == 0 {
   740  		return errors.Trace(requiredError(what))
   741  	}
   742  	for _, a := range addrs {
   743  		if !validAddr.MatchString(a) {
   744  			return errors.Errorf("invalid %s %q", what, a)
   745  		}
   746  	}
   747  	return nil
   748  }
   749  
   750  func (c *configInternal) fileContents() ([]byte, error) {
   751  	data, err := currentFormat.marshal(c)
   752  	if err != nil {
   753  		return nil, err
   754  	}
   755  	var buf bytes.Buffer
   756  	fmt.Fprintf(&buf, "%s%s\n", formatPrefix, currentFormat.version())
   757  	buf.Write(data)
   758  	return buf.Bytes(), nil
   759  }
   760  
   761  // WriteCommands is defined on Config interface.
   762  func (c *configInternal) WriteCommands(renderer shell.Renderer) ([]string, error) {
   763  	data, err := c.fileContents()
   764  	if err != nil {
   765  		return nil, errors.Trace(err)
   766  	}
   767  	commands := renderer.MkdirAll(c.Dir())
   768  	filename := c.File(agentConfigFilename)
   769  	commands = append(commands, renderer.WriteFile(filename, data)...)
   770  	commands = append(commands, renderer.Chmod(filename, 0600)...)
   771  	return commands, nil
   772  }
   773  
   774  // APIInfo is defined on Config interface.
   775  func (c *configInternal) APIInfo() (*api.Info, bool) {
   776  	if c.apiDetails == nil || c.apiDetails.addresses == nil {
   777  		return nil, false
   778  	}
   779  	servingInfo, isController := c.StateServingInfo()
   780  	addrs := c.apiDetails.addresses
   781  	if isController {
   782  		port := servingInfo.APIPort
   783  		localAPIAddr := net.JoinHostPort("localhost", strconv.Itoa(port))
   784  		if c.preferIPv6 {
   785  			localAPIAddr = net.JoinHostPort("::1", strconv.Itoa(port))
   786  		}
   787  		addrInAddrs := false
   788  		for _, addr := range addrs {
   789  			if addr == localAPIAddr {
   790  				addrInAddrs = true
   791  				break
   792  			}
   793  		}
   794  		if !addrInAddrs {
   795  			addrs = append(addrs, localAPIAddr)
   796  		}
   797  	}
   798  	return &api.Info{
   799  		Addrs:    addrs,
   800  		Password: c.apiDetails.password,
   801  		CACert:   c.caCert,
   802  		Tag:      c.tag,
   803  		Nonce:    c.nonce,
   804  		ModelTag: c.model,
   805  	}, true
   806  }
   807  
   808  // MongoInfo is defined on Config interface.
   809  func (c *configInternal) MongoInfo() (info *mongo.MongoInfo, ok bool) {
   810  	ssi, ok := c.StateServingInfo()
   811  	if !ok {
   812  		return nil, false
   813  	}
   814  	addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(ssi.StatePort))
   815  	if c.preferIPv6 {
   816  		addr = net.JoinHostPort("::1", strconv.Itoa(ssi.StatePort))
   817  	}
   818  	return &mongo.MongoInfo{
   819  		Info: mongo.Info{
   820  			Addrs:  []string{addr},
   821  			CACert: c.caCert,
   822  		},
   823  		Password: c.stateDetails.password,
   824  		Tag:      c.tag,
   825  	}, true
   826  }