github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/environs/cloudinit/cloudinit.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cloudinit
     5  
     6  import (
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"fmt"
    10  	"path"
    11  	"strings"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/names"
    15  	"github.com/juju/utils"
    16  	"github.com/juju/utils/apt"
    17  	"github.com/juju/utils/proxy"
    18  	"launchpad.net/goyaml"
    19  
    20  	"github.com/juju/juju/agent"
    21  	agenttools "github.com/juju/juju/agent/tools"
    22  	"github.com/juju/juju/cloudinit"
    23  	"github.com/juju/juju/constraints"
    24  	"github.com/juju/juju/environs/config"
    25  	"github.com/juju/juju/instance"
    26  	"github.com/juju/juju/state"
    27  	"github.com/juju/juju/state/api"
    28  	"github.com/juju/juju/state/api/params"
    29  	coretools "github.com/juju/juju/tools"
    30  	"github.com/juju/juju/upstart"
    31  	"github.com/juju/juju/version"
    32  )
    33  
    34  // fileSchemePrefix is the prefix for file:// URLs.
    35  const fileSchemePrefix = "file://"
    36  
    37  // MachineConfig represents initialization information for a new juju machine.
    38  type MachineConfig struct {
    39  	// Bootstrap specifies whether the new machine is the bootstrap
    40  	// machine. When this is true, StateServingInfo should be set
    41  	// and filled out.
    42  	Bootstrap bool
    43  
    44  	// StateServingInfo holds the information for serving the state.
    45  	// This must only be set if the Bootstrap field is true
    46  	// (state servers started subsequently will acquire their serving info
    47  	// from another server)
    48  	StateServingInfo *params.StateServingInfo
    49  
    50  	// StateInfo holds the means for the new instance to communicate with the
    51  	// juju state. Unless the new machine is running a state server (StateServer is
    52  	// set), there must be at least one state server address supplied.
    53  	// The entity name must match that of the machine being started,
    54  	// or be empty when starting a state server.
    55  	StateInfo *state.Info
    56  
    57  	// APIInfo holds the means for the new instance to communicate with the
    58  	// juju state API. Unless the new machine is running a state server (StateServer is
    59  	// set), there must be at least one state server address supplied.
    60  	// The entity name must match that of the machine being started,
    61  	// or be empty when starting a state server.
    62  	APIInfo *api.Info
    63  
    64  	// InstanceId is the instance ID of the machine being initialised.
    65  	// This is required when bootstrapping, and ignored otherwise.
    66  	InstanceId instance.Id
    67  
    68  	// HardwareCharacteristics contains the harrdware characteristics of
    69  	// the machine being initialised. This optional, and is only used by
    70  	// the bootstrap agent during state initialisation.
    71  	HardwareCharacteristics *instance.HardwareCharacteristics
    72  
    73  	// MachineNonce is set at provisioning/bootstrap time and used to
    74  	// ensure the agent is running on the correct instance.
    75  	MachineNonce string
    76  
    77  	// Tools is juju tools to be used on the new machine.
    78  	Tools *coretools.Tools
    79  
    80  	// DataDir holds the directory that juju state will be put in the new
    81  	// machine.
    82  	DataDir string
    83  
    84  	// LogDir holds the directory that juju logs will be written to.
    85  	LogDir string
    86  
    87  	// Jobs holds what machine jobs to run.
    88  	Jobs []params.MachineJob
    89  
    90  	// CloudInitOutputLog specifies the path to the output log for cloud-init.
    91  	// The directory containing the log file must already exist.
    92  	CloudInitOutputLog string
    93  
    94  	// MachineId identifies the new machine.
    95  	MachineId string
    96  
    97  	// MachineContainerType specifies the type of container that the machine
    98  	// is.  If the machine is not a container, then the type is "".
    99  	MachineContainerType instance.ContainerType
   100  
   101  	// Networks holds a list of networks the machine should be on.
   102  	Networks []string
   103  
   104  	// AuthorizedKeys specifies the keys that are allowed to
   105  	// connect to the machine (see cloudinit.SSHAddAuthorizedKeys)
   106  	// If no keys are supplied, there can be no ssh access to the node.
   107  	// On a bootstrap machine, that is fatal. On other
   108  	// machines it will mean that the ssh, scp and debug-hooks
   109  	// commands cannot work.
   110  	AuthorizedKeys string
   111  
   112  	// AgentEnvironment defines additional configuration variables to set in
   113  	// the machine agent config.
   114  	AgentEnvironment map[string]string
   115  
   116  	// WARNING: this is only set if the machine being configured is
   117  	// a state server node.
   118  	//
   119  	// Config holds the initial environment configuration.
   120  	Config *config.Config
   121  
   122  	// Constraints holds the initial environment constraints.
   123  	Constraints constraints.Value
   124  
   125  	// DisableSSLHostnameVerification can be set to true to tell cloud-init
   126  	// that it shouldn't verify SSL certificates
   127  	DisableSSLHostnameVerification bool
   128  
   129  	// SystemPrivateSSHKey is created at bootstrap time and recorded on every
   130  	// node that has an API server. At this stage, that is any machine where
   131  	// StateServer (member above) is set to true.
   132  	SystemPrivateSSHKey string
   133  
   134  	// DisablePackageCommands is a flag that specifies whether to suppress
   135  	// the addition of package management commands.
   136  	DisablePackageCommands bool
   137  
   138  	// MachineAgentServiceName is the Upstart service name for the Juju machine agent.
   139  	MachineAgentServiceName string
   140  
   141  	// ProxySettings define normal http, https and ftp proxies.
   142  	ProxySettings proxy.Settings
   143  
   144  	// AptProxySettings define the http, https and ftp proxy settings to use
   145  	// for apt, which may or may not be the same as the normal ProxySettings.
   146  	AptProxySettings proxy.Settings
   147  }
   148  
   149  func base64yaml(m *config.Config) string {
   150  	data, err := goyaml.Marshal(m.AllAttrs())
   151  	if err != nil {
   152  		// can't happen, these values have been validated a number of times
   153  		panic(err)
   154  	}
   155  	return base64.StdEncoding.EncodeToString(data)
   156  }
   157  
   158  // Configure updates the provided cloudinit.Config with
   159  // configuration to initialize a Juju machine agent.
   160  func Configure(cfg *MachineConfig, c *cloudinit.Config) error {
   161  	if err := ConfigureBasic(cfg, c); err != nil {
   162  		return err
   163  	}
   164  	return ConfigureJuju(cfg, c)
   165  }
   166  
   167  // NonceFile is written by cloud-init as the last thing it does.
   168  // The file will contain the machine's nonce. The filename is
   169  // relative to the Juju data-dir.
   170  const NonceFile = "nonce.txt"
   171  
   172  // ConfigureBasic updates the provided cloudinit.Config with
   173  // basic configuration to initialise an OS image, such that it can
   174  // be connected to via SSH, and log to a standard location.
   175  //
   176  // Any potentially failing operation should not be added to the
   177  // configuration, but should instead be done in ConfigureJuju.
   178  //
   179  // Note: we don't do apt update/upgrade here so as not to have to wait on
   180  // apt to finish when performing the second half of image initialisation.
   181  // Doing it later brings the benefit of feedback in the face of errors,
   182  // but adds to the running time of initialisation due to lack of activity
   183  // between image bringup and start of agent installation.
   184  func ConfigureBasic(cfg *MachineConfig, c *cloudinit.Config) error {
   185  	c.AddScripts(
   186  		"set -xe", // ensure we run all the scripts or abort.
   187  	)
   188  	c.AddSSHAuthorizedKeys(cfg.AuthorizedKeys)
   189  	c.SetOutput(cloudinit.OutAll, "| tee -a "+cfg.CloudInitOutputLog, "")
   190  	// Create a file in a well-defined location containing the machine's
   191  	// nonce. The presence and contents of this file will be verified
   192  	// during bootstrap.
   193  	//
   194  	// Note: this must be the last runcmd we do in ConfigureBasic, as
   195  	// the presence of the nonce file is used to gate the remainder
   196  	// of synchronous bootstrap.
   197  	noncefile := path.Join(cfg.DataDir, NonceFile)
   198  	c.AddFile(noncefile, cfg.MachineNonce, 0644)
   199  	return nil
   200  }
   201  
   202  // AddAptCommands update the cloudinit.Config instance with the necessary
   203  // packages, the request to do the apt-get update/upgrade on boot, and adds
   204  // the apt proxy settings if there are any.
   205  func AddAptCommands(proxySettings proxy.Settings, c *cloudinit.Config) {
   206  	// Bring packages up-to-date.
   207  	c.SetAptUpdate(true)
   208  	c.SetAptUpgrade(true)
   209  
   210  	// juju requires git for managing charm directories.
   211  	c.AddPackage("git")
   212  	c.AddPackage("curl")
   213  	c.AddPackage("cpu-checker")
   214  	c.AddPackage("bridge-utils")
   215  	c.AddPackage("rsyslog-gnutls")
   216  
   217  	// Write out the apt proxy settings
   218  	if (proxySettings != proxy.Settings{}) {
   219  		filename := apt.ConfFile
   220  		c.AddBootCmd(fmt.Sprintf(
   221  			`[ -f %s ] || (printf '%%s\n' %s > %s)`,
   222  			filename,
   223  			shquote(apt.ProxyContent(proxySettings)),
   224  			filename))
   225  	}
   226  }
   227  
   228  // ConfigureJuju updates the provided cloudinit.Config with configuration
   229  // to initialise a Juju machine agent.
   230  func ConfigureJuju(cfg *MachineConfig, c *cloudinit.Config) error {
   231  	if err := verifyConfig(cfg); err != nil {
   232  		return err
   233  	}
   234  
   235  	// Initialise progress reporting. We need to do separately for runcmd
   236  	// and (possibly, below) for bootcmd, as they may be run in different
   237  	// shell sessions.
   238  	initProgressCmd := cloudinit.InitProgressCmd()
   239  	c.AddRunCmd(initProgressCmd)
   240  
   241  	// If we're doing synchronous bootstrap or manual provisioning, then
   242  	// ConfigureBasic won't have been invoked; thus, the output log won't
   243  	// have been set. We don't want to show the log to the user, so simply
   244  	// append to the log file rather than teeing.
   245  	if stdout, _ := c.Output(cloudinit.OutAll); stdout == "" {
   246  		c.SetOutput(cloudinit.OutAll, ">> "+cfg.CloudInitOutputLog, "")
   247  		c.AddBootCmd(initProgressCmd)
   248  		c.AddBootCmd(cloudinit.LogProgressCmd("Logging to %s on remote host", cfg.CloudInitOutputLog))
   249  	}
   250  
   251  	if !cfg.DisablePackageCommands {
   252  		AddAptCommands(cfg.AptProxySettings, c)
   253  	}
   254  
   255  	// Write out the normal proxy settings so that the settings are
   256  	// sourced by bash, and ssh through that.
   257  	c.AddScripts(
   258  		// We look to see if the proxy line is there already as
   259  		// the manual provider may have had it aleady. The ubuntu
   260  		// user may not exist (local provider only).
   261  		`([ ! -e /home/ubuntu/.profile ] || grep -q '.juju-proxy' /home/ubuntu/.profile) || ` +
   262  			`printf '\n# Added by juju\n[ -f "$HOME/.juju-proxy" ] && . "$HOME/.juju-proxy"\n' >> /home/ubuntu/.profile`)
   263  	if (cfg.ProxySettings != proxy.Settings{}) {
   264  		exportedProxyEnv := cfg.ProxySettings.AsScriptEnvironment()
   265  		c.AddScripts(strings.Split(exportedProxyEnv, "\n")...)
   266  		c.AddScripts(
   267  			fmt.Sprintf(
   268  				`[ -e /home/ubuntu ] && (printf '%%s\n' %s > /home/ubuntu/.juju-proxy && chown ubuntu:ubuntu /home/ubuntu/.juju-proxy)`,
   269  				shquote(cfg.ProxySettings.AsScriptEnvironment())))
   270  	}
   271  
   272  	// Make the lock dir and change the ownership of the lock dir itself to
   273  	// ubuntu:ubuntu from root:root so the juju-run command run as the ubuntu
   274  	// user is able to get access to the hook execution lock (like the uniter
   275  	// itself does.)
   276  	lockDir := path.Join(cfg.DataDir, "locks")
   277  	c.AddScripts(
   278  		fmt.Sprintf("mkdir -p %s", lockDir),
   279  		// We only try to change ownership if there is an ubuntu user
   280  		// defined, and we determine this by the existance of the home dir.
   281  		fmt.Sprintf("[ -e /home/ubuntu ] && chown ubuntu:ubuntu %s", lockDir),
   282  		fmt.Sprintf("mkdir -p %s", cfg.LogDir),
   283  		fmt.Sprintf("chown syslog:adm %s", cfg.LogDir),
   284  	)
   285  
   286  	// Make a directory for the tools to live in, then fetch the
   287  	// tools and unarchive them into it.
   288  	var copyCmd string
   289  	if strings.HasPrefix(cfg.Tools.URL, fileSchemePrefix) {
   290  		copyCmd = fmt.Sprintf("cp %s $bin/tools.tar.gz", shquote(cfg.Tools.URL[len(fileSchemePrefix):]))
   291  	} else {
   292  		curlCommand := "curl -sSfw 'tools from %{url_effective} downloaded: HTTP %{http_code}; time %{time_total}s; size %{size_download} bytes; speed %{speed_download} bytes/s '"
   293  		if cfg.DisableSSLHostnameVerification {
   294  			curlCommand += " --insecure"
   295  		}
   296  		copyCmd = fmt.Sprintf("%s -o $bin/tools.tar.gz %s", curlCommand, shquote(cfg.Tools.URL))
   297  		c.AddRunCmd(cloudinit.LogProgressCmd("Fetching tools: %s", copyCmd))
   298  	}
   299  	toolsJson, err := json.Marshal(cfg.Tools)
   300  	if err != nil {
   301  		return err
   302  	}
   303  	c.AddScripts(
   304  		"bin="+shquote(cfg.jujuTools()),
   305  		"mkdir -p $bin",
   306  		copyCmd,
   307  		fmt.Sprintf("sha256sum $bin/tools.tar.gz > $bin/juju%s.sha256", cfg.Tools.Version),
   308  		fmt.Sprintf(`grep '%s' $bin/juju%s.sha256 || (echo "Tools checksum mismatch"; exit 1)`,
   309  			cfg.Tools.SHA256, cfg.Tools.Version),
   310  		fmt.Sprintf("tar zxf $bin/tools.tar.gz -C $bin"),
   311  		fmt.Sprintf("rm $bin/tools.tar.gz && rm $bin/juju%s.sha256", cfg.Tools.Version),
   312  		fmt.Sprintf("printf %%s %s > $bin/downloaded-tools.txt", shquote(string(toolsJson))),
   313  	)
   314  
   315  	// We add the machine agent's configuration info
   316  	// before running bootstrap-state so that bootstrap-state
   317  	// has a chance to rerwrite it to change the password.
   318  	// It would be cleaner to change bootstrap-state to
   319  	// be responsible for starting the machine agent itself,
   320  	// but this would not be backwardly compatible.
   321  	machineTag := names.MachineTag(cfg.MachineId)
   322  	_, err = cfg.addAgentInfo(c, machineTag)
   323  	if err != nil {
   324  		return err
   325  	}
   326  
   327  	// Add the cloud archive cloud-tools pocket to apt sources
   328  	// for series that need it. This gives us up-to-date LXC,
   329  	// MongoDB, and other infrastructure.
   330  	if !cfg.DisablePackageCommands {
   331  		series := cfg.Tools.Version.Series
   332  		MaybeAddCloudArchiveCloudTools(c, series)
   333  	}
   334  
   335  	if cfg.Bootstrap {
   336  		cons := cfg.Constraints.String()
   337  		if cons != "" {
   338  			cons = " --constraints " + shquote(cons)
   339  		}
   340  		var hardware string
   341  		if cfg.HardwareCharacteristics != nil {
   342  			if hardware = cfg.HardwareCharacteristics.String(); hardware != "" {
   343  				hardware = " --hardware " + shquote(hardware)
   344  			}
   345  		}
   346  		c.AddRunCmd(cloudinit.LogProgressCmd("Bootstrapping Juju machine agent"))
   347  		c.AddScripts(
   348  			// The bootstrapping is always run with debug on.
   349  			cfg.jujuTools() + "/jujud bootstrap-state" +
   350  				" --data-dir " + shquote(cfg.DataDir) +
   351  				" --env-config " + shquote(base64yaml(cfg.Config)) +
   352  				" --instance-id " + shquote(string(cfg.InstanceId)) +
   353  				hardware +
   354  				cons +
   355  				" --debug",
   356  		)
   357  	}
   358  
   359  	return cfg.addMachineAgentToBoot(c, machineTag, cfg.MachineId)
   360  }
   361  
   362  func (cfg *MachineConfig) dataFile(name string) string {
   363  	return path.Join(cfg.DataDir, name)
   364  }
   365  
   366  func (cfg *MachineConfig) agentConfig(tag string) (agent.ConfigSetter, error) {
   367  	// TODO for HAState: the stateHostAddrs and apiHostAddrs here assume that
   368  	// if the machine is a stateServer then to use localhost.  This may be
   369  	// sufficient, but needs thought in the new world order.
   370  	var password string
   371  	if cfg.StateInfo == nil {
   372  		password = cfg.APIInfo.Password
   373  	} else {
   374  		password = cfg.StateInfo.Password
   375  	}
   376  	configParams := agent.AgentConfigParams{
   377  		DataDir:           cfg.DataDir,
   378  		LogDir:            cfg.LogDir,
   379  		Jobs:              cfg.Jobs,
   380  		Tag:               tag,
   381  		UpgradedToVersion: version.Current.Number,
   382  		Password:          password,
   383  		Nonce:             cfg.MachineNonce,
   384  		StateAddresses:    cfg.stateHostAddrs(),
   385  		APIAddresses:      cfg.apiHostAddrs(),
   386  		CACert:            cfg.StateInfo.CACert,
   387  		Values:            cfg.AgentEnvironment,
   388  	}
   389  	if !cfg.Bootstrap {
   390  		return agent.NewAgentConfig(configParams)
   391  	}
   392  	return agent.NewStateMachineConfig(configParams, *cfg.StateServingInfo)
   393  }
   394  
   395  // addAgentInfo adds agent-required information to the agent's directory
   396  // and returns the agent directory name.
   397  func (cfg *MachineConfig) addAgentInfo(c *cloudinit.Config, tag string) (agent.Config, error) {
   398  	acfg, err := cfg.agentConfig(tag)
   399  	if err != nil {
   400  		return nil, err
   401  	}
   402  	acfg.SetValue(agent.AgentServiceName, cfg.MachineAgentServiceName)
   403  	cmds, err := acfg.WriteCommands()
   404  	if err != nil {
   405  		return nil, errors.Annotate(err, "failed to write commands")
   406  	}
   407  	c.AddScripts(cmds...)
   408  	return acfg, nil
   409  }
   410  
   411  func (cfg *MachineConfig) addMachineAgentToBoot(c *cloudinit.Config, tag, machineId string) error {
   412  	// Make the agent run via a symbolic link to the actual tools
   413  	// directory, so it can upgrade itself without needing to change
   414  	// the upstart script.
   415  	toolsDir := agenttools.ToolsDir(cfg.DataDir, tag)
   416  	// TODO(dfc) ln -nfs, so it doesn't fail if for some reason that the target already exists
   417  	c.AddScripts(fmt.Sprintf("ln -s %v %s", cfg.Tools.Version, shquote(toolsDir)))
   418  
   419  	name := cfg.MachineAgentServiceName
   420  	conf := upstart.MachineAgentUpstartService(name, toolsDir, cfg.DataDir, cfg.LogDir, tag, machineId, nil)
   421  	cmds, err := conf.InstallCommands()
   422  	if err != nil {
   423  		return errors.Annotatef(err, "cannot make cloud-init upstart script for the %s agent", tag)
   424  	}
   425  	c.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (%s)", name))
   426  	c.AddScripts(cmds...)
   427  	return nil
   428  }
   429  
   430  // versionDir converts a tools URL into a name
   431  // to use as a directory for storing the tools executables in
   432  // by using the last element stripped of its extension.
   433  func versionDir(toolsURL string) string {
   434  	name := path.Base(toolsURL)
   435  	ext := path.Ext(name)
   436  	return name[:len(name)-len(ext)]
   437  }
   438  
   439  func (cfg *MachineConfig) jujuTools() string {
   440  	return agenttools.SharedToolsDir(cfg.DataDir, cfg.Tools.Version)
   441  }
   442  
   443  func (cfg *MachineConfig) stateHostAddrs() []string {
   444  	var hosts []string
   445  	if cfg.Bootstrap {
   446  		hosts = append(hosts, fmt.Sprintf("localhost:%d", cfg.StateServingInfo.StatePort))
   447  	}
   448  	if cfg.StateInfo != nil {
   449  		hosts = append(hosts, cfg.StateInfo.Addrs...)
   450  	}
   451  	return hosts
   452  }
   453  
   454  func (cfg *MachineConfig) apiHostAddrs() []string {
   455  	var hosts []string
   456  	if cfg.Bootstrap {
   457  		hosts = append(hosts, fmt.Sprintf("localhost:%d", cfg.StateServingInfo.APIPort))
   458  	}
   459  	if cfg.APIInfo != nil {
   460  		hosts = append(hosts, cfg.APIInfo.Addrs...)
   461  	}
   462  	return hosts
   463  }
   464  
   465  const CanonicalCloudArchiveSigningKey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
   466  Version: SKS 1.1.4
   467  Comment: Hostname: keyserver.ubuntu.com
   468  
   469  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
   510  p/+af/HU1smBrOfIeRoxb8jQoHu3
   511  =xg4S
   512  -----END PGP PUBLIC KEY BLOCK-----`
   513  
   514  // MaybeAddCloudArchiveCloudTools adds the cloud-archive cloud-tools
   515  // pocket to apt sources, if the series requires it.
   516  func MaybeAddCloudArchiveCloudTools(c *cloudinit.Config, series string) {
   517  	if series != "precise" {
   518  		// Currently only precise; presumably we'll
   519  		// need to add each LTS in here as they're
   520  		// added to the cloud archive.
   521  		return
   522  	}
   523  	const url = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
   524  	name := fmt.Sprintf("deb %s %s-updates/cloud-tools main", url, series)
   525  	prefs := &cloudinit.AptPreferences{
   526  		Path:        cloudinit.CloudToolsPrefsPath,
   527  		Explanation: "Pin with lower priority, not to interfere with charms",
   528  		Package:     "*",
   529  		Pin:         fmt.Sprintf("release n=%s-updates/cloud-tools", series),
   530  		PinPriority: 400,
   531  	}
   532  	c.AddAptSource(name, CanonicalCloudArchiveSigningKey, prefs)
   533  }
   534  
   535  // HasNetworks returns if there are any networks set.
   536  func (cfg *MachineConfig) HasNetworks() bool {
   537  	return len(cfg.Networks) > 0 || cfg.Constraints.HaveNetworks()
   538  }
   539  
   540  func shquote(p string) string {
   541  	return utils.ShQuote(p)
   542  }
   543  
   544  type requiresError string
   545  
   546  func (e requiresError) Error() string {
   547  	return "invalid machine configuration: missing " + string(e)
   548  }
   549  
   550  func verifyConfig(cfg *MachineConfig) (err error) {
   551  	defer errors.Maskf(&err, "invalid machine configuration")
   552  	if !names.IsMachine(cfg.MachineId) {
   553  		return fmt.Errorf("invalid machine id")
   554  	}
   555  	if cfg.DataDir == "" {
   556  		return fmt.Errorf("missing var directory")
   557  	}
   558  	if cfg.LogDir == "" {
   559  		return fmt.Errorf("missing log directory")
   560  	}
   561  	if len(cfg.Jobs) == 0 {
   562  		return fmt.Errorf("missing machine jobs")
   563  	}
   564  	if cfg.CloudInitOutputLog == "" {
   565  		return fmt.Errorf("missing cloud-init output log path")
   566  	}
   567  	if cfg.Tools == nil {
   568  		return fmt.Errorf("missing tools")
   569  	}
   570  	if cfg.Tools.URL == "" {
   571  		return fmt.Errorf("missing tools URL")
   572  	}
   573  	if cfg.StateInfo == nil {
   574  		return fmt.Errorf("missing state info")
   575  	}
   576  	if len(cfg.StateInfo.CACert) == 0 {
   577  		return fmt.Errorf("missing CA certificate")
   578  	}
   579  	if cfg.APIInfo == nil {
   580  		return fmt.Errorf("missing API info")
   581  	}
   582  	if len(cfg.APIInfo.CACert) == 0 {
   583  		return fmt.Errorf("missing API CA certificate")
   584  	}
   585  	if cfg.MachineAgentServiceName == "" {
   586  		return fmt.Errorf("missing machine agent service name")
   587  	}
   588  	if cfg.Bootstrap {
   589  		if cfg.Config == nil {
   590  			return fmt.Errorf("missing environment configuration")
   591  		}
   592  		if cfg.StateInfo.Tag != "" {
   593  			return fmt.Errorf("entity tag must be blank when starting a state server")
   594  		}
   595  		if cfg.APIInfo.Tag != "" {
   596  			return fmt.Errorf("entity tag must be blank when starting a state server")
   597  		}
   598  		if cfg.StateServingInfo == nil {
   599  			return fmt.Errorf("missing state serving info")
   600  		}
   601  		if len(cfg.StateServingInfo.Cert) == 0 {
   602  			return fmt.Errorf("missing state server certificate")
   603  		}
   604  		if len(cfg.StateServingInfo.PrivateKey) == 0 {
   605  			return fmt.Errorf("missing state server private key")
   606  		}
   607  		if cfg.StateServingInfo.StatePort == 0 {
   608  			return fmt.Errorf("missing state port")
   609  		}
   610  		if cfg.StateServingInfo.APIPort == 0 {
   611  			return fmt.Errorf("missing API port")
   612  		}
   613  		if cfg.SystemPrivateSSHKey == "" {
   614  			return fmt.Errorf("missing system ssh identity")
   615  		}
   616  		if cfg.InstanceId == "" {
   617  			return fmt.Errorf("missing instance-id")
   618  		}
   619  	} else {
   620  		if len(cfg.StateInfo.Addrs) == 0 {
   621  			return fmt.Errorf("missing state hosts")
   622  		}
   623  		if cfg.StateInfo.Tag != names.MachineTag(cfg.MachineId) {
   624  			return fmt.Errorf("entity tag must match started machine")
   625  		}
   626  		if len(cfg.APIInfo.Addrs) == 0 {
   627  			return fmt.Errorf("missing API hosts")
   628  		}
   629  		if cfg.APIInfo.Tag != names.MachineTag(cfg.MachineId) {
   630  			return fmt.Errorf("entity tag must match started machine")
   631  		}
   632  		if cfg.StateServingInfo != nil {
   633  			return fmt.Errorf("state serving info unexpectedly present")
   634  		}
   635  	}
   636  	if cfg.MachineNonce == "" {
   637  		return fmt.Errorf("missing machine nonce")
   638  	}
   639  	return nil
   640  }