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