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