github.com/rakutentech/cli@v6.12.5-0.20151006231303-24468b65536e+incompatible/cf/commands/plugin/install_plugin.go (about) 1 package plugin 2 3 import ( 4 "fmt" 5 "net/rpc" 6 "os" 7 "os/exec" 8 "path/filepath" 9 10 "github.com/cloudfoundry/cli/cf/actors/plugin_installer" 11 "github.com/cloudfoundry/cli/cf/actors/plugin_repo" 12 "github.com/cloudfoundry/cli/cf/command_registry" 13 "github.com/cloudfoundry/cli/cf/configuration/core_config" 14 "github.com/cloudfoundry/cli/cf/configuration/plugin_config" 15 . "github.com/cloudfoundry/cli/cf/i18n" 16 "github.com/cloudfoundry/cli/cf/requirements" 17 "github.com/cloudfoundry/cli/cf/terminal" 18 "github.com/cloudfoundry/cli/fileutils" 19 "github.com/cloudfoundry/cli/flags" 20 "github.com/cloudfoundry/cli/flags/flag" 21 "github.com/cloudfoundry/cli/plugin" 22 "github.com/cloudfoundry/cli/utils" 23 24 rpcService "github.com/cloudfoundry/cli/plugin/rpc" 25 ) 26 27 type PluginInstall struct { 28 ui terminal.UI 29 config core_config.Reader 30 pluginConfig plugin_config.PluginConfiguration 31 pluginRepo plugin_repo.PluginRepo 32 checksum utils.Sha1Checksum 33 rpcService *rpcService.CliRpcService 34 } 35 36 func init() { 37 command_registry.Register(&PluginInstall{}) 38 } 39 40 func (cmd *PluginInstall) MetaData() command_registry.CommandMetadata { 41 fs := make(map[string]flags.FlagSet) 42 fs["r"] = &cliFlags.StringFlag{Name: "r", Usage: T("repo name where the plugin binary is located")} 43 fs["f"] = &cliFlags.BoolFlag{Name: "f", Usage: T("Force install of plugin without prompt")} 44 45 return command_registry.CommandMetadata{ 46 Name: "install-plugin", 47 Description: T("Install the plugin defined in command argument"), 48 Usage: T(`CF_NAME install-plugin URL or LOCAL-PATH/TO/PLUGIN [-r REPO_NAME] [-f] 49 50 The command will download the plugin binary from repository if '-r' is provided 51 Prompts for confirmation unless '-f' is provided 52 53 EXAMPLE: 54 cf install-plugin https://github.com/cf-experimental/plugin-foobar 55 cf install-plugin ~/Downloads/plugin-foobar 56 cf install-plugin plugin-echo -r My-Repo 57 `), 58 Flags: fs, 59 TotalArgs: 1, 60 } 61 } 62 63 func (cmd *PluginInstall) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) (reqs []requirements.Requirement, err error) { 64 if len(fc.Args()) != 1 { 65 cmd.ui.Failed(T("Incorrect Usage. Requires an argument\n\n") + command_registry.Commands.CommandUsage("install-plugin")) 66 } 67 68 return 69 } 70 71 func (cmd *PluginInstall) SetDependency(deps command_registry.Dependency, pluginCall bool) command_registry.Command { 72 cmd.ui = deps.Ui 73 cmd.config = deps.Config 74 cmd.pluginConfig = deps.PluginConfig 75 cmd.pluginRepo = deps.PluginRepo 76 cmd.checksum = deps.ChecksumUtil 77 78 //reset rpc registration in case there is other running instance, 79 //each service can only be registered once 80 rpc.DefaultServer = rpc.NewServer() 81 82 rpcService, err := rpcService.NewRpcService(deps.TeePrinter, deps.TeePrinter, deps.Config, deps.RepoLocator, rpcService.NewNonCodegangstaRunner()) 83 if err != nil { 84 cmd.ui.Failed("Error initializing RPC service: " + err.Error()) 85 } 86 87 cmd.rpcService = rpcService 88 89 return cmd 90 } 91 92 func (cmd *PluginInstall) Execute(c flags.FlagContext) { 93 if !cmd.confirmWithUser( 94 c, 95 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}}? (y or n)", map[string]interface{}{"Plugin": c.Args()[0]}), 96 ) { 97 cmd.ui.Failed(T("Plugin installation cancelled")) 98 } 99 100 fileDownloader := fileutils.NewDownloader(os.TempDir()) 101 102 removeTmpFile := func() { 103 err := fileDownloader.RemoveFile() 104 if err != nil { 105 cmd.ui.Say(T("Problem removing downloaded binary in temp directory: ") + err.Error()) 106 } 107 } 108 defer removeTmpFile() 109 110 deps := &plugin_installer.PluginInstallerContext{ 111 Checksummer: cmd.checksum, 112 GetPluginRepos: cmd.config.PluginRepos, 113 FileDownloader: fileDownloader, 114 PluginRepo: cmd.pluginRepo, 115 RepoName: c.String("r"), 116 Ui: cmd.ui, 117 } 118 installer := plugin_installer.NewPluginInstaller(deps) 119 pluginSourceFilepath := installer.Install(c.Args()[0]) 120 121 cmd.ui.Say(fmt.Sprintf(T("Installing plugin {{.PluginPath}}...", map[string]interface{}{"PluginPath": pluginSourceFilepath}))) 122 123 _, pluginExecutableName := filepath.Split(pluginSourceFilepath) 124 125 pluginDestinationFilepath := filepath.Join(cmd.pluginConfig.GetPluginPath(), pluginExecutableName) 126 127 cmd.ensurePluginBinaryWithSameFileNameDoesNotAlreadyExist(pluginDestinationFilepath, pluginExecutableName) 128 129 pluginMetadata := cmd.runBinaryAndObtainPluginMetadata(pluginSourceFilepath) 130 131 cmd.ensurePluginIsSafeForInstallation(pluginMetadata, pluginDestinationFilepath, pluginSourceFilepath) 132 133 cmd.installPlugin(pluginMetadata, pluginDestinationFilepath, pluginSourceFilepath) 134 135 cmd.ui.Ok() 136 cmd.ui.Say(fmt.Sprintf(T("Plugin {{.PluginName}} v{{.Version}} successfully installed.", map[string]interface{}{"PluginName": pluginMetadata.Name, "Version": fmt.Sprintf("%d.%d.%d", pluginMetadata.Version.Major, pluginMetadata.Version.Minor, pluginMetadata.Version.Build)}))) 137 } 138 139 func (cmd *PluginInstall) confirmWithUser(c flags.FlagContext, prompt string) bool { 140 return c.Bool("f") || cmd.ui.Confirm(prompt) 141 } 142 143 func (cmd *PluginInstall) ensurePluginBinaryWithSameFileNameDoesNotAlreadyExist(pluginDestinationFilepath, pluginExecutableName string) { 144 _, err := os.Stat(pluginDestinationFilepath) 145 if err == nil || os.IsExist(err) { 146 cmd.ui.Failed(fmt.Sprintf(T("The file {{.PluginExecutableName}} already exists under the plugin directory.\n", 147 map[string]interface{}{ 148 "PluginExecutableName": pluginExecutableName, 149 }))) 150 } else if !os.IsNotExist(err) { 151 cmd.ui.Failed(fmt.Sprintf(T("Unexpected error has occurred:\n{{.Error}}", map[string]interface{}{"Error": err.Error()}))) 152 } 153 } 154 155 func (cmd *PluginInstall) ensurePluginIsSafeForInstallation(pluginMetadata *plugin.PluginMetadata, pluginDestinationFilepath string, pluginSourceFilepath string) { 156 plugins := cmd.pluginConfig.Plugins() 157 if pluginMetadata.Name == "" { 158 cmd.ui.Failed(fmt.Sprintf(T("Unable to obtain plugin name for executable {{.Executable}}", map[string]interface{}{"Executable": pluginSourceFilepath}))) 159 } 160 161 if _, ok := plugins[pluginMetadata.Name]; ok { 162 cmd.ui.Failed(fmt.Sprintf(T("Plugin name {{.PluginName}} is already taken", map[string]interface{}{"PluginName": pluginMetadata.Name}))) 163 } 164 165 if pluginMetadata.Commands == nil { 166 cmd.ui.Failed(fmt.Sprintf(T("Error getting command list from plugin {{.FilePath}}", map[string]interface{}{"FilePath": pluginSourceFilepath}))) 167 } 168 169 for _, pluginCmd := range pluginMetadata.Commands { 170 171 //check for command conflicting core commands/alias 172 if pluginCmd.Name == "help" || command_registry.Commands.CommandExists(pluginCmd.Name) { 173 cmd.ui.Failed(fmt.Sprintf(T("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.", 174 map[string]interface{}{"Command": pluginCmd.Name}))) 175 } 176 177 //check for alias conflicting core command/alias 178 if pluginCmd.Alias == "help" || command_registry.Commands.CommandExists(pluginCmd.Alias) { 179 cmd.ui.Failed(fmt.Sprintf(T("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.", 180 map[string]interface{}{"Command": pluginCmd.Alias}))) 181 } 182 183 for installedPluginName, installedPlugin := range plugins { 184 for _, installedPluginCmd := range installedPlugin.Commands { 185 186 //check for command conflicting other plugin commands/alias 187 if installedPluginCmd.Name == pluginCmd.Name || installedPluginCmd.Alias == pluginCmd.Name { 188 cmd.ui.Failed(fmt.Sprintf(T("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.", 189 map[string]interface{}{"Command": pluginCmd.Name, "PluginName": installedPluginName}))) 190 } 191 192 //check for alias conflicting other plugin commands/alias 193 if pluginCmd.Alias != "" && (installedPluginCmd.Name == pluginCmd.Alias || installedPluginCmd.Alias == pluginCmd.Alias) { 194 cmd.ui.Failed(fmt.Sprintf(T("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.", 195 map[string]interface{}{"Command": pluginCmd.Alias, "PluginName": installedPluginName}))) 196 } 197 } 198 } 199 } 200 201 } 202 203 func (cmd *PluginInstall) installPlugin(pluginMetadata *plugin.PluginMetadata, pluginDestinationFilepath, pluginSourceFilepath string) { 204 err := fileutils.CopyFile(pluginDestinationFilepath, pluginSourceFilepath) 205 if err != nil { 206 cmd.ui.Failed(fmt.Sprintf(T("Could not copy plugin binary: \n{{.Error}}", map[string]interface{}{"Error": err.Error()}))) 207 } 208 209 configMetadata := plugin_config.PluginMetadata{ 210 Location: pluginDestinationFilepath, 211 Version: pluginMetadata.Version, 212 Commands: pluginMetadata.Commands, 213 } 214 215 cmd.pluginConfig.SetPlugin(pluginMetadata.Name, configMetadata) 216 } 217 218 func (cmd *PluginInstall) runBinaryAndObtainPluginMetadata(pluginSourceFilepath string) *plugin.PluginMetadata { 219 err := cmd.rpcService.Start() 220 if err != nil { 221 cmd.ui.Failed(err.Error()) 222 } 223 defer cmd.rpcService.Stop() 224 225 cmd.runPluginBinary(pluginSourceFilepath, cmd.rpcService.Port()) 226 227 return cmd.rpcService.RpcCmd.PluginMetadata 228 } 229 230 func (cmd *PluginInstall) runPluginBinary(location string, servicePort string) { 231 pluginInvocation := exec.Command(location, servicePort, "SendMetadata") 232 233 err := pluginInvocation.Run() 234 if err != nil { 235 cmd.ui.Failed(err.Error()) 236 } 237 }