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