func (*progressSuite) TestProgressCmds(c *gc.C) { initCmd := cloudinit.InitProgressCmd() re := regexp.MustCompile(`test -e /proc/self/fd/([0-9]+) \|\| exec ([0-9]+)>&2`) submatch := re.FindStringSubmatch(initCmd) c.Assert(submatch, gc.HasLen, 3) c.Assert(submatch[0], gc.Equals, initCmd) c.Assert(submatch[1], gc.Equals, submatch[2]) logCmd := cloudinit.LogProgressCmd("he'llo\"!") c.Assert(logCmd, gc.Equals, `echo 'he'"'"'llo"!' >&`+submatch[1]) }
func (w *unixConfigure) addDownloadToolsCmds() error { tools := w.icfg.ToolsList()[0] if strings.HasPrefix(tools.URL, fileSchemePrefix) { toolsData, err := ioutil.ReadFile(tools.URL[len(fileSchemePrefix):]) if err != nil { return err } w.conf.AddRunBinaryFile(path.Join(w.icfg.JujuTools(), "tools.tar.gz"), []byte(toolsData), 0644) } else { curlCommand := curlCommand var urls []string for _, tools := range w.icfg.ToolsList() { urls = append(urls, tools.URL) } if w.icfg.Bootstrap != nil { curlCommand += " --retry 10" if w.icfg.DisableSSLHostnameVerification { curlCommand += " --insecure" } } else { // Don't go through the proxy when downloading tools from the controllers curlCommand += ` --noproxy "*"` // Our API server certificates are unusable by curl (invalid subject name), // so we must disable certificate validation. It doesn't actually // matter, because there is no sensitive information being transmitted // and we verify the tools' hash after. curlCommand += " --insecure" } curlCommand += " -o $bin/tools.tar.gz" w.conf.AddRunCmd(cloudinit.LogProgressCmd("Fetching Juju agent version %s for %s", tools.Version.Number, tools.Version.Arch)) logger.Infof("Fetching agent: %s <%s>", curlCommand, urls) w.conf.AddRunCmd(toolsDownloadCommand(curlCommand, urls)) } w.conf.AddScripts( fmt.Sprintf("sha256sum $bin/tools.tar.gz > $bin/juju%s.sha256", tools.Version), fmt.Sprintf(`grep '%s' $bin/juju%s.sha256 || (echo "Tools checksum mismatch"; exit 1)`, tools.SHA256, tools.Version), "tar zxf $bin/tools.tar.gz -C $bin", ) toolsJson, err := json.Marshal(tools) if err != nil { return err } w.conf.AddScripts( fmt.Sprintf("printf %%s %s > $bin/downloaded-tools.txt", shquote(string(toolsJson))), ) return nil }
func (w *unixConfigure) configureBootstrap() error { // Add the Juju GUI to the bootstrap node. cleanup, err := w.setUpGUI() if err != nil { return errors.Annotate(err, "cannot set up Juju GUI") } if cleanup != nil { defer cleanup() } bootstrapParamsFile := path.Join(w.icfg.DataDir, "bootstrap-params") bootstrapParams, err := w.icfg.Bootstrap.StateInitializationParams.Marshal() if err != nil { return errors.Annotate(err, "marshalling bootstrap params") } w.conf.AddRunTextFile(bootstrapParamsFile, string(bootstrapParams), 0600) loggingOption := "--show-log" if loggo.GetLogger("").LogLevel() == loggo.DEBUG { // If the bootstrap command was requested with --debug, then the root // logger will be set to DEBUG. If it is, then we use --debug here too. loggingOption = "--debug" } featureFlags := featureflag.AsEnvironmentValue() if featureFlags != "" { featureFlags = fmt.Sprintf("%s=%s ", osenv.JujuFeatureFlagEnvKey, featureFlags) } bootstrapAgentArgs := []string{ featureFlags + w.icfg.JujuTools() + "/jujud", "bootstrap-state", "--timeout", w.icfg.Bootstrap.Timeout.String(), "--data-dir", shquote(w.icfg.DataDir), loggingOption, shquote(bootstrapParamsFile), } w.conf.AddRunCmd(cloudinit.LogProgressCmd("Installing Juju machine agent")) w.conf.AddScripts(strings.Join(bootstrapAgentArgs, " ")) return nil }
func (c *baseConfigure) addMachineAgentToBoot() error { svc, err := c.icfg.InitService(c.conf.ShellRenderer()) if err != nil { return errors.Trace(err) } // Make the agent run via a symbolic link to the actual tools // directory, so it can upgrade itself without needing to change // the init script. toolsDir := c.icfg.ToolsDir(c.conf.ShellRenderer()) c.conf.AddScripts(c.toolsSymlinkCommand(toolsDir)) name := c.tag.String() cmds, err := svc.InstallCommands() if err != nil { return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name) } startCmds, err := svc.StartCommands() if err != nil { return errors.Annotatef(err, "cannot make cloud-init init script for the %s agent", name) } cmds = append(cmds, startCmds...) svcName := c.icfg.MachineAgentServiceName // TODO (gsamfira): This is temporary until we find a cleaner way to fix // cloudinit.LogProgressCmd to not add >&9 on Windows. targetOS, err := series.GetOSFromSeries(c.icfg.Series) if err != nil { return err } if targetOS != os.Windows { c.conf.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (%s)", svcName)) } c.conf.AddScripts(cmds...) return nil }
// ConfigureJuju updates the provided cloudinit.Config with configuration // to initialise a Juju machine agent. func (w *unixConfigure) ConfigureJuju() error { if err := w.icfg.VerifyConfig(); err != nil { return err } // Initialise progress reporting. We need to do separately for runcmd // and (possibly, below) for bootcmd, as they may be run in different // shell sessions. initProgressCmd := cloudinit.InitProgressCmd() w.conf.AddRunCmd(initProgressCmd) // If we're doing synchronous bootstrap or manual provisioning, then // ConfigureBasic won't have been invoked; thus, the output log won't // have been set. We don't want to show the log to the user, so simply // append to the log file rather than teeing. if stdout, _ := w.conf.Output(cloudinit.OutAll); stdout == "" { w.conf.SetOutput(cloudinit.OutAll, ">> "+w.icfg.CloudInitOutputLog, "") w.conf.AddBootCmd(initProgressCmd) w.conf.AddBootCmd(cloudinit.LogProgressCmd("Logging to %s on remote host", w.icfg.CloudInitOutputLog)) } w.conf.AddPackageCommands( w.icfg.AptProxySettings, w.icfg.AptMirror, w.icfg.EnableOSRefreshUpdate, w.icfg.EnableOSUpgrade, ) // Write out the normal proxy settings so that the settings are // sourced by bash, and ssh through that. w.conf.AddScripts( // We look to see if the proxy line is there already as // the manual provider may have had it already. The ubuntu // user may not exist. `([ ! -e /home/ubuntu/.profile ] || grep -q '.juju-proxy' /home/ubuntu/.profile) || ` + `printf '\n# Added by juju\n[ -f "$HOME/.juju-proxy" ] && . "$HOME/.juju-proxy"\n' >> /home/ubuntu/.profile`) if (w.icfg.ProxySettings != proxy.Settings{}) { exportedProxyEnv := w.icfg.ProxySettings.AsScriptEnvironment() w.conf.AddScripts(strings.Split(exportedProxyEnv, "\n")...) w.conf.AddScripts( fmt.Sprintf( `(id ubuntu &> /dev/null) && (printf '%%s\n' %s > /home/ubuntu/.juju-proxy && chown ubuntu:ubuntu /home/ubuntu/.juju-proxy)`, shquote(w.icfg.ProxySettings.AsScriptEnvironment()))) } if w.icfg.PublicImageSigningKey != "" { keyFile := filepath.Join(agent.DefaultPaths.ConfDir, simplestreams.SimplestreamsPublicKeyFile) w.conf.AddRunTextFile(keyFile, w.icfg.PublicImageSigningKey, 0644) } // Make the lock dir and change the ownership of the lock dir itself to // ubuntu:ubuntu from root:root so the juju-run command run as the ubuntu // user is able to get access to the hook execution lock (like the uniter // itself does.) lockDir := path.Join(w.icfg.DataDir, "locks") w.conf.AddScripts( fmt.Sprintf("mkdir -p %s", lockDir), // We only try to change ownership if there is an ubuntu user defined. fmt.Sprintf("(id ubuntu &> /dev/null) && chown ubuntu:ubuntu %s", lockDir), fmt.Sprintf("mkdir -p %s", w.icfg.LogDir), w.setDataDirPermissions(), ) w.conf.AddScripts( "bin="+shquote(w.icfg.JujuTools()), "mkdir -p $bin", ) // Make a directory for the tools to live in, then fetch the // tools and unarchive them into it. if strings.HasPrefix(w.icfg.Tools.URL, fileSchemePrefix) { toolsData, err := ioutil.ReadFile(w.icfg.Tools.URL[len(fileSchemePrefix):]) if err != nil { return err } w.conf.AddRunBinaryFile(path.Join(w.icfg.JujuTools(), "tools.tar.gz"), []byte(toolsData), 0644) } else { curlCommand := curlCommand var urls []string if w.icfg.Bootstrap { curlCommand += " --retry 10" if w.icfg.DisableSSLHostnameVerification { curlCommand += " --insecure" } urls = append(urls, w.icfg.Tools.URL) } else { for _, addr := range w.icfg.ApiHostAddrs() { // TODO(axw) encode env UUID in URL when ModelTag // is guaranteed to be available in APIInfo. url := fmt.Sprintf("https://%s/tools/%s", addr, w.icfg.Tools.Version) urls = append(urls, url) } // Don't go through the proxy when downloading tools from the controllers curlCommand += ` --noproxy "*"` // Our API server certificates are unusable by curl (invalid subject name), // so we must disable certificate validation. It doesn't actually // matter, because there is no sensitive information being transmitted // and we verify the tools' hash after. curlCommand += " --insecure" } curlCommand += " -o $bin/tools.tar.gz" w.conf.AddRunCmd(cloudinit.LogProgressCmd("Fetching tools: %s <%s>", curlCommand, urls)) w.conf.AddRunCmd(toolsDownloadCommand(curlCommand, urls)) } toolsJson, err := json.Marshal(w.icfg.Tools) if err != nil { return err } w.conf.AddScripts( fmt.Sprintf("sha256sum $bin/tools.tar.gz > $bin/juju%s.sha256", w.icfg.Tools.Version), fmt.Sprintf(`grep '%s' $bin/juju%s.sha256 || (echo "Tools checksum mismatch"; exit 1)`, w.icfg.Tools.SHA256, w.icfg.Tools.Version), fmt.Sprintf("tar zxf $bin/tools.tar.gz -C $bin"), fmt.Sprintf("printf %%s %s > $bin/downloaded-tools.txt", shquote(string(toolsJson))), ) // Don't remove tools tarball until after bootstrap agent // runs, so it has a chance to add it to its catalogue. defer w.conf.AddRunCmd( fmt.Sprintf("rm $bin/tools.tar.gz && rm $bin/juju%s.sha256", w.icfg.Tools.Version), ) // We add the machine agent's configuration info // before running bootstrap-state so that bootstrap-state // has a chance to rerwrite it to change the password. // It would be cleaner to change bootstrap-state to // be responsible for starting the machine agent itself, // but this would not be backwardly compatible. machineTag := names.NewMachineTag(w.icfg.MachineId) _, err = w.addAgentInfo(machineTag) if err != nil { return errors.Trace(err) } // Add the cloud archive cloud-tools pocket to apt sources // for series that need it. This gives us up-to-date LXC, // MongoDB, and other infrastructure. // This is only done on ubuntu. if w.conf.SystemUpdate() && w.conf.RequiresCloudArchiveCloudTools() { w.conf.AddCloudArchiveCloudTools() } if w.icfg.Bootstrap { var metadataDir string if len(w.icfg.CustomImageMetadata) > 0 { metadataDir = path.Join(w.icfg.DataDir, "simplestreams") index, products, err := imagemetadata.MarshalImageMetadataJSON(w.icfg.CustomImageMetadata, nil, time.Now()) if err != nil { return err } indexFile := path.Join(metadataDir, imagemetadata.IndexStoragePath()) productFile := path.Join(metadataDir, imagemetadata.ProductMetadataStoragePath()) w.conf.AddRunTextFile(indexFile, string(index), 0644) w.conf.AddRunTextFile(productFile, string(products), 0644) metadataDir = " --image-metadata " + shquote(metadataDir) } bootstrapCons := w.icfg.Constraints.String() if bootstrapCons != "" { bootstrapCons = " --bootstrap-constraints " + shquote(bootstrapCons) } environCons := w.icfg.EnvironConstraints.String() if environCons != "" { environCons = " --constraints " + shquote(environCons) } var hardware string if w.icfg.HardwareCharacteristics != nil { if hardware = w.icfg.HardwareCharacteristics.String(); hardware != "" { hardware = " --hardware " + shquote(hardware) } } w.conf.AddRunCmd(cloudinit.LogProgressCmd("Bootstrapping Juju machine agent")) loggingOption := " --show-log" // If the bootstrap command was requsted with --debug, then the root // logger will be set to DEBUG. If it is, then we use --debug here too. if loggo.GetLogger("").LogLevel() == loggo.DEBUG { loggingOption = " --debug" } w.conf.AddScripts( // The bootstrapping is always run with debug on. w.icfg.JujuTools() + "/jujud bootstrap-state" + " --data-dir " + shquote(w.icfg.DataDir) + " --model-config " + shquote(base64yaml(w.icfg.Config)) + " --instance-id " + shquote(string(w.icfg.InstanceId)) + hardware + bootstrapCons + environCons + metadataDir + loggingOption, ) } return w.addMachineAgentToBoot() }
// ConfigureJuju updates the provided cloudinit.Config with configuration // to initialise a Juju machine agent. func (w *unixConfigure) ConfigureJuju() error { if err := w.icfg.VerifyConfig(); err != nil { return err } // Initialise progress reporting. We need to do separately for runcmd // and (possibly, below) for bootcmd, as they may be run in different // shell sessions. initProgressCmd := cloudinit.InitProgressCmd() w.conf.AddRunCmd(initProgressCmd) // If we're doing synchronous bootstrap or manual provisioning, then // ConfigureBasic won't have been invoked; thus, the output log won't // have been set. We don't want to show the log to the user, so simply // append to the log file rather than teeing. if stdout, _ := w.conf.Output(cloudinit.OutAll); stdout == "" { w.conf.SetOutput(cloudinit.OutAll, ">> "+w.icfg.CloudInitOutputLog, "") w.conf.AddBootCmd(initProgressCmd) w.conf.AddBootCmd(cloudinit.LogProgressCmd("Logging to %s on the bootstrap machine", w.icfg.CloudInitOutputLog)) } w.conf.AddPackageCommands( w.icfg.AptProxySettings, w.icfg.AptMirror, w.icfg.EnableOSRefreshUpdate, w.icfg.EnableOSUpgrade, ) // Write out the normal proxy settings so that the settings are // sourced by bash, and ssh through that. w.conf.AddScripts( // We look to see if the proxy line is there already as // the manual provider may have had it already. The ubuntu // user may not exist. `([ ! -e /home/ubuntu/.profile ] || grep -q '.juju-proxy' /home/ubuntu/.profile) || ` + `printf '\n# Added by juju\n[ -f "$HOME/.juju-proxy" ] && . "$HOME/.juju-proxy"\n' >> /home/ubuntu/.profile`) if (w.icfg.ProxySettings != proxy.Settings{}) { exportedProxyEnv := w.icfg.ProxySettings.AsScriptEnvironment() w.conf.AddScripts(strings.Split(exportedProxyEnv, "\n")...) w.conf.AddScripts( fmt.Sprintf( `(id ubuntu &> /dev/null) && (printf '%%s\n' %s > /home/ubuntu/.juju-proxy && chown ubuntu:ubuntu /home/ubuntu/.juju-proxy)`, shquote(w.icfg.ProxySettings.AsScriptEnvironment()))) } if w.icfg.Controller != nil && w.icfg.Controller.PublicImageSigningKey != "" { keyFile := filepath.Join(agent.DefaultPaths.ConfDir, simplestreams.SimplestreamsPublicKeyFile) w.conf.AddRunTextFile(keyFile, w.icfg.Controller.PublicImageSigningKey, 0644) } // Make the lock dir and change the ownership of the lock dir itself to // ubuntu:ubuntu from root:root so the juju-run command run as the ubuntu // user is able to get access to the hook execution lock (like the uniter // itself does.) lockDir := path.Join(w.icfg.DataDir, "locks") w.conf.AddScripts( fmt.Sprintf("mkdir -p %s", lockDir), // We only try to change ownership if there is an ubuntu user defined. fmt.Sprintf("(id ubuntu &> /dev/null) && chown ubuntu:ubuntu %s", lockDir), fmt.Sprintf("mkdir -p %s", w.icfg.LogDir), w.setDataDirPermissions(), ) // Make a directory for the tools to live in. w.conf.AddScripts( "bin="+shquote(w.icfg.JujuTools()), "mkdir -p $bin", ) // Fetch the tools and unarchive them into it. if err := w.addDownloadToolsCmds(); err != nil { return errors.Trace(err) } // Don't remove tools tarball until after bootstrap agent // runs, so it has a chance to add it to its catalogue. defer w.conf.AddRunCmd( fmt.Sprintf("rm $bin/tools.tar.gz && rm $bin/juju%s.sha256", w.icfg.AgentVersion()), ) // We add the machine agent's configuration info // before running bootstrap-state so that bootstrap-state // has a chance to rerwrite it to change the password. // It would be cleaner to change bootstrap-state to // be responsible for starting the machine agent itself, // but this would not be backwardly compatible. machineTag := names.NewMachineTag(w.icfg.MachineId) _, err := w.addAgentInfo(machineTag) if err != nil { return errors.Trace(err) } // Add the cloud archive cloud-tools pocket to apt sources // for series that need it. This gives us up-to-date LXC, // MongoDB, and other infrastructure. // This is only done on ubuntu. if w.conf.SystemUpdate() && w.conf.RequiresCloudArchiveCloudTools() { w.conf.AddCloudArchiveCloudTools() } if w.icfg.Bootstrap != nil { if err := w.configureBootstrap(); err != nil { return errors.Trace(err) } } return w.addMachineAgentToBoot() }
// ConfigureJuju updates the provided cloudinit.Config with configuration // to initialise a Juju machine agent. func (w *unixConfigure) ConfigureJuju() error { if err := w.icfg.VerifyConfig(); err != nil { return err } // Initialise progress reporting. We need to do separately for runcmd // and (possibly, below) for bootcmd, as they may be run in different // shell sessions. initProgressCmd := cloudinit.InitProgressCmd() w.conf.AddRunCmd(initProgressCmd) // If we're doing synchronous bootstrap or manual provisioning, then // ConfigureBasic won't have been invoked; thus, the output log won't // have been set. We don't want to show the log to the user, so simply // append to the log file rather than teeing. if stdout, _ := w.conf.Output(cloudinit.OutAll); stdout == "" { w.conf.SetOutput(cloudinit.OutAll, ">> "+w.icfg.CloudInitOutputLog, "") w.conf.AddBootCmd(initProgressCmd) w.conf.AddBootCmd(cloudinit.LogProgressCmd("Logging to %s on remote host", w.icfg.CloudInitOutputLog)) } w.conf.AddPackageCommands( w.icfg.AptProxySettings, w.icfg.AptMirror, w.icfg.EnableOSRefreshUpdate, w.icfg.EnableOSUpgrade, ) // Write out the normal proxy settings so that the settings are // sourced by bash, and ssh through that. w.conf.AddScripts( // We look to see if the proxy line is there already as // the manual provider may have had it already. The ubuntu // user may not exist. `([ ! -e /home/ubuntu/.profile ] || grep -q '.juju-proxy' /home/ubuntu/.profile) || ` + `printf '\n# Added by juju\n[ -f "$HOME/.juju-proxy" ] && . "$HOME/.juju-proxy"\n' >> /home/ubuntu/.profile`) if (w.icfg.ProxySettings != proxy.Settings{}) { exportedProxyEnv := w.icfg.ProxySettings.AsScriptEnvironment() w.conf.AddScripts(strings.Split(exportedProxyEnv, "\n")...) w.conf.AddScripts( fmt.Sprintf( `(id ubuntu &> /dev/null) && (printf '%%s\n' %s > /home/ubuntu/.juju-proxy && chown ubuntu:ubuntu /home/ubuntu/.juju-proxy)`, shquote(w.icfg.ProxySettings.AsScriptEnvironment()))) } if w.icfg.PublicImageSigningKey != "" { keyFile := filepath.Join(agent.DefaultPaths.ConfDir, simplestreams.SimplestreamsPublicKeyFile) w.conf.AddRunTextFile(keyFile, w.icfg.PublicImageSigningKey, 0644) } // Make the lock dir and change the ownership of the lock dir itself to // ubuntu:ubuntu from root:root so the juju-run command run as the ubuntu // user is able to get access to the hook execution lock (like the uniter // itself does.) lockDir := path.Join(w.icfg.DataDir, "locks") w.conf.AddScripts( fmt.Sprintf("mkdir -p %s", lockDir), // We only try to change ownership if there is an ubuntu user defined. fmt.Sprintf("(id ubuntu &> /dev/null) && chown ubuntu:ubuntu %s", lockDir), fmt.Sprintf("mkdir -p %s", w.icfg.LogDir), w.setDataDirPermissions(), ) // Make a directory for the tools to live in. w.conf.AddScripts( "bin="+shquote(w.icfg.JujuTools()), "mkdir -p $bin", ) // Fetch the tools and unarchive them into it. if err := w.addDownloadToolsCmds(); err != nil { return errors.Trace(err) } // Don't remove tools tarball until after bootstrap agent // runs, so it has a chance to add it to its catalogue. defer w.conf.AddRunCmd( fmt.Sprintf("rm $bin/tools.tar.gz && rm $bin/juju%s.sha256", w.icfg.AgentVersion()), ) // We add the machine agent's configuration info // before running bootstrap-state so that bootstrap-state // has a chance to rerwrite it to change the password. // It would be cleaner to change bootstrap-state to // be responsible for starting the machine agent itself, // but this would not be backwardly compatible. machineTag := names.NewMachineTag(w.icfg.MachineId) _, err := w.addAgentInfo(machineTag) if err != nil { return errors.Trace(err) } // Add the cloud archive cloud-tools pocket to apt sources // for series that need it. This gives us up-to-date LXC, // MongoDB, and other infrastructure. // This is only done on ubuntu. if w.conf.SystemUpdate() && w.conf.RequiresCloudArchiveCloudTools() { w.conf.AddCloudArchiveCloudTools() } if w.icfg.Bootstrap { // Add the Juju GUI to the bootstrap node. cleanup, err := w.setUpGUI() if err != nil { return errors.Annotate(err, "cannot set up Juju GUI") } if cleanup != nil { defer cleanup() } var metadataDir string if len(w.icfg.CustomImageMetadata) > 0 { metadataDir = path.Join(w.icfg.DataDir, "simplestreams") index, products, err := imagemetadata.MarshalImageMetadataJSON(w.icfg.CustomImageMetadata, nil, time.Now()) if err != nil { return err } indexFile := path.Join(metadataDir, imagemetadata.IndexStoragePath()) productFile := path.Join(metadataDir, imagemetadata.ProductMetadataStoragePath()) w.conf.AddRunTextFile(indexFile, string(index), 0644) w.conf.AddRunTextFile(productFile, string(products), 0644) metadataDir = " --image-metadata " + shquote(metadataDir) } bootstrapCons := w.icfg.Constraints.String() if bootstrapCons != "" { bootstrapCons = " --bootstrap-constraints " + shquote(bootstrapCons) } modelCons := w.icfg.ModelConstraints.String() if modelCons != "" { modelCons = " --constraints " + shquote(modelCons) } var hardware string if w.icfg.HardwareCharacteristics != nil { if hardware = w.icfg.HardwareCharacteristics.String(); hardware != "" { hardware = " --hardware " + shquote(hardware) } } w.conf.AddRunCmd(cloudinit.LogProgressCmd("Bootstrapping Juju machine agent")) loggingOption := " --show-log" // If the bootstrap command was requsted with --debug, then the root // logger will be set to DEBUG. If it is, then we use --debug here too. if loggo.GetLogger("").LogLevel() == loggo.DEBUG { loggingOption = " --debug" } featureFlags := featureflag.AsEnvironmentValue() if featureFlags != "" { featureFlags = fmt.Sprintf("%s=%s ", osenv.JujuFeatureFlagEnvKey, featureFlags) } w.conf.AddScripts( // The bootstrapping is always run with debug on. featureFlags + w.icfg.JujuTools() + "/jujud bootstrap-state" + " --data-dir " + shquote(w.icfg.DataDir) + " --model-config " + shquote(base64yaml(w.icfg.Config.AllAttrs())) + " --hosted-model-config " + shquote(base64yaml(w.icfg.HostedModelConfig)) + " --instance-id " + shquote(string(w.icfg.InstanceId)) + hardware + bootstrapCons + modelCons + metadataDir + loggingOption, ) } return w.addMachineAgentToBoot() }