github.com/cloudfoundry/cli@v7.1.0+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, 7:
   106  		if pluginLibraryMajorVersion > 1 {
   107  			return configv3.Plugin{}, actionerror.PluginInvalidLibraryVersionError{}
   108  		}
   109  	default:
   110  		panic("unrecognized major version")
   111  	}
   112  
   113  	installedPlugins := actor.config.Plugins()
   114  
   115  	conflictingNames := []string{}
   116  	conflictingAliases := []string{}
   117  
   118  	for _, command := range plugin.Commands {
   119  		if commandList.HasCommand(command.Name) || commandList.HasAlias(command.Name) {
   120  			conflictingNames = append(conflictingNames, command.Name)
   121  		}
   122  
   123  		if commandList.HasAlias(command.Alias) || commandList.HasCommand(command.Alias) {
   124  			conflictingAliases = append(conflictingAliases, command.Alias)
   125  		}
   126  
   127  		for _, installedPlugin := range installedPlugins {
   128  			// we do not error if a plugins commands conflict with previous
   129  			// versions of the same plugin
   130  			if plugin.Name == installedPlugin.Name {
   131  				continue
   132  			}
   133  
   134  			for _, installedCommand := range installedPlugin.Commands {
   135  				if command.Name == installedCommand.Name || command.Name == installedCommand.Alias {
   136  					conflictingNames = append(conflictingNames, command.Name)
   137  				}
   138  
   139  				if command.Alias != "" &&
   140  					(command.Alias == installedCommand.Alias || command.Alias == installedCommand.Name) {
   141  					conflictingAliases = append(conflictingAliases, command.Alias)
   142  				}
   143  			}
   144  		}
   145  	}
   146  
   147  	if len(conflictingNames) > 0 || len(conflictingAliases) > 0 {
   148  		sort.Slice(conflictingNames, func(i, j int) bool {
   149  			return strings.ToLower(conflictingNames[i]) < strings.ToLower(conflictingNames[j])
   150  		})
   151  
   152  		sort.Slice(conflictingAliases, func(i, j int) bool {
   153  			return strings.ToLower(conflictingAliases[i]) < strings.ToLower(conflictingAliases[j])
   154  		})
   155  
   156  		return configv3.Plugin{}, actionerror.PluginCommandsConflictError{
   157  			PluginName:     plugin.Name,
   158  			PluginVersion:  plugin.Version.String(),
   159  			CommandNames:   conflictingNames,
   160  			CommandAliases: conflictingAliases,
   161  		}
   162  	}
   163  
   164  	return plugin, nil
   165  }
   166  
   167  func (actor Actor) InstallPluginFromPath(path string, plugin configv3.Plugin) error {
   168  	installPath := generic.ExecutableFilename(filepath.Join(actor.config.PluginHome(), plugin.Name))
   169  	err := fileutils.CopyPathToPath(path, installPath)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	// rwxr-xr-x so that multiple users can share the same $CF_PLUGIN_HOME
   174  	err = os.Chmod(installPath, 0755)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	plugin.Location = installPath
   180  
   181  	actor.config.AddPlugin(plugin)
   182  
   183  	err = actor.config.WritePluginConfig()
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	return nil
   189  }
   190  
   191  func makeTempFile(tempDir string) (*os.File, error) {
   192  	tempFile, err := ioutil.TempFile(tempDir, "")
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  
   197  	err = tempFile.Close()
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	return tempFile, nil
   203  }