github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  // RunConfigureScript connects to the specified host over
    42  // SSH, and executes the provided script which is expected
    43  // to have been returned by cloudinit ConfigureScript.
    44  func RunConfigureScript(script string, params ConfigureParams) error {
    45  	logger.Tracef("Running script on %s: %s", params.Host, script)
    46  
    47  	encoded := base64.StdEncoding.EncodeToString([]byte(`
    48  set -e
    49  tmpfile=$(mktemp)
    50  trap "rm -f $tmpfile" EXIT
    51  cat > $tmpfile
    52  /bin/bash $tmpfile
    53  `))
    54  
    55  	// bash will read a byte at a time when consuming commands
    56  	// from stdin. We avoid sending the entire script -- which
    57  	// will be very large when uploading tools -- directly to
    58  	// bash for this reason. Instead, run cat which will write
    59  	// the script to disk, and then execute it from there.
    60  	cmd := ssh.Command(params.Host, []string{
    61  		"sudo", "/bin/bash", "-c",
    62  		// The outer bash interprets the $(...), and executes
    63  		// the decoded script in the nested bash. This avoids
    64  		// linebreaks in the commandline, which the go.crypto-
    65  		// based client has trouble with.
    66  		fmt.Sprintf(
    67  			`/bin/bash -c "$(echo %s | base64 -d)"`,
    68  			utils.ShQuote(encoded),
    69  		),
    70  	}, nil)
    71  
    72  	cmd.Stdin = strings.NewReader(script)
    73  	cmd.Stderr = params.ProgressWriter
    74  	return cmd.Run()
    75  }