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

     1  package db_local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  
     9  	"github.com/jackc/pgx/v5"
    10  	"github.com/turbot/steampipe-plugin-sdk/v5/sperr"
    11  	"github.com/turbot/steampipe/pkg/constants"
    12  	"github.com/turbot/steampipe/pkg/db/db_common"
    13  	"github.com/turbot/steampipe/pkg/introspection"
    14  	"github.com/turbot/steampipe/pkg/statushooks"
    15  	"github.com/turbot/steampipe/pkg/steampipeconfig"
    16  	"github.com/turbot/steampipe/pkg/utils"
    17  )
    18  
    19  // dropLegacyInternalSchema looks for a schema named 'internal'
    20  // which has a function called 'glob' and maybe a table named 'connection_state'
    21  // and drops it
    22  func dropLegacyInternalSchema(ctx context.Context, conn *pgx.Conn) error {
    23  	utils.LogTime("db_local.dropLegacyInternal start")
    24  	defer utils.LogTime("db_local.dropLegacyInternal end")
    25  
    26  	if exists, err := legacyInternalExists(ctx, conn); err == nil && !exists {
    27  		log.Println("[TRACE] could not find legacy 'internal' schema")
    28  		return nil
    29  	}
    30  
    31  	log.Println("[TRACE] dropping legacy 'internal' schema")
    32  	if _, err := conn.Exec(ctx, fmt.Sprintf("DROP SCHEMA %s CASCADE", constants.LegacyInternalSchema)); err != nil {
    33  		return sperr.WrapWithMessage(err, "could not drop legacy schema: '%s'", constants.LegacyInternalSchema)
    34  	}
    35  	log.Println("[TRACE] dropped legacy 'internal' schema")
    36  
    37  	return nil
    38  }
    39  
    40  // legacyInternalExists looks for a schema named 'internal'
    41  // which has a function called 'glob' and maybe a table named 'connection_state'
    42  func legacyInternalExists(ctx context.Context, conn *pgx.Conn) (bool, error) {
    43  	utils.LogTime("db_local.isLegacyInternalExists start")
    44  	defer utils.LogTime("db_local.isLegacyInternalExists end")
    45  
    46  	log.Println("[TRACE] querying for legacy 'internal' schema")
    47  
    48  	legacySchemaCountQuery := `
    49  WITH 
    50  internal_functions AS (
    51  		SELECT
    52  			COALESCE(STRING_AGG(DISTINCT(p.proname),','),'') as function_names
    53  		FROM
    54  			pg_proc p
    55  			LEFT JOIN pg_namespace n ON p.pronamespace = n.oid
    56  		WHERE
    57  			n.nspname = $1
    58  ),
    59  internal_tables AS (
    60  		SELECT 
    61  				COALESCE(STRING_AGG(DISTINCT(table_name),','),'') as table_names
    62  		FROM 
    63  				information_schema.tables 
    64  		WHERE 
    65  				table_schema = $1
    66  )
    67  SELECT 
    68  		internal_functions.function_names, 
    69  		internal_tables.table_names 
    70  FROM
    71  		internal_functions 
    72  INNER JOIN
    73  		internal_tables
    74  		ON true;
    75  	`
    76  
    77  	row := conn.QueryRow(ctx, legacySchemaCountQuery, constants.LegacyInternalSchema)
    78  
    79  	var functionNames string
    80  	var tableNames string
    81  	err := row.Scan(&functionNames, &tableNames)
    82  	if err != nil {
    83  		return false, sperr.WrapWithMessage(err, "could not query legacy 'internal' schema objects: '%s'", constants.LegacyInternalSchema)
    84  	}
    85  
    86  	if len(functionNames) == 0 && len(tableNames) == 0 {
    87  		log.Println("[TRACE] could not find any objects in 'internal' - skipping drop")
    88  		return false, nil
    89  	}
    90  
    91  	functions := strings.Split(functionNames, ",")
    92  	tables := strings.Split(tableNames, ",")
    93  
    94  	log.Println("[TRACE] isLegacyInternalExists: available function names", functions)
    95  	log.Println("[TRACE] isLegacyInternalExists: available table names", tables)
    96  
    97  	expectedFunctions := map[string]bool{
    98  		"glob": true,
    99  	}
   100  	expectedTables := map[string]bool{
   101  		"connection_state":                   true, // previous legacy table name
   102  		constants.LegacyConnectionStateTable: true,
   103  	}
   104  
   105  	for _, f := range functions {
   106  		if !expectedFunctions[f] {
   107  			log.Println("[TRACE] isLegacyInternalExists: unexpected function", f)
   108  			return false, nil
   109  		}
   110  	}
   111  
   112  	for _, t := range tables {
   113  		if !expectedTables[t] {
   114  			log.Println("[TRACE] isLegacyInternalExists: unexpected table", t)
   115  			return false, nil
   116  		}
   117  	}
   118  
   119  	return true, nil
   120  }
   121  
   122  func setupInternal(ctx context.Context, conn *pgx.Conn) error {
   123  	statushooks.SetStatus(ctx, "Dropping legacy schema")
   124  	if err := dropLegacyInternalSchema(ctx, conn); err != nil {
   125  		// do not fail
   126  		// worst case scenario is that we have a couple of extra schema
   127  		// these won't be in the search path anyway
   128  		log.Println("[INFO] failed to drop legacy 'internal' schema", err)
   129  	}
   130  
   131  	// setup internal schema
   132  	// this includes setting the state of all connections in the connection_state table to pending
   133  	statushooks.SetStatus(ctx, "Setting up internal schema")
   134  
   135  	utils.LogTime("db_local.setupInternal start")
   136  	defer utils.LogTime("db_local.setupInternal end")
   137  
   138  	queries := []string{
   139  		"lock table pg_namespace;",
   140  		// drop internal schema tables to force recreation (in case of schema change)
   141  		fmt.Sprintf(`DROP FOREIGN TABLE IF EXISTS %s.%s;`, constants.InternalSchema, constants.ForeignTableScanMetadataSummary),
   142  		fmt.Sprintf(`DROP FOREIGN TABLE IF EXISTS %s.%s;`, constants.InternalSchema, constants.ForeignTableScanMetadata),
   143  		fmt.Sprintf(`DROP FOREIGN TABLE IF EXISTS %s.%s;`, constants.InternalSchema, constants.ForeignTableSettings),
   144  		fmt.Sprintf(`CREATE SCHEMA IF NOT EXISTS %s;`, constants.InternalSchema),
   145  		fmt.Sprintf(`GRANT USAGE ON SCHEMA %s TO %s;`, constants.InternalSchema, constants.DatabaseUsersRole),
   146  		fmt.Sprintf("IMPORT FOREIGN SCHEMA \"%s\" FROM SERVER steampipe INTO %s;", constants.InternalSchema, constants.InternalSchema),
   147  		fmt.Sprintf("GRANT INSERT ON %s.%s TO %s;", constants.InternalSchema, constants.ForeignTableSettings, constants.DatabaseUsersRole),
   148  		fmt.Sprintf("GRANT SELECT ON %s.%s TO %s;", constants.InternalSchema, constants.ForeignTableScanMetadataSummary, constants.DatabaseUsersRole),
   149  		fmt.Sprintf("GRANT SELECT ON %s.%s TO %s;", constants.InternalSchema, constants.ForeignTableScanMetadata, constants.DatabaseUsersRole),
   150  		// legacy command schema support
   151  		fmt.Sprintf(`CREATE SCHEMA IF NOT EXISTS %s;`, constants.LegacyCommandSchema),
   152  		fmt.Sprintf(`GRANT USAGE ON SCHEMA %s TO %s;`, constants.LegacyCommandSchema, constants.DatabaseUsersRole),
   153  		fmt.Sprintf("IMPORT FOREIGN SCHEMA \"%s\" FROM SERVER steampipe INTO %s;", constants.LegacyCommandSchema, constants.LegacyCommandSchema),
   154  		fmt.Sprintf("GRANT INSERT ON %s.%s TO %s;", constants.LegacyCommandSchema, constants.LegacyCommandTableCache, constants.DatabaseUsersRole),
   155  		fmt.Sprintf("GRANT SELECT ON %s.%s TO %s;", constants.LegacyCommandSchema, constants.LegacyCommandTableScanMetadata, constants.DatabaseUsersRole),
   156  	}
   157  	queries = append(queries, getFunctionAddStrings(db_common.Functions)...)
   158  	if _, err := ExecuteSqlInTransaction(ctx, conn, queries...); err != nil {
   159  		return sperr.WrapWithMessage(err, "failed to initialise functions")
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  func getFunctionAddStrings(functions []db_common.SQLFunction) []string {
   166  	var addStrings []string
   167  	for _, function := range functions {
   168  		addStrings = append(addStrings, getFunctionAddString(function))
   169  	}
   170  	return addStrings
   171  }
   172  
   173  func getFunctionAddString(function db_common.SQLFunction) string {
   174  	if err := validateFunction(function); err != nil {
   175  		// panic - this should never happen,
   176  		// since the function definitions are
   177  		// tightly bound to development
   178  		panic(err)
   179  	}
   180  
   181  	var inputParams []string
   182  	for argName, argType := range function.Params {
   183  		inputParams = append(inputParams, fmt.Sprintf("%s %s", argName, argType))
   184  	}
   185  
   186  	return strings.TrimSpace(fmt.Sprintf(
   187  		`
   188  ;CREATE OR REPLACE FUNCTION %s.%s (%s) RETURNS %s LANGUAGE %s AS
   189  $$
   190  %s
   191  $$;
   192  `,
   193  		constants.InternalSchema,
   194  		function.Name,
   195  		strings.Join(inputParams, ","),
   196  		function.Returns,
   197  		function.Language,
   198  		strings.TrimSpace(function.Body),
   199  	))
   200  }
   201  
   202  func validateFunction(f db_common.SQLFunction) error {
   203  	return nil
   204  }
   205  
   206  /*
   207  	to initialize the connection state table:
   208  
   209  - load existing connection state (ignoring relation not found error)
   210  - delete and recreate the table
   211  - update status of existing connection state to pending or imncomplete as appropriate
   212  - write back connection state
   213  */
   214  func initializeConnectionStateTable(ctx context.Context, conn *pgx.Conn) error {
   215  	// load the state (if the table is there)
   216  	connectionStateMap, err := steampipeconfig.LoadConnectionState(ctx, conn)
   217  	if err != nil {
   218  		// ignore relation not found error
   219  		if !db_common.IsRelationNotFoundError(err) {
   220  			return err
   221  		}
   222  
   223  		// create an empty connectionStateMap
   224  		connectionStateMap = steampipeconfig.ConnectionStateMap{}
   225  	}
   226  	// if any connections are in a ready  state, set them to pending - we need to run refresh connections before we know this connection is still valid
   227  	// if any connections are not in a ready or error state, set them to pending_incomplete
   228  	connectionStateMap.SetConnectionsToPendingOrIncomplete()
   229  
   230  	// migration: ensure filename and line numbers are set for all connection states
   231  	connectionStateMap.PopulateFilename()
   232  
   233  	// drop the table and recreate
   234  	queries := introspection.GetConnectionStateTableDropSql()
   235  	queries = append(queries, introspection.GetConnectionStateTableCreateSql()...)
   236  	queries = append(queries, introspection.GetConnectionStateTableGrantSql()...)
   237  
   238  	// add insert queries for all connection state
   239  	for _, s := range connectionStateMap {
   240  		queries = append(queries, introspection.GetUpsertConnectionStateSql(s)...)
   241  	}
   242  
   243  	// for any connection in the connection config but NOT in the connection state table,
   244  	// add an entry with `pending_incomplete` state this is to work around the race condition where
   245  	// we wait for connection state before RefreshConnections has added any new connections into the state table
   246  	for connection, connectionConfig := range steampipeconfig.GlobalConfig.Connections {
   247  		if _, ok := connectionStateMap[connection]; !ok {
   248  			queries = append(queries, introspection.GetNewConnectionStateFromConnectionInsertSql(connectionConfig)...)
   249  		}
   250  	}
   251  	_, err = ExecuteSqlWithArgsInTransaction(ctx, conn, queries...)
   252  	return err
   253  }
   254  
   255  func PopulatePluginTable(ctx context.Context, conn *pgx.Conn) error {
   256  	plugins := steampipeconfig.GlobalConfig.PluginsInstances
   257  
   258  	// drop the table and recreate
   259  	queries := []db_common.QueryWithArgs{
   260  		introspection.GetPluginTableDropSql(),
   261  		introspection.GetPluginTableCreateSql(),
   262  		introspection.GetPluginTableGrantSql(),
   263  	}
   264  
   265  	// add insert queries for all connection state
   266  	for _, p := range plugins {
   267  		queries = append(queries, introspection.GetPluginTablePopulateSql(p))
   268  	}
   269  	_, err := ExecuteSqlWithArgsInTransaction(ctx, conn, queries...)
   270  	return err
   271  }