mirror of
https://github.com/turbot/steampipe.git
synced 2025-12-19 18:12:43 -05:00
305 lines
9.3 KiB
Go
305 lines
9.3 KiB
Go
package metaquery
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
pconstants "github.com/turbot/pipe-fittings/v2/constants"
|
|
"github.com/turbot/pipe-fittings/v2/querydisplay"
|
|
"github.com/turbot/steampipe-plugin-sdk/v5/sperr"
|
|
"github.com/turbot/steampipe/v2/pkg/constants"
|
|
"github.com/turbot/steampipe/v2/pkg/error_helpers"
|
|
"github.com/turbot/steampipe/v2/pkg/steampipeconfig"
|
|
)
|
|
|
|
// inspect
|
|
func inspect(ctx context.Context, input *HandlerInput) error {
|
|
connStateMap, err := input.GetConnectionStateMap(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if connStateMap == nil {
|
|
log.Printf("[TRACE] failed to load connection state - are we connected to a server running a previous steampipe version?")
|
|
// if there is no connection state, call legacy inspect
|
|
return inspectLegacy(ctx, input)
|
|
}
|
|
|
|
// if no args were provided just list connections
|
|
if len(input.args()) == 0 {
|
|
return listConnections(ctx, input)
|
|
}
|
|
|
|
// so there were args, try to determine what the args are
|
|
tableOrConnection := input.args()[0]
|
|
if len(input.args()) > 0 {
|
|
// this should be one argument, but may have been split by the tokenizer
|
|
// because of the escape characters that autocomplete puts in
|
|
// join them up
|
|
tableOrConnection = strings.Join(input.args(), " ")
|
|
}
|
|
|
|
// remove all double quotes (if any)
|
|
tableOrConnection = strings.Join(
|
|
strings.Split(tableOrConnection, "\""),
|
|
"",
|
|
)
|
|
|
|
// arg can be one of <connection_name> or <connection_name>.<table_name>
|
|
tokens := strings.SplitN(tableOrConnection, ".", 2)
|
|
|
|
// here tokens could be schema.tableName or tableName
|
|
|
|
if len(tokens) == 1 {
|
|
// only a connection name (or maybe unqualified table name)
|
|
return inspectSchemaOrUnqualifiedTable(ctx, tableOrConnection, input)
|
|
}
|
|
|
|
// this is a fully qualified table name
|
|
return inspectQualifiedTable(ctx, tokens[0], tokens[1], input)
|
|
}
|
|
|
|
func inspectSchemaOrUnqualifiedTable(ctx context.Context, tableOrConnection string, input *HandlerInput) error {
|
|
// only a connection name (or maybe unqualified table name)
|
|
if inspectConnection(ctx, tableOrConnection, input) {
|
|
return nil
|
|
}
|
|
|
|
// there was no schema
|
|
// add the temporary schema to the search_path so that it becomes searchable
|
|
// for the next step
|
|
//nolint:golint,gocritic // we don't want to modify the input value
|
|
searchPath := append(input.SearchPath, input.Schema.TemporarySchemaName)
|
|
|
|
// go through the searchPath one by one and try to find the table by this name
|
|
for _, schema := range searchPath {
|
|
tablesInThisSchema := input.Schema.GetTablesInSchema(schema)
|
|
// we have a table by this name here
|
|
if _, gotTable := tablesInThisSchema[tableOrConnection]; gotTable {
|
|
return inspectQualifiedTable(ctx, schema, tableOrConnection, input)
|
|
}
|
|
|
|
// check against the fully qualified name of the table
|
|
for _, table := range input.Schema.Schemas[schema] {
|
|
if tableOrConnection == table.FullName {
|
|
return inspectQualifiedTable(ctx, schema, table.Name, input)
|
|
}
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("could not find connection or table called '%s'. Is the plugin installed? Is the connection configured?", tableOrConnection)
|
|
}
|
|
|
|
// list all the tables in the schema
|
|
func listTables(ctx context.Context, input *HandlerInput) error {
|
|
|
|
if len(input.args()) == 0 {
|
|
schemas := input.Schema.GetSchemas()
|
|
for _, schema := range schemas {
|
|
if schema == input.Schema.TemporarySchemaName {
|
|
continue
|
|
}
|
|
fmt.Printf(" ==> %s\n", schema)
|
|
inspectConnection(ctx, schema, input)
|
|
}
|
|
|
|
fmt.Printf(`
|
|
To get information about the columns in a table, run %s
|
|
|
|
`, pconstants.Bold(".inspect {connection}.{table}"))
|
|
} else {
|
|
// could be one of connectionName and {string}*
|
|
arg := input.args()[0]
|
|
if !strings.HasSuffix(arg, "*") {
|
|
inspectConnection(ctx, arg, input)
|
|
fmt.Println()
|
|
return nil
|
|
}
|
|
|
|
// treat this as a wild card
|
|
r, err := regexp.Compile(arg)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid search string %s", arg)
|
|
}
|
|
header := []string{"Table", "Schema"}
|
|
var rows [][]string
|
|
for schemaName, schemaDetails := range input.Schema.Schemas {
|
|
var tables [][]string
|
|
for tableName := range schemaDetails {
|
|
if r.MatchString(tableName) {
|
|
tables = append(tables, []string{tableName, schemaName})
|
|
}
|
|
}
|
|
sort.SliceStable(tables, func(i, j int) bool {
|
|
return tables[i][0] < tables[j][0]
|
|
})
|
|
rows = append(rows, tables...)
|
|
}
|
|
querydisplay.ShowWrappedTable(header, rows, &querydisplay.ShowWrappedTableOptions{AutoMerge: true})
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func listConnections(ctx context.Context, input *HandlerInput) error {
|
|
connStateMap, err := input.GetConnectionStateMap(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// if there is no connection state in the input, call listConnectionsLegacy
|
|
if connStateMap == nil {
|
|
log.Printf("[TRACE] failed to load connection state - are we connected to a server running a previous steampipe version?")
|
|
// call legacy inspect
|
|
return listConnectionsLegacy(ctx, input)
|
|
}
|
|
|
|
header := []string{"connection", "plugin", "state"}
|
|
|
|
connectionState, err := input.GetConnectionStateMap(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
showStateSummary := connectionState.ConnectionsInState(
|
|
constants.ConnectionStateUpdating,
|
|
constants.ConnectionStateDeleting,
|
|
constants.ConnectionStateError)
|
|
|
|
var rows [][]string
|
|
|
|
for connectionName, state := range connectionState {
|
|
// skip disabled connections
|
|
if state.Disabled() {
|
|
continue
|
|
}
|
|
row := []string{connectionName, state.Plugin, state.State}
|
|
rows = append(rows, row)
|
|
}
|
|
|
|
// sort by connection name
|
|
sort.SliceStable(rows, func(i, j int) bool {
|
|
return rows[i][0] < rows[j][0]
|
|
})
|
|
|
|
querydisplay.ShowWrappedTable(header, rows, &querydisplay.ShowWrappedTableOptions{AutoMerge: false})
|
|
|
|
if showStateSummary {
|
|
showStateSummaryTable(connectionState)
|
|
}
|
|
|
|
fmt.Printf(`
|
|
To get information about the tables in a connection, run %s
|
|
To get information about the columns in a table, run %s
|
|
|
|
`, pconstants.Bold(".inspect {connection}"), pconstants.Bold(".inspect {connection}.{table}"))
|
|
|
|
return nil
|
|
}
|
|
|
|
func showStateSummaryTable(connectionState steampipeconfig.ConnectionStateMap) {
|
|
header := []string{"Connection state", "Count"}
|
|
var rows [][]string
|
|
stateSummary := connectionState.GetSummary()
|
|
|
|
for _, state := range constants.ConnectionStates {
|
|
if connectionsInState := stateSummary[state]; connectionsInState > 0 {
|
|
rows = append(rows, []string{state, fmt.Sprintf("%d", connectionsInState)})
|
|
}
|
|
}
|
|
querydisplay.ShowWrappedTable(header, rows, &querydisplay.ShowWrappedTableOptions{AutoMerge: false})
|
|
}
|
|
|
|
func inspectQualifiedTable(ctx context.Context, connectionName string, tableName string, input *HandlerInput) error {
|
|
header := []string{"column", "type", "description"}
|
|
var rows [][]string
|
|
|
|
connectionStateMap, err := input.GetConnectionStateMap(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// do we have connection state for this schema and if so is it disabled?
|
|
if connectionState := connectionStateMap[connectionName]; connectionState != nil && connectionState.Disabled() {
|
|
error_helpers.ShowWarning(fmt.Sprintf("connection '%s' has schema import disabled", connectionName))
|
|
return nil
|
|
}
|
|
|
|
schema, found := input.Schema.Schemas[connectionName]
|
|
if !found {
|
|
return fmt.Errorf("could not find connection called '%s'. Is the plugin installed? Is the connection configured?\n", connectionName)
|
|
}
|
|
tableSchema, found := schema[tableName]
|
|
if !found {
|
|
return fmt.Errorf("could not find table '%s' in '%s'", tableName, connectionName)
|
|
}
|
|
|
|
for _, columnSchema := range tableSchema.Columns {
|
|
rows = append(rows, []string{columnSchema.Name, columnSchema.Type, columnSchema.Description})
|
|
}
|
|
|
|
// sort by column name
|
|
sort.SliceStable(rows, func(i, j int) bool {
|
|
return rows[i][0] < rows[j][0]
|
|
})
|
|
|
|
querydisplay.ShowWrappedTable(header, rows, &querydisplay.ShowWrappedTableOptions{AutoMerge: false})
|
|
|
|
return nil
|
|
}
|
|
|
|
// inspect the connection with the given name
|
|
// return whether connectionName was identified as an existing connection
|
|
func inspectConnection(ctx context.Context, connectionName string, input *HandlerInput) bool {
|
|
connectionStateMap, err := input.GetConnectionStateMap(ctx)
|
|
if err != nil {
|
|
error_helpers.ShowError(ctx, sperr.WrapWithMessage(err, "connection '%s' has schema import disabled", connectionName))
|
|
return true
|
|
}
|
|
|
|
connectionState, connectionFoundInState := connectionStateMap[connectionName]
|
|
if !connectionFoundInState {
|
|
return false
|
|
}
|
|
if connectionState.Disabled() {
|
|
error_helpers.ShowWarning(fmt.Sprintf("connection '%s' has schema import disabled", connectionName))
|
|
return true
|
|
}
|
|
|
|
// have we loaded the schema for this connection yet?
|
|
schema, found := input.Schema.Schemas[connectionName]
|
|
|
|
var rows [][]string
|
|
var header []string
|
|
|
|
if found {
|
|
header = []string{"table", "description"}
|
|
for _, tableSchema := range schema {
|
|
rows = append(rows, []string{tableSchema.Name, tableSchema.Description})
|
|
}
|
|
} else {
|
|
// just display the connection state
|
|
header = []string{"connection", "plugin", "schema mode", "state", "error", "state updated"}
|
|
|
|
rows = [][]string{{
|
|
connectionName,
|
|
connectionState.Plugin,
|
|
connectionState.SchemaMode,
|
|
connectionState.State,
|
|
connectionState.Error(),
|
|
connectionState.ConnectionModTime.Format(time.RFC3339),
|
|
},
|
|
}
|
|
}
|
|
|
|
// sort by table name
|
|
sort.SliceStable(rows, func(i, j int) bool {
|
|
return rows[i][0] < rows[j][0]
|
|
})
|
|
|
|
querydisplay.ShowWrappedTable(header, rows, &querydisplay.ShowWrappedTableOptions{AutoMerge: false})
|
|
|
|
return true
|
|
}
|