github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 }