github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/cf/commands/plugin/install_plugin.go (about) 1 package plugin 2 3 import ( 4 "errors" 5 "fmt" 6 "net/rpc" 7 "os" 8 "os/exec" 9 "path/filepath" 10 11 "code.cloudfoundry.org/cli/cf/actors/plugininstaller" 12 "code.cloudfoundry.org/cli/cf/actors/pluginrepo" 13 "code.cloudfoundry.org/cli/cf/commandregistry" 14 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 15 "code.cloudfoundry.org/cli/cf/configuration/pluginconfig" 16 "code.cloudfoundry.org/cli/cf/flags" 17 . "code.cloudfoundry.org/cli/cf/i18n" 18 "code.cloudfoundry.org/cli/cf/requirements" 19 "code.cloudfoundry.org/cli/cf/terminal" 20 "code.cloudfoundry.org/cli/plugin" 21 "code.cloudfoundry.org/cli/util" 22 "code.cloudfoundry.org/cli/util/downloader" 23 "code.cloudfoundry.org/gofileutils/fileutils" 24 25 pluginRPCService "code.cloudfoundry.org/cli/plugin/rpc" 26 ) 27 28 type PluginInstall struct { 29 ui terminal.UI 30 config coreconfig.Reader 31 pluginConfig pluginconfig.PluginConfiguration 32 pluginRepo pluginrepo.PluginRepo 33 checksum util.Sha1Checksum 34 rpcService *pluginRPCService.CliRpcService 35 } 36 37 func init() { 38 commandregistry.Register(&PluginInstall{}) 39 } 40 41 func (cmd *PluginInstall) MetaData() commandregistry.CommandMetadata { 42 fs := make(map[string]flags.FlagSet) 43 fs["r"] = &flags.StringFlag{ShortName: "r", Usage: T("Name of a registered repository where the specified plugin is located")} 44 fs["f"] = &flags.BoolFlag{ShortName: "f", Usage: T("Force install of plugin without confirmation")} 45 46 return commandregistry.CommandMetadata{ 47 Name: "install-plugin", 48 Description: T("Install CLI plugin"), 49 Usage: []string{ 50 T(`CF_NAME install-plugin (LOCAL-PATH/TO/PLUGIN | URL | -r REPO_NAME PLUGIN_NAME) [-f] 51 52 Prompts for confirmation unless '-f' is provided.`), 53 }, 54 Examples: []string{ 55 "CF_NAME install-plugin ~/Downloads/plugin-foobar", 56 "CF_NAME install-plugin https://example.com/plugin-foobar_linux_amd64", 57 "CF_NAME install-plugin -r My-Repo plugin-echo", 58 }, 59 Flags: fs, 60 TotalArgs: 1, 61 } 62 } 63 64 func (cmd *PluginInstall) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) ([]requirements.Requirement, error) { 65 if len(fc.Args()) != 1 { 66 cmd.ui.Failed(T("Incorrect Usage. Requires an argument\n\n") + commandregistry.Commands.CommandUsage("install-plugin")) 67 return nil, fmt.Errorf("Incorrect usage: %d arguments of %d required", len(fc.Args()), 1) 68 } 69 70 reqs := []requirements.Requirement{} 71 return reqs, nil 72 } 73 74 func (cmd *PluginInstall) SetDependency(deps commandregistry.Dependency, pluginCall bool) commandregistry.Command { 75 cmd.ui = deps.UI 76 cmd.config = deps.Config 77 cmd.pluginConfig = deps.PluginConfig 78 cmd.pluginRepo = deps.PluginRepo 79 cmd.checksum = deps.ChecksumUtil 80 81 //reset rpc registration in case there is other running instance, 82 //each service can only be registered once 83 server := rpc.NewServer() 84 85 rpcService, err := pluginRPCService.NewRpcService(deps.TeePrinter, deps.TeePrinter, deps.Config, deps.RepoLocator, pluginRPCService.NewCommandRunner(), deps.Logger, cmd.ui.Writer(), server) 86 if err != nil { 87 cmd.ui.Failed("Error initializing RPC service: " + err.Error()) 88 } 89 90 cmd.rpcService = rpcService 91 92 return cmd 93 } 94 95 func (cmd *PluginInstall) Execute(c flags.FlagContext) error { 96 if !cmd.confirmWithUser( 97 c, 98 T("**Attention: Plugins are binaries written by potentially untrusted authors. Install and use plugins at your own risk.**\n\nDo you want to install the plugin {{.Plugin}}?", 99 map[string]interface{}{ 100 "Plugin": c.Args()[0], 101 }), 102 ) { 103 return errors.New(T("Plugin installation cancelled")) 104 } 105 106 fileDownloader := downloader.NewDownloader(os.TempDir()) 107 108 removeTmpFile := func() { 109 err := fileDownloader.RemoveFile() 110 if err != nil { 111 cmd.ui.Say(T("Problem removing downloaded binary in temp directory: ") + err.Error()) 112 } 113 } 114 defer removeTmpFile() 115 116 deps := &plugininstaller.Context{ 117 Checksummer: cmd.checksum, 118 GetPluginRepos: cmd.config.PluginRepos, 119 FileDownloader: fileDownloader, 120 PluginRepo: cmd.pluginRepo, 121 RepoName: c.String("r"), 122 UI: cmd.ui, 123 } 124 installer := plugininstaller.NewPluginInstaller(deps) 125 pluginSourceFilepath := installer.Install(c.Args()[0]) 126 127 _, pluginExecutableName := filepath.Split(pluginSourceFilepath) 128 129 cmd.ui.Say(T( 130 "Installing plugin {{.PluginPath}}...", 131 map[string]interface{}{ 132 "PluginPath": pluginExecutableName, 133 }), 134 ) 135 136 pluginDestinationFilepath := filepath.Join(cmd.pluginConfig.GetPluginPath(), pluginExecutableName) 137 138 err := cmd.ensurePluginBinaryWithSameFileNameDoesNotAlreadyExist(pluginDestinationFilepath, pluginExecutableName) 139 if err != nil { 140 return err 141 } 142 143 pluginMetadata, err := cmd.runBinaryAndObtainPluginMetadata(pluginSourceFilepath) 144 if err != nil { 145 return err 146 } 147 148 err = cmd.ensurePluginIsSafeForInstallation(pluginMetadata, pluginDestinationFilepath, pluginSourceFilepath) 149 if err != nil { 150 return err 151 } 152 153 err = cmd.installPlugin(pluginMetadata, pluginDestinationFilepath, pluginSourceFilepath) 154 if err != nil { 155 return err 156 } 157 158 cmd.ui.Ok() 159 cmd.ui.Say(T( 160 "Plugin {{.PluginName}} v{{.Version}} successfully installed.", 161 map[string]interface{}{ 162 "PluginName": pluginMetadata.Name, 163 "Version": fmt.Sprintf("%d.%d.%d", pluginMetadata.Version.Major, pluginMetadata.Version.Minor, pluginMetadata.Version.Build), 164 }), 165 ) 166 return nil 167 } 168 169 func (cmd *PluginInstall) confirmWithUser(c flags.FlagContext, prompt string) bool { 170 return c.Bool("f") || cmd.ui.Confirm(prompt) 171 } 172 173 func (cmd *PluginInstall) ensurePluginBinaryWithSameFileNameDoesNotAlreadyExist(pluginDestinationFilepath, pluginExecutableName string) error { 174 _, err := os.Stat(pluginDestinationFilepath) 175 if err == nil || os.IsExist(err) { 176 return errors.New(T( 177 "The file {{.PluginExecutableName}} already exists under the plugin directory.\n", 178 map[string]interface{}{ 179 "PluginExecutableName": pluginExecutableName, 180 }), 181 ) 182 } else if !os.IsNotExist(err) { 183 return errors.New(T( 184 "Unexpected error has occurred:\n{{.Error}}", 185 map[string]interface{}{ 186 "Error": err.Error(), 187 }), 188 ) 189 } 190 return nil 191 } 192 193 func (cmd *PluginInstall) ensurePluginIsSafeForInstallation(pluginMetadata *plugin.PluginMetadata, pluginDestinationFilepath string, pluginSourceFilepath string) error { 194 plugins := cmd.pluginConfig.Plugins() 195 if pluginMetadata.Name == "" { 196 return errors.New(T( 197 "Unable to obtain plugin name for executable {{.Executable}}", 198 map[string]interface{}{ 199 "Executable": pluginSourceFilepath, 200 }), 201 ) 202 } 203 204 if _, ok := plugins[pluginMetadata.Name]; ok { 205 return errors.New(T( 206 "Plugin name {{.PluginName}} is already taken", 207 map[string]interface{}{ 208 "PluginName": pluginMetadata.Name, 209 }), 210 ) 211 } 212 213 if pluginMetadata.Commands == nil { 214 return errors.New(T( 215 "Error getting command list from plugin {{.FilePath}}", 216 map[string]interface{}{ 217 "FilePath": pluginSourceFilepath, 218 }), 219 ) 220 } 221 222 for _, pluginCmd := range pluginMetadata.Commands { 223 //check for command conflicting core commands/alias 224 if pluginCmd.Name == "help" || commandregistry.Commands.CommandExists(pluginCmd.Name) { 225 return errors.New(T( 226 "Command `{{.Command}}` in the plugin being installed is a native CF command/alias. Rename the `{{.Command}}` command in the plugin being installed in order to enable its installation and use.", 227 map[string]interface{}{ 228 "Command": pluginCmd.Name, 229 }), 230 ) 231 } 232 233 //check for alias conflicting core command/alias 234 if pluginCmd.Alias == "help" || commandregistry.Commands.CommandExists(pluginCmd.Alias) { 235 return errors.New(T( 236 "Alias `{{.Command}}` in the plugin being installed is a native CF command/alias. Rename the `{{.Command}}` command in the plugin being installed in order to enable its installation and use.", 237 map[string]interface{}{ 238 "Command": pluginCmd.Alias, 239 }), 240 ) 241 } 242 243 for installedPluginName, installedPlugin := range plugins { 244 for _, installedPluginCmd := range installedPlugin.Commands { 245 246 //check for command conflicting other plugin commands/alias 247 if installedPluginCmd.Name == pluginCmd.Name || installedPluginCmd.Alias == pluginCmd.Name { 248 return errors.New(T( 249 "Command `{{.Command}}` is a command/alias in plugin '{{.PluginName}}'. You could try uninstalling plugin '{{.PluginName}}' and then install this plugin in order to invoke the `{{.Command}}` command. However, you should first fully understand the impact of uninstalling the existing '{{.PluginName}}' plugin.", 250 map[string]interface{}{ 251 "Command": pluginCmd.Name, 252 "PluginName": installedPluginName, 253 }), 254 ) 255 } 256 257 //check for alias conflicting other plugin commands/alias 258 if pluginCmd.Alias != "" && (installedPluginCmd.Name == pluginCmd.Alias || installedPluginCmd.Alias == pluginCmd.Alias) { 259 return errors.New(T( 260 "Alias `{{.Command}}` is a command/alias in plugin '{{.PluginName}}'. You could try uninstalling plugin '{{.PluginName}}' and then install this plugin in order to invoke the `{{.Command}}` command. However, you should first fully understand the impact of uninstalling the existing '{{.PluginName}}' plugin.", 261 map[string]interface{}{ 262 "Command": pluginCmd.Alias, 263 "PluginName": installedPluginName, 264 }), 265 ) 266 } 267 } 268 } 269 } 270 return nil 271 } 272 273 func (cmd *PluginInstall) installPlugin(pluginMetadata *plugin.PluginMetadata, pluginDestinationFilepath, pluginSourceFilepath string) error { 274 err := fileutils.CopyPathToPath(pluginSourceFilepath, pluginDestinationFilepath) 275 if err != nil { 276 return errors.New(T( 277 "Could not copy plugin binary: \n{{.Error}}", 278 map[string]interface{}{ 279 "Error": err.Error(), 280 }), 281 ) 282 } 283 284 configMetadata := pluginconfig.PluginMetadata{ 285 Location: pluginDestinationFilepath, 286 Version: pluginMetadata.Version, 287 Commands: pluginMetadata.Commands, 288 } 289 290 cmd.pluginConfig.SetPlugin(pluginMetadata.Name, configMetadata) 291 return nil 292 } 293 294 func (cmd *PluginInstall) runBinaryAndObtainPluginMetadata(pluginSourceFilepath string) (*plugin.PluginMetadata, error) { 295 err := cmd.rpcService.Start() 296 if err != nil { 297 return nil, err 298 } 299 defer cmd.rpcService.Stop() 300 301 err = cmd.runPluginBinary(pluginSourceFilepath, cmd.rpcService.Port()) 302 if err != nil { 303 return nil, err 304 } 305 306 c := cmd.rpcService.RpcCmd 307 c.MetadataMutex.RLock() 308 defer c.MetadataMutex.RUnlock() 309 return c.PluginMetadata, nil 310 } 311 312 func (cmd *PluginInstall) runPluginBinary(location string, servicePort string) error { 313 pluginInvocation := exec.Command(location, servicePort, "SendMetadata") 314 315 err := pluginInvocation.Run() 316 if err != nil { 317 return err 318 } 319 return nil 320 }