github.com/asifdxtreme/cli@v6.1.3-0.20150123051144-9ead8700b4ae+incompatible/cf/commands/plugin/install_plugin.go (about)

     1  package plugin
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  
     9  	"github.com/cloudfoundry/cli/cf/command"
    10  	"github.com/cloudfoundry/cli/cf/command_metadata"
    11  	"github.com/cloudfoundry/cli/cf/configuration/plugin_config"
    12  	. "github.com/cloudfoundry/cli/cf/i18n"
    13  	"github.com/cloudfoundry/cli/cf/requirements"
    14  	"github.com/cloudfoundry/cli/cf/terminal"
    15  	"github.com/cloudfoundry/cli/fileutils"
    16  	"github.com/cloudfoundry/cli/plugin"
    17  	"github.com/cloudfoundry/cli/plugin/rpc"
    18  	"github.com/codegangsta/cli"
    19  )
    20  
    21  type PluginInstall struct {
    22  	ui       terminal.UI
    23  	config   plugin_config.PluginConfiguration
    24  	coreCmds map[string]command.Command
    25  }
    26  
    27  func NewPluginInstall(ui terminal.UI, config plugin_config.PluginConfiguration, coreCmds map[string]command.Command) *PluginInstall {
    28  	return &PluginInstall{
    29  		ui:       ui,
    30  		config:   config,
    31  		coreCmds: coreCmds,
    32  	}
    33  }
    34  
    35  func (cmd *PluginInstall) Metadata() command_metadata.CommandMetadata {
    36  	return command_metadata.CommandMetadata{
    37  		Name:        "install-plugin",
    38  		Description: T("Install the plugin defined in command argument"),
    39  		Usage: T(`CF_NAME install-plugin URL or LOCAL-PATH/TO/PLUGIN
    40  
    41  EXAMPLE:
    42     cf install-plugin https://github.com/cf-experimental/plugin-foobar
    43     cf install-plugin ~/Downloads/plugin-foobar
    44  `),
    45  	}
    46  }
    47  
    48  func (cmd *PluginInstall) GetRequirements(_ requirements.Factory, c *cli.Context) (req []requirements.Requirement, err error) {
    49  	if len(c.Args()) != 1 {
    50  		cmd.ui.FailWithUsage(c)
    51  	}
    52  
    53  	return
    54  }
    55  
    56  func (cmd *PluginInstall) Run(c *cli.Context) {
    57  	var downloader fileutils.Downloader
    58  
    59  	pluginSourceFilepath := c.Args()[0]
    60  
    61  	if filepath.Dir(pluginSourceFilepath) == "." {
    62  		pluginSourceFilepath = "./" + filepath.Clean(pluginSourceFilepath)
    63  	}
    64  
    65  	cmd.ui.Say(fmt.Sprintf(T("Installing plugin {{.PluginPath}}...", map[string]interface{}{"PluginPath": pluginSourceFilepath})))
    66  
    67  	if !cmd.ensureCandidatePluginBinaryExistsAtGivenPath(pluginSourceFilepath) {
    68  		cmd.ui.Say("")
    69  		cmd.ui.Say(T("File not found locally, attempting to download binary file from internet ..."))
    70  		pluginSourceFilepath = cmd.tryDownloadPluginBinaryfromGivenPath(pluginSourceFilepath, downloader)
    71  	}
    72  
    73  	_, pluginExecutableName := filepath.Split(pluginSourceFilepath)
    74  
    75  	pluginDestinationFilepath := filepath.Join(cmd.config.GetPluginPath(), pluginExecutableName)
    76  
    77  	cmd.ensurePluginBinaryWithSameFileNameDoesNotAlreadyExist(pluginDestinationFilepath, pluginExecutableName)
    78  
    79  	pluginMetadata := cmd.runBinaryAndObtainPluginMetadata(pluginSourceFilepath)
    80  
    81  	cmd.ensurePluginIsSafeForInstallation(pluginMetadata, pluginDestinationFilepath, pluginSourceFilepath)
    82  
    83  	cmd.installPlugin(pluginMetadata, pluginDestinationFilepath, pluginSourceFilepath)
    84  
    85  	if downloader != nil {
    86  		err := downloader.RemoveFile()
    87  		if err != nil {
    88  			cmd.ui.Say(T("Problem removing downloaded binary in temp directory: ") + err.Error())
    89  		}
    90  	}
    91  
    92  	cmd.ui.Ok()
    93  	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)})))
    94  }
    95  
    96  func (cmd *PluginInstall) ensurePluginBinaryWithSameFileNameDoesNotAlreadyExist(pluginDestinationFilepath, pluginExecutableName string) {
    97  	_, err := os.Stat(pluginDestinationFilepath)
    98  	if err == nil || os.IsExist(err) {
    99  		cmd.ui.Failed(fmt.Sprintf(T("The file {{.PluginExecutableName}} already exists under the plugin directory.\n",
   100  			map[string]interface{}{
   101  				"PluginExecutableName": pluginExecutableName,
   102  			})))
   103  	} else if !os.IsNotExist(err) {
   104  		cmd.ui.Failed(fmt.Sprintf(T("Unexpected error has occurred:\n{{.Error}}", map[string]interface{}{"Error": err.Error()})))
   105  	}
   106  }
   107  
   108  func (cmd *PluginInstall) ensurePluginIsSafeForInstallation(pluginMetadata *plugin.PluginMetadata, pluginDestinationFilepath string, pluginSourceFilepath string) {
   109  	plugins := cmd.config.Plugins()
   110  	if pluginMetadata.Name == "" {
   111  		cmd.ui.Failed(fmt.Sprintf(T("Unable to obtain plugin name for executable {{.Executable}}", map[string]interface{}{"Executable": pluginSourceFilepath})))
   112  	}
   113  
   114  	if _, ok := plugins[pluginMetadata.Name]; ok {
   115  		cmd.ui.Failed(fmt.Sprintf(T("Plugin name {{.PluginName}} is already taken", map[string]interface{}{"PluginName": pluginMetadata.Name})))
   116  	}
   117  
   118  	if pluginMetadata.Commands == nil {
   119  		cmd.ui.Failed(fmt.Sprintf(T("Error getting command list from plugin {{.FilePath}}", map[string]interface{}{"FilePath": pluginSourceFilepath})))
   120  	}
   121  
   122  	shortNames := cmd.getShortNames()
   123  
   124  	for _, pluginCmd := range pluginMetadata.Commands {
   125  		//check for command conflicting core commands/alias
   126  		if _, exists := cmd.coreCmds[pluginCmd.Name]; exists || shortNames[pluginCmd.Name] || pluginCmd.Name == "help" {
   127  			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.",
   128  				map[string]interface{}{"Command": pluginCmd.Name})))
   129  		}
   130  
   131  		//check for alias conflicting core command/alias
   132  		if _, exists := cmd.coreCmds[pluginCmd.Alias]; exists || shortNames[pluginCmd.Alias] || pluginCmd.Alias == "help" {
   133  			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.",
   134  				map[string]interface{}{"Command": pluginCmd.Alias})))
   135  		}
   136  
   137  		for installedPluginName, installedPlugin := range plugins {
   138  			for _, installedPluginCmd := range installedPlugin.Commands {
   139  
   140  				//check for command conflicting other plugin commands/alias
   141  				if installedPluginCmd.Name == pluginCmd.Name || installedPluginCmd.Alias == pluginCmd.Name {
   142  					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.",
   143  						map[string]interface{}{"Command": pluginCmd.Name, "PluginName": installedPluginName})))
   144  				}
   145  
   146  				//check for alias conflicting other plugin commands/alias
   147  				if pluginCmd.Alias != "" && (installedPluginCmd.Name == pluginCmd.Alias || installedPluginCmd.Alias == pluginCmd.Alias) {
   148  					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.",
   149  						map[string]interface{}{"Command": pluginCmd.Alias, "PluginName": installedPluginName})))
   150  				}
   151  			}
   152  		}
   153  	}
   154  
   155  }
   156  
   157  func (cmd *PluginInstall) installPlugin(pluginMetadata *plugin.PluginMetadata, pluginDestinationFilepath, pluginSourceFilepath string) {
   158  	err := fileutils.CopyFile(pluginDestinationFilepath, pluginSourceFilepath)
   159  	if err != nil {
   160  		cmd.ui.Failed(fmt.Sprintf(T("Could not copy plugin binary: \n{{.Error}}", map[string]interface{}{"Error": err.Error()})))
   161  	}
   162  
   163  	configMetadata := plugin_config.PluginMetadata{
   164  		Location: pluginDestinationFilepath,
   165  		Version:  pluginMetadata.Version,
   166  		Commands: pluginMetadata.Commands,
   167  	}
   168  
   169  	cmd.config.SetPlugin(pluginMetadata.Name, configMetadata)
   170  }
   171  
   172  func (cmd *PluginInstall) runBinaryAndObtainPluginMetadata(pluginSourceFilepath string) *plugin.PluginMetadata {
   173  	rpcService, err := rpc.NewRpcService(nil, nil, nil)
   174  	if err != nil {
   175  		cmd.ui.Failed(err.Error())
   176  	}
   177  
   178  	err = rpcService.Start()
   179  	if err != nil {
   180  		cmd.ui.Failed(err.Error())
   181  	}
   182  	defer rpcService.Stop()
   183  
   184  	cmd.runPluginBinary(pluginSourceFilepath, rpcService.Port())
   185  
   186  	return rpcService.RpcCmd.PluginMetadata
   187  }
   188  
   189  func (cmd *PluginInstall) ensureCandidatePluginBinaryExistsAtGivenPath(pluginSourceFilepath string) bool {
   190  	_, err := os.Stat(pluginSourceFilepath)
   191  	if err != nil && os.IsNotExist(err) {
   192  		return false
   193  	}
   194  	return true
   195  }
   196  
   197  func (cmd *PluginInstall) tryDownloadPluginBinaryfromGivenPath(pluginSourceFilepath string, downloader fileutils.Downloader) string {
   198  
   199  	savePath := os.TempDir()
   200  	downloader = fileutils.NewDownloader(savePath)
   201  	size, filename, err := downloader.DownloadFile(pluginSourceFilepath)
   202  
   203  	if err != nil {
   204  		cmd.ui.Failed(fmt.Sprintf(T("Download attempt failed: {{.Error}}\n\nUnable to install, plugin is not available from local/internet.", map[string]interface{}{"Error": err.Error()})))
   205  	}
   206  
   207  	cmd.ui.Say(fmt.Sprintf("%d "+T("bytes downloaded")+"...", size))
   208  
   209  	executablePath := filepath.Join(savePath, filename)
   210  	os.Chmod(executablePath, 0700)
   211  
   212  	return executablePath
   213  }
   214  
   215  func (cmd *PluginInstall) getShortNames() map[string]bool {
   216  	shortNames := make(map[string]bool)
   217  	for _, singleCmd := range cmd.coreCmds {
   218  		metaData := singleCmd.Metadata()
   219  		if metaData.ShortName != "" {
   220  			shortNames[metaData.ShortName] = true
   221  		}
   222  	}
   223  	return shortNames
   224  }
   225  
   226  func (cmd *PluginInstall) runPluginBinary(location string, servicePort string) {
   227  	pluginInvocation := exec.Command(location, servicePort, "SendMetadata")
   228  
   229  	err := pluginInvocation.Run()
   230  	if err != nil {
   231  		cmd.ui.Failed(err.Error())
   232  	}
   233  }