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