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