github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/integration/v7/isolated/ssh_command_test.go (about) 1 package isolated 2 3 import ( 4 "fmt" 5 "net/http" 6 "time" 7 8 . "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers" 9 10 "code.cloudfoundry.org/cli/integration/helpers" 11 . "github.com/onsi/ginkgo" 12 . "github.com/onsi/gomega" 13 . "github.com/onsi/gomega/gbytes" 14 . "github.com/onsi/gomega/gexec" 15 ) 16 17 var _ = Describe("ssh command", func() { 18 var ( 19 appName string 20 orgName string 21 spaceName string 22 ) 23 24 BeforeEach(func() { 25 helpers.SkipIfClientCredentialsTestMode() 26 27 appName = helpers.PrefixedRandomName("app") 28 orgName = helpers.NewOrgName() 29 spaceName = helpers.NewSpaceName() 30 }) 31 32 When("--help flag is set", func() { 33 It("appears in cf help -a", func() { 34 session := helpers.CF("help", "-a") 35 Eventually(session).Should(Exit(0)) 36 Expect(session).To(HaveCommandInCategoryWithDescription("ssh", "APPS", "SSH to an application container instance")) 37 }) 38 39 It("Displays command usage to output", func() { 40 session := helpers.CF("ssh", "--help") 41 42 Eventually(session).Should(Say(`NAME:`)) 43 Eventually(session).Should(Say(`ssh - SSH to an application container instance`)) 44 Eventually(session).Should(Say(`USAGE:`)) 45 Eventually(session).Should(Say(`cf ssh APP_NAME \[--process PROCESS\] \[-i INDEX\] \[-c COMMAND\]...\n`)) 46 Eventually(session).Should(Say(`\[-L \[BIND_ADDRESS:\]LOCAL_PORT:REMOTE_HOST:REMOTE_PORT\]\.\.\. \[--skip-remote-execution\]`)) 47 Eventually(session).Should(Say(`\[--disable-pseudo-tty \| --force-pseudo-tty \| --request-pseudo-tty\] \[--skip-host-validation\]`)) 48 Eventually(session).Should(Say(`OPTIONS:`)) 49 Eventually(session).Should(Say(`--app-instance-index, -i\s+App process instance index \(Default: 0\)`)) 50 Eventually(session).Should(Say(`--command, -c\s+Command to run`)) 51 Eventually(session).Should(Say(`--disable-pseudo-tty, -T\s+Disable pseudo-tty allocation`)) 52 Eventually(session).Should(Say(`--force-pseudo-tty\s+Force pseudo-tty allocation`)) 53 Eventually(session).Should(Say(`-L\s+Local port forward specification`)) 54 Eventually(session).Should(Say(`--process\s+App process name \(Default: web\)`)) 55 Eventually(session).Should(Say(`--request-pseudo-tty, -t\s+Request pseudo-tty allocation`)) 56 Eventually(session).Should(Say(`--skip-host-validation, -k\s+Skip host key validation\. Not recommended!`)) 57 Eventually(session).Should(Say(`--skip-remote-execution, -N\s+Do not execute a remote command`)) 58 Eventually(session).Should(Say(`ENVIRONMENT:`)) 59 Eventually(session).Should(Say(`all_proxy=\s+Specify a proxy server to enable proxying for all requests`)) 60 Eventually(session).Should(Say(`SEE ALSO:`)) 61 Eventually(session).Should(Say(`allow-space-ssh, enable-ssh, space-ssh-allowed, ssh-code, ssh-enabled`)) 62 Eventually(session).Should(Exit(0)) 63 }) 64 }) 65 66 When("the app name is not provided", func() { 67 It("tells the user that the app name is required, prints help text, and exits 1", func() { 68 session := helpers.CF("ssh") 69 70 Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `APP_NAME` was not provided")) 71 Eventually(session).Should(Say("NAME:")) 72 Eventually(session).Should(Exit(1)) 73 }) 74 }) 75 76 When("the environment is not setup correctly", func() { 77 It("fails with the appropriate errors", func() { 78 helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "ssh", appName) 79 }) 80 }) 81 82 When("the environment is setup correctly", func() { 83 BeforeEach(func() { 84 helpers.SetupCF(orgName, spaceName) 85 }) 86 87 AfterEach(func() { 88 helpers.QuickDeleteOrg(orgName) 89 }) 90 91 When("the app does not exist", func() { 92 It("it displays the app does not exist", func() { 93 session := helpers.CF("ssh", appName) 94 Eventually(session).Should(Say("FAILED")) 95 Eventually(session.Err).Should(Say("App '%s' not found", appName)) 96 Eventually(session).Should(Exit(1)) 97 }) 98 }) 99 100 When("the app exists", func() { 101 BeforeEach(func() { 102 helpers.WithProcfileApp(func(appDir string) { 103 Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "push", appName)).Should(Exit(0)) 104 }) 105 }) 106 107 Context("TTY Options", func() { 108 // * The columns specify the various TTY flags passed to cf ssh 109 // (--disable-pseudo-tty, --force-pseudo-tty, --request-pseudo-tty). 110 // * The rows specify what kind of shell you’re running "cf ssh" from. To 111 // simulate an interactive shell, simply use your terminal as always. 112 // To simulate a non-interactive shell, append "<< EOF <new-line> 113 // <command-to-execute-on-remote-host> <new-line> EOF" to your command 114 // * The values (yes/no) determine whether a TTY session should be 115 // allocated on the remote host. Verify by running "TTY" on remote host. 116 // 117 // TTY Option -> | Default(auto) | Disable | Force | Request 118 // Shell_Type__________________|_______________|_________|_______|_____________ 119 // interactive | Yes | No | Yes | Yes 120 // non-interactive | No | No | No | No 121 // interactive w/ commands | No | No | Yes | Yes 122 // non-interactive w/ commands | No | No | Yes | No 123 124 When("the running session is interactive", func() { 125 // This should be tested manually (launching an interactive shell in code is hard) 126 }) 127 128 When("the running session is non-interactive", func() { 129 When("providing commands to run on the remote host", func() { 130 When("using default tty option (auto)", func() { 131 It("the remote shell is not TTY", func() { 132 // we echo hello because a successful ssh call returns the status 133 session := helpers.CF("ssh", appName, "-c tty;", "-c echo hello") 134 Eventually(session).Should(Say("not a tty")) 135 Eventually(session).Should(Exit(0)) 136 }) 137 }) 138 139 When("disable-pseudo-tty is specified", func() { 140 It("the remote shell is not TTY", func() { 141 session := helpers.CF("ssh", appName, "--disable-pseudo-tty", "-c tty;", "-c echo hello") 142 Eventually(session).Should(Say("not a tty")) 143 Eventually(session).Should(Exit(0)) 144 }) 145 }) 146 147 When("force-pseudo-tty is specified", func() { 148 It("the remote shell is TTY", func() { 149 session := helpers.CF("ssh", appName, "--force-pseudo-tty", "-c tty;", "-c echo hello") 150 Eventually(session).ShouldNot(Say("not a tty")) 151 Eventually(session).Should(Say("/dev/*")) 152 Eventually(session).Should(Exit(0)) 153 }) 154 }) 155 156 When("request-pseudo-tty is specified", func() { 157 It("the remote shell is not TTY", func() { 158 session := helpers.CF("ssh", appName, "--request-pseudo-tty", "-c tty;", "-c echo hello") 159 Eventually(session).Should(Say("not a tty")) 160 Eventually(session).Should(Exit(0)) 161 }) 162 }) 163 }) 164 165 When("not providing commands as args", func() { 166 var buffer *Buffer 167 168 BeforeEach(func() { 169 buffer = NewBuffer() 170 }) 171 172 When("using default tty option (auto)", func() { 173 It("the remote shell is not TTY", func() { 174 _, err := buffer.Write([]byte("tty\n")) 175 Expect(err).ToNot(HaveOccurred()) 176 _, err = buffer.Write([]byte("echo hello\n")) 177 Expect(err).ToNot(HaveOccurred()) 178 _, err = buffer.Write([]byte("exit\n")) 179 Expect(err).ToNot(HaveOccurred()) 180 session := helpers.CFWithStdin(buffer, "ssh", appName) 181 Eventually(session).Should(Say("not a tty")) 182 Eventually(session).Should(Exit(0)) 183 }) 184 }) 185 186 When("disable-pseudo-tty is specified", func() { 187 It("the remote shell is not TTY", func() { 188 _, err := buffer.Write([]byte("tty\n")) 189 Expect(err).ToNot(HaveOccurred()) 190 191 _, err = buffer.Write([]byte("echo hello\n")) 192 Expect(err).ToNot(HaveOccurred()) 193 194 _, err = buffer.Write([]byte("exit\n")) 195 Expect(err).ToNot(HaveOccurred()) 196 197 session := helpers.CFWithStdin(buffer, "ssh", appName, "--disable-pseudo-tty") 198 Eventually(session).Should(Say("not a tty")) 199 Eventually(session).Should(Exit(0)) 200 }) 201 }) 202 203 When("force-pseudo-tty is specified", func() { 204 It("the remote shell is TTY", func() { 205 _, err := buffer.Write([]byte("tty\n")) 206 Expect(err).ToNot(HaveOccurred()) 207 208 _, err = buffer.Write([]byte("echo hello\n")) 209 Expect(err).ToNot(HaveOccurred()) 210 211 _, err = buffer.Write([]byte("exit\n")) 212 Expect(err).ToNot(HaveOccurred()) 213 session := helpers.CFWithStdin(buffer, "ssh", appName, "--force-pseudo-tty") 214 215 Eventually(session).ShouldNot(Say("not a tty")) 216 Eventually(session).Should(Say("/dev/*")) 217 Eventually(session).Should(Exit(0)) 218 }) 219 }) 220 221 When("request-pseudo-tty is specified", func() { 222 It("the remote shell is TTY", func() { 223 _, err := buffer.Write([]byte("tty\n")) 224 Expect(err).ToNot(HaveOccurred()) 225 226 _, err = buffer.Write([]byte("echo hello\n")) 227 Expect(err).ToNot(HaveOccurred()) 228 229 _, err = buffer.Write([]byte("exit\n")) 230 Expect(err).ToNot(HaveOccurred()) 231 232 session := helpers.CFWithStdin(buffer, "ssh", appName, "--request-pseudo-tty") 233 Eventually(session).Should(Say("not a tty")) 234 Eventually(session).Should(Exit(0)) 235 }) 236 }) 237 }) 238 }) 239 }) 240 241 It("ssh's to the process 'web', index '0'", func() { 242 session := helpers.CF("ssh", appName, "-c", "ps aux;", "-c", "env") 243 // To verify we ssh'd into the web process we examine processes 244 // that were launched that are unique to that process 245 Eventually(session).Should(Say("vcap.*ruby")) 246 Eventually(session).Should(Say("INSTANCE_INDEX=0")) 247 Eventually(session).Should(Exit(0)) 248 }) 249 250 When("commands to run are specified", func() { 251 It("ssh's to the default container and runs the commands", func() { 252 session := helpers.CF("ssh", appName, "-c", "ls;", "-c", "echo $USER") 253 Eventually(session).Should(Say("app")) 254 Eventually(session).Should(Say("deps")) 255 Eventually(session).Should(Say("logs")) 256 Eventually(session).Should(Say("vcap")) 257 Eventually(session).Should(Exit(0)) 258 }) 259 }) 260 261 When("the application hasn't started", func() { 262 BeforeEach(func() { 263 session := helpers.CF("stop", appName) 264 Eventually(session).Should(Exit(0)) 265 }) 266 267 It("prints an error message", func() { 268 session := helpers.CF("ssh", appName) 269 Eventually(session).Should(Say("FAILED")) 270 Eventually(session.Err).Should(Say(fmt.Sprintf("Application '%s' is not in the STARTED state", appName))) 271 Eventually(session).Should(Exit(1)) 272 }) 273 }) 274 275 When("the remote command exits with a different status code", func() { 276 It("exits with that status code", func() { 277 session := helpers.CF("ssh", appName, "-c", "asdf") 278 Eventually(session).Should(Exit(127)) 279 }) 280 }) 281 282 When("port forwarding is used", func() { 283 var port int 284 285 BeforeEach(func() { 286 port = 55500 + GinkgoParallelNode() 287 }) 288 289 It("configures local port to connect to the app port", func() { 290 session := helpers.CF("ssh", appName, "-N", "-L", fmt.Sprintf("%d:localhost:8080", port)) 291 292 time.Sleep(35 * time.Second) // Need to wait a few seconds for pipes to connect. 293 response, err := http.Get(fmt.Sprintf("http://localhost:%d/", port)) 294 Expect(err).ToNot(HaveOccurred()) 295 defer response.Body.Close() 296 297 Eventually(BufferReader(response.Body)).Should(Say("WEBrick")) 298 299 session.Kill() 300 Eventually(session).Should(Exit()) 301 }) 302 }) 303 304 When("a process is specified", func() { 305 When("the process does not exist", func() { 306 It("displays the process does not exist", func() { 307 session := helpers.CF("ssh", appName, "--process", "fake-process") 308 Eventually(session).Should(Say("FAILED")) 309 Eventually(session.Err).Should(Say("Process fake-process not found")) 310 Eventually(session).Should(Exit(1)) 311 }) 312 }) 313 314 When("the process exists", func() { 315 BeforeEach(func() { 316 Eventually(helpers.CF("scale", appName, "--process", "console", "-i", "1")).Should(Exit(0)) 317 }) 318 319 It("ssh's to the process's default index", func() { 320 session := helpers.CF("ssh", appName, "--process", "console", "-c", "ps aux;", "-c", "env") 321 Eventually(session).Should(Say("vcap.*irb")) 322 Eventually(session).Should(Say("INSTANCE_INDEX=0")) 323 Eventually(session).Should(Exit(0)) 324 }) 325 326 When("the index is specified", func() { 327 When("the index does not exist", func() { 328 It("returns an instance not found error", func() { 329 session := helpers.CF("ssh", appName, "--process", "console", "-i", "1", "-c", "ps aux;", "-c", "env") 330 Eventually(session).Should(Say("FAILED")) 331 Eventually(session.Err).Should(Say("Instance %d of process console not found", 1)) 332 Eventually(session).Should(Exit(1)) 333 }) 334 }) 335 336 When("the index exists", func() { 337 It("ssh's to the provided index", func() { 338 session := helpers.CF("ssh", appName, "--process", "console", "-i", "0", "-c", "ps aux;", "-c", "env") 339 Eventually(session).Should(Say("vcap.*irb")) 340 Eventually(session).Should(Say("INSTANCE_INDEX=0")) 341 Eventually(session).Should(Exit(0)) 342 }) 343 }) 344 }) 345 }) 346 }) 347 348 When("a user isn't authorized", func() { 349 var ( 350 newUser string 351 newPass string 352 ) 353 354 BeforeEach(func() { 355 newUser = helpers.NewUsername() 356 newPass = helpers.NewPassword() 357 358 Eventually(helpers.CF("create-user", newUser, newPass)).Should(Exit(0)) 359 Eventually(helpers.CF("set-space-role", newUser, orgName, spaceName, "SpaceAuditor")).Should(Exit(0)) 360 env := map[string]string{ 361 "CF_USERNAME": newUser, 362 "CF_PASSWORD": newPass, 363 } 364 Eventually(helpers.CFWithEnv(env, "auth")).Should(Exit(0)) 365 helpers.TargetOrgAndSpace(orgName, spaceName) 366 }) 367 368 AfterEach(func() { 369 helpers.LoginCF() 370 }) 371 372 It("returns an error", func() { 373 session := helpers.CF("ssh", appName) 374 375 Eventually(session.Err).Should(Say("Error opening SSH connection: You are not authorized to perform the requested action.")) 376 Eventually(session).Should(Exit(1)) 377 }) 378 }) 379 }) 380 }) 381 })