github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/pluginmanager/lifecycle.go (about) 1 package pluginmanager 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "os/exec" 8 "syscall" 9 10 "github.com/hashicorp/go-hclog" 11 "github.com/hashicorp/go-plugin" 12 "github.com/turbot/steampipe-plugin-sdk/v5/logging" 13 "github.com/turbot/steampipe-plugin-sdk/v5/sperr" 14 "github.com/turbot/steampipe/pkg/constants" 15 "github.com/turbot/steampipe/pkg/filepaths" 16 "github.com/turbot/steampipe/pkg/pluginmanager_service/grpc" 17 pb "github.com/turbot/steampipe/pkg/pluginmanager_service/grpc/proto" 18 pluginshared "github.com/turbot/steampipe/pkg/pluginmanager_service/grpc/shared" 19 ) 20 21 // StartNewInstance loads the plugin manager state, stops any previous instance and instantiates a new plugin manager 22 func StartNewInstance(steampipeExecutablePath string) (*State, error) { 23 // try to load the plugin manager state 24 state, err := LoadState() 25 if err != nil { 26 log.Printf("[WARN] plugin manager StartNewInstance() - load state failed: %s", err) 27 return nil, err 28 } 29 30 if state.Running { 31 log.Printf("[TRACE] plugin manager StartNewInstance() found previous instance of plugin manager still running - stopping it") 32 // stop the current instance 33 if err := stop(state); err != nil { 34 log.Printf("[WARN] failed to stop previous instance of plugin manager: %s", err) 35 return nil, err 36 } 37 } 38 return start(steampipeExecutablePath) 39 } 40 41 // start plugin manager, without checking it is already running 42 // we need to be provided with the exe path as we have no way of knowing where the steampipe exe it 43 // when the plugin mananager is first started by steampipe, we derive the exe path from the running process and 44 // store it in the plugin manager state file - then if the fdw needs to start the plugin manager it knows how to 45 func start(steampipeExecutablePath string) (*State, error) { 46 // note: we assume the install dir has been assigned to file_paths.SteampipeDir 47 // - this is done both by the FDW and Steampipe 48 pluginManagerCmd := exec.Command(steampipeExecutablePath, 49 "plugin-manager", 50 "--"+constants.ArgInstallDir, filepaths.SteampipeDir) 51 // set attributes on the command to ensure the process is not shutdown when its parent terminates 52 pluginManagerCmd.SysProcAttr = &syscall.SysProcAttr{ 53 Setpgid: true, 54 } 55 56 // discard logging from the plugin manager client (plugin manager logs will still flow through to the log file 57 // as this is set up in the plugin manager) 58 logger := logging.NewLogger(&hclog.LoggerOptions{Name: "plugin", Output: io.Discard}) 59 60 // launch the plugin manager the plugin process 61 client := plugin.NewClient(&plugin.ClientConfig{ 62 HandshakeConfig: pluginshared.Handshake, 63 Plugins: pluginshared.PluginMap, 64 Cmd: pluginManagerCmd, 65 AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, 66 Logger: logger, 67 }) 68 69 if _, err := client.Start(); err != nil { 70 log.Printf("[WARN] plugin manager start() failed to start GRPC client for plugin manager: %s", err) 71 // attempt to retrieve error message encoded in the plugin stdout 72 err = sperr.WrapWithMessage(grpc.HandleStartFailure(err), "failed to start plugin manager") 73 return nil, err 74 } 75 76 // create a plugin manager state. 77 state := NewState(steampipeExecutablePath, client.ReattachConfig()) 78 79 log.Printf("[TRACE] start: started plugin manager, pid %d", state.Pid) 80 81 // now save the state 82 if err := state.Save(); err != nil { 83 return nil, err 84 } 85 return state, nil 86 } 87 88 // Stop loads the plugin manager state and if a running instance is found, stop it 89 func Stop() error { 90 log.Println("[DEBUG] pluginmanager.Stop start") 91 defer log.Println("[DEBUG] pluginmanager.Stop end") 92 // try to load the plugin manager state 93 state, err := LoadState() 94 if err != nil { 95 return err 96 } 97 if state == nil || !state.Running { 98 // nothing to do 99 return nil 100 } 101 return stop(state) 102 } 103 104 // stop the running plugin manager instance 105 func stop(state *State) error { 106 log.Println("[DEBUG] pluginmanager.stop start") 107 defer log.Println("[DEBUG] pluginmanager.stop end") 108 109 pluginManager, err := NewPluginManagerClient(state) 110 if err != nil { 111 return err 112 } 113 114 log.Printf("[TRACE] pluginManager.Shutdown") 115 // tell plugin manager to kill all plugins 116 _, err = pluginManager.Shutdown(&pb.ShutdownRequest{}) 117 if err != nil { 118 return err 119 } 120 log.Printf("[TRACE] pluginManager.Shutdown done") 121 122 // kill the underlying client 123 log.Printf("[TRACE] pluginManager.Shutdown killing raw client") 124 pluginManager.rawClient.Kill() 125 log.Printf("[TRACE] pluginManager.Shutdown killed raw client") 126 127 // now kill the plugin manager 128 return state.kill() 129 } 130 131 // GetPluginManager connects to a running plugin manager 132 func GetPluginManager() (pluginshared.PluginManager, error) { 133 return getPluginManager(true) 134 } 135 136 // getPluginManager determines whether the plugin manager is running 137 // if not,and if startIfNeeded is true, it starts the manager 138 // it then returns a plugin manager client 139 func getPluginManager(startIfNeeded bool) (pluginshared.PluginManager, error) { 140 // try to load the plugin manager state 141 state, err := LoadState() 142 if err != nil { 143 log.Printf("[WARN] failed to load plugin manager state: %s", err.Error()) 144 return nil, err 145 } 146 // if we did not load it and there was no error, it means the plugin manager is not running 147 // we cannot start it as we do not know the correct steampipe exe path - which is stored in the state 148 // this is not expected - we would expect the plugin manager to have been started with the datatbase 149 if state.Executable == "" { 150 return nil, fmt.Errorf("plugin manager is not running and there is no state file") 151 } 152 if state.Running { 153 log.Printf("[TRACE] plugin manager is running - returning client") 154 return NewPluginManagerClient(state) 155 } 156 157 // if the plugin manager is not running, it must have crashed/terminated 158 log.Printf("[TRACE] GetPluginManager called but plugin manager not running") 159 // is we are not already recursing, start the plugin manager then recurse back into this function 160 if startIfNeeded { 161 log.Printf("[TRACE] calling StartNewInstance()") 162 // start the plugin manager 163 if _, err := start(state.Executable); err != nil { 164 return nil, err 165 } 166 // recurse in, setting startIfNeeded to false to avoid further recursion on failure 167 return getPluginManager(false) 168 } 169 // not retrying - just fail 170 return nil, fmt.Errorf("plugin manager is not running") 171 }