github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/commands/ssh_common_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"regexp"
    11  	"strings"
    12  
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils/ssh"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/juju/testing"
    18  	"github.com/juju/juju/network"
    19  	"github.com/juju/juju/state"
    20  )
    21  
    22  // argsSpec is a test helper which converts a number of options into
    23  // expected ssh/scp command lines.
    24  type argsSpec struct {
    25  	// hostKeyChecking specifies the expected StrictHostKeyChecking
    26  	// option.
    27  	hostKeyChecking string
    28  
    29  	// withProxy specifies if the juju ProxyCommand option is
    30  	// expected.
    31  	withProxy bool
    32  
    33  	// enablePty specifies if the forced PTY allocation switches are
    34  	// expected.
    35  	enablePty bool
    36  
    37  	// knownHosts may either be:
    38  	// a comma separated list of machine ids - the host keys for these
    39  	//    machines are expected in the UserKnownHostsFile
    40  	// "null" - the UserKnownHostsFile must be "/dev/null"
    41  	// empty - no UserKnownHostsFile option expected
    42  	knownHosts string
    43  
    44  	// args specifies any other command line arguments expected. This
    45  	// includes the SSH/SCP targets.
    46  	args string
    47  }
    48  
    49  func (s *argsSpec) check(c *gc.C, output string) {
    50  	// The first line in the output from the fake ssh/scp is the
    51  	// command line. The remaining lines should contain the contents
    52  	// of the UserKnownHostsFile file provided (if any).
    53  	parts := strings.SplitN(output, "\n", 2)
    54  	actualCommandLine := parts[0]
    55  	actualKnownHosts := ""
    56  	if len(parts) == 2 {
    57  		actualKnownHosts = parts[1]
    58  	}
    59  
    60  	var expected []string
    61  	expect := func(part string) {
    62  		expected = append(expected, part)
    63  	}
    64  	if s.hostKeyChecking != "" {
    65  		expect("-o StrictHostKeyChecking " + s.hostKeyChecking)
    66  	}
    67  
    68  	if s.withProxy {
    69  		expect("-o ProxyCommand juju ssh --proxy=false --no-host-key-checks " +
    70  			"--pty=false ubuntu@localhost -q \"nc %h %p\"")
    71  	}
    72  	expect("-o PasswordAuthentication no -o ServerAliveInterval 30")
    73  	if s.enablePty {
    74  		expect("-t -t")
    75  	}
    76  	if s.knownHosts == "null" {
    77  		expect(`-o UserKnownHostsFile /dev/null`)
    78  	} else if s.knownHosts == "" {
    79  		// No UserKnownHostsFile option expected.
    80  	} else {
    81  		expect(`-o UserKnownHostsFile \S+`)
    82  
    83  		// Check that the provided known_hosts file contained the
    84  		// expected keys.
    85  		c.Check(actualKnownHosts, gc.Matches, s.expectedKnownHosts())
    86  	}
    87  
    88  	// Check the command line matches what is expected.
    89  	pattern := "^" + strings.Join(expected, " ") + " " + regexp.QuoteMeta(s.args) + "$"
    90  	c.Check(actualCommandLine, gc.Matches, pattern)
    91  }
    92  
    93  func (s *argsSpec) expectedKnownHosts() string {
    94  	out := ""
    95  	for _, id := range strings.Split(s.knownHosts, ",") {
    96  		out += fmt.Sprintf(".+ dsa-%s\n.+ rsa-%s\n", id, id)
    97  	}
    98  	return out
    99  }
   100  
   101  type SSHCommonSuite struct {
   102  	testing.JujuConnSuite
   103  	knownHostsDir string
   104  	binDir        string
   105  }
   106  
   107  // Commands to patch
   108  var patchedCommands = []string{"ssh", "scp"}
   109  
   110  // fakecommand outputs its arguments to stdout for verification
   111  var fakecommand = `#!/bin/bash
   112  
   113  {
   114      echo "$@"
   115  
   116      # If a custom known_hosts file was passed, emit the contents of
   117      # that too.
   118      while (( "$#" )); do
   119          if [[ $1 = UserKnownHostsFile* ]]; then
   120              IFS=" " read -ra parts <<< $1
   121              cat "${parts[1]}"
   122              break
   123          fi
   124          shift
   125      done
   126  }| tee $0.args
   127  `
   128  
   129  func (s *SSHCommonSuite) SetUpTest(c *gc.C) {
   130  	s.JujuConnSuite.SetUpTest(c)
   131  	ssh.ClearClientKeys()
   132  	s.PatchValue(&getJujuExecutable, func() (string, error) { return "juju", nil })
   133  
   134  	s.binDir = c.MkDir()
   135  	s.PatchEnvPathPrepend(s.binDir)
   136  	for _, name := range patchedCommands {
   137  		f, err := os.OpenFile(filepath.Join(s.binDir, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
   138  		c.Assert(err, jc.ErrorIsNil)
   139  		_, err = f.Write([]byte(fakecommand))
   140  		c.Assert(err, jc.ErrorIsNil)
   141  		err = f.Close()
   142  		c.Assert(err, jc.ErrorIsNil)
   143  	}
   144  
   145  	client, _ := ssh.NewOpenSSHClient()
   146  	s.PatchValue(&ssh.DefaultClient, client)
   147  }
   148  
   149  func (s *SSHCommonSuite) setupModel(c *gc.C) {
   150  	// Add machine-0 with a mysql service and mysql/0 unit
   151  	u := s.Factory.MakeUnit(c, nil)
   152  
   153  	// Set addresses and keys for machine-0
   154  	m := s.getMachineForUnit(c, u)
   155  	s.setAddresses(c, m)
   156  	s.setKeys(c, m)
   157  
   158  	// machine-1 has no public host keys available.
   159  	m1 := s.Factory.MakeMachine(c, nil)
   160  	s.setAddresses(c, m1)
   161  
   162  	// machine-2 has IPv6 addresses
   163  	m2 := s.Factory.MakeMachine(c, nil)
   164  	s.setAddresses6(c, m2)
   165  	s.setKeys(c, m2)
   166  }
   167  
   168  func (s *SSHCommonSuite) getMachineForUnit(c *gc.C, u *state.Unit) *state.Machine {
   169  	machineId, err := u.AssignedMachineId()
   170  	c.Assert(err, jc.ErrorIsNil)
   171  	m, err := s.State.Machine(machineId)
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	return m
   174  }
   175  
   176  func (s *SSHCommonSuite) setAddresses(c *gc.C, m *state.Machine) {
   177  	addrPub := network.NewScopedAddress(
   178  		fmt.Sprintf("%s.public", m.Id()),
   179  		network.ScopePublic,
   180  	)
   181  	addrPriv := network.NewScopedAddress(
   182  		fmt.Sprintf("%s.private", m.Id()),
   183  		network.ScopeCloudLocal,
   184  	)
   185  	err := m.SetProviderAddresses(addrPub, addrPriv)
   186  	c.Assert(err, jc.ErrorIsNil)
   187  }
   188  
   189  func (s *SSHCommonSuite) setAddresses6(c *gc.C, m *state.Machine) {
   190  	addrPub := network.NewScopedAddress("2001:db8::1", network.ScopePublic)
   191  	addrPriv := network.NewScopedAddress("fc00:bbb::1", network.ScopeCloudLocal)
   192  	err := m.SetProviderAddresses(addrPub, addrPriv)
   193  	c.Assert(err, jc.ErrorIsNil)
   194  }
   195  
   196  func (s *SSHCommonSuite) setKeys(c *gc.C, m *state.Machine) {
   197  	id := m.Id()
   198  	keys := state.SSHHostKeys{"dsa-" + id, "rsa-" + id}
   199  	err := s.State.SetSSHHostKeys(m.MachineTag(), keys)
   200  	c.Assert(err, jc.ErrorIsNil)
   201  }