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