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