// addPackageCommands returns a slice of commands that, when run, // will add the required apt repositories and packages. func addPackageCommands(cfg *cloudinit.Config) ([]string, error) { // If apt_get_wrapper is specified, then prepend it to aptget. aptget := aptget wrapper := cfg.AptGetWrapper() switch wrapper.Enabled { case true: aptget = utils.ShQuote(wrapper.Command) + " " + aptget case "auto": aptget = fmt.Sprintf("$(which %s || true) %s", utils.ShQuote(wrapper.Command), aptget) } var cmds []string if len(cfg.AptSources()) > 0 { // Ensure add-apt-repository is available. cmds = append(cmds, cloudinit.LogProgressCmd("Installing add-apt-repository")) cmds = append(cmds, aptget+"install python-software-properties") } for _, src := range cfg.AptSources() { // PPA keys are obtained by add-apt-repository, from launchpad. if !strings.HasPrefix(src.Source, "ppa:") { if src.Key != "" { key := utils.ShQuote(src.Key) cmd := fmt.Sprintf("printf '%%s\\n' %s | apt-key add -", key) cmds = append(cmds, cmd) } } cmds = append(cmds, cloudinit.LogProgressCmd("Adding apt repository: %s", src.Source)) cmds = append(cmds, "add-apt-repository -y "+utils.ShQuote(src.Source)) if src.Prefs != nil { path := utils.ShQuote(src.Prefs.Path) contents := utils.ShQuote(src.Prefs.FileContents()) cmds = append(cmds, "install -D -m 644 /dev/null "+path) cmds = append(cmds, `printf '%s\n' `+contents+` > `+path) } } if len(cfg.AptSources()) > 0 || cfg.AptUpdate() { cmds = append(cmds, cloudinit.LogProgressCmd("Running apt-get update")) cmds = append(cmds, aptget+"update") } if cfg.AptUpgrade() { cmds = append(cmds, cloudinit.LogProgressCmd("Running apt-get upgrade")) cmds = append(cmds, aptget+"upgrade") } for _, pkg := range cfg.Packages() { cmds = append(cmds, cloudinit.LogProgressCmd("Installing package: %s", pkg)) if !strings.Contains(pkg, "--target-release") { // We only need to shquote the package name if it does not // contain additional arguments. pkg = utils.ShQuote(pkg) } cmd := fmt.Sprintf(aptget+"install %s", pkg) cmds = append(cmds, cmd) } if len(cmds) > 0 { // setting DEBIAN_FRONTEND=noninteractive prevents debconf // from prompting, always taking default values instead. cmds = append([]string{"export DEBIAN_FRONTEND=noninteractive"}, cmds...) } return cmds, nil }
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 (cfg *MachineConfig) addMachineAgentToBoot(c *cloudinit.Config, tag, machineId string) error { // Make the agent run via a symbolic link to the actual tools // directory, so it can upgrade itself without needing to change // the upstart script. toolsDir := agenttools.ToolsDir(cfg.DataDir, tag) // TODO(dfc) ln -nfs, so it doesn't fail if for some reason that the target already exists c.AddScripts(fmt.Sprintf("ln -s %v %s", cfg.Tools.Version, shquote(toolsDir))) name := cfg.MachineAgentServiceName conf := upstart.MachineAgentUpstartService(name, toolsDir, cfg.DataDir, cfg.LogDir, tag, machineId, nil) cmds, err := conf.InstallCommands() if err != nil { return errors.Annotatef(err, "cannot make cloud-init upstart script for the %s agent", tag) } c.AddRunCmd(cloudinit.LogProgressCmd("Starting Juju machine agent (%s)", name)) c.AddScripts(cmds...) return nil }
// ConfigureJuju updates the provided cloudinit.Config with configuration // to initialise a Juju machine agent. func ConfigureJuju(cfg *MachineConfig, c *cloudinit.Config) error { if err := verifyConfig(cfg); 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() c.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, _ := c.Output(cloudinit.OutAll); stdout == "" { c.SetOutput(cloudinit.OutAll, ">> "+cfg.CloudInitOutputLog, "") c.AddBootCmd(initProgressCmd) c.AddBootCmd(cloudinit.LogProgressCmd("Logging to %s on remote host", cfg.CloudInitOutputLog)) } if !cfg.DisablePackageCommands { AddAptCommands(cfg.AptProxySettings, c) } // Write out the normal proxy settings so that the settings are // sourced by bash, and ssh through that. c.AddScripts( // We look to see if the proxy line is there already as // the manual provider may have had it aleady. The ubuntu // user may not exist (local provider only). `([ ! -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 (cfg.ProxySettings != proxy.Settings{}) { exportedProxyEnv := cfg.ProxySettings.AsScriptEnvironment() c.AddScripts(strings.Split(exportedProxyEnv, "\n")...) c.AddScripts( fmt.Sprintf( `[ -e /home/ubuntu ] && (printf '%%s\n' %s > /home/ubuntu/.juju-proxy && chown ubuntu:ubuntu /home/ubuntu/.juju-proxy)`, shquote(cfg.ProxySettings.AsScriptEnvironment()))) } // 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(cfg.DataDir, "locks") c.AddScripts( fmt.Sprintf("mkdir -p %s", lockDir), // We only try to change ownership if there is an ubuntu user // defined, and we determine this by the existance of the home dir. fmt.Sprintf("[ -e /home/ubuntu ] && chown ubuntu:ubuntu %s", lockDir), fmt.Sprintf("mkdir -p %s", cfg.LogDir), fmt.Sprintf("chown syslog:adm %s", cfg.LogDir), ) // Make a directory for the tools to live in, then fetch the // tools and unarchive them into it. var copyCmd string if strings.HasPrefix(cfg.Tools.URL, fileSchemePrefix) { copyCmd = fmt.Sprintf("cp %s $bin/tools.tar.gz", shquote(cfg.Tools.URL[len(fileSchemePrefix):])) } else { curlCommand := "curl -sSfw 'tools from %{url_effective} downloaded: HTTP %{http_code}; time %{time_total}s; size %{size_download} bytes; speed %{speed_download} bytes/s '" if cfg.DisableSSLHostnameVerification { curlCommand += " --insecure" } copyCmd = fmt.Sprintf("%s -o $bin/tools.tar.gz %s", curlCommand, shquote(cfg.Tools.URL)) c.AddRunCmd(cloudinit.LogProgressCmd("Fetching tools: %s", copyCmd)) } toolsJson, err := json.Marshal(cfg.Tools) if err != nil { return err } c.AddScripts( "bin="+shquote(cfg.jujuTools()), "mkdir -p $bin", copyCmd, fmt.Sprintf("sha256sum $bin/tools.tar.gz > $bin/juju%s.sha256", cfg.Tools.Version), fmt.Sprintf(`grep '%s' $bin/juju%s.sha256 || (echo "Tools checksum mismatch"; exit 1)`, cfg.Tools.SHA256, cfg.Tools.Version), fmt.Sprintf("tar zxf $bin/tools.tar.gz -C $bin"), fmt.Sprintf("rm $bin/tools.tar.gz && rm $bin/juju%s.sha256", cfg.Tools.Version), fmt.Sprintf("printf %%s %s > $bin/downloaded-tools.txt", shquote(string(toolsJson))), ) // 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(cfg.MachineId).String() _, err = cfg.addAgentInfo(c, machineTag) if err != nil { return 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. if !cfg.DisablePackageCommands { series := cfg.Tools.Version.Series MaybeAddCloudArchiveCloudTools(c, series) } if cfg.Bootstrap { cons := cfg.Constraints.String() if cons != "" { cons = " --constraints " + shquote(cons) } var hardware string if cfg.HardwareCharacteristics != nil { if hardware = cfg.HardwareCharacteristics.String(); hardware != "" { hardware = " --hardware " + shquote(hardware) } } c.AddRunCmd(cloudinit.LogProgressCmd("Bootstrapping Juju machine agent")) c.AddScripts( // The bootstrapping is always run with debug on. cfg.jujuTools() + "/jujud bootstrap-state" + " --data-dir " + shquote(cfg.DataDir) + " --env-config " + shquote(base64yaml(cfg.Config)) + " --instance-id " + shquote(string(cfg.InstanceId)) + hardware + cons + " --debug", ) } return cfg.addMachineAgentToBoot(c, machineTag, cfg.MachineId) }
// ConfigureJuju updates the provided cloudinit.Config with configuration // to initialise a Juju machine agent. func (w *ubuntuConfigure) ConfigureJuju() error { if err := verifyConfig(w.mcfg); 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.mcfg.CloudInitOutputLog, "") w.conf.AddBootCmd(initProgressCmd) w.conf.AddBootCmd(cloudinit.LogProgressCmd("Logging to %s on remote host", w.mcfg.CloudInitOutputLog)) } AddAptCommands( w.mcfg.AptProxySettings, w.conf, w.mcfg.EnableOSRefreshUpdate, w.mcfg.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 aleady. The ubuntu // user may not exist (local provider only). `([ ! -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.mcfg.ProxySettings != proxy.Settings{}) { exportedProxyEnv := w.mcfg.ProxySettings.AsScriptEnvironment() w.conf.AddScripts(strings.Split(exportedProxyEnv, "\n")...) w.conf.AddScripts( fmt.Sprintf( `[ -e /home/ubuntu ] && (printf '%%s\n' %s > /home/ubuntu/.juju-proxy && chown ubuntu:ubuntu /home/ubuntu/.juju-proxy)`, shquote(w.mcfg.ProxySettings.AsScriptEnvironment()))) } // 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.mcfg.DataDir, "locks") w.conf.AddScripts( fmt.Sprintf("mkdir -p %s", lockDir), // We only try to change ownership if there is an ubuntu user // defined, and we determine this by the existance of the home dir. fmt.Sprintf("[ -e /home/ubuntu ] && chown ubuntu:ubuntu %s", lockDir), fmt.Sprintf("mkdir -p %s", w.mcfg.LogDir), fmt.Sprintf("chown syslog:adm %s", w.mcfg.LogDir), ) w.conf.AddScripts( "bin="+shquote(w.mcfg.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.mcfg.Tools.URL, fileSchemePrefix) { toolsData, err := ioutil.ReadFile(w.mcfg.Tools.URL[len(fileSchemePrefix):]) if err != nil { return err } w.conf.AddBinaryFile(path.Join(w.mcfg.jujuTools(), "tools.tar.gz"), []byte(toolsData), 0644) } else { curlCommand := curlCommand var urls []string if w.mcfg.Bootstrap { curlCommand += " --retry 10" if w.mcfg.DisableSSLHostnameVerification { curlCommand += " --insecure" } urls = append(urls, w.mcfg.Tools.URL) } else { for _, addr := range w.mcfg.apiHostAddrs() { // TODO(axw) encode env UUID in URL when EnvironTag // is guaranteed to be available in APIInfo. url := fmt.Sprintf("https://%s/tools/%s", addr, w.mcfg.Tools.Version) urls = append(urls, url) } // 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.mcfg.Tools) if err != nil { return err } w.conf.AddScripts( fmt.Sprintf("sha256sum $bin/tools.tar.gz > $bin/juju%s.sha256", w.mcfg.Tools.Version), fmt.Sprintf(`grep '%s' $bin/juju%s.sha256 || (echo "Tools checksum mismatch"; exit 1)`, w.mcfg.Tools.SHA256, w.mcfg.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.mcfg.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.mcfg.MachineId) _, err = addAgentInfo(w.mcfg, w.conf, machineTag, w.mcfg.Tools.Version.Number) if err != nil { return 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. if w.conf.AptUpdate() { MaybeAddCloudArchiveCloudTools(w.conf, w.mcfg.Tools.Version.Series) } if w.mcfg.Bootstrap { cons := w.mcfg.Constraints.String() if cons != "" { cons = " --constraints " + shquote(cons) } var hardware string if w.mcfg.HardwareCharacteristics != nil { if hardware = w.mcfg.HardwareCharacteristics.String(); hardware != "" { hardware = " --hardware " + shquote(hardware) } } w.conf.AddRunCmd(cloudinit.LogProgressCmd("Bootstrapping Juju machine agent")) w.conf.AddScripts( // The bootstrapping is always run with debug on. w.mcfg.jujuTools() + "/jujud bootstrap-state" + " --data-dir " + shquote(w.mcfg.DataDir) + " --env-config " + shquote(base64yaml(w.mcfg.Config)) + " --instance-id " + shquote(string(w.mcfg.InstanceId)) + hardware + cons + " --debug", ) } return w.addMachineAgentToBoot(machineTag.String()) }