github.com/rakutentech/cli@v6.12.5-0.20151006231303-24468b65536e+incompatible/cf/commands/plugin/install_plugin.go (about)

     1  package plugin
     2  
     3  import (
     4  	"fmt"
     5  	"net/rpc"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  
    10  	"github.com/cloudfoundry/cli/cf/actors/plugin_installer"
    11  	"github.com/cloudfoundry/cli/cf/actors/plugin_repo"
    12  	"github.com/cloudfoundry/cli/cf/command_registry"
    13  	"github.com/cloudfoundry/cli/cf/configuration/core_config"
    14  	"github.com/cloudfoundry/cli/cf/configuration/plugin_config"
    15  	. "github.com/cloudfoundry/cli/cf/i18n"
    16  	"github.com/cloudfoundry/cli/cf/requirements"
    17  	"github.com/cloudfoundry/cli/cf/terminal"
    18  	"github.com/cloudfoundry/cli/fileutils"
    19  	"github.com/cloudfoundry/cli/flags"
    20  	"github.com/cloudfoundry/cli/flags/flag"
    21  	"github.com/cloudfoundry/cli/plugin"
    22  	"github.com/cloudfoundry/cli/utils"
    23  
    24  	rpcService "github.com/cloudfoundry/cli/plugin/rpc"
    25  )
    26  
    27  type PluginInstall struct {
    28  	ui           terminal.UI
    29  	config       core_config.Reader
    30  	pluginConfig plugin_config.PluginConfiguration
    31  	pluginRepo   plugin_repo.PluginRepo
    32  	checksum     utils.Sha1Checksum
    33  	rpcService   *rpcService.CliRpcService
    34  }
    35  
    36  func init() {
    37  	command_registry.Register(&PluginInstall{})
    38  }
    39  
    40  func (cmd *PluginInstall) MetaData() command_registry.CommandMetadata {
    41  	fs := make(map[string]flags.FlagSet)
    42  	fs["r"] = &cliFlags.StringFlag{Name: "r", Usage: T("repo name where the plugin binary is located")}
    43  	fs["f"] = &cliFlags.BoolFlag{Name: "f", Usage: T("Force install of plugin without prompt")}
    44  
    45  	return command_registry.CommandMetadata{
    46  		Name:        "install-plugin",
    47  		Description: T("Install the plugin defined in command argument"),
    48  		Usage: T(`CF_NAME install-plugin URL or LOCAL-PATH/TO/PLUGIN [-r REPO_NAME] [-f]
    49  
    50  The command will download the plugin binary from repository if '-r' is provided
    51  Prompts for confirmation unless '-f' is provided
    52  
    53  EXAMPLE:
    54     cf install-plugin https://github.com/cf-experimental/plugin-foobar
    55     cf install-plugin ~/Downloads/plugin-foobar
    56     cf install-plugin plugin-echo -r My-Repo 
    57  `),
    58  		Flags:     fs,
    59  		TotalArgs: 1,
    60  	}
    61  }
    62  
    63  func (cmd *PluginInstall) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) (reqs []requirements.Requirement, err error) {
    64  	if len(fc.Args()) != 1 {
    65  		cmd.ui.Failed(T("Incorrect Usage. Requires an argument\n\n") + command_registry.Commands.CommandUsage("install-plugin"))
    66  	}
    67  
    68  	return
    69  }
    70  
    71  func (cmd *PluginInstall) SetDependency(deps command_registry.Dependency, pluginCall bool) command_registry.Command {
    72  	cmd.ui = deps.Ui
    73  	cmd.config = deps.Config
    74  	cmd.pluginConfig = deps.PluginConfig
    75  	cmd.pluginRepo = deps.PluginRepo
    76  	cmd.checksum = deps.ChecksumUtil
    77  
    78  	//reset rpc registration in case there is other running instance,
    79  	//each service can only be registered once
    80  	rpc.DefaultServer = rpc.NewServer()
    81  
    82  	rpcService, err := rpcService.NewRpcService(deps.TeePrinter, deps.TeePrinter, deps.Config, deps.RepoLocator, rpcService.NewNonCodegangstaRunner())
    83  	if err != nil {
    84  		cmd.ui.Failed("Error initializing RPC service: " + err.Error())
    85  	}
    86  
    87  	cmd.rpcService = rpcService
    88  
    89  	return cmd
    90  }
    91  
    92  func (cmd *PluginInstall) Execute(c flags.FlagContext) {
    93  	if !cmd.confirmWithUser(
    94  		c,
    95  		T("**Attention: Plugins are binaries written by potentially untrusted authors. Install and use plugins at your own risk.**\n\nDo you want to install the plugin {{.Plugin}}? (y or n)", map[string]interface{}{"Plugin": c.Args()[0]}),
    96  	) {
    97  		cmd.ui.Failed(T("Plugin installation cancelled"))
    98  	}
    99  
   100  	fileDownloader := fileutils.NewDownloader(os.TempDir())
   101  
   102  	removeTmpFile := func() {
   103  		err := fileDownloader.RemoveFile()
   104  		if err != nil {
   105  			cmd.ui.Say(T("Problem removing downloaded binary in temp directory: ") + err.Error())
   106  		}
   107  	}
   108  	defer removeTmpFile()
   109  
   110  	deps := &plugin_installer.PluginInstallerContext{
   111  		Checksummer:    cmd.checksum,
   112  		GetPluginRepos: cmd.config.PluginRepos,
   113  		FileDownloader: fileDownloader,
   114  		PluginRepo:     cmd.pluginRepo,
   115  		RepoName:       c.String("r"),
   116  		Ui:             cmd.ui,
   117  	}
   118  	installer := plugin_installer.NewPluginInstaller(deps)
   119  	pluginSourceFilepath := installer.Install(c.Args()[0])
   120  
   121  	cmd.ui.Say(fmt.Sprintf(T("Installing plugin {{.PluginPath}}...", map[string]interface{}{"PluginPath": pluginSourceFilepath})))
   122  
   123  	_, pluginExecutableName := filepath.Split(pluginSourceFilepath)
   124  
   125  	pluginDestinationFilepath := filepath.Join(cmd.pluginConfig.GetPluginPath(), pluginExecutableName)
   126  
   127  	cmd.ensurePluginBinaryWithSameFileNameDoesNotAlreadyExist(pluginDestinationFilepath, pluginExecutableName)
   128  
   129  	pluginMetadata := cmd.runBinaryAndObtainPluginMetadata(pluginSourceFilepath)
   130  
   131  	cmd.ensurePluginIsSafeForInstallation(pluginMetadata, pluginDestinationFilepath, pluginSourceFilepath)
   132  
   133  	cmd.installPlugin(pluginMetadata, pluginDestinationFilepath, pluginSourceFilepath)
   134  
   135  	cmd.ui.Ok()
   136  	cmd.ui.Say(fmt.Sprintf(T("Plugin {{.PluginName}} v{{.Version}} successfully installed.", map[string]interface{}{"PluginName": pluginMetadata.Name, "Version": fmt.Sprintf("%d.%d.%d", pluginMetadata.Version.Major, pluginMetadata.Version.Minor, pluginMetadata.Version.Build)})))
   137  }
   138  
   139  func (cmd *PluginInstall) confirmWithUser(c flags.FlagContext, prompt string) bool {
   140  	return c.Bool("f") || cmd.ui.Confirm(prompt)
   141  }
   142  
   143  func (cmd *PluginInstall) ensurePluginBinaryWithSameFileNameDoesNotAlreadyExist(pluginDestinationFilepath, pluginExecutableName string) {
   144  	_, err := os.Stat(pluginDestinationFilepath)
   145  	if err == nil || os.IsExist(err) {
   146  		cmd.ui.Failed(fmt.Sprintf(T("The file {{.PluginExecutableName}} already exists under the plugin directory.\n",
   147  			map[string]interface{}{
   148  				"PluginExecutableName": pluginExecutableName,
   149  			})))
   150  	} else if !os.IsNotExist(err) {
   151  		cmd.ui.Failed(fmt.Sprintf(T("Unexpected error has occurred:\n{{.Error}}", map[string]interface{}{"Error": err.Error()})))
   152  	}
   153  }
   154  
   155  func (cmd *PluginInstall) ensurePluginIsSafeForInstallation(pluginMetadata *plugin.PluginMetadata, pluginDestinationFilepath string, pluginSourceFilepath string) {
   156  	plugins := cmd.pluginConfig.Plugins()
   157  	if pluginMetadata.Name == "" {
   158  		cmd.ui.Failed(fmt.Sprintf(T("Unable to obtain plugin name for executable {{.Executable}}", map[string]interface{}{"Executable": pluginSourceFilepath})))
   159  	}
   160  
   161  	if _, ok := plugins[pluginMetadata.Name]; ok {
   162  		cmd.ui.Failed(fmt.Sprintf(T("Plugin name {{.PluginName}} is already taken", map[string]interface{}{"PluginName": pluginMetadata.Name})))
   163  	}
   164  
   165  	if pluginMetadata.Commands == nil {
   166  		cmd.ui.Failed(fmt.Sprintf(T("Error getting command list from plugin {{.FilePath}}", map[string]interface{}{"FilePath": pluginSourceFilepath})))
   167  	}
   168  
   169  	for _, pluginCmd := range pluginMetadata.Commands {
   170  
   171  		//check for command conflicting core commands/alias
   172  		if pluginCmd.Name == "help" || command_registry.Commands.CommandExists(pluginCmd.Name) {
   173  			cmd.ui.Failed(fmt.Sprintf(T("Command `{{.Command}}` in the plugin being installed is a native CF command/alias.  Rename the `{{.Command}}` command in the plugin being installed in order to enable its installation and use.",
   174  				map[string]interface{}{"Command": pluginCmd.Name})))
   175  		}
   176  
   177  		//check for alias conflicting core command/alias
   178  		if pluginCmd.Alias == "help" || command_registry.Commands.CommandExists(pluginCmd.Alias) {
   179  			cmd.ui.Failed(fmt.Sprintf(T("Alias `{{.Command}}` in the plugin being installed is a native CF command/alias.  Rename the `{{.Command}}` command in the plugin being installed in order to enable its installation and use.",
   180  				map[string]interface{}{"Command": pluginCmd.Alias})))
   181  		}
   182  
   183  		for installedPluginName, installedPlugin := range plugins {
   184  			for _, installedPluginCmd := range installedPlugin.Commands {
   185  
   186  				//check for command conflicting other plugin commands/alias
   187  				if installedPluginCmd.Name == pluginCmd.Name || installedPluginCmd.Alias == pluginCmd.Name {
   188  					cmd.ui.Failed(fmt.Sprintf(T("Command `{{.Command}}` is a command/alias in plugin '{{.PluginName}}'.  You could try uninstalling plugin '{{.PluginName}}' and then install this plugin in order to invoke the `{{.Command}}` command.  However, you should first fully understand the impact of uninstalling the existing '{{.PluginName}}' plugin.",
   189  						map[string]interface{}{"Command": pluginCmd.Name, "PluginName": installedPluginName})))
   190  				}
   191  
   192  				//check for alias conflicting other plugin commands/alias
   193  				if pluginCmd.Alias != "" && (installedPluginCmd.Name == pluginCmd.Alias || installedPluginCmd.Alias == pluginCmd.Alias) {
   194  					cmd.ui.Failed(fmt.Sprintf(T("Alias `{{.Command}}` is a command/alias in plugin '{{.PluginName}}'.  You could try uninstalling plugin '{{.PluginName}}' and then install this plugin in order to invoke the `{{.Command}}` command.  However, you should first fully understand the impact of uninstalling the existing '{{.PluginName}}' plugin.",
   195  						map[string]interface{}{"Command": pluginCmd.Alias, "PluginName": installedPluginName})))
   196  				}
   197  			}
   198  		}
   199  	}
   200  
   201  }
   202  
   203  func (cmd *PluginInstall) installPlugin(pluginMetadata *plugin.PluginMetadata, pluginDestinationFilepath, pluginSourceFilepath string) {
   204  	err := fileutils.CopyFile(pluginDestinationFilepath, pluginSourceFilepath)
   205  	if err != nil {
   206  		cmd.ui.Failed(fmt.Sprintf(T("Could not copy plugin binary: \n{{.Error}}", map[string]interface{}{"Error": err.Error()})))
   207  	}
   208  
   209  	configMetadata := plugin_config.PluginMetadata{
   210  		Location: pluginDestinationFilepath,
   211  		Version:  pluginMetadata.Version,
   212  		Commands: pluginMetadata.Commands,
   213  	}
   214  
   215  	cmd.pluginConfig.SetPlugin(pluginMetadata.Name, configMetadata)
   216  }
   217  
   218  func (cmd *PluginInstall) runBinaryAndObtainPluginMetadata(pluginSourceFilepath string) *plugin.PluginMetadata {
   219  	err := cmd.rpcService.Start()
   220  	if err != nil {
   221  		cmd.ui.Failed(err.Error())
   222  	}
   223  	defer cmd.rpcService.Stop()
   224  
   225  	cmd.runPluginBinary(pluginSourceFilepath, cmd.rpcService.Port())
   226  
   227  	return cmd.rpcService.RpcCmd.PluginMetadata
   228  }
   229  
   230  func (cmd *PluginInstall) runPluginBinary(location string, servicePort string) {
   231  	pluginInvocation := exec.Command(location, servicePort, "SendMetadata")
   232  
   233  	err := pluginInvocation.Run()
   234  	if err != nil {
   235  		cmd.ui.Failed(err.Error())
   236  	}
   237  }