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  }