github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/cloudconfig/instancecfg/instancecfg.go (about)

     1  // Copyright 2012, 2013, 2015 Canonical Ltd.
     2  // Copyright 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package instancecfg
     6  
     7  import (
     8  	"fmt"
     9  	"net"
    10  	"path"
    11  	"strconv"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/names"
    16  	"github.com/juju/utils"
    17  	"github.com/juju/utils/proxy"
    18  	"github.com/juju/utils/shell"
    19  
    20  	"github.com/juju/juju/agent"
    21  	agenttools "github.com/juju/juju/agent/tools"
    22  	"github.com/juju/juju/api"
    23  	"github.com/juju/juju/apiserver/params"
    24  	"github.com/juju/juju/constraints"
    25  	"github.com/juju/juju/environs/config"
    26  	"github.com/juju/juju/environs/imagemetadata"
    27  	"github.com/juju/juju/environs/tags"
    28  	"github.com/juju/juju/instance"
    29  	"github.com/juju/juju/juju/paths"
    30  	"github.com/juju/juju/mongo"
    31  	"github.com/juju/juju/service"
    32  	"github.com/juju/juju/service/common"
    33  	"github.com/juju/juju/state/multiwatcher"
    34  	coretools "github.com/juju/juju/tools"
    35  	"github.com/juju/juju/version"
    36  )
    37  
    38  var logger = loggo.GetLogger("juju.cloudconfig.instancecfg")
    39  
    40  // InstanceConfig represents initialization information for a new juju instance.
    41  type InstanceConfig struct {
    42  	// Tags is a set of tags to set on the instance, if supported. This
    43  	// should be populated using the InstanceTags method in this package.
    44  	Tags map[string]string
    45  
    46  	// Bootstrap specifies whether the new instance is the bootstrap
    47  	// instance. When this is true, StateServingInfo should be set
    48  	// and filled out.
    49  	Bootstrap bool
    50  
    51  	// StateServingInfo holds the information for serving the state.
    52  	// This must only be set if the Bootstrap field is true
    53  	// (state servers started subsequently will acquire their serving info
    54  	// from another server)
    55  	StateServingInfo *params.StateServingInfo
    56  
    57  	// MongoInfo holds the means for the new instance to communicate with the
    58  	// juju state database. Unless the new instance is running a state server
    59  	// (StateServer is set), there must be at least one state server address supplied.
    60  	// The entity name must match that of the instance being started,
    61  	// or be empty when starting a state server.
    62  	MongoInfo *mongo.MongoInfo
    63  
    64  	// APIInfo holds the means for the new instance to communicate with the
    65  	// juju state API. Unless the new instance is running a state server (StateServer is
    66  	// set), there must be at least one state server address supplied.
    67  	// The entity name must match that of the instance being started,
    68  	// or be empty when starting a state server.
    69  	APIInfo *api.Info
    70  
    71  	// InstanceId is the instance ID of the instance being initialised.
    72  	// This is required when bootstrapping, and ignored otherwise.
    73  	InstanceId instance.Id
    74  
    75  	// HardwareCharacteristics contains the harrdware characteristics of
    76  	// the instance being initialised. This optional, and is only used by
    77  	// the bootstrap agent during state initialisation.
    78  	HardwareCharacteristics *instance.HardwareCharacteristics
    79  
    80  	// MachineNonce is set at provisioning/bootstrap time and used to
    81  	// ensure the agent is running on the correct instance.
    82  	MachineNonce string
    83  
    84  	// Tools is juju tools to be used on the new instance.
    85  	Tools *coretools.Tools
    86  
    87  	// DataDir holds the directory that juju state will be put in the new
    88  	// instance.
    89  	DataDir string
    90  
    91  	// LogDir holds the directory that juju logs will be written to.
    92  	LogDir string
    93  
    94  	// Jobs holds what machine jobs to run.
    95  	Jobs []multiwatcher.MachineJob
    96  
    97  	// CloudInitOutputLog specifies the path to the output log for cloud-init.
    98  	// The directory containing the log file must already exist.
    99  	CloudInitOutputLog string
   100  
   101  	// MachineId identifies the new machine.
   102  	MachineId string
   103  
   104  	// MachineContainerType specifies the type of container that the instance
   105  	// is.  If the instance is not a container, then the type is "".
   106  	MachineContainerType instance.ContainerType
   107  
   108  	// MachineContainerHostname specifies the hostname to be used with the
   109  	// cloud config for the instance. If this is not set, hostname uses the default.
   110  	MachineContainerHostname string
   111  
   112  	// Networks holds a list of networks the instances should be on.
   113  	//
   114  	// TODO(dimitern): Drop this in a follow-up in favor or spaces
   115  	// constraints.
   116  	Networks []string
   117  
   118  	// AuthorizedKeys specifies the keys that are allowed to
   119  	// connect to the instance (see cloudinit.SSHAddAuthorizedKeys)
   120  	// If no keys are supplied, there can be no ssh access to the node.
   121  	// On a bootstrap instance, that is fatal. On other
   122  	// instances it will mean that the ssh, scp and debug-hooks
   123  	// commands cannot work.
   124  	AuthorizedKeys string
   125  
   126  	// AgentEnvironment defines additional configuration variables to set in
   127  	// the instance agent config.
   128  	AgentEnvironment map[string]string
   129  
   130  	// WARNING: this is only set if the instance being configured is
   131  	// a state server node.
   132  	//
   133  	// Config holds the initial environment configuration.
   134  	Config *config.Config
   135  
   136  	// Constraints holds the initial environment constraints.
   137  	Constraints constraints.Value
   138  
   139  	// DisableSSLHostnameVerification can be set to true to tell cloud-init
   140  	// that it shouldn't verify SSL certificates
   141  	DisableSSLHostnameVerification bool
   142  
   143  	// Series represents the instance series.
   144  	Series string
   145  
   146  	// MachineAgentServiceName is the init service name for the Juju machine agent.
   147  	MachineAgentServiceName string
   148  
   149  	// ProxySettings define normal http, https and ftp proxies.
   150  	ProxySettings proxy.Settings
   151  
   152  	// AptProxySettings define the http, https and ftp proxy settings to use
   153  	// for apt, which may or may not be the same as the normal ProxySettings.
   154  	AptProxySettings proxy.Settings
   155  
   156  	// AptMirror defines an APT mirror location, which, if specified, will
   157  	// override the default APT sources.
   158  	AptMirror string
   159  
   160  	// PreferIPv6 mirrors the value of prefer-ipv6 environment setting
   161  	// and when set IPv6 addresses for connecting to the API/state
   162  	// servers will be preferred over IPv4 ones.
   163  	PreferIPv6 bool
   164  
   165  	// The type of Simple Stream to download and deploy on this instance.
   166  	ImageStream string
   167  
   168  	// CustomImageMetadata is optional custom simplestreams image metadata
   169  	// to store in environment storage at bootstrap time. This is ignored
   170  	// in non-bootstrap instances.
   171  	CustomImageMetadata []*imagemetadata.ImageMetadata
   172  
   173  	// EnableOSRefreshUpdate specifies whether Juju will refresh its
   174  	// respective OS's updates list.
   175  	EnableOSRefreshUpdate bool
   176  
   177  	// EnableOSUpgrade defines Juju's behavior when provisioning
   178  	// instances. If enabled, the OS will perform any upgrades
   179  	// available as part of its provisioning.
   180  	EnableOSUpgrade bool
   181  }
   182  
   183  func (cfg *InstanceConfig) agentInfo() service.AgentInfo {
   184  	return service.NewMachineAgentInfo(
   185  		cfg.MachineId,
   186  		cfg.DataDir,
   187  		cfg.LogDir,
   188  	)
   189  }
   190  
   191  func (cfg *InstanceConfig) ToolsDir(renderer shell.Renderer) string {
   192  	return cfg.agentInfo().ToolsDir(renderer)
   193  }
   194  
   195  func (cfg *InstanceConfig) InitService(renderer shell.Renderer) (service.Service, error) {
   196  	conf := service.AgentConf(cfg.agentInfo(), renderer)
   197  
   198  	name := cfg.MachineAgentServiceName
   199  	svc, err := newService(name, conf, cfg.Series)
   200  	return svc, errors.Trace(err)
   201  }
   202  
   203  var newService = func(name string, conf common.Conf, series string) (service.Service, error) {
   204  	return service.NewService(name, conf, series)
   205  }
   206  
   207  func (cfg *InstanceConfig) AgentConfig(
   208  	tag names.Tag,
   209  	toolsVersion version.Number,
   210  ) (agent.ConfigSetter, error) {
   211  	// TODO for HAState: the stateHostAddrs and apiHostAddrs here assume that
   212  	// if the instance is a stateServer then to use localhost.  This may be
   213  	// sufficient, but needs thought in the new world order.
   214  	var password string
   215  	if cfg.MongoInfo == nil {
   216  		password = cfg.APIInfo.Password
   217  	} else {
   218  		password = cfg.MongoInfo.Password
   219  	}
   220  	configParams := agent.AgentConfigParams{
   221  		DataDir:           cfg.DataDir,
   222  		LogDir:            cfg.LogDir,
   223  		Jobs:              cfg.Jobs,
   224  		Tag:               tag,
   225  		UpgradedToVersion: toolsVersion,
   226  		Password:          password,
   227  		Nonce:             cfg.MachineNonce,
   228  		StateAddresses:    cfg.stateHostAddrs(),
   229  		APIAddresses:      cfg.ApiHostAddrs(),
   230  		CACert:            cfg.MongoInfo.CACert,
   231  		Values:            cfg.AgentEnvironment,
   232  		PreferIPv6:        cfg.PreferIPv6,
   233  		Environment:       cfg.APIInfo.EnvironTag,
   234  	}
   235  	if !cfg.Bootstrap {
   236  		return agent.NewAgentConfig(configParams)
   237  	}
   238  	return agent.NewStateMachineConfig(configParams, *cfg.StateServingInfo)
   239  }
   240  
   241  func (cfg *InstanceConfig) JujuTools() string {
   242  	return agenttools.SharedToolsDir(cfg.DataDir, cfg.Tools.Version)
   243  }
   244  
   245  func (cfg *InstanceConfig) stateHostAddrs() []string {
   246  	var hosts []string
   247  	if cfg.Bootstrap {
   248  		if cfg.PreferIPv6 {
   249  			hosts = append(hosts, net.JoinHostPort("::1", strconv.Itoa(cfg.StateServingInfo.StatePort)))
   250  		} else {
   251  			hosts = append(hosts, net.JoinHostPort("localhost", strconv.Itoa(cfg.StateServingInfo.StatePort)))
   252  		}
   253  	}
   254  	if cfg.MongoInfo != nil {
   255  		hosts = append(hosts, cfg.MongoInfo.Addrs...)
   256  	}
   257  	return hosts
   258  }
   259  
   260  func (cfg *InstanceConfig) ApiHostAddrs() []string {
   261  	var hosts []string
   262  	if cfg.Bootstrap {
   263  		if cfg.PreferIPv6 {
   264  			hosts = append(hosts, net.JoinHostPort("::1", strconv.Itoa(cfg.StateServingInfo.APIPort)))
   265  		} else {
   266  			hosts = append(hosts, net.JoinHostPort("localhost", strconv.Itoa(cfg.StateServingInfo.APIPort)))
   267  		}
   268  	}
   269  	if cfg.APIInfo != nil {
   270  		hosts = append(hosts, cfg.APIInfo.Addrs...)
   271  	}
   272  	return hosts
   273  }
   274  
   275  // HasNetworks returns if there are any networks set.
   276  func (cfg *InstanceConfig) HasNetworks() bool {
   277  	return len(cfg.Networks) > 0 || cfg.Constraints.HaveNetworks()
   278  }
   279  
   280  type requiresError string
   281  
   282  func (e requiresError) Error() string {
   283  	return "invalid machine configuration: missing " + string(e)
   284  }
   285  
   286  func (cfg *InstanceConfig) VerifyConfig() (err error) {
   287  	defer errors.DeferredAnnotatef(&err, "invalid machine configuration")
   288  	if !names.IsValidMachine(cfg.MachineId) {
   289  		return errors.New("invalid machine id")
   290  	}
   291  	if cfg.DataDir == "" {
   292  		return errors.New("missing var directory")
   293  	}
   294  	if cfg.LogDir == "" {
   295  		return errors.New("missing log directory")
   296  	}
   297  	if len(cfg.Jobs) == 0 {
   298  		return errors.New("missing machine jobs")
   299  	}
   300  	if cfg.CloudInitOutputLog == "" {
   301  		return errors.New("missing cloud-init output log path")
   302  	}
   303  	if cfg.Tools == nil {
   304  		return errors.New("missing tools")
   305  	}
   306  	if cfg.Tools.URL == "" {
   307  		return errors.New("missing tools URL")
   308  	}
   309  	if cfg.MongoInfo == nil {
   310  		return errors.New("missing state info")
   311  	}
   312  	if len(cfg.MongoInfo.CACert) == 0 {
   313  		return errors.New("missing CA certificate")
   314  	}
   315  	if cfg.APIInfo == nil {
   316  		return errors.New("missing API info")
   317  	}
   318  	if cfg.APIInfo.EnvironTag.Id() == "" {
   319  		return errors.New("missing environment tag")
   320  	}
   321  	if len(cfg.APIInfo.CACert) == 0 {
   322  		return errors.New("missing API CA certificate")
   323  	}
   324  	if cfg.MachineAgentServiceName == "" {
   325  		return errors.New("missing machine agent service name")
   326  	}
   327  	if cfg.Bootstrap {
   328  		if cfg.Config == nil {
   329  			return errors.New("missing environment configuration")
   330  		}
   331  		if cfg.MongoInfo.Tag != nil {
   332  			return errors.New("entity tag must be nil when starting a state server")
   333  		}
   334  		if cfg.APIInfo.Tag != nil {
   335  			return errors.New("entity tag must be nil when starting a state server")
   336  		}
   337  		if cfg.StateServingInfo == nil {
   338  			return errors.New("missing state serving info")
   339  		}
   340  		if len(cfg.StateServingInfo.Cert) == 0 {
   341  			return errors.New("missing state server certificate")
   342  		}
   343  		if len(cfg.StateServingInfo.PrivateKey) == 0 {
   344  			return errors.New("missing state server private key")
   345  		}
   346  		if len(cfg.StateServingInfo.CAPrivateKey) == 0 {
   347  			return errors.New("missing ca cert private key")
   348  		}
   349  		if cfg.StateServingInfo.StatePort == 0 {
   350  			return errors.New("missing state port")
   351  		}
   352  		if cfg.StateServingInfo.APIPort == 0 {
   353  			return errors.New("missing API port")
   354  		}
   355  		if cfg.InstanceId == "" {
   356  			return errors.New("missing instance-id")
   357  		}
   358  	} else {
   359  		if len(cfg.MongoInfo.Addrs) == 0 {
   360  			return errors.New("missing state hosts")
   361  		}
   362  		if cfg.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) {
   363  			return errors.New("entity tag must match started machine")
   364  		}
   365  		if len(cfg.APIInfo.Addrs) == 0 {
   366  			return errors.New("missing API hosts")
   367  		}
   368  		if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) {
   369  			return errors.New("entity tag must match started machine")
   370  		}
   371  		if cfg.StateServingInfo != nil {
   372  			return errors.New("state serving info unexpectedly present")
   373  		}
   374  	}
   375  	if cfg.MachineNonce == "" {
   376  		return errors.New("missing machine nonce")
   377  	}
   378  	return nil
   379  }
   380  
   381  // logDir returns a filesystem path to the location where applications
   382  // may create a folder containing logs
   383  var logDir = paths.MustSucceed(paths.LogDir(version.Current.Series))
   384  
   385  // DefaultBridgeName is the network bridge device name used for LXC and KVM
   386  // containers
   387  const DefaultBridgeName = "juju-br0"
   388  
   389  // NewInstanceConfig sets up a basic machine configuration, for a
   390  // non-bootstrap node. You'll still need to supply more information,
   391  // but this takes care of the fixed entries and the ones that are
   392  // always needed.
   393  func NewInstanceConfig(
   394  	machineID,
   395  	machineNonce,
   396  	imageStream,
   397  	series string,
   398  	secureServerConnections bool,
   399  	networks []string,
   400  	mongoInfo *mongo.MongoInfo,
   401  	apiInfo *api.Info,
   402  ) (*InstanceConfig, error) {
   403  	dataDir, err := paths.DataDir(series)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  	logDir, err := paths.LogDir(series)
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  	cloudInitOutputLog := path.Join(logDir, "cloud-init-output.log")
   412  	icfg := &InstanceConfig{
   413  		// Fixed entries.
   414  		DataDir:                 dataDir,
   415  		LogDir:                  path.Join(logDir, "juju"),
   416  		Jobs:                    []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   417  		CloudInitOutputLog:      cloudInitOutputLog,
   418  		MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(),
   419  		Series:                  series,
   420  		Tags:                    map[string]string{},
   421  
   422  		// Parameter entries.
   423  		MachineId:    machineID,
   424  		MachineNonce: machineNonce,
   425  		Networks:     networks,
   426  		MongoInfo:    mongoInfo,
   427  		APIInfo:      apiInfo,
   428  		ImageStream:  imageStream,
   429  		AgentEnvironment: map[string]string{
   430  			agent.AllowsSecureConnection: strconv.FormatBool(secureServerConnections),
   431  		},
   432  	}
   433  	return icfg, nil
   434  }
   435  
   436  // NewBootstrapInstanceConfig sets up a basic machine configuration for a
   437  // bootstrap node.  You'll still need to supply more information, but this
   438  // takes care of the fixed entries and the ones that are always needed.
   439  func NewBootstrapInstanceConfig(cons constraints.Value, series string) (*InstanceConfig, error) {
   440  	// For a bootstrap instance, FinishInstanceConfig will provide the
   441  	// state.Info and the api.Info. The machine id must *always* be "0".
   442  	icfg, err := NewInstanceConfig("0", agent.BootstrapNonce, "", series, true, nil, nil, nil)
   443  	if err != nil {
   444  		return nil, err
   445  	}
   446  	icfg.Bootstrap = true
   447  	icfg.Jobs = []multiwatcher.MachineJob{
   448  		multiwatcher.JobManageEnviron,
   449  		multiwatcher.JobHostUnits,
   450  	}
   451  	icfg.Constraints = cons
   452  	return icfg, nil
   453  }
   454  
   455  // PopulateInstanceConfig is called both from the FinishInstanceConfig below,
   456  // which does have access to the environment config, and from the container
   457  // provisioners, which don't have access to the environment config. Everything
   458  // that is needed to provision a container needs to be returned to the
   459  // provisioner in the ContainerConfig structure. Those values are then used to
   460  // call this function.
   461  func PopulateInstanceConfig(icfg *InstanceConfig,
   462  	providerType, authorizedKeys string,
   463  	sslHostnameVerification bool,
   464  	proxySettings, aptProxySettings proxy.Settings,
   465  	aptMirror string,
   466  	preferIPv6 bool,
   467  	enableOSRefreshUpdates bool,
   468  	enableOSUpgrade bool,
   469  ) error {
   470  	if authorizedKeys == "" {
   471  		return fmt.Errorf("environment configuration has no authorized-keys")
   472  	}
   473  	icfg.AuthorizedKeys = authorizedKeys
   474  	if icfg.AgentEnvironment == nil {
   475  		icfg.AgentEnvironment = make(map[string]string)
   476  	}
   477  	icfg.AgentEnvironment[agent.ProviderType] = providerType
   478  	icfg.AgentEnvironment[agent.ContainerType] = string(icfg.MachineContainerType)
   479  	icfg.DisableSSLHostnameVerification = !sslHostnameVerification
   480  	icfg.ProxySettings = proxySettings
   481  	icfg.AptProxySettings = aptProxySettings
   482  	icfg.AptMirror = aptMirror
   483  	icfg.PreferIPv6 = preferIPv6
   484  	icfg.EnableOSRefreshUpdate = enableOSRefreshUpdates
   485  	icfg.EnableOSUpgrade = enableOSUpgrade
   486  	return nil
   487  }
   488  
   489  // FinishInstanceConfig sets fields on a InstanceConfig that can be determined by
   490  // inspecting a plain config.Config and the machine constraints at the last
   491  // moment before bootstrapping. It assumes that the supplied Config comes from
   492  // an environment that has passed through all the validation checks in the
   493  // Bootstrap func, and that has set an agent-version (via finding the tools to,
   494  // use for bootstrap, or otherwise).
   495  // TODO(fwereade) This function is not meant to be "good" in any serious way:
   496  // it is better that this functionality be collected in one place here than
   497  // that it be spread out across 3 or 4 providers, but this is its only
   498  // redeeming feature.
   499  func FinishInstanceConfig(icfg *InstanceConfig, cfg *config.Config) (err error) {
   500  	defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration")
   501  
   502  	if err := PopulateInstanceConfig(
   503  		icfg,
   504  		cfg.Type(),
   505  		cfg.AuthorizedKeys(),
   506  		cfg.SSLHostnameVerification(),
   507  		cfg.ProxySettings(),
   508  		cfg.AptProxySettings(),
   509  		cfg.AptMirror(),
   510  		cfg.PreferIPv6(),
   511  		cfg.EnableOSRefreshUpdate(),
   512  		cfg.EnableOSUpgrade(),
   513  	); err != nil {
   514  		return errors.Trace(err)
   515  	}
   516  
   517  	if isStateInstanceConfig(icfg) {
   518  		// Add NUMACTL preference. Needed to work for both bootstrap and high availability
   519  		// Only makes sense for state server
   520  		logger.Debugf("Setting numa ctl preference to %v", cfg.NumaCtlPreference())
   521  		// Unfortunately, AgentEnvironment can only take strings as values
   522  		icfg.AgentEnvironment[agent.NumaCtlPreference] = fmt.Sprintf("%v", cfg.NumaCtlPreference())
   523  	}
   524  	// The following settings are only appropriate at bootstrap time. At the
   525  	// moment, the only state server is the bootstrap node, but this
   526  	// will probably change.
   527  	if !icfg.Bootstrap {
   528  		return nil
   529  	}
   530  	if icfg.APIInfo != nil || icfg.MongoInfo != nil {
   531  		return errors.New("machine configuration already has api/state info")
   532  	}
   533  	caCert, hasCACert := cfg.CACert()
   534  	if !hasCACert {
   535  		return errors.New("environment configuration has no ca-cert")
   536  	}
   537  	password := cfg.AdminSecret()
   538  	if password == "" {
   539  		return errors.New("environment configuration has no admin-secret")
   540  	}
   541  	passwordHash := utils.UserPasswordHash(password, utils.CompatSalt)
   542  	envUUID, uuidSet := cfg.UUID()
   543  	if !uuidSet {
   544  		return errors.New("config missing environment uuid")
   545  	}
   546  	icfg.APIInfo = &api.Info{
   547  		Password:   passwordHash,
   548  		CACert:     caCert,
   549  		EnvironTag: names.NewEnvironTag(envUUID),
   550  	}
   551  	icfg.MongoInfo = &mongo.MongoInfo{Password: passwordHash, Info: mongo.Info{CACert: caCert}}
   552  
   553  	// These really are directly relevant to running a state server.
   554  	// Initially, generate a state server certificate with no host IP
   555  	// addresses in the SAN field. Once the state server is up and the
   556  	// NIC addresses become known, the certificate can be regenerated.
   557  	cert, key, err := cfg.GenerateStateServerCertAndKey(nil)
   558  	if err != nil {
   559  		return errors.Annotate(err, "cannot generate state server certificate")
   560  	}
   561  	caPrivateKey, hasCAPrivateKey := cfg.CAPrivateKey()
   562  	if !hasCAPrivateKey {
   563  		return errors.New("environment configuration has no ca-private-key")
   564  	}
   565  	srvInfo := params.StateServingInfo{
   566  		StatePort:    cfg.StatePort(),
   567  		APIPort:      cfg.APIPort(),
   568  		Cert:         string(cert),
   569  		PrivateKey:   string(key),
   570  		CAPrivateKey: caPrivateKey,
   571  	}
   572  	icfg.StateServingInfo = &srvInfo
   573  	if icfg.Config, err = bootstrapConfig(cfg); err != nil {
   574  		return errors.Trace(err)
   575  	}
   576  
   577  	return nil
   578  }
   579  
   580  // InstanceTags returns the minimum set of tags that should be set on a
   581  // machine instance, if the provider supports them.
   582  func InstanceTags(cfg *config.Config, jobs []multiwatcher.MachineJob) map[string]string {
   583  	uuid, _ := cfg.UUID()
   584  	instanceTags := tags.ResourceTags(names.NewEnvironTag(uuid), cfg)
   585  	if multiwatcher.AnyJobNeedsState(jobs...) {
   586  		instanceTags[tags.JujuStateServer] = "true"
   587  	}
   588  	return instanceTags
   589  }
   590  
   591  // bootstrapConfig returns a copy of the supplied configuration with the
   592  // admin-secret and ca-private-key attributes removed. If the resulting
   593  // config is not suitable for bootstrapping an environment, an error is
   594  // returned.
   595  // This function is copied from environs in here so we can avoid an import loop
   596  func bootstrapConfig(cfg *config.Config) (*config.Config, error) {
   597  	m := cfg.AllAttrs()
   598  	// We never want to push admin-secret or the root CA private key to the cloud.
   599  	delete(m, "admin-secret")
   600  	delete(m, "ca-private-key")
   601  	cfg, err := config.New(config.NoDefaults, m)
   602  	if err != nil {
   603  		return nil, err
   604  	}
   605  	if _, ok := cfg.AgentVersion(); !ok {
   606  		return nil, fmt.Errorf("environment configuration has no agent-version")
   607  	}
   608  	return cfg, nil
   609  }
   610  
   611  // isStateInstanceConfig determines if given machine configuration
   612  // is for State Server by iterating over machine's jobs.
   613  // If JobManageEnviron is present, this is a state server.
   614  func isStateInstanceConfig(icfg *InstanceConfig) bool {
   615  	for _, aJob := range icfg.Jobs {
   616  		if aJob == multiwatcher.JobManageEnviron {
   617  			return true
   618  		}
   619  	}
   620  	return false
   621  }