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 }