github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/db/db_local/start_services.go (about)

     1  package db_local
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"strings"
    11  	"sync"
    12  	"syscall"
    13  
    14  	"github.com/jackc/pgx/v5"
    15  	psutils "github.com/shirou/gopsutil/process"
    16  	"github.com/spf13/viper"
    17  	"github.com/turbot/go-kit/helpers"
    18  	"github.com/turbot/steampipe-plugin-sdk/v5/sperr"
    19  	"github.com/turbot/steampipe/pkg/constants"
    20  	"github.com/turbot/steampipe/pkg/db/db_common"
    21  	"github.com/turbot/steampipe/pkg/error_helpers"
    22  	"github.com/turbot/steampipe/pkg/filepaths"
    23  	"github.com/turbot/steampipe/pkg/pluginmanager"
    24  	"github.com/turbot/steampipe/pkg/statushooks"
    25  	"github.com/turbot/steampipe/pkg/utils"
    26  )
    27  
    28  // StartResult is a pseudoEnum for outcomes of StartNewInstance
    29  type StartResult struct {
    30  	error_helpers.ErrorAndWarnings
    31  	Status             StartDbStatus
    32  	DbState            *RunningDBInstanceInfo
    33  	PluginManagerState *pluginmanager.State
    34  	PluginManager      *pluginmanager.PluginManagerClient
    35  }
    36  
    37  func (r *StartResult) SetError(err error) *StartResult {
    38  	r.Error = err
    39  	r.Status = ServiceFailedToStart
    40  	return r
    41  }
    42  
    43  // StartDbStatus is a pseudoEnum for outcomes of starting the db
    44  type StartDbStatus int
    45  
    46  const (
    47  	// start from 1 to prevent confusion with int zero-value
    48  	ServiceStarted StartDbStatus = iota + 1
    49  	ServiceAlreadyRunning
    50  	ServiceFailedToStart
    51  )
    52  
    53  // StartListenType is a pseudoEnum of network binding for postgres
    54  type StartListenType string
    55  
    56  const (
    57  	// ListenTypeNetwork - bind to all known interfaces
    58  	ListenTypeNetwork StartListenType = "network"
    59  	// ListenTypeLocal - bind to localhost only
    60  	ListenTypeLocal = "local"
    61  )
    62  
    63  // ToListenAddresses is transforms StartListenType known aliases into their actual value
    64  func (slt StartListenType) ToListenAddresses() []string {
    65  	switch slt {
    66  	case ListenTypeNetwork:
    67  		return []string{"*"}
    68  	case ListenTypeLocal:
    69  		return []string{"localhost"}
    70  	}
    71  	return strings.Split(string(slt), ",")
    72  }
    73  
    74  func StartServices(ctx context.Context, listenAddresses []string, port int, invoker constants.Invoker) *StartResult {
    75  	utils.LogTime("db_local.StartServices start")
    76  	defer utils.LogTime("db_local.StartServices end")
    77  
    78  	// we want the service to always listen on IPv4 loopback
    79  	if !utils.ListenAddressesContainsOneOfAddresses(listenAddresses, []string{"127.0.0.1", "*", "localhost"}) {
    80  		log.Println("[TRACE] StartServices - prepending 127.0.0.1 to listenAddresses")
    81  		listenAddresses = append([]string{"127.0.0.1"}, listenAddresses...)
    82  	}
    83  
    84  	res := &StartResult{}
    85  
    86  	// if we were not successful, stop services again
    87  	defer func() {
    88  		if res.Status == ServiceStarted && res.Error != nil {
    89  			StopServices(ctx, false, invoker)
    90  			res.Status = ServiceFailedToStart
    91  		}
    92  	}()
    93  
    94  	res.DbState, res.Error = GetState()
    95  	if res.Error != nil {
    96  		return res
    97  	}
    98  
    99  	if res.DbState == nil {
   100  		res = startDB(ctx, listenAddresses, port, invoker)
   101  		if res.Error != nil {
   102  			return res
   103  		}
   104  	} else {
   105  		res.Status = ServiceAlreadyRunning
   106  
   107  		// if the service is already running, also load the state of the plugin manager
   108  		pluginManagerState, err := pluginmanager.LoadState()
   109  		if err != nil {
   110  			res.Error = err
   111  			return res
   112  		}
   113  		res.PluginManagerState = pluginManagerState
   114  	}
   115  
   116  	if res.Status == ServiceStarted {
   117  		// execute post startup setup
   118  		if err := postServiceStart(ctx, res); err != nil {
   119  			// NOTE do not update res.Status - this will be done by defer block
   120  			res.Error = err
   121  			return res
   122  		}
   123  
   124  		// start plugin manager if needed
   125  		pluginManager, pluginManagerState, err := ensurePluginManager(ctx)
   126  		res.PluginManagerState = pluginManagerState
   127  		res.PluginManager = pluginManager
   128  		if err != nil {
   129  			res.Error = err
   130  			return res
   131  		}
   132  
   133  		statushooks.SetStatus(ctx, "Service startup complete")
   134  
   135  	}
   136  	return res
   137  }
   138  
   139  func ensurePluginManager(ctx context.Context) (*pluginmanager.PluginManagerClient, *pluginmanager.State, error) {
   140  	// start the plugin manager if needed
   141  	state, err := pluginmanager.LoadState()
   142  	if err != nil {
   143  		return nil, nil, err
   144  	}
   145  
   146  	if !state.Running {
   147  		// get the location of the currently running steampipe process
   148  		executable, err := os.Executable()
   149  		if err != nil {
   150  			log.Printf("[WARN] plugin manager start() - failed to get steampipe executable path: %s", err)
   151  			return nil, nil, err
   152  		}
   153  		if state, err = pluginmanager.StartNewInstance(executable); err != nil {
   154  			log.Printf("[WARN] StartServices plugin manager failed to start: %s", err)
   155  			return nil, nil, err
   156  		}
   157  	}
   158  	client, err := pluginmanager.NewPluginManagerClient(state)
   159  	if err != nil {
   160  		return nil, state, err
   161  	}
   162  	return client, state, nil
   163  }
   164  
   165  func postServiceStart(ctx context.Context, res *StartResult) error {
   166  	conn, err := CreateLocalDbConnection(ctx, &CreateDbOptions{DatabaseName: res.DbState.Database, Username: constants.DatabaseSuperUser})
   167  	if err != nil {
   168  		return err
   169  	}
   170  	defer conn.Close(ctx)
   171  
   172  	// setup internal schema
   173  	if err := setupInternal(ctx, conn); err != nil {
   174  		return err
   175  	}
   176  
   177  	statushooks.SetStatus(ctx, "Initialize steampipe_connection table")
   178  
   179  	// ensure connection state table contains entries for all connections in connection config
   180  	// (this is to allow for the race condition between polling connection state and calling refresh connections,
   181  	// which does not update the connection_state with added connections until it has built the ConnectionUpdates
   182  	if err := initializeConnectionStateTable(ctx, conn); err != nil {
   183  		return err
   184  	}
   185  	if err := PopulatePluginTable(ctx, conn); err != nil {
   186  		return err
   187  	}
   188  
   189  	statushooks.SetStatus(ctx, "Create steampipe_server_settings table")
   190  	// create the server settings table
   191  	// this table contains configuration that this instance of the service
   192  	// is booting with
   193  	if err := setupServerSettingsTable(ctx, conn); err != nil {
   194  		return err
   195  	}
   196  
   197  	// create the clone_foreign_schema function
   198  	if _, err := executeSqlAsRoot(ctx, cloneForeignSchemaSQL); err != nil {
   199  		return sperr.WrapWithMessage(err, "failed to create clone_foreign_schema function")
   200  	}
   201  	// create the clone_comments function
   202  	if _, err := executeSqlAsRoot(ctx, cloneCommentsSQL); err != nil {
   203  		return sperr.WrapWithMessage(err, "failed to create clone_comments function")
   204  	}
   205  
   206  	// if there is an unprocessed db backup file, restore it now
   207  	if err := restoreDBBackup(ctx); err != nil {
   208  		return sperr.WrapWithMessage(err, "failed to migrate db public schema")
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  // StartDB starts the database if not already running
   215  func startDB(ctx context.Context, listenAddresses []string, port int, invoker constants.Invoker) (res *StartResult) {
   216  	log.Printf("[TRACE] StartDB invoker %s (listenAddresses=%s, port=%d)", invoker, listenAddresses, port)
   217  	utils.LogTime("db.StartDB start")
   218  	defer utils.LogTime("db.StartDB end")
   219  	var postgresCmd *exec.Cmd
   220  
   221  	res = &StartResult{}
   222  	defer func() {
   223  		if r := recover(); r != nil {
   224  			res.Error = helpers.ToError(r)
   225  		}
   226  		// if there was an error and we started the service, stop it again
   227  		if res.Error != nil {
   228  			if res.Status == ServiceStarted {
   229  				StopServices(ctx, false, invoker)
   230  			}
   231  			// remove the state file if we are going back with an error
   232  			removeRunningInstanceInfo()
   233  			// we are going back with an error
   234  			// if the process was started,
   235  			if postgresCmd != nil && postgresCmd.Process != nil {
   236  				// kill it
   237  				postgresCmd.Process.Kill()
   238  			}
   239  		}
   240  	}()
   241  
   242  	// remove the stale info file, ignoring errors - will overwrite anyway
   243  	_ = removeRunningInstanceInfo()
   244  
   245  	if err := utils.EnsureDirectoryPermission(filepaths.GetDataLocation()); err != nil {
   246  		return res.SetError(fmt.Errorf("%s does not have the necessary permissions to start the service", filepaths.GetDataLocation()))
   247  	}
   248  
   249  	// Remove any old and expiring certificates
   250  	if err := removeExpiringSelfIssuedCertificates(); err != nil {
   251  		error_helpers.ShowWarning("failed to remove expired certificates")
   252  		log.Println("[TRACE] failed to remove expired certificates", err)
   253  	}
   254  
   255  	// Generate the certificate if it fails then set the ssl to off
   256  	if err := ensureCertificates(); err != nil {
   257  		error_helpers.ShowWarning("self signed certificate creation failed, connecting to the database without SSL")
   258  	}
   259  
   260  	if err := utils.IsPortBindable(utils.GetFirstListenAddress(listenAddresses), port); err != nil {
   261  		return res.SetError(fmt.Errorf("cannot listen on port %d and %s %s. To check if there's any other steampipe services running, use %s", constants.Bold(port), utils.Pluralize("address", len(listenAddresses)), constants.Bold(strings.Join(listenAddresses, ",")), constants.Bold("steampipe service status --all")))
   262  	}
   263  
   264  	if err := migrateLegacyPasswordFile(); err != nil {
   265  		return res.SetError(err)
   266  	}
   267  
   268  	password, err := resolvePassword()
   269  	if err != nil {
   270  		return res.SetError(err)
   271  	}
   272  
   273  	postgresCmd, err = startPostgresProcess(ctx, listenAddresses, port, invoker)
   274  	if err != nil {
   275  		return res.SetError(err)
   276  	}
   277  
   278  	// create a RunningInfo with empty database name
   279  	// we need this to connect to the service using 'root', required retrieve the name of the installed database
   280  	res.DbState = newRunningDBInstanceInfo(postgresCmd, listenAddresses, port, "", password, invoker)
   281  	err = res.DbState.Save()
   282  	if err != nil {
   283  		return res.SetError(err)
   284  	}
   285  
   286  	databaseName, err := getDatabaseName(ctx, port)
   287  	if err != nil {
   288  		return res.SetError(err)
   289  	}
   290  
   291  	res.DbState, err = updateDatabaseNameInRunningInfo(ctx, databaseName)
   292  	if err != nil {
   293  		return res.SetError(err)
   294  	}
   295  
   296  	err = setServicePassword(ctx, password)
   297  	if err != nil {
   298  		return res.SetError(err)
   299  	}
   300  
   301  	err = ensureService(ctx, databaseName)
   302  	if err != nil {
   303  		return res.SetError(err)
   304  	}
   305  
   306  	// release the process - let the OS adopt it, so that we can exit
   307  	err = postgresCmd.Process.Release()
   308  	if err != nil {
   309  		return res.SetError(err)
   310  	}
   311  
   312  	utils.LogTime("postgresCmd end")
   313  	res.Status = ServiceStarted
   314  	return res
   315  }
   316  
   317  func ensureService(ctx context.Context, databaseName string) error {
   318  	connection, err := CreateLocalDbConnection(ctx, &CreateDbOptions{DatabaseName: databaseName, Username: constants.DatabaseSuperUser})
   319  	if err != nil {
   320  		return err
   321  	}
   322  	defer connection.Close(ctx)
   323  
   324  	// ensure the foreign server exists in the database
   325  	err = ensureSteampipeServer(ctx, connection)
   326  	if err != nil {
   327  		return err
   328  	}
   329  
   330  	// ensure that the necessary extensions are installed in the database
   331  	err = ensurePgExtensions(ctx, connection)
   332  	if err != nil {
   333  		// there was a problem with the installation
   334  		return err
   335  	}
   336  
   337  	// ensure permissions for writing to temp tables
   338  	err = ensureTempTablePermissions(ctx, databaseName, connection)
   339  	if err != nil {
   340  		return err
   341  	}
   342  
   343  	return nil
   344  }
   345  
   346  // getDatabaseName connects to the service and retrieves the database name
   347  func getDatabaseName(ctx context.Context, port int) (string, error) {
   348  	databaseName, err := retrieveDatabaseNameFromService(ctx, port)
   349  	if err != nil {
   350  		return "", err
   351  	}
   352  	if len(databaseName) == 0 {
   353  		return "", fmt.Errorf("could not find database to connect to")
   354  	}
   355  	return databaseName, nil
   356  }
   357  
   358  func resolvePassword() (string, error) {
   359  	// get the password from the password file
   360  	password, err := readPasswordFile()
   361  	if err != nil {
   362  		return "", err
   363  	}
   364  
   365  	// if a password was set through the `STEAMPIPE_DATABASE_PASSWORD` environment variable
   366  	// or through the `--database-password` cmdline flag, then use that for this session
   367  	// instead of the default one
   368  	if viper.IsSet(constants.ArgServicePassword) {
   369  		password = viper.GetString(constants.ArgServicePassword)
   370  	}
   371  	return password, nil
   372  }
   373  
   374  func startPostgresProcess(ctx context.Context, listenAddresses []string, port int, invoker constants.Invoker) (*exec.Cmd, error) {
   375  	if error_helpers.IsContextCanceled(ctx) {
   376  		return nil, ctx.Err()
   377  	}
   378  
   379  	if err := writePGConf(ctx); err != nil {
   380  		return nil, err
   381  	}
   382  
   383  	postgresCmd := createCmd(ctx, port, listenAddresses)
   384  	log.Printf("[TRACE] startPostgresProcess - postgres command: %s", postgresCmd)
   385  
   386  	setupLogCollection(postgresCmd)
   387  	err := postgresCmd.Start()
   388  	if err != nil {
   389  		return nil, err
   390  	}
   391  
   392  	return postgresCmd, nil
   393  }
   394  
   395  func retrieveDatabaseNameFromService(ctx context.Context, port int) (string, error) {
   396  	connection, err := createMaintenanceClient(ctx, port)
   397  	if err != nil {
   398  		return "", fmt.Errorf("failed to connect to the database: %v - please try again or reset your steampipe database", err)
   399  	}
   400  	defer connection.Close(ctx)
   401  
   402  	out := connection.QueryRow(ctx, "select datname from pg_database where datistemplate=false AND datname <> 'postgres';")
   403  
   404  	var databaseName string
   405  	err = out.Scan(&databaseName)
   406  	if err != nil {
   407  		return "", err
   408  	}
   409  
   410  	return databaseName, nil
   411  }
   412  
   413  func writePGConf(ctx context.Context) error {
   414  	// Apply default settings in conf files
   415  	err := os.WriteFile(filepaths.GetPostgresqlConfLocation(), []byte(constants.PostgresqlConfContent), 0600)
   416  	if err != nil {
   417  		return err
   418  	}
   419  	err = os.WriteFile(filepaths.GetSteampipeConfLocation(), []byte(constants.SteampipeConfContent), 0600)
   420  	if err != nil {
   421  		return err
   422  	}
   423  
   424  	// create the postgresql.conf.d location, don't fail if it errors
   425  	err = os.MkdirAll(filepaths.GetPostgresqlConfDLocation(), 0700)
   426  	if err != nil {
   427  		return err
   428  	}
   429  	return nil
   430  }
   431  
   432  func updateDatabaseNameInRunningInfo(ctx context.Context, databaseName string) (*RunningDBInstanceInfo, error) {
   433  	runningInfo, err := loadRunningInstanceInfo()
   434  	if err != nil {
   435  		return runningInfo, err
   436  	}
   437  	runningInfo.Database = databaseName
   438  	return runningInfo, runningInfo.Save()
   439  }
   440  
   441  func createCmd(ctx context.Context, port int, listenAddresses []string) *exec.Cmd {
   442  	postgresCmd := exec.Command(
   443  		filepaths.GetPostgresBinaryExecutablePath(),
   444  		// by this time, we are sure that the port is free to listen to
   445  		"-p", fmt.Sprint(port),
   446  		"-c", fmt.Sprintf("listen_addresses=%s", strings.Join(listenAddresses, ",")),
   447  		"-c", fmt.Sprintf("application_name=%s", constants.AppName),
   448  		"-c", fmt.Sprintf("cluster_name=%s", constants.AppName),
   449  
   450  		// log directory
   451  		"-c", fmt.Sprintf("log_directory=%s", filepaths.EnsureLogDir()),
   452  
   453  		// If ssl is off  it doesnot matter what we pass in the ssl_cert_file and ssl_key_file
   454  		// SSL will only get validated if ssl is on
   455  		"-c", fmt.Sprintf("ssl=%s", sslStatus()),
   456  		"-c", fmt.Sprintf("ssl_cert_file=%s", filepaths.GetServerCertLocation()),
   457  		"-c", fmt.Sprintf("ssl_key_file=%s", filepaths.GetServerCertKeyLocation()),
   458  
   459  		// Data Directory
   460  		"-D", filepaths.GetDataLocation())
   461  
   462  	if sslpassword := viper.GetString(constants.ArgDatabaseSSLPassword); sslpassword != "" {
   463  		postgresCmd.Args = append(
   464  			postgresCmd.Args,
   465  			"-c", fmt.Sprintf("ssl_passphrase_command_supports_reload=%s", "true"),
   466  			"-c", fmt.Sprintf("ssl_passphrase_command=%s", "echo "+sslpassword),
   467  		)
   468  	}
   469  
   470  	postgresCmd.Env = append(os.Environ(), fmt.Sprintf("STEAMPIPE_INSTALL_DIR=%s", filepaths.SteampipeDir))
   471  
   472  	//  Check if the /etc/ssl directory exist in os
   473  	dirExist, _ := os.Stat(constants.SslConfDir)
   474  	_, envVariableExist := os.LookupEnv("OPENSSL_CONF")
   475  
   476  	// This is particularly required for debian:buster
   477  	// https://github.com/kelaberetiv/TagUI/issues/787
   478  	// For other os the env variable OPENSSL_CONF
   479  	// does not matter so its safe to put
   480  	// this in env variable
   481  	// Tested in amazonlinux, debian:buster, ubuntu, mac
   482  	if dirExist != nil && !envVariableExist {
   483  		postgresCmd.Env = append(os.Environ(), fmt.Sprintf("OPENSSL_CONF=%s", constants.SslConfDir))
   484  	}
   485  
   486  	// set group pgid attributes on the command to ensure the process is not shutdown when its parent terminates
   487  	postgresCmd.SysProcAttr = &syscall.SysProcAttr{
   488  		Setpgid:    true,
   489  		Foreground: false,
   490  	}
   491  
   492  	return postgresCmd
   493  }
   494  
   495  func setupLogCollection(cmd *exec.Cmd) {
   496  	logChannel, stopListenFn, err := setupLogCollector(cmd)
   497  	if err == nil {
   498  		go traceoutServiceLogs(logChannel, stopListenFn)
   499  	} else {
   500  		// this is a convenience and therefore, we shouldn't error out if we
   501  		// are not able to capture the logs.
   502  		// instead, log to TRACE that we couldn't and continue
   503  		log.Println("[TRACE] Warning: Could not attach to service logs")
   504  	}
   505  }
   506  
   507  func traceoutServiceLogs(logChannel chan string, stopLogStreamFn func()) {
   508  	for logLine := range logChannel {
   509  		log.Printf("[TRACE] SERVICE: %s\n", logLine)
   510  		if strings.Contains(logLine, "Future log output will appear in") {
   511  			stopLogStreamFn()
   512  			break
   513  		}
   514  	}
   515  }
   516  
   517  func setServicePassword(ctx context.Context, password string) error {
   518  	connection, err := CreateLocalDbConnection(ctx, &CreateDbOptions{DatabaseName: "postgres", Username: constants.DatabaseSuperUser})
   519  	if err != nil {
   520  		return err
   521  	}
   522  	defer connection.Close(ctx)
   523  	statements := []string{
   524  		"LOCK TABLE pg_user IN SHARE ROW EXCLUSIVE MODE;",
   525  		fmt.Sprintf(`ALTER USER steampipe WITH PASSWORD '%s';`, password),
   526  	}
   527  	_, err = ExecuteSqlInTransaction(ctx, connection, statements...)
   528  	return err
   529  }
   530  
   531  func setupLogCollector(postgresCmd *exec.Cmd) (chan string, func(), error) {
   532  	var publishChannel chan string
   533  
   534  	stdoutPipe, err := postgresCmd.StdoutPipe()
   535  	if err != nil {
   536  		return nil, nil, err
   537  	}
   538  	stderrPipe, err := postgresCmd.StderrPipe()
   539  	if err != nil {
   540  		return nil, nil, err
   541  	}
   542  	closeFunction := func() {
   543  		// close the sources to make sure they don't send anymore data
   544  		stdoutPipe.Close()
   545  		stderrPipe.Close()
   546  
   547  		// always close from the sender
   548  		close(publishChannel)
   549  	}
   550  	stdoutScanner := bufio.NewScanner(stdoutPipe)
   551  	stderrScanner := bufio.NewScanner(stderrPipe)
   552  
   553  	stdoutScanner.Split(bufio.ScanLines)
   554  	stderrScanner.Split(bufio.ScanLines)
   555  
   556  	// create a channel with a big buffer, so that it doesn't choke
   557  	publishChannel = make(chan string, 1000)
   558  
   559  	go func() {
   560  		for stdoutScanner.Scan() {
   561  			line := stdoutScanner.Text()
   562  			if len(line) > 0 {
   563  				publishChannel <- line
   564  			}
   565  		}
   566  	}()
   567  
   568  	go func() {
   569  		for stderrScanner.Scan() {
   570  			line := stderrScanner.Text()
   571  			if len(line) > 0 {
   572  				publishChannel <- line
   573  			}
   574  		}
   575  	}()
   576  
   577  	return publishChannel, closeFunction, nil
   578  }
   579  
   580  // ensures that the necessary extensions are installed on the database
   581  func ensurePgExtensions(ctx context.Context, rootClient *pgx.Conn) error {
   582  	extensions := []string{
   583  		"tablefunc",
   584  		"ltree",
   585  	}
   586  
   587  	var errors []error
   588  	for _, extn := range extensions {
   589  		_, err := rootClient.Exec(ctx, fmt.Sprintf("create extension if not exists %s", db_common.PgEscapeName(extn)))
   590  		if err != nil {
   591  			errors = append(errors, err)
   592  		}
   593  	}
   594  	return error_helpers.CombineErrors(errors...)
   595  }
   596  
   597  // ensures that the 'steampipe' foreign server exists
   598  //
   599  //	(re)install FDW and creates server if it doesn't
   600  func ensureSteampipeServer(ctx context.Context, rootClient *pgx.Conn) error {
   601  	res := rootClient.QueryRow(ctx, "select srvname from pg_catalog.pg_foreign_server where srvname='steampipe'")
   602  
   603  	var serverName string
   604  	err := res.Scan(&serverName)
   605  	// if there is an error, we need to reinstall the foreign server
   606  	if err != nil {
   607  		return installForeignServer(ctx, rootClient)
   608  	}
   609  	return nil
   610  }
   611  
   612  // ensures that the 'steampipe_users' role has permissions to work with temporary tables
   613  // this is done during database installation, but we need to migrate current installations
   614  func ensureTempTablePermissions(ctx context.Context, databaseName string, rootClient *pgx.Conn) error {
   615  	statements := []string{
   616  		"lock table pg_namespace;",
   617  		fmt.Sprintf("grant temporary on database %s to %s", databaseName, constants.DatabaseUser),
   618  	}
   619  	if _, err := ExecuteSqlInTransaction(ctx, rootClient, statements...); err != nil {
   620  		return err
   621  	}
   622  	return nil
   623  }
   624  
   625  // kill all postgres processes that were started as part of steampipe (if any)
   626  func killInstanceIfAny(ctx context.Context) bool {
   627  	processes, err := FindAllSteampipePostgresInstances(ctx)
   628  	if err != nil {
   629  		return false
   630  	}
   631  	wg := sync.WaitGroup{}
   632  	for _, process := range processes {
   633  		wg.Add(1)
   634  		go func(p *psutils.Process) {
   635  			doThreeStepPostgresExit(ctx, p)
   636  			wg.Done()
   637  		}(process)
   638  	}
   639  	wg.Wait()
   640  	return len(processes) > 0
   641  }
   642  
   643  func FindAllSteampipePostgresInstances(ctx context.Context) ([]*psutils.Process, error) {
   644  	var instances []*psutils.Process
   645  	allProcesses, err := psutils.ProcessesWithContext(ctx)
   646  	if err != nil {
   647  		return nil, err
   648  	}
   649  	for _, p := range allProcesses {
   650  		cmdLine, err := p.CmdlineSliceWithContext(ctx)
   651  		if err != nil {
   652  			return nil, err
   653  		}
   654  		if isSteampipePostgresProcess(ctx, cmdLine) {
   655  			instances = append(instances, p)
   656  		}
   657  	}
   658  	return instances, nil
   659  }
   660  
   661  func isSteampipePostgresProcess(ctx context.Context, cmdline []string) bool {
   662  	if len(cmdline) < 1 {
   663  		return false
   664  	}
   665  	if strings.Contains(cmdline[0], "postgres") {
   666  		// this is a postgres process - but is it a steampipe service?
   667  		return helpers.StringSliceContains(cmdline, fmt.Sprintf("application_name=%s", constants.AppName))
   668  	}
   669  	return false
   670  }