github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 // SSHOptions contains options for running the SSH command. 31 SSHOptions *ssh.Options 32 33 // Config is the cloudinit config to carry out. 34 Config cloudinit.CloudConfig 35 36 // ProgressWriter is an io.Writer to which progress will be written, 37 // for realtime feedback. 38 ProgressWriter io.Writer 39 40 // Series is the series of the machine on which the script will be carried out 41 Series string 42 } 43 44 // RunConfigureScript connects to the specified host over 45 // SSH, and executes the provided script which is expected 46 // to have been returned by cloudinit ConfigureScript. 47 func RunConfigureScript(script string, params ConfigureParams) error { 48 logger.Tracef("Running script on %s: %s", params.Host, script) 49 50 encoded := base64.StdEncoding.EncodeToString([]byte(` 51 set -e 52 tmpfile=$(mktemp) 53 trap "rm -f $tmpfile" EXIT 54 cat > $tmpfile 55 /bin/bash $tmpfile 56 `)) 57 58 client := params.Client 59 if client == nil { 60 client = ssh.DefaultClient 61 } 62 63 // bash will read a byte at a time when consuming commands 64 // from stdin. We avoid sending the entire script -- which 65 // will be very large when uploading tools -- directly to 66 // bash for this reason. Instead, run cat which will write 67 // the script to disk, and then execute it from there. 68 cmd := client.Command(params.Host, []string{ 69 "sudo", "/bin/bash", "-c", 70 // The outer bash interprets the $(...), and executes 71 // the decoded script in the nested bash. This avoids 72 // linebreaks in the commandline, which the go.crypto- 73 // based client has trouble with. 74 fmt.Sprintf( 75 `/bin/bash -c "$(echo %s | base64 -d)"`, 76 utils.ShQuote(encoded), 77 ), 78 }, params.SSHOptions) 79 80 cmd.Stdin = strings.NewReader(script) 81 cmd.Stderr = params.ProgressWriter 82 return cmd.Run() 83 }