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