mirror of
https://github.com/turbot/steampipe.git
synced 2026-02-25 05:01:06 -05:00
248 lines
9.7 KiB
Go
248 lines
9.7 KiB
Go
package steampipeconfig
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/turbot/steampipe-plugin-sdk/v4/grpc/proto"
|
|
"github.com/turbot/steampipe-plugin-sdk/v4/plugin"
|
|
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
|
|
"github.com/turbot/steampipe/pkg/utils"
|
|
)
|
|
|
|
type ConnectionUpdates struct {
|
|
Update ConnectionDataMap
|
|
Delete ConnectionDataMap
|
|
MissingPlugins []string
|
|
// the connections which will exist after the update
|
|
RequiredConnectionState ConnectionDataMap
|
|
// connection plugins required to perform the updates
|
|
ConnectionPlugins map[string]*ConnectionPlugin
|
|
currentConnectionState ConnectionDataMap
|
|
}
|
|
|
|
// NewConnectionUpdates returns updates to be made to the database to sync with connection config
|
|
func NewConnectionUpdates(schemaNames []string) (*ConnectionUpdates, *RefreshConnectionResult) {
|
|
utils.LogTime("NewConnectionUpdates start")
|
|
defer utils.LogTime("NewConnectionUpdates end")
|
|
|
|
res := &RefreshConnectionResult{}
|
|
// build connection data for all required connections
|
|
// NOTE: this will NOT populate SchemaMode for the connections, as we need to load the schema for that
|
|
// this will be updated below on the call to updateRequiredStateWithSchemaProperties
|
|
requiredConnectionState, missingPlugins, err := NewConnectionDataMap(GlobalConfig.Connections)
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
|
|
updates := &ConnectionUpdates{
|
|
Update: ConnectionDataMap{},
|
|
Delete: ConnectionDataMap{},
|
|
MissingPlugins: missingPlugins,
|
|
RequiredConnectionState: requiredConnectionState,
|
|
}
|
|
|
|
// load the connection state file and filter out any connections which are not in the list of schemas
|
|
// this allows for the database being rebuilt,modified externally
|
|
currentConnectionState, err := GetConnectionState(schemaNames)
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
updates.currentConnectionState = currentConnectionState
|
|
|
|
// for any connections with dynamic schema, we need to reload their schema
|
|
// instantiate connection plugins for all connections with dynamic schema - this will retrieve their current schema
|
|
dynamicSchemaHashMap, connectionsPluginsWithDynamicSchema, err := getSchemaHashesForDynamicSchemas(requiredConnectionState, currentConnectionState)
|
|
if err != nil {
|
|
res.Error = err
|
|
return nil, res
|
|
}
|
|
|
|
// connections to create/update
|
|
for name, requiredConnectionData := range requiredConnectionState {
|
|
// check whether this connection exists in the state
|
|
currentConnectionData, ok := currentConnectionState[name]
|
|
// if it does not exist, or is not equal, add to updates
|
|
if !ok || !currentConnectionData.Equals(requiredConnectionData) {
|
|
log.Printf("[TRACE] connection %s is out of date or missing\n", name)
|
|
updates.Update[name] = requiredConnectionData
|
|
}
|
|
}
|
|
|
|
// connections to delete - any connection which is in connection state but NOT required connections
|
|
for connection, requiredPlugin := range currentConnectionState {
|
|
if _, ok := requiredConnectionState[connection]; !ok {
|
|
log.Printf("[TRACE] connection %s is no longer required\n", connection)
|
|
updates.Delete[connection] = requiredPlugin
|
|
}
|
|
}
|
|
|
|
// now for every connection with dynamic schema,
|
|
// check whether the schema we have just fetched matches the existing db schema
|
|
// if not, add to updates
|
|
for name, requiredHash := range dynamicSchemaHashMap {
|
|
// get the connection data from the loaded connection state
|
|
connectionData, ok := currentConnectionState[name]
|
|
// if the connection exists in the state, does the schemas hash match?
|
|
if ok && connectionData.SchemaHash != requiredHash {
|
|
updates.Update[name] = connectionData
|
|
}
|
|
}
|
|
|
|
// instantiate connection plugins for all updates
|
|
otherRes := updates.populateConnectionPlugins(connectionsPluginsWithDynamicSchema)
|
|
res.Merge(otherRes)
|
|
if res.Error != nil {
|
|
return nil, res
|
|
}
|
|
|
|
// set the schema mode and hash on the connection data in required state
|
|
// this uses data from the ConnectionPlugins which we have now loaded
|
|
updates.updateRequiredStateWithSchemaProperties(dynamicSchemaHashMap)
|
|
|
|
return updates, res
|
|
}
|
|
|
|
// update requiredConnections - set the schema hash and schema mode for all elements of RequiredConnectionState
|
|
// default to the existing state, but if anm update is required, get the updated value
|
|
func (u *ConnectionUpdates) updateRequiredStateWithSchemaProperties(schemaHashMap map[string]string) {
|
|
// we only need to update connections which are being updated
|
|
for k, v := range u.RequiredConnectionState {
|
|
if currentConectionState, ok := u.currentConnectionState[k]; ok {
|
|
v.SchemaHash = currentConectionState.SchemaHash
|
|
v.SchemaMode = currentConectionState.SchemaMode
|
|
}
|
|
// if the schemaHashMap contains this connection, use that value
|
|
if schemaHash, ok := schemaHashMap[k]; ok {
|
|
v.SchemaHash = schemaHash
|
|
}
|
|
// have we loaded a connection plugin for this connection
|
|
// - if so us the schema mode from the schema it has loaded
|
|
if connectionPlugin, ok := u.ConnectionPlugins[k]; ok {
|
|
v.SchemaMode = connectionPlugin.ConnectionMap[k].Schema.Mode
|
|
// if the schema mode is dynamic and the hash is not set yet, calculate the value from the connection plugin schema
|
|
// this will happen the first time we load a plugin - as schemaHashMap will NOT include the has
|
|
// because we do not know yet that the plugin is dynamic
|
|
if v.SchemaMode == plugin.SchemaModeDynamic && v.SchemaHash == "" {
|
|
v.SchemaHash = pluginSchemaHash(connectionPlugin.ConnectionMap[k].Schema)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (u *ConnectionUpdates) populateConnectionPlugins(alreadyCreatedConnectionPlugins map[string]*ConnectionPlugin) *RefreshConnectionResult {
|
|
// get list of connections to update:
|
|
// - exclude connections already created
|
|
// - for any aggregator connections, instantiate the first child connection instead
|
|
connectionsToCreate := u.getConnectionsToCreate(alreadyCreatedConnectionPlugins)
|
|
// now create them
|
|
connectionPlugins, res := CreateConnectionPlugins(connectionsToCreate)
|
|
if res.Error != nil {
|
|
return res
|
|
}
|
|
// add back in the already created plugins
|
|
for name, connectionPlugin := range alreadyCreatedConnectionPlugins {
|
|
connectionPlugins[name] = connectionPlugin
|
|
}
|
|
// and set our ConnectionPlugins property
|
|
u.ConnectionPlugins = connectionPlugins
|
|
return res
|
|
}
|
|
|
|
func (u *ConnectionUpdates) getConnectionsToCreate(alreadyCreatedConnectionPlugins map[string]*ConnectionPlugin) []*modconfig.Connection {
|
|
updateConnections := u.Update.Connections()
|
|
// put connections into a map to avoid dupes
|
|
var connectionMap = make(map[string]*modconfig.Connection, len(updateConnections))
|
|
for _, connection := range updateConnections {
|
|
// if this connection is an aggregator, instantiate its first child connection instead
|
|
if connection.Type == modconfig.ConnectionTypeAggregator {
|
|
connection = connection.FirstChild()
|
|
}
|
|
|
|
connectionMap[connection.Name] = connection
|
|
}
|
|
// NOTE - we may have already created some connection plugins (if they have dynamic schema)
|
|
// - remove these from list of plugins to create
|
|
for name := range alreadyCreatedConnectionPlugins {
|
|
delete(connectionMap, name)
|
|
}
|
|
|
|
// now copy result into array
|
|
res := make([]*modconfig.Connection, len(connectionMap))
|
|
idx := 0
|
|
for _, c := range connectionMap {
|
|
res[idx] = c
|
|
idx++
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
func (u *ConnectionUpdates) HasUpdates() bool {
|
|
return len(u.Update)+len(u.Delete) > 0
|
|
}
|
|
|
|
func getSchemaHashesForDynamicSchemas(requiredConnectionData ConnectionDataMap, connectionState ConnectionDataMap) (map[string]string, map[string]*ConnectionPlugin, error) {
|
|
log.Printf("[TRACE] getSchemaHashesForDynamicSchemas")
|
|
// for every required connection, check the connection state to determine whether the schema mode is 'dynamic'
|
|
// if we have never loaded the connection, there will be no state, so we cannot retrieve this information
|
|
// however in this case we will load the connection anyway
|
|
// - at which point the state will be updated with the schema mode for the next time round
|
|
|
|
var connectionsWithDynamicSchema = make(ConnectionDataMap)
|
|
for requiredConnectionName, requiredConnection := range requiredConnectionData {
|
|
if existingConnection, ok := connectionState[requiredConnectionName]; ok {
|
|
// SchemaMode will be unpopulated for plugins using an older version of the sdk
|
|
// that is fine, we treat that as SchemaModeDynamic
|
|
if existingConnection.SchemaMode == plugin.SchemaModeDynamic {
|
|
log.Printf("[TRACE] fetching schema for connection %s using dynamic plugin %s", requiredConnectionName, requiredConnection.Plugin)
|
|
connectionsWithDynamicSchema[requiredConnectionName] = requiredConnection
|
|
}
|
|
}
|
|
}
|
|
connectionsPluginsWithDynamicSchema, res := CreateConnectionPlugins(connectionsWithDynamicSchema.Connections())
|
|
if res.Error != nil {
|
|
return nil, nil, res.Error
|
|
}
|
|
|
|
log.Printf("[TRACE] fetched schema for %d dynamic %s", len(connectionsPluginsWithDynamicSchema), utils.Pluralize("plugin", len(connectionsPluginsWithDynamicSchema)))
|
|
|
|
hashMap := make(map[string]string)
|
|
for name, c := range connectionsPluginsWithDynamicSchema {
|
|
// update schema hash stored in required connections so it is persisted in the state if updates are made
|
|
schemaHash := pluginSchemaHash(c.ConnectionMap[name].Schema)
|
|
hashMap[name] = schemaHash
|
|
}
|
|
return hashMap, connectionsPluginsWithDynamicSchema, nil
|
|
}
|
|
|
|
func pluginSchemaHash(s *proto.Schema) string {
|
|
var sb strings.Builder
|
|
|
|
// build ordered list of tables
|
|
var tables = make([]string, len(s.Schema))
|
|
idx := 0
|
|
for tableName := range s.Schema {
|
|
tables[idx] = tableName
|
|
idx++
|
|
}
|
|
sort.Strings(tables)
|
|
|
|
// now build a string from the ordered table schemas
|
|
for _, tableName := range tables {
|
|
sb.WriteString(tableName)
|
|
tableSchema := s.Schema[tableName]
|
|
for _, c := range tableSchema.Columns {
|
|
sb.WriteString(c.Name)
|
|
sb.WriteString(fmt.Sprintf("%d", c.Type))
|
|
}
|
|
}
|
|
str := sb.String()
|
|
return utils.GetMD5Hash(str)
|
|
}
|