github.com/nimakaviani/cli@v6.37.1-0.20180619223813-e734901a73fa+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  	Context("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  		Context("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  		Context("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  			Context("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  				Context("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  				Context("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  				Context("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  				Context("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  				Context("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  				Context("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  				Context("when there is a command conflict", func() {
   286  					Context("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  					Context("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  					Context("when the plugin has a command that is the same as another plugin command", func() {
   331  						BeforeEach(func() {
   332  							helpers.InstallConfigurablePlugin("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  					Context("when the plugin has a command that is the same as another plugin alias", func() {
   358  						BeforeEach(func() {
   359  							helpers.InstallConfigurablePlugin("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  					Context("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  					Context("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  					Context("when the plugin has an alias that is the same as another plugin command", func() {
   432  						BeforeEach(func() {
   433  							helpers.InstallConfigurablePlugin("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  					Context("when the plugin has an alias that is the same as another plugin alias", func() {
   459  						BeforeEach(func() {
   460  							helpers.InstallConfigurablePlugin("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  					Context("when the plugin has a command and an alias that are both taken by another plugin", func() {
   488  						BeforeEach(func() {
   489  							helpers.InstallConfigurablePlugin("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  			Context("when the -f flag is not given", func() {
   517  				Context("when the user says yes", func() {
   518  					BeforeEach(func() {
   519  						buffer = NewBuffer()
   520  						_, _ = buffer.Write([]byte("y\n"))
   521  					})
   522  
   523  					It("installs the plugin", func() {
   524  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   525  
   526  						Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   527  						Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   528  						Eventually(session).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: y", helpers.ConvertPathToRegularExpression(pluginPath)))
   529  						Eventually(session).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   530  						Eventually(session).Should(Say("OK"))
   531  						Eventually(session).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   532  
   533  						Eventually(session).Should(Exit(0))
   534  
   535  						pluginsSession := helpers.CF("plugins", "--checksum")
   536  						expectedSha := helpers.Sha1Sum(
   537  							generic.ExecutableFilename(filepath.Join(homeDir, ".cf/plugins/some-plugin")))
   538  						Eventually(pluginsSession).Should(Say("some-plugin\\s+1.0.0\\s+%s", expectedSha))
   539  						Eventually(pluginsSession).Should(Exit(0))
   540  
   541  						Eventually(helpers.CF("some-command")).Should(Exit(0))
   542  
   543  						helpSession := helpers.CF("help")
   544  						Eventually(helpSession).Should(Say("some-command"))
   545  						Eventually(helpSession).Should(Exit(0))
   546  					})
   547  
   548  					Context("when the plugin is already installed", func() {
   549  						BeforeEach(func() {
   550  							Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   551  						})
   552  
   553  						It("fails and tells the user how to force a reinstall", func() {
   554  							session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   555  
   556  							Eventually(session).Should(Say("FAILED"))
   557  							Eventually(session.Err).Should(Say("Plugin some-plugin 1\\.0\\.0 could not be installed\\. A plugin with that name is already installed\\."))
   558  							Eventually(session.Err).Should(Say("TIP: Use 'cf install-plugin -f' to force a reinstall\\."))
   559  
   560  							Eventually(session).Should(Exit(1))
   561  						})
   562  					})
   563  				})
   564  
   565  				Context("when the user says no", func() {
   566  					BeforeEach(func() {
   567  						buffer = NewBuffer()
   568  						_, _ = buffer.Write([]byte("n\n"))
   569  					})
   570  
   571  					It("does not install the plugin", func() {
   572  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   573  
   574  						Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   575  						Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   576  						Eventually(session).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: n", helpers.ConvertPathToRegularExpression(pluginPath)))
   577  						Eventually(session).Should(Say("Plugin installation cancelled\\."))
   578  
   579  						Eventually(session).Should(Exit(0))
   580  					})
   581  
   582  					Context("when the plugin is already installed", func() {
   583  						BeforeEach(func() {
   584  							Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   585  						})
   586  
   587  						It("does not uninstall the existing plugin", func() {
   588  							session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   589  
   590  							Eventually(session).Should(Say("Plugin installation cancelled\\."))
   591  
   592  							Consistently(session).ShouldNot(Say("Plugin some-plugin 1\\.0\\.0 is already installed\\. Uninstalling existing plugin\\.\\.\\."))
   593  							Consistently(session).ShouldNot(Say("CLI-MESSAGE-UNINSTALL"))
   594  							Consistently(session).ShouldNot(Say("Plugin some-plugin successfully uninstalled\\."))
   595  
   596  							Eventually(session).Should(Exit(0))
   597  						})
   598  					})
   599  				})
   600  
   601  				Context("when the user interrupts with control-c", func() {
   602  					BeforeEach(func() {
   603  						buffer = NewBuffer()
   604  						_, _ = buffer.Write([]byte("y")) // but not enter
   605  					})
   606  
   607  					It("does not install the plugin and does not create a bad state", func() {
   608  						session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   609  
   610  						Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   611  						Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   612  						Eventually(session).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]:", helpers.ConvertPathToRegularExpression(pluginPath)))
   613  
   614  						session.Interrupt()
   615  
   616  						Eventually(session).Should(Say("FAILED"))
   617  
   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  						// make sure cf plugins did not break
   622  						Eventually(helpers.CF("plugins", "--checksum")).Should(Exit(0))
   623  
   624  						// make sure a retry of the plugin install works
   625  						retrySession := helpers.CF("install-plugin", pluginPath, "-f")
   626  						Eventually(retrySession).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   627  						Eventually(retrySession).Should(Exit(0))
   628  					})
   629  				})
   630  			})
   631  		})
   632  	})
   633  
   634  	Describe("installing a plugin from a URL", func() {
   635  		var (
   636  			server *Server
   637  		)
   638  
   639  		BeforeEach(func() {
   640  			server = NewTLSServer()
   641  			// Suppresses ginkgo server logs
   642  			server.HTTPTestServer.Config.ErrorLog = log.New(&bytes.Buffer{}, "", 0)
   643  		})
   644  
   645  		AfterEach(func() {
   646  			server.Close()
   647  		})
   648  
   649  		Context("when a URL and the -f flag are provided", func() {
   650  			Context("when an executable is available for download at the URL", func() {
   651  				var (
   652  					pluginData []byte
   653  				)
   654  
   655  				BeforeEach(func() {
   656  					pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   657  						[]helpers.PluginCommand{
   658  							{Name: "some-command", Help: "some-command-help"},
   659  						},
   660  					)
   661  
   662  					var err error
   663  					pluginData, err = ioutil.ReadFile(pluginPath)
   664  					Expect(err).ToNot(HaveOccurred())
   665  					server.AppendHandlers(
   666  						CombineHandlers(
   667  							VerifyRequest(http.MethodGet, "/"),
   668  							RespondWith(http.StatusOK, pluginData),
   669  						),
   670  					)
   671  				})
   672  
   673  				AfterEach(func() {
   674  					err := os.Remove(pluginPath)
   675  					Expect(err).ToNot(HaveOccurred())
   676  				})
   677  
   678  				It("installs the plugin", func() {
   679  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   680  
   681  					Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   682  					Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   683  
   684  					Eventually(session).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   685  					Eventually(session).Should(Say("\\d.* .*B / ?"))
   686  
   687  					Eventually(session).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   688  					Eventually(session).Should(Say("OK"))
   689  					Eventually(session).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   690  
   691  					Eventually(session).Should(Exit(0))
   692  				})
   693  
   694  				Context("when the URL redirects", func() {
   695  					BeforeEach(func() {
   696  						server.Reset()
   697  						server.AppendHandlers(
   698  							CombineHandlers(
   699  								VerifyRequest(http.MethodGet, "/redirect"),
   700  								RespondWith(http.StatusMovedPermanently, nil, http.Header{"Location": []string{server.URL()}}),
   701  							),
   702  							CombineHandlers(
   703  								VerifyRequest(http.MethodGet, "/"),
   704  								RespondWith(http.StatusOK, pluginData),
   705  							))
   706  					})
   707  
   708  					It("installs the plugin", func() {
   709  						session := helpers.CF("install-plugin", "-f", fmt.Sprintf("%s/redirect", server.URL()), "-k")
   710  
   711  						Eventually(session).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   712  						Eventually(session).Should(Say("OK"))
   713  						Eventually(session).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   714  
   715  						Eventually(session).Should(Exit(0))
   716  					})
   717  				})
   718  
   719  				Context("when the plugin has already been installed", func() {
   720  					BeforeEach(func() {
   721  						Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   722  					})
   723  
   724  					It("uninstalls and reinstalls the plugin", func() {
   725  						session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   726  
   727  						Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   728  						Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   729  
   730  						Eventually(session).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   731  						Eventually(session).Should(Say("\\d.* .*B / ?"))
   732  
   733  						Eventually(session).Should(Say("Plugin some-plugin 1\\.0\\.0 is already installed\\. Uninstalling existing plugin\\.\\.\\."))
   734  						Eventually(session).Should(Say("CLI-MESSAGE-UNINSTALL"))
   735  						Eventually(session).Should(Say("Plugin some-plugin successfully uninstalled\\."))
   736  						Eventually(session).Should(Say("OK"))
   737  						Eventually(session).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   738  
   739  						Eventually(session).Should(Exit(0))
   740  					})
   741  				})
   742  			})
   743  
   744  			Context("when a 4xx or 5xx HTTP response status is encountered", func() {
   745  				BeforeEach(func() {
   746  					server.AppendHandlers(
   747  						CombineHandlers(
   748  							VerifyRequest(http.MethodGet, "/"),
   749  							RespondWith(http.StatusNotFound, nil),
   750  						),
   751  					)
   752  				})
   753  
   754  				It("displays an appropriate error", func() {
   755  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   756  
   757  					Eventually(session).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   758  					Eventually(session).Should(Say("FAILED"))
   759  					Eventually(session.Err).Should(Say("Download attempt failed; server returned 404 Not Found"))
   760  					Eventually(session.Err).Should(Say("Unable to install; plugin is not available from the given URL\\."))
   761  
   762  					Eventually(session).Should(Exit(1))
   763  				})
   764  			})
   765  
   766  			Context("when the file is not a plugin", func() {
   767  				BeforeEach(func() {
   768  					var err error
   769  					pluginPath, err = Build("code.cloudfoundry.org/cli/integration/assets/non_plugin")
   770  					Expect(err).ToNot(HaveOccurred())
   771  
   772  					pluginData, err := ioutil.ReadFile(pluginPath)
   773  					Expect(err).ToNot(HaveOccurred())
   774  					server.AppendHandlers(
   775  						CombineHandlers(
   776  							VerifyRequest(http.MethodGet, "/"),
   777  							RespondWith(http.StatusOK, pluginData),
   778  						),
   779  					)
   780  				})
   781  
   782  				AfterEach(func() {
   783  					err := os.Remove(pluginPath)
   784  					Expect(err).ToNot(HaveOccurred())
   785  				})
   786  
   787  				It("tells the user that the file is not a plugin and fails", func() {
   788  					session := helpers.CF("install-plugin", "-f", server.URL(), "-k")
   789  
   790  					Eventually(session).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   791  					Eventually(session).Should(Say("FAILED"))
   792  					Eventually(session.Err).Should(Say("File is not a valid cf CLI plugin binary\\."))
   793  
   794  					Eventually(session).Should(Exit(1))
   795  				})
   796  			})
   797  		})
   798  
   799  		Context("when the -f flag is not provided", func() {
   800  			var (
   801  				pluginData []byte
   802  			)
   803  
   804  			BeforeEach(func() {
   805  				pluginPath = helpers.BuildConfigurablePlugin("configurable_plugin", "some-plugin", "1.0.0",
   806  					[]helpers.PluginCommand{
   807  						{Name: "some-command", Help: "some-command-help"},
   808  					},
   809  				)
   810  
   811  				var err error
   812  				pluginData, err = ioutil.ReadFile(pluginPath)
   813  				Expect(err).ToNot(HaveOccurred())
   814  				server.AppendHandlers(
   815  					CombineHandlers(
   816  						VerifyRequest(http.MethodGet, "/"),
   817  						RespondWith(http.StatusOK, pluginData),
   818  					),
   819  				)
   820  			})
   821  
   822  			AfterEach(func() {
   823  				err := os.Remove(pluginPath)
   824  				Expect(err).ToNot(HaveOccurred())
   825  			})
   826  
   827  			Context("when the user says yes", func() {
   828  				BeforeEach(func() {
   829  					buffer = NewBuffer()
   830  					_, _ = buffer.Write([]byte("y\n"))
   831  				})
   832  
   833  				It("installs the plugin", func() {
   834  					session := helpers.CFWithStdin(buffer, "install-plugin", server.URL(), "-k")
   835  
   836  					Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   837  					Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   838  					Eventually(session).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: y", server.URL()))
   839  
   840  					Eventually(session).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   841  					Eventually(session).Should(Say("\\d.* .*B / ?"))
   842  
   843  					Eventually(session).Should(Say("Installing plugin some-plugin\\.\\.\\."))
   844  					Eventually(session).Should(Say("OK"))
   845  					Eventually(session).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   846  
   847  					Eventually(session).Should(Exit(0))
   848  				})
   849  
   850  				Context("when the plugin is already installed", func() {
   851  					BeforeEach(func() {
   852  						Eventually(helpers.CF("install-plugin", pluginPath, "-f")).Should(Exit(0))
   853  					})
   854  
   855  					It("fails and tells the user how to force a reinstall", func() {
   856  						session := helpers.CFWithStdin(buffer, "install-plugin", server.URL(), "-k")
   857  
   858  						Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   859  						Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   860  						Eventually(session).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: y", server.URL()))
   861  
   862  						Eventually(session).Should(Say("Starting download of plugin binary from URL\\.\\.\\."))
   863  						Eventually(session).Should(Say("\\d.* .*B / ?"))
   864  
   865  						Eventually(session).Should(Say("FAILED"))
   866  						Eventually(session.Err).Should(Say("Plugin some-plugin 1\\.0\\.0 could not be installed\\. A plugin with that name is already installed\\."))
   867  						Eventually(session.Err).Should(Say("TIP: Use 'cf install-plugin -f' to force a reinstall\\."))
   868  						Eventually(session).Should(Exit(1))
   869  					})
   870  				})
   871  			})
   872  
   873  			Context("when the user says no", func() {
   874  				BeforeEach(func() {
   875  					buffer = NewBuffer()
   876  					_, _ = buffer.Write([]byte("n\n"))
   877  				})
   878  
   879  				It("does not install the plugin", func() {
   880  					session := helpers.CFWithStdin(buffer, "install-plugin", server.URL())
   881  
   882  					Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   883  					Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   884  					Eventually(session).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]: n", server.URL()))
   885  					Eventually(session).Should(Say("Plugin installation cancelled\\."))
   886  
   887  					Eventually(session).Should(Exit(0))
   888  
   889  					Expect(server.ReceivedRequests()).To(HaveLen(0))
   890  				})
   891  			})
   892  
   893  			Context("when the user interrupts with control-c", func() {
   894  				BeforeEach(func() {
   895  					buffer = NewBuffer()
   896  					_, _ = buffer.Write([]byte("y")) // but not enter
   897  				})
   898  
   899  				It("does not install the plugin and does not create a bad state", func() {
   900  					session := helpers.CFWithStdin(buffer, "install-plugin", pluginPath)
   901  
   902  					Eventually(session).Should(Say("Attention: Plugins are binaries written by potentially untrusted authors\\."))
   903  					Eventually(session).Should(Say("Install and use plugins at your own risk\\."))
   904  					Eventually(session).Should(Say("Do you want to install the plugin %s\\? \\[yN\\]:", helpers.ConvertPathToRegularExpression(pluginPath)))
   905  
   906  					session.Interrupt()
   907  
   908  					Eventually(session).Should(Say("FAILED"))
   909  
   910  					// There is a timing issue -- the exit code may be either 1 (processed error), 2 (config writing error), or 130 (Ctrl-C)
   911  					Eventually(session).Should(SatisfyAny(Exit(1), Exit(2), Exit(130)))
   912  
   913  					Expect(server.ReceivedRequests()).To(HaveLen(0))
   914  
   915  					// make sure cf plugins did not break
   916  					Eventually(helpers.CF("plugins", "--checksum")).Should(Exit(0))
   917  
   918  					// make sure a retry of the plugin install works
   919  					retrySession := helpers.CF("install-plugin", pluginPath, "-f")
   920  					Eventually(retrySession).Should(Say("Plugin some-plugin 1\\.0\\.0 successfully installed\\."))
   921  					Eventually(retrySession).Should(Exit(0))
   922  				})
   923  			})
   924  		})
   925  	})
   926  })