github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/cmd/service.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "log" 8 "os" 9 "os/signal" 10 "strings" 11 "time" 12 13 psutils "github.com/shirou/gopsutil/process" 14 "github.com/spf13/cobra" 15 "github.com/spf13/viper" 16 "github.com/turbot/go-kit/helpers" 17 "github.com/turbot/steampipe-plugin-sdk/v5/sperr" 18 "github.com/turbot/steampipe/pkg/cmdconfig" 19 "github.com/turbot/steampipe/pkg/constants" 20 "github.com/turbot/steampipe/pkg/dashboard/dashboardserver" 21 "github.com/turbot/steampipe/pkg/db/db_local" 22 "github.com/turbot/steampipe/pkg/display" 23 "github.com/turbot/steampipe/pkg/error_helpers" 24 "github.com/turbot/steampipe/pkg/filepaths" 25 "github.com/turbot/steampipe/pkg/pluginmanager" 26 pb "github.com/turbot/steampipe/pkg/pluginmanager_service/grpc/proto" 27 "github.com/turbot/steampipe/pkg/statushooks" 28 "github.com/turbot/steampipe/pkg/utils" 29 ) 30 31 func serviceCmd() *cobra.Command { 32 var cmd = &cobra.Command{ 33 Use: "service [command]", 34 Args: cobra.NoArgs, 35 Short: "Steampipe service management", 36 Long: `Steampipe service management. 37 38 Run Steampipe as a local service, exposing it as a database endpoint for 39 connection from any Postgres compatible database client.`, 40 } 41 42 cmd.AddCommand(serviceStartCmd()) 43 cmd.AddCommand(serviceStatusCmd()) 44 cmd.AddCommand(serviceStopCmd()) 45 cmd.AddCommand(serviceRestartCmd()) 46 cmd.Flags().BoolP(constants.ArgHelp, "h", false, "Help for service") 47 return cmd 48 } 49 50 // handler for service start 51 func serviceStartCmd() *cobra.Command { 52 var cmd = &cobra.Command{ 53 Use: "start", 54 Args: cobra.NoArgs, 55 Run: runServiceStartCmd, 56 Short: "Start Steampipe in service mode", 57 Long: `Start the Steampipe service. 58 59 Run Steampipe as a local service, exposing it as a database endpoint for 60 connection from any Postgres compatible database client.`, 61 } 62 63 cmdconfig. 64 OnCmd(cmd). 65 AddModLocationFlag(). 66 AddBoolFlag(constants.ArgHelp, false, "Help for service start", cmdconfig.FlagOptions.WithShortHand("h")). 67 AddIntFlag(constants.ArgDatabasePort, constants.DatabaseDefaultPort, "Database service port"). 68 AddStringFlag(constants.ArgDatabaseListenAddresses, string(db_local.ListenTypeNetwork), "Accept connections from: `local` (an alias for `localhost` only), `network` (an alias for `*`), or a comma separated list of hosts and/or IP addresses"). 69 AddStringFlag(constants.ArgServicePassword, "", "Set the database password for this session"). 70 // default is false and hides the database user password from service start prompt 71 AddBoolFlag(constants.ArgServiceShowPassword, false, "View database password for connecting from another machine"). 72 // dashboard server 73 AddBoolFlag(constants.ArgDashboard, false, "Run the dashboard webserver with the service"). 74 AddStringFlag(constants.ArgDashboardListen, string(dashboardserver.ListenTypeNetwork), "Accept connections from: local (localhost only) or network (open) (dashboard)"). 75 AddIntFlag(constants.ArgDashboardPort, constants.DashboardServerDefaultPort, "Report server port"). 76 // foreground enables the service to run in the foreground - till exit 77 AddBoolFlag(constants.ArgForeground, false, "Run the service in the foreground"). 78 79 // flags relevant only if the --dashboard arg is used: 80 AddStringSliceFlag(constants.ArgVarFile, nil, "Specify an .spvar file containing variable values (only applies if '--dashboard' flag is also set)"). 81 // NOTE: use StringArrayFlag for ArgVariable, not StringSliceFlag 82 // Cobra will interpret values passed to a StringSliceFlag as CSV, 83 // where args passed to StringArrayFlag are not parsed and used raw 84 AddStringArrayFlag(constants.ArgVariable, nil, "Specify the value of a variable (only applies if '--dashboard' flag is also set)"). 85 86 // hidden flags for internal use 87 AddStringFlag(constants.ArgInvoker, string(constants.InvokerService), "Invoked by \"service\" or \"query\"", cmdconfig.FlagOptions.Hidden()) 88 89 return cmd 90 } 91 92 // serviceStatusCmd :: handler for service status 93 func serviceStatusCmd() *cobra.Command { 94 var cmd = &cobra.Command{ 95 Use: "status", 96 Args: cobra.NoArgs, 97 Run: runServiceStatusCmd, 98 Short: "Status of the Steampipe service", 99 Long: `Status of the Steampipe service. 100 101 Report current status of the Steampipe database service.`, 102 } 103 104 cmdconfig.OnCmd(cmd). 105 AddBoolFlag(constants.ArgHelp, false, "Help for service status", cmdconfig.FlagOptions.WithShortHand("h")). 106 // default is false and hides the database user password from service start prompt 107 AddBoolFlag(constants.ArgServiceShowPassword, false, "View database password for connecting from another machine"). 108 AddBoolFlag(constants.ArgAll, false, "Bypasses the INSTALL_DIR and reports status of all running steampipe services") 109 110 return cmd 111 } 112 113 // handler for service stop 114 func serviceStopCmd() *cobra.Command { 115 cmd := &cobra.Command{ 116 Use: "stop", 117 Args: cobra.NoArgs, 118 Run: runServiceStopCmd, 119 Short: "Stop Steampipe service", 120 Long: `Stop the Steampipe service.`, 121 } 122 123 cmdconfig. 124 OnCmd(cmd). 125 AddBoolFlag(constants.ArgHelp, false, "Help for service stop", cmdconfig.FlagOptions.WithShortHand("h")). 126 AddBoolFlag(constants.ArgForce, false, "Forces all services to shutdown, releasing all open connections and ports") 127 128 return cmd 129 } 130 131 // restarts the database service 132 func serviceRestartCmd() *cobra.Command { 133 var cmd = &cobra.Command{ 134 Use: "restart", 135 Args: cobra.NoArgs, 136 Run: runServiceRestartCmd, 137 Short: "Restart Steampipe service", 138 Long: `Restart the Steampipe service.`, 139 } 140 141 cmdconfig. 142 OnCmd(cmd). 143 AddBoolFlag(constants.ArgHelp, false, "Help for service restart", cmdconfig.FlagOptions.WithShortHand("h")). 144 AddBoolFlag(constants.ArgForce, false, "Forces the service to restart, releasing all open connections and ports") 145 146 return cmd 147 } 148 149 func runServiceStartCmd(cmd *cobra.Command, _ []string) { 150 ctx := cmd.Context() 151 utils.LogTime("runServiceStartCmd start") 152 defer func() { 153 utils.LogTime("runServiceStartCmd end") 154 if r := recover(); r != nil { 155 error_helpers.ShowError(ctx, helpers.ToError(r)) 156 if exitCode == constants.ExitCodeSuccessful { 157 // there was an error and the exitcode 158 // was not set to a non-zero value. 159 // set it 160 exitCode = constants.ExitCodeUnknownErrorPanic 161 } 162 } 163 }() 164 165 ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, os.Kill) 166 defer cancel() 167 168 listenAddresses := db_local.StartListenType(viper.GetString(constants.ArgDatabaseListenAddresses)).ToListenAddresses() 169 170 port := viper.GetInt(constants.ArgDatabasePort) 171 if port < 1 || port > 65535 { 172 exitCode = constants.ExitCodeInsufficientOrWrongInputs 173 panic("Invalid port - must be within range (1:65535)") 174 } 175 176 invoker := constants.Invoker(cmdconfig.Viper().GetString(constants.ArgInvoker)) 177 if invoker.IsValid() != nil { 178 exitCode = constants.ExitCodeInsufficientOrWrongInputs 179 error_helpers.FailOnError(invoker.IsValid()) 180 } 181 182 startResult, dashboardState, dbServiceStarted := startService(ctx, listenAddresses, port, invoker) 183 alreadyRunning := !dbServiceStarted 184 185 printStatus(ctx, startResult.DbState, startResult.PluginManagerState, dashboardState, alreadyRunning) 186 187 if viper.GetBool(constants.ArgForeground) { 188 runServiceInForeground(ctx) 189 } 190 } 191 192 func startService(ctx context.Context, listenAddresses []string, port int, invoker constants.Invoker) (_ *db_local.StartResult, _ *dashboardserver.DashboardServiceState, dbServiceStarted bool) { 193 statushooks.Show(ctx) 194 defer statushooks.Done(ctx) 195 log.Printf("[TRACE] startService - listenAddresses=%q", listenAddresses) 196 197 err := db_local.EnsureDBInstalled(ctx) 198 if err != nil { 199 exitCode = constants.ExitCodeServiceStartupFailure 200 error_helpers.FailOnError(err) 201 } 202 203 // start db, refreshing connections 204 startResult := startServiceAndRefreshConnections(ctx, listenAddresses, port, invoker) 205 if startResult.Status == db_local.ServiceFailedToStart { 206 error_helpers.ShowError(ctx, sperr.New("steampipe service failed to start")) 207 exitCode = constants.ExitCodeServiceStartupFailure 208 return 209 } 210 211 // if the service is already running, then service start should make the service persistent 212 if startResult.Status == db_local.ServiceAlreadyRunning { 213 // check that we have the same port and listen parameters 214 if port != startResult.DbState.Port { 215 exitCode = constants.ExitCodeInsufficientOrWrongInputs 216 error_helpers.FailOnError(sperr.New("service is already running on port %d - cannot change port while it's running", startResult.DbState.Port)) 217 } 218 if !startResult.DbState.MatchWithGivenListenAddresses(listenAddresses) { 219 exitCode = constants.ExitCodeInsufficientOrWrongInputs 220 // this messaging assumes that the resolved addresses from the given addresses have not changed while the service is running 221 // although this is an edge case, ideally, we should check for the resolved addresses and give the relevant message 222 error_helpers.FailOnError(sperr.New("service is already running and listening on %s - cannot change listen address while it's running", strings.Join(startResult.DbState.ResolvedListenAddresses, ", "))) 223 } 224 225 // convert to being invoked by service 226 startResult.DbState.Invoker = constants.InvokerService 227 err = startResult.DbState.Save() 228 if err != nil { 229 exitCode = constants.ExitCodeFileSystemAccessFailure 230 error_helpers.FailOnErrorWithMessage(err, "service was already running, but could not make it persistent") 231 } 232 } 233 234 dbServiceStarted = startResult.Status == db_local.ServiceStarted 235 236 var dashboardState *dashboardserver.DashboardServiceState 237 if viper.GetBool(constants.ArgDashboard) { 238 dashboardState, err = dashboardserver.GetDashboardServiceState() 239 if err != nil { 240 tryToStopServices(ctx) 241 exitCode = constants.ExitCodeServiceStartupFailure 242 error_helpers.FailOnError(err) 243 } 244 if dashboardState == nil { 245 dashboardState, err = startDashboardServer(ctx) 246 if err != nil { 247 tryToStopServices(ctx) 248 exitCode = constants.ExitCodeServiceStartupFailure 249 error_helpers.FailOnError(err) 250 } 251 dbServiceStarted = true 252 } 253 } 254 return startResult, dashboardState, dbServiceStarted 255 } 256 257 func startServiceAndRefreshConnections(ctx context.Context, listenAddresses []string, port int, invoker constants.Invoker) *db_local.StartResult { 258 startResult := db_local.StartServices(ctx, listenAddresses, port, invoker) 259 if startResult.Error != nil { 260 exitCode = constants.ExitCodeServiceStartupFailure 261 error_helpers.FailOnError(startResult.Error) 262 } 263 264 if startResult.Status == db_local.ServiceStarted { 265 // ask the plugin manager to refresh connections 266 // this is executed asyncronously by the plugin manager 267 // we ignore this error, since RefreshConnections is async and all errors will flow through 268 // the notification system 269 // we do not expect any I/O errors on this since the PluginManager is running in the same box 270 _, _ = startResult.PluginManager.RefreshConnections(&pb.RefreshConnectionsRequest{}) 271 } 272 return startResult 273 } 274 275 func tryToStopServices(ctx context.Context) { 276 // stop db service 277 if _, err := db_local.StopServices(ctx, false, constants.InvokerService); err != nil { 278 error_helpers.ShowError(ctx, err) 279 } 280 // stop the dashboard service 281 if err := dashboardserver.StopDashboardService(ctx); err != nil { 282 error_helpers.ShowError(ctx, err) 283 } 284 } 285 286 func startDashboardServer(ctx context.Context) (*dashboardserver.DashboardServiceState, error) { 287 var dashboardState *dashboardserver.DashboardServiceState 288 var err error 289 290 serverPort := dashboardserver.ListenPort(viper.GetInt(constants.ArgDashboardPort)) 291 serverListen := dashboardserver.ListenType(viper.GetString(constants.ArgDashboardListen)) 292 293 dashboardState, err = dashboardserver.GetDashboardServiceState() 294 if err != nil { 295 return nil, err 296 } 297 298 if dashboardState == nil { 299 // try stopping the previous service 300 // StopDashboardService does nothing if the service is not running 301 err = dashboardserver.StopDashboardService(ctx) 302 if err != nil { 303 return nil, err 304 } 305 // start dashboard service 306 err = dashboardserver.RunForService(ctx, serverListen, serverPort) 307 if err != nil { 308 return nil, err 309 } 310 // get the updated state 311 dashboardState, err = dashboardserver.GetDashboardServiceState() 312 if err != nil { 313 error_helpers.ShowWarning(fmt.Sprintf("Started Dashboard server, but could not retrieve state: %v", err)) 314 } 315 } 316 317 return dashboardState, err 318 } 319 320 func runServiceInForeground(ctx context.Context) { 321 fmt.Println("Hit Ctrl+C to stop the service") 322 323 sigIntChannel := make(chan os.Signal, 1) 324 signal.Notify(sigIntChannel, os.Interrupt) 325 326 checkTimer := time.NewTicker(100 * time.Millisecond) 327 defer checkTimer.Stop() 328 329 var lastCtrlC time.Time 330 331 for { 332 select { 333 case <-checkTimer.C: 334 // get the current status 335 newInfo, err := db_local.GetState() 336 if err != nil { 337 continue 338 } 339 if newInfo == nil { 340 fmt.Println("Steampipe service stopped.") 341 return 342 } 343 case <-sigIntChannel: 344 fmt.Print("\r") 345 dashboardserver.StopDashboardService(ctx) 346 // if we have received this signal, then the user probably wants to shut down 347 // everything. Shutdowns MUST NOT happen in cancellable contexts 348 connectedClients, err := db_local.GetClientCount(context.Background()) 349 if err != nil { 350 // report the error in the off chance that there's one 351 error_helpers.ShowError(ctx, err) 352 return 353 } 354 355 // we know there will be at least 1 client (connectionWatcher) 356 if connectedClients.TotalClients > 1 { 357 if lastCtrlC.IsZero() || time.Since(lastCtrlC) > 30*time.Second { 358 lastCtrlC = time.Now() 359 fmt.Println(buildForegroundClientsConnectedMsg()) 360 continue 361 } 362 } 363 fmt.Println("Stopping Steampipe service.") 364 if _, err := db_local.StopServices(ctx, false, constants.InvokerService); err != nil { 365 error_helpers.ShowError(ctx, err) 366 } else { 367 fmt.Println("Steampipe service stopped.") 368 } 369 return 370 } 371 } 372 } 373 374 func runServiceRestartCmd(cmd *cobra.Command, _ []string) { 375 ctx := cmd.Context() 376 utils.LogTime("runServiceRestartCmd start") 377 defer func() { 378 utils.LogTime("runServiceRestartCmd end") 379 if r := recover(); r != nil { 380 error_helpers.ShowError(ctx, helpers.ToError(r)) 381 if exitCode == constants.ExitCodeSuccessful { 382 // there was an error and the exitcode 383 // was not set to a non-zero value. 384 // set it 385 exitCode = constants.ExitCodeUnknownErrorPanic 386 } 387 } 388 }() 389 390 dbStartResult, currentDashboardState := restartService(ctx) 391 392 if dbStartResult != nil { 393 printStatus(ctx, dbStartResult.DbState, dbStartResult.PluginManagerState, currentDashboardState, false) 394 } 395 } 396 397 func restartService(ctx context.Context) (_ *db_local.StartResult, _ *dashboardserver.DashboardServiceState) { 398 statushooks.Show(ctx) 399 defer statushooks.Done(ctx) 400 401 // get current db statue 402 currentDbState, err := db_local.GetState() 403 error_helpers.FailOnError(err) 404 if currentDbState == nil { 405 fmt.Println("Steampipe service is not running.") 406 return 407 } 408 409 // along with the current dashboard state - maybe nil 410 currentDashboardState, err := dashboardserver.GetDashboardServiceState() 411 error_helpers.FailOnError(err) 412 413 // stop db 414 stopStatus, err := db_local.StopServices(ctx, viper.GetBool(constants.ArgForce), constants.InvokerService) 415 if err != nil { 416 exitCode = constants.ExitCodeServiceStopFailure 417 error_helpers.FailOnErrorWithMessage(err, "could not stop current instance") 418 } 419 420 if stopStatus != db_local.ServiceStopped { 421 fmt.Println(` 422 Service stop failed. 423 424 Try using: 425 steampipe service restart --force 426 427 to force a restart. 428 `) 429 return 430 } 431 432 // stop the running dashboard server 433 err = dashboardserver.StopDashboardService(ctx) 434 if err != nil { 435 exitCode = constants.ExitCodeServiceStopFailure 436 error_helpers.FailOnErrorWithMessage(err, "could not stop dashboard service") 437 } 438 439 // the DB must be installed and therefore is a noop, 440 // and EnsureDBInstalled also checks and installs the latest FDW 441 err = db_local.EnsureDBInstalled(ctx) 442 if err != nil { 443 exitCode = constants.ExitCodeServiceStartupFailure 444 error_helpers.FailOnError(err) 445 } 446 447 // set the password in 'viper' so that it can be used by 'service start' 448 viper.Set(constants.ArgServicePassword, currentDbState.Password) 449 450 // start db 451 dbStartResult := startServiceAndRefreshConnections(ctx, currentDbState.ResolvedListenAddresses, currentDbState.Port, currentDbState.Invoker) 452 if dbStartResult.Status == db_local.ServiceFailedToStart { 453 exitCode = constants.ExitCodeServiceStartupFailure 454 fmt.Println("Steampipe service was stopped, but failed to restart.") 455 return 456 } 457 458 // if the dashboard was running, start it 459 if currentDashboardState != nil { 460 err = dashboardserver.RunForService(ctx, dashboardserver.ListenType(currentDashboardState.ListenType), dashboardserver.ListenPort(currentDashboardState.Port)) 461 error_helpers.FailOnError(err) 462 463 // reload the state 464 currentDashboardState, err = dashboardserver.GetDashboardServiceState() 465 error_helpers.FailOnError(err) 466 } 467 468 return dbStartResult, currentDashboardState 469 } 470 471 func runServiceStatusCmd(cmd *cobra.Command, _ []string) { 472 ctx := cmd.Context() 473 utils.LogTime("runServiceStatusCmd status") 474 defer func() { 475 utils.LogTime("runServiceStatusCmd end") 476 if r := recover(); r != nil { 477 error_helpers.ShowError(ctx, helpers.ToError(r)) 478 } 479 }() 480 481 if !db_local.IsDBInstalled() || !db_local.IsFDWInstalled() { 482 fmt.Println("Steampipe service is not installed.") 483 return 484 } 485 486 if viper.GetBool(constants.ArgAll) { 487 showAllStatus(ctx) 488 } else { 489 dbState, dbStateErr := db_local.GetState() 490 pmState, pmStateErr := pluginmanager.LoadState() 491 dashboardState, dashboardStateErr := dashboardserver.GetDashboardServiceState() 492 493 if dbStateErr != nil || pmStateErr != nil { 494 error_helpers.ShowError(ctx, composeStateError(dbStateErr, pmStateErr, dashboardStateErr)) 495 return 496 } 497 printStatus(ctx, dbState, pmState, dashboardState, false) 498 } 499 } 500 501 func composeStateError(dbStateErr error, pmStateErr error, dashboardStateErr error) error { 502 msg := "could not get Steampipe service status:" 503 504 if dbStateErr != nil { 505 msg = fmt.Sprintf(`%s 506 failed to get db state: %s`, msg, dbStateErr.Error()) 507 } 508 if pmStateErr != nil { 509 msg = fmt.Sprintf(`%s 510 failed to get plugin manager state: %s`, msg, pmStateErr.Error()) 511 } 512 if dashboardStateErr != nil { 513 msg = fmt.Sprintf(`%s 514 failed to get dashboard server state: %s`, msg, pmStateErr.Error()) 515 } 516 517 return errors.New(msg) 518 } 519 520 func runServiceStopCmd(cmd *cobra.Command, _ []string) { 521 ctx := cmd.Context() 522 utils.LogTime("runServiceStopCmd stop") 523 524 var status db_local.StopStatus 525 var dbStopError error 526 var dbState *db_local.RunningDBInstanceInfo 527 528 defer func() { 529 utils.LogTime("runServiceStopCmd end") 530 if r := recover(); r != nil { 531 error_helpers.ShowError(ctx, helpers.ToError(r)) 532 if exitCode == constants.ExitCodeSuccessful { 533 // there was an error and the exitcode 534 // was not set to a non-zero value. 535 // set it 536 exitCode = constants.ExitCodeUnknownErrorPanic 537 } 538 } 539 }() 540 541 force := cmdconfig.Viper().GetBool(constants.ArgForce) 542 if force { 543 dashboardStopError := dashboardserver.StopDashboardService(ctx) 544 status, dbStopError = db_local.StopServices(ctx, force, constants.InvokerService) 545 dbStopError = error_helpers.CombineErrors(dbStopError, dashboardStopError) 546 if dbStopError != nil { 547 exitCode = constants.ExitCodeServiceStopFailure 548 error_helpers.FailOnError(dbStopError) 549 } 550 } else { 551 dbState, dbStopError = db_local.GetState() 552 if dbStopError != nil { 553 exitCode = constants.ExitCodeServiceStopFailure 554 error_helpers.FailOnErrorWithMessage(dbStopError, "could not stop Steampipe service") 555 } 556 557 dashboardState, err := dashboardserver.GetDashboardServiceState() 558 if err != nil { 559 exitCode = constants.ExitCodeServiceStopFailure 560 error_helpers.FailOnErrorWithMessage(err, "could not stop Steampipe service") 561 } 562 563 if dbState == nil { 564 fmt.Println("Steampipe service is not running.") 565 return 566 } 567 if dbState.Invoker != constants.InvokerService { 568 printRunningImplicit(dbState.Invoker) 569 return 570 } 571 572 if dashboardState != nil { 573 err = dashboardserver.StopDashboardService(ctx) 574 if err != nil { 575 exitCode = constants.ExitCodeServiceStopFailure 576 error_helpers.FailOnErrorWithMessage(err, "could not stop dashboard server") 577 } 578 } 579 580 // check if there are any connected clients to the service 581 connectedClients, err := db_local.GetClientCount(ctx) 582 if err != nil { 583 exitCode = constants.ExitCodeServiceStopFailure 584 error_helpers.FailOnErrorWithMessage(err, "service stop failed") 585 } 586 587 // if there are any clients connected (apart from plugin manager clients), do not exit 588 if connectedClients.TotalClients-connectedClients.PluginManagerClients > 0 { 589 printClientsConnected() 590 return 591 } 592 593 status, err = db_local.StopServices(ctx, false, constants.InvokerService) 594 if err != nil { 595 exitCode = constants.ExitCodeServiceStopFailure 596 error_helpers.FailOnErrorWithMessage(err, "service stop failed") 597 } 598 } 599 600 switch status { 601 case db_local.ServiceStopped: 602 fmt.Println("Steampipe database service stopped.") 603 case db_local.ServiceNotRunning: 604 fmt.Println("Steampipe service is not running.") 605 case db_local.ServiceStopFailed: 606 fmt.Println("Could not stop Steampipe service.") 607 case db_local.ServiceStopTimedOut: 608 fmt.Println(` 609 Service stop operation timed-out. 610 611 This is probably because other clients are connected to the database service. 612 613 Disconnect all clients, or use 614 steampipe service stop --force 615 616 to force a shutdown. 617 `) 618 619 } 620 } 621 622 func showAllStatus(ctx context.Context) { 623 var processes []*psutils.Process 624 var err error 625 626 statushooks.SetStatus(ctx, "Getting details") 627 processes, err = db_local.FindAllSteampipePostgresInstances(ctx) 628 statushooks.Done(ctx) 629 630 error_helpers.FailOnError(err) 631 632 if len(processes) == 0 { 633 fmt.Println("There are no steampipe services running.") 634 return 635 } 636 headers := []string{"PID", "Install Directory", "Port", "Listen"} 637 rows := [][]string{} 638 639 for _, process := range processes { 640 pid, installDir, port, listen := getServiceProcessDetails(process) 641 rows = append(rows, []string{pid, installDir, port, string(listen)}) 642 } 643 644 display.ShowWrappedTable(headers, rows, &display.ShowWrappedTableOptions{AutoMerge: false}) 645 } 646 647 func getServiceProcessDetails(process *psutils.Process) (string, string, string, db_local.StartListenType) { 648 cmdLine, _ := process.CmdlineSlice() 649 installDir := strings.TrimSuffix(cmdLine[0], filepaths.ServiceExecutableRelativeLocation()) 650 var port string 651 var listenType db_local.StartListenType 652 653 for idx, param := range cmdLine { 654 if param == "-p" { 655 port = cmdLine[idx+1] 656 } 657 if strings.HasPrefix(param, "listen_addresses") { 658 if strings.Contains(param, "localhost") { 659 listenType = db_local.ListenTypeLocal 660 } else { 661 listenType = db_local.ListenTypeNetwork 662 } 663 } 664 } 665 666 return fmt.Sprintf("%d", process.Pid), installDir, port, listenType 667 } 668 669 func printStatus(ctx context.Context, dbState *db_local.RunningDBInstanceInfo, pmState *pluginmanager.State, dashboardState *dashboardserver.DashboardServiceState, alreadyRunning bool) { 670 if dbState == nil && !pmState.Running { 671 fmt.Println("Service is not running") 672 return 673 } 674 675 var statusMessage string 676 677 prefix := `Steampipe service is running: 678 ` 679 if alreadyRunning { 680 prefix = `Steampipe service is already running: 681 ` 682 } 683 suffix := ` 684 Managing the Steampipe service: 685 686 # Get status of the service 687 steampipe service status 688 689 # View database password for connecting from another machine 690 steampipe service status --show-password 691 692 # Restart the service 693 steampipe service restart 694 695 # Stop the service 696 steampipe service stop 697 ` 698 699 var connectionStr string 700 var password string 701 if viper.GetBool(constants.ArgServiceShowPassword) { 702 connectionStr = fmt.Sprintf( 703 "postgres://%v:%v@%v:%v/%v", 704 dbState.User, 705 dbState.Password, 706 utils.GetFirstListenAddress(dbState.ResolvedListenAddresses), 707 dbState.Port, 708 dbState.Database, 709 ) 710 password = dbState.Password 711 } else { 712 connectionStr = fmt.Sprintf( 713 "postgres://%v@%v:%v/%v", 714 dbState.User, 715 utils.GetFirstListenAddress(dbState.ResolvedListenAddresses), 716 dbState.Port, 717 dbState.Database, 718 ) 719 password = "********* [use --show-password to reveal]" 720 } 721 722 postgresFmt := ` 723 Database: 724 725 Host(s): %v 726 Port: %v 727 Database: %v 728 User: %v 729 Password: %v 730 Connection string: %v 731 ` 732 postgresMsg := fmt.Sprintf( 733 postgresFmt, 734 strings.Join(dbState.ResolvedListenAddresses, ", "), 735 dbState.Port, 736 dbState.Database, 737 dbState.User, 738 password, 739 connectionStr, 740 ) 741 742 dashboardMsg := "" 743 744 if dashboardState != nil { 745 browserUrl := fmt.Sprintf("http://%s:%d/", dashboardState.Listen[0], dashboardState.Port) 746 dashboardMsg = fmt.Sprintf(` 747 Dashboard: 748 749 Host(s): %v 750 Port: %v 751 URL: %v 752 `, strings.Join(dashboardState.Listen, ", "), dashboardState.Port, browserUrl) 753 } 754 755 if dbState.Invoker == constants.InvokerService { 756 statusMessage = fmt.Sprintf( 757 "%s%s%s%s", 758 prefix, 759 postgresMsg, 760 dashboardMsg, 761 suffix, 762 ) 763 } else { 764 msg := ` 765 Steampipe service was started for an active %s session. The service will exit when all active sessions exit. 766 767 To keep the service running after the %s session completes, use %s. 768 ` 769 770 statusMessage = fmt.Sprintf( 771 msg, 772 fmt.Sprintf("steampipe %s", dbState.Invoker), 773 dbState.Invoker, 774 constants.Bold("steampipe service start"), 775 ) 776 } 777 778 fmt.Println(statusMessage) 779 780 if dbState != nil && pmState == nil { 781 // the service is running, but the plugin_manager is not running and there's no state file 782 // meaning that it cannot be restarted by the FDW 783 // it's an ERROR 784 error_helpers.ShowError(ctx, sperr.New(` 785 Service is running, but the Plugin Manager cannot be recovered. 786 Please use %s to recover the service 787 `, 788 constants.Bold("steampipe service restart"), 789 )) 790 } 791 } 792 793 func printRunningImplicit(invoker constants.Invoker) { 794 fmt.Printf(` 795 Steampipe service is running exclusively for an active %s session. 796 797 To force stop the service, use %s 798 799 `, 800 fmt.Sprintf("steampipe %s", invoker), 801 constants.Bold("steampipe service stop --force"), 802 ) 803 } 804 805 func printClientsConnected() { 806 fmt.Printf( 807 ` 808 Cannot stop service since there are clients connected to the service. 809 810 To force stop the service, use %s 811 812 `, 813 constants.Bold("steampipe service stop --force"), 814 ) 815 } 816 817 func buildForegroundClientsConnectedMsg() string { 818 return ` 819 Not shutting down service as there as clients connected. 820 821 To force shutdown, press Ctrl+C again. 822 ` 823 }