github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cloudconfig/sshinit/configure.go (about) 1 // Copyright 2013, 2015 Canonical Ltd. 2 // Copyright 2015 Cloudbase Solutions SRL 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package sshinit 6 7 import ( 8 "encoding/base64" 9 "fmt" 10 "io" 11 "strings" 12 13 "github.com/juju/loggo" 14 "github.com/juju/utils" 15 "github.com/juju/utils/ssh" 16 17 "github.com/juju/juju/cloudconfig/cloudinit" 18 ) 19 20 var logger = loggo.GetLogger("juju.cloudinit.sshinit") 21 22 type ConfigureParams struct { 23 // Host is the host to configure, in the format [user@]hostname. 24 Host string 25 26 // Client is the SSH client to connect with. 27 // If Client is nil, ssh.DefaultClient will be used. 28 Client ssh.Client 29 30 // Config is the cloudinit config to carry out. 31 Config cloudinit.CloudConfig 32 33 // ProgressWriter is an io.Writer to which progress will be written, 34 // for realtime feedback. 35 ProgressWriter io.Writer 36 37 // Series is the series of the machine on which the script will be carried out 38 Series string 39 } 40 41 // Configure connects to the specified host over SSH, 42 // and executes a script that carries out cloud-config. 43 // This isn't actually used anywhere because everybody wants to add custom stuff 44 // in between getting the script and actually running it 45 // I really suggest deleting it 46 func Configure(params ConfigureParams) error { 47 logger.Infof("Provisioning machine agent on %s", params.Host) 48 script, err := params.Config.RenderScript() 49 if err != nil { 50 return err 51 } 52 return RunConfigureScript(script, params) 53 } 54 55 // RunConfigureScript connects to the specified host over 56 // SSH, and executes the provided script which is expected 57 // to have been returned by cloudinit ConfigureScript. 58 func RunConfigureScript(script string, params ConfigureParams) error { 59 logger.Tracef("Running script on %s: %s", params.Host, script) 60 61 encoded := base64.StdEncoding.EncodeToString([]byte(` 62 set -e 63 tmpfile=$(mktemp) 64 trap "rm -f $tmpfile" EXIT 65 cat > $tmpfile 66 /bin/bash $tmpfile 67 `)) 68 69 // bash will read a byte at a time when consuming commands 70 // from stdin. We avoid sending the entire script -- which 71 // will be very large when uploading tools -- directly to 72 // bash for this reason. Instead, run cat which will write 73 // the script to disk, and then execute it from there. 74 cmd := ssh.Command(params.Host, []string{ 75 "sudo", "/bin/bash", "-c", 76 // The outer bash interprets the $(...), and executes 77 // the decoded script in the nested bash. This avoids 78 // linebreaks in the commandline, which the go.crypto- 79 // based client has trouble with. 80 fmt.Sprintf( 81 `/bin/bash -c "$(echo %s | base64 -d)"`, 82 utils.ShQuote(encoded), 83 ), 84 }, nil) 85 86 cmd.Stdin = strings.NewReader(script) 87 cmd.Stderr = params.ProgressWriter 88 return cmd.Run() 89 }