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 }