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 }