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