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