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