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  })