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