github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/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/v4" 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 go run github.com/maxbrunsfeld/counterfeiter/v6 . PluginMetadata 20 21 type PluginMetadata interface { 22 GetMetadata(pluginPath string) (configv3.Plugin, error) 23 } 24 25 //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . 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, 8: 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 }