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  }