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  }