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 }