github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/steampipeconfig/parse/connection.go (about)

     1  package parse
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/hcl/v2/gohcl"
    10  	"github.com/hashicorp/hcl/v2/hclsyntax"
    11  	"github.com/turbot/pipe-fittings/hclhelpers"
    12  	"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
    13  	"github.com/zclconf/go-cty/cty"
    14  	"golang.org/x/exp/maps"
    15  )
    16  
    17  func DecodeConnection(block *hcl.Block) (*modconfig.Connection, hcl.Diagnostics) {
    18  	connectionContent, rest, diags := block.Body.PartialContent(ConnectionBlockSchema)
    19  	if diags.HasErrors() {
    20  		return nil, diags
    21  	}
    22  
    23  	connection := modconfig.NewConnection(block)
    24  
    25  	// decode the plugin property
    26  	// NOTE: this mutates connection to set PluginAlias and possible PluginInstance
    27  	diags = decodeConnectionPluginProperty(connectionContent, connection)
    28  	if diags.HasErrors() {
    29  		return nil, diags
    30  	}
    31  
    32  	if connectionContent.Attributes["type"] != nil {
    33  		var connectionType string
    34  		diags = gohcl.DecodeExpression(connectionContent.Attributes["type"].Expr, nil, &connectionType)
    35  		if diags.HasErrors() {
    36  			return nil, diags
    37  		}
    38  		connection.Type = connectionType
    39  	}
    40  	if connectionContent.Attributes["import_schema"] != nil {
    41  		var importSchema string
    42  		diags = gohcl.DecodeExpression(connectionContent.Attributes["import_schema"].Expr, nil, &importSchema)
    43  		if diags.HasErrors() {
    44  			return nil, diags
    45  		}
    46  		connection.ImportSchema = importSchema
    47  	}
    48  	if connectionContent.Attributes["connections"] != nil {
    49  		var connections []string
    50  		diags = gohcl.DecodeExpression(connectionContent.Attributes["connections"].Expr, nil, &connections)
    51  		if diags.HasErrors() {
    52  			return nil, diags
    53  		}
    54  		connection.ConnectionNames = connections
    55  	}
    56  
    57  	// check for nested options
    58  	for _, connectionBlock := range connectionContent.Blocks {
    59  		switch connectionBlock.Type {
    60  		case "options":
    61  			// if we already found settings, fail
    62  			opts, moreDiags := DecodeOptions(connectionBlock)
    63  			if moreDiags.HasErrors() {
    64  				diags = append(diags, moreDiags...)
    65  				break
    66  			}
    67  			moreDiags = connection.SetOptions(opts, connectionBlock)
    68  			if moreDiags.HasErrors() {
    69  				diags = append(diags, moreDiags...)
    70  			}
    71  
    72  		default:
    73  			// this can never happen
    74  			diags = append(diags, &hcl.Diagnostic{
    75  				Severity: hcl.DiagError,
    76  				Summary:  fmt.Sprintf("connections do not support '%s' blocks", block.Type),
    77  				Subject:  hclhelpers.BlockRangePointer(connectionBlock),
    78  			})
    79  		}
    80  	}
    81  
    82  	// tactical - update when support for options blocks is removed
    83  	// this needs updating to use a single block check
    84  	// at present we do not support blocks for plugin specific connection config
    85  	// so any blocks present in 'rest' are an error
    86  	if hclBody, ok := rest.(*hclsyntax.Body); ok {
    87  		for _, b := range hclBody.Blocks {
    88  			if b.Type != "options" {
    89  				diags = append(diags, &hcl.Diagnostic{
    90  					Severity: hcl.DiagError,
    91  					Summary:  fmt.Sprintf("connections do not support '%s' blocks", b.Type),
    92  					Subject:  hclhelpers.HclSyntaxBlockRangePointer(b),
    93  				})
    94  			}
    95  		}
    96  	}
    97  
    98  	// convert the remaining config to a hcl string to pass to the plugin
    99  	config, moreDiags := hclhelpers.HclBodyToHclString(rest, connectionContent)
   100  	if moreDiags.HasErrors() {
   101  		diags = append(diags, moreDiags...)
   102  	} else {
   103  		connection.Config = config
   104  	}
   105  
   106  	return connection, diags
   107  }
   108  
   109  func decodeConnectionPluginProperty(connectionContent *hcl.BodyContent, connection *modconfig.Connection) hcl.Diagnostics {
   110  	var pluginName string
   111  	evalCtx := &hcl.EvalContext{Variables: make(map[string]cty.Value)}
   112  
   113  	diags := gohcl.DecodeExpression(connectionContent.Attributes["plugin"].Expr, evalCtx, &pluginName)
   114  	res := newDecodeResult()
   115  	res.handleDecodeDiags(diags)
   116  	if res.Diags.HasErrors() {
   117  		return res.Diags
   118  	}
   119  	if len(res.Depends) > 0 {
   120  		log.Printf("[INFO] decodeConnectionPluginProperty plugin property is HCL reference")
   121  		// if this is a plugin reference, extract the plugin instance
   122  		pluginInstance, ok := getPluginInstanceFromDependency(maps.Values(res.Depends))
   123  		if !ok {
   124  			log.Printf("[INFO] failed to resolve plugin property")
   125  			// return the original diagnostics
   126  			return diags
   127  		}
   128  
   129  		// so we have resolved a reference to a plugin config
   130  		// we will validate that this block exists later in initializePlugins
   131  		// set PluginInstance ONLY
   132  		// (the PluginInstance property being set means that we will raise the correct error if we fail to resolve the plugin block)
   133  		connection.PluginInstance = &pluginInstance
   134  		return nil
   135  	}
   136  
   137  	// NOTE: plugin property is set in initializePlugins
   138  	connection.PluginAlias = pluginName
   139  
   140  	return nil
   141  }
   142  
   143  func getPluginInstanceFromDependency(dependencies []*modconfig.ResourceDependency) (string, bool) {
   144  	if len(dependencies) != 1 {
   145  		return "", false
   146  	}
   147  	if len(dependencies[0].Traversals) != 1 {
   148  		return "", false
   149  	}
   150  	traversalString := hclhelpers.TraversalAsString(dependencies[0].Traversals[0])
   151  	split := strings.Split(traversalString, ".")
   152  	if len(split) != 2 || split[0] != "plugin" {
   153  		return "", false
   154  	}
   155  	return split[1], true
   156  }