github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/actor/pluginaction/install.go (about)

     1  package pluginaction
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"sort"
     8  	"strings"
     9  
    10  	"code.cloudfoundry.org/cli/actor/actionerror"
    11  	"code.cloudfoundry.org/cli/api/plugin"
    12  	"code.cloudfoundry.org/cli/util/configv3"
    13  	"code.cloudfoundry.org/cli/util/generic"
    14  	"code.cloudfoundry.org/gofileutils/fileutils"
    15  )
    16  
    17  //go:generate counterfeiter . PluginMetadata
    18  
    19  type PluginMetadata interface {
    20  	GetMetadata(pluginPath string) (configv3.Plugin, error)
    21  }
    22  
    23  //go:generate counterfeiter . CommandList
    24  
    25  type CommandList interface {
    26  	HasCommand(string) bool
    27  	HasAlias(string) bool
    28  }
    29  
    30  // CreateExecutableCopy makes a temporary copy of a plugin binary and makes it
    31  // executable.
    32  //
    33  // config.PluginHome() + /temp is used as the temp dir instead of the system
    34  // temp for security reasons.
    35  func (actor Actor) CreateExecutableCopy(path string, tempPluginDir string) (string, error) {
    36  	tempFile, err := makeTempFile(tempPluginDir)
    37  	if err != nil {
    38  		return "", err
    39  	}
    40  
    41  	// add '.exe' to the temp file if on Windows
    42  	executablePath := generic.ExecutableFilename(tempFile.Name())
    43  	err = os.Rename(tempFile.Name(), executablePath)
    44  	if err != nil {
    45  		return "", err
    46  	}
    47  
    48  	err = fileutils.CopyPathToPath(path, executablePath)
    49  	if err != nil {
    50  		return "", err
    51  	}
    52  
    53  	err = os.Chmod(executablePath, 0700)
    54  	if err != nil {
    55  		return "", err
    56  	}
    57  
    58  	return executablePath, nil
    59  }
    60  
    61  // DownloadExecutableBinaryFromURL fetches a plugin binary from the specified
    62  // URL, if it exists.
    63  func (actor Actor) DownloadExecutableBinaryFromURL(pluginURL string, tempPluginDir string, proxyReader plugin.ProxyReader) (string, error) {
    64  	tempFile, err := makeTempFile(tempPluginDir)
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  
    69  	err = actor.client.DownloadPlugin(pluginURL, tempFile.Name(), proxyReader)
    70  	if err != nil {
    71  		return "", err
    72  	}
    73  
    74  	return tempFile.Name(), nil
    75  }
    76  
    77  // FileExists returns true if the file exists. It returns false if the file
    78  // doesn't exist or there is an error checking.
    79  func (actor Actor) FileExists(path string) bool {
    80  	_, err := os.Stat(path)
    81  	return err == nil
    82  }
    83  
    84  func (actor Actor) GetAndValidatePlugin(pluginMetadata PluginMetadata, commandList CommandList, path string) (configv3.Plugin, error) {
    85  	plugin, err := pluginMetadata.GetMetadata(path)
    86  	if err != nil || plugin.Name == "" || len(plugin.Commands) == 0 {
    87  		return configv3.Plugin{}, actionerror.PluginInvalidError{Err: err}
    88  	}
    89  
    90  	installedPlugins := actor.config.Plugins()
    91  
    92  	conflictingNames := []string{}
    93  	conflictingAliases := []string{}
    94  
    95  	for _, command := range plugin.Commands {
    96  		if commandList.HasCommand(command.Name) || commandList.HasAlias(command.Name) {
    97  			conflictingNames = append(conflictingNames, command.Name)
    98  		}
    99  
   100  		if commandList.HasAlias(command.Alias) || commandList.HasCommand(command.Alias) {
   101  			conflictingAliases = append(conflictingAliases, command.Alias)
   102  		}
   103  
   104  		for _, installedPlugin := range installedPlugins {
   105  			// we do not error if a plugins commands conflict with previous
   106  			// versions of the same plugin
   107  			if plugin.Name == installedPlugin.Name {
   108  				continue
   109  			}
   110  
   111  			for _, installedCommand := range installedPlugin.Commands {
   112  				if command.Name == installedCommand.Name || command.Name == installedCommand.Alias {
   113  					conflictingNames = append(conflictingNames, command.Name)
   114  				}
   115  
   116  				if command.Alias != "" &&
   117  					(command.Alias == installedCommand.Alias || command.Alias == installedCommand.Name) {
   118  					conflictingAliases = append(conflictingAliases, command.Alias)
   119  				}
   120  			}
   121  		}
   122  	}
   123  
   124  	if len(conflictingNames) > 0 || len(conflictingAliases) > 0 {
   125  		sort.Slice(conflictingNames, func(i, j int) bool {
   126  			return strings.ToLower(conflictingNames[i]) < strings.ToLower(conflictingNames[j])
   127  		})
   128  
   129  		sort.Slice(conflictingAliases, func(i, j int) bool {
   130  			return strings.ToLower(conflictingAliases[i]) < strings.ToLower(conflictingAliases[j])
   131  		})
   132  
   133  		return configv3.Plugin{}, actionerror.PluginCommandsConflictError{
   134  			PluginName:     plugin.Name,
   135  			PluginVersion:  plugin.Version.String(),
   136  			CommandNames:   conflictingNames,
   137  			CommandAliases: conflictingAliases,
   138  		}
   139  	}
   140  
   141  	return plugin, nil
   142  }
   143  
   144  func (actor Actor) InstallPluginFromPath(path string, plugin configv3.Plugin) error {
   145  	installPath := generic.ExecutableFilename(filepath.Join(actor.config.PluginHome(), plugin.Name))
   146  	err := fileutils.CopyPathToPath(path, installPath)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	// rwxr-xr-x so that multiple users can share the same $CF_PLUGIN_HOME
   151  	err = os.Chmod(installPath, 0755)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	plugin.Location = installPath
   157  
   158  	actor.config.AddPlugin(plugin)
   159  
   160  	err = actor.config.WritePluginConfig()
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  func makeTempFile(tempDir string) (*os.File, error) {
   169  	tempFile, err := ioutil.TempFile(tempDir, "")
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	err = tempFile.Close()
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	return tempFile, nil
   180  }