github.com/cloudfoundry/cli@v7.1.0+incompatible/integration/shared/plugin/install_plugin_command_test.go (about)

     1  package plugin
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"net/http"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"code.cloudfoundry.org/cli/integration/helpers"
    13  	"code.cloudfoundry.org/cli/util/generic"
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  	. "github.com/onsi/gomega/gbytes"
    17  	. "github.com/onsi/gomega/gexec"
    18  	. "github.com/onsi/gomega/ghttp"
    19  )
    20  
    21  var _ = Describe("install-plugin command", func() {
    22  	var (
    23  		buffer     *Buffer
    24  		pluginPath string
    25  	)
    26  
    27  	AfterEach(func() {
    28  		pluginsHomeDirContents, err := ioutil.ReadDir(filepath.Join(homeDir, ".cf", "plugins"))
    29  		if os.IsNotExist(err) {
    30  			return
    31  		}
    32  
    33  		Expect(err).ToNot(HaveOccurred())
    34  
    35  		for _, entry := range pluginsHomeDirContents {
    36  			Expect(entry.Name()).NotTo(ContainSubstring("temp"))
    37  		}
    38  	})
    39  
    40  	Describe("help", func() {
    41  		When("the --help flag is given", func() {
    42  			It("displays command usage to stdout", func() {
    43  				session := helpers.CF("install-plugin", "--help")
    44  
    45  				Eventually(session).Should(Say("NAME:"))
    46  				Eventually(session).Should(Say("install-plugin - Install CLI plugin"))
    47  				Eventually(session).Should(Say("USAGE:"))
    48  				Eventually(session).Should(Say(`cf install-plugin PLUGIN_NAME \[-r REPO_NAME\] \[-f\]`))
    49  				Eventually(session).Should(Say(`cf install-plugin LOCAL-PATH/TO/PLUGIN | URL \[-f\]`))
    50  				Eventually(session).Should(Say(""))
    51  				Eventually(session).Should(Say("WARNING:"))
    52  				Eventually(session).Should(Say("Plugins are binaries written by potentially untrusted authors."))
    53  				Eventually(session).Should(Say("Install and use plugins at your own risk."))
    54  				Eventually(session).Should(Say(""))
    55  				Eventually(session).Should(Say("EXAMPLES:"))
    56  				Eventually(session).Should(Say("cf install-plugin ~/Downloads/plugin-foobar"))
    57  				Eventually(session).Should(Say("cf install-plugin https://example.com/plugin-foobar_linux_amd64"))
    58  				Eventually(session).Should(Say("cf install-plugin -r My-Repo plugin-echo"))
    59  				Eventually(session).Should(Say("OPTIONS:"))
    60  				Eventually(session).Should(Say(`-f\s+Force install of plugin without confirmation`))
    61  				Eventually(session).Should(Say(`-r\s+Restrict search for plugin to this registered repository`))
    62  				Eventually(session).Should(Say("SEE ALSO:"))
    63  				Eventually(session).Should(Say("add-plugin-repo, list-plugin-repos, plugins"))
    64  
    65  				Eventually(session).Should(Exit(0))
    66  			})
    67  		})
    68  	})
    69  
    70  	When("the user does not provide a plugin name or location", func() {
    71  		It("errors and displays usage", func() {
    72  			session := helpers.CF("install-plugin")
    73  			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `PLUGIN_NAME_OR_LOCATION` was not provided"))
    74  			Eventually(session).Should(Say("USAGE:"))
    75  
    76  			Eventually(session).Should(Exit(1))
    77  		})
    78  	})
    79  
    80  	When("the plugin dir does not exist", func() {
    81  		var (
    82  			newPluginHome string
    83  
    84  			cfPluginHome string
    85  		)
    86  
    87  		BeforeEach(func() {
    88  			cfPluginHome = os.Getenv("CF_PLUGIN_HOME")
    89  
    90  			var err error
    91  			newPluginHome, err = ioutil.TempDir("", "plugin-temp-dir")
    92  			Expect(err).ToNot(HaveOccurred())
    93  			Expect(os.RemoveAll(newPluginHome))
    94  
    95  			Expect(os.Setenv("CF_PLUGIN_HOME", newPluginHome)).ToNot(HaveOccurred())
    96  
    97  			pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
    98  				[]helpers.PluginCommand{
    99  					{Name: "some-command", Help: "some-command-help"},
   100  				},
   101  			)
   102  		})
   103  
   104  		AfterEach(func() {
   105  			Expect(os.RemoveAll(newPluginHome)).ToNot(HaveOccurred())
   106  			Expect(os.Setenv("CF_PLUGIN_HOME", cfPluginHome)).ToNot(HaveOccurred())
   107  		})
   108  
   109  		It("creates the new directory, and continues as normal", func() {
   110  			session := helpers.CF("install-plugin", pluginPath, "-f")
   111  			Eventually(session).Should(Exit(0))
   112  
   113  			log.Println(newPluginHome)
   114  			_, err := os.Stat(newPluginHome)
   115  			Expect(os.IsNotExist(err)).To(Equal(false))
   116  		})
   117  	})
   118  
   119  	Describe("installing a plugin from a local file", func() {
   120  		When("the file is compiled for a different os and architecture", func() {
   121  			BeforeEach(func() {
   122  				goos := os.Getenv("GOOS")
   123  				goarch := os.Getenv("GOARCH")
   124  
   125  				err := os.Setenv("GOOS", "openbsd")
   126  				Expect(err).ToNot(HaveOccurred())
   127  				err = os.Setenv("GOARCH", "amd64")
   128  				Expect(err).ToNot(HaveOccurred())
   129  
   130  				pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   131  					[]helpers.PluginCommand{
   132  						{Name: "some-command", Help: "some-command-help"},
   133  					},
   134  				)
   135  
   136  				err = os.Setenv("GOOS", goos)
   137  				Expect(err).ToNot(HaveOccurred())
   138  				err = os.Setenv("GOARCH", goarch)
   139  				Expect(err).ToNot(HaveOccurred())
   140  			})
   141  
   142  			It("fails and reports the file is not a valid CLI plugin", func() {
   143  				session := helpers.CF("install-plugin", pluginPath, "-f")
   144  
   145  				Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   146  				Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   147  				Eventually(session).Should(Say("FAILED"))
   148  				Eventually(session.Err).Should(Say(`File is not a valid cf CLI plugin binary\.`))
   149  
   150  				Eventually(session).Should(Exit(1))
   151  			})
   152  		})
   153  
   154  		When("the file is compiled for the correct os and architecture", func() {
   155  			BeforeEach(func() {
   156  				pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   157  					[]helpers.PluginCommand{
   158  						{Name: "some-command", Help: "some-command-help"},
   159  					},
   160  				)
   161  			})
   162  
   163  			When("the -f flag is given", func() {
   164  				It("installs the plugin and cleans up all temp files", func() {
   165  					session := helpers.CF("install-plugin", pluginPath, "-f")
   166  
   167  					Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   168  					Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   169  					Eventually(session).Should(Say(`Installing plugin some-plugin\.\.\.`))
   170  					Eventually(session).Should(Say("OK"))
   171  					Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   172  
   173  					Eventually(session).Should(Exit(0))
   174  
   175  					installedPath := generic.ExecutableFilename(filepath.Join(homeDir, ".cf", "plugins", "some-plugin"))
   176  
   177  					pluginsSession := helpers.CF("plugins", "--checksum")
   178  					expectedSha := helpers.Sha1Sum(installedPath)
   179  
   180  					Eventually(pluginsSession).Should(Say(`some-plugin\s+1\.0\.0\s+%s`, expectedSha))
   181  					Eventually(pluginsSession).Should(Exit(0))
   182  
   183  					Eventually(helpers.CF("some-command")).Should(Exit(0))
   184  
   185  					helpSession := helpers.CF("help")
   186  					Eventually(helpSession).Should(Say("some-command"))
   187  					Eventually(helpSession).Should(Exit(0))
   188  				})
   189  
   190  				When("the file does not have executable permissions", func() {
   191  					BeforeEach(func() {
   192  						Expect(os.Chmod(pluginPath, 0666)).ToNot(HaveOccurred())
   193  					})
   194  
   195  					It("installs the plugin", func() {
   196  						session := helpers.CF("install-plugin", pluginPath, "-f")
   197  						Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   198  						Eventually(session).Should(Exit(0))
   199  					})
   200  				})
   201  
   202  				When("the plugin is already installed", func() {
   203  					BeforeEach(func() {
   204  						Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   205  					})
   206  
   207  					It("uninstalls the existing plugin and installs the plugin", func() {
   208  						session := helpers.CF("install-plugin", pluginPath, "-f")
   209  
   210  						Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 is already installed\. Uninstalling existing plugin\.\.\.`))
   211  						Eventually(session).Should(Say("CLI-MESSAGE-UNINSTALL"))
   212  						Eventually(session).Should(Say(`Plugin some-plugin successfully uninstalled\.`))
   213  						Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   214  
   215  						Eventually(session).Should(Exit(0))
   216  					})
   217  				})
   218  
   219  				When("the file does not exist", func() {
   220  					It("tells the user that the file was not found and fails", func() {
   221  						session := helpers.CF("install-plugin", "some/path/that/does/not/exist", "-f")
   222  						Eventually(session.Err).Should(Say(`Plugin some/path/that/does/not/exist not found on disk or in any registered repo\.`))
   223  						Eventually(session.Err).Should(Say(`Use 'cf repo-plugins' to list plugins available in the repos\.`))
   224  
   225  						Consistently(session).ShouldNot(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   226  						Consistently(session).ShouldNot(Say(`Install and use plugins at your own risk\.`))
   227  
   228  						Eventually(session).Should(Exit(1))
   229  					})
   230  				})
   231  
   232  				When("the file is not an executable", func() {
   233  					BeforeEach(func() {
   234  						badPlugin, err := ioutil.TempFile("", "")
   235  						Expect(err).ToNot(HaveOccurred())
   236  						pluginPath = badPlugin.Name()
   237  						err = badPlugin.Close()
   238  						Expect(err).ToNot(HaveOccurred())
   239  					})
   240  
   241  					AfterEach(func() {
   242  						err := os.Remove(pluginPath)
   243  						Expect(err).ToNot(HaveOccurred())
   244  					})
   245  
   246  					It("tells the user that the file is not a plugin and fails", func() {
   247  						session := helpers.CF("install-plugin", pluginPath, "-f")
   248  						Eventually(session.Err).Should(Say(`File is not a valid cf CLI plugin binary\.`))
   249  
   250  						Eventually(session).Should(Exit(1))
   251  					})
   252  				})
   253  
   254  				When("the file is not a plugin", func() {
   255  					BeforeEach(func() {
   256  						var err error
   257  						pluginPath, err = Build("code.cloudfoundry.org/cli/integration/assets/non_plugin")
   258  						Expect(err).ToNot(HaveOccurred())
   259  					})
   260  
   261  					It("tells the user that the file is not a plugin and fails", func() {
   262  						session := helpers.CF("install-plugin", pluginPath, "-f")
   263  						Eventually(session.Err).Should(Say(`File is not a valid cf CLI plugin binary\.`))
   264  
   265  						Eventually(session).Should(Exit(1))
   266  					})
   267  				})
   268  
   269  				When("getting metadata from the plugin errors", func() {
   270  					BeforeEach(func() {
   271  						var err error
   272  						pluginPath, err = Build("code.cloudfoundry.org/cli/integration/assets/test_plugin_fails_metadata")
   273  						Expect(err).ToNot(HaveOccurred())
   274  					})
   275  
   276  					It("displays the error to stderr", func() {
   277  						session := helpers.CF("install-plugin", pluginPath, "-f")
   278  						Eventually(session.Err).Should(Say("exit status 51"))
   279  						Eventually(session.Err).Should(Say(`File is not a valid cf CLI plugin binary\.`))
   280  
   281  						Eventually(session).Should(Exit(1))
   282  					})
   283  				})
   284  
   285  				When("there is a command conflict", func() {
   286  					When("the plugin has a command that is the same as a built-in command", func() {
   287  						BeforeEach(func() {
   288  							pluginPath = helpers.BuildConfigurablePlugin(
   289  								"configurable_plugin", "some-plugin", "1.1.1",
   290  								[]helpers.PluginCommand{
   291  									{Name: "version"},
   292  								})
   293  						})
   294  
   295  						It("tells the user about the conflict and fails", func() {
   296  							session := helpers.CF("install-plugin", "-f", pluginPath)
   297  
   298  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   299  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   300  
   301  							Eventually(session).Should(Say("FAILED"))
   302  							Eventually(session.Err).Should(Say(`Plugin some-plugin v1\.1\.1 could not be installed as it contains commands with names that are already used: version`))
   303  
   304  							Eventually(session).Should(Exit(1))
   305  						})
   306  					})
   307  
   308  					When("the plugin has a command that is the same as a built-in alias", func() {
   309  						BeforeEach(func() {
   310  							pluginPath = helpers.BuildConfigurablePlugin(
   311  								"configurable_plugin", "some-plugin", "1.1.1",
   312  								[]helpers.PluginCommand{
   313  									{Name: "cups"},
   314  								})
   315  						})
   316  
   317  						It("tells the user about the conflict and fails", func() {
   318  							session := helpers.CF("install-plugin", "-f", pluginPath)
   319  
   320  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   321  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   322  
   323  							Eventually(session).Should(Say("FAILED"))
   324  							Eventually(session.Err).Should(Say(`Plugin some-plugin v1\.1\.1 could not be installed as it contains commands with names that are already used: cups`))
   325  
   326  							Eventually(session).Should(Exit(1))
   327  						})
   328  					})
   329  
   330  					When("the plugin has a command that is the same as another plugin command", func() {
   331  						BeforeEach(func() {
   332  							helpers.InstallConfigurablePlugin("configurable_plugin", "existing-plugin", "1.1.1",
   333  								[]helpers.PluginCommand{
   334  									{Name: "existing-command"},
   335  								})
   336  
   337  							pluginPath = helpers.BuildConfigurablePlugin(
   338  								"configurable_plugin", "new-plugin", "1.1.1",
   339  								[]helpers.PluginCommand{
   340  									{Name: "existing-command"},
   341  								})
   342  						})
   343  
   344  						It("tells the user about the conflict and fails", func() {
   345  							session := helpers.CF("install-plugin", "-f", pluginPath)
   346  
   347  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   348  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   349  
   350  							Eventually(session).Should(Say("FAILED"))
   351  							Eventually(session.Err).Should(Say(`Plugin new-plugin v1\.1\.1 could not be installed as it contains commands with names that are already used: existing-command\.`))
   352  
   353  							Eventually(session).Should(Exit(1))
   354  						})
   355  					})
   356  
   357  					When("the plugin has a command that is the same as another plugin alias", func() {
   358  						BeforeEach(func() {
   359  							helpers.InstallConfigurablePlugin("configurable_plugin", "existing-plugin", "1.1.1",
   360  								[]helpers.PluginCommand{
   361  									{Name: "existing-command"},
   362  								})
   363  
   364  							pluginPath = helpers.BuildConfigurablePlugin(
   365  								"configurable_plugin", "new-plugin", "1.1.1",
   366  								[]helpers.PluginCommand{
   367  									{Name: "new-command", Alias: "existing-command"},
   368  								})
   369  						})
   370  
   371  						It("tells the user about the conflict and fails", func() {
   372  							session := helpers.CF("install-plugin", "-f", pluginPath)
   373  
   374  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   375  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   376  
   377  							Eventually(session).Should(Say("FAILED"))
   378  							Eventually(session.Err).Should(Say(`Plugin new-plugin v1\.1\.1 could not be installed as it contains commands with aliases that are already used: existing-command\.`))
   379  
   380  							Eventually(session).Should(Exit(1))
   381  						})
   382  					})
   383  				})
   384  
   385  				Context("alias conflict", func() {
   386  					When("the plugin has an alias that is the same as a built-in command", func() {
   387  
   388  						BeforeEach(func() {
   389  							pluginPath = helpers.BuildConfigurablePlugin(
   390  								"configurable_plugin", "some-plugin", "1.1.1",
   391  								[]helpers.PluginCommand{
   392  									{Name: "some-command", Alias: "version"},
   393  								})
   394  						})
   395  
   396  						It("tells the user about the conflict and fails", func() {
   397  							session := helpers.CF("install-plugin", "-f", pluginPath)
   398  
   399  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   400  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   401  
   402  							Eventually(session).Should(Say("FAILED"))
   403  							Eventually(session.Err).Should(Say(`Plugin some-plugin v1\.1\.1 could not be installed as it contains commands with aliases that are already used: version`))
   404  
   405  							Eventually(session).Should(Exit(1))
   406  						})
   407  					})
   408  
   409  					When("the plugin has an alias that is the same as a built-in alias", func() {
   410  						BeforeEach(func() {
   411  							pluginPath = helpers.BuildConfigurablePlugin(
   412  								"configurable_plugin", "some-plugin", "1.1.1",
   413  								[]helpers.PluginCommand{
   414  									{Name: "some-command", Alias: "cups"},
   415  								})
   416  						})
   417  
   418  						It("tells the user about the conflict and fails", func() {
   419  							session := helpers.CF("install-plugin", "-f", pluginPath)
   420  
   421  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   422  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   423  
   424  							Eventually(session).Should(Say("FAILED"))
   425  							Eventually(session.Err).Should(Say(`Plugin some-plugin v1\.1\.1 could not be installed as it contains commands with aliases that are already used: cups`))
   426  
   427  							Eventually(session).Should(Exit(1))
   428  						})
   429  					})
   430  
   431  					When("the plugin has an alias that is the same as another plugin command", func() {
   432  						BeforeEach(func() {
   433  							helpers.InstallConfigurablePlugin("configurable_plugin", "existing-plugin", "1.1.1",
   434  								[]helpers.PluginCommand{
   435  									{Name: "existing-command"},
   436  								})
   437  
   438  							pluginPath = helpers.BuildConfigurablePlugin(
   439  								"configurable_plugin", "new-plugin", "1.1.1",
   440  								[]helpers.PluginCommand{
   441  									{Name: "new-command", Alias: "existing-command"},
   442  								})
   443  						})
   444  
   445  						It("tells the user about the conflict and fails", func() {
   446  							session := helpers.CF("install-plugin", "-f", pluginPath)
   447  
   448  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   449  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   450  
   451  							Eventually(session).Should(Say("FAILED"))
   452  							Eventually(session.Err).Should(Say(`Plugin new-plugin v1\.1\.1 could not be installed as it contains commands with aliases that are already used: existing-command\.`))
   453  
   454  							Eventually(session).Should(Exit(1))
   455  						})
   456  					})
   457  
   458  					When("the plugin has an alias that is the same as another plugin alias", func() {
   459  						BeforeEach(func() {
   460  							helpers.InstallConfigurablePlugin("configurable_plugin", "existing-plugin", "1.1.1",
   461  								[]helpers.PluginCommand{
   462  									{Name: "existing-command", Alias: "existing-alias"},
   463  								})
   464  
   465  							pluginPath = helpers.BuildConfigurablePlugin(
   466  								"configurable_plugin", "new-plugin", "1.1.1",
   467  								[]helpers.PluginCommand{
   468  									{Name: "new-command", Alias: "existing-alias"},
   469  								})
   470  						})
   471  
   472  						It("tells the user about the conflict and fails", func() {
   473  							session := helpers.CF("install-plugin", "-f", pluginPath)
   474  
   475  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   476  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   477  
   478  							Eventually(session).Should(Say("FAILED"))
   479  							Eventually(session.Err).Should(Say(`Plugin new-plugin v1\.1\.1 could not be installed as it contains commands with aliases that are already used: existing-alias\.`))
   480  
   481  							Eventually(session).Should(Exit(1))
   482  						})
   483  					})
   484  				})
   485  
   486  				Context("alias and command conflicts", func() {
   487  					When("the plugin has a command and an alias that are both taken by another plugin", func() {
   488  						BeforeEach(func() {
   489  							helpers.InstallConfigurablePlugin("configurable_plugin", "existing-plugin", "1.1.1",
   490  								[]helpers.PluginCommand{
   491  									{Name: "existing-command", Alias: "existing-alias"},
   492  								})
   493  
   494  							pluginPath = helpers.BuildConfigurablePlugin(
   495  								"configurable_plugin", "new-plugin", "1.1.1",
   496  								[]helpers.PluginCommand{
   497  									{Name: "existing-command", Alias: "existing-alias"},
   498  								})
   499  						})
   500  
   501  						It("tells the user about the conflict and fails", func() {
   502  							session := helpers.CF("install-plugin", "-f", pluginPath)
   503  
   504  							Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   505  							Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   506  
   507  							Eventually(session).Should(Say("FAILED"))
   508  							Eventually(session.Err).Should(Say(`Plugin new-plugin v1\.1\.1 could not be installed as it contains commands with names and aliases that are already used: existing-command, existing-alias\.`))
   509  
   510  							Eventually(session).Should(Exit(1))
   511  						})
   512  					})
   513  				})
   514  			})
   515  
   516  			When("the -f flag is not given", func() {
   517  				When("the user says yes", func() {
   518  					BeforeEach(func() {
   519  						buffer = NewBuffer()
   520  						_, err := buffer.Write([]byte("y\n"))
   521  						Expect(err).ToNot(HaveOccurred())
   522  					})
   523  
   524  					It("installs the plugin", func() {
   525  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   526  
   527  						Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   528  						Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   529  						Eventually(session).Should(Say(`Do you want to install the plugin %s\? \[yN\]: y`, helpers.ConvertPathToRegularExpression(pluginPath)))
   530  						Eventually(session).Should(Say(`Installing plugin some-plugin\.\.\.`))
   531  						Eventually(session).Should(Say("OK"))
   532  						Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   533  
   534  						Eventually(session).Should(Exit(0))
   535  
   536  						pluginsSession := helpers.CF("plugins", "--checksum")
   537  						expectedSha := helpers.Sha1Sum(
   538  							generic.ExecutableFilename(filepath.Join(homeDir, ".cf/plugins/some-plugin")))
   539  						Eventually(pluginsSession).Should(Say(`some-plugin\s+1.0.0\s+%s`, expectedSha))
   540  						Eventually(pluginsSession).Should(Exit(0))
   541  
   542  						Eventually(helpers.CF("some-command")).Should(Exit(0))
   543  
   544  						helpSession := helpers.CF("help")
   545  						Eventually(helpSession).Should(Say("some-command"))
   546  						Eventually(helpSession).Should(Exit(0))
   547  					})
   548  
   549  					When("the plugin is already installed", func() {
   550  						BeforeEach(func() {
   551  							Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   552  						})
   553  
   554  						It("fails and tells the user how to force a reinstall", func() {
   555  							session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   556  
   557  							Eventually(session).Should(Say("FAILED"))
   558  							Eventually(session.Err).Should(Say(`Plugin some-plugin 1\.0\.0 could not be installed\. A plugin with that name is already installed\.`))
   559  							Eventually(session.Err).Should(Say(`TIP: Use 'cf install-plugin -f' to force a reinstall\.`))
   560  
   561  							Eventually(session).Should(Exit(1))
   562  						})
   563  					})
   564  				})
   565  
   566  				When("the user says no", func() {
   567  					BeforeEach(func() {
   568  						buffer = NewBuffer()
   569  						_, err := buffer.Write([]byte("n\n"))
   570  						Expect(err).ToNot(HaveOccurred())
   571  					})
   572  
   573  					It("does not install the plugin", func() {
   574  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   575  
   576  						Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   577  						Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   578  						Eventually(session).Should(Say(`Do you want to install the plugin %s\? \[yN\]: n`, helpers.ConvertPathToRegularExpression(pluginPath)))
   579  						Eventually(session).Should(Say(`Plugin installation cancelled\.`))
   580  
   581  						Eventually(session).Should(Exit(0))
   582  					})
   583  
   584  					When("the plugin is already installed", func() {
   585  						BeforeEach(func() {
   586  							Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   587  						})
   588  
   589  						It("does not uninstall the existing plugin", func() {
   590  							session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   591  
   592  							Eventually(session).Should(Say(`Plugin installation cancelled\.`))
   593  
   594  							Consistently(session).ShouldNot(Say(`Plugin some-plugin 1\.0\.0 is already installed\. Uninstalling existing plugin\.\.\.`))
   595  							Consistently(session).ShouldNot(Say("CLI-MESSAGE-UNINSTALL"))
   596  							Consistently(session).ShouldNot(Say(`Plugin some-plugin successfully uninstalled\.`))
   597  
   598  							Eventually(session).Should(Exit(0))
   599  						})
   600  					})
   601  				})
   602  
   603  				When("the user interrupts with control-c", func() {
   604  					BeforeEach(func() {
   605  						buffer = NewBuffer()
   606  						_, err := buffer.Write([]byte("y")) // but not enter
   607  						Expect(err).ToNot(HaveOccurred())
   608  					})
   609  
   610  					It("does not install the plugin and does not create a bad state", func() {
   611  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   612  
   613  						Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   614  						Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   615  						Eventually(session).Should(Say(`Do you want to install the plugin %s\? \[yN\]:`, helpers.ConvertPathToRegularExpression(pluginPath)))
   616  
   617  						session.Interrupt()
   618  						// There is a timing issue -- the exit code may be either 1 (processed error), 2 (config writing error), or 130 (Ctrl-C)
   619  						Eventually(session).Should(SatisfyAny(Exit(1), Exit(2), Exit(130)))
   620  
   621  						Expect(session).Should(Say("FAILED"))
   622  
   623  						// make sure cf plugins did not break
   624  						Eventually(helpers.CF("plugins", "--checksum")).Should(Exit(0))
   625  
   626  						// make sure a retry of the plugin install works
   627  						retrySession := helpers.CF("install-plugin", pluginPath, "-f")
   628  						Eventually(retrySession).Should(Exit(0))
   629  						Expect(retrySession).To(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   630  					})
   631  				})
   632  			})
   633  		})
   634  	})
   635  
   636  	Describe("installing a plugin from a URL", func() {
   637  		var (
   638  			server *Server
   639  		)
   640  
   641  		BeforeEach(func() {
   642  			server = NewTLSServer()
   643  			// Suppresses ginkgo server logs
   644  			server.HTTPTestServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   645  		})
   646  
   647  		AfterEach(func() {
   648  			server.Close()
   649  		})
   650  
   651  		When("a URL and the -f flag are provided", func() {
   652  			When("an executable is available for download at the URL", func() {
   653  				var (
   654  					pluginData []byte
   655  				)
   656  
   657  				BeforeEach(func() {
   658  					pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   659  						[]helpers.PluginCommand{
   660  							{Name: "some-command", Help: "some-command-help"},
   661  						},
   662  					)
   663  
   664  					var err error
   665  					pluginData, err = ioutil.ReadFile(pluginPath)
   666  					Expect(err).ToNot(HaveOccurred())
   667  					server.AppendHandlers(
   668  						CombineHandlers(
   669  							VerifyRequest(http.MethodGet, "/"),
   670  							RespondWith(http.StatusOK, pluginData),
   671  						),
   672  					)
   673  				})
   674  
   675  				AfterEach(func() {
   676  					err := os.Remove(pluginPath)
   677  					Expect(err).ToNot(HaveOccurred())
   678  				})
   679  
   680  				It("installs the plugin", func() {
   681  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   682  
   683  					Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   684  					Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   685  
   686  					Eventually(session).Should(Say(`Starting download of plugin binary from URL\.\.\.`))
   687  					Eventually(session).Should(Say(`\d.* .*B / ?`))
   688  
   689  					Eventually(session).Should(Say(`Installing plugin some-plugin\.\.\.`))
   690  					Eventually(session).Should(Say("OK"))
   691  					Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   692  
   693  					Eventually(session).Should(Exit(0))
   694  				})
   695  
   696  				When("the URL redirects", func() {
   697  					BeforeEach(func() {
   698  						server.Reset()
   699  						server.AppendHandlers(
   700  							CombineHandlers(
   701  								VerifyRequest(http.MethodGet, "/redirect"),
   702  								RespondWith(http.StatusMovedPermanently, nil, http.Header{"Location": []string{server.URL()}}),
   703  							),
   704  							CombineHandlers(
   705  								VerifyRequest(http.MethodGet, "/"),
   706  								RespondWith(http.StatusOK, pluginData),
   707  							))
   708  					})
   709  
   710  					It("installs the plugin", func() {
   711  						session := helpers.CF("install-plugin", "-f", fmt.Sprintf("%s/redirect", server.URL()), "-k")
   712  
   713  						Eventually(session).Should(Say(`Installing plugin some-plugin\.\.\.`))
   714  						Eventually(session).Should(Say("OK"))
   715  						Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   716  
   717  						Eventually(session).Should(Exit(0))
   718  					})
   719  				})
   720  
   721  				When("the plugin has already been installed", func() {
   722  					BeforeEach(func() {
   723  						Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   724  					})
   725  
   726  					It("uninstalls and reinstalls the plugin", func() {
   727  						session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   728  
   729  						Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   730  						Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   731  
   732  						Eventually(session).Should(Say(`Starting download of plugin binary from URL\.\.\.`))
   733  						Eventually(session).Should(Say(`\d.* .*B / ?`))
   734  
   735  						Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 is already installed\. Uninstalling existing plugin\.\.\.`))
   736  						Eventually(session).Should(Say("CLI-MESSAGE-UNINSTALL"))
   737  						Eventually(session).Should(Say(`Plugin some-plugin successfully uninstalled\.`))
   738  						Eventually(session).Should(Say("OK"))
   739  						Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   740  
   741  						Eventually(session).Should(Exit(0))
   742  					})
   743  				})
   744  			})
   745  
   746  			When("a 4xx or 5xx HTTP response status is encountered", func() {
   747  				BeforeEach(func() {
   748  					server.AppendHandlers(
   749  						CombineHandlers(
   750  							VerifyRequest(http.MethodGet, "/"),
   751  							RespondWith(http.StatusNotFound, nil),
   752  						),
   753  					)
   754  				})
   755  
   756  				It("displays an appropriate error", func() {
   757  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   758  
   759  					Eventually(session).Should(Say(`Starting download of plugin binary from URL\.\.\.`))
   760  					Eventually(session).Should(Say("FAILED"))
   761  					Eventually(session.Err).Should(Say("Download attempt failed; server returned 404 Not Found"))
   762  					Eventually(session.Err).Should(Say(`Unable to install; plugin is not available from the given URL\.`))
   763  
   764  					Eventually(session).Should(Exit(1))
   765  				})
   766  			})
   767  
   768  			When("the file is not a plugin", func() {
   769  				BeforeEach(func() {
   770  					var err error
   771  					pluginPath, err = Build("code.cloudfoundry.org/cli/integration/assets/non_plugin")
   772  					Expect(err).ToNot(HaveOccurred())
   773  
   774  					pluginData, err := ioutil.ReadFile(pluginPath)
   775  					Expect(err).ToNot(HaveOccurred())
   776  					server.AppendHandlers(
   777  						CombineHandlers(
   778  							VerifyRequest(http.MethodGet, "/"),
   779  							RespondWith(http.StatusOK, pluginData),
   780  						),
   781  					)
   782  				})
   783  
   784  				AfterEach(func() {
   785  					err := os.Remove(pluginPath)
   786  					Expect(err).ToNot(HaveOccurred())
   787  				})
   788  
   789  				It("tells the user that the file is not a plugin and fails", func() {
   790  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   791  
   792  					Eventually(session).Should(Say(`Starting download of plugin binary from URL\.\.\.`))
   793  					Eventually(session).Should(Say("FAILED"))
   794  					Eventually(session.Err).Should(Say(`File is not a valid cf CLI plugin binary\.`))
   795  
   796  					Eventually(session).Should(Exit(1))
   797  				})
   798  			})
   799  		})
   800  
   801  		When("the -f flag is not provided", func() {
   802  			var (
   803  				pluginData []byte
   804  			)
   805  
   806  			BeforeEach(func() {
   807  				pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   808  					[]helpers.PluginCommand{
   809  						{Name: "some-command", Help: "some-command-help"},
   810  					},
   811  				)
   812  
   813  				var err error
   814  				pluginData, err = ioutil.ReadFile(pluginPath)
   815  				Expect(err).ToNot(HaveOccurred())
   816  				server.AppendHandlers(
   817  					CombineHandlers(
   818  						VerifyRequest(http.MethodGet, "/"),
   819  						RespondWith(http.StatusOK, pluginData),
   820  					),
   821  				)
   822  			})
   823  
   824  			AfterEach(func() {
   825  				err := os.Remove(pluginPath)
   826  				Expect(err).ToNot(HaveOccurred())
   827  			})
   828  
   829  			When("the user says yes", func() {
   830  				BeforeEach(func() {
   831  					buffer = NewBuffer()
   832  					_, err := buffer.Write([]byte("y\n"))
   833  					Expect(err).ToNot(HaveOccurred())
   834  				})
   835  
   836  				It("installs the plugin", func() {
   837  					session := helpers.CFWithStdin(buffer, "install-plugin", server.URL(), "-k")
   838  
   839  					Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   840  					Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   841  					Eventually(session).Should(Say(`Do you want to install the plugin %s\? \[yN\]: y`, server.URL()))
   842  
   843  					Eventually(session).Should(Say(`Starting download of plugin binary from URL\.\.\.`))
   844  					Eventually(session).Should(Say(`\d.* .*B / ?`))
   845  
   846  					Eventually(session).Should(Say(`Installing plugin some-plugin\.\.\.`))
   847  					Eventually(session).Should(Say("OK"))
   848  					Eventually(session).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   849  
   850  					Eventually(session).Should(Exit(0))
   851  				})
   852  
   853  				When("the plugin is already installed", func() {
   854  					BeforeEach(func() {
   855  						Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   856  					})
   857  
   858  					It("fails and tells the user how to force a reinstall", func() {
   859  						session := helpers.CFWithStdin(buffer, "install-plugin", server.URL(), "-k")
   860  
   861  						Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   862  						Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   863  						Eventually(session).Should(Say(`Do you want to install the plugin %s\? \[yN\]: y`, server.URL()))
   864  
   865  						Eventually(session).Should(Say(`Starting download of plugin binary from URL\.\.\.`))
   866  						Eventually(session).Should(Say(`\d.* .*B / ?`))
   867  
   868  						Eventually(session).Should(Say("FAILED"))
   869  						Eventually(session.Err).Should(Say(`Plugin some-plugin 1\.0\.0 could not be installed\. A plugin with that name is already installed\.`))
   870  						Eventually(session.Err).Should(Say(`TIP: Use 'cf install-plugin -f' to force a reinstall\.`))
   871  						Eventually(session).Should(Exit(1))
   872  					})
   873  				})
   874  			})
   875  
   876  			When("the user says no", func() {
   877  				BeforeEach(func() {
   878  					buffer = NewBuffer()
   879  					_, err := buffer.Write([]byte("n\n"))
   880  					Expect(err).ToNot(HaveOccurred())
   881  				})
   882  
   883  				It("does not install the plugin", func() {
   884  					session := helpers.CFWithStdin(buffer, "install-plugin", server.URL())
   885  
   886  					Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   887  					Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   888  					Eventually(session).Should(Say(`Do you want to install the plugin %s\? \[yN\]: n`, server.URL()))
   889  					Eventually(session).Should(Say(`Plugin installation cancelled\.`))
   890  
   891  					Eventually(session).Should(Exit(0))
   892  
   893  					Expect(server.ReceivedRequests()).To(HaveLen(0))
   894  				})
   895  			})
   896  
   897  			When("the user interrupts with control-c", func() {
   898  				BeforeEach(func() {
   899  					buffer = NewBuffer()
   900  					_, err := buffer.Write([]byte("y")) // but not enter
   901  					Expect(err).ToNot(HaveOccurred())
   902  				})
   903  
   904  				It("does not install the plugin and does not create a bad state", func() {
   905  					session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   906  
   907  					Eventually(session).Should(Say(`Attention: Plugins are binaries written by potentially untrusted authors\.`))
   908  					Eventually(session).Should(Say(`Install and use plugins at your own risk\.`))
   909  					Eventually(session).Should(Say(`Do you want to install the plugin %s\? \[yN\]:`, helpers.ConvertPathToRegularExpression(pluginPath)))
   910  
   911  					session.Interrupt()
   912  
   913  					// There is a timing issue -- the exit code may be either 1 (processed error), 2 (config writing error), or 130 (Ctrl-C)
   914  					Eventually(session).Should(SatisfyAny(Exit(1), Exit(2), Exit(130)))
   915  
   916  					Expect(session).To(Say("FAILED"))
   917  
   918  					Expect(server.ReceivedRequests()).To(HaveLen(0))
   919  
   920  					// make sure cf plugins did not break
   921  					Eventually(helpers.CF("plugins", "--checksum")).Should(Exit(0))
   922  
   923  					// make sure a retry of the plugin install works
   924  					retrySession := helpers.CF("install-plugin", pluginPath, "-f")
   925  					Eventually(retrySession).Should(Say(`Plugin some-plugin 1\.0\.0 successfully installed\.`))
   926  					Eventually(retrySession).Should(Exit(0))
   927  				})
   928  			})
   929  		})
   930  	})
   931  })