github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/commands/ssh_unix_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Copyright 2014 Cloudbase Solutions SRL 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 // +build !windows 6 7 package commands 8 9 import ( 10 "fmt" 11 "reflect" 12 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/apiserver" 17 coretesting "github.com/juju/juju/testing" 18 ) 19 20 type SSHSuite struct { 21 SSHCommonSuite 22 } 23 24 var _ = gc.Suite(&SSHSuite{}) 25 26 var sshTests = []struct { 27 about string 28 args []string 29 expected argsSpec 30 expectedErr string 31 }{ 32 { 33 about: "connect to machine 0", 34 args: []string{"0"}, 35 expected: argsSpec{ 36 hostKeyChecking: "yes", 37 knownHosts: "0", 38 enablePty: true, 39 args: "ubuntu@0.public", 40 }, 41 }, 42 { 43 about: "connect to machine 0 and pass extra arguments", 44 args: []string{"0", "uname", "-a"}, 45 expected: argsSpec{ 46 hostKeyChecking: "yes", 47 knownHosts: "0", 48 enablePty: true, 49 args: "ubuntu@0.public uname -a", 50 }, 51 }, 52 { 53 about: "connect to machine 0 with no pseudo-tty", 54 args: []string{"--pty=false", "0"}, 55 expected: argsSpec{ 56 hostKeyChecking: "yes", 57 knownHosts: "0", 58 enablePty: false, 59 args: "ubuntu@0.public", 60 }, 61 }, 62 { 63 about: "connect to machine 1 which has no SSH host keys", 64 args: []string{"1"}, 65 expectedErr: `retrieving SSH host keys for "1": keys not found`, 66 }, 67 { 68 about: "connect to machine 1 which has no SSH host keys, no host key checks", 69 args: []string{"--no-host-key-checks", "1"}, 70 expected: argsSpec{ 71 hostKeyChecking: "no", 72 knownHosts: "null", 73 enablePty: true, 74 args: "ubuntu@1.public", 75 }, 76 }, 77 { 78 about: "connect to arbitrary (non-entity) hostname", 79 args: []string{"foo@some.host"}, 80 expected: argsSpec{ 81 // In this case, use the user's own known_hosts and own 82 // StrictHostKeyChecking config. 83 hostKeyChecking: "", 84 knownHosts: "", 85 enablePty: true, 86 args: "foo@some.host", 87 }, 88 }, 89 { 90 about: "connect to unit mysql/0", 91 args: []string{"mysql/0"}, 92 expected: argsSpec{ 93 hostKeyChecking: "yes", 94 knownHosts: "0", 95 enablePty: true, 96 args: "ubuntu@0.public", 97 }, 98 }, 99 { 100 about: "connect to unit mysql/0 as the mongo user", 101 args: []string{"mongo@mysql/0"}, 102 expected: argsSpec{ 103 hostKeyChecking: "yes", 104 knownHosts: "0", 105 enablePty: true, 106 args: "mongo@0.public", 107 }, 108 }, 109 { 110 about: "connect to unit mysql/0 and pass extra arguments", 111 args: []string{"mysql/0", "ls", "/"}, 112 expected: argsSpec{ 113 hostKeyChecking: "yes", 114 knownHosts: "0", 115 enablePty: true, 116 args: "ubuntu@0.public ls /", 117 }, 118 }, 119 { 120 about: "connect to unit mysql/0 with proxy", 121 args: []string{"--proxy=true", "mysql/0"}, 122 expected: argsSpec{ 123 hostKeyChecking: "yes", 124 knownHosts: "0", 125 enablePty: true, 126 withProxy: true, 127 args: "ubuntu@0.private", 128 }, 129 }, 130 } 131 132 func (s *SSHSuite) TestSSHCommand(c *gc.C) { 133 s.setupModel(c) 134 135 for i, t := range sshTests { 136 c.Logf("test %d: %s -> %s", i, t.about, t.args) 137 138 ctx, err := coretesting.RunCommand(c, newSSHCommand(), t.args...) 139 if t.expectedErr != "" { 140 c.Check(err, gc.ErrorMatches, t.expectedErr) 141 } else { 142 c.Check(err, jc.ErrorIsNil) 143 c.Check(coretesting.Stderr(ctx), gc.Equals, "") 144 stdout := coretesting.Stdout(ctx) 145 t.expected.check(c, stdout) 146 } 147 } 148 } 149 150 func (s *SSHSuite) TestSSHCommandModelConfigProxySSH(c *gc.C) { 151 s.setupModel(c) 152 153 // Setting proxy-ssh=true in the environment overrides --proxy. 154 err := s.State.UpdateModelConfig(map[string]interface{}{"proxy-ssh": true}, nil, nil) 155 c.Assert(err, jc.ErrorIsNil) 156 157 ctx, err := coretesting.RunCommand(c, newSSHCommand(), "0") 158 c.Check(err, jc.ErrorIsNil) 159 c.Check(coretesting.Stderr(ctx), gc.Equals, "") 160 expectedArgs := argsSpec{ 161 hostKeyChecking: "yes", 162 knownHosts: "0", 163 enablePty: true, 164 withProxy: true, 165 args: "ubuntu@0.private", 166 } 167 expectedArgs.check(c, coretesting.Stdout(ctx)) 168 } 169 170 func (s *SSHSuite) TestSSHWillWorkInUpgrade(c *gc.C) { 171 // Check the API client interface used by "juju ssh" against what 172 // the API server will allow during upgrades. Ensure that the API 173 // server will allow all required API calls to support SSH. 174 type concrete struct { 175 sshAPIClient 176 } 177 t := reflect.TypeOf(concrete{}) 178 for i := 0; i < t.NumMethod(); i++ { 179 name := t.Method(i).Name 180 181 // Close isn't an API method. 182 if name == "Close" { 183 continue 184 } 185 c.Logf("checking %q", name) 186 c.Check(apiserver.IsMethodAllowedDuringUpgrade("SSHClient", name), jc.IsTrue) 187 } 188 } 189 190 func (s *SSHSuite) TestSSHCommandHostAddressRetry(c *gc.C) { 191 s.testSSHCommandHostAddressRetry(c, false) 192 } 193 194 func (s *SSHSuite) TestSSHCommandHostAddressRetryProxy(c *gc.C) { 195 s.testSSHCommandHostAddressRetry(c, true) 196 } 197 198 func (s *SSHSuite) testSSHCommandHostAddressRetry(c *gc.C, proxy bool) { 199 m := s.Factory.MakeMachine(c, nil) 200 s.setKeys(c, m) 201 202 called := 0 203 attemptStarter := &callbackAttemptStarter{next: func() bool { 204 called++ 205 return called < 2 206 }} 207 s.PatchValue(&sshHostFromTargetAttemptStrategy, attemptStarter) 208 209 // Ensure that the ssh command waits for a public address, or the attempt 210 // strategy's Done method returns false. 211 args := []string{"--proxy=" + fmt.Sprint(proxy), "0"} 212 _, err := coretesting.RunCommand(c, newSSHCommand(), args...) 213 c.Assert(err, gc.ErrorMatches, "no .+ address") 214 c.Assert(called, gc.Equals, 2) 215 216 called = 0 217 attemptStarter.next = func() bool { 218 called++ 219 if called > 1 { 220 s.setAddresses(c, m) 221 } 222 return true 223 } 224 225 _, err = coretesting.RunCommand(c, newSSHCommand(), args...) 226 c.Assert(err, jc.ErrorIsNil) 227 c.Assert(called, gc.Equals, 2) 228 } 229 230 type callbackAttemptStarter struct { 231 next func() bool 232 } 233 234 func (s *callbackAttemptStarter) Start() attempt { 235 return callbackAttempt{next: s.next} 236 } 237 238 type callbackAttempt struct { 239 next func() bool 240 } 241 242 func (a callbackAttempt) Next() bool { 243 return a.next() 244 }