github.com/cloudfoundry/cli@v7.1.0+incompatible/plugin/rpc/cli_rpc_server.go (about) 1 package rpc 2 3 import ( 4 "os" 5 "strings" 6 7 "code.cloudfoundry.org/cli/cf/api" 8 "code.cloudfoundry.org/cli/cf/commandregistry" 9 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 10 "code.cloudfoundry.org/cli/cf/terminal" 11 "code.cloudfoundry.org/cli/plugin" 12 plugin_models "code.cloudfoundry.org/cli/plugin/models" 13 "code.cloudfoundry.org/cli/version" 14 "github.com/blang/semver" 15 16 "fmt" 17 "net" 18 "net/rpc" 19 "strconv" 20 21 "bytes" 22 "io" 23 24 "sync" 25 26 "code.cloudfoundry.org/cli/cf/trace" 27 ) 28 29 var dialTimeout = os.Getenv("CF_DIAL_TIMEOUT") 30 31 type CliRpcService struct { 32 listener net.Listener 33 stopCh chan struct{} 34 Pinged bool 35 RpcCmd *CliRpcCmd 36 Server *rpc.Server 37 } 38 39 type CliRpcCmd struct { 40 PluginMetadata *plugin.PluginMetadata 41 MetadataMutex *sync.RWMutex 42 outputCapture OutputCapture 43 terminalOutputSwitch TerminalOutputSwitch 44 cliConfig coreconfig.Repository 45 repoLocator api.RepositoryLocator 46 newCmdRunner CommandRunner 47 outputBucket *bytes.Buffer 48 logger trace.Printer 49 stdout io.Writer 50 } 51 52 //go:generate counterfeiter . TerminalOutputSwitch 53 54 type TerminalOutputSwitch interface { 55 DisableTerminalOutput(bool) 56 } 57 58 //go:generate counterfeiter . OutputCapture 59 60 type OutputCapture interface { 61 SetOutputBucket(io.Writer) 62 } 63 64 func NewRpcService( 65 outputCapture OutputCapture, 66 terminalOutputSwitch TerminalOutputSwitch, 67 cliConfig coreconfig.Repository, 68 repoLocator api.RepositoryLocator, 69 newCmdRunner CommandRunner, 70 logger trace.Printer, 71 w io.Writer, 72 rpcServer *rpc.Server, 73 ) (*CliRpcService, error) { 74 rpcService := &CliRpcService{ 75 Server: rpcServer, 76 RpcCmd: &CliRpcCmd{ 77 PluginMetadata: &plugin.PluginMetadata{}, 78 MetadataMutex: &sync.RWMutex{}, 79 outputCapture: outputCapture, 80 terminalOutputSwitch: terminalOutputSwitch, 81 cliConfig: cliConfig, 82 repoLocator: repoLocator, 83 newCmdRunner: newCmdRunner, 84 logger: logger, 85 outputBucket: &bytes.Buffer{}, 86 stdout: w, 87 }, 88 } 89 90 err := rpcService.Server.Register(rpcService.RpcCmd) 91 if err != nil { 92 return nil, err 93 } 94 95 return rpcService, nil 96 } 97 98 func (cli *CliRpcService) Stop() { 99 close(cli.stopCh) 100 cli.listener.Close() 101 } 102 103 func (cli *CliRpcService) Port() string { 104 return strconv.Itoa(cli.listener.Addr().(*net.TCPAddr).Port) 105 } 106 107 func (cli *CliRpcService) Start() error { 108 var err error 109 110 cli.stopCh = make(chan struct{}) 111 112 cli.listener, err = net.Listen("tcp", "127.0.0.1:0") 113 if err != nil { 114 return err 115 } 116 117 go func() { 118 for { 119 conn, err := cli.listener.Accept() 120 if err != nil { 121 select { 122 case <-cli.stopCh: 123 return 124 default: 125 fmt.Println(err) 126 } 127 } else { 128 go cli.Server.ServeConn(conn) 129 } 130 } 131 }() 132 133 return nil 134 } 135 136 func (cmd *CliRpcCmd) IsMinCliVersion(passedVersion string, retVal *bool) error { 137 if version.VersionString() == version.DefaultVersion { 138 *retVal = true 139 return nil 140 } 141 142 actualVersion, err := semver.Make(version.VersionString()) 143 if err != nil { 144 return err 145 } 146 147 requiredVersion, err := semver.Make(passedVersion) 148 if err != nil { 149 return err 150 } 151 152 *retVal = actualVersion.GTE(requiredVersion) 153 154 return nil 155 } 156 157 func (cmd *CliRpcCmd) SetPluginMetadata(pluginMetadata plugin.PluginMetadata, retVal *bool) error { 158 cmd.MetadataMutex.Lock() 159 defer cmd.MetadataMutex.Unlock() 160 161 cmd.PluginMetadata = &pluginMetadata 162 *retVal = true 163 return nil 164 } 165 166 func (cmd *CliRpcCmd) DisableTerminalOutput(disable bool, retVal *bool) error { 167 cmd.terminalOutputSwitch.DisableTerminalOutput(disable) 168 *retVal = true 169 return nil 170 } 171 172 func (cmd *CliRpcCmd) CallCoreCommand(args []string, retVal *bool) error { 173 var err error 174 cmdRegistry := commandregistry.Commands 175 176 cmd.outputBucket = &bytes.Buffer{} 177 cmd.outputCapture.SetOutputBucket(cmd.outputBucket) 178 179 if cmdRegistry.CommandExists(args[0]) { 180 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 181 182 //set deps objs to be the one used by all other commands 183 //once all commands are converted, we can make fresh deps for each command run 184 deps.Config = cmd.cliConfig 185 deps.RepoLocator = cmd.repoLocator 186 187 //set command ui's TeePrinter to be the one used by RpcService, for output to be captured 188 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.outputCapture.(*terminal.TeePrinter), cmd.logger) 189 190 err = cmd.newCmdRunner.Command(args, deps, false) 191 } else { 192 *retVal = false 193 return nil 194 } 195 196 if err != nil { 197 *retVal = false 198 return err 199 } 200 201 *retVal = true 202 return nil 203 } 204 205 func (cmd *CliRpcCmd) GetOutputAndReset(args bool, retVal *[]string) error { 206 v := strings.TrimSuffix(cmd.outputBucket.String(), "\n") 207 *retVal = strings.Split(v, "\n") 208 return nil 209 } 210 211 func (cmd *CliRpcCmd) GetCurrentOrg(args string, retVal *plugin_models.Organization) error { 212 retVal.Name = cmd.cliConfig.OrganizationFields().Name 213 retVal.Guid = cmd.cliConfig.OrganizationFields().GUID 214 return nil 215 } 216 217 func (cmd *CliRpcCmd) GetCurrentSpace(args string, retVal *plugin_models.Space) error { 218 retVal.Name = cmd.cliConfig.SpaceFields().Name 219 retVal.Guid = cmd.cliConfig.SpaceFields().GUID 220 221 return nil 222 } 223 224 func (cmd *CliRpcCmd) Username(args string, retVal *string) error { 225 *retVal = cmd.cliConfig.Username() 226 227 return nil 228 } 229 230 func (cmd *CliRpcCmd) UserGuid(args string, retVal *string) error { 231 *retVal = cmd.cliConfig.UserGUID() 232 233 return nil 234 } 235 236 func (cmd *CliRpcCmd) UserEmail(args string, retVal *string) error { 237 *retVal = cmd.cliConfig.UserEmail() 238 239 return nil 240 } 241 242 func (cmd *CliRpcCmd) IsLoggedIn(args string, retVal *bool) error { 243 *retVal = cmd.cliConfig.IsLoggedIn() 244 245 return nil 246 } 247 248 func (cmd *CliRpcCmd) IsSSLDisabled(args string, retVal *bool) error { 249 *retVal = cmd.cliConfig.IsSSLDisabled() 250 251 return nil 252 } 253 254 func (cmd *CliRpcCmd) HasOrganization(args string, retVal *bool) error { 255 *retVal = cmd.cliConfig.HasOrganization() 256 257 return nil 258 } 259 260 func (cmd *CliRpcCmd) HasSpace(args string, retVal *bool) error { 261 *retVal = cmd.cliConfig.HasSpace() 262 263 return nil 264 } 265 266 func (cmd *CliRpcCmd) ApiEndpoint(args string, retVal *string) error { 267 *retVal = cmd.cliConfig.APIEndpoint() 268 269 return nil 270 } 271 272 func (cmd *CliRpcCmd) HasAPIEndpoint(args string, retVal *bool) error { 273 *retVal = cmd.cliConfig.HasAPIEndpoint() 274 275 return nil 276 } 277 278 func (cmd *CliRpcCmd) ApiVersion(args string, retVal *string) error { 279 *retVal = cmd.cliConfig.APIVersion() 280 281 return nil 282 } 283 284 func (cmd *CliRpcCmd) LoggregatorEndpoint(args string, retVal *string) error { 285 *retVal = "" 286 287 return nil 288 } 289 290 func (cmd *CliRpcCmd) DopplerEndpoint(args string, retVal *string) error { 291 *retVal = cmd.cliConfig.DopplerEndpoint() 292 293 return nil 294 } 295 296 func (cmd *CliRpcCmd) AccessToken(args string, retVal *string) error { 297 token, err := cmd.repoLocator.GetAuthenticationRepository().RefreshAuthToken() 298 if err != nil { 299 return err 300 } 301 302 *retVal = token 303 304 return nil 305 } 306 307 func (cmd *CliRpcCmd) GetApp(appName string, retVal *plugin_models.GetAppModel) error { 308 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 309 310 //set deps objs to be the one used by all other commands 311 //once all commands are converted, we can make fresh deps for each command run 312 deps.Config = cmd.cliConfig 313 deps.RepoLocator = cmd.repoLocator 314 deps.PluginModels.Application = retVal 315 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 316 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 317 318 return cmd.newCmdRunner.Command([]string{"app", appName}, deps, true) 319 } 320 321 func (cmd *CliRpcCmd) GetApps(_ string, retVal *[]plugin_models.GetAppsModel) error { 322 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 323 324 //set deps objs to be the one used by all other commands 325 //once all commands are converted, we can make fresh deps for each command run 326 deps.Config = cmd.cliConfig 327 deps.RepoLocator = cmd.repoLocator 328 deps.PluginModels.AppsSummary = retVal 329 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 330 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 331 332 return cmd.newCmdRunner.Command([]string{"apps"}, deps, true) 333 } 334 335 func (cmd *CliRpcCmd) GetOrgs(_ string, retVal *[]plugin_models.GetOrgs_Model) error { 336 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 337 338 //set deps objs to be the one used by all other commands 339 //once all commands are converted, we can make fresh deps for each command run 340 deps.Config = cmd.cliConfig 341 deps.RepoLocator = cmd.repoLocator 342 deps.PluginModels.Organizations = retVal 343 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 344 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 345 346 return cmd.newCmdRunner.Command([]string{"orgs"}, deps, true) 347 } 348 349 func (cmd *CliRpcCmd) GetSpaces(_ string, retVal *[]plugin_models.GetSpaces_Model) error { 350 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 351 352 //set deps objs to be the one used by all other commands 353 //once all commands are converted, we can make fresh deps for each command run 354 deps.Config = cmd.cliConfig 355 deps.RepoLocator = cmd.repoLocator 356 deps.PluginModels.Spaces = retVal 357 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 358 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 359 360 return cmd.newCmdRunner.Command([]string{"spaces"}, deps, true) 361 } 362 363 func (cmd *CliRpcCmd) GetServices(_ string, retVal *[]plugin_models.GetServices_Model) error { 364 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 365 366 //set deps objs to be the one used by all other commands 367 //once all commands are converted, we can make fresh deps for each command run 368 //once all commands are converted, we can make fresh deps for each command run 369 deps.Config = cmd.cliConfig 370 deps.RepoLocator = cmd.repoLocator 371 deps.PluginModels.Services = retVal 372 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 373 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 374 375 return cmd.newCmdRunner.Command([]string{"services"}, deps, true) 376 } 377 378 func (cmd *CliRpcCmd) GetOrgUsers(args []string, retVal *[]plugin_models.GetOrgUsers_Model) error { 379 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 380 381 //set deps objs to be the one used by all other commands 382 //once all commands are converted, we can make fresh deps for each command run 383 deps.Config = cmd.cliConfig 384 deps.RepoLocator = cmd.repoLocator 385 deps.PluginModels.OrgUsers = retVal 386 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 387 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 388 389 return cmd.newCmdRunner.Command(append([]string{"org-users"}, args...), deps, true) 390 } 391 392 func (cmd *CliRpcCmd) GetSpaceUsers(args []string, retVal *[]plugin_models.GetSpaceUsers_Model) error { 393 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 394 395 //set deps objs to be the one used by all other commands 396 //once all commands are converted, we can make fresh deps for each command run 397 deps.Config = cmd.cliConfig 398 deps.RepoLocator = cmd.repoLocator 399 deps.PluginModels.SpaceUsers = retVal 400 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 401 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 402 403 return cmd.newCmdRunner.Command(append([]string{"space-users"}, args...), deps, true) 404 } 405 406 func (cmd *CliRpcCmd) GetOrg(orgName string, retVal *plugin_models.GetOrg_Model) error { 407 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 408 409 //set deps objs to be the one used by all other commands 410 //once all commands are converted, we can make fresh deps for each command run 411 deps.Config = cmd.cliConfig 412 deps.RepoLocator = cmd.repoLocator 413 deps.PluginModels.Organization = retVal 414 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 415 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 416 417 return cmd.newCmdRunner.Command([]string{"org", orgName}, deps, true) 418 } 419 420 func (cmd *CliRpcCmd) GetSpace(spaceName string, retVal *plugin_models.GetSpace_Model) error { 421 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 422 423 //set deps objs to be the one used by all other commands 424 //once all commands are converted, we can make fresh deps for each command run 425 deps.Config = cmd.cliConfig 426 deps.RepoLocator = cmd.repoLocator 427 deps.PluginModels.Space = retVal 428 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 429 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 430 431 return cmd.newCmdRunner.Command([]string{"space", spaceName}, deps, true) 432 } 433 434 func (cmd *CliRpcCmd) GetService(serviceInstance string, retVal *plugin_models.GetService_Model) error { 435 deps := commandregistry.NewDependency(cmd.stdout, cmd.logger, dialTimeout) 436 437 //set deps objs to be the one used by all other commands 438 //once all commands are converted, we can make fresh deps for each command run 439 deps.Config = cmd.cliConfig 440 deps.RepoLocator = cmd.repoLocator 441 deps.PluginModels.Service = retVal 442 cmd.terminalOutputSwitch.DisableTerminalOutput(true) 443 deps.UI = terminal.NewUI(os.Stdin, cmd.stdout, cmd.terminalOutputSwitch.(*terminal.TeePrinter), cmd.logger) 444 445 return cmd.newCmdRunner.Command([]string{"service", serviceInstance}, deps, true) 446 }