github.com/sleungcy-sap/cli@v7.1.0+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  })