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